mirror of
https://github.com/RsyncProject/rsync.git
synced 2026-11-04 11:03:25 -05:00
hlink: fix hard-link failure abort (#741) and the latent drain hang it exposes
When a hard-link group member fails its direct link() (EMLINK or EXDEV) it is written as a regular file and finishes first, claiming the group's prior_hlinks node; a later member's finish then hits the "Hlink node data ... already has path" check and aborts the whole transfer (code 13). Tolerate the duplicate finish: keep the first leader's path, log an FINFO warning, and continue. Removing the abort exposes a second, latent bug: finish_hard_link() decrements flist->in_progress for every file in the completion chain, but members that failed their direct link() in hard_link_check() were never counted, so the counter drifts negative and the final drain hangs forever. Add FLAG_HLINK_WAIT, set at the three increment sites, and gate the two affected decrements on it. Tested with a 65000-link pinned-inode EMLINK fixture (full repro and evidence in the PR description), an -H --link-dest regression matrix, and the upstream hardlink/link-dest test suite. hlink: fix hard-link failure abort (#741) and the latent drain hang it exposes When a hard-link group member fails its direct link() (EMLINK or EXDEV) it is written as a regular file and finishes first, claiming the group's prior_hlinks node; a later member's finish then hits the "Hlink node data ... already has path" check and aborts the whole transfer (code 13). Tolerate the duplicate finish: keep the first leader's path, log an FINFO warning, and continue. Removing the abort exposes a second, latent bug: finish_hard_link() decrements flist->in_progress for every file in the completion chain, but members that failed their direct link() in hard_link_check() were never counted, so the counter drifts negative and the final drain hangs forever. Add FLAG_HLINK_WAIT, set at the three increment sites, and gate the two affected decrements on it. Tested with a 65000-link pinned-inode EMLINK fixture (full repro and evidence in the PR description), an -H --link-dest regression matrix, and the upstream hardlink/link-dest test suite.
This commit is contained in:
1 parent
8718cd28c5
commit
350425a81d
3 files changed
+21
-4
No files matched your search
+6
-1
@@ -2253,6 +2253,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
|
||||
if (statret != 0) {
|
||||
#ifdef SUPPORT_HARD_LINKS
|
||||
if (preserve_hard_links && F_HLINK_NOT_LAST(file)) {
|
||||
file->flags |= FLAG_HLINK_WAIT;
|
||||
cur_flist->in_progress++;
|
||||
goto cleanup;
|
||||
}
|
||||
@@ -2358,6 +2359,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
|
||||
/* pretend the file didn't exist */
|
||||
#ifdef SUPPORT_HARD_LINKS
|
||||
if (preserve_hard_links && F_HLINK_NOT_LAST(file)) {
|
||||
file->flags |= FLAG_HLINK_WAIT;
|
||||
cur_flist->in_progress++;
|
||||
goto cleanup;
|
||||
}
|
||||
@@ -2595,7 +2597,10 @@ static void handle_skipped_hlink(struct file_struct *file, int itemizing,
|
||||
return;
|
||||
|
||||
file = cur_flist->files[new_last_ndx - cur_flist->ndx_start];
|
||||
cur_flist->in_progress--; /* undo prior increment */
|
||||
if (file->flags & FLAG_HLINK_WAIT) {
|
||||
file->flags &= ~FLAG_HLINK_WAIT;
|
||||
cur_flist->in_progress--; /* undo prior increment */
|
||||
}
|
||||
f_name(file, fbuf);
|
||||
recv_generator(fbuf, file, new_last_ndx, itemizing, code, f_out);
|
||||
|
||||
|
||||
@@ -335,6 +335,7 @@ int hard_link_check(struct file_struct *file, int ndx, char *fname,
|
||||
F_HL_PREV(file) = F_HL_PREV(prev_file);
|
||||
F_HL_PREV(prev_file) = ndx;
|
||||
file->flags |= FLAG_FILE_SENT;
|
||||
file->flags |= FLAG_HLINK_WAIT;
|
||||
cur_flist->in_progress++;
|
||||
if (DEBUG_GTE(HLINK, 2)) {
|
||||
rprintf(FINFO, "hlink for %d (%s,%d): waiting for %d\n",
|
||||
@@ -535,7 +536,10 @@ void finish_hard_link(struct file_struct *file, const char *fname, int fin_ndx,
|
||||
prev_statret = link_stat(f_name(file, prev_name), &prev_sx.st, 0);
|
||||
val = maybe_hard_link(file, ndx, prev_name, prev_statret, &prev_sx,
|
||||
our_name, stp, fname, itemizing, code);
|
||||
flist->in_progress--;
|
||||
if (file->flags & FLAG_HLINK_WAIT) {
|
||||
file->flags &= ~FLAG_HLINK_WAIT;
|
||||
flist->in_progress--;
|
||||
}
|
||||
free_stat_x(&prev_sx);
|
||||
if (val < 0)
|
||||
continue;
|
||||
@@ -555,9 +559,16 @@ void finish_hard_link(struct file_struct *file, const char *fname, int fin_ndx,
|
||||
exit_cleanup(RERR_MESSAGEIO);
|
||||
}
|
||||
if (CVAL(node->data, 0) != 0) {
|
||||
rprintf(FERROR, "Hlink node data for %d already has path=%s (%s)\n",
|
||||
/* A finished leader was already recorded for this group.
|
||||
* This can happen when an earlier member of the group failed
|
||||
* to hard-link (e.g. EMLINK or EXDEV) and was written as a
|
||||
* regular file, then finished. Keep the original leader's
|
||||
* path and tolerate the duplicate finish instead of aborting
|
||||
* the whole transfer. (Upstream: RsyncProject/rsync#741.) */
|
||||
rprintf(FINFO,
|
||||
"hlink: group %d already finished at path=%s (%s); ignoring duplicate finish\n",
|
||||
gnum, (char*)node->data, f_name(file, prev_name));
|
||||
exit_cleanup(RERR_MESSAGEIO);
|
||||
return;
|
||||
}
|
||||
free(node->data);
|
||||
node->data = strdup(our_name);
|
||||
|
||||
@@ -93,6 +93,7 @@
|
||||
#define FLAG_TIME_FAILED (1<<11)/* generator */
|
||||
#define FLAG_MOD_NSEC (1<<12) /* sender/receiver/generator */
|
||||
#define FLAG_GOT_DIR_FLIST (1<<13)/* sender/receiver/generator - dir_flist only */
|
||||
#define FLAG_HLINK_WAIT (1<<14) /* generator (w/FLAG_HLINKED): counted in in_progress, pending hard-link processing */
|
||||
|
||||
/* These flags are passed to functions but not stored. */
|
||||
|
||||
|
||||
Reference in new issue
Block a user