From bbecff9c0476c2a8f3ade454a16f17cbdf5ce1f3 Mon Sep 17 00:00:00 2001 From: TapTap Date: Thu, 17 Sep 2026 01:34:26 +0200 Subject: [PATCH] fix(delete): keep the empty-scan safety guard file-only Adding every traversed directory to the per-directory plan keep set must not make an I/O-errored partial scan look non-empty. Count only transmitted file entries for delete_plan_sender_empty(), so a scan that hit an unreadable directory and found no files still refuses to delete. --- src/shared/delete_plan.c | 11 +++++++++-- src/shared/delete_plan.h | 4 +++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/shared/delete_plan.c b/src/shared/delete_plan.c index f0e8fb8..f5aea91 100644 --- a/src/shared/delete_plan.c +++ b/src/shared/delete_plan.c @@ -48,6 +48,10 @@ struct DeletePlanSender { const ArrayList* size_skipped; const ArrayList* missing_args; size_t entries; + /* Transmitted FILE entries only. The caller's "empty scan" safety guard keys + off this (an I/O error that hid every file must refuse to delete even when + some directories were traversed), so directory keep entries do not count. */ + size_t file_entries; }; static size_t plan_hash(const char* key) { @@ -254,8 +258,11 @@ bool delete_plan_sender_add(DeletePlanSender* sender, const char* path, bool is_ } if (ok) ok = plan_ensure_ancestors(sender, parent); - if (ok) + if (ok) { sender->entries++; + if (!is_dir) + sender->file_entries++; + } free(clean); free(parent); free(base); @@ -272,7 +279,7 @@ void delete_plan_sender_finalize(DeletePlanSender* sender, const ArrayList* sync } bool delete_plan_sender_empty(const DeletePlanSender* sender) { - return !sender || sender->entries == 0; + return !sender || sender->file_entries == 0; } void delete_plan_sender_set_config(DeletePlanSender* sender, const ArrayList* protected_prefixes, diff --git a/src/shared/delete_plan.h b/src/shared/delete_plan.h index 1f02a68..0f4644b 100644 --- a/src/shared/delete_plan.h +++ b/src/shared/delete_plan.h @@ -43,7 +43,9 @@ bool delete_plan_sender_add(DeletePlanSender* sender, const char* path, bool is_ * recursive transfer and for --files-from. */ void delete_plan_sender_finalize(DeletePlanSender* sender, const ArrayList* synced_dirs, const char* walk_root); -/* True when no transmitted entry was recorded (an ambiguous empty scan). */ +/* True when no transmitted FILE entry was recorded (an ambiguous empty scan). + Directory keep entries do not count, so an I/O error that hid every file + still refuses to delete. */ bool delete_plan_sender_empty(const DeletePlanSender* sender); /* Attach the global config sections advertised on the first plan frame. */ void delete_plan_sender_set_config(DeletePlanSender* sender, const ArrayList* protected_prefixes,