fix: free basis path on copy-dest hits; keep --delete staging skip top-level-only
- copy-dest basis hits leaked the heap-allocated basis path: BASIS_DEST_COPY did not transfer it (only LINK does) and returned before the basis cleanup. basis_match_free is now called on every materialization return path (success and send-failure) after content/link ownership is transferred. - the --delete walker regression: the delay-updates staging name must be protected only as a DIRECT child of the receive root, while basis dirs may be skipped at any depth. delete_extras_limited now takes DeleteSkipEntry entries carrying a top_level_only flag instead of a flat prefix list, so a nested destination directory named .fastsync-stage is ordinary content again (its extras are deleted) and a basis tree is still never removed.
This commit is contained in:
+25
-19
@@ -204,22 +204,26 @@ static bool is_dir_in_manifest(const char* rel_path, ArrayList* manifest) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* True when the relative path is, or lies below, one of the protected
|
||||
prefixes. A prefix "a" therefore protects "a" and "a/b/c" but not "ab". */
|
||||
static bool path_under_skip_prefix(const char* rel_path, const char* const* prefixes,
|
||||
int prefix_count) {
|
||||
for (int i = 0; i < prefix_count; i++) {
|
||||
size_t prefix_len = strlen(prefixes[i]);
|
||||
if (strncmp(rel_path, prefixes[i], prefix_len) == 0 &&
|
||||
(rel_path[prefix_len] == '\0' || rel_path[prefix_len] == '/'))
|
||||
/* True when child_rel is, or lies below, a protected entry. A prefix "a"
|
||||
therefore protects "a" and "a/b/c" but not "ab". Entries with top_level_only
|
||||
set only protect DIRECT children of the receive root (at_root); nested
|
||||
directories that share such a name stay ordinary destination content. */
|
||||
static bool path_under_skip_prefix(const char* child_rel, bool at_root,
|
||||
const DeleteSkipEntry* skips, int skip_count) {
|
||||
for (int i = 0; i < skip_count; i++) {
|
||||
if (skips[i].top_level_only && !at_root)
|
||||
continue;
|
||||
size_t prefix_len = strlen(skips[i].prefix);
|
||||
if (strncmp(child_rel, skips[i].prefix, prefix_len) == 0 &&
|
||||
(child_rel[prefix_len] == '\0' || child_rel[prefix_len] == '/'))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool delete_extras_fd(int dirfd, const char* rel_path, ArrayList* manifest,
|
||||
size_t max_delete, size_t* deleted_count,
|
||||
const char* const* skip_prefixes, int skip_prefix_count) {
|
||||
size_t max_delete, size_t* deleted_count, const DeleteSkipEntry* skips,
|
||||
int skip_count) {
|
||||
int scanfd = dup(dirfd);
|
||||
if (scanfd < 0)
|
||||
return false;
|
||||
@@ -238,11 +242,14 @@ static bool delete_extras_fd(int dirfd, const char* rel_path, ArrayList* manifes
|
||||
operation_ok = false;
|
||||
continue;
|
||||
}
|
||||
/* A --delay-updates run keeps its staging directory below the receive
|
||||
root, and basis-dir snapshots live there too. Their contents are not
|
||||
manifest entries, so descending into them would delete every staged /
|
||||
basis file as an "extra". */
|
||||
if (path_under_skip_prefix(child_rel, skip_prefixes, skip_prefix_count)) {
|
||||
/* A --delay-updates run keeps its staging directory as a direct child of
|
||||
the receive root, and basis-dir snapshots live below it too. Their
|
||||
contents are not manifest entries, so descending into them would delete
|
||||
every staged / basis file as an "extra". Only the staging name (a
|
||||
top-level-only prefix) and the basis prefixes are protected: a nested
|
||||
destination directory that happens to be called .fastsync-stage is
|
||||
ordinary content. */
|
||||
if (path_under_skip_prefix(child_rel, rel_path[0] == '\0', skips, skip_count)) {
|
||||
free(child_rel);
|
||||
continue;
|
||||
}
|
||||
@@ -263,7 +270,7 @@ static bool delete_extras_fd(int dirfd, const char* rel_path, ArrayList* manifes
|
||||
bool child_removed = false;
|
||||
if (childfd >= 0) {
|
||||
child_removed = delete_extras_fd(childfd, child_rel, manifest, max_delete, deleted_count,
|
||||
skip_prefixes, skip_prefix_count);
|
||||
skips, skip_count);
|
||||
if (!child_removed)
|
||||
operation_ok = false;
|
||||
close(childfd);
|
||||
@@ -315,7 +322,7 @@ static bool delete_extras_fd(int dirfd, const char* rel_path, ArrayList* manifes
|
||||
}
|
||||
|
||||
bool delete_extras_limited(const char* dest_root, ArrayList* manifest, size_t max_delete,
|
||||
const char* const* skip_prefixes, int skip_prefix_count) {
|
||||
const DeleteSkipEntry* skips, int skip_count) {
|
||||
if (!manifest)
|
||||
return false;
|
||||
int rootfd;
|
||||
@@ -332,8 +339,7 @@ bool delete_extras_limited(const char* dest_root, ArrayList* manifest, size_t ma
|
||||
if (rootfd < 0)
|
||||
return false;
|
||||
size_t deleted_count = 0;
|
||||
bool ok = delete_extras_fd(rootfd, "", manifest, max_delete, &deleted_count, skip_prefixes,
|
||||
skip_prefix_count);
|
||||
bool ok = delete_extras_fd(rootfd, "", manifest, max_delete, &deleted_count, skips, skip_count);
|
||||
if (close(rootfd) != 0)
|
||||
ok = false;
|
||||
return ok;
|
||||
|
||||
Reference in New Issue
Block a user