urbackup_backend/urbackupserver/ImageMount.cpp

779 lines
22 KiB
C++

#include "ImageMount.h"
#include "../Interface/Server.h"
#include "../Interface/File.h"
#include "../fsimageplugin/IFSImageFactory.h"
#include "../fsimageplugin/IVHDFile.h"
#include "dao/ServerBackupDao.h"
#include "database.h"
#include "../urbackupcommon/os_functions.h"
#include "../stringtools.h"
#include "server_cleanup.h"
#include <assert.h>
#ifdef _WIN32
#include <Windows.h>
#include <Subauth.h>
#include <imdisk.h>
bool os_link_symbolic_junctions_raw(const std::string &target, const std::string &lname, void* transaction);
#else
#include <errno.h>
#endif
std::map<ImageMount::SMountId, size_t> ImageMount::mounted_images;
IMutex* ImageMount::mounted_images_mutex;
std::set<int> ImageMount::locked_images;
IMutex* ImageMount::mount_processes_mutex;
std::map<ImageMount::SMountId, THREADPOOL_TICKET> ImageMount::mount_processes;
extern IFSImageFactory *image_fak;
namespace
{
#ifdef _WIN32
const std::string alt_mount_path = "C:\\UrBackupMounts";
bool os_mount_image(const std::string& path, int backupid, int partnum, IFSImageFactory::SPartition partition, std::string& errmsg)
{
if (path.size() <= 1)
{
return false;
}
std::wstring filename = Server->ConvertToWchar(path);
std::string mpath = "contents";
if (partition.offset >= 0)
{
filename += Server->ConvertToWchar(":" + convert(partition.offset) + "-" + convert(partition.length));
mpath += convert(partnum);
}
std::vector<char> create_data_buf(sizeof(IMDISK_CREATE_DATA) + filename.size() * sizeof(wchar_t));
IMDISK_CREATE_DATA* create_data = reinterpret_cast<IMDISK_CREATE_DATA*>(create_data_buf.data());
create_data->FileNameLength = static_cast<USHORT>(filename.size() * sizeof(wchar_t));
memcpy(create_data->FileName, filename.c_str(), create_data->FileNameLength);
create_data->DeviceNumber = IMDISK_AUTO_DEVICE_NUMBER;
create_data->Flags = IMDISK_TYPE_PROXY | IMDISK_PROXY_TYPE_TCP | IMDISK_OPTION_RO;
HANDLE hDriver = CreateFile(IMDISK_CTL_DOSDEV_NAME,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
0,
NULL);
if (hDriver == INVALID_HANDLE_VALUE)
{
errmsg = "Error communicating with ImDisk driver. " + os_last_error_str();
Server->Log(errmsg, LL_ERROR);
return false;
}
DWORD bytes_returned;
DWORD buffer_size = static_cast<DWORD>(create_data_buf.size());
if (!DeviceIoControl(hDriver, IOCTL_IMDISK_CREATE_DEVICE,
create_data, buffer_size, create_data,
buffer_size, &bytes_returned, NULL))
{
errmsg = "Error creating ImDisk device. " + os_last_error_str();
Server->Log(errmsg, LL_ERROR);
return false;
}
CloseHandle(hDriver);
std::string device_path = Server->ConvertFromWchar(IMDISK_DEVICE_BASE_NAME) +
convert(static_cast<int64>(create_data->DeviceNumber)) + "\\";
std::string mountpoint = ExtractFilePath(path) + os_file_sep() + mpath;
if (!os_link_symbolic_junctions_raw(device_path, mountpoint, NULL))
{
Server->Log("Error creating junction on ImDisk mountpoint at \"" + mountpoint + "\". " + os_last_error_str(), LL_WARNING);
mountpoint = alt_mount_path + os_file_sep() + convert(backupid);
if (!((os_directory_exists(alt_mount_path) || os_create_dir_recursive(alt_mount_path))
&& os_link_symbolic(device_path, mountpoint)))
{
errmsg = "Error creating junction on ImDisk mountpoint. " + os_last_error_str();
Server->Log(errmsg, LL_ERROR);
return false;
}
}
int64 starttime = Server->getTimeMS();
bool has_error = false;
while (getFiles(mountpoint, &has_error).empty()
&& has_error
&& Server->getTimeMS() - starttime < 60 * 1000)
{
Server->wait(1000);
}
if (!getFiles(mountpoint, &has_error).empty()
|| !has_error)
{
return true;
}
errmsg = "Timeout while mounting volume image";
return false;
}
bool os_unmount_image(const std::string& mountpoint, const std::string& path, int backupid, int partnum, std::string& errmsg)
{
std::string target;
if (!os_get_symlink_target(mountpoint, target)
|| target.empty())
{
errmsg = "Error getting mountpoint target. " + os_last_error_str();
Server->Log(errmsg, LL_ERROR);
return false;
}
if (target[target.size() - 1] == '\\')
{
target.erase(target.size() - 1, 1);
}
HANDLE hDevice = CreateFile(Server->ConvertToWchar("\\\\?\\GLOBALROOT" + target).c_str(),
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
0,
NULL);
if (hDevice == INVALID_HANDLE_VALUE)
{
errmsg = "Cannot open ImDisk device " + target + ". " + os_last_error_str();
Server->Log(errmsg, LL_WARNING);
os_remove_symlink_dir(mountpoint);
return false;
}
DWORD bytes_returned;
if (!DeviceIoControl(hDevice, FSCTL_LOCK_VOLUME, NULL, 0, NULL, 0, &bytes_returned, NULL))
{
Server->Log("Locking ImDisk device failed. " + os_last_error_str() + ". Forcing unmount...", LL_WARNING);
DeviceIoControl(hDevice, FSCTL_DISMOUNT_VOLUME, NULL, 0, NULL, 0, &bytes_returned, NULL);
DeviceIoControl(hDevice, FSCTL_LOCK_VOLUME, NULL, 0, NULL, 0, &bytes_returned, NULL);
}
else
{
if (!DeviceIoControl(hDevice, FSCTL_DISMOUNT_VOLUME, NULL, 0, NULL, 0, &bytes_returned, NULL))
{
Server->Log("Dismounting volume failed. " + os_last_error_str(), LL_ERROR);
}
}
if (!DeviceIoControl(hDevice, IOCTL_STORAGE_EJECT_MEDIA, NULL, 0, NULL, 0, &bytes_returned, NULL))
{
Server->Log("Ejecting ImDisk failed. " + os_last_error_str() + ". Forcing removal...", LL_WARNING);
std::string device_number = getafter(Server->ConvertFromWchar(IMDISK_DEVICE_BASE_NAME), target);
HANDLE hDriver = CreateFile(IMDISK_CTL_DOSDEV_NAME,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
0,
NULL);
if (hDriver == INVALID_HANDLE_VALUE)
{
CloseHandle(hDevice);
errmsg = "Error communicating with ImDisk driver (2). " + os_last_error_str();
Server->Log(errmsg, LL_ERROR);
os_remove_symlink_dir(mountpoint);
return false;
}
DWORD deviceNumber = watoi(device_number);
if (!DeviceIoControl(hDriver, IOCTL_IMDISK_REMOVE_DEVICE,
&deviceNumber, sizeof(deviceNumber), NULL,
0, &bytes_returned, NULL))
{
errmsg = "Error removing ImDisk device. " + os_last_error_str();
Server->Log(errmsg, LL_ERROR);
CloseHandle(hDevice);
CloseHandle(hDriver);
os_remove_symlink_dir(mountpoint);
return false;
}
CloseHandle(hDriver);
}
DeviceIoControl(hDevice, FSCTL_UNLOCK_VOLUME, NULL, 0, NULL, 0, &bytes_returned, NULL);
CloseHandle(hDevice);
return os_remove_symlink_dir(mountpoint);
}
#else
bool image_helper_action(const std::string& path, int backupid, const std::string& action, int partnum, IFSImageFactory::SPartition partition, std::string& errmsg)
{
IDatabase* db = Server->getDatabase(Server->getThreadID(), URBACKUPDB_SERVER);
ServerBackupDao backup_dao(db);
ServerBackupDao::CondString clientname = backup_dao.getClientnameByImageid(backupid);
if (!clientname.exists)
{
return false;
}
std::string foldername = ExtractFileName(ExtractFilePath(path));
std::string imagename = ExtractFileName(path);
std::string mount_helper = Server->getServerParameter("mount_helper");
if (mount_helper.empty())
{
mount_helper = "urbackup_mount_helper";
}
std::string cmd = mount_helper + " " + action + " \"" + clientname.value
+ "\" \"" + foldername + "\" \"" + imagename + "\"";
if (partnum >= 0)
{
cmd += " " + convert(partnum);
}
if (partition.offset >= 0)
{
cmd += " " + convert(partition.offset) + " " + convert(partition.length);
}
cmd += " 2>&1";
int rc = os_popen(cmd.c_str(), errmsg);
if (rc != 0)
{
Server->Log("Image mounting failed: "+errmsg, LL_ERROR);
}
return rc == 0;
}
bool os_mount_image(const std::string& path, int backupid, int partnum, IFSImageFactory::SPartition partition, std::string& errmsg)
{
return image_helper_action(path, backupid, "mount", partnum, partition, errmsg);
}
bool os_unmount_image(const std::string& mountpoint, const std::string& path, int backupid, int partnum, std::string& errmsg)
{
return image_helper_action(path, backupid, "umount", partnum, IFSImageFactory::SPartition(), errmsg);
}
#endif
class ScopedLockImage
{
int backupid;
bool locked;
public:
ScopedLockImage(int backupid, int64 timeoutms)
: backupid(backupid)
{
locked = ImageMount::lockImage(backupid, timeoutms);
}
~ScopedLockImage()
{
if (backupid != 0
&& locked)
{
ImageMount::unlockImage(backupid);
}
}
bool has_lock()
{
return locked;
}
};
bool unmount_image(ServerBackupDao& backup_dao, int backupid, int partition, std::string& errmsg)
{
ServerBackupDao::SMountedImage image_inf = backup_dao.getMountedImage(backupid, partition);
if (!image_inf.exists)
{
return false;
}
std::string mpath = "contents";
if (partition >= 0)
{
mpath += convert(partition);
}
std::string mountpoint = ExtractFilePath(image_inf.path) + os_file_sep() + mpath;
#ifdef _WIN32
if (!os_directory_exists(mountpoint))
{
mountpoint = alt_mount_path + os_file_sep() + convert(backupid);
}
if (!os_directory_exists(mountpoint))
{
return false;
}
#else
if (!os_directory_exists(mountpoint) || errno == EACCES || errno == ENOTCONN)
{
mountpoint = ExtractFilePath(image_inf.path) + "_mnt";
if (partition >= 0)
{
mountpoint += convert(partition);
}
}
if (!os_directory_exists(mountpoint) && errno != EACCES && errno != ENOTCONN)
{
return false;
}
#endif
return os_unmount_image(mountpoint, image_inf.path, backupid, partition, errmsg);
}
}
void ImageMount::operator()()
{
mounted_images_mutex = Server->createMutex();
mount_processes_mutex = Server->createMutex();
Server->waitForStartupComplete();
IDatabase* db = Server->getDatabase(Server->getThreadID(), URBACKUPDB_SERVER);
ServerBackupDao backup_dao(db);
int waittimems = 0;
int64 oldtimes = 0;
while (true)
{
Server->wait(waittimems);
std::vector<ServerBackupDao::SMountedImage> old_mounted_images =
backup_dao.getOldMountedImages(oldtimes);
waittimems = 60 * 1000;
oldtimes = 60 * 60;
{
IScopedLock lock(mounted_images_mutex);
for (size_t i = 0; i < old_mounted_images.size(); ++i)
{
if (mounted_images.find(SMountId(old_mounted_images[i].backupid, old_mounted_images[i].partition)) == mounted_images.end()
&& locked_images.find(old_mounted_images[i].backupid) == locked_images.end())
{
locked_images.insert(old_mounted_images[i].backupid);
lock.relock(NULL);
int64 passed_time_s = Server->getTimeSeconds() - old_mounted_images[i].mounttime;
Server->Log("Unmounting mounted image backup id " + convert(old_mounted_images[i].backupid) +
" partition "+convert(old_mounted_images[i].partition)+" path \"" + old_mounted_images[i].path + "\" mounted " + PrettyPrintTime(passed_time_s * 1000) + " ago", LL_INFO);
std::string errmsg;
if (!unmount_image(backup_dao, old_mounted_images[i].backupid, old_mounted_images[i].partition, errmsg) )
{
Server->Log("Unmounting image backup id " + convert(old_mounted_images[i].backupid) +
" path \"" + old_mounted_images[i].path + "\" mounted " + PrettyPrintTime(passed_time_s * 1000)
+ " ago failed: " + errmsg, LL_ERROR);
}
backup_dao.delImageMounted(old_mounted_images[i].id);
lock.relock(mounted_images_mutex);
locked_images.erase(locked_images.find(old_mounted_images[i].backupid));
}
}
}
{
IScopedLock lock(mount_processes_mutex);
for (std::map<SMountId, THREADPOOL_TICKET>::iterator it = mount_processes.begin();
it != mount_processes.end();)
{
std::map<SMountId, THREADPOOL_TICKET>::iterator it_curr = it++;
if (Server->getThreadPool()->waitFor(it_curr->second))
{
mount_processes.erase(it_curr);
}
}
}
}
}
bool ImageMount::mount_image(int backupid, int partition, ScopedMountedImage& mounted_image, int64 timeoutms, bool& has_timeout, std::string& errmsg)
{
has_timeout = false;
ScopedLockImage lock_image(backupid, timeoutms);
if (!lock_image.has_lock())
{
has_timeout = true;
return false;
}
return mount_image_int(backupid, partition, mounted_image, timeoutms, has_timeout, errmsg);
}
namespace
{
class MountImageThread : public IThread
{
int backupid;
std::string& errmsg;
int partition;
public:
MountImageThread(int backupid, int partition, std::string& errmsg)
: backupid(backupid), partition(partition), errmsg(errmsg)
{
}
void operator()()
{
ImageMount::mount_image_thread(backupid, partition, errmsg);
delete this;
}
};
}
bool ImageMount::mount_image_int(int backupid, int partition, ScopedMountedImage& mounted_image,
int64 timeoutms, bool& has_timeout, std::string& errmsg)
{
IScopedLock lock(mount_processes_mutex);
std::map<SMountId, THREADPOOL_TICKET>::iterator it = mount_processes.find(SMountId(backupid, partition));
THREADPOOL_TICKET ticket;
if (it != mount_processes.end())
{
ticket = it->second;
lock.relock(NULL);
}
else
{
ticket = Server->getThreadPool()->execute(new MountImageThread(backupid, partition, errmsg), "mnt image");
mount_processes.insert(std::make_pair(SMountId(backupid, partition), ticket));
lock.relock(NULL);
}
if (Server->getThreadPool()->waitFor(ticket, static_cast<int>(timeoutms)))
{
IScopedLock lock(mount_processes_mutex);
std::map<SMountId, THREADPOOL_TICKET>::iterator it = mount_processes.find(SMountId(backupid, partition) );
if (it != mount_processes.end()
&& it->second == ticket)
{
mount_processes.erase(it);
}
lock.relock(NULL);
IDatabase* db = Server->getDatabase(Server->getThreadID(), URBACKUPDB_SERVER);
ServerBackupDao backup_dao(db);
ServerBackupDao::SMountedImage image_inf = backup_dao.getMountedImage(backupid, partition);
if (image_inf.mounttime != 0)
{
mounted_image.reset(backupid);
}
return image_inf.mounttime!=0;
}
else
{
has_timeout = true;
return false;
}
}
std::string ImageMount::get_mount_path(int backupid, int clientid, int partition, bool do_mount, ScopedMountedImage& mounted_image,
int64 timeoutms, bool& has_timeout, std::string& errmsg)
{
has_timeout = false;
IDatabase* db = Server->getDatabase(Server->getThreadID(), URBACKUPDB_SERVER);
ServerBackupDao backup_dao(db);
ServerBackupDao::SMountedImage image_inf = backup_dao.getImageInfo(backupid);
if (!image_inf.exists
|| image_inf.clientid != clientid)
{
return std::string();
}
ScopedLockImage lock_image(backupid, timeoutms);
if (!lock_image.has_lock())
{
has_timeout = true;
return std::string();
}
image_inf = backup_dao.getMountedImage(backupid, partition);
bool has_mount_process = false;
{
IScopedLock lock(mount_processes_mutex);
std::map<SMountId, THREADPOOL_TICKET>::iterator it = mount_processes.find(SMountId(backupid, partition));
has_mount_process = it != mount_processes.end();
}
bool image_mount_failed = false;
if ( (!image_inf.exists || image_inf.mounttime == 0)
|| has_mount_process)
{
if (do_mount)
{
if (!mount_image_int(backupid, partition, mounted_image, timeoutms, has_timeout, errmsg))
{
image_mount_failed = true;
}
}
else
{
return std::string();
}
}
std::string mpath = "contents";
if (partition >= 0)
{
mpath += convert(partition);
}
ServerBackupDao::SMountedImage image_path;
if (image_inf.exists)
image_path = image_inf;
else
image_path = backup_dao.getImageInfo(backupid);
std::string ret = ExtractFilePath(image_path.path) + os_file_sep() + mpath;
if (os_directory_exists(ret))
{
if (image_mount_failed
&& getFiles(ret).empty())
return std::string();
mounted_image.reset(backupid);
if (image_inf.exists)
backup_dao.updateImageMounted(image_inf.id);
else
backup_dao.addImageMounted(backupid, partition);
return ret;
}
#ifdef _WIN32
ret = alt_mount_path + os_file_sep() + convert(backupid);
if (os_directory_exists(ret))
{
if (image_mount_failed
&& getFiles(ret).empty())
return std::string();
mounted_image.reset(backupid);
if (image_inf.exists)
backup_dao.updateImageMounted(image_inf.id);
else
backup_dao.addImageMounted(backupid, partition);
return ret;
}
#else
ret = ExtractFilePath(image_inf.path) + "_mnt";
if (partition >= 0)
{
ret += convert(partition);
}
if (os_directory_exists(ret))
{
if (image_mount_failed
&& getFiles(ret).empty())
return std::string();
mounted_image.reset(backupid);
if (image_inf.exists)
backup_dao.updateImageMounted(image_inf.id);
else
backup_dao.addImageMounted(backupid, partition);
return ret;
}
#endif
return std::string();
}
bool ImageMount::unmount_images(int backupid)
{
IDatabase* db = Server->getDatabase(Server->getThreadID(), URBACKUPDB_SERVER);
ServerBackupDao backup_dao(db);
std::vector<ServerBackupDao::SMountedImage> old_mounted_images =
backup_dao.getOldMountedImages(0);
IScopedLock lock(mounted_images_mutex);
for (size_t i = 0; i < old_mounted_images.size(); ++i)
{
if (old_mounted_images[i].backupid != backupid)
{
continue;
}
if (mounted_images.find(SMountId(old_mounted_images[i].backupid, old_mounted_images[i].partition)) != mounted_images.end())
{
Server->Log("Image id " + convert(old_mounted_images[i].backupid) +
" partition " + convert(old_mounted_images[i].partition)
+ " currently mounted", LL_INFO);
return false;
}
if (locked_images.find(old_mounted_images[i].backupid) != locked_images.end())
{
Server->Log("Image id " + convert(old_mounted_images[i].backupid) +
" partition " + convert(old_mounted_images[i].partition)
+ " currently locked", LL_INFO);
return false;
}
locked_images.insert(old_mounted_images[i].backupid);
lock.relock(NULL);
int64 passed_time_s = Server->getTimeSeconds() - old_mounted_images[i].mounttime;
Server->Log("Unmounting mounted image backup id " + convert(old_mounted_images[i].backupid) +
" partition " + convert(old_mounted_images[i].partition) + " path \"" + old_mounted_images[i].path + "\" mounted " + PrettyPrintTime(passed_time_s * 1000) + " ago (unmount_images)", LL_INFO);
std::string errmsg;
if (!unmount_image(backup_dao, old_mounted_images[i].backupid, old_mounted_images[i].partition, errmsg))
{
Server->Log("Unmounting image backup id " + convert(old_mounted_images[i].backupid) +
" path \"" + old_mounted_images[i].path + "\" mounted " + PrettyPrintTime(passed_time_s * 1000)
+ " ago failed: " + errmsg + " (unmount_images)", LL_ERROR);
}
backup_dao.delImageMounted(old_mounted_images[i].id);
lock.relock(mounted_images_mutex);
locked_images.erase(locked_images.find(old_mounted_images[i].backupid));
}
return true;
}
void ImageMount::incrImageMounted(int backupid, int partition)
{
IScopedLock lock(mounted_images_mutex);
std::map<SMountId, size_t>::iterator it = mounted_images.find(SMountId(backupid, partition) );
if (it == mounted_images.end())
{
ServerCleanupThread::lockImageFromCleanup(backupid);
}
++mounted_images[SMountId(backupid, partition)];
}
void ImageMount::decrImageMounted(int backupid, int partition)
{
IScopedLock lock(mounted_images_mutex);
std::map<SMountId, size_t>::iterator it = mounted_images.find(SMountId(backupid, partition) );
assert(it != mounted_images.end()
&& it->second > 0);
if (it == mounted_images.end())
{
return;
}
--it->second;
if (it->second == 0)
{
mounted_images.erase(it);
ServerCleanupThread::unlockImageFromCleanup(backupid);
}
}
bool ImageMount::lockImage(int backupid, int64 timeoutms)
{
int64 starttime = Server->getTimeMS();
IScopedLock lock(mounted_images_mutex);
while (locked_images.find(backupid) != locked_images.end())
{
lock.relock(NULL);
if (timeoutms == 0)
return false;
Server->wait(100);
if (timeoutms > 0
&& Server->getTimeMS() - starttime > timeoutms)
{
return false;
}
lock.relock(mounted_images_mutex);
}
locked_images.insert(backupid);
return true;
}
void ImageMount::unlockImage(int backupid)
{
IScopedLock lock(mounted_images_mutex);
locked_images.erase(locked_images.find(backupid));
}
void ImageMount::mount_image_thread(int backupid, int partition, std::string& errmsg)
{
IDatabase* db = Server->getDatabase(Server->getThreadID(), URBACKUPDB_SERVER);
ServerBackupDao backup_dao(db);
ServerBackupDao::SMountedImage image_inf = backup_dao.getImageInfo(backupid);
if (!image_inf.exists
|| backup_dao.getMountedImage(backupid, partition).exists)
{
return;
}
IFSImageFactory::SPartition sel_part;
if (partition >= 0)
{
std::string ext = findextension(image_inf.path);
std::unique_ptr<IVHDFile> vhdfile;
if (ext == "raw")
{
vhdfile.reset(image_fak->createVHDFile(image_inf.path, true, 0, 2 * 1024 * 1024, false, IFSImageFactory::ImageFormat_RawCowFile));
}
else
{
vhdfile.reset(image_fak->createVHDFile(image_inf.path, true, 0));
}
if (vhdfile.get() != NULL)
{
if (!vhdfile->isOpen())
return;
bool gpt_style;
std::vector<IFSImageFactory::SPartition> partitions = image_fak->readPartitions(vhdfile.get(), 0, gpt_style);
if (partition < partitions.size())
{
sel_part = partitions[partition];
}
}
}
backup_dao.addImageMounted(backupid, partition);
int64 insert_id = db->getLastInsertID();
if (!os_mount_image(image_inf.path, backupid, partition, sel_part, errmsg))
{
backup_dao.delImageMounted(insert_id);
return;
}
}