+ int32 from, prev;
+ struct file_struct *file, *file_next;
+ struct ht_int32_node *node = NULL;
+ int32 gnum, gnum_next;
+
+ qsort(ndx_list, ndx_count, sizeof ndx_list[0],
+ (int (*)()) hlink_compare_gnum);
+
+ for (from = 0; from < ndx_count; from++) {
+ file = hlink_flist->sorted[ndx_list[from]];
+ gnum = F_HL_GNUM(file);
+ if (inc_recurse) {
+ node = hashtable_find(prior_hlinks, gnum, 1);
+ if (!node->data) {
+ node->data = new_array0(char, 5);
+ assert(gnum >= hlink_flist->ndx_start);
+ file->flags |= FLAG_HLINK_FIRST;
+ prev = -1;
+ } else if (CVAL(node->data, 0) == 0) {
+ struct file_list *flist;
+ prev = IVAL(node->data, 1);
+ flist = flist_for_ndx(prev);
+ if (flist)
+ flist->files[prev - flist->ndx_start]->flags &= ~FLAG_HLINK_LAST;
+ else {
+ /* We skipped all prior files in this
+ * group, so mark this as a "first". */
+ file->flags |= FLAG_HLINK_FIRST;
+ prev = -1;
+ }
+ } else
+ prev = -1;
+ } else {
+ file->flags |= FLAG_HLINK_FIRST;
+ prev = -1;
+ }
+ for ( ; from < ndx_count-1; file = file_next, gnum = gnum_next, from++) { /*SHARED ITERATOR*/
+ file_next = hlink_flist->sorted[ndx_list[from+1]];
+ gnum_next = F_HL_GNUM(file_next);
+ if (gnum != gnum_next)
+ break;
+ F_HL_PREV(file) = prev;
+ /* The linked list uses over-the-wire ndx values. */
+ if (unsort_ndx)
+ prev = F_NDX(file);
+ else
+ prev = ndx_list[from] + hlink_flist->ndx_start;
+ }
+ if (prev < 0 && !inc_recurse) {
+ /* Disable hard-link bit and set DONE so that
+ * HLINK_BUMP()-dependent values are unaffected. */
+ file->flags &= ~(FLAG_HLINKED | FLAG_HLINK_FIRST);
+ file->flags |= FLAG_HLINK_DONE;
+ continue;
+ }
+
+ file->flags |= FLAG_HLINK_LAST;
+ F_HL_PREV(file) = prev;
+ if (inc_recurse && CVAL(node->data, 0) == 0) {
+ if (unsort_ndx)
+ prev = F_NDX(file);
+ else
+ prev = ndx_list[from] + hlink_flist->ndx_start;
+ SIVAL(node->data, 1, prev);
+ }
+ }
+}
+
+/* Analyze the hard-links in the file-list by creating a list of all the
+ * items that have hlink data, sorting them, and matching up identical
+ * values into clusters. These will be a single linked list from last
+ * to first when we're done. */
+void match_hard_links(struct file_list *flist)
+{
+ if (!list_only) {
+ int i, ndx_count = 0;
+ int32 *ndx_list;
+
+ if (!(ndx_list = new_array(int32, flist->used)))
+ out_of_memory("match_hard_links");
+
+ for (i = 0; i < flist->used; i++) {
+ if (F_IS_HLINKED(flist->sorted[i]))
+ ndx_list[ndx_count++] = i;
+ }
+
+ hlink_flist = flist;
+
+ if (ndx_count)
+ match_gnums(ndx_list, ndx_count);
+
+ free(ndx_list);
+ }
+ if (protocol_version < 30)
+ idev_destroy();
+}
+
+static int maybe_hard_link(struct file_struct *file, int ndx,
+ const char *fname, int statret, stat_x *sxp,
+ const char *oldname, STRUCT_STAT *old_stp,
+ const char *realname, int itemizing, enum logcode code)
+{
+ if (statret == 0) {
+ if (sxp->st.st_dev == old_stp->st_dev
+ && sxp->st.st_ino == old_stp->st_ino) {
+ if (itemizing) {
+ itemize(fname, file, ndx, statret, sxp,
+ ITEM_LOCAL_CHANGE | ITEM_XNAME_FOLLOWS,
+ 0, "");
+ }
+ if (verbose > 1 && maybe_ATTRS_REPORT)
+ rprintf(FCLIENT, "%s is uptodate\n", fname);
+ file->flags |= FLAG_HLINK_DONE;
+ return 0;
+ }
+ if (make_backups > 0) {
+ if (!make_backup(fname))
+ return -1;
+ } else if (robust_unlink(fname)) {
+ rsyserr(FERROR_XFER, errno, "unlink %s failed",
+ full_fname(fname));
+ return -1;
+ }
+ }
+
+ if (hard_link_one(file, fname, oldname, 0)) {
+ if (itemizing) {
+ itemize(fname, file, ndx, statret, sxp,
+ ITEM_LOCAL_CHANGE | ITEM_XNAME_FOLLOWS, 0,
+ realname);
+ }
+ if (code != FNONE && verbose)
+ rprintf(code, "%s => %s\n", fname, realname);
+ return 0;
+ }
+ return -1;
+}
+
+/* Figure out if a prior entry is still there or if we just have a
+ * cached name for it. */
+static char *check_prior(struct file_struct *file, int gnum,
+ int *prev_ndx_p, struct file_list **flist_p)
+{
+ struct file_struct *fp;
+ struct file_list *flist;
+ struct ht_int32_node *node;
+ int prev_ndx = F_HL_PREV(file);
+
+ while (1) {
+ if (prev_ndx < 0) {
+ *prev_ndx_p = prev_ndx;
+ *flist_p = NULL;
+ return NULL;
+ }
+ if ((flist = flist_for_ndx(prev_ndx)) == NULL)
+ break;
+ fp = flist->files[prev_ndx - flist->ndx_start];
+ if (!(fp->flags & FLAG_SKIP_HLINK)) {
+ *prev_ndx_p = prev_ndx;
+ *flist_p = flist;
+ return NULL;
+ }
+ F_HL_PREV(file) = prev_ndx = F_HL_PREV(fp);
+ }
+
+ node = hashtable_find(prior_hlinks, gnum, 0);
+ assert(node != NULL && node->data);
+
+ if (CVAL(node->data, 0) == 0) {
+ /* The prior file must have been skipped. */
+ F_HL_PREV(file) = prev_ndx = -1;
+ *prev_ndx_p = prev_ndx;
+ *flist_p = NULL;
+ return NULL;
+ }