From e771cc9da614871f5d67333b39967e3c4db70be6 Mon Sep 17 00:00:00 2001 From: TapTap Date: Wed, 16 Sep 2026 23:26:14 +0200 Subject: [PATCH] fix(delete): guard per-dir missing-args by server policy; fall back for --dirs - Only honor the --delete-missing-args exact paths when the server's --allow-delete policy left delete_missing_args set. - -d/--dirs does not recurse, so a per-directory plan would carry no child information and could delete the contents of an untraversed directory; fall back to the whole-tree end-of-transfer commit for that mode. --- src/client/client_send.c | 12 +++++++++--- src/shared/delete_plan.c | 4 +++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/client/client_send.c b/src/client/client_send.c index 3ddfcd7..b050541 100644 --- a/src/client/client_send.c +++ b/src/client/client_send.c @@ -2479,7 +2479,10 @@ int send_files(Config* config) { ArrayList* size_skipped = NULL; ArrayList* synced_dirs = NULL; bool delete_early = config->use_delete && config_delete_timing_early(config); - bool delete_per_dir = config->use_delete && config_delete_timing_per_dir(config); + /* -d/--dirs does not recurse, so a per-directory plan would carry no child + information and could delete the contents of an untraversed directory; + fall back to the whole-tree end-of-transfer commit for that mode. */ + bool delete_per_dir = config->use_delete && config_delete_timing_per_dir(config) && !config->dirs; bool send_failed = false; bool had_scan_io = false; PreparedScanner prepared; @@ -2915,12 +2918,15 @@ int send_files_multithreaded(Config** config_ptr) { return 1; } } - if (config_delete_timing_early(config) || config_delete_timing_per_dir(config)) { + /* -d/--dirs does not recurse, so a per-directory plan would carry no child + information and could delete the contents of an untraversed directory; + fall back to the whole-tree end-of-transfer commit for that mode. */ + bool per_dir = config_delete_timing_per_dir(config) && !config->dirs; + if (config_delete_timing_early(config) || per_dir) { /* --delete-before / --delete-during / --delete-delay: build the keep-set (paths only, nothing loaded or sent) up front so the sender thread can transmit it before/with the data. The path-only pre-scan also fills the protected excluded prefixes and synchronized directories. */ - bool per_dir = config_delete_timing_per_dir(config); PreparedScanner prepared; memset(&prepared, 0, sizeof(prepared)); bool prepared_ok = prepare_scanner(config, config->scanner_threads, &prepared); diff --git a/src/shared/delete_plan.c b/src/shared/delete_plan.c index 9d69138..b12e24c 100644 --- a/src/shared/delete_plan.c +++ b/src/shared/delete_plan.c @@ -718,7 +718,9 @@ static bool apply_missing(DeletePlanSession* session, const Config* config) { if (session->missing_applied) return true; session->missing_applied = true; - if (session->missing->size == 0) + /* The server clears delete_missing_args when its --allow-delete policy is + off; never honor the client's exact-path requests then. */ + if (!config->delete_missing_args || session->missing->size == 0) return true; DeleteManifest manifest = { .keeps = NULL, .protected = NULL, .missing = session->missing, .dirs = NULL};