Add parent-dir validation for --no-inc-recurse too.
[rsync.git] / generator.c
index e3890bb1de7e6e9ce0ca5b214f9156f8b86dca64..e3fc28453577f0370a1c4a9588cd657d7fc3c4de 100644 (file)
@@ -4,7 +4,7 @@
  * Copyright (C) 1996-2000 Andrew Tridgell
  * Copyright (C) 1996 Paul Mackerras
  * Copyright (C) 2002 Martin Pool <mbp@samba.org>
- * Copyright (C) 2003-2009 Wayne Davison
+ * Copyright (C) 2003-2014 Wayne Davison
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -57,6 +57,7 @@ extern int update_only;
 extern int human_readable;
 extern int ignore_existing;
 extern int ignore_non_existing;
+extern int want_xattr_optim;
 extern int inplace;
 extern int append_mode;
 extern int make_backups;
@@ -81,6 +82,7 @@ extern int link_dest;
 extern int whole_file;
 extern int list_only;
 extern int read_batch;
+extern int write_batch;
 extern int safe_symlinks;
 extern long block_size; /* "long" because popt can't set an int32. */
 extern int unsort_ndx;
@@ -288,7 +290,7 @@ static void delete_in_dir(char *fbuf, struct file_struct *file, dev_t *fs_dev)
        if (allowed_lull)
                maybe_send_keepalive(time(NULL), MSK_ALLOW_FLUSH);
 
