Updated the FSF's address to an even newer one.
[rsync/rsync.git] / flist.c
diff --git a/flist.c b/flist.c
index 8f4e182..6f1033a 100644 (file)
--- a/flist.c
+++ b/flist.c
@@ -1,34 +1,29 @@
 /*
-   Copyright (C) Andrew Tridgell 1996
-   Copyright (C) Paul Mackerras 1996
-   Copyright (C) 2001, 2002 by Martin Pool <mbp@samba.org>
-
-   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
-   the Free Software Foundation; either version 2 of the License, or
-   (at your option) any later version.
-
-   This program is distributed in the hope that it will be useful,
-   but WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-   GNU General Public License for more details.
-
-   You should have received a copy of the GNU General Public License
-   along with this program; if not, write to the Free Software
-   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-*/
-
-/** @file flist.c
- * Generate and receive file lists
+ * Generate and receive file lists.
  *
- * @sa http://lists.samba.org/pipermail/rsync/2000-June/002351.html
+ * Copyright (C) 1996 Andrew Tridgell
+ * Copyright (C) 1996 Paul Mackerras
+ * Copyright (C) 2001, 2002 Martin Pool <mbp@samba.org>
+ * Copyright (C) 2002, 2003, 2004, 2005, 2006 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
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
 
 #include "rsync.h"
 
 extern int verbose;
-extern int dry_run;
 extern int list_only;
 extern int am_root;
 extern int am_server;
@@ -43,20 +38,21 @@ extern int recurse;
 extern int xfer_dirs;
 extern int filesfrom_fd;
 extern int one_file_system;
+extern int copy_dirlinks;
 extern int keep_dirlinks;
 extern int preserve_links;
 extern int preserve_hard_links;
-extern int preserve_perms;
 extern int preserve_devices;
+extern int preserve_specials;
 extern int preserve_uid;
 extern int preserve_gid;
 extern int relative_paths;
 extern int implied_dirs;
+extern int prune_empty_dirs;
 extern int copy_links;
 extern int copy_unsafe_links;
 extern int protocol_version;
 extern int sanitize_paths;
-extern int orig_umask;
 extern struct stats stats;
 extern struct file_list *the_file_list;
 
@@ -70,10 +66,10 @@ extern struct filter_list_struct server_filter_list;
 int io_error;
 int checksum_len;
 dev_t filesystem_dev; /* used to implement -x */
+unsigned int file_struct_len;
 
 static char empty_sum[MD4_SUM_LENGTH];
 static int flist_count_offset;
-static unsigned int file_struct_len;
 
 static void clean_flist(struct file_list *flist, int strip_root, int no_dups);
 static void output_flist(struct file_list *flist);
@@ -128,26 +124,26 @@ void show_flist_stats(void)
 
 static void list_file_entry(struct file_struct *f)
 {
-       char perms[11];
+       char permbuf[PERMSTRING_SIZE];
 
        if (!f->basename) {
                /* this can happen if duplicate names were removed */
                return;
        }
 
-       permstring(perms, f->mode);
+       permstring(permbuf, f->mode);
 
 #ifdef SUPPORT_LINKS
        if (preserve_links && S_ISLNK(f->mode)) {
                rprintf(FINFO, "%s %11.0f %s %s -> %s\n",
-                       perms,
+                       permbuf,
                        (double)f->length, timestring(f->modtime),
                        f_name(f, NULL), f->u.link);
        } else
 #endif
        {
                rprintf(FINFO, "%s %11.0f %s %s\n",
-                       perms,
+                       permbuf,
                        (double)f->length, timestring(f->modtime),
                        f_name(f, NULL));
        }
@@ -172,7 +168,7 @@ static int readlink_stat(const char *path, STRUCT_STAT *buffer, char *linkbuf)
 #ifdef SUPPORT_LINKS
        if (copy_links)
                return do_stat(path, buffer);
-       if (link_stat(path, buffer, 0) < 0)
+       if (link_stat(path, buffer, copy_dirlinks) < 0)
                return -1;
        if (S_ISLNK(buffer->st_mode)) {
                int l = readlink((char *)path, linkbuf, MAXPATHLEN - 1);
@@ -333,8 +329,6 @@ static void send_file_entry(struct file_struct *file, int f)
                return;
        }
 
-       io_write_phase = "send_file_entry";
-
        f_name(file, fname);
 
        flags = file->flags & XMIT_TOP_DIR;
@@ -343,16 +337,14 @@ static void send_file_entry(struct file_struct *file, int f)
                flags |= XMIT_SAME_MODE;
        else
                mode = file->mode;
-       if (preserve_devices) {
+       if ((preserve_devices && IS_DEVICE(mode))
+        || (preserve_specials && IS_SPECIAL(mode))) {
                if (protocol_version < 28) {
-                       if (IS_DEVICE(mode)) {
-                               if (file->u.rdev == rdev)
-                                       flags |= XMIT_SAME_RDEV_pre28;
-                               else
-                                       rdev = file->u.rdev;
-                       } else
-                               rdev = makedev(0, 0);
-               } else if (IS_DEVICE(mode)) {
+                       if (file->u.rdev == rdev)
+                               flags |= XMIT_SAME_RDEV_pre28;
+                       else
+                               rdev = file->u.rdev;
+               } else {
                        rdev = file->u.rdev;
                        if ((uint32)major(rdev) == rdev_major)
                                flags |= XMIT_SAME_RDEV_MAJOR;
@@ -361,7 +353,8 @@ static void send_file_entry(struct file_struct *file, int f)
                        if ((uint32)minor(rdev) <= 0xFFu)
                                flags |= XMIT_RDEV_MINOR_IS_SMALL;
                }
-       }
+       } else if (protocol_version < 28)
+               rdev = makedev(0, 0);
        if (file->uid == uid)
                flags |= XMIT_SAME_UID;
        else
@@ -437,7 +430,8 @@ static void send_file_entry(struct file_struct *file, int f)
                        add_gid(gid);
                write_int(f, gid);
        }
