Implement --tweak, --no-tweak, --no-tweak-hlinked options.
[rsync/rsync.git] / generator.c
index 125b313..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;
@@ -1210,9 +1211,22 @@ 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
@@ -1227,9 +1241,12 @@ static int dflt_perms;
 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;
@@ -1241,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
@@ -1258,56 +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:
+       if (skip_dir) {
+               if (is_below(file, skip_dir)) {
+                       if (is_dir)
+                               file->flags |= FLAG_MISSING_DIR;
 #ifdef SUPPORT_HARD_LINKS
-                       if (F_IS_HLINKED(file))
+                       else if (F_IS_HLINKED(file))
                                handle_skipped_hlink(file, itemizing, code, f_out);
 #endif
-                       rprintf(FERROR_XFER,
-                               "skipping daemon-excluded file \"%s\"\n",
-                               fname);
                        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 (is_dir)
-                               file->flags |= FLAG_MISSING_DIR;
+       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)
+                               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;
@@ -1317,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);
@@ -1352,12 +1360,7 @@ 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
@@ -1413,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;
@@ -1449,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;
                        }
@@ -1483,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;
        }
@@ -1505,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) {
@@ -1525,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
@@ -1535,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)
@@ -1569,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
@@ -1589,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) {
@@ -1606,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
@@ -1616,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) {
@@ -1653,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))
@@ -1738,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];
@@ -1773,13 +1799,31 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
        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);
@@ -2134,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());