diff --git a/src/main.cpp b/src/main.cpp index 4d7d8aa..cd69fb7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "config.h" #include "rsync_manager.h" #include "snapshot_manager.h" @@ -14,6 +15,27 @@ static FILE* log_file = nullptr; static bool log_to_stdout = false; +static bool is_root() { + return getuid() == 0; +} + +static void escalate_privileges(int argc, char* argv[]) { + if (is_root()) return; + + g_print("x3-backup requires root privileges. Re-launching with pkexec...\n"); + + std::string cmd = "pkexec"; + for (int i = 0; i < argc; i++) { + std::string arg = argv[i]; + if (arg.find(' ') != std::string::npos) + cmd += " \"" + arg + "\""; + else + cmd += " " + arg; + } + int ret = system(cmd.c_str()); + exit(ret); +} + static std::string get_log_path() { const char* home = getenv("HOME"); if (!home) home = "."; @@ -66,18 +88,15 @@ static void setup_logging(bool cli_mode) { g_log_set_handler("x3-backup", (GLogLevelFlags)(G_LOG_LEVEL_MASK), log_handler, nullptr); g_log_set_handler(nullptr, (GLogLevelFlags)(G_LOG_LEVEL_MASK), log_handler, nullptr); - g_message("=== x3-backup started ==="); + g_message("=== x3-backup started (uid=%d) ===", getuid()); g_message("Log file: %s", path.c_str()); } int main(int argc, char* argv[]) { - gtk_init(&argc, &argv); - bool system_mode = false; bool backup_now = false; bool show_help = false; bool cli_mode = false; - std::string sudo_password; for (int i = 1; i < argc; i++) { if (strcmp(argv[i], "--system-backup") == 0) { @@ -91,29 +110,29 @@ int main(int argc, char* argv[]) { show_help = true; } else if (strcmp(argv[i], "--log") == 0) { log_to_stdout = true; - } else if (strcmp(argv[i], "--sudo-password") == 0 && i + 1 < argc) { - sudo_password = argv[++i]; } } - setup_logging(cli_mode); - if (show_help) { g_print("x3-Backup - GTK3 Backup/Snapshot Tool\n\n"); g_print("Usage: x3-backup [OPTIONS]\n\n"); g_print("Options:\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(" --sudo-password PW Provide sudo password for backup (required for root permissions)\n"); - g_print(" --log Print log to stdout\n"); - g_print(" --help, -h Show this help\n\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(" --log Print log to stdout\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("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("Note: x3-backup requires root privileges and will prompt\n"); + g_print(" for authentication via pkexec on startup.\n\n"); g_print("Log file: ~/.config/x3-backup/x3-backup.log\n"); return 0; } + escalate_privileges(argc, argv); + gtk_init(&argc, &argv); + + setup_logging(cli_mode); + Config config; config.load(); @@ -124,32 +143,13 @@ int main(int argc, char* argv[]) { 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); SnapshotManager snapshots(config, rsync); - g_message("Starting backup with sudo..."); + g_message("Starting backup..."); snapshots.cleanup_old_snapshots(); std::string ts = snapshots.create_snapshot( - system_mode ? "System backup" : "Manual backup", - sudo_password); + system_mode ? "System backup" : "Manual backup"); if (!ts.empty()) { g_message("Backup completed: %s", ts.c_str()); } else { diff --git a/src/main_window.cpp b/src/main_window.cpp index ce53ad6..80c490a 100644 --- a/src/main_window.cpp +++ b/src/main_window.cpp @@ -4,16 +4,17 @@ #include #include #include -#include -#include static std::string format_size(int64_t bytes) { - const char* units[] = {"B", "KB", "MB", "GB", "TB"}; - int u = 0; - double size = bytes; - while (size >= 1024 && u < 4) { size /= 1024; u++; } char buf[64]; - snprintf(buf, sizeof(buf), "%.1f %s", size, units[u]); + if (bytes < 1024) + snprintf(buf, sizeof(buf), "%ld B", (long)bytes); + else if (bytes < 1024LL * 1024) + snprintf(buf, sizeof(buf), "%.1f KB", (double)bytes / 1024); + else if (bytes < 1024LL * 1024 * 1024) + snprintf(buf, sizeof(buf), "%.1f MB", (double)bytes / (1024 * 1024)); + else + snprintf(buf, sizeof(buf), "%.2f GB", (double)bytes / (1024LL * 1024 * 1024)); return buf; } @@ -612,11 +613,9 @@ 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(d); - c->self->show_sudo_dialog([c](const std::string& pw) { - std::vector 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); - }); + std::vector files = {c->path}; + c->self->snapshots_.restore_selected(c->self->current_snapshot_, files); + 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); @@ -641,11 +640,9 @@ 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(d); - c->self->show_sudo_dialog([c](const std::string& pw) { - std::vector 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); - }); + std::vector files = {c->path}; + c->self->snapshots_.restore_selected(c->self->current_snapshot_, files); + c->self->tray_.show_notification("x3-Backup", "Directory restored: " + c->path); delete c; }), ctx, nullptr, (GConnectFlags)0); } @@ -658,48 +655,6 @@ gboolean MainWindow::on_file_button_press(GtkWidget*, GdkEventButton* event, gpo return TRUE; } -void MainWindow::show_sudo_dialog(std::function 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 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_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 */ @@ -879,17 +834,15 @@ void MainWindow::append_backup_line(const std::string& line, const char* tag) { void MainWindow::on_backup(GtkWidget*, gpointer d) { auto* self = static_cast(d); - 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..."); + 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(data); - s->snapshots_.create_snapshot("", s->current_sudo_password_); - return nullptr; - }, self); - }); + g_thread_new("backup", [](gpointer data) -> gpointer { + auto* s = static_cast(data); + s->snapshots_.create_snapshot(""); + return nullptr; + }, self); } void MainWindow::start_quick_backup() { @@ -925,13 +878,11 @@ void MainWindow::on_restore(GtkWidget*, gpointer d) { ? std::string(name) : self->current_path_ + "/" + name; g_free(name); - self->show_sudo_dialog([self, file_path](const std::string& pw) { - std::vector 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); - }); + std::vector 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); } void MainWindow::on_delete_snapshot(GtkWidget*, gpointer d) { @@ -1053,10 +1004,8 @@ void MainWindow::on_system_restore(GtkWidget*, gpointer d) { return; } - 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"); - }); + self->snapshots_.restore_full(self->current_snapshot_); + self->tray_.show_notification("x3-Backup", "Full system restore completed"); } gtk_widget_destroy(dialog); } diff --git a/src/main_window.h b/src/main_window.h index a9bce87..4864506 100644 --- a/src/main_window.h +++ b/src/main_window.h @@ -34,7 +34,6 @@ private: void browse_path(const std::string& snapshot, const std::string& path); void navigate_up(); - void show_sudo_dialog(std::function callback); void open_backup_window(const std::string& description = ""); void close_backup_window(); void append_backup_line(const std::string& line, const char* tag = nullptr); @@ -99,7 +98,6 @@ private: std::string current_snapshot_; std::string current_path_; - std::string current_sudo_password_; std::vector path_stack_; int col_name = 0, col_size = 1, col_type = 2, col_permissions = 3, col_mtime = 4; diff --git a/src/rsync_manager.cpp b/src/rsync_manager.cpp index 779e648..e5b7532 100644 --- a/src/rsync_manager.cpp +++ b/src/rsync_manager.cpp @@ -54,8 +54,7 @@ std::vector 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& sudo_password) { + const std::string& link_dest) { if (running_) return false; cancel_requested_ = false; running_ = true; @@ -89,12 +88,7 @@ bool RsyncManager::create_snapshot(const std::string& snapshot_name, 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; - } + std::string 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; @@ -201,8 +195,7 @@ bool RsyncManager::create_snapshot(const std::string& snapshot_name, bool RsyncManager::restore_files(const std::string& snapshot_path, const std::vector& files, - const std::string& restore_dest, - const std::string& sudo_password) { + const std::string& restore_dest) { if (running_) return false; cancel_requested_ = false; running_ = true; @@ -223,7 +216,7 @@ bool RsyncManager::restore_files(const std::string& snapshot_path, std::string src = snapshot_path + "/" + f; std::string dst = restore_dest + "/" + f; - std::string cmd = "echo '" + sudo_password + "' | sudo -S rsync -aAX \"" + src + "\" \"" + dst + "\" 2>&1"; + std::string cmd = "rsync -aAX \"" + src + "\" \"" + dst + "\" 2>&1"; g_message("restore cmd: %s", cmd.c_str()); @@ -273,8 +266,7 @@ 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& sudo_password) { + const std::string& restore_dest) { if (running_) return false; cancel_requested_ = false; running_ = true; @@ -284,7 +276,7 @@ bool RsyncManager::restore_full_snapshot(const std::string& snapshot_path, log_buffer_.clear(); } - std::string cmd = "echo '" + sudo_password + "' | sudo -S rsync -aAX --delete \"" + std::string cmd = "rsync -aAX --delete \"" + snapshot_path + "/\" \"" + restore_dest + "/\" 2>&1"; g_message("full restore cmd: %s", cmd.c_str()); diff --git a/src/rsync_manager.h b/src/rsync_manager.h index 791d5bf..d9765a9 100644 --- a/src/rsync_manager.h +++ b/src/rsync_manager.h @@ -31,17 +31,14 @@ 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& sudo_password = ""); + const std::string& link_dest = ""); bool restore_files(const std::string& snapshot_path, const std::vector& files, - const std::string& restore_dest, - const std::string& sudo_password); + const std::string& restore_dest); bool restore_full_snapshot(const std::string& snapshot_path, - const std::string& restore_dest, - const std::string& sudo_password); + const std::string& restore_dest); void cancel(); bool is_running() const { return running_; } diff --git a/src/snapshot_manager.cpp b/src/snapshot_manager.cpp index 4553d6d..0e6be6a 100644 --- a/src/snapshot_manager.cpp +++ b/src/snapshot_manager.cpp @@ -20,19 +20,12 @@ std::string SnapshotManager::get_timestamp() { return std::string(buf); } -std::string SnapshotManager::create_snapshot(const std::string& description, - const std::string& sudo_password) { +std::string SnapshotManager::create_snapshot(const std::string& description) { std::string ts = get_timestamp(); std::string snapshots_dir = config_.snapshots_dir(); std::string metadata_dir = config_.metadata_dir(); - std::string mkdir_cmd; - if (!sudo_password.empty()) { - mkdir_cmd = "echo '" + sudo_password + "' | sudo -S mkdir -p \"" + snapshots_dir + "\" && " - "echo '" + sudo_password + "' | sudo -S mkdir -p \"" + metadata_dir + "\""; - } else { - mkdir_cmd = "mkdir -p \"" + snapshots_dir + "\" && mkdir -p \"" + metadata_dir + "\""; - } + std::string mkdir_cmd = "mkdir -p \"" + snapshots_dir + "\" && mkdir -p \"" + metadata_dir + "\""; system(mkdir_cmd.c_str()); /* Find latest snapshot for hardlink delta */ @@ -44,7 +37,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, sudo_password); + bool ok = rsync_.create_snapshot(ts, config_.settings().source_path, snapshots_dir, link_dest); if (ok) { SnapshotInfo info; @@ -62,11 +55,10 @@ void SnapshotManager::delete_snapshot(const std::string& name) { std::string path = config_.snapshots_dir() + "/" + name; 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 + "\""; + std::string cmd = "rm -rf \"" + path + "\""; system(cmd.c_str()); - 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()); } @@ -158,14 +150,12 @@ std::vector SnapshotManager::browse_directory(const std::string& snap } bool SnapshotManager::restore_selected(const std::string& snapshot_path, - const std::vector& files, - const std::string& sudo_password) { - return rsync_.restore_files(snapshot_path, files, "/", sudo_password); + const std::vector& files) { + return rsync_.restore_files(snapshot_path, files, "/"); } -bool SnapshotManager::restore_full(const std::string& snapshot_path, - const std::string& sudo_password) { - return rsync_.restore_full_snapshot(snapshot_path, "/", sudo_password); +bool SnapshotManager::restore_full(const std::string& snapshot_path) { + return rsync_.restore_full_snapshot(snapshot_path, "/"); } void SnapshotManager::cleanup_old_snapshots() { diff --git a/src/snapshot_manager.h b/src/snapshot_manager.h index 4c89e7c..e766094 100644 --- a/src/snapshot_manager.h +++ b/src/snapshot_manager.h @@ -31,8 +31,7 @@ class SnapshotManager { public: SnapshotManager(Config& config, RsyncManager& rsync); - std::string create_snapshot(const std::string& description = "", - const std::string& sudo_password = ""); + std::string create_snapshot(const std::string& description = ""); void delete_snapshot(const std::string& name); std::vector list_snapshots(); SnapshotInfo get_snapshot_info(const std::string& name); @@ -41,11 +40,9 @@ public: const std::string& subpath = ""); bool restore_selected(const std::string& snapshot_path, - const std::vector& files, - const std::string& sudo_password); + const std::vector& files); - bool restore_full(const std::string& snapshot_path, - const std::string& sudo_password); + bool restore_full(const std::string& snapshot_path); void cleanup_old_snapshots();