/************************************************************************* * UrBackup - Client/Server backup system * Copyright (C) 2011-2016 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 "server_archive.h" #include "../Interface/Server.h" #include "../Interface/Database.h" #include "database.h" #include "../stringtools.h" #include "../urbackupcommon/os_functions.h" #include "dao/ServerCleanupDao.h" #include "ClientMain.h" #include #include ICondition *ServerAutomaticArchive::cond=NULL; IMutex *ServerAutomaticArchive::mutex=NULL; volatile bool ServerAutomaticArchive::do_quit=false; void ServerAutomaticArchive::operator()(void) { Server->waitForStartupComplete(); db=Server->getDatabase(Server->getThreadID(), URBACKUPDB_SERVER); while(!do_quit) { archiveTimeoutFileBackups(); archiveTimeoutImageBackups(); archiveBackups(); Server->clearDatabases(Server->getThreadID()); IScopedLock lock(mutex); cond->wait(&lock, 60*60*1000); } delete this; } void ServerAutomaticArchive::archiveTimeoutFileBackups() { IQuery *q_timeout=db->Prepare("SELECT id FROM backups WHERE archived=1 AND archive_timeout<>0 AND archive_timeoutBind(Server->getTimeSeconds()); db_results res_timeout=q_timeout->Read(); IQuery *q_unarchive=db->Prepare("UPDATE backups SET archived=0 WHERE id=?"); if(q_unarchive==NULL) return; for(size_t i=0;iBind(res_timeout[i]["id"]); q_unarchive->Write(); q_unarchive->Reset(); } } void ServerAutomaticArchive::archiveTimeoutImageBackups() { IQuery *q_timeout = db->Prepare("SELECT id FROM backup_images WHERE archived=1 AND archive_timeout<>0 AND archive_timeoutBind(Server->getTimeSeconds()); db_results res_timeout = q_timeout->Read(); IQuery *q_unarchive = db->Prepare("UPDATE backup_images SET archived=0 WHERE id=?"); if (q_unarchive == NULL) return; ServerCleanupDao cleanupdao(db); for (size_t i = 0; i tounarchive; int backupid = watoi(res_timeout[i]["id"]); tounarchive.push_back(backupid); std::vector assoc_images = cleanupdao.getAssocImageBackups(backupid); tounarchive.insert(tounarchive.end(), assoc_images.begin(), assoc_images.end()); assoc_images = cleanupdao.getAssocImageBackupsReverse(backupid); tounarchive.insert(tounarchive.end(), assoc_images.begin(), assoc_images.end()); for (size_t j = 0; j < tounarchive.size(); ++j) { q_unarchive->Bind(tounarchive[j]); q_unarchive->Write(); q_unarchive->Reset(); } } } void ServerAutomaticArchive::archiveBackups(void) { IQuery *q_get_setting = db->Prepare("SELECT value FROM settings_db.settings WHERE clientid=? AND key=?"); IQuery *q_get_archived = db->Prepare("SELECT id, next_archival, interval, length, backup_types, archive_window, letters FROM settings_db.automatic_archival WHERE clientid=?"); IQuery *q_get_letters = db->Prepare("SELECT DISTINCT letter FROM backup_images WHERE complete=1 AND archived=0 AND clientid=?"); db_results res_clients=db->Read("SELECT id FROM clients"); for(size_t i=0;iBind(clientid); q_get_setting->Bind("archive_update"); db_results res= q_get_setting->Read(); q_get_setting->Reset(); if (!res.empty() && res[0]["value"] == "1") { updateArchiveSettings(clientid); } q_get_archived->Bind(clientid); db_results res_archived=q_get_archived->Read(); q_get_archived->Reset(); for(size_t j=0;jgetTimeSeconds(); if(next_archival 0; std::vector letters; if (image) { std::string letter_str = res_archived[j]["letters"]; if (strlower(trim(letter_str)) == "all") { q_get_letters->Bind(clientid); db_results res = q_get_letters->Read(); q_get_letters->Reset(); for (size_t k = 0; k < res.size(); ++k) { if (!res[k]["letter"].empty() && res[k]["letter"]!="SYSVOL" && res[k]["letter"]!="ESP" ) { letters.push_back(res[k]["letter"]); } } } else { Tokenize(letter_str, letters, ",;"); for (size_t k = 0; k < res.size();) { if (letters[k].empty()) { letters.erase(letters.begin() + k); continue; } letters[k] = ClientMain::normalizeVolumeUpper(letters[k]); ++k; } } } else { letters.push_back(std::string()); } for (size_t k = 0; k < letters.size(); ++k) { int backupid = getNonArchivedBackup(backup_types, clientid, letters[k]); if (backupid != 0) { int length = watoi(res_archived[j]["length"]); archiveBackup(backupid, length, image); Server->Log("Archived backup with id=" + convert(backupid) + " image=" + convert(image)+" letter="+letters[k]+ " for " + convert(length) + " seconds", LL_INFO); updateInterval(watoi(res_archived[j]["id"]), watoi(res_archived[j]["interval"])); } else { Server->Log("Did not find backup suitable for archiving with backup_type=" + convert(watoi(res_archived[j]["backup_types"])) + " image=" + convert(image)+" letter="+letters[k], LL_INFO); } } } } } } void ServerAutomaticArchive::updateInterval(int archiveid, int interval) { IQuery *q_update_interval=db->Prepare("UPDATE settings_db.automatic_archival SET next_archival=? WHERE id=?"); if(interval>0) { interval-=60; } q_update_interval->Bind(Server->getTimeSeconds()+interval); q_update_interval->Bind(archiveid); q_update_interval->Write(); } int ServerAutomaticArchive::getNonArchivedBackup(int backup_types, int clientid, const std::string& letter) { int type_incr = backup_type_incr_file; int type_full = backup_type_full_file; if (!letter.empty()) { type_incr = backup_type_incr_image; type_full = backup_type_full_image; } std::string incremental; if(backup_types & type_full && backup_types & type_incr) incremental=""; else if( backup_types & type_incr ) incremental=" AND incremental<>0"; else if( backup_types & type_full) incremental=" AND incremental=0"; std::string tbl = "backups"; if (!letter.empty()) { tbl = "backup_images"; incremental += " AND letter=?"; } IQuery *q_get_backups=db->Prepare("SELECT id FROM "+tbl+" WHERE complete=1 AND archived=0 AND clientid=?"+incremental+" ORDER BY backuptime DESC LIMIT 1"); q_get_backups->Bind(clientid); if (!letter.empty()) { q_get_backups->Bind(letter); } db_results res=q_get_backups->Read(); if(!res.empty()) return watoi(res[0]["id"]); else return 0; } void ServerAutomaticArchive::archiveBackup(int backupid, int length, bool image) { std::vector toarchive; toarchive.push_back(backupid); std::string tbl = "backups"; if (image) { tbl = "backup_images"; ServerCleanupDao cleanupdao(db); std::vector assoc_images = cleanupdao.getAssocImageBackups(backupid); toarchive.insert(toarchive.end(), assoc_images.begin(), assoc_images.end()); assoc_images = cleanupdao.getAssocImageBackupsReverse(backupid); toarchive.insert(toarchive.end(), assoc_images.begin(), assoc_images.end()); } for (size_t i = 0; i < toarchive.size(); ++i) { IQuery *q_archive = db->Prepare("UPDATE " + tbl + " SET archived=1, archive_timeout=? WHERE id=?"); if (length != -1) { q_archive->Bind(Server->getTimeSeconds() + length); } else { q_archive->Bind(-1); } q_archive->Bind(toarchive[i]); q_archive->Write(); q_archive->Reset(); } } int ServerAutomaticArchive::getBackupTypes(const std::string &backup_type_name) { int type=0; if (backup_type_name == "incr_file") type |= backup_type_incr_file; else if (backup_type_name == "full_file") type |= backup_type_full_file; else if (backup_type_name == "file") type |= backup_type_incr_file | backup_type_full_file; else if (backup_type_name == "image") type |= backup_type_full_image | backup_type_incr_image; else if (backup_type_name == "incr_image") type |= backup_type_incr_image; else if (backup_type_name == "full_image") type |= backup_type_full_image; return type; } std::string ServerAutomaticArchive::getBackupType(int backup_types) { if ( (backup_types & backup_type_full_file) && (backup_types & backup_type_incr_file) ) return "file"; else if (backup_types & backup_type_full_file) return "full_file"; else if (backup_types & backup_type_incr_file) return "incr_file"; else if ( (backup_types & backup_type_full_image) && (backup_types & backup_type_incr_image) ) return "image"; else if (backup_types & backup_type_full_image) return "full_image"; else if (backup_types & backup_type_incr_image) return "incr_image"; return ""; } namespace { bool nextArchiveIdx(str_map params, std::string& prefix, int& i, std::string& idx) { if (params.find("every_" + prefix + convert(i)) == params.end()) { if (prefix == "d") prefix = "g"; else if (prefix == "g") prefix = "c"; else if (prefix == "c") return false; i = 0; if (params.find("every_" + prefix + convert(i)) == params.end()) { idx = prefix + convert(i); return true; } else { return nextArchiveIdx(params, prefix, i, idx); } } idx = prefix + convert(i); return true; } struct SArchival { int64 next; int64 every; int64 archive_for; int backup_types; std::string every_unit; std::string for_unit; std::string window; std::string letters; std::string uuid; }; } void ServerAutomaticArchive::updateArchiveSettings(int clientid) { ServerSettings settings(db, clientid); str_map params; ParseParamStrHttp(settings.getSettings()->archive, ¶ms); IQuery *q_next = db->Prepare("SELECT next_archival FROM settings_db.automatic_archival WHERE clientid=? AND uuid=?"); IQuery *q_insert_all = db->Prepare("INSERT INTO settings_db.automatic_archival (next_archival, interval, interval_unit, length, length_unit, backup_types, clientid, archive_window, letters)" "VALUES (?,?,?,?,?,?,?,?,?)"); std::string prefix = "d"; std::string idx; std::vector new_settings; for (int i = 0; nextArchiveIdx(params, prefix, i, idx); ++i) { SArchival archive; archive.every = watoi64(params["every_" + idx]); archive.archive_for = watoi64(params["for_" + idx]); std::string backup_type_str = params["backup_type_" + idx]; archive.backup_types = ServerAutomaticArchive::getBackupTypes(backup_type_str); archive.window = params["window_" + idx]; archive.letters = params["window_" + idx]; archive.every_unit = params["every_unit_" + idx]; archive.for_unit = params["for_unit_" + idx]; archive.uuid = hexToBytes(params["uuid_" + idx]); q_next->Bind(clientid); q_next->Bind(archive.uuid); db_results res_next = q_next->Read(); q_next->Reset(); archive.next = 0; if (!res_next.empty()) { archive.next = watoi64(res_next[0]["next_archival"]); } if (archive.next == 0) { archive.next = Server->getTimeSeconds(); } new_settings.push_back(archive); } DBScopedWriteTransaction trans(db); IQuery *q_del_all = db->Prepare("DELETE FROM settings_db.automatic_archival WHERE clientid=?"); q_del_all->Bind(clientid); q_del_all->Write(); for (size_t i = 0; i < new_settings.size(); ++i) { SArchival& archive = new_settings[i]; q_insert_all->Bind(archive.next); q_insert_all->Bind(archive.every); q_insert_all->Bind(archive.every_unit); q_insert_all->Bind(archive.archive_for); q_insert_all->Bind(archive.for_unit); q_insert_all->Bind(archive.backup_types); q_insert_all->Bind(clientid); q_insert_all->Bind(archive.window); q_insert_all->Bind(archive.letters); q_insert_all->Write(); q_insert_all->Reset(); } IQuery *q_del_copied=db->Prepare("DELETE FROM settings_db.settings WHERE key='archive_update' AND clientid=?"); q_del_copied->Bind(clientid); q_del_copied->Write(); q_del_copied->Reset(); } bool ServerAutomaticArchive::isInArchiveWindow(const std::string &window_def) { std::vector toks; Tokenize(window_def, toks, ";"); bool matched_dom=false; bool has_dom = false; bool matched_dow = false; for(size_t i=0;i stoks; Tokenize(toks[i], stoks, ","); std::vector nums; for(size_t j=0;jnotify_all(); } void ServerAutomaticArchive::initMutex(void) { mutex=Server->createMutex(); cond=Server->createCondition(); } void ServerAutomaticArchive::destroyMutex(void) { Server->destroy(mutex); Server->destroy(cond); }