-       if (io_error && !ignore_errors) {
+       if (io_error & IOERR_GENERAL && !ignore_errors) {
                if (already_warned)
                        return;
                rprintf(FINFO,
@@ -355,6 +357,9 @@ static void do_delete_pass(void)
        for (j = 0; j < cur_flist->used; j++) {
                struct file_struct *file = cur_flist->sorted[j];
 
+               if (!F_IS_ACTIVE(file))
+                       continue;
+
                f_name(file, fbuf);
 
                if (!(file->flags & FLAG_CONTENT_DIR)) {
@@ -443,7 +448,7 @@ int unchanged_attrs(const char *fname, struct file_struct *file, stat_x *sxp)
                if (perms_differ(file, sxp))
                        return 0;
 #endif
-#ifndef CAN_CHOWN_SYMLINK
+#ifdef CAN_CHOWN_SYMLINK
                if (ownership_differs(file, sxp))
                        return 0;
 #endif
@@ -550,7 +555,7 @@ void itemize(const char *fnamecmp, struct file_struct *file, int ndx, int statre
                        if (preserve_xattrs && do_xfers
                         && iflags & (ITEM_REPORT_XATTR|ITEM_TRANSFER)) {
                                int fd = iflags & ITEM_REPORT_XATTR
-                                     && (protocol_version < 31 || !BITS_SET(iflags, ITEM_XNAME_FOLLOWS|ITEM_LOCAL_CHANGE))
+                                     && !(want_xattr_optim && BITS_SET(iflags, ITEM_XNAME_FOLLOWS|ITEM_LOCAL_CHANGE))
                                       ? sock_f_out : -1;
                                send_xattr_request(NULL, file, fd);
                        }
@@ -573,7 +578,7 @@ int unchanged_file(char *fn, struct file_struct *file, STRUCT_STAT *st)
           of the file time to determine whether to sync */
        if (always_checksum > 0 && S_ISREG(st->st_mode)) {
                char sum[MAX_DIGEST_LEN];
-               file_checksum(fn, sum, st->st_size);
+               file_checksum(fn, st, sum);
                return memcmp(sum, F_SUM(file), checksum_len) == 0;
        }
 
@@ -733,56 +738,81 @@ static int generate_and_send_sums(int fd, OFF_T len, int f_out, int f_copy)
 
 
 /* Try to find a filename in the same dir as "fname" with a similar name. */
-static int find_fuzzy(struct file_struct *file, struct file_list *dirlist)
+static struct file_struct *find_fuzzy(struct file_struct *file, struct file_list *dirlist_array[], uchar *fnamecmp_type_ptr)
 {
        int fname_len, fname_suf_len;
        const char *fname_suf, *fname = file->basename;
        uint32 lowest_dist = 25 << 16; /* ignore a distance greater than 25 */
-       int j, lowest_j = -1;
+       int i, j;
+       struct file_struct *lowest_fp = NULL;
 
        fname_len = strlen(fname);
        fname_suf = find_filename_suffix(fname, fname_len, &fname_suf_len);
 
-       for (j = 0; j < dirlist->used; j++) {
-               struct file_struct *fp = dirlist->files[j];
-               const char *suf, *name;
-               int len, suf_len;
-               uint32 dist;
+       /* Try to find an exact size+mtime match first. */
+       for (i = 0; i < fuzzy_basis; i++) {
+               struct file_list *dirlist = dirlist_array[i];
 
-               if (!S_ISREG(fp->mode) || !F_LENGTH(fp)
-                || fp->flags & FLAG_FILE_SENT)
+               if (!dirlist)
                        continue;
 
-               name = fp->basename;
+               for (j = 0; j < dirlist->used; j++) {
+                       struct file_struct *fp = dirlist->files[j];
 
-               if (F_LENGTH(fp) == F_LENGTH(file)
-                   && cmp_time(fp->modtime, file->modtime) == 0) {
-                       if (DEBUG_GTE(FUZZY, 2)) {
-                               rprintf(FINFO,
-                                       "fuzzy size/modtime match for %s\n",
-                                       name);
+                       if (!F_IS_ACTIVE(fp))
+                               continue;
+
+                       if (!S_ISREG(fp->mode) || !F_LENGTH(fp) || fp->flags & FLAG_FILE_SENT)
+                               continue;
+
+                       if (F_LENGTH(fp) == F_LENGTH(file) && cmp_time(fp->modtime, file->modtime) == 0) {
+                               if (DEBUG_GTE(FUZZY, 2))
+                                       rprintf(FINFO, "fuzzy size/modtime match for %s\n", f_name(fp, NULL));
+                               *fnamecmp_type_ptr = FNAMECMP_FUZZY + i;
+                               return fp;
                        }
-                       return j;
+
                }
+       }
 
-               len = strlen(name);
-               suf = find_filename_suffix(name, len, &suf_len);
+       for (i = 0; i < fuzzy_basis; i++) {
+               struct file_list *dirlist = dirlist_array[i];
 
-               dist = fuzzy_distance(name, len, fname, fname_len);
-               /* Add some extra weight to how well the suffixes match. */
-               dist += fuzzy_distance(suf, suf_len, fname_suf, fname_suf_len)
-                     * 10;
-               if (DEBUG_GTE(FUZZY, 2)) {
-                       rprintf(FINFO, "fuzzy distance for %s = %d.%05d\n",
-                               name, (int)(dist>>16), (int)(dist&0xFFFF));
-               }
-               if (dist <= lowest_dist) {
-                       lowest_dist = dist;
-                       lowest_j = j;
+               if (!dirlist)
+                       continue;
+
+               for (j = 0; j < dirlist->used; j++) {
+                       struct file_struct *fp = dirlist->files[j];
+                       const char *suf, *name;
+                       int len, suf_len;
+                       uint32 dist;
+
+                       if (!F_IS_ACTIVE(fp))
+                               continue;
+
+                       if (!S_ISREG(fp->mode) || !F_LENGTH(fp) || fp->flags & FLAG_FILE_SENT)
+                               continue;
+
+                       name = fp->basename;
+                       len = strlen(name);
+                       suf = find_filename_suffix(name, len, &suf_len);
+
+                       dist = fuzzy_distance(name, len, fname, fname_len);
+                       /* Add some extra weight to how well the suffixes match. */
+                       dist += fuzzy_distance(suf, suf_len, fname_suf, fname_suf_len) * 10;
+                       if (DEBUG_GTE(FUZZY, 2)) {
+                               rprintf(FINFO, "fuzzy distance for %s = %d.%05d\n",
+                                       f_name(fp, NULL), (int)(dist>>16), (int)(dist&0xFFFF));
+                       }
+                       if (dist <= lowest_dist) {
+                               lowest_dist = dist;
+                               lowest_fp = fp;
+                               *fnamecmp_type_ptr = FNAMECMP_FUZZY + i;
+                       }
                }
        }
 
-       return lowest_j;
+       return lowest_fp;
 }
 
 /* Copy a file found in our --copy-dest handling. */
@@ -824,11 +854,15 @@ static int copy_altdest_file(const char *src, const char *dest, struct file_stru
 
 /* This is only called for regular files.  We return -2 if we've finished
  * handling the file, -1 if no dest-linking occurred, or a non-negative
- * value if we found an alternate basis file. */
+ * value if we found an alternate basis file.  If we're called with the
+ * find_exact_for_existing flag, the destination file already exists, so
+ * we only try to find an exact alt-dest match.  In this case, the returns
+ * are only -2 & -1 (both as above). */
 static int try_dests_reg(struct file_struct *file, char *fname, int ndx,
-                        char *cmpbuf, stat_x *sxp, int itemizing,
-                        enum logcode code)
+                        char *cmpbuf, stat_x *sxp, int find_exact_for_existing,
+                        int itemizing, enum logcode code)
 {
+       STRUCT_STAT real_st = sxp->st;
        int best_match = -1;
        int match_level = 0;
        int j = 0;
@@ -849,8 +883,10 @@ static int try_dests_reg(struct file_struct *file, char *fname, int ndx,
                        match_level = 2;
                        /* FALL THROUGH */
                case 2:
-                       if (!unchanged_attrs(cmpbuf, file, sxp))
+                       if (!unchanged_attrs(cmpbuf, file, sxp)) {
+                               free_stat_x(sxp);
                                continue;
+                       }
                        best_match = j;
                        match_level = 3;
                        break;
@@ -869,6 +905,14 @@ static int try_dests_reg(struct file_struct *file, char *fname, int ndx,
        }
 
        if (match_level == 3 && !copy_dest) {
+               if (find_exact_for_existing) {
+                       if (link_dest && real_st.st_dev == sxp->st.st_dev && real_st.st_ino == sxp->st.st_ino)
+                               return -1;
+                       if (do_unlink(fname) < 0 && errno != ENOENT) {
+                               sxp->st = real_st;
+                               return -1;
+                       }
+               }
 #ifdef SUPPORT_HARD_LINKS
                if (link_dest) {
                        if (!hard_link_one(file, fname, cmpbuf, 1))
@@ -882,19 +926,29 @@ static int try_dests_reg(struct file_struct *file, char *fname, int ndx,
                        }
                } else
 #endif
-               if (itemizing)
-                       itemize(cmpbuf, file, ndx, 0, sxp, 0, 0, NULL);
+               {
+                       if (itemizing)
+                               itemize(cmpbuf, file, ndx, 0, sxp, 0, 0, NULL);
+               }
                if (INFO_GTE(NAME, 2) && maybe_ATTRS_REPORT)
                        rprintf(FCLIENT, "%s is uptodate\n", fname);
                return -2;
        }
 
+       if (find_exact_for_existing) {
+               sxp->st = real_st;
+               return -1;
+       }
+
        if (match_level >= 2) {
 #ifdef SUPPORT_HARD_LINKS
          try_a_copy: /* Copy the file locally. */
 #endif
-               if (!dry_run && copy_altdest_file(cmpbuf, fname, file) < 0)
+               if (!dry_run && copy_altdest_file(cmpbuf, fname, file) < 0) {
+                       if (find_exact_for_existing) /* Can get here via hard-link failure */
+                               sxp->st = real_st;
                        return -1;
+               }
                if (itemizing)
                        itemize(cmpbuf, file, ndx, 0, sxp, ITEM_LOCAL_CHANGE, 0, NULL);
                if (maybe_ATTRS_REPORT
@@ -1123,12 +1177,13 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
                           int itemizing, enum logcode code, int f_out)
 {
        static const char *parent_dirname = "";
+       static struct file_struct *prior_dir_file = NULL;
        /* Missing dir not created due to --dry-run; will still be scanned. */
        static struct file_struct *dry_missing_dir = NULL;
        /* Missing dir whose contents are skipped altogether due to
         * --ignore-non-existing, daemon exclude, or mkdir failure. */
        static struct file_struct *skip_dir = NULL;
-       static struct file_list *fuzzy_dirlist = NULL;
+       static struct file_list *fuzzy_dirlist[MAX_BASIS_DIRS+1];
        static int need_fuzzy_dirlist = 0;
        struct file_struct *fuzzy_file = NULL;
        int fd = -1, f_copy = -1;
@@ -1178,7 +1233,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
                                handle_skipped_hlink(file, itemizing, code, f_out);
 #endif
                        rprintf(FERROR_XFER,
-                               "skipping daemon-excluded %s \"%s\"\n",
+                               "ERROR: daemon refused to receive %s \"%s\"\n",
                                is_dir ? "directory" : "file", fname);
                        if (is_dir)
                                goto skipping_dir_contents;
@@ -1187,10 +1242,13 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
        }
 
        if (dry_run > 1 || (dry_missing_dir && is_below(file, dry_missing_dir))) {
+               int i;
          parent_is_dry_missing:
-               if (fuzzy_dirlist) {
-                       flist_free(fuzzy_dirlist);
-                       fuzzy_dirlist = NULL;
+               for (i = 0; i < fuzzy_basis; i++) {
+                       if (fuzzy_dirlist[i]) {
+                               flist_free(fuzzy_dirlist[i]);
+                               fuzzy_dirlist[i] = NULL;
+                       }
                }
                parent_dirname = "";
                statret = -1;
@@ -1199,6 +1257,18 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
                const char *dn = file->dirname ? file->dirname : ".";
                dry_missing_dir = NULL;
                if (parent_dirname != dn && strcmp(parent_dirname, dn) != 0) {
+                       /* Each parent dir must be in the file list or the flist data is bad.
+                        * Optimization: most of the time the parent dir will be the last dir
+                        * this function was asked to process in the file list. */
+                       if (!inc_recurse
+                        && (*dn != '.' || dn[1]) /* Avoid an issue with --relative and the "." dir. */
+                        && (prior_dir_file && strcmp(dn, f_name(prior_dir_file, NULL)) != 0)
+                        && flist_find_name(cur_flist, dn, 1) < 0) {
+                               rprintf(FERROR,
+                                       "ABORTING due to invalid path from sender: %s/%s\n",
+                                       dn, file->basename);
+                               exit_cleanup(RERR_PROTOCOL);
+                       }
                        if (relative_paths && !implied_dirs
                         && do_stat(dn, &sx.st) < 0) {
                                if (dry_run)
@@ -1209,12 +1279,16 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
                                                full_fname(dn));
                                }
                        }
-                       if (fuzzy_dirlist) {
-                               flist_free(fuzzy_dirlist);
-                               fuzzy_dirlist = NULL;
-                       }
-                       if (fuzzy_basis)
+                       if (fuzzy_basis) {
+                               int i;
+                               for (i = 0; i < fuzzy_basis; i++) {
+                                       if (fuzzy_dirlist[i]) {
+                                               flist_free(fuzzy_dirlist[i]);
+                                               fuzzy_dirlist[i] = NULL;
+                                       }
+                               }
                                need_fuzzy_dirlist = 1;
+                       }
 #ifdef SUPPORT_ACLS
                        if (!preserve_perms)
                                dflt_perms = default_perms_for_dir(dn);
@@ -1223,8 +1297,17 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
                parent_dirname = dn;
 
                if (need_fuzzy_dirlist && S_ISREG(file->mode)) {
+                       int i;
                        strlcpy(fnamecmpbuf, dn, sizeof fnamecmpbuf);
-                       fuzzy_dirlist = get_dirlist(fnamecmpbuf, -1, GDL_IGNORE_FILTER_RULES);
+                       for (i = 0; i < fuzzy_basis; i++) {
+                               if (i && pathjoin(fnamecmpbuf, MAXPATHLEN, basis_dir[i-1], dn) >= MAXPATHLEN)
+                                       continue;
+                               fuzzy_dirlist[i] = get_dirlist(fnamecmpbuf, -1, GDL_IGNORE_FILTER_RULES);
+                               if (fuzzy_dirlist[i] && fuzzy_dirlist[i]->used == 0) {
+                                       flist_free(fuzzy_dirlist[i]);
+                                       fuzzy_dirlist[i] = NULL;
+                               }
+                       }
                        need_fuzzy_dirlist = 0;
                }
 
@@ -1286,6 +1369,8 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
                } else
                        added_perms = 0;
                if (is_dir < 0) {
+                       if (!(preserve_times & PRESERVE_DIR_TIMES))
+                               return;
                        /* In inc_recurse mode we want to make sure any missing
                         * directories get created while we're still processing
                         * the parent dir (which allows us to touch the parent
@@ -1314,8 +1399,9 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
                                dry_missing_dir = file;
                        file->flags |= FLAG_MISSING_DIR;
                }
+               init_stat_x(&real_sx);
+               real_sx.st = sx.st;
                real_ret = statret;
-               real_sx = sx;
                if (file->flags & FLAG_DIR_CREATED)
                        statret = -1;
                if (!preserve_perms) { /* See comment in non-dir code below. */
@@ -1353,6 +1439,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
                                goto cleanup;
                        }
                }
+
 #ifdef SUPPORT_XATTRS
                if (preserve_xattrs && statret == 1)
                        copy_xattrs(fnamecmpbuf, fname);
@@ -1361,12 +1448,13 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
                    && INFO_GTE(NAME, 1) && code != FNONE && f_out != -1)
                        rprintf(code, "%s/\n", fname);
 
-               /* We need to ensure that the dirs in the transfer have writable
-                * permissions during the time we are putting files within them.
-                * This is then fixed after the transfer is done. */
+               /* We need to ensure that the dirs in the transfer have both
+                * readable and writable permissions during the time we are
+                * putting files within them.  This is then restored to the
+                * former permissions after the transfer is done. */
 #ifdef HAVE_CHMOD
-               if (!am_root && !(file->mode & S_IWUSR) && dir_tweaking) {
-                       mode_t mode = file->mode | S_IWUSR;
+               if (!am_root && (file->mode & S_IRWXU) != S_IRWXU && dir_tweaking) {
+                       mode_t mode = file->mode | S_IRWXU;
                        if (do_chmod(fname, mode) < 0) {
                                rsyserr(FERROR_XFER, errno,
                                        "failed to modify permissions on %s",
@@ -1392,6 +1480,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
                        else
                                change_local_filter_dir(fname, strlen(fname), F_DEPTH(file));
                }
+               prior_dir_file = file;
                goto cleanup;
        }
 
@@ -1436,7 +1525,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
                                set_file_attrs(fname, file, &sx, NULL, maybe_ATTRS_REPORT);
                                if (itemizing)
                                        itemize(fname, file, ndx, 0, &sx, 0, 0, NULL);
-#if defined SUPPORT_HARD_LINKS && defined CAN_HARDLINK_SYMLINK
+#ifdef SUPPORT_HARD_LINKS
                                if (preserve_hard_links && F_IS_HLINKED(file))
                                        finish_hard_link(file, fname, ndx, &sx.st, itemizing, code, -1);
 #endif
@@ -1457,15 +1546,17 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
                                        goto cleanup;
                                itemizing = 0;
                                code = FNONE;
-                       } else if (j >= 0)
+                       } else if (j >= 0) {
                                statret = 1;
+                               fnamecmp = fnamecmpbuf;
+                       }
                }
-               if (atomic_create(file, fname, sl, MAKEDEV(0, 0), &sx, statret == 0 ? DEL_FOR_SYMLINK : 0)) {
+               if (atomic_create(file, fname, sl, NULL, MAKEDEV(0, 0), &sx, statret == 0 ? DEL_FOR_SYMLINK : 0)) {
                        set_file_attrs(fname, file, NULL, NULL, 0);
                        if (itemizing) {
                                if (statret == 0 && !S_ISLNK(sx.st.st_mode))
                                        statret = -1;
-                               itemize(fname, file, ndx, statret, &sx,
+                               itemize(fnamecmp, file, ndx, statret, &sx,
                                        ITEM_LOCAL_CHANGE|ITEM_REPORT_CHANGE, 0, NULL);
                        }
                        if (code != FNONE && INFO_GTE(NAME, 1))
@@ -1531,18 +1622,20 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
                                        goto cleanup;
                                itemizing = 0;
                                code = FNONE;
-                       } else if (j >= 0)
+                       } else if (j >= 0) {
                                statret = 1;
+                               fnamecmp = fnamecmpbuf;
+                       }
                }
                if (DEBUG_GTE(GENR, 1)) {
                        rprintf(FINFO, "mknod(%s, 0%o, [%ld,%ld])\n",
                                fname, (int)file->mode,
                                (long)major(rdev), (long)minor(rdev));
                }
-               if (atomic_create(file, fname, NULL, rdev, &sx, del_for_flag)) {
+               if (atomic_create(file, fname, NULL, NULL, rdev, &sx, del_for_flag)) {
                        set_file_attrs(fname, file, NULL, NULL, 0);
                        if (itemizing) {
-                               itemize(fname, file, ndx, statret, &sx,
+                               itemize(fnamecmp, file, ndx, statret, &sx,
                                        ITEM_LOCAL_CHANGE|ITEM_REPORT_CHANGE, 0, NULL);
                        }
                        if (code != FNONE && INFO_GTE(NAME, 1))
@@ -1564,7 +1657,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
                goto cleanup;
        }
 
-       if (max_size > 0 && F_LENGTH(file) > max_size) {
+       if (max_size >= 0 && F_LENGTH(file) > max_size) {
                if (INFO_GTE(SKIP, 1)) {
                        if (solo_file)
                                fname = f_name(file, NULL);
@@ -1572,7 +1665,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
                }
                goto cleanup;
        }
-       if (min_size > 0 && F_LENGTH(file) < min_size) {
+       if (min_size >= 0 && F_LENGTH(file) < min_size) {
                if (INFO_GTE(SKIP, 1)) {
                        if (solo_file)
                                fname = f_name(file, NULL);
@@ -1601,9 +1694,9 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
                stat_errno = ENOENT;
        }
 
-       if (statret != 0 && basis_dir[0] != NULL) {
+       if (basis_dir[0] != NULL && (statret != 0 || !copy_dest)) {
                int j = try_dests_reg(file, fname, ndx, fnamecmpbuf, &sx,
-                                     itemizing, code);
+                                     statret == 0, itemizing, code);
                if (j == -2) {
                        if (remove_source_files == 1)
                                goto return_with_success;
@@ -1616,8 +1709,9 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
                }
        }
 
+       init_stat_x(&real_sx);
+       real_sx.st = sx.st; /* Don't copy xattr/acl pointers, as they would free wrong. */
        real_ret = statret;
-       real_sx = sx;
 
        if (partial_dir && (partialptr = partial_dir_fname(fname)) != NULL
            && link_stat(partialptr, &partial_st, 0) == 0
@@ -1627,10 +1721,10 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
        } else
                partialptr = NULL;
 
-       if (statret != 0 && fuzzy_dirlist) {
-               int j = find_fuzzy(file, fuzzy_dirlist);
-               if (j >= 0) {
-                       fuzzy_file = fuzzy_dirlist->files[j];
+       if (statret != 0 && fuzzy_basis) {
+               /* Sets fnamecmp_type to FNAMECMP_FUZZY or above. */
+               fuzzy_file = find_fuzzy(file, fuzzy_dirlist, &fnamecmp_type);
+               if (fuzzy_file) {
                        f_name(fuzzy_file, fnamecmpbuf);
                        if (DEBUG_GTE(FUZZY, 1)) {
                                rprintf(FINFO, "fuzzy basis selected for %s: %s\n",
@@ -1639,7 +1733,6 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
                        sx.st.st_size = F_LENGTH(fuzzy_file);
                        statret = 0;
                        fnamecmp = fnamecmpbuf;
-                       fnamecmp_type = FNAMECMP_FUZZY;
                }
        }
 
@@ -1715,10 +1808,10 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
                goto notify_others;
        }
 
-       if (fuzzy_dirlist) {
-               int j = flist_find(fuzzy_dirlist, file);
+       if (fuzzy_dirlist[0]) {
+               int j = flist_find(fuzzy_dirlist[0], file);
                if (j >= 0) /* don't use changing file as future fuzzy basis */
-                       fuzzy_dirlist->files[j]->flags |= FLAG_FILE_SENT;
+                       fuzzy_dirlist[0]->files[j]->flags |= FLAG_FILE_SENT;
        }
 
        /* open the file */
@@ -1775,7 +1868,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
   notify_others:
        if (remove_source_files && !delay_updates && !phase && !dry_run)
                increment_active_files(ndx, itemizing, code);
-       if (inc_recurse && !dry_run)
+       if (inc_recurse && (!dry_run || write_batch < 0))
                cur_flist->in_progress++;
 #ifdef SUPPORT_HARD_LINKS
        if (preserve_hard_links && F_IS_HLINKED(file))
@@ -1788,18 +1881,11 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
                        iflags |= ITEM_REPORT_CHANGE;
                if (fnamecmp_type != FNAMECMP_FNAME)
                        iflags |= ITEM_BASIS_TYPE_FOLLOWS;
-               if (fnamecmp_type == FNAMECMP_FUZZY)
+               if (fnamecmp_type >= FNAMECMP_FUZZY)
                        iflags |= ITEM_XNAME_FOLLOWS;
                itemize(fnamecmp, file, -1, real_ret, &real_sx, iflags, fnamecmp_type,
                        fuzzy_file ? fuzzy_file->basename : NULL);
-#ifdef SUPPORT_ACLS
-               if (preserve_acls)
-                       free_acl(&real_sx);
-#endif
-#ifdef SUPPORT_XATTRS
-               if (preserve_xattrs)
-                       free_xattr(&real_sx);
-#endif
+               free_stat_x(&real_sx);
        }
 
        if (!do_xfers) {
@@ -1847,23 +1933,15 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
                unmake_file(back_file);
        }
 
-#ifdef SUPPORT_ACLS
-       if (preserve_acls)
-               free_acl(&sx);
-#endif
-#ifdef SUPPORT_XATTRS
-       if (preserve_xattrs)
-               free_xattr(&sx);
-#endif
-       return;
+       free_stat_x(&sx);
 }
 
 /* If we are replacing an existing hard link, symlink, device, or special file,
- * create a temp-name item and rename it into place.  Only a symlink or hard
- * link puts a non-NULL value into the lnk arg.  Only a device puts a non-0
- * value into the rdev arg.  Specify 0 for the del_for_flag if there is not a
- * file to replace.  This returns 1 on success and 0 on failure. */
-int atomic_create(struct file_struct *file, char *fname, const char *lnk,
+ * create a temp-name item and rename it into place.  A symlimk specifies slnk,
+ * a hard link specifies hlnk, otherwise we create a device based on rdev.
+ * Specify 0 for the del_for_flag if there is not a file to replace.  This
+ * returns 1 on success and 0 on failure. */
+int atomic_create(struct file_struct *file, char *fname, const char *slnk, const char *hlnk,
                  dev_t rdev, stat_x *sxp, int del_for_flag)
 {
        char tmpname[MAXPATHLEN];
@@ -1888,23 +1966,22 @@ int atomic_create(struct file_struct *file, char *fname, const char *lnk,
 
        create_name = skip_atomic ? fname : tmpname;
 
-       if (lnk) {
+       if (slnk) {
 #ifdef SUPPORT_LINKS
-               if (S_ISLNK(file->mode)
-#ifdef SUPPORT_HARD_LINKS /* The first symlink in a hard-linked cluster is always created. */
-                && (!F_IS_HLINKED(file) || file->flags & FLAG_HLINK_FIRST)
-#endif
-                ) {
-                       if (do_symlink(lnk, create_name) < 0) {
-                               rsyserr(FERROR_XFER, errno, "symlink %s -> \"%s\" failed",
-                                       full_fname(create_name), lnk);
-                               return 0;
-                       }
-               } else
+               if (do_symlink(slnk, create_name) < 0) {
+                       rsyserr(FERROR_XFER, errno, "symlink %s -> \"%s\" failed",
+                               full_fname(create_name), slnk);
+                       return 0;
+               }
+#else
+               return 0;
 #endif
+       } else if (hlnk) {
 #ifdef SUPPORT_HARD_LINKS
-               if (!hard_link_one(file, create_name, lnk, 0))
+               if (!hard_link_one(file, create_name, hlnk, 0))
                        return 0;
+#else
+               return 0;
 #endif
        } else {
                if (do_mknod(create_name, file->mode, rdev) < 0) {
@@ -1966,6 +2043,8 @@ static void touch_up_dirs(struct file_list *flist, int ndx)
         * transfer and/or re-set any tweaked modified-time values. */
        for (i = start; i <= end; i++, counter++) {
                file = flist->files[i];
+               if (!F_IS_ACTIVE(file))
+                       continue;
                if (!S_ISDIR(file->mode)
                 || (!implied_dirs && file->flags & FLAG_IMPLIED_DIR))
                        continue;
@@ -1976,8 +2055,7 @@ static void touch_up_dirs(struct file_list *flist, int ndx)
                }
                /* Be sure not to retouch permissions with --fake-super. */
                fix_dir_perms = !am_root && !(file->mode & S_IWUSR);
-               if (!F_IS_ACTIVE(file) || file->flags & FLAG_MISSING_DIR
-                || !(need_retouch_dir_times || fix_dir_perms))
+               if (file->flags & FLAG_MISSING_DIR || !(need_retouch_dir_times || fix_dir_perms))
                        continue;
                fname = f_name(file, NULL);
                if (fix_dir_perms)
@@ -2008,19 +2086,27 @@ void check_for_finished_files(int itemizing, enum logcode code, int check_redo)
        while (1) {
 #ifdef SUPPORT_HARD_LINKS
                if (preserve_hard_links && (ndx = get_hlink_num()) != -1) {
+                       int send_failed = (ndx == -2);
+                       if (send_failed)
+                               ndx = get_hlink_num();
                        flist = flist_for_ndx(ndx, "check_for_finished_files.1");
                        file = flist->files[ndx - flist->ndx_start];
                        assert(file->flags & FLAG_HLINKED);
-                       finish_hard_link(file, f_name(file, fbuf), ndx, NULL, itemizing, code, -1);
+                       if (send_failed)
+                               handle_skipped_hlink(file, itemizing, code, sock_f_out);
+                       else
+                               finish_hard_link(file, f_name(file, fbuf), ndx, NULL, itemizing, code, -1);
                        flist->in_progress--;
                        continue;
                }
 #endif
 
                if (check_redo && (ndx = get_redo_num()) != -1) {
+                       OFF_T save_max_size = max_size;
+                       OFF_T save_min_size = min_size;
                        csum_length = SUM_LENGTH;
-                       max_size = -max_size;
-                       min_size = -min_size;
+                       max_size = -1;
+                       min_size = -1;
                        ignore_existing = -ignore_existing;
                        ignore_non_existing = -ignore_non_existing;
                        update_only = -update_only;
@@ -2044,8 +2130,8 @@ void check_for_finished_files(int itemizing, enum logcode code, int check_redo)
                        cur_flist = flist;
 
                        csum_length = SHORT_SUM_LENGTH;
-                       max_size = -max_size;
-                       min_size = -min_size;
+                       max_size = save_max_size;
+                       min_size = save_min_size;
                        ignore_existing = -ignore_existing;
                        ignore_non_existing = -ignore_non_existing;
                        update_only = -update_only;
@@ -2116,7 +2202,7 @@ void generate_files(int f_out, const char *local_name)
        implied_dirs_are_missing = relative_paths && !implied_dirs && protocol_version < 30;
 
        if (DEBUG_GTE(GENR, 1))
-               rprintf(FINFO, "generator starting pid=%ld\n", (long)getpid());
+               rprintf(FINFO, "generator starting pid=%d\n", (int)getpid());
 
        if (delete_before && !solo_file && cur_flist->used > 0)
                do_delete_pass();