diff --git a/src/client/client_cli.c b/src/client/client_cli.c index 5778f41..985059e 100644 --- a/src/client/client_cli.c +++ b/src/client/client_cli.c @@ -454,6 +454,11 @@ static const OptionEntry OPTION_TABLE[] = { {"--delete-during", "--del", OPT_FLAG, offsetof(Config, delete_during)}, {"--delete-delay", NULL, OPT_FLAG, offsetof(Config, delete_delay)}, {"--delete-after", NULL, OPT_FLAG, offsetof(Config, delete_after)}, + {"--delete-excluded", NULL, OPT_FLAG, offsetof(Config, delete_excluded)}, + {"--max-delete", NULL, OPT_NONNEG_INT, offsetof(Config, max_delete)}, + {"--ignore-errors", NULL, OPT_FLAG, offsetof(Config, ignore_errors)}, + {"--force", NULL, OPT_FLAG, offsetof(Config, force_delete)}, + {"--prune-empty-dirs", NULL, OPT_FLAG, offsetof(Config, prune_empty_dirs)}, {"--source-dir", NULL, OPT_STRING, offsetof(Config, send_directory)}, {"--dest-dir", NULL, OPT_STRING, offsetof(Config, receive_root_directory)}, diff --git a/src/client/usage.c b/src/client/usage.c index 0cb0d76..2c26513 100644 --- a/src/client/usage.c +++ b/src/client/usage.c @@ -35,6 +35,20 @@ void print_usage(void) { printf(" (implies --delete)\n"); printf(" --delete-after Delete only after the whole transfer succeeded\n"); printf(" (the default --delete timing; implies --delete)\n"); + printf(" --delete-excluded Also delete destination files that were excluded on\n"); + printf(" the source (default protects them, matching rsync)\n"); + printf(" --max-delete=NUM Never delete more than NUM destination entries per run;\n"); + printf(" if the extras would exceed NUM, nothing is deleted and\n"); + printf(" the run fails with a clear error (implies --delete only\n"); + printf(" when used with it)\n"); + printf(" --ignore-errors Continue (and still delete) when a source directory is\n"); + printf(" unreadable during the scan, instead of aborting with no\n"); + printf(" deletion\n"); + printf(" --force A file may replace a destination directory by removing\n"); + printf(" that (non-empty) directory first\n"); + printf(" --prune-empty-dirs Do not transfer empty directory entries (--dirs mode);\n"); + printf(" recursive transfers never send empty dirs. rsync's -m\n"); + printf(" short form stays FastSync multithreading\n"); printf(" Note: each timing flag implies --delete. Combining a timing flag with\n"); printf(" --no-delete (in either order) is rejected as a config error.\n"); printf(" --ignore-existing Skip files that already exist on receiver\n"); diff --git a/src/shared/config.c b/src/shared/config.c index b839744..d3937da 100644 --- a/src/shared/config.c +++ b/src/shared/config.c @@ -92,7 +92,9 @@ static void config_set_defaults(Config* config) { config->append_verify = false; config->delete_excluded = false; config->delete_after = false; - config->max_delete = 0; + config->max_delete = -1; + config->ignore_errors = false; + config->force_delete = false; config->filters = NULL; config->files_from = NULL; config->files_from_set = NULL; @@ -161,11 +163,11 @@ static bool validate_received_config(const Config* config) { valid_wire_bool(config->existing) && valid_wire_bool(config->update) && valid_wire_bool(config->inplace) && valid_wire_bool(config->append) && valid_wire_bool(config->use_fsync) && valid_wire_bool(config->append_verify) && - valid_wire_bool(config->delete_excluded) && valid_wire_bool(config->delete_after) && - valid_wire_bool(config->delete_delay) && valid_wire_bool(config->delete_during) && - valid_wire_bool(config->relative) && valid_wire_bool(config->prune_empty_dirs) && - valid_wire_bool(config->delay_updates) && valid_wire_bool(config->mkpath) && - !(config->delay_updates && config->inplace) && + valid_wire_bool(config->delete_excluded) && valid_wire_bool(config->force_delete) && + valid_wire_bool(config->delete_after) && valid_wire_bool(config->delete_delay) && + valid_wire_bool(config->delete_during) && valid_wire_bool(config->relative) && + valid_wire_bool(config->prune_empty_dirs) && valid_wire_bool(config->delay_updates) && + valid_wire_bool(config->mkpath) && !(config->delay_updates && config->inplace) && !(config->delay_updates && delay_updates_staging_name_conflict(config->backup_dir)) && valid_wire_bool(config->partial) && valid_wire_bool(config->delete_before) && valid_wire_bool(config->checksum) && valid_wire_bool(config->eight_bit_output) && @@ -177,7 +179,7 @@ static bool validate_received_config(const Config* config) { config->delta_block_size >= DELTA_BLOCK_SIZE_MIN && config->delta_block_size <= DELTA_BLOCK_SIZE_MAX && config->delta_max_file_size <= DELTA_MAX_FILE_SIZE && config->modify_window >= 0 && - config->max_delete >= 0 && config->skip_compress_count >= 0 && + config->max_delete >= -1 && config->skip_compress_count >= 0 && config->skip_compress_count <= 10000 && config->max_alloc > 0 && (!config->chmod_spec || !*config->chmod_spec || chmod_apply(0, config->chmod_spec, &(mode_t){0})); @@ -417,10 +419,10 @@ static bool send_selection_options(int fd, const Config* c) { return send_int(fd, c->ignore_existing) && send_int(fd, c->existing) && send_int(fd, c->update) && send_int(fd, c->inplace) && send_int(fd, c->delay_updates) && send_int(fd, c->append) && send_int(fd, c->use_fsync) && send_int(fd, c->append_verify) && - send_int(fd, c->delete_excluded) && send_int(fd, c->delete_after) && - send_n_data(fd, &c->max_delete, sizeof(c->max_delete)) && send_int(fd, c->relative) && - send_int(fd, c->prune_empty_dirs) && send_int(fd, c->mkpath) && - send_int(fd, c->delete_during) && send_int(fd, c->delete_delay); + send_int(fd, c->delete_excluded) && send_int(fd, c->force_delete) && + send_int(fd, c->delete_after) && send_n_data(fd, &c->max_delete, sizeof(c->max_delete)) && + send_int(fd, c->relative) && send_int(fd, c->prune_empty_dirs) && + send_int(fd, c->mkpath) && send_int(fd, c->delete_during) && send_int(fd, c->delete_delay); } static bool send_skip_compress_options(int fd, const Config* c) { @@ -523,9 +525,9 @@ static bool receive_file_options(int fd, Config* c) { } static bool receive_selection_options(int fd, Config* c) { - bool* flags[] = {&c->ignore_existing, &c->existing, &c->update, &c->inplace, - &c->delay_updates, &c->append, &c->use_fsync, &c->append_verify, - &c->delete_excluded, &c->delete_after}; + bool* flags[] = {&c->ignore_existing, &c->existing, &c->update, &c->inplace, + &c->delay_updates, &c->append, &c->use_fsync, &c->append_verify, + &c->delete_excluded, &c->force_delete, &c->delete_after}; for (size_t i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { if (!receive_wire_bool(fd, flags[i])) return false; diff --git a/src/shared/config.h b/src/shared/config.h index 4ee1caa..8cf310b 100644 --- a/src/shared/config.h +++ b/src/shared/config.h @@ -117,9 +117,26 @@ typedef struct Config { bool append_verify; // Issue #128: Extended delete options + /* --delete-excluded: also delete destination entries that were excluded on + * the source. Default (off) matches rsync: excluded paths are protected from + * deletion. Crosses the wire (the sender encodes the choice by whether it + * transmits a protected-prefix list with the keep-set manifest). */ bool delete_excluded; bool delete_after; + /* --max-delete=NUM: the receiver refuses to delete more than NUM entries per + * run (all-or-nothing: when the extras would exceed NUM nothing is removed and + * the transfer fails with a distinct error). -1 == no client limit (the + * server hard bound MAX_SERVER_DELETE_COUNT still applies). */ int max_delete; + /* --ignore-errors (client-only, never serialized): a sender-side source I/O + * error (an unreadable directory during the scan) normally aborts the run so + * no deletion happens; with --ignore-errors the scan continues and the + * (partial) keep-set is still transmitted so the deletion runs. */ + bool ignore_errors; + /* --force (receiver-side): a regular file may replace a destination + * directory by removing that (possibly non-empty, symlink-safe) directory + * tree first, instead of failing the write. Crosses the wire. */ + bool force_delete; // Issue #129: Advanced file selection. These fields are CLIENT-ONLY: they are // never serialized to the wire (the receiver must not learn them). @@ -206,7 +223,7 @@ typedef struct Config { DelayUpdatesContext* delay_context; } Config; -#define PROTOCOL_VERSION "2.8.0" +#define PROTOCOL_VERSION "2.9.0" #define DEFAULT_CHUNK_SIZE (10 * 1024 * 1024) /* Upper bound on total basis-dir entries (rsync caps --link-dest at 20). */ #define MAX_BASIS_DIRS 64