270 lines
8.9 KiB
C++
270 lines
8.9 KiB
C++
|
#include <map>
|
||
|
#include "file.h"
|
||
|
#include "logger.h"
|
||
|
#include "config.h"
|
||
|
#ifndef WIN32
|
||
|
#include <stdio.h>
|
||
|
#endif
|
||
|
|
||
|
using namespace std;
|
||
|
#define EXPERIMENTAL_FS
|
||
|
#ifdef EXPERIMENTAL_FS
|
||
|
#include <experimental/filesystem>
|
||
|
#include <iostream>
|
||
|
|
||
|
namespace fs = std::experimental::filesystem;
|
||
|
#else
|
||
|
#include <filesystem>
|
||
|
namespace fs = std::filesystem;
|
||
|
#endif
|
||
|
using namespace log_helper;
|
||
|
|
||
|
std::map<std::string, std::string> move_back;
|
||
|
std::map<std::string, std::string> backup_mapping;
|
||
|
|
||
|
bool rename_or_move(const fs::path& source, const fs::path& target, std::string& error) {
|
||
|
try {
|
||
|
fs::rename(source, target);
|
||
|
return true;
|
||
|
} catch(const fs::filesystem_error& ex) {
|
||
|
#ifndef WIN32
|
||
|
if(ex.code() == errc::cross_device_link) {
|
||
|
/* try with move command */
|
||
|
char buffer[2048];
|
||
|
auto length = snprintf(buffer, 2048, R"(%s "%s" "%s")", "mv", source.c_str(), target.c_str()); /* build the move command */
|
||
|
if(length < 1 || length >= 2049) {
|
||
|
error = "failed to prepare move command!";
|
||
|
return false;
|
||
|
}
|
||
|
auto code = system(buffer);
|
||
|
if(code != 0) {
|
||
|
error = "move command resulted in " + to_string(code);
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
#endif
|
||
|
error = "rename returned error code " + to_string(ex.code().value()) + " (" + ex.code().message() + ")";
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool file::move(std::string &error, const std::string &source, const std::string &target) {
|
||
|
auto source_path = fs::u8path(source);
|
||
|
if(!fs::exists(source_path)) {
|
||
|
error = "source file does not exists";
|
||
|
return false;
|
||
|
}
|
||
|
source_path = fs::absolute(source_path);
|
||
|
|
||
|
if(target.empty() || target == "/dev/null") {
|
||
|
/* delete the file or directory */
|
||
|
|
||
|
logger::debug("Deleting file " + argument_t<fs::path::value_type>::value, source_path.c_str());
|
||
|
if(config::backup) {
|
||
|
auto backup_target = file::register_backup(source_path.string());
|
||
|
if(backup_target.empty()) {
|
||
|
drop_backup(backup_target);
|
||
|
error = "failed to register backup";
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if(!rename_or_move(source_path, fs::u8path(backup_target), error)) {
|
||
|
drop_backup(backup_target);
|
||
|
error = "failed to move file to backup target (" + error + ")";
|
||
|
return false;
|
||
|
}
|
||
|
} else {
|
||
|
if(fs::is_directory(source_path)) {
|
||
|
try {
|
||
|
if(!fs::remove_all(source_path))
|
||
|
throw fs::filesystem_error("invalid result", error_code());
|
||
|
} catch(const fs::filesystem_error& ex) {
|
||
|
error = "failed to delete directory (" + string(ex.what()) + ")";
|
||
|
return false;
|
||
|
}
|
||
|
} else {
|
||
|
try {
|
||
|
if(!fs::remove(source_path))
|
||
|
throw fs::filesystem_error("invalid result", error_code());
|
||
|
} catch(const fs::filesystem_error& ex) {
|
||
|
error = "failed to delete file (" + string(ex.what()) + ")";
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
auto target_path = fs::u8path(target);
|
||
|
target_path = fs::absolute(target_path);
|
||
|
|
||
|
if(!fs::is_directory(target_path.parent_path())) {
|
||
|
if(!fs::exists(target_path.parent_path())) {
|
||
|
try {
|
||
|
fs::create_directories(target_path.parent_path());
|
||
|
} catch(const fs::filesystem_error& ex) {
|
||
|
error = "failed to create target directories";
|
||
|
return false;
|
||
|
}
|
||
|
} else {
|
||
|
error = "target isn't a directory!";
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
logger::debug("Moving file " + argument_t<fs::path::value_type>::value + " to " + argument_t<fs::path::value_type>::value, source_path.c_str(), target_path.c_str());
|
||
|
if(fs::exists(target_path)) {
|
||
|
logger::debug("Target file already exists. Deleting it");
|
||
|
if(config::backup) {
|
||
|
auto path = file::register_backup(target_path.string());
|
||
|
if(path.empty()) {
|
||
|
drop_backup(path);
|
||
|
error = "failed to create backup path";
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
|
||
|
if(!rename_or_move(target_path, fs::u8path(path), error)) {
|
||
|
drop_backup(path);
|
||
|
error = "failed to delete old target file (" + error + ")";
|
||
|
return false;
|
||
|
}
|
||
|
} else {
|
||
|
try {
|
||
|
if(fs::is_directory(target_path))
|
||
|
fs::remove_all(target_path);
|
||
|
else
|
||
|
fs::remove(target_path);
|
||
|
} catch(const fs::filesystem_error& ex) {
|
||
|
error = "failed to delete old target file: " + string(ex.what());
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
move_back[target_path.string()] = source_path.string();
|
||
|
|
||
|
if(!rename_or_move(source_path, target_path, error)) {
|
||
|
error = "failed to move file (" + error + ")";
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
string random_string(size_t length) {
|
||
|
static const char alphanum[] =
|
||
|
"0123456789"
|
||
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||
|
"abcdefghijklmnopqrstuvwxyz";
|
||
|
|
||
|
string result;
|
||
|
result.resize(length, '\0');
|
||
|
|
||
|
for(char& c : result)
|
||
|
c = alphanum[rand() % (sizeof(alphanum) - 1)];
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
std::string file::register_backup(const std::string &source) {
|
||
|
{
|
||
|
auto path = fs::u8path(config::backup_directory);
|
||
|
if(!fs::exists(path)) {
|
||
|
try {
|
||
|
fs::create_directories(path);
|
||
|
}catch(const fs::filesystem_error& ex) {
|
||
|
logger::error("Failed to create backup directory \"" + argument_t<fs::path::value_type>::value + "\": %s", path.c_str(), ex.what());
|
||
|
return "";
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
std::string key;
|
||
|
while(backup_mapping.count((key = random_string(20))) > 0);
|
||
|
backup_mapping[key] = source;
|
||
|
return fs::absolute(fs::u8path(config::backup_directory) / key).string();
|
||
|
}
|
||
|
|
||
|
void file::drop_backup(const std::string &source) {
|
||
|
for(const auto& entry : backup_mapping) {
|
||
|
if(entry.second == source) {
|
||
|
backup_mapping.erase(entry.first);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void file::rollback() {
|
||
|
if(!config::backup)
|
||
|
return;
|
||
|
|
||
|
logger::info("Rollbacking %d moved files", move_back.size());
|
||
|
for(const pair<string, string>& key : move_back) {
|
||
|
logger::debug("Attempting to moveback %s to %s", key.first.c_str(), key.second.c_str());
|
||
|
try {
|
||
|
fs::rename(fs::u8path(key.first), fs::u8path(key.second));
|
||
|
} catch(const fs::filesystem_error& ex) {
|
||
|
logger::warn("Failed to moveback file from %s to %s (%s)", key.first.c_str(), key.second.c_str(), ex.what());
|
||
|
continue;
|
||
|
}
|
||
|
logger::debug("=> success");
|
||
|
}
|
||
|
|
||
|
logger::info("Rollbacking %d deleted files", backup_mapping.size());
|
||
|
|
||
|
for(const pair<string, string>& key : backup_mapping) {
|
||
|
logger::debug("Attempting to restore %s to %s", key.first.c_str(), key.second.c_str());
|
||
|
try {
|
||
|
auto source = fs::absolute(fs::u8path(config::backup_directory) / key.first);
|
||
|
if(!fs::exists(source)) {
|
||
|
logger::warn("Failed to restore file %s (Source file missing)", key.second.c_str());
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
auto target = fs::u8path(key.second);
|
||
|
fs::rename(source, target);
|
||
|
} catch(const fs::filesystem_error& ex) {
|
||
|
logger::warn("Failed to restore file %s (%s)", key.second.c_str(), ex.what());
|
||
|
continue;
|
||
|
}
|
||
|
logger::debug("=> success");
|
||
|
}
|
||
|
logger::info("Rollback done");
|
||
|
}
|
||
|
|
||
|
#ifdef WIN32
|
||
|
bool file::file_locked(const std::string &file) {
|
||
|
auto file_handle = CreateFile(
|
||
|
(LPCSTR) file.c_str(), /* file name*/
|
||
|
(DWORD) GENERIC_WRITE,
|
||
|
(DWORD) 0, /* we dont want to share */
|
||
|
(LPSECURITY_ATTRIBUTES) nullptr,
|
||
|
(DWORD) OPEN_EXISTING, /* file should be availible */
|
||
|
FILE_ATTRIBUTE_NORMAL,
|
||
|
nullptr
|
||
|
);
|
||
|
if(file_handle == INVALID_HANDLE_VALUE) {
|
||
|
if(GetLastError() == ERROR_SHARING_VIOLATION)
|
||
|
return true; /* file is beeing used */
|
||
|
|
||
|
wchar_t buf[256];
|
||
|
FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
||
|
nullptr, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||
|
buf, (sizeof(buf) / sizeof(wchar_t)), nullptr);
|
||
|
logger::info("Failed to open file! (%S) (%d)", buf, GetLastError());
|
||
|
return true;
|
||
|
}
|
||
|
CloseHandle(file_handle);
|
||
|
return false;
|
||
|
}
|
||
|
#else
|
||
|
bool file::file_locked(const std::string &file) {
|
||
|
auto handle = fopen(file.c_str(), "a+");
|
||
|
if(handle) {
|
||
|
fclose(handle);
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
#endif
|