urbackup_backend/urbackupserver/server_dir_links.cpp

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);
}