/************************************************************************* * UrBackup - Client/Server backup system * Copyright (C) 2011-2017 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 "copy_storage.h" #include "dao/ServerBackupDao.h" #include "dao/ServerCleanupDao.h" #include "../stringtools.h" #include "../urbackupcommon/os_functions.h" #ifdef NO_EMBEDDED_LMDB #include #else #include "lmdb/lmdb.h" #endif #include "../Interface/Server.h" #include "../Interface/Mutex.h" #include "database.h" #include "server_dir_links.h" #include "server_log.h" #include "server_status.h" #if defined(_WIN32) || defined(__APPLE__) || defined(__FreeBSD__) #define stat64 stat #endif #ifndef _WIN32 #include #include #include #include #else #include #endif namespace { std::string getBackupfolder(IDatabase *db) { db_results res = db->Read("SELECT value FROM settings_db.settings WHERE key='backupfolder' AND clientid=0"); if (!res.empty()) { return res[0]["value"]; } return std::string(); } void deleteIncomplete(const std::string& dest_folder) { std::vector clients = getFiles(dest_folder); for (size_t i = 0; i < clients.size(); ++i) { if (!clients[i].isdir) continue; std::vector backups = getFiles(dest_folder + os_file_sep() + clients[i].name); for (size_t j = 0; j < backups.size(); ++j) { if (backups[j].name.find("_incomplete") != std::string::npos) { if (backups[j].isdir) { Server->Log("Deleting incomplete folder \"" + dest_folder + os_file_sep() + clients[i].name + os_file_sep() + backups[j].name + "\"..."); os_remove_nonempty_dir(os_file_prefix(dest_folder + os_file_sep() + clients[i].name + os_file_sep() + backups[j].name)); } else { Server->Log("Deleting incomplete file \"" + dest_folder + os_file_sep() + clients[i].name + os_file_sep() + backups[j].name+"\"..."); Server->deleteFile(os_file_prefix(dest_folder + os_file_sep() + clients[i].name + os_file_sep() + backups[j].name)); } } if (backups[j].name == ".directory_pool") { std::vector pool1 = getFiles(dest_folder + os_file_sep() + clients[i].name+os_file_sep()+ backups[j].name); for (size_t k = 0; k < pool1.size(); ++k) { std::vector pool2 = getFiles(dest_folder + os_file_sep() + clients[i].name + os_file_sep() + backups[j].name +os_file_sep()+pool1[k].name); for (size_t l = 0; l < pool2.size(); ++l) { if (pool2[l].name.find("_incomplete") != std::string::npos) { Server->Log("Deleting incomplete symlink pool folder \"" + dest_folder + os_file_sep() + clients[i].name + os_file_sep() + backups[j].name + os_file_sep() + pool1[k].name + os_file_sep() + pool2[l].name + "\"..."); os_remove_nonempty_dir(os_file_prefix(dest_folder + os_file_sep() + clients[i].name + os_file_sep() + backups[j].name + os_file_sep() + pool1[k].name + os_file_sep() + pool2[l].name)); } } } } } } } bool openMdb(const std::string& dst_folder, MDB_env*& env) { if (!os_directory_exists(dst_folder + os_file_sep() + "inode_db")) { if (!os_create_dir(dst_folder + os_file_sep() + "inode_db")) { Server->Log("Error creating directory \"" + dst_folder + os_file_sep() + "inode_db" + "\". " + os_last_error_str(), LL_ERROR); return false; } } int rc = mdb_env_create(&env); if(rc!=0) { Server->Log("Error creating mdb env (" + (std::string)mdb_strerror(rc)+")", LL_ERROR); return false; } rc = mdb_env_set_maxreaders(env, 4094); if (rc) { Server->Log("LMDB: Failed to set max readers (" + (std::string)mdb_strerror(rc) + ")", LL_ERROR); mdb_env_close(env); return false; } uint64 envsize = 1ULL * 1024 * 1024 * 1024 * 1024; //1TB int64 freespace = os_free_space(dst_folder); if (freespace > 0 && static_cast(freespace)(0.9*freespace); } rc = mdb_env_set_mapsize(env, envsize); if (rc) { Server->Log("LMDB: Failed to set map size (" + (std::string)mdb_strerror(rc) + ")", LL_ERROR); mdb_env_close(env); return false; } unsigned int flags = MDB_NOSUBDIR | MDB_NOMETASYNC; rc = mdb_env_open(env, (dst_folder + os_file_sep() + "inode_db" + os_file_sep() + "inode_db.lmdb").c_str(), flags, 0664); if (rc) { Server->Log("LMDB: Failed to open LMDB database file (" + (std::string)mdb_strerror(rc) + ")", LL_ERROR); mdb_env_close(env); return false; } return true; } class ScopedCloseLmdbEnv { public: ScopedCloseLmdbEnv(MDB_env* env) : env(env) {} ~ScopedCloseLmdbEnv() { mdb_env_close(env); } private: MDB_env* env; }; bool getFileInode(const std::string& fpath, int64& inode) { #ifndef _WIN32 struct stat64 statbuf; int rc = stat64(fpath.c_str(), &statbuf); if (rc != 0) { Server->Log("Error with stat of " + fpath + " errorcode: " + convert(errno), LL_ERROR); return false; } inode = statbuf.st_ino; return true; #else HANDLE hFile = CreateFileW(Server->ConvertToWchar(os_file_prefix(fpath)).c_str(), FILE_READ_ATTRIBUTES, FILE_SHARE_WRITE | FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); if (hFile == INVALID_HANDLE_VALUE) { Server->Log("Error opening file " + fpath + ". "+os_last_error_str(), LL_ERROR); return false; } BY_HANDLE_FILE_INFORMATION fileInformation; BOOL b = GetFileInformationByHandle(hFile, &fileInformation); CloseHandle(hFile); if(!b) { Server->Log("Error getting file information of " + fpath + ". " + os_last_error_str(), LL_ERROR); return false; } LARGE_INTEGER li; li.HighPart = fileInformation.nFileIndexHigh; li.LowPart = fileInformation.nFileIndexLow; inode = li.QuadPart; return true; #endif } std::string remove_incomplete_folder(const std::string& path) { std::vector toks; Tokenize(path, toks, os_file_sep()); std::string ret; std::string inc_str = "_incomplete"; for (size_t i = 0; i < toks.size(); ++i) { if (i != 0) { ret += os_file_sep(); } if (toks[i].size() > inc_str.size() + 1 && toks[i].find(inc_str) == toks[i].size() - inc_str.size()) { ret += toks[i].substr(0, toks[i].size() - inc_str.size()); } else { ret += toks[i]; } } return ret; } bool copy_filebackup(const std::string& src_folder, const std::string& dst_folder, const std::string& pool_dest, MDB_txn* txn, MDB_dbi dbi, bool ignore_copy_errors) { bool has_error = false; std::vector files = getFiles(os_file_prefix(src_folder), &has_error); if (has_error) { Server->Log("Error listing files is directory \"" + src_folder + "\". " + os_last_error_str(), LL_ERROR); return false; } for (size_t i = 0; i < files.size(); ++i) { if (files[i].issym) { std::string sym_target; if (!os_get_symlink_target(os_file_prefix(src_folder + os_file_sep() + files[i].name), sym_target)) { Server->Log("Error getting symlink target of \"" + src_folder + os_file_sep() + files[i].name + "\". " + os_last_error_str(), LL_ERROR); return false; } std::string directory_pool = ExtractFileName(ExtractFilePath(ExtractFilePath(sym_target, os_file_sep()), os_file_sep()), os_file_sep()); if (directory_pool == ".directory_pool") { std::string pool_path = pool_dest + os_file_sep() + ExtractFileName(ExtractFilePath(sym_target, os_file_sep()), os_file_sep()) + os_file_sep() + ExtractFileName(sym_target, os_file_sep()); if (!os_directory_exists(pool_path) && !os_directory_exists(pool_path + "_incomplete")) { if (!os_create_dir_recursive(pool_path + "_incomplete")) { Server->Log("Error creating pool path \"" + pool_path + "_incomplete\". "+os_last_error_str(), LL_ERROR); return false; } if (!os_directory_exists(os_file_prefix(sym_target))) { Server->Log("Directory pool path target \"" + sym_target + "\" does not exist", LL_ERROR); if (!ignore_copy_errors) { return false; } } else { if (!copy_filebackup(sym_target, pool_path + "_incomplete", pool_dest, txn, dbi, ignore_copy_errors)) { return false; } //os_sync(pool_path + "_incomplete"); if (!os_rename_file(pool_path + "_incomplete", pool_path, NULL)) { Server->Log("Error renaming to \"" + pool_path + "\". " + os_last_error_str(), LL_ERROR); return false; } } } } bool isdir = files[i].isdir; if (!os_link_symbolic(sym_target, os_file_prefix(dst_folder + os_file_sep() + files[i].name), NULL, &isdir)) { Server->Log("Error creating symlink at \"" + dst_folder + os_file_sep() + files[i].name + "\". " + os_last_error_str(), LL_ERROR); return false; } } else if (files[i].isdir) { if (!os_create_dir(os_file_prefix(dst_folder + os_file_sep() + files[i].name))) { Server->Log("Error creating folder \"" + dst_folder + os_file_sep() + files[i].name + "\". "+os_last_error_str(), LL_ERROR); return false; } if (!copy_filebackup(src_folder + os_file_sep() + files[i].name, dst_folder + os_file_sep() + files[i].name, pool_dest, txn, dbi, ignore_copy_errors)) { return false; } } else { int64 inode; if (!getFileInode(src_folder + os_file_sep() + files[i].name, inode)) { Server->Log("Error getting inode of file " + src_folder + os_file_sep() + files[i].name, LL_ERROR); return false; } MDB_val mdb_tkey; mdb_tkey.mv_data = static_cast(&inode); mdb_tkey.mv_size = sizeof(inode); MDB_val mdb_tval; int rc = mdb_get(txn, dbi, &mdb_tkey, &mdb_tval); if (rc == 0) { std::string hl_source(reinterpret_cast(mdb_tval.mv_data), mdb_tval.mv_size); if (os_get_file_type(os_file_prefix(hl_source))==0) { hl_source = remove_incomplete_folder(hl_source); } if (!os_create_hardlink(os_file_prefix(dst_folder + os_file_sep() + files[i].name), os_file_prefix(hl_source), false, NULL)) { Server->Log("Error creating hard link at \"" + src_folder + os_file_sep() + files[i].name+"\" to \""+ hl_source+"\"", LL_ERROR); return false; } } else if (rc == MDB_NOTFOUND) { std::string hl_source = dst_folder + os_file_sep() + files[i].name; std::string error_str; if (!copy_file(os_file_prefix(src_folder + os_file_sep() + files[i].name), os_file_prefix(hl_source), false, &error_str)) { Server->Log("Error copying file from \"" + src_folder + os_file_sep() + files[i].name + "\" to \"" + dst_folder + os_file_sep() + files[i].name + "\". " + error_str, LL_ERROR); if (!ignore_copy_errors) { return false; } } mdb_tval.mv_data = &hl_source[0]; mdb_tval.mv_size = hl_source.size(); rc = mdb_put(txn, dbi, &mdb_tkey, &mdb_tval, 0); if (rc != 0) { Server->Log("LMDB: mdb_put failed (" + (std::string)mdb_strerror(rc) + ")", LL_ERROR); return false; } } else { Server->Log("LMDB: mdb_get failed (" + (std::string)mdb_strerror(rc) + ")", LL_ERROR); return false; } } } return true; } bool copy_folder_contents(const std::string& src, const std::string& dst) { std::vector files = getFiles(src); for (size_t i = 0; i < files.size(); ++i) { if (files[i].isdir) { Server->Log("Found folder " + src + os_file_sep() + files[i].name, LL_ERROR); return false; } std::string error_str; if (!copy_file(src + os_file_sep() + files[i].name, dst + os_file_sep() + files[i].name, false, &error_str)) { Server->Log("Error copying \"" + src + os_file_sep() + files[i].name + "\" to \"" + dst + os_file_sep() + files[i].name + "\"", LL_ERROR); return false; } } return true; } bool copy_with_ext(std::string src, std::string dst, std::string ext) { if (!FileExists(src + ext)) { return true; } std::string error_str; if (!copy_file(src + ext, dst + ext, false, &error_str)) { Server->Log("Error copying \"" + src +ext + "\" to \"" + dst +ext + "\"", LL_ERROR); return false; } return true; } bool rename_with_ext(std::string dst_incomplete, std::string dst, std::string ext) { Server->deleteFile(dst + ext); if(!os_rename_file(dst_incomplete +ext, dst+ext)) { Server->Log("Error renaming \"" + dst_incomplete + ext + "\" to \"" + dst + ext + "\"", LL_ERROR); return false; } return true; } bool copy_image_backup(const std::string& src, const std::string& dst_folder) { std::string src_name_incomplete = ExtractFileName(src)+"_incomplete"; if (!copy_with_ext(src, dst_folder + os_file_sep() + src_name_incomplete, "")) { return false; } if (!copy_with_ext(src, dst_folder + os_file_sep() + src_name_incomplete, ".mbr")) { return false; } if (!copy_with_ext(src, dst_folder + os_file_sep() + src_name_incomplete, ".hash")) { return false; } if (!copy_with_ext(src, dst_folder + os_file_sep() + src_name_incomplete, ".bitmap")) { return false; } if (!copy_with_ext(src, dst_folder + os_file_sep() + src_name_incomplete, ".cbitmap")) { return false; } if (!copy_with_ext(src, dst_folder + os_file_sep() + src_name_incomplete, ".sync")) { return false; } //os_sync(dst_folder); std::string src_name = ExtractFileName(src); if (FileExists(dst_folder + os_file_sep() + src_name_incomplete) && !rename_with_ext(dst_folder + os_file_sep() + src_name_incomplete, dst_folder + os_file_sep() + src_name, ".mbr")) { return false; } if (FileExists(dst_folder + os_file_sep() + src_name_incomplete+".hash") && !rename_with_ext(dst_folder + os_file_sep() + src_name_incomplete, dst_folder + os_file_sep() + src_name, ".hash")) { return false; } if (FileExists(dst_folder + os_file_sep() + src_name_incomplete + ".bitmap") && !rename_with_ext(dst_folder + os_file_sep() + src_name_incomplete, dst_folder + os_file_sep() + src_name, ".bitmap")) { return false; } if (FileExists(dst_folder + os_file_sep() + src_name_incomplete + ".cbitmap") && !rename_with_ext(dst_folder + os_file_sep() + src_name_incomplete, dst_folder + os_file_sep() + src_name, ".cbitmap")) { return false; } if (FileExists(dst_folder + os_file_sep() + src_name_incomplete + ".sync") && !rename_with_ext(dst_folder + os_file_sep() + src_name_incomplete, dst_folder + os_file_sep() + src_name, ".sync")) { return false; } if (!rename_with_ext(dst_folder + os_file_sep() + src_name_incomplete, dst_folder + os_file_sep() + src_name, "")) { return false; } return true; } void update_process_pcdone(size_t n_backups, size_t processed_backups, ScopedProcess& storage_migration) { if (n_backups > 0) { int pcdone = (int)((processed_backups*100.f) / n_backups + 0.5f); ServerStatus::setProcessPcDone(std::string(), storage_migration.getStatusId(), pcdone); } } } int copy_storage(const std::string& dest_folder, bool ignore_copy_errors) { logid_t logid = ServerLogger::getLogId(LOG_CATEGORY_CLEANUP); ScopedProcess storage_migration(std::string(), sa_storage_migration, std::string(), logid, false, LOG_CATEGORY_CLEANUP); IDatabase *db = Server->getDatabase(Server->getThreadID(), URBACKUPDB_SERVER); if (db == NULL) { ServerLogger::Log(logid, "Could not open files database", LL_ERROR); return 1; } std::string backupfolder = getBackupfolder(db); if (backupfolder.empty()) { ServerLogger::Log(logid, "Error getting backupfolder", LL_ERROR); return 1; } ServerLogger::Log(logid, "Deleting incomplete transfers..."); deleteIncomplete(dest_folder); MDB_env* env = NULL; if(!openMdb(dest_folder, env)) { ServerLogger::Log(logid, "Error opening inode db", LL_ERROR); return 1; } ScopedCloseLmdbEnv close_env(env); ServerBackupDao backup_dao(db); ServerCleanupDao cleanup_dao(db); std::vector clientids = backup_dao.getClientIds(); size_t n_backups = 0; for (size_t i = 0; i < clientids.size(); ++i) { ServerCleanupDao::CondString clientname = cleanup_dao.getClientName(clientids[i]); if (!clientname.exists) { continue; } std::vector file_backups = cleanup_dao.getFileBackupsOfClient(clientids[i]); for (size_t j = 0; j < file_backups.size(); ++j) { if (file_backups[j].done == 0) { continue; } if (os_directory_exists(os_file_prefix(dest_folder + os_file_sep() + clientname.value + os_file_sep() + file_backups[j].path))) { continue; } if (!os_directory_exists(os_file_prefix(backupfolder + os_file_sep() + clientname.value + os_file_sep() + file_backups[j].path))) { continue; } ++n_backups; } std::vector image_backups = cleanup_dao.getImageBackupsOfClient(clientids[i]); for (size_t j = 0; j < image_backups.size(); ++j) { ServerCleanupDao::SImageBackupInfo& ibackup = image_backups[j]; if (ibackup.complete == 0) { continue; } if (!FileExists(ibackup.path)) { continue; } std::string ibackup_name = ExtractFileName(ExtractFilePath(ibackup.path)); if (ibackup_name.find("Image_") != std::string::npos) { std::string ibackup_dest = dest_folder + os_file_sep() + clientname.value + os_file_sep() + ibackup_name; if (!os_directory_exists(ibackup_dest)) { ++n_backups; } } else { std::string dst_name = dest_folder + os_file_sep() + clientname.value + os_file_sep() + ExtractFileName(ibackup.path); if (!FileExists(dst_name)) { ++n_backups; } } } } ServerStatus::setProcessPcDone(std::string(), storage_migration.getStatusId(), 0); size_t processed_backups = 0; for (size_t i = 0; i < clientids.size(); ++i) { ServerCleanupDao::CondString clientname = cleanup_dao.getClientName(clientids[i]); if (!clientname.exists) { continue; } if (!os_directory_exists(dest_folder + os_file_sep() + clientname.value) && !os_create_dir(dest_folder + os_file_sep() + clientname.value)) { ServerLogger::Log(logid, "Error creating folder \"" + dest_folder + os_file_sep() + clientname.value + "\". "+os_last_error_str(), LL_ERROR); return 1; } ServerLogger::Log(logid, "Copying backups of client \"" + clientname.value + "\"...", LL_INFO); std::vector file_backups = cleanup_dao.getFileBackupsOfClient(clientids[i]); for (size_t j = 0; j < file_backups.size(); ++j) { if (file_backups[j].done == 0) { continue; } if (os_directory_exists(os_file_prefix(dest_folder + os_file_sep() + clientname.value + os_file_sep() + file_backups[j].path))) { continue; } if (!os_directory_exists(os_file_prefix(backupfolder + os_file_sep() + clientname.value + os_file_sep() + file_backups[j].path))) { ServerLogger::Log(logid, "Backup id " + convert(file_backups[j].id) + " path " + file_backups[j].path + " of client \"" + clientname.value + "\" does not exist on current storage. Skipping.", LL_WARNING); continue; } IScopedLock client_lock(NULL); dir_link_lock_client_mutex(clientids[i], client_lock); ServerLogger::Log(logid, "Copying backup id " + convert(file_backups[j].id) + " path " + file_backups[j].path + " of client \"" + clientname.value + "\"...", LL_INFO); MDB_txn* txn = NULL; int rc = mdb_txn_begin(env, NULL, 0, &txn); if (rc) { ServerLogger::Log(logid, "LMDB: Failed to open transaction handle (" + (std::string)mdb_strerror(rc) + ")", LL_ERROR); return 1; } MDB_dbi dbi; rc = mdb_dbi_open(txn, NULL, 0, &dbi); if (rc) { ServerLogger::Log(logid, "LMDB: Failed to open database (" + (std::string)mdb_strerror(rc) + ")", LL_ERROR); mdb_txn_abort(txn); return 1; } std::string pool_dest = dest_folder + os_file_sep() + clientname.value + os_file_sep() + ".directory_pool"; if (!os_create_dir(dest_folder + os_file_sep() + clientname.value + os_file_sep() + file_backups[j].path + "_incomplete")) { ServerLogger::Log(logid, "Error creating folder \""+ dest_folder + os_file_sep() + clientname.value + os_file_sep() + file_backups[j].path + "_incomplete\". "+os_last_error_str(), LL_ERROR); mdb_txn_abort(txn); return 1; } if (!copy_filebackup(backupfolder + os_file_sep() + clientname.value + os_file_sep() + file_backups[j].path, dest_folder + os_file_sep() + clientname.value + os_file_sep() + file_backups[j].path+"_incomplete", pool_dest, txn, dbi, ignore_copy_errors)) { ServerLogger::Log(logid, "Copying backup id " + convert(file_backups[j].id) + " path " + file_backups[j].path + " of client \"" + clientname.value + "\" failed.", LL_ERROR); mdb_txn_abort(txn); continue; } //os_sync(dest_folder + os_file_sep() + clientname.value + os_file_sep() + file_backups[j].path + "_incomplete"); if (!os_rename_file(os_file_prefix(dest_folder + os_file_sep() + clientname.value + os_file_sep() + file_backups[j].path + "_incomplete"), os_file_prefix(dest_folder + os_file_sep() + clientname.value + os_file_sep() + file_backups[j].path))) { ServerLogger::Log(logid, "Error renaming folder after copying. " + os_last_error_str(), LL_ERROR); mdb_txn_abort(txn); return 1; } rc = mdb_txn_commit(txn); if (rc) { ServerLogger::Log(logid, "LMDB: mdb_txn_commit failed (" + (std::string)mdb_strerror(rc) + ")", LL_ERROR); return 1; } ++processed_backups; update_process_pcdone(n_backups, processed_backups, storage_migration); } std::vector image_backups = cleanup_dao.getImageBackupsOfClient(clientids[i]); for (size_t j = 0; j < image_backups.size(); ++j) { ServerCleanupDao::SImageBackupInfo& ibackup = image_backups[j]; if (ibackup.complete == 0) { continue; } if (!FileExists(ibackup.path)) { ServerLogger::Log(logid, "Image backup id " + convert(image_backups[j].id) + " path " + image_backups[j].path + " of client \"" + clientname.value + "\" does not exist on current storage. Skipping.", LL_WARNING); continue; } std::string ibackup_name = ExtractFileName(ExtractFilePath(ibackup.path)); if (ibackup_name.find("Image_") != std::string::npos) { std::string ibackup_dest = dest_folder + os_file_sep() + clientname.value + os_file_sep() + ibackup_name; if (os_directory_exists(ibackup_dest)) { continue; } ServerLogger::Log(logid, "Copying image backup id " + convert(image_backups[j].id) + " path " + image_backups[j].path + " (whole parent folder) of client \"" + clientname.value + "\"...", LL_INFO); if (!os_create_dir(ibackup_dest + "_incomplete")) { ServerLogger::Log(logid, "Error creating folder \"" + ibackup_dest+"_incomplete" + "\". "+os_last_error_str(), LL_ERROR); return 1; } if (!copy_folder_contents(ExtractFilePath(ibackup.path), ibackup_dest + "_incomplete")) { ServerLogger::Log(logid, "Copying image backup id " + convert(image_backups[j].id) + " path " + image_backups[j].path + " of client \"" + clientname.value + "\" failed.", LL_ERROR); continue; } if (!os_rename_file(os_file_prefix(ibackup_dest + "_incomplete"), os_file_prefix(ibackup_dest))) { ServerLogger::Log(logid, "Error renaming image folder after copying. " + os_last_error_str(), LL_ERROR); return 1; } ++processed_backups; update_process_pcdone(n_backups, processed_backups, storage_migration); } else { std::string dst_name = dest_folder + os_file_sep() + clientname.value + os_file_sep() + ExtractFileName(ibackup.path); if (FileExists(dst_name)) { continue; } ServerLogger::Log(logid, "Copying image backup id " + convert(image_backups[j].id) + " path " + image_backups[j].path + " of client \"" + clientname.value + "\"...", LL_INFO); if (!copy_image_backup(ibackup.path, dest_folder + os_file_sep() + clientname.value)) { ServerLogger::Log(logid, "Copying image backup id " + convert(image_backups[j].id) + " path " + image_backups[j].path + " of client \"" + clientname.value + "\" failed.", LL_ERROR); continue; } } } } ServerLogger::Log(logid, "Storage migration successfully finished.", LL_INFO); return 0; }