Мини релиз
This commit is contained in:
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
/.cache
|
||||||
|
/.vscode
|
||||||
|
/build
|
||||||
|
/Work
|
||||||
55
CMakeLists.txt
Normal file
55
CMakeLists.txt
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.13)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 23)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdata-sections -ffunction-sections")
|
||||||
|
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections") # -rdynamic
|
||||||
|
|
||||||
|
# gprof
|
||||||
|
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pg")
|
||||||
|
# set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pg")
|
||||||
|
|
||||||
|
project(btrfs-sync-backend VERSION 0.0 LANGUAGES CXX)
|
||||||
|
add_executable(${PROJECT_NAME})
|
||||||
|
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_23)
|
||||||
|
|
||||||
|
# sanitizer
|
||||||
|
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||||
|
target_compile_options(${PROJECT_NAME} PUBLIC -fsanitize=address,undefined -fno-omit-frame-pointer -fno-sanitize-recover=all)
|
||||||
|
target_link_options(${PROJECT_NAME} PUBLIC -fsanitize=address,undefined)
|
||||||
|
set(ENV{ASAN_OPTIONS} detect_leaks=0)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU")
|
||||||
|
target_compile_options(${PROJECT_NAME} PUBLIC -fcoroutines)
|
||||||
|
elseif("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
|
||||||
|
target_compile_options(${PROJECT_NAME} PUBLIC -fcoroutine)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
file(GLOB_RECURSE SOURCES RELATIVE ${PROJECT_SOURCE_DIR} "Src/*.cpp")
|
||||||
|
target_sources(${PROJECT_NAME} PRIVATE ${SOURCES})
|
||||||
|
target_include_directories(${PROJECT_NAME} PUBLIC "${PROJECT_SOURCE_DIR}/Src")
|
||||||
|
|
||||||
|
|
||||||
|
include(FetchContent)
|
||||||
|
|
||||||
|
# Boost
|
||||||
|
set(Boost_USE_STATIC_LIBS ON)
|
||||||
|
set(Boost_ENABLE_CMAKE ON)
|
||||||
|
set(Boost_INCLUDE_LIBRARIES ${Boost_INCLUDE_LIBRARIES} system asio json thread stacktrace timer serialization uuid process filesystem)
|
||||||
|
FetchContent_Declare(
|
||||||
|
Boost
|
||||||
|
GIT_REPOSITORY https://github.com/boostorg/boost.git
|
||||||
|
GIT_TAG boost-1.89.0
|
||||||
|
GIT_SHALLOW true
|
||||||
|
GIT_PROGRESS true
|
||||||
|
USES_TERMINAL_DOWNLOAD true
|
||||||
|
)
|
||||||
|
FetchContent_MakeAvailable(Boost)
|
||||||
|
target_link_libraries(${PROJECT_NAME} PUBLIC Boost::asio Boost::process Boost::json)
|
||||||
|
|
||||||
|
# ICU
|
||||||
|
find_package(ICU REQUIRED COMPONENTS uc i18n)
|
||||||
|
target_link_libraries(${PROJECT_NAME} PUBLIC ICU::uc ICU::i18n)
|
||||||
212
Src/Asio.hpp
Normal file
212
Src/Asio.hpp
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <boost/asio.hpp>
|
||||||
|
#include <boost/asio/experimental/awaitable_operators.hpp>
|
||||||
|
#include <boost/algorithm/string/replace.hpp>
|
||||||
|
#include <functional>
|
||||||
|
#include <iostream>
|
||||||
|
#include <boost/stacktrace.hpp>
|
||||||
|
|
||||||
|
|
||||||
|
using namespace boost::asio::experimental::awaitable_operators;
|
||||||
|
|
||||||
|
|
||||||
|
inline std::string tabulate(const std::string& tb) {
|
||||||
|
return boost::algorithm::replace_all_copy(tb, "\n", "\n\t");
|
||||||
|
}
|
||||||
|
|
||||||
|
// #ifdef NDEBUG
|
||||||
|
#define MAKE_ERROR(text) { std::stringstream error; error << text << std::endl; throw std::runtime_error(error.str()); }
|
||||||
|
#define MAKE_WARNING(text) { std::stringstream error; error << text << std::endl; std::cerr << error.str(); }
|
||||||
|
// #else
|
||||||
|
// #define MAKE_ERROR(text) { std::stringstream error; error << boost::stacktrace::stacktrace(); std::string tb = tabulate(error.str()); error.str(""); error << text << "\n\t" << tb << std::endl; throw std::runtime_error(error.str()); }
|
||||||
|
// #define MAKE_WARNING(text) { std::stringstream error; error << boost::stacktrace::stacktrace(); std::string tb = tabulate(error.str()); error.str(""); error << text << "\n\t" << tb << std::endl; std::cerr << error.str(); }
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
#define MAKE_INFO(text) { std::stringstream info; info << text << std::endl; std::cout << info.str(); }
|
||||||
|
|
||||||
|
namespace Asio {
|
||||||
|
|
||||||
|
namespace asio = boost::asio;
|
||||||
|
template<typename Result = void, typename Executor = asio::any_io_executor>
|
||||||
|
using coro = asio::awaitable<Result, Executor>;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Асинхроный ожидатель завершения использования ресурса
|
||||||
|
*/
|
||||||
|
class AsyncUseControl {
|
||||||
|
public:
|
||||||
|
class Lock {
|
||||||
|
AsyncUseControl *AUC;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Lock(AsyncUseControl *auc)
|
||||||
|
: AUC(auc)
|
||||||
|
{}
|
||||||
|
|
||||||
|
Lock()
|
||||||
|
: AUC(nullptr)
|
||||||
|
{}
|
||||||
|
|
||||||
|
~Lock() {
|
||||||
|
if(AUC)
|
||||||
|
unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
Lock(const Lock&) = delete;
|
||||||
|
Lock(Lock&& obj)
|
||||||
|
: AUC(obj.AUC)
|
||||||
|
{
|
||||||
|
obj.AUC = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Lock& operator=(const Lock&) = delete;
|
||||||
|
Lock& operator=(Lock&& obj) {
|
||||||
|
if(&obj == this)
|
||||||
|
return *this;
|
||||||
|
|
||||||
|
if(AUC)
|
||||||
|
unlock();
|
||||||
|
|
||||||
|
AUC = obj.AUC;
|
||||||
|
obj.AUC = nullptr;
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void unlock() {
|
||||||
|
assert(AUC);
|
||||||
|
|
||||||
|
if(--AUC->Uses == 0 && AUC->OnNoUse) {
|
||||||
|
asio::post(AUC->IOC, std::move(AUC->OnNoUse));
|
||||||
|
}
|
||||||
|
|
||||||
|
AUC = nullptr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
asio::io_context &IOC;
|
||||||
|
bool NoUse = false;
|
||||||
|
std::move_only_function<void()> OnNoUse;
|
||||||
|
std::atomic_int Uses = 0;
|
||||||
|
|
||||||
|
public:
|
||||||
|
AsyncUseControl(asio::io_context &ioc)
|
||||||
|
: IOC(ioc)
|
||||||
|
{}
|
||||||
|
|
||||||
|
template<BOOST_ASIO_COMPLETION_TOKEN_FOR(void()) Token = asio::default_completion_token_t<asio::io_context>>
|
||||||
|
auto wait(Token&& token = asio::default_completion_token_t<asio::io_context>()) {
|
||||||
|
NoUse = true;
|
||||||
|
|
||||||
|
auto initiation = [this](auto&& token) {
|
||||||
|
int value;
|
||||||
|
do {
|
||||||
|
value = Uses.exchange(-1);
|
||||||
|
} while(value == -1);
|
||||||
|
|
||||||
|
OnNoUse = std::move(token);
|
||||||
|
|
||||||
|
if(value == 0) {
|
||||||
|
asio::post(IOC, std::move(OnNoUse));
|
||||||
|
}
|
||||||
|
|
||||||
|
Uses.exchange(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
return asio::async_initiate<Token, void()>(initiation, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
Lock use() {
|
||||||
|
int value;
|
||||||
|
do {
|
||||||
|
value = Uses.exchange(-1);
|
||||||
|
} while(value == -1);
|
||||||
|
|
||||||
|
if(NoUse) {
|
||||||
|
Uses.exchange(value);
|
||||||
|
throw boost::system::system_error(asio::error::operation_aborted, "OnNoUse");
|
||||||
|
}
|
||||||
|
|
||||||
|
Uses.exchange(++value);
|
||||||
|
return Lock(this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Используется, чтобы вместо уничтожения объекта в умной ссылке, вызвать корутину с co_await asyncDestructor()
|
||||||
|
*/
|
||||||
|
class IAsyncDestructible : public std::enable_shared_from_this<IAsyncDestructible> {
|
||||||
|
protected:
|
||||||
|
asio::io_context &IOC;
|
||||||
|
AsyncUseControl AUC;
|
||||||
|
|
||||||
|
virtual coro<> asyncDestructor() { co_await AUC.wait(); }
|
||||||
|
|
||||||
|
public:
|
||||||
|
IAsyncDestructible(asio::io_context &ioc)
|
||||||
|
: IOC(ioc), AUC(ioc)
|
||||||
|
{}
|
||||||
|
|
||||||
|
virtual ~IAsyncDestructible() {}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
template<typename T, typename = typename std::is_same<IAsyncDestructible, T>>
|
||||||
|
static std::shared_ptr<T> CreateShared(asio::io_context &ioc, T *ptr)
|
||||||
|
{
|
||||||
|
return std::shared_ptr<T>(ptr, [&ioc](T *ptr) {
|
||||||
|
boost::asio::co_spawn(ioc,
|
||||||
|
[ptr]() mutable -> coro<> {
|
||||||
|
try {
|
||||||
|
co_await dynamic_cast<IAsyncDestructible*>(ptr)->asyncDestructor();
|
||||||
|
} catch(const std::exception& exc) {
|
||||||
|
std::string error = "IAsyncDestructible<";
|
||||||
|
error += typeid(T).name();
|
||||||
|
error += ">:\n";
|
||||||
|
error += tabulate(exc.what());
|
||||||
|
std::cerr << error << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
delete ptr;
|
||||||
|
} catch(const std::exception& exc) {
|
||||||
|
std::string error = "IAsyncDestructible~<";
|
||||||
|
error += typeid(T).name();
|
||||||
|
error += ">:\n";
|
||||||
|
error += tabulate(exc.what());
|
||||||
|
std::cerr << error << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
boost::asio::detached);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T, typename = typename std::is_same<IAsyncDestructible, T>>
|
||||||
|
static coro<std::shared_ptr<T>> CreateShared(T *ptr)
|
||||||
|
{
|
||||||
|
co_return std::shared_ptr<T>(ptr, [ioc = asio::get_associated_executor(co_await asio::this_coro::executor)](T *ptr) {
|
||||||
|
boost::asio::co_spawn(ioc,
|
||||||
|
[ptr]() mutable -> coro<> {
|
||||||
|
try {
|
||||||
|
co_await dynamic_cast<IAsyncDestructible*>(ptr)->asyncDestructor();
|
||||||
|
} catch(const std::exception& exc) {
|
||||||
|
std::string error = "IAsyncDestructible<";
|
||||||
|
error += typeid(T).name();
|
||||||
|
error += ">:\n";
|
||||||
|
std::string subError = exc.what();
|
||||||
|
boost::replace_all(subError, "\n", "\n\t");
|
||||||
|
error += subError;
|
||||||
|
std::cerr << error << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
asio::post(asio::get_associated_executor(co_await asio::this_coro::executor), [ptr](){ delete ptr; });
|
||||||
|
},
|
||||||
|
boost::asio::detached);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
773
Src/main.cpp
Normal file
773
Src/main.cpp
Normal file
@@ -0,0 +1,773 @@
|
|||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include "Asio.hpp"
|
||||||
|
#include "boost/asio.hpp"
|
||||||
|
#include "boost/system.hpp"
|
||||||
|
#include "boost/process.hpp"
|
||||||
|
#include "boost/json.hpp"
|
||||||
|
#include "boost/date_time.hpp"
|
||||||
|
|
||||||
|
|
||||||
|
using namespace Asio;
|
||||||
|
namespace js = boost::json;
|
||||||
|
constexpr uint32_t IntervalLen = 30;
|
||||||
|
|
||||||
|
coro<std::tuple<int, std::string>> runProc(const std::string procName, const std::vector<std::string> args = {}) {
|
||||||
|
auto exec = co_await asio::this_coro::executor;
|
||||||
|
asio::readable_pipe r(exec);
|
||||||
|
|
||||||
|
boost::process::process proc(exec, boost::process::environment::find_executable(procName),
|
||||||
|
args,
|
||||||
|
boost::process::process_stdio{nullptr, r, STDOUT_FILENO}
|
||||||
|
);
|
||||||
|
|
||||||
|
std::string result;
|
||||||
|
std::vector<char> tempBuff(1024*64);
|
||||||
|
|
||||||
|
while(true) {
|
||||||
|
try {
|
||||||
|
size_t size = co_await r.async_read_some(asio::mutable_buffer(tempBuff.data(), tempBuff.size()));
|
||||||
|
result += std::string_view(tempBuff.data(), size);
|
||||||
|
} catch(const std::exception& exc) {
|
||||||
|
if(const auto* serr = dynamic_cast<const boost::system::system_error*>(&exc)) {
|
||||||
|
if(serr->code() == asio::error::eof)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
co_return std::tuple<int, std::string>{co_await proc.async_wait(), std::move(result)};
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
extern asio::io_context IOC;
|
||||||
|
|
||||||
|
constexpr const char *ConfigFile = "config.json";
|
||||||
|
constexpr const char *ConfigFilePrev = "config.prev.json";
|
||||||
|
constexpr const char *ConfigFileNext = "config.next.json";
|
||||||
|
|
||||||
|
using StorageId = uint32_t;
|
||||||
|
using PolicyId = uint32_t;
|
||||||
|
using ObjectId = uint32_t;
|
||||||
|
|
||||||
|
struct Storage {
|
||||||
|
// Путь относительно SystemRoot
|
||||||
|
fs::path Path;
|
||||||
|
|
||||||
|
js::object dump() const {
|
||||||
|
return {
|
||||||
|
{"Path", (std::string_view) Path.string()}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void load(const js::object& obj) {
|
||||||
|
Path = (std::string_view) obj.at("Path").get_string();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Policy {
|
||||||
|
// Каждые 30 минут
|
||||||
|
// Где храним (-1 на той же системе)
|
||||||
|
// Сколько последних копий должно хранится
|
||||||
|
|
||||||
|
// Множитель интервала, хранилище, сколько последних хранить
|
||||||
|
std::vector<std::array<uint16_t, 3>> SubIntervals;
|
||||||
|
|
||||||
|
|
||||||
|
js::object dump() const {
|
||||||
|
js::array res;
|
||||||
|
|
||||||
|
for(const auto& arrays : SubIntervals) {
|
||||||
|
res.push_back({arrays[0], arrays[1], arrays[2]});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
{"SubIntervals", std::move(res)}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void load(const js::object& obj) {
|
||||||
|
for(const auto& arrays : obj.at("SubIntervals").as_array()) {
|
||||||
|
const auto& array = arrays.as_array();
|
||||||
|
SubIntervals.push_back(
|
||||||
|
std::array<uint16_t, 3>{
|
||||||
|
(uint16_t) array[0].to_number<uint64_t>(),
|
||||||
|
(uint16_t) array[1].to_number<uint64_t>(),
|
||||||
|
(uint16_t) array[2].to_number<uint64_t>()
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Object {
|
||||||
|
fs::path Path;
|
||||||
|
PolicyId Id;
|
||||||
|
|
||||||
|
js::object dump() const {
|
||||||
|
return {
|
||||||
|
{"Path", (std::string_view) Path.string()},
|
||||||
|
{"Id", Id}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void load(const js::object& obj) {
|
||||||
|
Path = (std::string_view) obj.at("Path").get_string();
|
||||||
|
Id = obj.at("Id").to_number<uint64_t>();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Configuration {
|
||||||
|
fs::path SystemRoot = "/";
|
||||||
|
// Хранилища на которых могут хранится бэкапы
|
||||||
|
std::unordered_map<StorageId, Storage> Storages;
|
||||||
|
// Шаблон временных политик единичных объектов
|
||||||
|
Policy TemplatePolicy;
|
||||||
|
// Политики резервного копирования
|
||||||
|
std::unordered_map<PolicyId, Policy> Policies;
|
||||||
|
// Объекты резервного копирования
|
||||||
|
std::unordered_map<ObjectId, Object> Objects;
|
||||||
|
|
||||||
|
void save() {
|
||||||
|
std::string data;
|
||||||
|
|
||||||
|
try {
|
||||||
|
js::object storages, policies, objects;
|
||||||
|
|
||||||
|
for(const auto& [id, storage] : Storages)
|
||||||
|
storages.emplace(std::to_string(id), storage.dump());
|
||||||
|
|
||||||
|
for(const auto& [id, policy] : Policies)
|
||||||
|
policies.emplace(std::to_string(id), policy.dump());
|
||||||
|
|
||||||
|
for(const auto& [id, object] : Objects)
|
||||||
|
objects.emplace(std::to_string(id), object.dump());
|
||||||
|
|
||||||
|
js::object val {
|
||||||
|
{"SystemRoot", SystemRoot.string()},
|
||||||
|
{"Storages", std::move(storages)},
|
||||||
|
{"TemplatePolicy", TemplatePolicy.dump()},
|
||||||
|
{"Policies", std::move(policies)},
|
||||||
|
{"Objects", std::move(objects)},
|
||||||
|
};
|
||||||
|
|
||||||
|
data = js::serialize(val);
|
||||||
|
} catch(const std::exception& exc) {
|
||||||
|
MAKE_ERROR("Ошибка во время формирования json объекта:\n\t" << tabulate(exc.what()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(fs::exists(ConfigFile)) {
|
||||||
|
try {
|
||||||
|
fs::copy_file(ConfigFile, ConfigFilePrev, fs::copy_options::overwrite_existing);
|
||||||
|
} catch(const std::exception& exc) {
|
||||||
|
MAKE_ERROR("Ошибка во время создания бэкапа конфигурации:\n\t" << tabulate(exc.what()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
std::ofstream fd(ConfigFileNext);
|
||||||
|
fd.write(data.c_str(), data.size());
|
||||||
|
fd.close();
|
||||||
|
} catch(const std::exception& exc) {
|
||||||
|
MAKE_ERROR("Ошибка во время сохранения новой конфигурации:\n\t" << tabulate(exc.what()));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
fs::rename(ConfigFileNext, ConfigFile);
|
||||||
|
} catch(const std::exception& exc) {
|
||||||
|
MAKE_ERROR("Ошибка во время применения сохранённой конфигурации:\n\t" << tabulate(exc.what()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void load() {
|
||||||
|
if(!fs::exists(ConfigFile)) {
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
js::object val;
|
||||||
|
|
||||||
|
for(int iter = 0; iter < 2; iter++) {
|
||||||
|
try {
|
||||||
|
js::parser parser;
|
||||||
|
std::ifstream fd(iter == 0 ? ConfigFile : ConfigFilePrev);
|
||||||
|
std::vector<char> buff(1024*64);
|
||||||
|
while(size_t size = fd.readsome(buff.data(), buff.size())) {
|
||||||
|
size_t writed = 0;
|
||||||
|
while(writed != size) {
|
||||||
|
writed += parser.write(buff.data()+writed, size-writed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val = parser.release().as_object();
|
||||||
|
break;
|
||||||
|
} catch(const std::exception& exc) {
|
||||||
|
if(iter == 0)
|
||||||
|
MAKE_WARNING("Ошибка во время загрузки файла конфигурации:\n\t" << tabulate(exc.what()))
|
||||||
|
else if(iter == 1)
|
||||||
|
MAKE_ERROR("Ошибка во время загрузки резервной копии файла конфигурации:\n\t" << tabulate(exc.what()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
SystemRoot = (std::string_view) val["SystemRoot"].get_string();
|
||||||
|
|
||||||
|
for(const auto& [key, storage] : val["Storages"].as_object()) {
|
||||||
|
Storages[std::stoul(key)].load(storage.as_object());
|
||||||
|
}
|
||||||
|
|
||||||
|
TemplatePolicy.load(val["TemplatePolicy"].as_object());
|
||||||
|
|
||||||
|
for(const auto& [key, policy] : val["Policies"].as_object()) {
|
||||||
|
Policies[std::stoul(key)].load(policy.as_object());
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const auto& [key, object] : val["Objects"].as_object()) {
|
||||||
|
Objects[std::stoul(key)].load(object.as_object());
|
||||||
|
}
|
||||||
|
} catch(const std::exception& exc) {
|
||||||
|
MAKE_ERROR("Ошибка во время парсинга файла конфигурации:\n\t" << tabulate(exc.what()));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Configuration GlobalConf;
|
||||||
|
|
||||||
|
// Проверка действительности конфигурации
|
||||||
|
// Проверка что все хранилища доступны и являются btrfs.
|
||||||
|
// Проверка связности OneObject политик.
|
||||||
|
void checkConfigurationConsistency() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Tp>
|
||||||
|
uint32_t getNextIdFor(const std::unordered_map<uint32_t, Tp>& map) {
|
||||||
|
if(map.empty())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
std::vector<uint32_t> keys;
|
||||||
|
keys.reserve(map.size());
|
||||||
|
for(const auto& pair : map) {
|
||||||
|
keys.push_back(pair.first);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(keys.begin(), keys.end());
|
||||||
|
|
||||||
|
uint32_t expected = 0;
|
||||||
|
for(uint32_t key : keys) {
|
||||||
|
if(key > expected)
|
||||||
|
return expected;
|
||||||
|
|
||||||
|
if(expected == UINT32_MAX)
|
||||||
|
throw std::runtime_error("Нет свободных идентификаторов");
|
||||||
|
|
||||||
|
++expected;
|
||||||
|
}
|
||||||
|
|
||||||
|
return expected;
|
||||||
|
}
|
||||||
|
|
||||||
|
coro<std::string> getMountUUID(const fs::path path) {
|
||||||
|
std::vector<std::string> args;
|
||||||
|
args.push_back("-no");
|
||||||
|
args.push_back("UUID");
|
||||||
|
args.push_back("--target");
|
||||||
|
args.push_back(path.string());
|
||||||
|
|
||||||
|
auto [code, data] = co_await runProc("findmnt",
|
||||||
|
args
|
||||||
|
);
|
||||||
|
|
||||||
|
if(code != 0)
|
||||||
|
MAKE_ERROR("Путь не существует");
|
||||||
|
|
||||||
|
co_return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::posix_time::ptime parseDateTime(const std::string time) {
|
||||||
|
std::istringstream ss(time);
|
||||||
|
boost::posix_time::ptime local_time;
|
||||||
|
|
||||||
|
ss.imbue(std::locale(ss.getloc(), new boost::posix_time::time_input_facet("%Y-%m-%d %H:%M:%S")));
|
||||||
|
ss >> local_time;
|
||||||
|
|
||||||
|
return local_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SubVolumeInfo {
|
||||||
|
boost::posix_time::ptime CreationTime{boost::local_time::not_a_date_time};
|
||||||
|
std::string UUID, ParentUUID, ReceivedUUID;
|
||||||
|
};
|
||||||
|
|
||||||
|
coro<SubVolumeInfo> getSubvolumeInfo(const fs::path path) {
|
||||||
|
std::vector<std::string> args;
|
||||||
|
args.push_back("subvolume");
|
||||||
|
args.push_back("show");
|
||||||
|
args.push_back(path);
|
||||||
|
|
||||||
|
auto [code, data] = co_await runProc("btrfs",
|
||||||
|
args
|
||||||
|
);
|
||||||
|
|
||||||
|
if(code != 0)
|
||||||
|
MAKE_ERROR("Не удалось получить информацию о подразделе btrfs:\n\t" << tabulate(data));
|
||||||
|
|
||||||
|
SubVolumeInfo info;
|
||||||
|
std::istringstream ss(data);
|
||||||
|
std::string line;
|
||||||
|
|
||||||
|
while(std::getline(ss, line)) {
|
||||||
|
if(info.UUID.empty() && line.find("UUID:") != std::string::npos) {
|
||||||
|
info.UUID = line.substr(line.find(':') + 1);
|
||||||
|
info.UUID.erase(0, info.UUID.find_first_not_of(" \t"));
|
||||||
|
} else if(line.find("Parent UUID:") != std::string::npos) {
|
||||||
|
info.ParentUUID = line.substr(line.find(':') + 1);
|
||||||
|
info.ParentUUID.erase(0, info.ParentUUID.find_first_not_of(" \t"));
|
||||||
|
if(info.ParentUUID == "-") {
|
||||||
|
info.ParentUUID.clear();
|
||||||
|
}
|
||||||
|
} else if(line.find("Received UUID:") != std::string::npos) {
|
||||||
|
info.ReceivedUUID = line.substr(line.find(':') + 1);
|
||||||
|
info.ReceivedUUID.erase(0, info.ReceivedUUID.find_first_not_of(" \t"));
|
||||||
|
if(info.ReceivedUUID == "-") {
|
||||||
|
info.ReceivedUUID.clear();
|
||||||
|
}
|
||||||
|
} else if(line.find("Creation time:") != std::string::npos) {
|
||||||
|
std::string timePart = line.substr(line.find(':') + 1);
|
||||||
|
timePart.erase(0, timePart.find_first_not_of(" \t"));
|
||||||
|
info.CreationTime = parseDateTime(timePart);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
co_return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unordered_map<std::string, StorageId> UUIDToStorage;
|
||||||
|
|
||||||
|
struct CycleStorageInfo {
|
||||||
|
SubVolumeInfo SVInfo;
|
||||||
|
fs::path Path;
|
||||||
|
int Reference = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
fs::path remapPath(const fs::path& original_path, const fs::path& new_root) {
|
||||||
|
if(original_path.is_absolute()) {
|
||||||
|
fs::path relative_part;
|
||||||
|
bool first = true;
|
||||||
|
for(const auto& part : original_path) {
|
||||||
|
if(first) {
|
||||||
|
first = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
relative_part /= part;
|
||||||
|
}
|
||||||
|
return new_root / relative_part;
|
||||||
|
} else {
|
||||||
|
return new_root / original_path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
coro<std::vector<CycleStorageInfo>> getStorageInfo(StorageId id) {
|
||||||
|
std::vector<CycleStorageInfo> result;
|
||||||
|
fs::directory_iterator begin(remapPath(GlobalConf.Storages.at(id).Path, GlobalConf.SystemRoot)), end;
|
||||||
|
|
||||||
|
for(; begin != end; begin++) {
|
||||||
|
fs::path snapshot = begin->path();
|
||||||
|
|
||||||
|
try {
|
||||||
|
SubVolumeInfo info = co_await getSubvolumeInfo(snapshot);
|
||||||
|
result.emplace_back(std::move(info), snapshot, 0);
|
||||||
|
} catch(const std::exception& exc) {
|
||||||
|
MAKE_WARNING("Не удалось получить информацию о подразделе \"" << snapshot.string()
|
||||||
|
<< "\" из хранилища:\n\t" << tabulate(exc.what()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
co_return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string timeToString(const boost::posix_time::ptime& time) {
|
||||||
|
std::ostringstream ss;
|
||||||
|
boost::posix_time::time_facet *facet =
|
||||||
|
new boost::posix_time::time_facet("%Y-%m-%d %H:%M");
|
||||||
|
|
||||||
|
ss.imbue(std::locale(ss.getloc(), facet));
|
||||||
|
ss << time;
|
||||||
|
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
coro<> cycle() {
|
||||||
|
// Собираем информацию обо всех подразделах в хранилищах
|
||||||
|
std::unordered_map<StorageId, std::vector<CycleStorageInfo>> storagesInfo;
|
||||||
|
for(const auto& [id, storage] : GlobalConf.Storages) {
|
||||||
|
storagesInfo[id] = co_await getStorageInfo(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::int64_t half_hours;
|
||||||
|
boost::posix_time::ptime now = boost::posix_time::microsec_clock::local_time();
|
||||||
|
{
|
||||||
|
boost::posix_time::ptime epoch(boost::gregorian::date(1970, 1, 1));
|
||||||
|
boost::posix_time::time_duration diff = now - epoch;
|
||||||
|
boost::int64_t total_seconds = diff.total_seconds();
|
||||||
|
half_hours = total_seconds / 1800;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проходимся по объектам и смотрим кому нужно сделать снапшот (пока на том же хранилише)
|
||||||
|
for(const auto& [id, object] : GlobalConf.Objects) {
|
||||||
|
try {
|
||||||
|
const Policy& policy = GlobalConf.Policies.at(object.Id);
|
||||||
|
fs::path objectPath = remapPath(object.Path, GlobalConf.SystemRoot);
|
||||||
|
SubVolumeInfo objectInfo = co_await getSubvolumeInfo(objectPath);
|
||||||
|
std::string muuid = co_await getMountUUID(objectPath);
|
||||||
|
StorageId sid = UUIDToStorage.at(muuid);
|
||||||
|
|
||||||
|
boost::posix_time::time_duration last = boost::posix_time::pos_infin;
|
||||||
|
const CycleStorageInfo* lastLocalSnapshot = nullptr;
|
||||||
|
const std::vector<CycleStorageInfo>& storageInfo = storagesInfo.at(sid);
|
||||||
|
|
||||||
|
for(const auto& snapshot : storageInfo) {
|
||||||
|
if(snapshot.SVInfo.ParentUUID != objectInfo.UUID)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto delta = now-snapshot.SVInfo.CreationTime;
|
||||||
|
if(delta < last) {
|
||||||
|
last = delta;
|
||||||
|
lastLocalSnapshot = &snapshot;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::path dstStoragePath = GlobalConf.Storages.at(sid).Path;
|
||||||
|
|
||||||
|
if(last >= boost::posix_time::time_duration(boost::posix_time::minutes(IntervalLen*policy.SubIntervals.front()[0]))) {
|
||||||
|
std::cout << "Создание локального снапшота для объекта " << object.Path.string() << " в хранилище " << dstStoragePath.string() << std::endl;
|
||||||
|
std::vector<std::string> args;
|
||||||
|
args.push_back("subvolume");
|
||||||
|
args.push_back("snapshot");
|
||||||
|
args.push_back("-r");
|
||||||
|
args.push_back(object.Path);
|
||||||
|
args.push_back(dstStoragePath / (objectInfo.UUID + " " + timeToString(now)));
|
||||||
|
|
||||||
|
auto [code, output] = co_await runProc("btrfs",
|
||||||
|
args
|
||||||
|
);
|
||||||
|
|
||||||
|
if(code != 0)
|
||||||
|
MAKE_ERROR("Не удалось создать снапшот:\n\t" << tabulate(output));
|
||||||
|
|
||||||
|
std::cout << "Создан снапшот объекта " << object.Path.string() << std::endl;
|
||||||
|
|
||||||
|
// Обновим информацию о хранилище
|
||||||
|
storagesInfo[sid] = co_await getStorageInfo(sid);
|
||||||
|
}
|
||||||
|
} catch(const std::exception& exc) {
|
||||||
|
MAKE_WARNING("Во время проверки необходимости сделать первый интервальный снапшот произошла ошибка, объект: \"" << object.Path.string()
|
||||||
|
<< "\"\n\t" << tabulate(exc.what()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Теперь нужно перекинуть снапшоты относительно родительских на нужные хранилища
|
||||||
|
for(const auto& [id, object] : GlobalConf.Objects) {
|
||||||
|
try {
|
||||||
|
const Policy& policy = GlobalConf.Policies.at(object.Id);
|
||||||
|
fs::path objectPath = remapPath(object.Path, GlobalConf.SystemRoot);
|
||||||
|
SubVolumeInfo objectInfo = co_await getSubvolumeInfo(objectPath);
|
||||||
|
std::string muuid = co_await getMountUUID(objectPath);
|
||||||
|
StorageId sid = UUIDToStorage.at(muuid);
|
||||||
|
|
||||||
|
CycleStorageInfo* lastLocalSnapshot = nullptr;
|
||||||
|
std::vector<CycleStorageInfo>& localStorageInfo = storagesInfo.at(sid);
|
||||||
|
{
|
||||||
|
boost::posix_time::time_duration last = boost::posix_time::pos_infin;
|
||||||
|
|
||||||
|
for(auto& snapshot : localStorageInfo) {
|
||||||
|
if(snapshot.SVInfo.ParentUUID != objectInfo.UUID)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto delta = now-snapshot.SVInfo.CreationTime;
|
||||||
|
if(delta < last) {
|
||||||
|
last = delta;
|
||||||
|
lastLocalSnapshot = &snapshot;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(last >= boost::posix_time::time_duration(boost::posix_time::minutes(IntervalLen*policy.SubIntervals.front()[0])))
|
||||||
|
lastLocalSnapshot = nullptr;
|
||||||
|
|
||||||
|
if(lastLocalSnapshot)
|
||||||
|
lastLocalSnapshot->Reference += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!lastLocalSnapshot) {
|
||||||
|
MAKE_ERROR("Не найден свежий снапшот объекта");
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t fullInterval = 1;
|
||||||
|
|
||||||
|
// Проход по всем интервалам
|
||||||
|
for(auto [interval, storageId, count] : policy.SubIntervals) {
|
||||||
|
fullInterval *= interval;
|
||||||
|
|
||||||
|
if(storageId == uint16_t(-1)) {
|
||||||
|
storageId = sid;
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::posix_time::time_duration last = boost::posix_time::pos_infin;
|
||||||
|
CycleStorageInfo* lastSnapshot = nullptr;
|
||||||
|
|
||||||
|
std::vector<CycleStorageInfo>& dstStorageInfo = storagesInfo.at(storageId);
|
||||||
|
const Storage& dstStorageConf = GlobalConf.Storages.at(storageId);
|
||||||
|
|
||||||
|
// Поиск последнего снапшота в хранилище
|
||||||
|
for(auto& snapshot : dstStorageInfo) {
|
||||||
|
std::string name = snapshot.Path.filename();
|
||||||
|
size_t pos = name.find(" ");
|
||||||
|
|
||||||
|
if(pos == std::string::npos) {
|
||||||
|
std::cout << "Неверно названный объект в хранилище: " << snapshot.Path << std::endl;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(name.substr(0, pos) != objectInfo.UUID)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto delta = now-snapshot.SVInfo.CreationTime;
|
||||||
|
if(delta < last) {
|
||||||
|
last = delta;
|
||||||
|
lastSnapshot = &snapshot;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::path parent;
|
||||||
|
if(lastSnapshot) {
|
||||||
|
// Найти предка в локальном хранилище
|
||||||
|
const CycleStorageInfo* parentSnapshot = nullptr;
|
||||||
|
for(const auto& snapshot : localStorageInfo) {
|
||||||
|
if(snapshot.SVInfo.UUID != lastSnapshot->SVInfo.ReceivedUUID)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
parentSnapshot = &snapshot;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!parentSnapshot) {
|
||||||
|
MAKE_WARNING("Не найден родительский подраздел для хранилища "
|
||||||
|
<< dstStorageConf.Path << ", объект " << object.Path
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
lastSnapshot->Reference += 1;
|
||||||
|
parent = parentSnapshot->Path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(last >= boost::posix_time::time_duration(boost::posix_time::minutes(IntervalLen*fullInterval))) {
|
||||||
|
// Необходимо скопировать сюда снапшот
|
||||||
|
|
||||||
|
asio::readable_pipe sender{IOC};
|
||||||
|
asio::writable_pipe receiver(IOC);
|
||||||
|
|
||||||
|
auto exe = boost::process::environment::find_executable("btrfs");
|
||||||
|
std::vector<std::string> args = {"send", "-q", "--proto", "0"};
|
||||||
|
|
||||||
|
if(!parent.empty()) {
|
||||||
|
args.push_back("-p");
|
||||||
|
args.push_back(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
args.push_back(lastLocalSnapshot->Path);
|
||||||
|
|
||||||
|
boost::process::process senderProc(IOC, exe,
|
||||||
|
args,
|
||||||
|
boost::process::process_stdio{nullptr, sender, STDOUT_FILENO}
|
||||||
|
);
|
||||||
|
|
||||||
|
args = {"receive", dstStorageConf.Path};
|
||||||
|
boost::process::process receiverProc(IOC, exe,
|
||||||
|
args,
|
||||||
|
boost::process::process_stdio{receiver, nullptr, STDOUT_FILENO}
|
||||||
|
);
|
||||||
|
|
||||||
|
std::vector<char> tempBuff(1024*1024*32);
|
||||||
|
|
||||||
|
size_t transfered = 0;
|
||||||
|
while(true) {
|
||||||
|
try {
|
||||||
|
size_t size = co_await sender.async_read_some(asio::mutable_buffer(tempBuff.data(), tempBuff.size()));
|
||||||
|
transfered += size;
|
||||||
|
co_await asio::async_write(receiver, asio::const_buffer(tempBuff.data(), size));
|
||||||
|
} catch(const std::exception& exc) {
|
||||||
|
if(const auto* serr = dynamic_cast<const boost::system::system_error*>(&exc)) {
|
||||||
|
if(serr->code() == asio::error::eof)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int senderCode = co_await senderProc.async_wait();
|
||||||
|
receiver.close();
|
||||||
|
int receiverCode = co_await receiverProc.async_wait();
|
||||||
|
|
||||||
|
if(senderCode != 0 || receiverCode != 0) {
|
||||||
|
MAKE_ERROR("В процессе передачи подраздела один из процессов вернул не ноль");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(parent.empty())
|
||||||
|
std::cout << "Передан снапшот объекта " << object.Path.string() << " в хранилище " << dstStorageConf.Path << " (передано " << transfered << ")" << std::endl;
|
||||||
|
else
|
||||||
|
std::cout << "Передан снапшот объекта " << object.Path.string() << " в хранилище " << dstStorageConf.Path << " (передано " << transfered << ") " << " относительно родителя" << std::endl;
|
||||||
|
|
||||||
|
fs::path dstSubvolumePath = dstStorageConf.Path / lastLocalSnapshot->Path.filename();
|
||||||
|
storagesInfo[storageId].emplace_back(co_await getSubvolumeInfo(dstSubvolumePath), dstSubvolumePath, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch(const std::exception& exc) {
|
||||||
|
MAKE_WARNING("Во время проверки необходимости отправки последнего снапшота на внешние хранилища произошла ошибка: \"" << object.Path.string()
|
||||||
|
<< "\"\n\t" << tabulate(exc.what()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Отмечаем необходимые подразделы
|
||||||
|
for(const auto& [id, object] : GlobalConf.Objects) {
|
||||||
|
try {
|
||||||
|
const Policy& policy = GlobalConf.Policies.at(object.Id);
|
||||||
|
fs::path objectPath = remapPath(object.Path, GlobalConf.SystemRoot);
|
||||||
|
SubVolumeInfo objectInfo = co_await getSubvolumeInfo(objectPath);
|
||||||
|
std::string muuid = co_await getMountUUID(objectPath);
|
||||||
|
StorageId sid = UUIDToStorage.at(muuid);
|
||||||
|
|
||||||
|
uint32_t fullInterval = 1;
|
||||||
|
|
||||||
|
// По всем интервалам на хранилищах
|
||||||
|
// Проход по всем интервалам
|
||||||
|
for(auto [interval, storageId, count] : policy.SubIntervals) {
|
||||||
|
fullInterval *= interval;
|
||||||
|
|
||||||
|
if(storageId == uint16_t(-1)) {
|
||||||
|
storageId = sid;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<CycleStorageInfo>& dstStorageInfo = storagesInfo.at(storageId);
|
||||||
|
|
||||||
|
// Найти последнее в интервале и отметить
|
||||||
|
for(int iter = 0; iter < count; iter++) {
|
||||||
|
uint32_t countInterval = fullInterval*(iter+1);
|
||||||
|
CycleStorageInfo* lastSnapshot = nullptr;
|
||||||
|
boost::posix_time::time_duration last = boost::posix_time::neg_infin;
|
||||||
|
|
||||||
|
for(auto& snapshot : dstStorageInfo) {
|
||||||
|
std::string name = snapshot.Path.filename();
|
||||||
|
size_t pos = name.find(" ");
|
||||||
|
|
||||||
|
if(pos == std::string::npos) {
|
||||||
|
std::cout << "Неверно названный объект в хранилище: " << snapshot.Path << std::endl;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(name.substr(0, pos) != objectInfo.UUID)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto delta = now-boost::posix_time::minutes(IntervalLen*countInterval)-snapshot.SVInfo.CreationTime;
|
||||||
|
if(delta.is_negative() && delta > last) {
|
||||||
|
last = delta;
|
||||||
|
lastSnapshot = &snapshot;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(lastSnapshot)
|
||||||
|
lastSnapshot->Reference += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch(const std::exception& exc) {
|
||||||
|
MAKE_WARNING("Во время проверки зависимостей существующих подразделов произошла ошибка: \"" << object.Path.string()
|
||||||
|
<< "\"\n\t" << tabulate(exc.what()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Удалить ненужные снапшоты (на локальном хранилище должно остаться по одному экземпляру на каждый интервал политики)
|
||||||
|
for(auto& [id, storage] : storagesInfo) {
|
||||||
|
for(auto& [svinfo, path, refs] : storage) {
|
||||||
|
if(refs != 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
try {
|
||||||
|
std::vector<std::string> args;
|
||||||
|
args.push_back("subvolume");
|
||||||
|
args.push_back("delete");
|
||||||
|
args.push_back(path);
|
||||||
|
auto [code, output] = co_await runProc("btrfs", args);
|
||||||
|
if(code != 0) {
|
||||||
|
MAKE_ERROR(output);
|
||||||
|
}
|
||||||
|
} catch(const std::exception& exc) {
|
||||||
|
MAKE_WARNING("Ошибка удаления подраздела \"" << path
|
||||||
|
<< "\"\n\t" << tabulate(exc.what()));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "Удалено " << path << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
co_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Needshutdown = false;
|
||||||
|
asio::io_context IOC;
|
||||||
|
asio::steady_timer HourHalfTimer(IOC);
|
||||||
|
asio::signal_set SignalSet(IOC, SIGINT);
|
||||||
|
|
||||||
|
coro<> async_main() {
|
||||||
|
try {
|
||||||
|
std::cout << "Поиск точек монтирования хранилищ..." << std::endl;
|
||||||
|
for(const auto& [id, storage] : GlobalConf.Storages) {
|
||||||
|
std::string uuid = co_await getMountUUID(storage.Path);
|
||||||
|
UUIDToStorage[uuid] = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "Запускаем цикл" << std::endl;
|
||||||
|
|
||||||
|
while(!Needshutdown) {
|
||||||
|
HourHalfTimer.expires_after(asio::chrono::minutes(std::max<uint16_t>(IntervalLen / 6, 1)));
|
||||||
|
co_await cycle();
|
||||||
|
co_await HourHalfTimer.async_wait();
|
||||||
|
}
|
||||||
|
} catch(const std::exception& exc) {
|
||||||
|
std::cout << "Корутина завершилась ошибкой:\n\t" << tabulate(exc.what()) << std::endl;
|
||||||
|
SignalSet.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
// auto [code, data] = co_await runProc("btrfs", {"filesystem", "show"});
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
std::cout << "Загружаем конфигурацию..." << std::endl;
|
||||||
|
GlobalConf.load();
|
||||||
|
std::cout << "Готово" << std::endl;
|
||||||
|
|
||||||
|
SignalSet.async_wait([](const boost::system::error_code& errc, int code) {
|
||||||
|
if(errc)
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::cout << "Получена команда на завершение работы..." << std::endl;
|
||||||
|
Needshutdown = true;
|
||||||
|
HourHalfTimer.cancel();
|
||||||
|
});
|
||||||
|
|
||||||
|
asio::co_spawn(IOC, async_main(), asio::detached);
|
||||||
|
IOC.run();
|
||||||
|
|
||||||
|
std::cout << "Сохраняем конфигурацию..." << std::endl;
|
||||||
|
GlobalConf.save();
|
||||||
|
|
||||||
|
std::cout << "End of program" << std::endl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user