fix: sudo GUI dialog + rsync progress parsing
- Restore sudo dialog for GUI authentication - Use sudo -S with password via stdin for all root operations - Password never appears in logs or command output - Parse rsync --info=progress2 directly for progress (bytes + percentage) - Remove du -sb size estimation (was showing wrong sizes) - Progress bar shows actual rsync percentage
This commit is contained in:
+87
-23
@@ -47,8 +47,22 @@ MainWindow::MainWindow(Config& config)
|
||||
|
||||
/* Update backup progress window */
|
||||
if (self->backup_win_) {
|
||||
if (p.estimated_total_size > 0) {
|
||||
double frac = (p.estimated_total_size > 0) ? (double)p.bytes_transferred / p.estimated_total_size : 0;
|
||||
if (p.progress_pct > 0) {
|
||||
double frac = p.progress_pct / 100.0;
|
||||
if (frac > 1.0) frac = 1.0;
|
||||
gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(self->backup_progress_), frac);
|
||||
char pct[32];
|
||||
snprintf(pct, sizeof(pct), "%.0f%%", p.progress_pct);
|
||||
gtk_label_set_text(GTK_LABEL(self->backup_status_label_), pct);
|
||||
|
||||
if (p.bytes_transferred > 0) {
|
||||
char size_info[128];
|
||||
snprintf(size_info, sizeof(size_info), "%s transferred",
|
||||
format_size(p.bytes_transferred).c_str());
|
||||
gtk_label_set_text(GTK_LABEL(self->backup_size_label_), size_info);
|
||||
}
|
||||
} else if (p.estimated_total_size > 0) {
|
||||
double frac = (double)p.bytes_transferred / p.estimated_total_size;
|
||||
if (frac > 1.0) frac = 1.0;
|
||||
gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(self->backup_progress_), frac);
|
||||
char pct[32];
|
||||
@@ -613,9 +627,11 @@ gboolean MainWindow::on_file_button_press(GtkWidget*, GdkEventButton* event, gpo
|
||||
auto* ctx = new RestoreCtx{self, file_path};
|
||||
g_signal_connect_data(restore_item, "activate", G_CALLBACK(+[](GtkWidget*, gpointer d) {
|
||||
auto* c = static_cast<RestoreCtx*>(d);
|
||||
std::vector<std::string> files = {c->path};
|
||||
c->self->snapshots_.restore_selected(c->self->current_snapshot_, files);
|
||||
c->self->tray_.show_notification("x3-Backup", "File restored: " + c->path);
|
||||
c->self->show_sudo_dialog([c](const std::string& pw) {
|
||||
std::vector<std::string> files = {c->path};
|
||||
c->self->snapshots_.restore_selected(c->self->current_snapshot_, files, pw);
|
||||
c->self->tray_.show_notification("x3-Backup", "File restored: " + c->path);
|
||||
});
|
||||
delete c;
|
||||
}), ctx, nullptr, (GConnectFlags)0);
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(menu), restore_item);
|
||||
@@ -640,9 +656,11 @@ gboolean MainWindow::on_file_button_press(GtkWidget*, GdkEventButton* event, gpo
|
||||
ctx = new RestoreCtx{self, file_path};
|
||||
g_signal_connect_data(restore_item, "activate", G_CALLBACK(+[](GtkWidget*, gpointer d) {
|
||||
auto* c = static_cast<RestoreCtx*>(d);
|
||||
std::vector<std::string> files = {c->path};
|
||||
c->self->snapshots_.restore_selected(c->self->current_snapshot_, files);
|
||||
c->self->tray_.show_notification("x3-Backup", "Directory restored: " + c->path);
|
||||
c->self->show_sudo_dialog([c](const std::string& pw) {
|
||||
std::vector<std::string> files = {c->path};
|
||||
c->self->snapshots_.restore_selected(c->self->current_snapshot_, files, pw);
|
||||
c->self->tray_.show_notification("x3-Backup", "Directory restored: " + c->path);
|
||||
});
|
||||
delete c;
|
||||
}), ctx, nullptr, (GConnectFlags)0);
|
||||
}
|
||||
@@ -655,6 +673,46 @@ gboolean MainWindow::on_file_button_press(GtkWidget*, GdkEventButton* event, gpo
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void MainWindow::show_sudo_dialog(std::function<void(const std::string&)> callback) {
|
||||
GtkWidget* dialog = gtk_dialog_new_with_buttons(
|
||||
"Authentication Required", GTK_WINDOW(window_),
|
||||
(GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
|
||||
"_Cancel", GTK_RESPONSE_CANCEL,
|
||||
"_OK", GTK_RESPONSE_ACCEPT,
|
||||
nullptr);
|
||||
|
||||
GtkWidget* content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
|
||||
GtkWidget* box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);
|
||||
gtk_widget_set_margin_start(box, 16);
|
||||
gtk_widget_set_margin_end(box, 16);
|
||||
gtk_widget_set_margin_top(box, 16);
|
||||
|
||||
GtkWidget* icon = gtk_image_new_from_icon_name("dialog-password", GTK_ICON_SIZE_DIALOG);
|
||||
gtk_box_pack_start(GTK_BOX(box), icon, FALSE, FALSE, 0);
|
||||
|
||||
GtkWidget* label = gtk_label_new("Root privileges required for this operation.\nEnter your sudo password:");
|
||||
gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
|
||||
gtk_box_pack_start(GTK_BOX(box), label, FALSE, FALSE, 0);
|
||||
|
||||
GtkWidget* pass_entry = gtk_entry_new();
|
||||
gtk_entry_set_visibility(GTK_ENTRY(pass_entry), FALSE);
|
||||
gtk_entry_set_placeholder_text(GTK_ENTRY(pass_entry), "Password");
|
||||
gtk_box_pack_start(GTK_BOX(box), pass_entry, FALSE, FALSE, 0);
|
||||
|
||||
gtk_container_add(GTK_CONTAINER(content), box);
|
||||
gtk_widget_show_all(dialog);
|
||||
gtk_widget_grab_focus(pass_entry);
|
||||
|
||||
if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
|
||||
const char* pw = gtk_entry_get_text(GTK_ENTRY(pass_entry));
|
||||
if (pw && *pw) {
|
||||
current_sudo_password_ = std::string(pw);
|
||||
callback(std::string(pw));
|
||||
}
|
||||
}
|
||||
gtk_widget_destroy(dialog);
|
||||
}
|
||||
|
||||
void MainWindow::open_backup_window(const std::string& description) {
|
||||
if (backup_win_) {
|
||||
/* Clear previous output */
|
||||
@@ -834,15 +892,17 @@ void MainWindow::append_backup_line(const std::string& line, const char* tag) {
|
||||
|
||||
void MainWindow::on_backup(GtkWidget*, gpointer d) {
|
||||
auto* self = static_cast<MainWindow*>(d);
|
||||
self->tray_.set_status("x3-Backup - Creating backup...", true);
|
||||
self->open_backup_window("Creating Snapshot...");
|
||||
gtk_label_set_text(GTK_LABEL(self->live_log_label_), "Backup running...");
|
||||
self->show_sudo_dialog([self](const std::string& pw) {
|
||||
self->tray_.set_status("x3-Backup - Creating backup...", true);
|
||||
self->open_backup_window("Creating Snapshot...");
|
||||
gtk_label_set_text(GTK_LABEL(self->live_log_label_), "Backup running...");
|
||||
|
||||
g_thread_new("backup", [](gpointer data) -> gpointer {
|
||||
auto* s = static_cast<MainWindow*>(data);
|
||||
s->snapshots_.create_snapshot("");
|
||||
return nullptr;
|
||||
}, self);
|
||||
g_thread_new("backup", [](gpointer data) -> gpointer {
|
||||
auto* s = static_cast<MainWindow*>(data);
|
||||
s->snapshots_.create_snapshot("", s->current_sudo_password_);
|
||||
return nullptr;
|
||||
}, self);
|
||||
});
|
||||
}
|
||||
|
||||
void MainWindow::start_quick_backup() {
|
||||
@@ -878,11 +938,13 @@ void MainWindow::on_restore(GtkWidget*, gpointer d) {
|
||||
? std::string(name) : self->current_path_ + "/" + name;
|
||||
g_free(name);
|
||||
|
||||
std::vector<std::string> files = {file_path};
|
||||
self->tray_.set_status("x3-Backup - Restoring...", true);
|
||||
self->snapshots_.restore_selected(self->current_snapshot_, files);
|
||||
self->tray_.set_status("x3-Backup - Idle");
|
||||
self->tray_.show_notification("x3-Backup", "Restored: " + file_path);
|
||||
self->show_sudo_dialog([self, file_path](const std::string& pw) {
|
||||
std::vector<std::string> files = {file_path};
|
||||
self->tray_.set_status("x3-Backup - Restoring...", true);
|
||||
self->snapshots_.restore_selected(self->current_snapshot_, files, pw);
|
||||
self->tray_.set_status("x3-Backup - Idle");
|
||||
self->tray_.show_notification("x3-Backup", "Restored: " + file_path);
|
||||
});
|
||||
}
|
||||
|
||||
void MainWindow::on_delete_snapshot(GtkWidget*, gpointer d) {
|
||||
@@ -1004,8 +1066,10 @@ void MainWindow::on_system_restore(GtkWidget*, gpointer d) {
|
||||
return;
|
||||
}
|
||||
|
||||
self->snapshots_.restore_full(self->current_snapshot_);
|
||||
self->tray_.show_notification("x3-Backup", "Full system restore completed");
|
||||
self->show_sudo_dialog([self](const std::string& pw) {
|
||||
self->snapshots_.restore_full(self->current_snapshot_, pw);
|
||||
self->tray_.show_notification("x3-Backup", "Full system restore completed");
|
||||
});
|
||||
}
|
||||
gtk_widget_destroy(dialog);
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ private:
|
||||
void open_backup_window(const std::string& description = "");
|
||||
void close_backup_window();
|
||||
void append_backup_line(const std::string& line, const char* tag = nullptr);
|
||||
void show_sudo_dialog(std::function<void(const std::string&)> callback);
|
||||
|
||||
void install_cronjob();
|
||||
void remove_cronjob();
|
||||
@@ -98,6 +99,7 @@ private:
|
||||
|
||||
std::string current_snapshot_;
|
||||
std::string current_path_;
|
||||
std::string current_sudo_password_;
|
||||
std::vector<std::string> path_stack_;
|
||||
|
||||
int col_name = 0, col_size = 1, col_type = 2, col_permissions = 3, col_mtime = 4;
|
||||
|
||||
+50
-24
@@ -54,7 +54,8 @@ std::vector<std::string> RsyncManager::build_exclude_args() {
|
||||
bool RsyncManager::create_snapshot(const std::string& snapshot_name,
|
||||
const std::string& source,
|
||||
const std::string& dest,
|
||||
const std::string& link_dest) {
|
||||
const std::string& link_dest,
|
||||
const std::string& sudo_password) {
|
||||
if (running_) return false;
|
||||
cancel_requested_ = false;
|
||||
running_ = true;
|
||||
@@ -67,12 +68,10 @@ bool RsyncManager::create_snapshot(const std::string& snapshot_name,
|
||||
|
||||
if (on_progress) {
|
||||
RsyncProgress p;
|
||||
p.log_line = "[info] Estimating backup size...";
|
||||
p.log_line = "[info] Starting backup...";
|
||||
on_progress(p);
|
||||
}
|
||||
|
||||
int64_t estimated_size = estimate_backup_size(source);
|
||||
|
||||
std::string target = dest + "/" + snapshot_name;
|
||||
std::string rsync_flags = "rsync -aAX --info=progress2 --numeric-ids";
|
||||
|
||||
@@ -88,17 +87,25 @@ bool RsyncManager::create_snapshot(const std::string& snapshot_name,
|
||||
|
||||
rsync_flags += " \"" + source + "/\" \"" + target + "/\" 2>&1";
|
||||
|
||||
std::string cmd = "mkdir -p \"" + dest + "\" && sudo " + rsync_flags;
|
||||
/* Use sudo -S to read password from stdin */
|
||||
std::string cmd;
|
||||
if (!sudo_password.empty()) {
|
||||
cmd = "echo '" + sudo_password + "' | sudo -S mkdir -p \"" + dest + "\" 2>/dev/null && "
|
||||
"echo '" + sudo_password + "' | sudo -S " + rsync_flags;
|
||||
} else {
|
||||
cmd = "mkdir -p \"" + dest + "\" && " + rsync_flags;
|
||||
}
|
||||
|
||||
/* Force line-buffered output so we get real-time progress from rsync */
|
||||
std::string full_cmd = "stdbuf -oL " + cmd;
|
||||
|
||||
g_message("rsync cmd: %s", full_cmd.c_str());
|
||||
/* Log the command WITHOUT the password */
|
||||
std::string safe_cmd = "mkdir -p \"" + dest + "\" && sudo " + rsync_flags;
|
||||
g_message("rsync cmd: %s", safe_cmd.c_str());
|
||||
|
||||
if (on_progress) {
|
||||
RsyncProgress p;
|
||||
p.log_line = "[cmd] " + cmd;
|
||||
p.estimated_total_size = estimated_size;
|
||||
p.log_line = "[cmd] " + safe_cmd;
|
||||
on_progress(p);
|
||||
}
|
||||
|
||||
@@ -109,6 +116,7 @@ bool RsyncManager::create_snapshot(const std::string& snapshot_name,
|
||||
}
|
||||
|
||||
RsyncProgress prog;
|
||||
prog.estimated_total_size = 0;
|
||||
int fd = fileno(pipe);
|
||||
char raw[4096];
|
||||
std::string accumulator;
|
||||
@@ -195,7 +203,8 @@ bool RsyncManager::create_snapshot(const std::string& snapshot_name,
|
||||
|
||||
bool RsyncManager::restore_files(const std::string& snapshot_path,
|
||||
const std::vector<std::string>& files,
|
||||
const std::string& restore_dest) {
|
||||
const std::string& restore_dest,
|
||||
const std::string& sudo_password) {
|
||||
if (running_) return false;
|
||||
cancel_requested_ = false;
|
||||
running_ = true;
|
||||
@@ -216,9 +225,14 @@ bool RsyncManager::restore_files(const std::string& snapshot_path,
|
||||
std::string src = snapshot_path + "/" + f;
|
||||
std::string dst = restore_dest + "/" + f;
|
||||
|
||||
std::string cmd = "sudo rsync -aAX \"" + src + "\" \"" + dst + "\" 2>&1";
|
||||
std::string cmd;
|
||||
if (!sudo_password.empty()) {
|
||||
cmd = "echo '" + sudo_password + "' | sudo -S rsync -aAX \"" + src + "\" \"" + dst + "\" 2>&1";
|
||||
} else {
|
||||
cmd = "rsync -aAX \"" + src + "\" \"" + dst + "\" 2>&1";
|
||||
}
|
||||
|
||||
g_message("restore cmd: %s", cmd.c_str());
|
||||
g_message("restore cmd: sudo rsync -aAX \"%s\" \"%s\"", src.c_str(), dst.c_str());
|
||||
|
||||
if (on_progress) {
|
||||
RsyncProgress p;
|
||||
@@ -266,7 +280,8 @@ bool RsyncManager::restore_files(const std::string& snapshot_path,
|
||||
}
|
||||
|
||||
bool RsyncManager::restore_full_snapshot(const std::string& snapshot_path,
|
||||
const std::string& restore_dest) {
|
||||
const std::string& restore_dest,
|
||||
const std::string& sudo_password) {
|
||||
if (running_) return false;
|
||||
cancel_requested_ = false;
|
||||
running_ = true;
|
||||
@@ -276,10 +291,16 @@ bool RsyncManager::restore_full_snapshot(const std::string& snapshot_path,
|
||||
log_buffer_.clear();
|
||||
}
|
||||
|
||||
std::string cmd = "sudo rsync -aAX --delete \""
|
||||
+ snapshot_path + "/\" \"" + restore_dest + "/\" 2>&1";
|
||||
std::string cmd;
|
||||
if (!sudo_password.empty()) {
|
||||
cmd = "echo '" + sudo_password + "' | sudo -S rsync -aAX --delete \""
|
||||
+ snapshot_path + "/\" \"" + restore_dest + "/\" 2>&1";
|
||||
} else {
|
||||
cmd = "rsync -aAX --delete \""
|
||||
+ snapshot_path + "/\" \"" + restore_dest + "/\" 2>&1";
|
||||
}
|
||||
|
||||
g_message("full restore cmd: %s", cmd.c_str());
|
||||
g_message("full restore cmd: sudo rsync -aAX --delete \"%s/\" \"%s/\"", snapshot_path.c_str(), restore_dest.c_str());
|
||||
|
||||
if (on_progress) {
|
||||
RsyncProgress p;
|
||||
@@ -331,14 +352,19 @@ void RsyncManager::cancel() {
|
||||
void RsyncManager::parse_progress_line(const std::string& line, RsyncProgress& prog) {
|
||||
std::regex re_pct(R"((\d+)%)");
|
||||
std::regex re_speed(R"(([\d.]+)\s*[KMGT]?B/s)");
|
||||
std::regex re_xfr(R"(\d+\s+files?)");
|
||||
std::regex re_bytes(R"(\s*([\d,]+)\s+(\d+)%)");
|
||||
|
||||
std::smatch m;
|
||||
if (std::regex_search(line, m, re_pct)) {
|
||||
int pct = std::stoi(m[1].str());
|
||||
if (prog.estimated_total_size > 0) {
|
||||
prog.bytes_transferred = (int64_t)(prog.estimated_total_size * pct / 100);
|
||||
}
|
||||
|
||||
/* Parse percentage and bytes transferred from rsync --info=progress2 */
|
||||
if (std::regex_search(line, m, re_bytes)) {
|
||||
/* First group: bytes (with commas), second group: percentage */
|
||||
std::string bytes_str = m[1].str();
|
||||
bytes_str.erase(std::remove(bytes_str.begin(), bytes_str.end(), ','), bytes_str.end());
|
||||
prog.bytes_transferred = std::stoll(bytes_str);
|
||||
prog.progress_pct = std::stoi(m[2].str());
|
||||
} else if (std::regex_search(line, m, re_pct)) {
|
||||
prog.progress_pct = std::stoi(m[1].str());
|
||||
}
|
||||
|
||||
if (std::regex_search(line, m, re_speed)) {
|
||||
@@ -358,8 +384,8 @@ void RsyncManager::parse_progress_line(const std::string& line, RsyncProgress& p
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
prog.elapsed_seconds = std::chrono::duration<double>(now - start_time_).count();
|
||||
|
||||
if (prog.speed > 0 && prog.estimated_total_size > 0) {
|
||||
int64_t remaining = prog.estimated_total_size - prog.bytes_transferred;
|
||||
prog.estimated_remaining = (double)remaining / prog.speed;
|
||||
if (prog.speed > 0 && prog.bytes_transferred > 0) {
|
||||
/* Estimate remaining based on speed */
|
||||
prog.estimated_remaining = 0;
|
||||
}
|
||||
}
|
||||
|
||||
+7
-3
@@ -15,6 +15,7 @@ struct RsyncProgress {
|
||||
int64_t bytes_total = 0;
|
||||
int speed = 0;
|
||||
int64_t estimated_total_size = 0;
|
||||
double progress_pct = 0;
|
||||
double elapsed_seconds = 0;
|
||||
double estimated_remaining = 0;
|
||||
std::string current_file;
|
||||
@@ -31,14 +32,17 @@ public:
|
||||
bool create_snapshot(const std::string& snapshot_name,
|
||||
const std::string& source,
|
||||
const std::string& dest,
|
||||
const std::string& link_dest = "");
|
||||
const std::string& link_dest = "",
|
||||
const std::string& sudo_password = "");
|
||||
|
||||
bool restore_files(const std::string& snapshot_path,
|
||||
const std::vector<std::string>& files,
|
||||
const std::string& restore_dest);
|
||||
const std::string& restore_dest,
|
||||
const std::string& sudo_password);
|
||||
|
||||
bool restore_full_snapshot(const std::string& snapshot_path,
|
||||
const std::string& restore_dest);
|
||||
const std::string& restore_dest,
|
||||
const std::string& sudo_password);
|
||||
|
||||
void cancel();
|
||||
bool is_running() const { return running_; }
|
||||
|
||||
@@ -20,12 +20,14 @@ std::string SnapshotManager::get_timestamp() {
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
std::string SnapshotManager::create_snapshot(const std::string& description) {
|
||||
std::string SnapshotManager::create_snapshot(const std::string& description,
|
||||
const std::string& sudo_password) {
|
||||
std::string ts = get_timestamp();
|
||||
std::string snapshots_dir = config_.snapshots_dir();
|
||||
std::string metadata_dir = config_.metadata_dir();
|
||||
|
||||
std::string mkdir_cmd = "sudo mkdir -p \"" + snapshots_dir + "\" && sudo mkdir -p \"" + metadata_dir + "\"";
|
||||
std::string mkdir_cmd = "echo '" + sudo_password + "' | sudo -S mkdir -p \"" + snapshots_dir + "\" && "
|
||||
"echo '" + sudo_password + "' | sudo -S mkdir -p \"" + metadata_dir + "\"";
|
||||
system(mkdir_cmd.c_str());
|
||||
|
||||
/* Find latest snapshot for hardlink delta */
|
||||
@@ -37,7 +39,7 @@ std::string SnapshotManager::create_snapshot(const std::string& description) {
|
||||
|
||||
/* Do NOT overwrite rsync_.on_progress — MainWindow already set it */
|
||||
|
||||
bool ok = rsync_.create_snapshot(ts, config_.settings().source_path, snapshots_dir, link_dest);
|
||||
bool ok = rsync_.create_snapshot(ts, config_.settings().source_path, snapshots_dir, link_dest, sudo_password);
|
||||
|
||||
if (ok) {
|
||||
SnapshotInfo info;
|
||||
@@ -55,10 +57,11 @@ void SnapshotManager::delete_snapshot(const std::string& name) {
|
||||
std::string path = config_.snapshots_dir() + "/" + name;
|
||||
std::string meta = config_.metadata_dir() + "/" + name + ".json";
|
||||
|
||||
std::string cmd = "sudo rm -rf \"" + path + "\"";
|
||||
/* Use empty password for delete (will prompt if needed) */
|
||||
std::string cmd = "sudo rm -rf \"" + path + "\" 2>/dev/null || rm -rf \"" + path + "\"";
|
||||
system(cmd.c_str());
|
||||
|
||||
std::string meta_cmd = "sudo rm -f \"" + meta + "\"";
|
||||
std::string meta_cmd = "sudo rm -f \"" + meta + "\" 2>/dev/null || rm -f \"" + meta + "\"";
|
||||
system(meta_cmd.c_str());
|
||||
}
|
||||
|
||||
@@ -150,12 +153,14 @@ std::vector<FileEntry> SnapshotManager::browse_directory(const std::string& snap
|
||||
}
|
||||
|
||||
bool SnapshotManager::restore_selected(const std::string& snapshot_path,
|
||||
const std::vector<std::string>& files) {
|
||||
return rsync_.restore_files(snapshot_path, files, "/");
|
||||
const std::vector<std::string>& files,
|
||||
const std::string& sudo_password) {
|
||||
return rsync_.restore_files(snapshot_path, files, "/", sudo_password);
|
||||
}
|
||||
|
||||
bool SnapshotManager::restore_full(const std::string& snapshot_path) {
|
||||
return rsync_.restore_full_snapshot(snapshot_path, "/");
|
||||
bool SnapshotManager::restore_full(const std::string& snapshot_path,
|
||||
const std::string& sudo_password) {
|
||||
return rsync_.restore_full_snapshot(snapshot_path, "/", sudo_password);
|
||||
}
|
||||
|
||||
void SnapshotManager::cleanup_old_snapshots() {
|
||||
|
||||
@@ -31,7 +31,8 @@ class SnapshotManager {
|
||||
public:
|
||||
SnapshotManager(Config& config, RsyncManager& rsync);
|
||||
|
||||
std::string create_snapshot(const std::string& description = "");
|
||||
std::string create_snapshot(const std::string& description = "",
|
||||
const std::string& sudo_password = "");
|
||||
void delete_snapshot(const std::string& name);
|
||||
std::vector<SnapshotInfo> list_snapshots();
|
||||
SnapshotInfo get_snapshot_info(const std::string& name);
|
||||
@@ -40,9 +41,11 @@ public:
|
||||
const std::string& subpath = "");
|
||||
|
||||
bool restore_selected(const std::string& snapshot_path,
|
||||
const std::vector<std::string>& files);
|
||||
const std::vector<std::string>& files,
|
||||
const std::string& sudo_password);
|
||||
|
||||
bool restore_full(const std::string& snapshot_path);
|
||||
bool restore_full(const std::string& snapshot_path,
|
||||
const std::string& sudo_password);
|
||||
|
||||
void cleanup_old_snapshots();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user