/************************************************************************* * UrBackup - Client/Server backup system * Copyright (C) 2021 Martin Raiber * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . **************************************************************************/ #include "LocalBackup.h" #include "../Interface/Mutex.h" #include "../Interface/Condition.h" #include "../stringtools.h" #include "ClientService.h" #include "../common/data.h" #include "../clouddrive/IClouddriveFactory.h" #include "FilesystemManager.h" #include "client.h" #include #include #include #include using namespace std::chrono_literals; extern IClouddriveFactory* clouddrive_fak; namespace { class BackupUpdaterThread : public IThread { public: BackupUpdaterThread(bool file, bool incr, int64 local_process_id, int64 backup_id, int64 status_id, std::string server_token) : stopped(false), curr_pc(-1), backup_id(backup_id), status_id(status_id), server_token(server_token), local_process_id(local_process_id) { SRunningProcess new_proc; new_proc.id = local_process_id; new_proc.action = file ? ( incr ? RUNNING_INCR_FILE : RUNNING_FULL_FILE) : (incr ? RUNNING_INCR_IMAGE : RUNNING_FULL_IMAGE); new_proc.server_id = status_id; new_proc.server_token = server_token; ClientConnector::addNewProcess(new_proc); } void operator()() { { std::unique_lock lock(update_mutex); while (!stopped) { ClientConnector::updateLocalBackupPc(local_process_id, backup_id, status_id, curr_pc, server_token, std::string(), total_bytes, done_bytes, speed_bpms, eta, eta_set_time); speed_set = false; update_cond.wait_for(lock, 10s); if (!speed_set) speed_bpms = 0; } } ClientConnector::updateRestorePc(local_process_id, backup_id, status_id, 101, server_token, std::string(), -1, total_bytes, success ? total_bytes : -2, 0); delete this; } void stop() { std::lock_guard lock(update_mutex); stopped = true; update_cond.notify_all(); } void update_pc(int new_pc, int64 p_total_bytes, int64 p_done_bytes) { std::lock_guard lock(update_mutex); curr_pc = new_pc; total_bytes = p_total_bytes; done_bytes = p_done_bytes; update_cond.notify_all(); } void update_details(const std::string& details) { std::lock_guard lock(update_mutex); curr_details = details; update_cond.notify_all(); } void update_speed(double n_speed_bpms) { std::lock_guard lock(update_mutex); speed_bpms = n_speed_bpms; speed_set = true; update_cond.notify_all(); } void set_success(bool b) { std::lock_guard lock(update_mutex); success = b; } void update_eta(int64 p_eta, int64 p_eta_set_time) { std::lock_guard lock(update_mutex); eta = p_eta; eta_set_time = p_eta_set_time; update_cond.notify_all(); } void update(int new_pc, int64 p_total_bytes, int64 p_done_bytes, double n_speed_bpms, int64 p_eta, int64 p_eta_set_time) { std::lock_guard lock(update_mutex); curr_pc = new_pc; total_bytes = p_total_bytes; done_bytes = p_done_bytes; speed_bpms = n_speed_bpms; eta = p_eta; eta_set_time = p_eta_set_time; speed_set = true; update_cond.notify_all(); } private: std::mutex update_mutex; std::condition_variable update_cond; bool stopped; int curr_pc; std::string curr_details; int64 backup_id; int64 status_id; std::string server_token; int64 local_process_id; int64 total_bytes = -1; int64 done_bytes = 0; double speed_bpms = 0; bool success = false; int64 eta = 0; int64 eta_set_time = 0; bool speed_set = false; }; } LocalBackup::LocalBackup(bool file, bool incr, int64 local_process_id, int64 server_log_id, int64 server_status_id, int64 backupid, std::string server_token, std::string server_identity, int facet_id, size_t max_backups, const std::string& dest_url, const std::string& dest_url_params, const str_map& dest_secret_params) : file(file), incr(incr), backup_updater_thread(nullptr), server_log_id(server_log_id), server_status_id(server_status_id), backupid(backupid), server_token(std::move(server_token)), server_identity(std::move(server_identity)), local_process_id(local_process_id), facet_id(facet_id), max_backups(max_backups), dest_url(dest_url), dest_url_params(dest_url_params), dest_secret_params(dest_secret_params) { } bool LocalBackup::onStartBackup() { if (!openFileSystem()) return false; backup_updater_thread = new BackupUpdaterThread(file, incr, local_process_id, backupid, server_status_id, server_token); Server->getThreadPool()->execute(backup_updater_thread, "bck update"); return true; } void LocalBackup::onBackupFinish(bool image) { log("Cleaning up old backups...", LL_INFO); if (!cleanupOldBackups(image)) { log("Cleaning up old backups failed", LL_ERROR); backup_success = false; } if (backup_success && clouddrive_fak != nullptr && clouddrive_fak->isCloudFile(backup_files->getBackingFile())) { log("Waiting for upload to finish...", LL_INFO); while (linecount(clouddrive_fak->getCfNumDirtyItems(backup_files->getBackingFile())) > 2) { Server->wait(1000); } log("Calculating background delete data...", LL_INFO); int64 transid = clouddrive_fak->getCfTransid(backup_files->getBackingFile()); if (!clouddrive_fak->runBackgroundWorker(backup_files->getBackingFile(), "urbackup/bg_del_" + conv_filename(server_token) + "_" + std::to_string(transid) + "_"+std::to_string(backupid)+".dat")) { log("Error generating background delete data", LL_ERROR); backup_success = false; } } if (!ClientConnector::localBackupDone(server_log_id, server_status_id, backupid, backup_success, server_token)) { log("Error notifying server about local backup success", LL_WARNING); } log("Backup finished.", LL_INFO); updateProgressSuccess(backup_success); backup_updater_thread->stop(); backup_updater_thread = nullptr; int64 log_send_starttime = Server->getTimeMS(); while (Server->getTimeMS() - log_send_starttime<3*60*1000) { if (sendLogBuffer()) { break; } else { Server->wait(5000); } } } void LocalBackup::updateProgressPc(int new_pc, int64 p_total_bytes, int64 p_done_bytes) { backup_updater_thread->update_pc(new_pc, p_total_bytes, p_done_bytes); } void LocalBackup::updateProgressDetails(const std::string& details) { backup_updater_thread->update_details(details); } void LocalBackup::updateProgressSuccess(bool b) { backup_updater_thread->set_success(b); } void LocalBackup::updateProgressSpeed(double n_speed_bpms) { backup_updater_thread->update_speed(n_speed_bpms); } void LocalBackup::prepareBackupFiles(const std::string& prefix) { backup_files = std::make_unique(orig_backup_files.release(), prefix + "\\"); } bool LocalBackup::createSymlink(const std::string& name, size_t depth, const std::string& symlink_target, const std::string& dir_sep, bool isdir) { std::vector toks; Tokenize(symlink_target, toks, dir_sep); std::string target; for (size_t i = 0; i < depth; ++i) { target += ".." + os_file_sep(); } for (size_t i = 0; i < toks.size(); ++i) { std::set emptyset; std::string emptypath; std::string component = fixFilenameForOS(toks[i]); if (component == ".." || component == ".") continue; target += component; if (i + 1 < toks.size()) { target += os_file_sep(); } } if (toks.empty() && isdir) { target += ".symlink_void_dir"; } if (toks.empty() && !isdir) { target += ".symlink_void_file"; } //return backup_files->createSymlink(target, os_file_prefix(name), NULL, &isdir); return false; } bool LocalBackup::sync() { if (!backup_files->sync(std::string())) { log("Error syncing backup file system -2", LL_ERROR); return false; } IFile* backing_file = backup_files->getBackingFile(); if (!clouddrive_fak->flush(backing_file, true)) { log("Error syncing backup file system backing file", LL_ERROR); return false; } return true; } bool LocalBackup::sendLogBuffer() { size_t send_ok = std::string::npos; for (size_t i = 0; i < log_buffer.size(); ++i) { if (ClientConnector::tochannelLog(server_log_id, log_buffer[i].first, log_buffer[i].second, server_token, 0)) { send_ok = i; } } if (send_ok != std::string::npos) { log_buffer.erase(log_buffer.begin(), log_buffer.begin() + send_ok + 1); } return log_buffer.empty(); } void LocalBackup::log(const std::string& msg, int loglevel) { Server->Log(msg, loglevel); if (!sendLogBuffer() || !ClientConnector::tochannelLog(server_log_id, msg, loglevel, server_token, 0)) { if (log_buffer.size() > 100) { log_buffer.erase(log_buffer.begin()); } log_buffer.push_back(std::make_pair(msg, loglevel)); } } void LocalBackup::updateProgress(int64 ctime) { int pc_nv = (std::min)(100, (int)(((float)(file_done_bytes+ done_bytes)) / ((float)total_bytes / 100.f) + 0.5f)); std::string details; calculateBackupSpeed(ctime); calculateEta(ctime); backup_updater_thread->update(pc_nv, total_bytes, file_done_bytes + done_bytes, curr_speed_bpms, curr_eta, eta_set_time); } void LocalBackup::logIndexResult() { CWData data; data.addChar(IndexThread::IndexThreadAction_GetLog); unsigned int result_id = IndexThread::getResultId(); data.addUInt(result_id); IndexThread::getMsgPipe()->Write(data.getDataPtr(), data.getDataSize()); std::string ret; IndexThread::getResult(result_id, 8000, ret); IndexThread::removeResult(result_id); if (!ret.empty()) { std::vector lines; Tokenize(ret, lines, "\n"); for (std::string& line : lines) { size_t f1 = line.find("-"); size_t f2 = line.find("-", f1 + 1); int ll = watoi(line.substr(0, f1)); int64 times = watoi64(line.substr(f1+1, f2 - f1)); log(line.substr(f2 + 1), ll); } } } bool LocalBackup::cleanupOldBackups(bool image) { if (!backup_files) return false; std::vector backups = backup_files->root()->listFiles(std::string()); size_t n_backups = 0; for (SFile& backup: backups) { if (backup.isdir && backup.name.find("Image") == std::string::npos && !image) ++n_backups; else if (backup.isdir && backup.name.find("Image") != std::string::npos && image) ++n_backups; } bool did_cleanup = false; while (n_backups > max_backups) { for (SFile& backup : backups) { bool del_backup = false; if (backup.isdir && backup.name.find("Image") == std::string::npos && !image) del_backup = true; else if (backup.isdir && backup.name.find("Image") != std::string::npos && image) del_backup = true; if (del_backup) { if (!backup_files->root()->deleteSubvol(backup.name)) { log("Error deleting subvol \"" + backup.name + "\". " + backup_files->lastError(), LL_ERROR); return false; } did_cleanup = true; --n_backups; break; } } backups = backup_files->root()->listFiles(std::string()); } if (did_cleanup) { if (!backup_files->root()->sync(std::string())) { log("Error syncing fs after cleanup. " + backup_files->lastError(), LL_ERROR); return false; } } return true; } bool LocalBackup::openFileSystem() { if (!FilesystemManager::openFilesystemImage(dest_url, dest_url_params, dest_secret_params)) { log("Error opening destination file system "+dest_url, LL_ERROR); return false; } orig_backup_files.reset(FilesystemManager::getFileSystem(dest_url)); if (!orig_backup_files) { log("Error getting destination file system " + dest_url, LL_ERROR); return false; } return true; } void LocalBackup::calculateBackupSpeed(int64 ctime) { if (speed_set_time == 0) { speed_set_time = ctime; } if (ctime - speed_set_time > 10000) { int64 received_data_bytes = file_done_bytes + done_bytes; int64 new_bytes = received_data_bytes - last_speed_received_bytes; int64 passed_time = ctime - speed_set_time; if (passed_time > 0) { speed_set_time = ctime; double speed_bpms = static_cast(new_bytes) / passed_time; if (last_speed_received_bytes > 0) { curr_speed_bpms = speed_bpms; } last_speed_received_bytes = received_data_bytes; } } } void LocalBackup::calculateEta(int64 ctime) { last_eta_update = ctime; int64 received_data_bytes = file_done_bytes + done_bytes; int64 new_bytes = received_data_bytes - last_eta_received_bytes; int64 passed_time = Server->getTimeMS() - eta_set_time; if (passed_time > 0) { eta_set_time = Server->getTimeMS(); double speed_bpms = static_cast(new_bytes) / passed_time; if (eta_estimated_speed == 0) { eta_estimated_speed = speed_bpms; } else { eta_estimated_speed = eta_estimated_speed * 0.9 + speed_bpms * 0.1; } if (last_eta_received_bytes > 0 && eta_estimated_speed > 0) { curr_eta = static_cast((total_bytes - received_data_bytes) / eta_estimated_speed + 0.5); } last_eta_received_bytes = received_data_bytes; } } bool LocalBackup::PrefixedBackupFiles::hasError() { return backup_files->hasError(); } IFsFile* LocalBackup::PrefixedBackupFiles::openFile(const std::string& path, int mode) { return backup_files->openFile(prefix + path, mode); } bool LocalBackup::PrefixedBackupFiles::reflinkFile(const std::string& source, const std::string& dest) { return backup_files->reflinkFile(source, prefix+dest); } bool LocalBackup::PrefixedBackupFiles::createDir(const std::string& path) { return backup_files->createDir(prefix + path); } bool LocalBackup::PrefixedBackupFiles::deleteFile(const std::string& path) { return backup_files->deleteFile(prefix + path); } int LocalBackup::PrefixedBackupFiles::getFileType(const std::string& path) { return backup_files->getFileType(prefix+path); } bool LocalBackup::PrefixedBackupFiles::renameToFinal() { return backup_files->rename(prefix.substr(0, prefix.size() - 1), prefix.substr(0, prefix.size() - 5)); } bool LocalBackup::PrefixedBackupFiles::sync(const std::string& path) { return backup_files->sync(prefix+path); } bool LocalBackup::PrefixedBackupFiles::deleteSubvol(const std::string& path) { return backup_files->deleteSubvol(prefix+path); } int64 LocalBackup::PrefixedBackupFiles::totalSpace() { return backup_files->totalSpace(); } int64 LocalBackup::PrefixedBackupFiles::freeSpace() { return backup_files->freeSpace(); } int64 LocalBackup::PrefixedBackupFiles::freeMetadataSpace() { return backup_files->freeMetadataSpace(); } int64 LocalBackup::PrefixedBackupFiles::unallocatedSpace() { return backup_files->unallocatedSpace(); } bool LocalBackup::PrefixedBackupFiles::forceAllocMetadata() { return backup_files->forceAllocMetadata(); } bool LocalBackup::PrefixedBackupFiles::balance(int usage, size_t limit, bool metadata, bool& enospc, size_t& relocated) { return backup_files->balance(usage, limit, metadata, enospc, relocated); } std::string LocalBackup::PrefixedBackupFiles::fileSep() { return backup_files->fileSep(); } std::string LocalBackup::PrefixedBackupFiles::filePath(IFile* f) { return backup_files->filePath(f); } bool LocalBackup::PrefixedBackupFiles::getXAttr(const std::string& path, const std::string& key, std::string& value) { return backup_files->getXAttr(prefix + path, key, value); } bool LocalBackup::PrefixedBackupFiles::setXAttr(const std::string& path, const std::string& key, const std::string& val) { return backup_files->setXAttr(prefix + path, key, val); } std::string LocalBackup::PrefixedBackupFiles::getName() { return prefix + "|" + backup_files->getName(); } IFile* LocalBackup::PrefixedBackupFiles::getBackingFile() { return backup_files->getBackingFile(); } std::string LocalBackup::PrefixedBackupFiles::lastError() { return std::string(); } std::vector LocalBackup::PrefixedBackupFiles::getChunks() { return backup_files->getChunks(); } bool LocalBackup::PrefixedBackupFiles::copyFile(const std::string& src, const std::string& dst, bool flush, std::string* error_str) { return backup_files->copyFile(prefix+src, prefix+dst, flush, error_str); } bool LocalBackup::PrefixedBackupFiles::removeDirRecursive(const std::string& path) { return backup_files->removeDirRecursive(prefix+path); } bool LocalBackup::PrefixedBackupFiles::directoryExists(const std::string& path) { return backup_files->directoryExists(prefix+path); } bool LocalBackup::PrefixedBackupFiles::linkSymbolic(const std::string& target, const std::string& lname) { return backup_files->linkSymbolic(prefix+target, prefix+lname); } std::vector LocalBackup::PrefixedBackupFiles::listFiles(const std::string& path) { return backup_files->listFiles(prefix + path); } bool LocalBackup::PrefixedBackupFiles::createSubvol(const std::string& path) { return backup_files->createSubvol(prefix + path); } bool LocalBackup::PrefixedBackupFiles::createSnapshot(const std::string& src_path, const std::string& dest_path) { return backup_files->createSnapshot(prefix+src_path, prefix+dest_path); } bool LocalBackup::PrefixedBackupFiles::rename(const std::string& src_name, const std::string& dest_name) { return backup_files->rename(prefix+src_name, prefix+dest_name); }