fix: Backup-Snapshots werden jetzt mit sudo erstellt fuer korrekte Berechtigungen
Problem: Backups wurden ohne Root-Rechte erstellt. Dadurch wurden Datei- berechtigungen (UID/GID, rwx, ACLs, xattrs) nicht korrekt uebernommen. Root-Dateien konnten nicht gelesen werden. Backups waren unvollstaendig. Loesung: - rsync_manager: create_snapshot() nutzt jetzt sudo fuer rsync - rsync_manager: --numeric-ids Flag hinzugefuegt fuer korrekte UID/GID - snapshot_manager: mkdir mit sudo fuer Root-Besitz der Snapshots - snapshot_manager: delete_snapshot mit sudo (Snapshots sind Root-owned) - main_window: Sudo-Passwort-Dialog vor jedem Backup - main_window: Dialog-Text erweitert (Backup erfordert sudo) - main.cpp: --sudo-password Argument fuer CLI/Cron-Modus - main.cpp: Interaktive Passwort-Eingabe im CLI-Modus - README.md: Build-Abhaengigkeiten fuer alle Distros hinzugefuegt - README.md: Rechte-Tabelle aktualisiert - README.md: Cronjob-Beispiel mit Passwort-Speicherung aktualisiert Betroffene Dateien: src/rsync_manager.h, src/rsync_manager.cpp src/snapshot_manager.h, src/snapshot_manager.cpp src/main_window.h, src/main_window.cpp src/main.cpp README.md
This commit is contained in:
@@ -151,11 +151,14 @@ Platzbedarf: voll + delta (statt 2x voll)
|
|||||||
## CLI Nutzung
|
## CLI Nutzung
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Backup jetzt erstellen und beenden
|
# Backup jetzt erstellen (fragt nach sudo-Passwort)
|
||||||
x3-backup --backup
|
x3-backup --backup
|
||||||
|
|
||||||
|
# Backup mit Passwort-Argument (z.B. fuer Cron)
|
||||||
|
x3-backup --backup --sudo-password "DEIN_PASSWORT"
|
||||||
|
|
||||||
# System-Backup (von Live-ISO)
|
# System-Backup (von Live-ISO)
|
||||||
x3-backup --system-backup
|
x3-backup --system-backup --sudo-password "DEIN_PASSWORT"
|
||||||
|
|
||||||
# Log auf STDOUT ausgeben
|
# Log auf STDOUT ausgeben
|
||||||
x3-backup --backup --log
|
x3-backup --backup --log
|
||||||
@@ -167,14 +170,26 @@ x3-backup --help
|
|||||||
## Cronjob Beispiel
|
## Cronjob Beispiel
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# WICHTIG: Sudo-Passwort in Datei speichern fuer Cron:
|
||||||
|
echo "DEIN_PASSWORT" > ~/.config/x3-backup/cron-pw.conf
|
||||||
|
chmod 600 ~/.config/x3-backup/cron-pw.conf
|
||||||
|
|
||||||
|
# Script fuer Cron erstellen:
|
||||||
|
cat > ~/.config/x3-backup/run-backup.sh << 'EOF'
|
||||||
|
#!/bin/bash
|
||||||
|
PW=$(cat ~/.config/x3-backup/cron-pw.conf 2>/dev/null)
|
||||||
|
/usr/local/bin/x3-backup --backup --sudo-password "$PW" >> ~/.config/x3-backup/x3-backup.log 2>&1
|
||||||
|
EOF
|
||||||
|
chmod +x ~/.config/x3-backup/run-backup.sh
|
||||||
|
|
||||||
# Taeglich um 2:00 Uhr
|
# Taeglich um 2:00 Uhr
|
||||||
0 2 * * * /usr/local/bin/x3-backup --backup &
|
0 2 * * * ~/.config/x3-backup/run-backup.sh
|
||||||
|
|
||||||
# Alle 6 Stunden
|
# Alle 6 Stunden
|
||||||
0 */6 * * * /usr/local/bin/x3-backup --backup &
|
0 */6 * * * ~/.config/x3-backup/run-backup.sh
|
||||||
|
|
||||||
# Woechentlich Sonntag 3:00 Uhr
|
# Woechentlich Sonntag 3:00 Uhr
|
||||||
0 3 * * 0 /usr/local/bin/x3-backup --backup &
|
0 3 * * 0 ~/.config/x3-backup/run-backup.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
## Log Format
|
## Log Format
|
||||||
@@ -190,17 +205,51 @@ x3-backup --help
|
|||||||
|
|
||||||
| Aktion | Benoetigte Rechte |
|
| Aktion | Benoetigte Rechte |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Backup erstellen | Normaler User (nur lesend auf Quelle) |
|
| Backup erstellen | sudo (fuer korrekte Berechtigungen) |
|
||||||
| Datei wiederherstellen | sudo (fuer Root-Berechtigungen) |
|
| Datei wiederherstellen | sudo (fuer Root-Berechtigungen) |
|
||||||
| System wiederherstellen | sudo |
|
| System wiederherstellen | sudo |
|
||||||
| Cronjob installieren | Normaler User (crontab -e) |
|
| Cronjob installieren | Normaler User (crontab -e) |
|
||||||
|
|
||||||
|
**Wichtig:** Backups werden mit sudo ausgefuehrt, um alle Dateiberechtigungen (UID/GID, rwx, ACLs, xattrs)
|
||||||
|
rekursiv korrekt zu uebernehmen. Ohne sudo waere das Backup unvollstaendig, da Root-Dateien nicht
|
||||||
|
gelesen und Berechtigungen nicht preserve werden koennen.
|
||||||
|
|
||||||
## Abhaengigkeiten
|
## Abhaengigkeiten
|
||||||
|
|
||||||
|
### Build-Abhaengigkeiten
|
||||||
|
|
||||||
|
**Ubuntu / Debian:**
|
||||||
|
```bash
|
||||||
|
sudo apt install build-essential autoconf automake pkg-config libgtk-3-dev libjson-glib-dev rsync
|
||||||
|
```
|
||||||
|
|
||||||
|
**Arch Linux:**
|
||||||
|
```bash
|
||||||
|
sudo pacman -S base-devel gtk3 json-glib rsync
|
||||||
|
```
|
||||||
|
|
||||||
|
**Gentoo Linux:**
|
||||||
|
```bash
|
||||||
|
sudo emerge --ask dev-build/autoconf dev-build/automake dev-util/pkgconfig x11-libs/gtk+:3 dev-libs/json-glib net-misc/rsync
|
||||||
|
```
|
||||||
|
|
||||||
|
**openSUSE:**
|
||||||
|
```bash
|
||||||
|
sudo zypper install -y gcc-c++ autoconf automake pkg-config gtk3-devel json-glib-devel rsync
|
||||||
|
```
|
||||||
|
|
||||||
|
**Fedora:**
|
||||||
|
```bash
|
||||||
|
sudo dnf install gcc-c++ autoconf automake pkgconfig gtk3-devel json-glib-devel rsync
|
||||||
|
```
|
||||||
|
|
||||||
|
### Laufzeit-Abhaengigkeiten
|
||||||
|
|
||||||
- GTK+ 3.0 (>= 3.24)
|
- GTK+ 3.0 (>= 3.24)
|
||||||
- json-glib-1.0
|
- json-glib-1.0
|
||||||
- rsync
|
- rsync
|
||||||
- crontab (optional)
|
- sudo (fuer Root-Rechte bei Backups)
|
||||||
|
- crontab (optional, fuer geplante Backups)
|
||||||
|
|
||||||
## Architektur
|
## Architektur
|
||||||
|
|
||||||
|
|||||||
+28
-2
@@ -77,6 +77,7 @@ int main(int argc, char* argv[]) {
|
|||||||
bool backup_now = false;
|
bool backup_now = false;
|
||||||
bool show_help = false;
|
bool show_help = false;
|
||||||
bool cli_mode = false;
|
bool cli_mode = false;
|
||||||
|
std::string sudo_password;
|
||||||
|
|
||||||
for (int i = 1; i < argc; i++) {
|
for (int i = 1; i < argc; i++) {
|
||||||
if (strcmp(argv[i], "--system-backup") == 0) {
|
if (strcmp(argv[i], "--system-backup") == 0) {
|
||||||
@@ -90,6 +91,8 @@ int main(int argc, char* argv[]) {
|
|||||||
show_help = true;
|
show_help = true;
|
||||||
} else if (strcmp(argv[i], "--log") == 0) {
|
} else if (strcmp(argv[i], "--log") == 0) {
|
||||||
log_to_stdout = true;
|
log_to_stdout = true;
|
||||||
|
} else if (strcmp(argv[i], "--sudo-password") == 0 && i + 1 < argc) {
|
||||||
|
sudo_password = argv[++i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,9 +104,12 @@ int main(int argc, char* argv[]) {
|
|||||||
g_print("Options:\n");
|
g_print("Options:\n");
|
||||||
g_print(" --backup Create a backup immediately and exit\n");
|
g_print(" --backup Create a backup immediately and exit\n");
|
||||||
g_print(" --system-backup Create a full system backup and exit\n");
|
g_print(" --system-backup Create a full system backup and exit\n");
|
||||||
|
g_print(" --sudo-password PW Provide sudo password for backup (required for root permissions)\n");
|
||||||
g_print(" --log Print log to stdout\n");
|
g_print(" --log Print log to stdout\n");
|
||||||
g_print(" --help, -h Show this help\n\n");
|
g_print(" --help, -h Show this help\n\n");
|
||||||
g_print("Without options, x3-backup starts in GUI mode with system tray.\n\n");
|
g_print("Without options, x3-backup starts in GUI mode with system tray.\n\n");
|
||||||
|
g_print("Note: Backups require sudo to preserve file ownership and permissions.\n");
|
||||||
|
g_print(" Use --sudo-password or run interactively for password prompt.\n\n");
|
||||||
g_print("Log file: ~/.config/x3-backup/x3-backup.log\n");
|
g_print("Log file: ~/.config/x3-backup/x3-backup.log\n");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -118,12 +124,32 @@ int main(int argc, char* argv[]) {
|
|||||||
g_message("System backup mode: backing up entire system");
|
g_message("System backup mode: backing up entire system");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Prompt for sudo password if not provided */
|
||||||
|
if (sudo_password.empty()) {
|
||||||
|
g_print("Sudo password required for backup (to preserve file permissions):\n");
|
||||||
|
g_print("Password: ");
|
||||||
|
fflush(stdout);
|
||||||
|
char* pw = nullptr;
|
||||||
|
size_t len = 0;
|
||||||
|
ssize_t nread = getline(&pw, &len, stdin);
|
||||||
|
if (nread > 0 && pw[nread - 1] == '\n') pw[nread - 1] = '\0';
|
||||||
|
if (pw && *pw) {
|
||||||
|
sudo_password = std::string(pw);
|
||||||
|
}
|
||||||
|
free(pw);
|
||||||
|
if (sudo_password.empty()) {
|
||||||
|
g_warning("No password provided, backup may have incorrect permissions");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
RsyncManager rsync(config);
|
RsyncManager rsync(config);
|
||||||
SnapshotManager snapshots(config, rsync);
|
SnapshotManager snapshots(config, rsync);
|
||||||
|
|
||||||
g_message("Starting backup...");
|
g_message("Starting backup with sudo...");
|
||||||
snapshots.cleanup_old_snapshots();
|
snapshots.cleanup_old_snapshots();
|
||||||
std::string ts = snapshots.create_snapshot(system_mode ? "System backup" : "Manual backup");
|
std::string ts = snapshots.create_snapshot(
|
||||||
|
system_mode ? "System backup" : "Manual backup",
|
||||||
|
sudo_password);
|
||||||
if (!ts.empty()) {
|
if (!ts.empty()) {
|
||||||
g_message("Backup completed: %s", ts.c_str());
|
g_message("Backup completed: %s", ts.c_str());
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+7
-2
@@ -673,7 +673,9 @@ void MainWindow::show_sudo_dialog(std::function<void(const std::string&)> callba
|
|||||||
GtkWidget* icon = gtk_image_new_from_icon_name("dialog-password", GTK_ICON_SIZE_DIALOG);
|
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);
|
gtk_box_pack_start(GTK_BOX(box), icon, FALSE, FALSE, 0);
|
||||||
|
|
||||||
GtkWidget* label = gtk_label_new("Root access is required to restore files.\nEnter your sudo password:");
|
GtkWidget* label = gtk_label_new("Root access is required to preserve file permissions.\n"
|
||||||
|
"Backups will be created with sudo to ensure correct ownership and permissions.\n\n"
|
||||||
|
"Enter your sudo password:");
|
||||||
gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
|
gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
|
||||||
gtk_box_pack_start(GTK_BOX(box), label, FALSE, FALSE, 0);
|
gtk_box_pack_start(GTK_BOX(box), label, FALSE, FALSE, 0);
|
||||||
|
|
||||||
@@ -689,6 +691,7 @@ void MainWindow::show_sudo_dialog(std::function<void(const std::string&)> callba
|
|||||||
if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
|
if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
|
||||||
const char* pw = gtk_entry_get_text(GTK_ENTRY(pass_entry));
|
const char* pw = gtk_entry_get_text(GTK_ENTRY(pass_entry));
|
||||||
if (pw && *pw) {
|
if (pw && *pw) {
|
||||||
|
current_sudo_password_ = std::string(pw);
|
||||||
callback(std::string(pw));
|
callback(std::string(pw));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -874,15 +877,17 @@ void MainWindow::append_backup_line(const std::string& line, const char* tag) {
|
|||||||
|
|
||||||
void MainWindow::on_backup(GtkWidget*, gpointer d) {
|
void MainWindow::on_backup(GtkWidget*, gpointer d) {
|
||||||
auto* self = static_cast<MainWindow*>(d);
|
auto* self = static_cast<MainWindow*>(d);
|
||||||
|
self->show_sudo_dialog([self](const std::string& pw) {
|
||||||
self->tray_.set_status("x3-Backup - Creating backup...", true);
|
self->tray_.set_status("x3-Backup - Creating backup...", true);
|
||||||
self->open_backup_window("Creating Snapshot...");
|
self->open_backup_window("Creating Snapshot...");
|
||||||
gtk_label_set_text(GTK_LABEL(self->live_log_label_), "Backup running...");
|
gtk_label_set_text(GTK_LABEL(self->live_log_label_), "Backup running...");
|
||||||
|
|
||||||
g_thread_new("backup", [](gpointer data) -> gpointer {
|
g_thread_new("backup", [](gpointer data) -> gpointer {
|
||||||
auto* s = static_cast<MainWindow*>(data);
|
auto* s = static_cast<MainWindow*>(data);
|
||||||
s->snapshots_.create_snapshot();
|
s->snapshots_.create_snapshot("", s->current_sudo_password_);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}, self);
|
}, self);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::start_quick_backup() {
|
void MainWindow::start_quick_backup() {
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ private:
|
|||||||
|
|
||||||
std::string current_snapshot_;
|
std::string current_snapshot_;
|
||||||
std::string current_path_;
|
std::string current_path_;
|
||||||
|
std::string current_sudo_password_;
|
||||||
std::vector<std::string> path_stack_;
|
std::vector<std::string> path_stack_;
|
||||||
|
|
||||||
int col_name = 0, col_size = 1, col_type = 2, col_permissions = 3, col_mtime = 4;
|
int col_name = 0, col_size = 1, col_type = 2, col_permissions = 3, col_mtime = 4;
|
||||||
|
|||||||
+14
-6
@@ -54,7 +54,8 @@ std::vector<std::string> RsyncManager::build_exclude_args() {
|
|||||||
bool RsyncManager::create_snapshot(const std::string& snapshot_name,
|
bool RsyncManager::create_snapshot(const std::string& snapshot_name,
|
||||||
const std::string& source,
|
const std::string& source,
|
||||||
const std::string& dest,
|
const std::string& dest,
|
||||||
const std::string& link_dest) {
|
const std::string& link_dest,
|
||||||
|
const std::string& sudo_password) {
|
||||||
if (running_) return false;
|
if (running_) return false;
|
||||||
cancel_requested_ = false;
|
cancel_requested_ = false;
|
||||||
running_ = true;
|
running_ = true;
|
||||||
@@ -74,19 +75,26 @@ bool RsyncManager::create_snapshot(const std::string& snapshot_name,
|
|||||||
int64_t estimated_size = estimate_backup_size(source);
|
int64_t estimated_size = estimate_backup_size(source);
|
||||||
|
|
||||||
std::string target = dest + "/" + snapshot_name;
|
std::string target = dest + "/" + snapshot_name;
|
||||||
std::string cmd = "rsync -aAX --info=progress2";
|
std::string rsync_flags = "rsync -aAX --info=progress2 --numeric-ids";
|
||||||
|
|
||||||
if (config_.settings().delete_removed)
|
if (config_.settings().delete_removed)
|
||||||
cmd += " --delete";
|
rsync_flags += " --delete";
|
||||||
|
|
||||||
if (!link_dest.empty())
|
if (!link_dest.empty())
|
||||||
cmd += " --link-dest=\"" + link_dest + "\"";
|
rsync_flags += " --link-dest=\"" + link_dest + "\"";
|
||||||
|
|
||||||
for (const auto& e : config_.settings().exclude_patterns) {
|
for (const auto& e : config_.settings().exclude_patterns) {
|
||||||
cmd += " --exclude=\"" + e + "\"";
|
rsync_flags += " --exclude=\"" + e + "\"";
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd += " \"" + source + "/\" \"" + target + "/\" 2>&1";
|
rsync_flags += " \"" + source + "/\" \"" + target + "/\" 2>&1";
|
||||||
|
|
||||||
|
std::string cmd;
|
||||||
|
if (!sudo_password.empty()) {
|
||||||
|
cmd = "mkdir -p \"" + dest + "\" && echo '" + sudo_password + "' | sudo -S " + rsync_flags;
|
||||||
|
} else {
|
||||||
|
cmd = rsync_flags;
|
||||||
|
}
|
||||||
|
|
||||||
/* Force line-buffered output so we get real-time progress from rsync */
|
/* Force line-buffered output so we get real-time progress from rsync */
|
||||||
std::string full_cmd = "stdbuf -oL " + cmd;
|
std::string full_cmd = "stdbuf -oL " + cmd;
|
||||||
|
|||||||
+2
-1
@@ -31,7 +31,8 @@ public:
|
|||||||
bool create_snapshot(const std::string& snapshot_name,
|
bool create_snapshot(const std::string& snapshot_name,
|
||||||
const std::string& source,
|
const std::string& source,
|
||||||
const std::string& dest,
|
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,
|
bool restore_files(const std::string& snapshot_path,
|
||||||
const std::vector<std::string>& files,
|
const std::vector<std::string>& files,
|
||||||
|
|||||||
@@ -20,15 +20,20 @@ std::string SnapshotManager::get_timestamp() {
|
|||||||
return std::string(buf);
|
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 ts = get_timestamp();
|
||||||
std::string snapshots_dir = config_.snapshots_dir();
|
std::string snapshots_dir = config_.snapshots_dir();
|
||||||
std::string metadata_dir = config_.metadata_dir();
|
std::string metadata_dir = config_.metadata_dir();
|
||||||
|
|
||||||
std::string cmd1 = "mkdir -p \"" + snapshots_dir + "\"";
|
std::string mkdir_cmd;
|
||||||
std::string cmd2 = "mkdir -p \"" + metadata_dir + "\"";
|
if (!sudo_password.empty()) {
|
||||||
system(cmd1.c_str());
|
mkdir_cmd = "echo '" + sudo_password + "' | sudo -S mkdir -p \"" + snapshots_dir + "\" && "
|
||||||
system(cmd2.c_str());
|
"echo '" + sudo_password + "' | sudo -S mkdir -p \"" + metadata_dir + "\"";
|
||||||
|
} else {
|
||||||
|
mkdir_cmd = "mkdir -p \"" + snapshots_dir + "\" && mkdir -p \"" + metadata_dir + "\"";
|
||||||
|
}
|
||||||
|
system(mkdir_cmd.c_str());
|
||||||
|
|
||||||
/* Find latest snapshot for hardlink delta */
|
/* Find latest snapshot for hardlink delta */
|
||||||
std::string link_dest = "";
|
std::string link_dest = "";
|
||||||
@@ -39,7 +44,7 @@ std::string SnapshotManager::create_snapshot(const std::string& description) {
|
|||||||
|
|
||||||
/* Do NOT overwrite rsync_.on_progress — MainWindow already set it */
|
/* 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) {
|
if (ok) {
|
||||||
SnapshotInfo info;
|
SnapshotInfo info;
|
||||||
@@ -55,11 +60,13 @@ std::string SnapshotManager::create_snapshot(const std::string& description) {
|
|||||||
|
|
||||||
void SnapshotManager::delete_snapshot(const std::string& name) {
|
void SnapshotManager::delete_snapshot(const std::string& name) {
|
||||||
std::string path = config_.snapshots_dir() + "/" + name;
|
std::string path = config_.snapshots_dir() + "/" + name;
|
||||||
std::string cmd = "rm -rf \"" + path + "\"";
|
std::string meta = config_.metadata_dir() + "/" + name + ".json";
|
||||||
|
|
||||||
|
/* Try sudo first (snapshots are owned by root), fallback to normal rm */
|
||||||
|
std::string cmd = "echo '' | sudo -S rm -rf \"" + path + "\" 2>/dev/null || rm -rf \"" + path + "\"";
|
||||||
system(cmd.c_str());
|
system(cmd.c_str());
|
||||||
|
|
||||||
std::string meta = config_.metadata_dir() + "/" + name + ".json";
|
std::string meta_cmd = "echo '' | sudo -S rm -f \"" + meta + "\" 2>/dev/null || rm -f \"" + meta + "\"";
|
||||||
std::string meta_cmd = "rm -f \"" + meta + "\"";
|
|
||||||
system(meta_cmd.c_str());
|
system(meta_cmd.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,8 @@ class SnapshotManager {
|
|||||||
public:
|
public:
|
||||||
SnapshotManager(Config& config, RsyncManager& rsync);
|
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);
|
void delete_snapshot(const std::string& name);
|
||||||
std::vector<SnapshotInfo> list_snapshots();
|
std::vector<SnapshotInfo> list_snapshots();
|
||||||
SnapshotInfo get_snapshot_info(const std::string& name);
|
SnapshotInfo get_snapshot_info(const std::string& name);
|
||||||
|
|||||||
Reference in New Issue
Block a user