diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..0b43ae4 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // Verwendet IntelliSense zum Ermitteln möglicher Attribute. + // Zeigen Sie auf vorhandene Attribute, um die zugehörigen Beschreibungen anzuzeigen. + // Weitere Informationen finden Sie unter https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug", + "program": "${command:cmake.launchTargetPath}", + "args": ["workstation"], + "cwd": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/main.cpp b/main.cpp index d6e6d02..b59950c 100644 --- a/main.cpp +++ b/main.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -24,107 +25,160 @@ namespace asio = boost::asio; namespace po = boost::program_options; namespace fs = boost::filesystem; -bool dry_run = false; -templatestd::set::reverse_iterator handle_backups_after(std::string & snapshotbase, std::set & times, std::set::reverse_iterator ct, boost::posix_time::ptime now) +class zfsbackupcleaner { - if(ct == times.rend()) - return ct; - - boost::posix_time::time_duration dur; - boost::posix_time::ptime last_kept_time; - - while( ct != times.rend() && (dur = now - *ct).hours() <= till_y_day * 24) +public: + zfsbackupcleaner(std::string backupsystem): _backupsystem(backupsystem), _now(boost::posix_time::second_clock::local_time()), _dry_run(false) { - last_kept_time = *ct; - std::cout << "Keeping Backup " << last_kept_time << " of this day." << std::endl << std::endl; - ct++; + std::cout << "Gehe aus von aktueller Zeit: " << _now << std::endl << std::endl; + } - while( ct != times.rend() && (dur = last_kept_time - *ct).hours() < keep_every_x_days * 24) + void set_dry_run(bool val = true) { _dry_run = val; } + + void read_backups() + { + asio::io_context ctx; + asio::readable_pipe out(ctx); + + bp::process proc(ctx, bp::environment::find_executable("zfs"), {"list", "-t", "snapshot", "/backup/"+_backupsystem}, bp::process_stdio{{/* in to default*/}, out, {/* err to default */}}); + + std::string output; + + boost::system::error_code ec; + asio::read(out, asio::dynamic_buffer(output), ec); + assert(!ec || (ec==asio::error::eof)); + proc.wait(); + + std::stringstream lines(output); + + for(std::string line; std::getline(lines, line);) { - if(ct == --times.rend()) + std::string::size_type pos = line.find(' '); + line = line.substr(0,pos); + //std::cout << "X: " << line << std::endl; + + if(line == "NAME") + continue; + + pos = line.find('@'); + _backupbase = line.substr(0,pos); + std::string timestring = line.substr(pos+1); + timestring[10]=' '; + timestring[13]=':'; + timestring[16]=':'; + boost::posix_time::ptime t(boost::posix_time::time_from_string(timestring)); + + _backuptimes.insert(t); + } + } + + void clean_backups () + { + if(_backuptimes.empty()) + { + std::cout << "Keine Backups für >" << _backupsystem << "< gefunden." << std::endl << "Keine weitere Behandlung." << std::endl; + return; + } + + if(_backuptimes.size() < 7) + { + std::cout << "Weniger als 7 Backups für >" << _backupsystem << "< gefunden." << std::endl << "Keine weitere Behandlung." << std::endl; + return; + } + + // Hinweis: Die Backups werden so von "zfs list" übermittelt, dass das älteste am Anfang steht und das jüngste am Ende der Liste + + _iter_current=_backuptimes.begin(); + _iter_last_to_handle = _backuptimes.end(); + std::advance(_iter_last_to_handle, -7); // Die letzten sieben Backups werden besonders behandelt - sie bleiben in jedem Fall erhalten! + + // Nun von hinten her die Backups durchgehen + + // A) Das allererste Backup behalten wir in jedem Fall + std::cout << "Behalte allererstes Backup " << *_iter_current << " in diesem Backup-Set." << std::endl << std::endl; + _iter_current++; + + // B) after 365 days, we keep one backup every 30 days + handle_backups<30,365>(); + + // C) after 30 days, we keep one backup every 7 days + handle_backups<7,30>(); + + // D) after one day, we keep one backup per day + handle_backups<1,1>(); + + // E) Behalte die letzten bis zu sieben Backups + for(; _iter_current != _backuptimes.end(); _iter_current++) + { + + std::cout << "Behalte Backup " << *_iter_current << ", weil es zu den letzten 7 gehört." << std::endl; + } + } + +private: + + template void handle_backups() + { + if(_iter_current == _iter_last_to_handle) + return; + + boost::gregorian::date_duration dur; + boost::posix_time::ptime last_kept_time; + + while( _iter_current != _iter_last_to_handle && (dur = _now.date() - _iter_current->date()) > boost::gregorian::days(after_y_day)) + { + last_kept_time = *_iter_current; + std::cout << "Behalte Backup " << last_kept_time << " [Älter als " << after_y_day << ", Backup alle " << keep_every_x_days << " Tage behalten]." << std::endl << std::endl; + _iter_current++; + + while( _iter_current != _iter_last_to_handle && (dur = _iter_current->date() - last_kept_time.date()) < boost::gregorian::days(keep_every_x_days)) { - std::cout << "Keeping last Backup " << *ct << " in this backup-set." << std::endl << std::endl; - return ct++; + std::cout << "Entferne Backup " << *_iter_current << " weil wir bereits eines im aktuellen Zyklus haben [Älter als " << after_y_day << ", Backup alle " << keep_every_x_days << " Tage behalten]." << std::endl; + + if(!_dry_run) + { + std::string timestring = boost::posix_time::to_iso_extended_string(*_iter_current); + timestring[10]='_'; + timestring[13]='-'; + timestring[16]='-'; + timestring=timestring.substr(0, 19); + + asio::io_context ctx; + asio::readable_pipe out(ctx); + + std::vector args; + args.push_back("destroy"); + args.push_back("-v"); + if(_dry_run) + args.push_back("-n"); + args.push_back(_backupbase + "@" + timestring); + + bp::process proc(ctx, bp::environment::find_executable("zfs"), args, bp::process_stdio{{/* in to default*/}, out, {/* err to default */}}); + + std::string output; + + boost::system::error_code ec; + asio::read(out, asio::dynamic_buffer(output), ec); + assert(!ec || (ec==asio::error::eof)); + proc.wait(); + + std::cout << output << std::endl; + } + _iter_current++; } - - std::cout << "Removing Backup " << *ct << " because we have already one in the relevant set of keeping every " << keep_every_x_days << " till " << till_y_day << " days." << std::endl; - - std::string timestring = boost::posix_time::to_iso_extended_string(*ct); - timestring[10]='_'; - timestring[13]='-'; - timestring[16]='-'; - timestring=timestring.substr(0, 19); - - asio::io_context ctx; - asio::readable_pipe out(ctx); - - std::vector args; - args.push_back("destroy"); - args.push_back("-v"); - if(dry_run) - args.push_back("-n"); - args.push_back(snapshotbase + "@" + timestring); - - bp::process proc(ctx, bp::environment::find_executable("zfs"), args, bp::process_stdio{{/* in to default*/}, out, {/* err to default */}}); - - std::string output; - - boost::system::error_code ec; - asio::read(out, asio::dynamic_buffer(output), ec); - assert(!ec || (ec==asio::error::eof)); - proc.wait(); - - std::cout << output << std::endl; - - ct++; } } - return ct; -} + std::string _backupsystem; + boost::posix_time::ptime _now; + bool _dry_run; -std::set::reverse_iterator handle_backups_after_one_day(std::string & snapshotbase, std::set & times, std::set::reverse_iterator ct, boost::posix_time::ptime now) -{ - // after one day, we keep one backup per day - - return handle_backups_after<1,30>(snapshotbase, times, ct, now); - - /*if(ct == times.rend()) - return ct; - - boost::posix_time::time_duration dur; - boost::posix_time::ptime last_kept_time; - - while( ct != times.rend() && (dur = now - *ct).hours() <= 30 * 24) - { - last_kept_time = *ct; - std::cout << "Keeping Backup " << last_kept_time << " of this day." << std::endl; - ct++; - - while( ct != times.rend() && (dur = last_kept_time - *ct).hours() < 24) - { - std::cout << "Removing Backup " << *ct << " because we have already one in 24 hours." << std::endl; - ct++; - } - } - - return ct;*/ -} - -std::set::reverse_iterator handle_backups_after_30_days(std::string & snapshotbase, std::set & times, std::set::reverse_iterator ct, boost::posix_time::ptime now) -{ - // after 30 days, we keep one backup every 7 days - - return handle_backups_after<7,365>(snapshotbase, times, ct, now); -} - -std::set::reverse_iterator handle_backups_after_365_days(std::string & snapshotbase, std::set & times, std::set::reverse_iterator ct, boost::posix_time::ptime now) -{ - // after 365 days, we keep one backup every 30 days - - return handle_backups_after<30, 365*100>(snapshotbase, times, ct, now); -} + std::string _backupbase; + std::set _backuptimes; + std::set::iterator _iter_current; + std::set::iterator _iter_last_to_handle; +}; int main(int argc, char** argv){ @@ -156,10 +210,7 @@ int main(int argc, char** argv){ std::cout << options << std::endl; return 0; } - - if(vm.count("dry-run")) - dry_run = true; - + std::string backupsystem = vm["backupsystem"].as(); if(backupsystem != "workstation" && ! fs::exists("/backup/"+backupsystem)) @@ -168,72 +219,14 @@ int main(int argc, char** argv){ return -1; } - asio::io_context ctx; - asio::readable_pipe out(ctx); + zfsbackupcleaner zbc(backupsystem); - bp::process proc(ctx, bp::environment::find_executable("zfs"), {"list", "-t", "snapshot", "/backup/"+backupsystem}, bp::process_stdio{{/* in to default*/}, out, {/* err to default */}}); + if(vm.count("dry-run")) + zbc.set_dry_run(true); - std::string output; - - boost::system::error_code ec; - asio::read(out, asio::dynamic_buffer(output), ec); - assert(!ec || (ec==asio::error::eof)); - proc.wait(); - - //std::cout << "OUTPUT" << output << "END" << std::endl; - - std::stringstream lines(output); - - std::set times; - - std::string snapshotbase; - - for(std::string line; std::getline(lines, line);) - { - std::string::size_type pos = line.find(' '); - line = line.substr(0,pos); - //std::cout << "X: " << line << std::endl; - - if(line == "NAME") - continue; - - pos = line.find('@'); - snapshotbase = line.substr(0,pos); - std::string timestring = line.substr(pos+1); - timestring[10]=' '; - timestring[13]=':'; - timestring[16]=':'; - boost::posix_time::ptime t(boost::posix_time::time_from_string(timestring)); - - times.insert(t); - } - - if(times.empty()) - { - std::cout << "NO SNAPSHOTS FOUND — EXIT"; - return 0; - } - - std::set::reverse_iterator current_time = times.rbegin(); - - // handle fist 24 hours!!! - - boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); - - std::cout << "Gehe aus von aktueller Zeit: " << now << std::endl << std::endl; - - boost::posix_time::time_duration dur; + zbc.read_backups(); - while( (dur = now - *current_time).hours() <= 72) - { - std::cout << "Omitting Backup " << *current_time << " because it is too young to handle (" << dur.hours() << " hours old)." << std::endl; - current_time++; - } - std::cout << std::endl; + zbc.clean_backups(); - current_time = handle_backups_after_one_day(snapshotbase, times, current_time, now); - current_time = handle_backups_after_30_days(snapshotbase, times, current_time, now); - current_time = handle_backups_after_365_days(snapshotbase, times, current_time, now); - return 0; } diff --git a/main.cpp.wrong b/main.cpp.wrong new file mode 100644 index 0000000..dc70736 --- /dev/null +++ b/main.cpp.wrong @@ -0,0 +1,242 @@ +// ACHTUNG: Die Backups müssen von hinten her, also vor ersten Backup aus, bis NOW durchgegangen werden, nicht umgekehrt!!! + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include + +namespace bp = boost::process; +namespace asio = boost::asio; +namespace po = boost::program_options; +namespace fs = boost::filesystem; + +bool dry_run = false; + +templatestd::set::reverse_iterator handle_backups_after(std::string & snapshotbase, std::set & times, std::set::reverse_iterator ct, boost::posix_time::ptime now) +{ + if(ct == times.rend()) + return ct; + + boost::posix_time::time_duration dur; + boost::posix_time::ptime last_kept_time; + + while( ct != times.rend() && (dur = now - *ct).hours() <= till_y_day * 24) + { + last_kept_time = *ct; + std::cout << "Keeping Backup " << last_kept_time << " of this day." << std::endl << std::endl; + ct++; + + while( ct != times.rend() && (dur = last_kept_time - *ct).hours() < keep_every_x_days * 24) + { + if(ct == --times.rend()) + { + std::cout << "Keeping last Backup " << *ct << " in this backup-set." << std::endl << std::endl; + return ct++; + } + + std::cout << "Removing Backup " << *ct << " because we have already one in the relevant set of keeping every " << keep_every_x_days << " till " << till_y_day << " days." << std::endl; + + std::string timestring = boost::posix_time::to_iso_extended_string(*ct); + timestring[10]='_'; + timestring[13]='-'; + timestring[16]='-'; + timestring=timestring.substr(0, 19); + + asio::io_context ctx; + asio::readable_pipe out(ctx); + + std::vector args; + args.push_back("destroy"); + args.push_back("-v"); + if(dry_run) + args.push_back("-n"); + args.push_back(snapshotbase + "@" + timestring); + + bp::process proc(ctx, bp::environment::find_executable("zfs"), args, bp::process_stdio{{/* in to default*/}, out, {/* err to default */}}); + + std::string output; + + boost::system::error_code ec; + asio::read(out, asio::dynamic_buffer(output), ec); + assert(!ec || (ec==asio::error::eof)); + proc.wait(); + + std::cout << output << std::endl; + + ct++; + } + } + + return ct; +} + +std::set::reverse_iterator handle_backups_after_one_day(std::string & snapshotbase, std::set & times, std::set::reverse_iterator ct, boost::posix_time::ptime now) +{ + // after one day, we keep one backup per day + + return handle_backups_after<1,30>(snapshotbase, times, ct, now); + + /*if(ct == times.rend()) + return ct; + + boost::posix_time::time_duration dur; + boost::posix_time::ptime last_kept_time; + + while( ct != times.rend() && (dur = now - *ct).hours() <= 30 * 24) + { + last_kept_time = *ct; + std::cout << "Keeping Backup " << last_kept_time << " of this day." << std::endl; + ct++; + + while( ct != times.rend() && (dur = last_kept_time - *ct).hours() < 24) + { + std::cout << "Removing Backup " << *ct << " because we have already one in 24 hours." << std::endl; + ct++; + } + } + + return ct;*/ +} + +std::set::reverse_iterator handle_backups_after_30_days(std::string & snapshotbase, std::set & times, std::set::reverse_iterator ct, boost::posix_time::ptime now) +{ + // after 30 days, we keep one backup every 7 days + + return handle_backups_after<7,365>(snapshotbase, times, ct, now); +} + +std::set::reverse_iterator handle_backups_after_365_days(std::string & snapshotbase, std::set & times, std::set::reverse_iterator ct, boost::posix_time::ptime now) +{ + // after 365 days, we keep one backup every 30 days + + return handle_backups_after<30, 365*100>(snapshotbase, times, ct, now); +} + + +int main(int argc, char** argv){ + std::cout << "Hello, from zfsbackupcleaner!\n"; + + po::options_description options("Argumente"); + options.add_options() + ("help", "Hilfe anzeigen") + ("dry-run,n", "Test-Modus; keine Backups entfernen") + ("backupsystem", po::value(), "Computer, dessen Backups bereinigt werden sollen") + ; + + po::positional_options_description p; + p.add("backupsystem", 1); + + po::variables_map vm; + try { + po::store(po::command_line_parser(argc,argv).options(options).positional(p).run(), vm); + po::notify(vm); + } catch (const boost::program_options::invalid_command_line_syntax & error) { + std::cout << "Error parsing commandline-arguments:" << std::endl; + std::cout << error.what() << std::endl; + return -1; + } + + if(vm.count("help") || !vm.count("backupsystem")) + { + std::cout << "Benutzung: zfsbackupcleaner [Optionen] " << std::endl << std::endl; + std::cout << options << std::endl; + return 0; + } + + if(vm.count("dry-run")) + dry_run = true; + + std::string backupsystem = vm["backupsystem"].as(); + + if(backupsystem != "workstation" && ! fs::exists("/backup/"+backupsystem)) + { + std::cout << "Für das System >" << backupsystem << "< gibt es keine Backups." << std::endl; + return -1; + } + + asio::io_context ctx; + asio::readable_pipe out(ctx); + + bp::process proc(ctx, bp::environment::find_executable("zfs"), {"list", "-t", "snapshot", "/backup/"+backupsystem}, bp::process_stdio{{/* in to default*/}, out, {/* err to default */}}); + + std::string output; + + boost::system::error_code ec; + asio::read(out, asio::dynamic_buffer(output), ec); + assert(!ec || (ec==asio::error::eof)); + proc.wait(); + + //std::cout << "OUTPUT" << output << "END" << std::endl; + + std::stringstream lines(output); + + std::set times; + + std::string snapshotbase; + + for(std::string line; std::getline(lines, line);) + { + std::string::size_type pos = line.find(' '); + line = line.substr(0,pos); + //std::cout << "X: " << line << std::endl; + + if(line == "NAME") + continue; + + pos = line.find('@'); + snapshotbase = line.substr(0,pos); + std::string timestring = line.substr(pos+1); + timestring[10]=' '; + timestring[13]=':'; + timestring[16]=':'; + boost::posix_time::ptime t(boost::posix_time::time_from_string(timestring)); + + times.insert(t); + } + + if(times.empty()) + { + std::cout << "NO SNAPSHOTS FOUND — EXIT"; + return 0; + } + + std::set::reverse_iterator current_time = times.rbegin(); + + // handle fist 24 hours!!! + + boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); + + std::cout << "Gehe aus von aktueller Zeit: " << now << std::endl << std::endl; + + boost::posix_time::time_duration dur; + + while( (dur = now - *current_time).hours() <= 72) + { + std::cout << "Omitting Backup " << *current_time << " because it is too young to handle (" << dur.hours() << " hours old)." << std::endl; + current_time++; + } + std::cout << std::endl; + + current_time = handle_backups_after_one_day(snapshotbase, times, current_time, now); + current_time = handle_backups_after_30_days(snapshotbase, times, current_time, now); + current_time = handle_backups_after_365_days(snapshotbase, times, current_time, now); + + return 0; +}