fix: treat an absent missing-mirror parent as a no-op, only claim real deletions
BLOCKER: an absent mirror whose PARENT directory does not exist on the destination (a deeper --files-from missing entry, -R or full-mirror layout) was treated as a hard failure because file_open_secure_parent returned -1 when the parent was missing. That aborted the whole run, skipped the --delete extras walk, and tore down an early --delete-before/during connection, contradicting 'missing mirror = no-op / all-missing succeeds'. A parent-open failure is now a no-op when errno is ENOENT/ENOTDIR (matching file_remove_tree_secure); only a genuine I/O error fails the run. Also: an already-absent mirror reached via unlinkat-ENOENT no longer prints 'Deleted: <path>' (a no-op dressed as a deletion); the per-path 'Deleted:' line is printed only when an entry was actually removed.
This commit is contained in:
@@ -262,12 +262,26 @@ static bool files_from_list_check(const Config* config, ArrayList* missing_dest,
|
|||||||
free(full);
|
free(full);
|
||||||
}
|
}
|
||||||
if (*skipped_out > 0) {
|
if (*skipped_out > 0) {
|
||||||
if (config->delete_missing_args)
|
if (config->delete_missing_args) {
|
||||||
log_message(LOG_LEVEL_WARNING,
|
/* --list-only never deletes and a --dry-run only shows intent, so the
|
||||||
"--delete-missing-args: %d missing --files-from entr%s will be deleted from the "
|
summary must not claim a real deletion happened in those modes. */
|
||||||
"destination",
|
if (config->list_only)
|
||||||
*skipped_out, *skipped_out == 1 ? "y" : "ies");
|
log_message(LOG_LEVEL_WARNING,
|
||||||
else if (config->ignore_missing_args)
|
"--delete-missing-args: %d missing --files-from entr%s skipped (--list-only "
|
||||||
|
"never deletes)",
|
||||||
|
*skipped_out, *skipped_out == 1 ? "y" : "ies");
|
||||||
|
else if (config->dry_run)
|
||||||
|
log_message(LOG_LEVEL_WARNING,
|
||||||
|
"--delete-missing-args: %d missing --files-from entr%s would be deleted from "
|
||||||
|
"the destination (dry run)",
|
||||||
|
*skipped_out, *skipped_out == 1 ? "y" : "ies");
|
||||||
|
else
|
||||||
|
log_message(
|
||||||
|
LOG_LEVEL_WARNING,
|
||||||
|
"--delete-missing-args: %d missing --files-from entr%s will be deleted from the "
|
||||||
|
"destination",
|
||||||
|
*skipped_out, *skipped_out == 1 ? "y" : "ies");
|
||||||
|
} else if (config->ignore_missing_args)
|
||||||
log_message(LOG_LEVEL_WARNING,
|
log_message(LOG_LEVEL_WARNING,
|
||||||
"--ignore-missing-args: ignored %d missing --files-from entr%s", *skipped_out,
|
"--ignore-missing-args: ignored %d missing --files-from entr%s", *skipped_out,
|
||||||
*skipped_out == 1 ? "y" : "ies");
|
*skipped_out == 1 ? "y" : "ies");
|
||||||
|
|||||||
+24
-17
@@ -1590,13 +1590,21 @@ bool manifest_delete_missing_args(const Config* config, DeleteManifest* manifest
|
|||||||
char* leaf = NULL;
|
char* leaf = NULL;
|
||||||
int parent_fd = file_open_secure_parent(full, &leaf, false);
|
int parent_fd = file_open_secure_parent(full, &leaf, false);
|
||||||
if (parent_fd < 0) {
|
if (parent_fd < 0) {
|
||||||
|
/* The mirror's parent directory may itself not exist on the destination
|
||||||
|
(a deeper missing entry whose leading directories were never created).
|
||||||
|
That is a no-op -- there is nothing to delete -- matching
|
||||||
|
file_remove_tree_secure's absent-path handling; only a genuine I/O
|
||||||
|
error (EACCES, a symlink loop, ...) fails the run. */
|
||||||
|
bool absent = errno == ENOENT || errno == ENOTDIR;
|
||||||
free(full);
|
free(full);
|
||||||
free(leaf);
|
free(leaf);
|
||||||
ok = false;
|
if (!absent)
|
||||||
|
ok = false;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
struct stat st;
|
struct stat st;
|
||||||
if (fstatat(parent_fd, leaf, &st, AT_SYMLINK_NOFOLLOW) != 0) {
|
if (fstatat(parent_fd, leaf, &st, AT_SYMLINK_NOFOLLOW) != 0) {
|
||||||
|
/* Already absent: nothing to delete (a no-op, not a deletion). */
|
||||||
if (errno != ENOENT)
|
if (errno != ENOENT)
|
||||||
ok = false;
|
ok = false;
|
||||||
close(parent_fd);
|
close(parent_fd);
|
||||||
@@ -1604,24 +1612,20 @@ bool manifest_delete_missing_args(const Config* config, DeleteManifest* manifest
|
|||||||
free(full);
|
free(full);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
bool removed = false;
|
||||||
if (S_ISDIR(st.st_mode)) {
|
if (S_ISDIR(st.st_mode)) {
|
||||||
if (unlinkat(parent_fd, leaf, AT_REMOVEDIR) == 0 || errno == ENOENT) {
|
if (unlinkat(parent_fd, leaf, AT_REMOVEDIR) == 0) {
|
||||||
char* escaped = output_escape(rel, log_get_8_bit_output());
|
removed = true;
|
||||||
fprintf(stderr, " Deleted: %s\n", escaped ? escaped : "<allocation failed>");
|
|
||||||
free(escaped);
|
|
||||||
} else if (errno == ENOTEMPTY || errno == EEXIST) {
|
} else if (errno == ENOTEMPTY || errno == EEXIST) {
|
||||||
close(parent_fd);
|
close(parent_fd);
|
||||||
parent_fd = -1;
|
parent_fd = -1;
|
||||||
free(leaf);
|
free(leaf);
|
||||||
leaf = NULL;
|
leaf = NULL;
|
||||||
if (config->use_delete || config->force_delete) {
|
if (config->use_delete || config->force_delete) {
|
||||||
if (file_remove_tree_secure(full)) {
|
if (!file_remove_tree_secure(full))
|
||||||
char* escaped = output_escape(rel, log_get_8_bit_output());
|
|
||||||
fprintf(stderr, " Deleted: %s\n", escaped ? escaped : "<allocation failed>");
|
|
||||||
free(escaped);
|
|
||||||
} else {
|
|
||||||
ok = false;
|
ok = false;
|
||||||
}
|
else
|
||||||
|
removed = true;
|
||||||
} else {
|
} else {
|
||||||
char* escaped = output_escape(rel, log_get_8_bit_output());
|
char* escaped = output_escape(rel, log_get_8_bit_output());
|
||||||
log_message(LOG_LEVEL_WARNING,
|
log_message(LOG_LEVEL_WARNING,
|
||||||
@@ -1630,18 +1634,21 @@ bool manifest_delete_missing_args(const Config* config, DeleteManifest* manifest
|
|||||||
escaped ? escaped : "<allocation failed>");
|
escaped ? escaped : "<allocation failed>");
|
||||||
free(escaped);
|
free(escaped);
|
||||||
}
|
}
|
||||||
} else {
|
} else if (errno != ENOENT) {
|
||||||
ok = false;
|
ok = false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (unlinkat(parent_fd, leaf, 0) == 0 || errno == ENOENT) {
|
if (unlinkat(parent_fd, leaf, 0) == 0) {
|
||||||
char* escaped = output_escape(rel, log_get_8_bit_output());
|
removed = true;
|
||||||
fprintf(stderr, " Deleted: %s\n", escaped ? escaped : "<allocation failed>");
|
} else if (errno != ENOENT) {
|
||||||
free(escaped);
|
|
||||||
} else {
|
|
||||||
ok = false;
|
ok = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (removed) {
|
||||||
|
char* escaped = output_escape(rel, log_get_8_bit_output());
|
||||||
|
fprintf(stderr, " Deleted: %s\n", escaped ? escaped : "<allocation failed>");
|
||||||
|
free(escaped);
|
||||||
|
}
|
||||||
if (parent_fd >= 0)
|
if (parent_fd >= 0)
|
||||||
close(parent_fd);
|
close(parent_fd);
|
||||||
free(leaf);
|
free(leaf);
|
||||||
|
|||||||
Reference in New Issue
Block a user