Implement --tweak, --no-tweak, --no-tweak-hlinked options.
[rsync/rsync.git] / generator.c
index b1634bd..4a3e65b 100644 (file)
@@ -58,6 +58,7 @@ extern int update_only;
 extern int ignore_existing;
 extern int ignore_non_existing;
 extern int inplace;
+extern int tweak_attrs;
 extern int append_mode;
 extern int make_backups;
 extern int csum_length;
@@ -97,7 +98,7 @@ extern char *backup_dir;
 extern char *backup_suffix;
 extern int backup_suffix_len;
 extern struct file_list *cur_flist, *first_flist, *dir_flist;
-extern struct filter_list_struct server_filter_list;
+extern struct filter_list_struct daemon_filter_list;
 
 int ignore_perishable = 0;
 int non_perishable_cnt = 0;
@@ -135,8 +136,12 @@ enum delret {
     DR_SUCCESS = 0, DR_FAILURE, DR_AT_LIMIT, DR_NOT_EMPTY
 };
 
-/* Forward declaration for delete_item(). */
+/* Forward declarations. */
 static enum delret delete_dir_contents(char *fname, uint16 flags);
+#ifdef SUPPORT_HARD_LINKS
+static void handle_skipped_hlink(struct file_struct *file, int itemizing,
+                                enum logcode code, int f_out);
+#endif
 
 static int is_backup_file(char *fn)
 {
@@ -279,7 +284,7 @@ static enum delret delete_dir_contents(char *fname, uint16 flags)
        for (j = dirlist->used; j--; ) {
                struct file_struct *fp = dirlist->files[j];
 
-               if (fp->flags & FLAG_MOUNT_DIR) {
+               if (fp->flags & FLAG_MOUNT_DIR && S_ISDIR(fp->mode)) {
                        if (verbose > 1) {
                                rprintf(FINFO,
                                    "mount point, %s, pins parent directory\n",
@@ -511,7 +516,7 @@ static void delete_in_dir(char *fbuf, struct file_struct *file, dev_t *fs_dev)
                struct file_struct *fp = dirlist->files[i];
                if (!F_IS_ACTIVE(fp))
                        continue;
-               if (fp->flags & FLAG_MOUNT_DIR) {
+               if (fp->flags & FLAG_MOUNT_DIR && S_ISDIR(fp->mode)) {
                        if (verbose > 1)
                                rprintf(FINFO, "cannot delete mount point: %s\n",
                                        f_name(fp, NULL));
@@ -1206,25 +1211,42 @@ static void list_file_entry(struct file_struct *f)
        }
 }
 
+static int may_tweak(STRUCT_STAT *st)
+{
+       return tweak_attrs == 2 || (tweak_attrs == 1 && st->st_nlink == 1);
+}
+
 static int phase = 0;
 static int dflt_perms;
 
+static int implied_dirs_are_missing;
+/* Helper for recv_generator's skip_dir and dry_missing_dir tests. */
+static BOOL is_below(struct file_struct *file, struct file_struct *subtree)
+{
+       return F_DEPTH(file) > F_DEPTH(subtree)
+               && (!implied_dirs_are_missing || f_name_has_prefix(file, subtree));
+}
+
 /* Acts on the indicated item in cur_flist whose name is fname.  If a dir,
  * make sure it exists, and has the right permissions/timestamp info.  For
  * all other non-regular files (symlinks, etc.) we create them here.  For
  * regular files that have changed, we try to find a basis file and then
  * start sending checksums.  The ndx is the file's unique index value.
  *
- * When fname is non-null, it must point to a MAXPATHLEN buffer!
+ * The fname parameter must point to a MAXPATHLEN buffer!  (e.g it gets
+ * passed to delete_item(), which can use it during a recursive delete.)
  *
  * Note that f_out is set to -1 when doing final directory-permission and
  * modification-time repair. */
 static void recv_generator(char *fname, struct file_struct *file, int ndx,
                           int itemizing, enum logcode code, int f_out)
 {
-       static int missing_below = -1, excluded_below = -1;
        static const char *parent_dirname = "";
-       static struct file_struct *missing_dir = NULL, *excluded_dir = 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 int need_fuzzy_dirlist = 0;
        struct file_struct *fuzzy_file = NULL;
@@ -1236,7 +1258,6 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
        char *fnamecmp, *partialptr, *backupptr = NULL;
        char fnamecmpbuf[MAXPATHLEN];
        uchar fnamecmp_type;
-       int implied_dirs_are_missing = relative_paths && !implied_dirs && protocol_version < 30;
        int del_opts = delete_mode || force_delete ? DEL_RECURSE : 0;
        int is_dir = !S_ISDIR(file->mode) ? 0
                   : inc_recurse && ndx != cur_flist->ndx_start - 1 ? -1
@@ -1253,48 +1274,44 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
                return;
        }
 
-       if (server_filter_list.head) {
-               int filtered = check_filter(&server_filter_list, fname, is_dir) < 0;
-               if (is_dir < 0 && filtered)
-                       return;
-               if (excluded_below >= 0) {
-                       if (F_DEPTH(file) > excluded_below
-                        && (!implied_dirs_are_missing || f_name_has_prefix(file, excluded_dir)))
-                               goto skipping;
-                       excluded_below = -1;
-               }
-               if (filtered) {
-                       if (is_dir) {
-                               excluded_below = F_DEPTH(file);
-                               excluded_dir = file;
-                       }
-                 skipping:
-                       rprintf(FERROR_XFER,
-                               "skipping daemon-excluded file \"%s\"\n",
-                               fname);
+       if (skip_dir) {
+               if (is_below(file, skip_dir)) {
+                       if (is_dir)
+                               file->flags |= FLAG_MISSING_DIR;
+#ifdef SUPPORT_HARD_LINKS
+                       else if (F_IS_HLINKED(file))
+                               handle_skipped_hlink(file, itemizing, code, f_out);
+#endif
                        return;
                }
+               skip_dir = NULL;
        }
 
-       if (missing_below >= 0) {
-               if (F_DEPTH(file) <= missing_below
-                || (implied_dirs_are_missing && !f_name_has_prefix(file, missing_dir))) {
-                       if (dry_run)
-                               dry_run--;
-                       missing_below = -1;
-               } else if (!dry_run) {
+       if (daemon_filter_list.head) {
+               if (check_filter(&daemon_filter_list, fname, is_dir) < 0) {
+                       if (is_dir < 0)
+                               return;
+#ifdef SUPPORT_HARD_LINKS
+                       if (F_IS_HLINKED(file))
+                               handle_skipped_hlink(file, itemizing, code, f_out);
+#endif
+                       rprintf(FERROR_XFER,
+                               "skipping daemon-excluded %s \"%s\"\n",
+                               is_dir ? "directory" : "file", fname);
                        if (is_dir)
-                               file->flags |= FLAG_MISSING_DIR;
+                               goto skipping_dir_contents;
                        return;
                }
        }
+
 #ifdef SUPPORT_ACLS
        sx.acc_acl = sx.def_acl = NULL;
 #endif
 #ifdef SUPPORT_XATTRS
        sx.xattr = NULL;
 #endif
-       if (dry_run > 1) {
+       if (dry_run > 1 || (dry_missing_dir && is_below(file, dry_missing_dir))) {
+         parent_is_dry_missing:
                if (fuzzy_dirlist) {
                        flist_free(fuzzy_dirlist);
                        fuzzy_dirlist = NULL;
@@ -1304,13 +1321,17 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
                stat_errno = ENOENT;
        } else {
                const char *dn = file->dirname ? file->dirname : ".";
+               dry_missing_dir = NULL;
                if (parent_dirname != dn && strcmp(parent_dirname, dn) != 0) {
                        if (relative_paths && !implied_dirs
-                        && do_stat(dn, &sx.st) < 0
-                        && create_directory_path(fname) < 0) {
-                               rsyserr(FERROR_XFER, errno,
-                                       "recv_generator: mkdir %s failed",
-                                       full_fname(dn));
+                        && do_stat(dn, &sx.st) < 0) {
+                               if (dry_run)
+                                       goto parent_is_dry_missing;
+                               if (create_directory_path(fname) < 0) {
+                                       rsyserr(FERROR_XFER, errno,
+                                               "recv_generator: mkdir %s failed",
+                                               full_fname(dn));
+                               }
                        }
                        if (fuzzy_dirlist) {
                                flist_free(fuzzy_dirlist);
@@ -1339,14 +1360,13 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
                if (is_dir) {
                        if (is_dir < 0)
                                return;
-                       if (missing_below < 0) {
-                               if (dry_run)
-                                       dry_run++;
-                               missing_below = F_DEPTH(file);
-                               missing_dir = file;
-                       }
+                       skip_dir = file;
                        file->flags |= FLAG_MISSING_DIR;
                }
+#ifdef SUPPORT_HARD_LINKS
+               else if (F_IS_HLINKED(file))
+                       handle_skipped_hlink(file, itemizing, code, f_out);
+#endif
                if (verbose > 1) {
                        rprintf(FINFO, "not creating new %s \"%s\"\n",
                                is_dir ? "directory" : "file", fname);
@@ -1358,6 +1378,17 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
         && !am_root && sx.st.st_uid == our_uid)
                del_opts |= DEL_NO_UID_WRITE;
 
+       if (ignore_existing > 0 && statret == 0
+        && (!is_dir || !S_ISDIR(sx.st.st_mode))) {
+               if (verbose > 1 && is_dir >= 0)
+                       rprintf(FINFO, "%s exists\n", fname);
+#ifdef SUPPORT_HARD_LINKS
+               if (F_IS_HLINKED(file))
+                       handle_skipped_hlink(file, itemizing, code, f_out);
+#endif
+               goto cleanup;
+       }
+
        if (is_dir) {
                if (!implied_dirs && file->flags & FLAG_IMPLIED_DIR)
                        goto cleanup;
@@ -1385,10 +1416,10 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
                                goto skipping_dir_contents;
                        statret = -1;
                }
-               if (dry_run && statret != 0 && missing_below < 0) {
-                       missing_below = F_DEPTH(file);
-                       missing_dir = file;
-                       dry_run++;
+               if (dry_run && statret != 0) {
+                       if (!dry_missing_dir)
+                               dry_missing_dir = file;
+                       file->flags |= FLAG_MISSING_DIR;
                }
                real_ret = statret;
                real_sx = sx;
@@ -1421,8 +1452,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
                          skipping_dir_contents:
                                rprintf(FERROR,
                                    "*** Skipping any contents from this failed directory ***\n");
-                               missing_below = F_DEPTH(file);
-                               missing_dir = file;
+                               skip_dir = file;
                                file->flags |= FLAG_MISSING_DIR;
                                goto cleanup;
                        }
@@ -1455,8 +1485,8 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
                                DEV_MINOR(devp) = minor(real_sx.st.st_dev);
                        }
                }
-               else if (delete_during && f_out != -1 && !phase && dry_run < 2
-                   && (file->flags & FLAG_CONTENT_DIR))
+               else if (delete_during && f_out != -1 && !phase
+                   && BITS_SETnUNSET(file->flags, FLAG_CONTENT_DIR, FLAG_MISSING_DIR))
                        delete_in_dir(fname, file, &real_sx.st.st_dev);
                goto cleanup;
        }
@@ -1477,6 +1507,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
 
        if (preserve_links && S_ISLNK(file->mode)) {
 #ifdef SUPPORT_LINKS
+               int iflags = 0;
                const char *sl = F_SYMLINK(file);
                if (safe_symlinks && unsafe_symlink(sl, fname)) {
                        if (verbose) {
@@ -1497,7 +1528,15 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
                        else if ((len = readlink(fname, lnk, MAXPATHLEN-1)) > 0
                              && strncmp(lnk, sl, len) == 0 && sl[len] == '\0') {
                                /* The link is pointing to the right place. */
-                               set_file_attrs(fname, file, &sx, NULL, maybe_ATTRS_REPORT);
+                               if (may_tweak(&sx.st)) {
+                                       if (verbose > 2)
+                                               rprintf(FINFO, "possibly tweaking attributes of %s\n", fname);
+                                       set_file_attrs(fname, file, &sx, NULL, maybe_ATTRS_REPORT);
+                               } else if (!unchanged_attrs(fname, file, &sx)) {
+                                       if (verbose > 2)
+                                               rprintf(FINFO, "recreating %s due to changed attributes\n", fname);
+                                       goto recreate_symlink;
+                               }
                                if (itemizing)
                                        itemize(fname, file, ndx, 0, &sx, 0, 0, NULL);
 #if defined SUPPORT_HARD_LINKS && defined CAN_HARDLINK_SYMLINK
@@ -1507,7 +1546,9 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
                                if (remove_source_files == 1)
                                        goto return_with_success;
                                goto cleanup;
-                       }
+                       } else
+                               iflags = ITEM_REPORT_CHANGE;
+                 recreate_symlink:
                        /* Not the right symlink (or not a symlink), so
                         * delete it. */
                        if (delete_item(fname, sx.st.st_mode, del_opts | DEL_FOR_SYMLINK) != 0)
@@ -1541,18 +1582,20 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
                        set_file_attrs(fname, file, NULL, NULL, 0);
                        if (itemizing) {
                                itemize(fname, file, ndx, statret, &sx,
-                                       ITEM_LOCAL_CHANGE|ITEM_REPORT_CHANGE, 0, NULL);
+                                       ITEM_LOCAL_CHANGE|iflags, 0, NULL);
                        }
-                       if (code != FNONE && verbose)
+                       if ((iflags & ITEM_REPORT_CHANGE) && code != FNONE && verbose)
                                rprintf(code, "%s -> %s\n", fname, sl);
 #ifdef SUPPORT_HARD_LINKS
                        if (preserve_hard_links && F_IS_HLINKED(file))
                                finish_hard_link(file, fname, ndx, NULL, itemizing, code, -1);
 #endif
-                       /* This does not check remove_source_files == 1
-                        * because this is one of the items that the old
-                        * --remove-sent-files option would remove. */
-                       if (remove_source_files)
+                       /* When the symlink value changed, we do not check
+                        * remove_source_files == 1 because this is one of the
+                        * items that the old --remove-sent-files option would
+                        * remove. */
+                       if ((iflags & ITEM_REPORT_CHANGE) ? remove_source_files
+                             : remove_source_files == 1)
                                goto return_with_success;
                }
 #endif
@@ -1561,6 +1604,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
 
        if ((am_root && preserve_devices && IS_DEVICE(file->mode))
         || (preserve_specials && IS_SPECIAL(file->mode))) {
+               int iflags = 0;
                uint32 *devp = F_RDEV_P(file);
                dev_t rdev = MAKEDEV(DEV_MAJOR(devp), DEV_MINOR(devp));
                if (statret == 0) {
@@ -1578,7 +1622,15 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
                         && BITS_EQUAL(sx.st.st_mode, file->mode, _S_IFMT)
                         && sx.st.st_rdev == rdev) {
                                /* The device or special file is identical. */
-                               set_file_attrs(fname, file, &sx, NULL, maybe_ATTRS_REPORT);
+                               if (may_tweak(&sx.st)) {
+                                       if (verbose > 2)
+                                               rprintf(FINFO, "possibly tweaking attributes of %s\n", fname);
+                                       set_file_attrs(fname, file, &sx, NULL, maybe_ATTRS_REPORT);
+                               } else if (!unchanged_attrs(fname, file, &sx)) {
+                                       if (verbose > 2)
+                                               rprintf(FINFO, "recreating %s due to changed attributes\n", fname);
+                                       goto recreate_D;
+                               }
                                if (itemizing)
                                        itemize(fname, file, ndx, 0, &sx, 0, 0, NULL);
 #ifdef SUPPORT_HARD_LINKS
@@ -1588,7 +1640,9 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
                                if (remove_source_files == 1)
                                        goto return_with_success;
                                goto cleanup;
-                       }
+                       } else
+                               iflags = ITEM_REPORT_CHANGE;
+                 recreate_D:
                        if (delete_item(fname, sx.st.st_mode, del_opts | del_for_flag) != 0)
                                goto cleanup;
                } else if (basis_dir[0] != NULL) {
@@ -1625,9 +1679,9 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
                        set_file_attrs(fname, file, NULL, NULL, 0);
                        if (itemizing) {
                                itemize(fname, file, ndx, statret, &sx,
-                                       ITEM_LOCAL_CHANGE|ITEM_REPORT_CHANGE, 0, NULL);
+                                       ITEM_LOCAL_CHANGE|iflags, 0, NULL);
                        }
-                       if (code != FNONE && verbose)
+                       if ((iflags & ITEM_REPORT_CHANGE) && code != FNONE && verbose)
                                rprintf(code, "%s\n", fname);
 #ifdef SUPPORT_HARD_LINKS
                        if (preserve_hard_links && F_IS_HLINKED(file))
@@ -1663,16 +1717,14 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
                goto cleanup;
        }
 
-       if (ignore_existing > 0 && statret == 0) {
-               if (verbose > 1)
-                       rprintf(FINFO, "%s exists\n", fname);
-               goto cleanup;
-       }
-
        if (update_only > 0 && statret == 0
            && cmp_time(sx.st.st_mtime, file->modtime) > 0) {
                if (verbose > 1)
                        rprintf(FINFO, "%s is newer\n", fname);
+#ifdef SUPPORT_HARD_LINKS
+               if (F_IS_HLINKED(file))
+                       handle_skipped_hlink(file, itemizing, code, f_out);
+#endif
                goto cleanup;
        }
 
@@ -1712,7 +1764,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
        } else
                partialptr = NULL;
 
-       if (statret != 0 && fuzzy_dirlist && dry_run <= 1) {
+       if (statret != 0 && fuzzy_dirlist) {
                int j = find_fuzzy(file, fuzzy_dirlist);
                if (j >= 0) {
                        fuzzy_file = fuzzy_dirlist->files[j];
@@ -1742,21 +1794,36 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
                goto cleanup;
        }
 
-       if (append_mode > 0 && sx.st.st_size >= F_LENGTH(file))
-               goto cleanup;
-
        if (fnamecmp_type <= FNAMECMP_BASIS_DIR_HIGH)
                ;
        else if (fnamecmp_type == FNAMECMP_FUZZY)
                ;
        else if (unchanged_file(fnamecmp, file, &sx.st)) {
+               /* fnamecmp == fname, fnamecmp_type == FNAMECMP_FNAME */
+               int iflags = 0;
+
                if (partialptr) {
                        do_unlink(partialptr);
                        handle_partial_dir(partialptr, PDIR_DELETE);
                }
-               set_file_attrs(fname, file, &sx, NULL, maybe_ATTRS_REPORT);
+               if (may_tweak(&sx.st)) {
+                       /* Currently, we call set_file_attrs on all tweakable
+                        * files, though ideally it would have no effect when
+                        * unchanged_attrs returns true. */
+                       if (verbose > 2)
+                               rprintf(FINFO, "possibly tweaking attributes of %s\n", fname);
+                       set_file_attrs(fname, file, &sx, NULL, maybe_ATTRS_REPORT);
+               } else if (!unchanged_attrs(fname, file, &sx)) {
+                       /* Need to recreate the file.
+                        * copy_altdest_file sets its attributes, etc. */
+                       if (verbose > 2)
+                               rprintf(FINFO, "recreating %s due to changed attributes\n", fname);
+                       if (!dry_run && copy_altdest_file(fnamecmp, fname, file))
+                               goto cleanup;
+                       iflags |= ITEM_LOCAL_CHANGE;
+               }
                if (itemizing)
-                       itemize(fnamecmp, file, ndx, statret, &sx, 0, 0, NULL);
+                       itemize(fnamecmp, file, ndx, statret, &sx, iflags, 0, NULL);
 #ifdef SUPPORT_HARD_LINKS
                if (preserve_hard_links && F_IS_HLINKED(file))
                        finish_hard_link(file, fname, ndx, &sx.st, itemizing, code, -1);
@@ -1769,6 +1836,14 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
                goto cleanup;
        }
 
+       if (append_mode > 0 && sx.st.st_size >= F_LENGTH(file)) {
+#ifdef SUPPORT_HARD_LINKS
+               if (F_IS_HLINKED(file))
+                       handle_skipped_hlink(file, itemizing, code, f_out);
+#endif
+               goto cleanup;
+       }
+
   prepare_to_open:
        if (partialptr) {
                sx.st = partial_st;
@@ -1856,7 +1931,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
                rprintf(FINFO, "generating and sending sums for %d\n", ndx);
 
   notify_others:
-       if (remove_source_files && !delay_updates && !phase)
+       if (remove_source_files && !delay_updates && !phase && !dry_run)
                increment_active_files(ndx, itemizing, code);
        if (inc_recurse && !dry_run)
                cur_flist->in_progress++;
@@ -1925,6 +2000,28 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
        return;
 }
 
+#ifdef SUPPORT_HARD_LINKS
+static void handle_skipped_hlink(struct file_struct *file, int itemizing,
+                                enum logcode code, int f_out)
+{
+       char fbuf[MAXPATHLEN];
+       int new_last_ndx;
+       struct file_list *save_flist = cur_flist;
+
+       /* If we skip the last item in a chain of links and there was a
+        * prior non-skipped hard-link waiting to finish, finish it now. */
+       if ((new_last_ndx = skip_hard_link(file, &cur_flist)) < 0)
+               return;
+
+       file = cur_flist->files[new_last_ndx - cur_flist->ndx_start];
+       cur_flist->in_progress--; /* undo prior increment */
+       f_name(file, fbuf);
+       recv_generator(fbuf, file, new_last_ndx, itemizing, code, f_out);
+
+       cur_flist = save_flist;
+}
+#endif
+
 static void touch_up_dirs(struct file_list *flist, int ndx)
 {
        static int counter = 0;
@@ -2081,6 +2178,7 @@ void generate_files(int f_out, const char *local_name)
        lull_mod = allowed_lull * 5;
        symlink_timeset_failed_flags = ITEM_REPORT_TIME
            | (protocol_version >= 30 || !am_server ? ITEM_REPORT_TIMEFAIL : 0);
+       implied_dirs_are_missing = relative_paths && !implied_dirs && protocol_version < 30;
 
        if (verbose > 2)
                rprintf(FINFO, "generator starting pid=%ld\n", (long)getpid());