/************************************************************************* * 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) { db_results res_clients=db->Read("SELECT id FROM clients"); for(size_t i=0;iPrepare("SELECT value FROM settings_db.settings WHERE clientid=? AND key=?"); q_get->Bind(clientid); q_get->Bind("group_id"); db_results res = q_get->Read(); q_get->Reset(); if (!res.empty()) { group_id = watoi(res[0]["value"])*-1; } q_get->Bind(clientid); q_get->Bind("overwrite"); res=q_get->Read(); q_get->Reset(); if(res.empty() || res[0]["value"]!="true") r_clientid= group_id; q_get->Bind(clientid); q_get->Bind("overwrite_archive_settings"); res=q_get->Read(); q_get->Reset(); if(res.empty() || res[0]["value"]!="true") r_clientid= group_id; bool archive_settings_copied=false; q_get->Bind(clientid); q_get->Bind("archive_settings_copied"); res=q_get->Read(); if(!res.empty() && res[0]["value"]=="true") archive_settings_copied=true; if(r_clientid<=0 && !archive_settings_copied) { copyArchiveSettings(r_clientid, clientid); } IQuery *q_get_archived=db->Prepare("SELECT id, next_archival, interval, length, backup_types, archive_window, letters FROM settings_db.automatic_archival WHERE clientid=?"); q_get_archived->Bind(clientid); db_results res_archived=q_get_archived->Read(); 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") { IQuery *q_get_letters = db->Prepare("SELECT DISTINCT letter FROM backup_images WHERE complete=1 AND archived=0 AND clientid=?"); q_get_letters->Bind(clientid); db_results res = q_get_letters->Read(); 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 ""; } void ServerAutomaticArchive::copyArchiveSettings(int source_id, int clientid) { db_results res_all=db->Read("SELECT id, next_archival, interval, interval_unit, length, length_unit, backup_types, archive_window, letters FROM settings_db.automatic_archival WHERE clientid="+convert(source_id)); std::vector next_archivals; for(size_t i=0;iPrepare("SELECT next_archival FROM settings_db.automatic_archival WHERE clientid=? AND interval=? AND length=? AND backup_types=? AND archive_window=? AND letters=?"); IQuery *q_num=db->Prepare("SELECT count(*) AS num FROM settings_db.automatic_archival WHERE clientid="+convert(source_id)+" AND interval=? AND length=? AND backup_types=? AND archive_window=? AND letters=? AND idBind(interval); q_num->Bind(length); q_num->Bind(backup_types); q_num->Bind(archive_window); q_num->Bind(letters); q_num->Bind(id); db_results res_num=q_num->Read(); int num=watoi(res_num[0]["num"]); q_next->Bind(clientid); q_next->Bind(interval); q_next->Bind(length); q_next->Bind(backup_types); q_next->Bind(archive_window); q_next->Bind(letters); db_results res_next=q_next->Read(); if((size_t)numgetTimeSeconds()); } } next_archivals.push_back(next_archival); } IQuery *q_del_all=db->Prepare("DELETE FROM settings_db.automatic_archival WHERE clientid=?"); 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 (?,?,?,?,?,?,?,?,?)"); q_del_all->Bind(clientid); q_del_all->Write(); for(size_t i=0;iBind(next_archivals[i]); q_insert_all->Bind(interval); q_insert_all->Bind(res_all[i]["interval_unit"]); q_insert_all->Bind(length); q_insert_all->Bind(res_all[i]["length_unit"]); q_insert_all->Bind(backup_types); q_insert_all->Bind(clientid); q_insert_all->Bind(archive_window); q_insert_all->Bind(letters); q_insert_all->Write(); q_insert_all->Reset(); } IQuery *q_del_copied=db->Prepare("DELETE FROM settings_db.settings WHERE key='archive_settings_copied' AND clientid=?"); q_del_copied->Bind(clientid); q_del_copied->Write(); q_del_copied->Reset(); IQuery *q_insert_copied=db->Prepare("INSERT INTO settings_db.settings (key, value, clientid) VALUES ('archive_settings_copied','true',?)"); q_insert_copied->Bind(clientid); q_insert_copied->Write(); q_insert_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); }