mirror of
https://github.com/uroni/urbackup_backend.git
synced 2025-10-26 11:36:50 +00:00
598 lines
17 KiB
C++
598 lines
17 KiB
C++
/*************************************************************************
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
**************************************************************************/
|
|
|
|
#include "server_dir_links.h"
|
|
#include "../urbackupcommon/os_functions.h"
|
|
#include "../Interface/Server.h"
|
|
#include "../stringtools.h"
|
|
#include "server_settings.h"
|
|
#include "../Interface/Mutex.h"
|
|
#include "../Interface/Database.h"
|
|
#include "../Interface/File.h"
|
|
#include "database.h"
|
|
#include <assert.h>
|
|
|
|
namespace
|
|
{
|
|
void reference_all_sublinks(ServerLinkDao& link_dao, int clientid, const std::string& target, const std::string& new_target)
|
|
{
|
|
std::string escaped_target = escape_glob_sql(target);
|
|
|
|
std::vector<ServerLinkDao::DirectoryLinkEntry> entries = link_dao.getLinksInDirectory(clientid, escaped_target+os_file_sep()+"*");
|
|
|
|
for(size_t i=0;i<entries.size();++i)
|
|
{
|
|
std::string subpath = entries[i].target.substr(target.size());
|
|
std::string new_link_path = new_target + subpath;
|
|
link_dao.addDirectoryLink(clientid, entries[i].name, new_link_path);
|
|
}
|
|
}
|
|
|
|
bool reference_parents(ServerLinkDao& link_dao, int clientid, const std::string& target,
|
|
const std::string& new_pool_name, int depth, bool remove)
|
|
{
|
|
bool ret = true;
|
|
std::string path = target;
|
|
std::string sub_path;
|
|
for (int i = 0; i < depth; ++i)
|
|
{
|
|
sub_path = os_file_sep() + ExtractFileName(path, os_file_sep()) + sub_path;
|
|
path = ExtractFilePath(path, os_file_sep());
|
|
|
|
if (os_is_symlink(os_file_prefix(path)))
|
|
{
|
|
std::string link_src_dir;
|
|
if (!os_get_symlink_target(os_file_prefix(path), link_src_dir))
|
|
{
|
|
Server->Log("Could not get symlink target of source directory \"" + path + "\". (1)", LL_ERROR);
|
|
ret = false;
|
|
continue;
|
|
}
|
|
|
|
std::string pool_name = ExtractFileName(link_src_dir);
|
|
|
|
if (pool_name.empty())
|
|
{
|
|
Server->Log("Error extracting pool name from link source \"" + link_src_dir + "\" (1)", LL_ERROR);
|
|
ret = false;
|
|
continue;
|
|
}
|
|
|
|
std::vector<ServerLinkDao::DirectoryLinkEntry> entries = link_dao.getLinksByPoolName(clientid, pool_name);
|
|
|
|
for (size_t j = 0; j < entries.size(); ++j)
|
|
{
|
|
std::string new_link_path = entries[j].target + sub_path;
|
|
|
|
if (new_link_path == target)
|
|
continue;
|
|
|
|
assert(os_directory_exists(os_file_prefix(new_link_path)));
|
|
if (remove)
|
|
{
|
|
link_dao.removeDirectoryLinkWithName(clientid, new_link_path, new_pool_name);
|
|
}
|
|
else
|
|
{
|
|
link_dao.addDirectoryLink(clientid, new_pool_name, new_link_path);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
IMutex* dir_link_mutex;
|
|
std::map<int, IMutex*> dir_link_client_mutexes;
|
|
}
|
|
|
|
void dir_link_lock_client_mutex(int clientid, IScopedLock& lock)
|
|
{
|
|
IScopedLock lock2(dir_link_mutex);
|
|
|
|
std::map<int, IMutex*>::iterator it = dir_link_client_mutexes.find(clientid);
|
|
if (it != dir_link_client_mutexes.end())
|
|
{
|
|
IMutex* mut = it->second;
|
|
lock2.relock(NULL);
|
|
lock.relock(mut);
|
|
}
|
|
else
|
|
{
|
|
IMutex* mut = Server->createMutex();
|
|
dir_link_client_mutexes.insert(std::make_pair(clientid, mut));
|
|
lock.relock(mut);
|
|
}
|
|
}
|
|
|
|
std::string escape_glob_sql(const std::string& glob)
|
|
{
|
|
std::string ret;
|
|
ret.reserve(glob.size());
|
|
for(size_t i=0;i<glob.size();++i)
|
|
{
|
|
if(glob[i]=='?')
|
|
{
|
|
ret+="[?]";
|
|
}
|
|
else if(glob[i]=='[')
|
|
{
|
|
ret+="[[]";
|
|
}
|
|
else if(glob[i]=='*')
|
|
{
|
|
ret+="[*]";
|
|
}
|
|
else
|
|
{
|
|
ret+=glob[i];
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
bool link_directory_pool( int clientid, const std::string& target_dir, const std::string& src_dir, const std::string& pooldir, bool with_transaction,
|
|
ServerLinkDao*& link_dao, ServerLinkJournalDao*& link_journal_dao, int depth)
|
|
{
|
|
if (link_dao == NULL)
|
|
{
|
|
link_dao=new ServerLinkDao(Server->getDatabase(Server->getThreadID(), URBACKUPDB_SERVER_LINKS));
|
|
}
|
|
|
|
DBScopedSynchronous synchonous_link_journal(NULL);
|
|
|
|
if (!with_transaction && link_journal_dao == NULL)
|
|
{
|
|
IDatabase* link_journal_db = Server->getDatabase(Server->getThreadID(), URBACKUPDB_SERVER_LINK_JOURNAL);
|
|
link_journal_dao = new ServerLinkJournalDao(link_journal_db);
|
|
synchonous_link_journal.reset(link_journal_db);
|
|
}
|
|
|
|
DBScopedSynchronous synchonous_link(link_dao->getDatabase());
|
|
|
|
IScopedLock lock(NULL);
|
|
dir_link_lock_client_mutex(clientid, lock);
|
|
|
|
DBScopedWriteTransaction link_transaction(link_dao->getDatabase());
|
|
|
|
std::string link_src_dir;
|
|
std::string pool_name;
|
|
bool remove_glob=false;
|
|
if(os_is_symlink(os_file_prefix(src_dir)))
|
|
{
|
|
if(!os_get_symlink_target(os_file_prefix(src_dir), link_src_dir))
|
|
{
|
|
Server->Log("Could not get symlink target of source directory \""+src_dir+"\".", LL_ERROR);
|
|
return false;
|
|
}
|
|
|
|
pool_name = ExtractFileName(link_src_dir);
|
|
|
|
if(pool_name.empty())
|
|
{
|
|
Server->Log("Error extracting pool name from link source \""+link_src_dir+"\"", LL_ERROR);
|
|
return false;
|
|
}
|
|
|
|
link_dao->addDirectoryLink(clientid, pool_name, target_dir);
|
|
reference_all_sublinks(*link_dao, clientid, src_dir, target_dir);
|
|
remove_glob=true;
|
|
}
|
|
else if(os_directory_exists(os_file_prefix(src_dir)))
|
|
{
|
|
std::string parent_src_dir;
|
|
do
|
|
{
|
|
pool_name = ServerSettings::generateRandomAuthKey(10)+convert(Server->getTimeSeconds())+convert(Server->getTimeMS());
|
|
parent_src_dir = pooldir + os_file_sep() + pool_name.substr(0, 2);
|
|
link_src_dir = parent_src_dir + os_file_sep() + pool_name;
|
|
} while (os_directory_exists(os_file_prefix(link_src_dir)));
|
|
|
|
if(!os_directory_exists(os_file_prefix(parent_src_dir)) && !os_create_dir_recursive(os_file_prefix(parent_src_dir)))
|
|
{
|
|
Server->Log("Could not create directory for pool directory: \""+parent_src_dir+"\"", LL_ERROR);
|
|
return false;
|
|
}
|
|
|
|
std::vector<int64> parent_ids;
|
|
reference_parents(*link_dao, clientid, src_dir, pool_name, depth, false);
|
|
link_dao->addDirectoryLink(clientid, pool_name, src_dir);
|
|
reference_all_sublinks(*link_dao, clientid, src_dir, target_dir);
|
|
remove_glob = true;
|
|
link_dao->addDirectoryLink(clientid, pool_name, target_dir);
|
|
|
|
|
|
int64 replay_entry_id;
|
|
if(!with_transaction)
|
|
{
|
|
link_journal_dao->addDirectoryLinkJournalEntry(src_dir, link_src_dir);
|
|
replay_entry_id = link_journal_dao->getLastId();
|
|
}
|
|
|
|
link_transaction.end();
|
|
|
|
void* transaction=NULL;
|
|
if(with_transaction)
|
|
{
|
|
transaction=os_start_transaction();
|
|
|
|
if(!transaction)
|
|
{
|
|
Server->Log("Error starting filesystem transaction", LL_ERROR);
|
|
reference_parents(*link_dao, clientid, src_dir, pool_name, depth, true);
|
|
link_dao->removeDirectoryLink(clientid, src_dir);
|
|
link_dao->removeDirectoryLinkGlob(clientid, escape_glob_sql(target_dir) + os_file_sep() + "*");
|
|
link_dao->removeDirectoryLink(clientid, target_dir);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if(!os_rename_file(os_file_prefix(src_dir), os_file_prefix(link_src_dir), transaction))
|
|
{
|
|
Server->Log("Could not rename folder \""+src_dir+"\" to \""+link_src_dir+"\"", LL_ERROR);
|
|
os_finish_transaction(transaction);
|
|
reference_parents(*link_dao, clientid, src_dir, pool_name, depth, true);
|
|
link_dao->removeDirectoryLink(clientid, src_dir);
|
|
link_dao->removeDirectoryLinkGlob(clientid, escape_glob_sql(target_dir) + os_file_sep() + "*");
|
|
link_dao->removeDirectoryLink(clientid, target_dir);
|
|
|
|
if (!with_transaction)
|
|
{
|
|
link_journal_dao->removeDirectoryLinkJournalEntry(replay_entry_id);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool l_isdir = true;
|
|
if(!os_link_symbolic(os_file_prefix(link_src_dir), os_file_prefix(src_dir), transaction, &l_isdir))
|
|
{
|
|
Server->Log("Could not create a symbolic link at \""+src_dir+"\" to \""+link_src_dir+"\"", LL_ERROR);
|
|
os_rename_file(link_src_dir, src_dir, transaction);
|
|
os_finish_transaction(transaction);
|
|
reference_parents(*link_dao, clientid, src_dir, pool_name, depth, true);
|
|
link_dao->removeDirectoryLink(clientid, src_dir);
|
|
link_dao->removeDirectoryLinkGlob(clientid, escape_glob_sql(target_dir) + os_file_sep() + "*");
|
|
link_dao->removeDirectoryLink(clientid, target_dir);
|
|
|
|
if (!with_transaction)
|
|
{
|
|
link_journal_dao->removeDirectoryLinkJournalEntry(replay_entry_id);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
if(with_transaction)
|
|
{
|
|
if(!os_finish_transaction(transaction))
|
|
{
|
|
Server->Log("Error finishing filesystem transaction", LL_ERROR);
|
|
reference_parents(*link_dao, clientid, src_dir, pool_name, depth, true);
|
|
link_dao->removeDirectoryLink(clientid, src_dir);
|
|
link_dao->removeDirectoryLinkGlob(clientid, escape_glob_sql(target_dir) + os_file_sep() + "*");
|
|
link_dao->removeDirectoryLink(clientid, target_dir);
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
link_journal_dao->removeDirectoryLinkJournalEntry(replay_entry_id);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Server->Log("Cannot link directory \"" + target_dir + "\" because source directory \"" + src_dir + "\" does not exist.", LL_DEBUG);
|
|
return false;
|
|
}
|
|
|
|
if(!os_link_symbolic(os_file_prefix(link_src_dir), os_file_prefix(target_dir)))
|
|
{
|
|
Server->Log("Error creating symbolic link from \"" + link_src_dir +"\" to \"" +
|
|
target_dir+"\" -2", LL_ERROR);
|
|
|
|
link_dao->removeDirectoryLink(clientid, target_dir);
|
|
|
|
if(remove_glob)
|
|
{
|
|
link_dao->removeDirectoryLinkGlob(clientid, escape_glob_sql(target_dir)+os_file_sep()+"*");
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool replay_directory_link_journal( )
|
|
{
|
|
IScopedLock lock(dir_link_mutex);
|
|
|
|
ServerLinkJournalDao link_journal_dao(Server->getDatabase(Server->getThreadID(), URBACKUPDB_SERVER_LINK_JOURNAL));
|
|
|
|
std::vector<ServerLinkJournalDao::JournalEntry> journal_entries = link_journal_dao.getDirectoryLinkJournalEntries();
|
|
|
|
bool has_error=false;
|
|
|
|
for(size_t i=0;i<journal_entries.size();++i)
|
|
{
|
|
const ServerLinkJournalDao::JournalEntry& je = journal_entries[i];
|
|
|
|
std::string symlink_real_target;
|
|
|
|
if(!os_is_symlink(je.linkname)
|
|
|| (os_get_symlink_target(je.linkname, symlink_real_target) && symlink_real_target!=je.linktarget) )
|
|
{
|
|
if(os_directory_exists(je.linktarget))
|
|
{
|
|
os_remove_symlink_dir(os_file_prefix(je.linkname));
|
|
if(!os_link_symbolic(os_file_prefix(je.linktarget), os_file_prefix(je.linkname)))
|
|
{
|
|
Server->Log("Error replaying symlink journal: Could not create link at \""+je.linkname+"\" to \""+je.linktarget+"\". "+os_last_error_str(), LL_ERROR);
|
|
has_error=true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
link_journal_dao.removeDirectoryLinkJournalEntries();
|
|
|
|
return has_error;
|
|
}
|
|
|
|
bool remove_directory_link(const std::string & path, ServerLinkDao & link_dao, int clientid,
|
|
std::unique_ptr<DBScopedSynchronous>& synchronous_link_dao, bool with_transaction)
|
|
{
|
|
std::string pool_path;
|
|
if (!os_get_symlink_target(path, pool_path))
|
|
{
|
|
Server->Log("Error getting symlink path in pool of \"" + path + "\"", LL_ERROR);
|
|
return false;
|
|
}
|
|
|
|
std::string pool_name = ExtractFileName(pool_path, os_file_sep());
|
|
|
|
if (pool_name.empty())
|
|
{
|
|
Server->Log("Error extracting pool name from pool path \"" + pool_path + "\"", LL_ERROR);
|
|
return false;
|
|
}
|
|
|
|
std::string directory_pool = ExtractFileName(ExtractFilePath(ExtractFilePath(pool_path, os_file_sep()), os_file_sep()), os_file_sep());
|
|
|
|
if (directory_pool != ".directory_pool")
|
|
{
|
|
//Other symlink. Simply delete
|
|
|
|
if (!os_remove_symlink_dir(os_file_prefix(path)))
|
|
{
|
|
Server->Log("Error removing symlink dir \"" + path + "\"", LL_ERROR);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
std::string target_raw;
|
|
if (next(path, 0, os_file_prefix("")))
|
|
{
|
|
target_raw = path.substr(os_file_prefix("").size());
|
|
}
|
|
else
|
|
{
|
|
target_raw = path;
|
|
}
|
|
|
|
if (with_transaction)
|
|
{
|
|
synchronous_link_dao.reset(new DBScopedSynchronous(link_dao.getDatabase()));
|
|
link_dao.getDatabase()->BeginWriteTransaction();
|
|
}
|
|
|
|
link_dao.removeDirectoryLink(clientid, target_raw);
|
|
|
|
if (link_dao.getLastChanges()>0)
|
|
{
|
|
bool ret = true;
|
|
if (link_dao.getDirectoryRefcount(clientid, pool_name) == 0)
|
|
{
|
|
ret = remove_directory_link_dir(path, link_dao, clientid, false, false);
|
|
ret = ret && os_remove_dir(os_file_prefix(pool_path));
|
|
|
|
if (!ret)
|
|
{
|
|
Server->Log("Error removing directory link \"" + path + "\" with pool path \"" + pool_path + "\"", LL_ERROR);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
link_dao.removeDirectoryLinkGlob(clientid, escape_glob_sql(target_raw) + os_file_sep() + "*");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Server->Log("Directory link \"" + path + "\" with pool path \"" + pool_path + "\" not found in database. Deleting symlink only.", LL_WARNING);
|
|
}
|
|
|
|
|
|
if (!os_remove_symlink_dir(os_file_prefix(path)))
|
|
{
|
|
Server->Log("Error removing symlink dir \"" + path + "\"", LL_ERROR);
|
|
}
|
|
|
|
{
|
|
#ifdef _WIN32
|
|
int mode = MODE_READ_SEQUENTIAL_BACKUP;
|
|
#else
|
|
int mode = MODE_READ;
|
|
#endif
|
|
std::unique_ptr<IFile> dir_f(Server->openFile(os_file_prefix(ExtractFilePath(path, os_file_sep())), mode));
|
|
if (dir_f.get() != NULL)
|
|
{
|
|
dir_f->Sync();
|
|
}
|
|
}
|
|
|
|
if (with_transaction)
|
|
{
|
|
link_dao.getDatabase()->EndTransaction();;
|
|
synchronous_link_dao.reset();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
namespace
|
|
{
|
|
struct SSymlinkCallbackData
|
|
{
|
|
SSymlinkCallbackData(ServerLinkDao* link_dao,
|
|
int clientid, bool with_transaction)
|
|
: link_dao(link_dao), clientid(clientid),
|
|
with_transaction(with_transaction)
|
|
{
|
|
|
|
}
|
|
|
|
ServerLinkDao* link_dao;
|
|
std::unique_ptr<DBScopedSynchronous> synchronous_link_dao;
|
|
int clientid;
|
|
bool with_transaction;
|
|
};
|
|
|
|
bool symlink_callback(const std::string &path, bool* isdir, void* userdata)
|
|
{
|
|
if (isdir!=NULL && !*isdir)
|
|
{
|
|
if (!Server->deleteFile(os_file_prefix(path)))
|
|
{
|
|
Server->Log("Error removing symlink file \"" + path + "\"", LL_ERROR);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
SSymlinkCallbackData* data = reinterpret_cast<SSymlinkCallbackData*>(userdata);
|
|
|
|
return remove_directory_link(path, *data->link_dao, data->clientid,
|
|
data->synchronous_link_dao, data->with_transaction);
|
|
}
|
|
}
|
|
|
|
bool remove_directory_link_dir(const std::string &path, ServerLinkDao& link_dao, int clientid, bool delete_root, bool with_transaction)
|
|
{
|
|
IScopedLock lock(NULL);
|
|
dir_link_lock_client_mutex(clientid, lock);
|
|
|
|
SSymlinkCallbackData userdata(&link_dao, clientid, with_transaction);
|
|
return os_remove_nonempty_dir(os_file_prefix(path), symlink_callback, &userdata, delete_root);
|
|
}
|
|
|
|
bool reference_contained_directory_links(ServerLinkDao& link_dao, int clientid,
|
|
const std::string& pool_name, const std::string &path, const std::string& link_path)
|
|
{
|
|
std::vector<SFile> files = getFiles(link_path + path);
|
|
|
|
std::vector<ServerLinkDao::DirectoryLinkEntry> targets = link_dao.getLinksByPoolName(clientid, pool_name);
|
|
|
|
bool ret = true;
|
|
for (size_t i = 0; i < files.size(); ++i)
|
|
{
|
|
if (files[i].issym)
|
|
{
|
|
std::string sympath = link_path + path + os_file_sep() + files[i].name;
|
|
std::string pool_path;
|
|
if (!os_get_symlink_target(sympath, pool_path))
|
|
{
|
|
Server->Log("Error getting symlink path in pool of \"" + link_path + path + "\" (contains)", LL_ERROR);
|
|
continue;
|
|
}
|
|
|
|
std::string curr_pool_name = ExtractFileName(pool_path, os_file_sep());
|
|
|
|
if (curr_pool_name.empty())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
std::string directory_pool = ExtractFileName(ExtractFilePath(ExtractFilePath(pool_path, os_file_sep()), os_file_sep()), os_file_sep());
|
|
|
|
if (directory_pool != ".directory_pool")
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for (size_t j = 0; j < targets.size(); ++j)
|
|
{
|
|
std::string ref_target = targets[j].target + path + os_file_sep() + files[i].name;
|
|
if (link_dao.getDirectoryRefcountWithTarget(clientid, curr_pool_name, ref_target) == 0)
|
|
{
|
|
Server->Log("Adding missing directory reference of pool " + curr_pool_name + " to \"" + ref_target + "\"", LL_WARNING);
|
|
link_dao.addDirectoryLink(clientid, curr_pool_name, ref_target);
|
|
}
|
|
}
|
|
|
|
if (!reference_contained_directory_links(link_dao, clientid,
|
|
curr_pool_name, std::string(), pool_path))
|
|
{
|
|
ret = false;
|
|
}
|
|
}
|
|
else if (files[i].isdir)
|
|
{
|
|
if (!reference_contained_directory_links(link_dao, clientid,
|
|
pool_name, path + os_file_sep() + files[i].name, link_path))
|
|
{
|
|
ret = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool is_directory_link(const std::string & path)
|
|
{
|
|
int ftype = os_get_file_type(os_file_prefix(path));
|
|
|
|
if ( (ftype & EFileType_Directory) == 0
|
|
|| (ftype & EFileType_Symlink) == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
std::string symlink_target;
|
|
if (!os_get_symlink_target(os_file_prefix(path), symlink_target))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
std::string directory_pool = ExtractFileName(ExtractFilePath(ExtractFilePath(symlink_target, os_file_sep()), os_file_sep()), os_file_sep());
|
|
|
|
return directory_pool == ".directory_pool";
|
|
}
|
|
|
|
void init_dir_link_mutex()
|
|
{
|
|
dir_link_mutex=Server->createMutex();
|
|
}
|
|
|
|
void destroy_dir_link_mutex()
|
|
{
|
|
Server->destroy(dir_link_mutex);
|
|
}
|