diff --git a/generator.c b/generator.c index 420a88bd..5c012b4d 100644 --- a/generator.c +++ b/generator.c @@ -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); diff --git a/hlink.c b/hlink.c index 2e84b49f..894f01a1 100644 --- a/hlink.c +++ b/hlink.c @@ -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); diff --git a/rsync.h b/rsync.h index 8e8e76e9..adf9bf1f 100644 --- a/rsync.h +++ b/rsync.h @@ -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. */