-       if (preserve_devices && IS_DEVICE(mode)) {
+       if ((preserve_devices && IS_DEVICE(mode))
+        || (preserve_specials && IS_SPECIAL(mode))) {
                if (protocol_version < 28) {
                        if (!(flags & XMIT_SAME_RDEV_pre28))
                                write_int(f, (int)rdev);
@@ -486,8 +480,6 @@ static void send_file_entry(struct file_struct *file, int f)
        }
 
        strlcpy(lastname, fname, MAXPATHLEN);
-
-       io_write_phase = "unknown";
 }
 
 static struct file_struct *receive_file_entry(struct file_list *flist,
@@ -569,7 +561,7 @@ static struct file_struct *receive_file_entry(struct file_list *flist,
        if (!(flags & XMIT_SAME_MODE))
                mode = from_wire_mode(read_int(f));
 
-       if (chmod_modes && (S_ISREG(mode) || S_ISDIR(mode)))
+       if (chmod_modes && !S_ISLNK(mode))
                mode = tweak_mode(mode, chmod_modes);
 
        if (preserve_uid && !(flags & XMIT_SAME_UID))
@@ -577,14 +569,12 @@ static struct file_struct *receive_file_entry(struct file_list *flist,
        if (preserve_gid && !(flags & XMIT_SAME_GID))
                gid = (gid_t)read_int(f);
 
-       if (preserve_devices) {
+       if ((preserve_devices && IS_DEVICE(mode))
+        || (preserve_specials && IS_SPECIAL(mode))) {
                if (protocol_version < 28) {
-                       if (IS_DEVICE(mode)) {
-                               if (!(flags & XMIT_SAME_RDEV_pre28))
-                                       rdev = (dev_t)read_int(f);
-                       } else
-                               rdev = makedev(0, 0);
-               } else if (IS_DEVICE(mode)) {
+                       if (!(flags & XMIT_SAME_RDEV_pre28))
+                               rdev = (dev_t)read_int(f);
+               } else {
                        uint32 rdev_minor;
                        if (!(flags & XMIT_SAME_RDEV_MAJOR))
                                rdev_major = read_int(f);
@@ -594,7 +584,8 @@ static struct file_struct *receive_file_entry(struct file_list *flist,
                                rdev_minor = read_int(f);
                        rdev = makedev(rdev_major, rdev_minor);
                }
-       }
+       } else if (protocol_version < 28)
+               rdev = makedev(0, 0);
 
 #ifdef SUPPORT_LINKS
        if (preserve_links && S_ISLNK(mode)) {
@@ -619,7 +610,6 @@ static struct file_struct *receive_file_entry(struct file_list *flist,
        memset(bp, 0, file_struct_len);
        bp += file_struct_len;
 
-       file->flags = 0;
        file->modtime = modtime;
        file->length = file_length;
        file->mode = mode;
@@ -647,13 +637,14 @@ static struct file_struct *receive_file_entry(struct file_list *flist,
                        in_del_hier = recurse;
                        del_hier_name_len = file->dir.depth == 0 ? 0 : l1 + l2;
                        if (relative_paths && del_hier_name_len > 2
-                           && basename_len == 1+1 && *basename == '.')
+                           && lastname[del_hier_name_len-1] == '.'
+                           && lastname[del_hier_name_len-2] == '/')
                                del_hier_name_len -= 2;
                        file->flags |= FLAG_TOP_DIR | FLAG_DEL_HERE;
                } else if (in_del_hier) {
                        if (!relative_paths || !del_hier_name_len
                         || (l1 >= del_hier_name_len
-                         && thisname[del_hier_name_len] == '/'))
+                         && lastname[del_hier_name_len] == '/'))
                                file->flags |= FLAG_DEL_HERE;
                        else
                                in_del_hier = 0;
@@ -664,7 +655,8 @@ static struct file_struct *receive_file_entry(struct file_list *flist,
        memcpy(bp, basename, basename_len);
        bp += basename_len;
 
-       if (preserve_devices && IS_DEVICE(mode))
+       if ((preserve_devices && IS_DEVICE(mode))
+        || (preserve_specials && IS_SPECIAL(mode)))
                file->u.rdev = rdev;
 
 #ifdef SUPPORT_LINKS
@@ -711,12 +703,6 @@ static struct file_struct *receive_file_entry(struct file_list *flist,
                read_buf(f, sum, checksum_len);
        }
 
-       if (!preserve_perms) {
-               /* set an appropriate set of permissions based on original
-                * permissions and umask. This emulates what GNU cp does */
-               file->mode &= ~orig_umask;
-       }
-
        return file;
 }
 
@@ -809,9 +795,14 @@ struct file_struct *make_file(char *fname, struct file_list *flist,
         * into a mount-point directory, not to avoid copying a symlinked
         * file if -L (or similar) was specified. */
        if (one_file_system && st.st_dev != filesystem_dev
-           && S_ISDIR(st.st_mode)) {
-               if (one_file_system > 1)
+        && S_ISDIR(st.st_mode)) {
+               if (one_file_system > 1) {
+                       if (verbose > 2) {
+                               rprintf(FINFO, "skipping mount-point dir %s\n",
+                                       thisname);
+                       }
                        return NULL;
+               }
                flags |= FLAG_MOUNT_POINT;
        }
 
@@ -911,7 +902,8 @@ struct file_struct *make_file(char *fname, struct file_list *flist,
        bp += basename_len;
 
 #ifdef HAVE_STRUCT_STAT_ST_RDEV
-       if (preserve_devices && IS_DEVICE(st.st_mode))
+       if ((preserve_devices && IS_DEVICE(st.st_mode))
+        || (preserve_specials && IS_SPECIAL(st.st_mode)))
                file->u.rdev = st.st_rdev;
 #endif
 
@@ -966,7 +958,7 @@ static struct file_struct *send_file_name(int f, struct file_list *flist,
        if (!file)
                return NULL;
 
-       if (chmod_modes && (S_ISREG(file->mode) || S_ISDIR(file->mode)))
+       if (chmod_modes && !S_ISLNK(file->mode))
                file->mode = tweak_mode(file->mode, chmod_modes);
 
        maybe_emit_filelist_progress(flist->count + flist_count_offset);
@@ -1139,7 +1131,7 @@ struct file_list *send_file_list(int f, int argc, char *argv[])
                                   && (len == 1 || fbuf[len-2] == '/');
                }
 
-               if (link_stat(fbuf, &st, keep_dirlinks) != 0) {
+               if (link_stat(fbuf, &st, copy_dirlinks) != 0) {
                        io_error |= IOERR_GENERAL;
                        rsyserr(FERROR, errno, "link_stat %s failed",
                                full_fname(fbuf));
@@ -1254,7 +1246,7 @@ struct file_list *send_file_list(int f, int argc, char *argv[])
                        if (fn != p || (*lp && *lp != '/')) {
                                int save_copy_links = copy_links;
                                int save_xfer_dirs = xfer_dirs;
-                               copy_links = copy_unsafe_links;
+                               copy_links |= copy_unsafe_links;
                                xfer_dirs = 1;
                                while ((slash = strchr(slash+1, '/')) != 0) {
                                        *slash = '\0';
@@ -1318,8 +1310,6 @@ struct file_list *send_file_list(int f, int argc, char *argv[])
         * without causing a compatibility problem with older versions. */
        clean_flist(flist, 0, 0);
 
-       /* Now send the uid/gid list. This was introduced in
-        * protocol version 15 */
        send_uid_list(f);
 
        /* send the io_error flag */
@@ -1366,7 +1356,7 @@ struct file_list *recv_file_list(int f)
                        flags |= read_byte(f) << 8;
                file = receive_file_entry(flist, flags, f);
 
-               if (S_ISREG(file->mode))
+               if (S_ISREG(file->mode) || S_ISLNK(file->mode))
                        stats.total_size += file->length;
 
                flist->files[flist->count++] = file;
@@ -1389,8 +1379,6 @@ struct file_list *recv_file_list(int f)
        clean_flist(flist, relative_paths, 1);
 
        if (f >= 0) {
-               /* Now send the uid/gid list. This was introduced in
-                * protocol version 15 */
                recv_uid_list(f, flist);
 
                /* Recv the io_error flag */
@@ -1432,26 +1420,48 @@ static int file_compare(struct file_struct **file1, struct file_struct **file2)
 int flist_find(struct file_list *flist, struct file_struct *f)
 {
        int low = flist->low, high = flist->high;
-       int ret, mid, mid_up;
+       int diff, mid, mid_up;
 
        while (low <= high) {
                mid = (low + high) / 2;
-               for (mid_up = mid; !flist->files[mid_up]->basename; mid_up++) {}
-               if (mid_up <= high)
-                       ret = f_name_cmp(flist->files[mid_up], f);
-               else
-                       ret = 1;
-               if (ret == 0) {
+               if (flist->files[mid]->basename)
+                       mid_up = mid;
+               else {
+                       /* Scan for the next non-empty entry using the cached
+                        * distance values.  If the value isn't fully up-to-
+                        * date, update it. */
+                       mid_up = mid + flist->files[mid]->dir.depth;
+                       if (!flist->files[mid_up]->basename) {
+                               do {
+                                   mid_up += flist->files[mid_up]->dir.depth;
+                               } while (!flist->files[mid_up]->basename);
+                               flist->files[mid]->dir.depth = mid_up - mid;
+                       }
+                       if (mid_up > high) {
+                               /* If there's nothing left above us, set high to
+                                * a non-empty entry below us and continue. */
+                               high = mid - flist->files[mid]->length;
+                               if (!flist->files[high]->basename) {
+                                       do {
+                                           high -= flist->files[high]->length;
+                                       } while (!flist->files[high]->basename);
+                                       flist->files[mid]->length = mid - high;
+                               }
+                               continue;
+                       }
+               }
+               diff = f_name_cmp(flist->files[mid_up], f);
+               if (diff == 0) {
                        if (protocol_version < 29
                            && S_ISDIR(flist->files[mid_up]->mode)
                            != S_ISDIR(f->mode))
                                return -1;
                        return mid_up;
                }
-               if (ret > 0)
-                       high = mid - 1;
-               else
+               if (diff < 0)
                        low = mid_up + 1;
+               else
+                       high = mid - 1;
        }
        return -1;
 }
@@ -1460,11 +1470,16 @@ int flist_find(struct file_list *flist, struct file_struct *f)
  * Free up any resources a file_struct has allocated
  * and clear the file.
  */
-void clear_file(int i, struct file_list *flist)
+void clear_file(struct file_struct *file, struct file_list *flist)
 {
-       if (flist->hlink_pool && flist->files[i]->link_u.idev)
-               pool_free(flist->hlink_pool, 0, flist->files[i]->link_u.idev);
-       memset(flist->files[i], 0, file_struct_len);
+       if (flist->hlink_pool && file->link_u.idev)
+               pool_free(flist->hlink_pool, 0, file->link_u.idev);
+       memset(file, 0, file_struct_len);
+       /* In an empty entry, dir.depth is an offset to the next non-empty
+        * entry.  Likewise for length in the opposite direction.  We assume
+        * that we're alone for now since flist_find() will adjust the counts
+        * it runs into that aren't up-to-date. */
+       file->length = file->dir.depth = 1;
 }
 
 /*
@@ -1512,6 +1527,7 @@ void flist_free(struct file_list *flist)
  */
 static void clean_flist(struct file_list *flist, int strip_root, int no_dups)
 {
+       char fbuf[MAXPATHLEN];
        int i, prev_i = 0;
 
        if (!flist)
@@ -1565,14 +1581,14 @@ static void clean_flist(struct file_list *flist, int strip_root, int no_dups)
                        if (verbose > 1 && !am_server) {
                                rprintf(FINFO,
                                        "removing duplicate name %s from file list (%d)\n",
-                                       f_name(file, NULL), drop);
+                                       f_name(file, fbuf), drop);
                        }
-                       /* Make sure that if we unduplicate '.', that we don't
-                        * lose track of a user-specified top directory. */
+                       /* Make sure we don't lose track of a user-specified
+                        * top directory. */
                        flist->files[keep]->flags |= flist->files[drop]->flags
                                                   & (FLAG_TOP_DIR|FLAG_DEL_HERE);
 
-                       clear_file(drop, flist);
+                       clear_file(flist->files[drop], flist);
 
                        if (keep == i) {
                                if (flist->low == drop) {
@@ -1596,16 +1612,78 @@ static void clean_flist(struct file_list *flist, int strip_root, int no_dups)
 
                        if (!file->dirname)
                                continue;
-                       if (*file->dirname == '/') {
-                               char *s = file->dirname + 1;
-                               while (*s == '/') s++;
-                               memmove(file->dirname, s, strlen(s) + 1);
-                       }
-
+                       while (*file->dirname == '/')
+                               file->dirname++;
                        if (!*file->dirname)
                                file->dirname = NULL;
                }
        }
+
+       if (prune_empty_dirs && no_dups) {
+               int j, prev_depth = 0;
+
+               prev_i = 0; /* It's OK that this isn't really true. */
+
+               for (i = flist->low; i <= flist->high; i++) {
+                       struct file_struct *fp, *file = flist->files[i];
+
+                       /* This temporarily abuses the dir.depth value for a
+                        * directory that is in a chain that might get pruned.
+                        * We restore the old value if it gets a reprieve. */
+                       if (S_ISDIR(file->mode) && file->dir.depth) {
+                               /* Dump empty dirs when coming back down. */
+                               for (j = prev_depth; j >= file->dir.depth; j--) {
+                                       fp = flist->files[prev_i];
+                                       if (fp->dir.depth >= 0)
+                                               break;
+                                       prev_i = -fp->dir.depth-1;
+                                       clear_file(fp, flist);
+                               }
+                               prev_depth = file->dir.depth;
+                               if (is_excluded(f_name(file, fbuf), 1,
+                                                      ALL_FILTERS)) {
+                                       /* Keep dirs through this dir. */
+                                       for (j = prev_depth-1; ; j--) {
+                                               fp = flist->files[prev_i];
+                                               if (fp->dir.depth >= 0)
+                                                       break;
+                                               prev_i = -fp->dir.depth-1;
+                                               fp->dir.depth = j;
+                                       }
+                               } else
+                                       file->dir.depth = -prev_i-1;
+                               prev_i = i;
+                       } else {
+                               /* Keep dirs through this non-dir. */
+                               for (j = prev_depth; ; j--) {
+                                       fp = flist->files[prev_i];
+                                       if (fp->dir.depth >= 0)
+                                               break;
+                                       prev_i = -fp->dir.depth-1;
+                                       fp->dir.depth = j;
+                               }
+                       }
+               }
+               /* Dump empty all remaining empty dirs. */
+               while (1) {
+                       struct file_struct *fp = flist->files[prev_i];
+                       if (fp->dir.depth >= 0)
+                               break;
+                       prev_i = -fp->dir.depth-1;
+                       clear_file(fp, flist);
+               }
+
+               for (i = flist->low; i <= flist->high; i++) {
+                       if (flist->files[i]->basename)
+                               break;
+               }
+               flist->low = i;
+               for (i = flist->high; i >= flist->low; i--) {
+                       if (flist->files[i]->basename)
+                               break;
+               }
+               flist->high = i;
+       }
 }
 
 static void output_flist(struct file_list *flist)
@@ -1822,6 +1900,7 @@ struct file_list *get_dirlist(char *dirname, int dlen,
        struct file_list *dirlist;
        char dirbuf[MAXPATHLEN];
        int save_recurse = recurse;
+       int save_xfer_dirs = xfer_dirs;
 
        if (dlen < 0) {
                dlen = strlcpy(dirbuf, dirname, MAXPATHLEN);
@@ -1833,7 +1912,9 @@ struct file_list *get_dirlist(char *dirname, int dlen,
        dirlist = flist_new(WITHOUT_HLINK, "get_dirlist");
 
        recurse = 0;
+       xfer_dirs = 1;
        send_directory(ignore_filter_rules ? -2 : -1, dirlist, dirname, dlen);
+       xfer_dirs = save_xfer_dirs;
        recurse = save_recurse;
        if (do_progress)
                flist_count_offset += dirlist->count;