Мини релиз
This commit is contained in:
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