Unified the file-list pool used in incremental recursion mode so that
[rsync/rsync.git] / flist.c
diff --git a/flist.c b/flist.c
index 2b38ccb..e6a8085 100644 (file)
--- a/flist.c
+++ b/flist.c
@@ -38,6 +38,7 @@ extern int module_id;
 extern int ignore_errors;
 extern int numeric_ids;
 extern int recurse;
+extern int use_qsort;
 extern int xfer_dirs;
 extern int filesfrom_fd;
 extern int one_file_system;
@@ -76,6 +77,8 @@ extern int need_unsorted_flist;
 extern iconv_t ic_send, ic_recv;
 #endif
 
+#define PTR_SIZE (sizeof (struct file_struct *))
+
 int io_error;
 int checksum_len;
 dev_t filesystem_dev; /* used to implement -x */
@@ -97,6 +100,7 @@ static char tmp_sum[MAX_DIGEST_LEN];
 
 static char empty_sum[MAX_DIGEST_LEN];
 static int flist_count_offset; /* for --delete --progress */
+static int dir_count = 0;
 
 static void clean_flist(struct file_list *flist, int strip_root);
 static void output_flist(struct file_list *flist);
@@ -268,15 +272,12 @@ static const char *pathname, *orig_dir;
 static int pathname_len;
 
 
-/**
- * Make sure @p flist is big enough to hold at least @p flist->count
- * entries.
- **/
-void flist_expand(struct file_list *flist)
+/* Make sure flist can hold at least flist->count + extra entries. */
+static void flist_expand(struct file_list *flist, int extra)
 {
        struct file_struct **new_ptr;
 
-       if (flist->count < flist->malloced)
+       if (flist->count + extra <= flist->malloced)
                return;
 
        if (flist->malloced < FLIST_START)
@@ -288,15 +289,8 @@ void flist_expand(struct file_list *flist)
 
        /* In case count jumped or we are starting the list
         * with a known size just set it. */
-       if (flist->malloced < flist->count)
-               flist->malloced = flist->count;
-
-#ifdef ICONV_OPTION
-       if (inc_recurse && flist == dir_flist && need_unsorted_flist) {
-               flist->sorted = realloc_array(flist->sorted, struct file_struct *,
-                                             flist->malloced);
-       }
-#endif
+       if (flist->malloced < flist->count + extra)
+               flist->malloced = flist->count + extra;
 
        new_ptr = realloc_array(flist->files, struct file_struct *,
                                flist->malloced);
@@ -314,6 +308,15 @@ void flist_expand(struct file_list *flist)
                out_of_memory("flist_expand");
 }
 
+static void flist_done_allocating(struct file_list *flist)
+{
+       void *ptr = pool_boundary(flist->file_pool, 8*1024);
+       if (flist->pool_boundary == ptr)
+               flist->pool_boundary = NULL; /* list didn't use any pool memory */
+       else
+               flist->pool_boundary = ptr;
+}
+
 int push_pathname(const char *dir, int len)
 {
        if (dir == pathname)
@@ -643,7 +646,8 @@ static struct file_struct *recv_file_entry(struct file_list *flist,
 
 #ifdef ICONV_OPTION
        if (ic_recv != (iconv_t)-1) {
-               char *obuf = thisname, *ibuf = lastname;
+               char *obuf = thisname;
+               ICONV_CONST char *ibuf = (ICONV_CONST char *)lastname;
                size_t ocnt = MAXPATHLEN, icnt = basename_len;
 
                if (icnt >= MAXPATHLEN) {
@@ -996,6 +1000,7 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
        int alloc_len, basename_len, linkname_len;
        int extra_len = file_extra_cnt * EXTRA_LEN;
        const char *basename;
+       alloc_pool_t *pool;
        char *bp;
 
        if (strlcpy(thisname, fname, sizeof thisname)
@@ -1086,12 +1091,17 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
   skip_filters:
 
        /* Only divert a directory in the main transfer. */
-       if (flist && flist->prev && S_ISDIR(st.st_mode)
-        && flags & FLAG_DIVERT_DIRS) {
-               flist = dir_flist;
-               /* Room for parent/sibling/next-child info. */
-               extra_len += 3 * EXTRA_LEN;
-       }
+       if (flist) {
+               if (flist->prev && S_ISDIR(st.st_mode)
+                && flags & FLAG_DIVERT_DIRS) {
+                       /* Room for parent/sibling/next-child info. */
+                       extra_len += 3 * EXTRA_LEN;
+                       dir_count++;
+                       pool = dir_flist->file_pool;
+               } else
+                       pool = flist->file_pool;
+       } else
+               pool = NULL;
 
        if (verbose > 2) {
                rprintf(FINFO, "[%s] make_file(%s,*,%d)\n",
@@ -1126,8 +1136,8 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
 
        alloc_len = FILE_STRUCT_LEN + extra_len + basename_len
                  + linkname_len;
-       if (flist)
-               bp = pool_alloc(flist->file_pool, alloc_len, "make_file");
+       if (pool)
+               bp = pool_alloc(pool, alloc_len, "make_file");
        else {
                if (!(bp = new_array(char, alloc_len)))
                        out_of_memory("make_file");
@@ -1195,7 +1205,7 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
                int save_mode = file->mode;
                file->mode = S_IFDIR; /* Find a directory with our name. */
                if (flist_find(dir_flist, file) >= 0
-                   && x_stat(thisname, &st2, NULL) == 0 && S_ISDIR(st2.st_mode)) {
+                && x_stat(thisname, &st2, NULL) == 0 && S_ISDIR(st2.st_mode)) {
                        file->modtime = st2.st_mtime;
                        file->len32 = 0;
                        file->mode = st2.st_mode;
@@ -1210,16 +1220,10 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
        if (basename_len == 0+1)
                return NULL;
 
-       if (inc_recurse && flist == dir_flist) {
-               flist_expand(dir_flist);
 #ifdef ICONV_OPTION
-               if (ic_ndx)
-                       F_NDX(file) = dir_flist->count;
-               if (need_unsorted_flist)
-                       dir_flist->sorted[dir_flist->count] = file;
+       if (ic_ndx)
+               F_NDX(file) = dir_count - 1;
 #endif
-               dir_flist->files[dir_flist->count++] = file;
-       }
 
        return file;
 }
@@ -1269,7 +1273,7 @@ static struct file_struct *send_file_name(int f, struct file_list *flist,
 
        maybe_emit_filelist_progress(flist->count + flist_count_offset);
 
-       flist_expand(flist);
+       flist_expand(flist, 1);
        flist->files[flist->count++] = file;
        if (f >= 0) {
                send_file_entry(f, file, flist->count - 1);
@@ -1317,35 +1321,105 @@ static void send_if_directory(int f, struct file_list *flist,
        }
 }
 
-static int file_compare(struct file_struct **file1, struct file_struct **file2)
+static int file_compare(const void *file1, const void *file2)
 {
-       return f_name_cmp(*file1, *file2);
+       return f_name_cmp(*(struct file_struct **)file1,
+                         *(struct file_struct **)file2);
 }
 
-/* We take an entire set of sibling dirs from dir_flist (start <= ndx <= end),
- * sort them by name, and link them into the tree, setting the appropriate
- * parent/child/sibling pointers. */
-static void add_dirs_to_tree(int parent_ndx, int start, int end)
+/* The guts of a merge-sort algorithm.  This was derived from the glibc
+ * version, but I (Wayne) changed the merge code to do less copying and
+ * to require only half the amount of temporary memory. */
+static void fsort_tmp(struct file_struct **fp, size_t num,
+                     struct file_struct **tmp)
+{
+       struct file_struct **f1, **f2, **t;
+       size_t n1, n2;
+
+       n1 = num / 2;
+       n2 = num - n1;
+       f1 = fp;
+       f2 = fp + n1;
+
+       if (n1 > 1)
+               fsort_tmp(f1, n1, tmp);
+       if (n2 > 1)
+               fsort_tmp(f2, n2, tmp);
+
+       while (f_name_cmp(*f1, *f2) <= 0) {
+               if (!--n1)
+                       return;
+               f1++;
+       }
+
+       t = tmp;
+       memcpy(t, f1, n1 * PTR_SIZE);
+
+       *f1++ = *f2++, n2--;
+
+       while (n1 > 0 && n2 > 0) {
+               if (f_name_cmp(*t, *f2) <= 0)
+                       *f1++ = *t++, n1--;
+               else
+                       *f1++ = *f2++, n2--;
+       }
+
+       if (n1 > 0)
+               memcpy(f1, t, n1 * PTR_SIZE);
+}
+
+/* This file-struct sorting routine makes sure that any identical names in
+ * the file list stay in the same order as they were in the original list.
+ * This is particularly vital in inc_recurse mode where we expect a sort
+ * on the flist to match the exact order of a sort on the dir_flist. */
+static void fsort(struct file_struct **fp, size_t num)
+{
+       if (num <= 1)
+               return;
+
+       if (use_qsort)
+               qsort(fp, num, PTR_SIZE, file_compare);
+       else {
+               struct file_struct **tmp = new_array(struct file_struct *,
+                                                    (num+1) / 2);
+               fsort_tmp(fp, num, tmp);
+               free(tmp);
+       }
+}
+
+/* We take an entire set of sibling dirs from the sorted flist and link them
+ * into the tree, setting the appropriate parent/child/sibling pointers. */
+static void add_dirs_to_tree(int parent_ndx, struct file_list *from_flist,
+                            int dir_cnt)
 {
        int i;
        int32 *dp = NULL;
        int32 *parent_dp = parent_ndx < 0 ? NULL
                         : F_DIRNODE_P(dir_flist->sorted[parent_ndx]);
 
-       qsort(dir_flist->sorted + start, end - start + 1,
-             sizeof dir_flist->sorted[0], (int (*)())file_compare);
+       flist_expand(dir_flist, dir_cnt);
+       dir_flist->sorted = dir_flist->files;
+
+       for (i = 0; dir_cnt; i++) {
+               struct file_struct *file = from_flist->sorted[i];
+
+               if (!S_ISDIR(file->mode))
+                       continue;
+
+               dir_flist->files[dir_flist->count++] = file;
+               dir_cnt--;
 
-       for (i = start; i <= end; i++) {
-               struct file_struct *file = dir_flist->sorted[i];
                if (!(file->flags & FLAG_XFER_DIR)
                 || file->flags & FLAG_MOUNT_DIR)
                        continue;
+
                if (dp)
-                       DIR_NEXT_SIBLING(dp) = i;
+                       DIR_NEXT_SIBLING(dp) = dir_flist->count - 1;
                else if (parent_dp)
-                       DIR_FIRST_CHILD(parent_dp) = i;
+                       DIR_FIRST_CHILD(parent_dp) = dir_flist->count - 1;
                else
-                       send_dir_ndx = i;
+                       send_dir_ndx = dir_flist->count - 1;
+
                dp = F_DIRNODE_P(file);
                DIR_PARENT(dp) = parent_ndx;
                DIR_FIRST_CHILD(dp) = -1;
@@ -1454,79 +1528,50 @@ void send_extra_file_list(int f, int at_least)
                future_cnt = 0;
        while (future_cnt < at_least) {
                struct file_struct *file = dir_flist->sorted[send_dir_ndx];
-               int start = dir_flist->count;
+               int dir_ndx, dstart = dir_count;
                int32 *dp;
 
                flist = flist_new(0, "send_extra_file_list");
                start_write = stats.total_written;
 
-               /* If this is the first of a set of duplicate dirs, we must
-                * send all the dirs together in a single file-list.  We must
-                * also send the index of the last dir in the header. */
-               if (file->flags & FLAG_DUPLICATE) {
-                       int dir_ndx, end_ndx = send_dir_ndx;
-                       struct file_struct *fp = file;
-
-                       while (1) {
-                               dp = F_DIRNODE_P(fp);
-                               end_ndx = DIR_NEXT_SIBLING(dp);
-                               fp = dir_flist->sorted[end_ndx];
-                               if (!(fp->flags & FLAG_DUPLICATE))
-                                       break;
-                       }
-
 #ifdef ICONV_OPTION
-                       if (ic_ndx)
-                               dir_ndx = F_NDX(fp);
-                       else
+               if (ic_ndx)
+                       dir_ndx = F_NDX(file);
+               else
 #endif
-                               dir_ndx = end_ndx;
-                       write_ndx(f, NDX_FLIST_OFFSET - dir_ndx);
+                       dir_ndx = send_dir_ndx;
+               write_ndx(f, NDX_FLIST_OFFSET - dir_ndx);
 
-                       while (1) {
-                               send1extra(f, file, flist);
-                               if (send_dir_ndx == end_ndx)
-                                       break;
-                               dp = F_DIRNODE_P(file);
-                               send_dir_ndx = DIR_NEXT_SIBLING(dp);
-                               file = dir_flist->sorted[send_dir_ndx];
-                       }
-               } else {
-                       int dir_ndx;
-#ifdef ICONV_OPTION
-                       if (ic_ndx)
-                               dir_ndx = F_NDX(file);
-                       else
-#endif
-                               dir_ndx = send_dir_ndx;
-                       write_ndx(f, NDX_FLIST_OFFSET - dir_ndx);
+               send1extra(f, file, flist);
+               dp = F_DIRNODE_P(file);
 
+               /* If there are any duplicate directory names that follow, we
+                * send all the dirs together in one file-list.  The dir_flist
+                * tree links all the child subdirs onto the last dup dir. */
+               while ((dir_ndx = DIR_NEXT_SIBLING(dp)) >= 0
+                   && dir_flist->sorted[dir_ndx]->flags & FLAG_DUPLICATE) {
+                       send_dir_ndx = dir_ndx;
+                       file = dir_flist->sorted[dir_ndx];
                        send1extra(f, file, flist);
+                       dp = F_DIRNODE_P(file);
                }
-               write_byte(f, 0);
 
-#ifdef ICONV_OPTION
-               if (!need_unsorted_flist)
-#endif
-                       dir_flist->sorted = dir_flist->files;
-               add_dirs_to_tree(send_dir_ndx, start, dir_flist->count - 1);
+               write_byte(f, 0);
 
 #ifdef ICONV_OPTION
                if (need_unsorted_flist) {
-                       if (inc_recurse) {
-                               if (!(flist->sorted = new_array(struct file_struct *, flist->count)))
-                                       out_of_memory("send_extra_file_list");
-                               memcpy(flist->sorted, flist->files,
-                                      flist->count * sizeof (struct file_struct*));
-                               clean_flist(flist, 0);
-                       } else
-                               flist->sorted = flist->files;
+                       if (!(flist->sorted = new_array(struct file_struct *, flist->count)))
+                               out_of_memory("send_extra_file_list");
+                       memcpy(flist->sorted, flist->files,
+                              flist->count * sizeof (struct file_struct*));
                } else
 #endif
-               {
                        flist->sorted = flist->files;
-                       clean_flist(flist, 0);
-               }
+
+               clean_flist(flist, 0);
+
+               add_dirs_to_tree(send_dir_ndx, flist, dir_count - dstart);
+               flist_done_allocating(flist);
 
                file_total += flist->count;
                future_cnt += flist->count;
@@ -1535,7 +1580,6 @@ void send_extra_file_list(int f, int at_least)
                if (verbose > 3)
                        output_flist(flist);
 
-               dp = F_DIRNODE_P(file);
                if (DIR_FIRST_CHILD(dp) >= 0) {
                        send_dir_ndx = DIR_FIRST_CHILD(dp);
                        send_dir_depth++;
@@ -1860,11 +1904,8 @@ struct file_list *send_file_list(int f, int argc, char *argv[])
                rprintf(FINFO, "send_file_list done\n");
 
        if (inc_recurse) {
-#ifdef ICONV_OPTION
-               if (!need_unsorted_flist)
-#endif
-                       dir_flist->sorted = dir_flist->files;
-               add_dirs_to_tree(-1, 0, dir_flist->count - 1);
+               add_dirs_to_tree(-1, flist, dir_count);
+               flist_done_allocating(flist);
                if (send_dir_ndx < 0) {
                        write_ndx(f, NDX_FLIST_EOF);
                        flist_eof = 1;
@@ -1914,18 +1955,14 @@ struct file_list *recv_file_list(int f)
        while ((flags = read_byte(f)) != 0) {
                struct file_struct *file;
 
-               flist_expand(flist);
+               flist_expand(flist, 1);
 
                if (protocol_version >= 28 && (flags & XMIT_EXTENDED_FLAGS))
                        flags |= read_byte(f) << 8;
                file = recv_file_entry(flist, flags, f);
 
                if (inc_recurse && S_ISDIR(file->mode)) {
-                       flist_expand(dir_flist);
-#ifdef ICONV_OPTION
-                       if (need_unsorted_flist)
-                               dir_flist->sorted[dir_flist->count] = file;
-#endif
+                       flist_expand(dir_flist, 1);
                        dir_flist->files[dir_flist->count++] = file;
                }
 
@@ -1957,22 +1994,27 @@ struct file_list *recv_file_list(int f)
                        out_of_memory("recv_file_list");
                memcpy(flist->sorted, flist->files,
                       flist->count * sizeof (struct file_struct*));
-               if (inc_recurse) {
-                       qsort(dir_flist->sorted + dstart, dir_flist->count - dstart,
-                             sizeof (struct file_struct*), (int (*)())file_compare);
+               if (inc_recurse && dir_flist->count > dstart) {
+                       dir_flist->sorted = realloc_array(dir_flist->sorted,
+                                               struct file_struct *,
+                                               dir_flist->count);
+                       memcpy(dir_flist->sorted + dstart, dir_flist->files + dstart,
+                              (dir_flist->count - dstart) * sizeof (struct file_struct*));
+                       fsort(dir_flist->sorted + dstart, dir_flist->count - dstart);
                }
        } else
 #endif
        {
                flist->sorted = flist->files;
-               if (inc_recurse) {
+               if (inc_recurse && dir_flist->count > dstart) {
                        dir_flist->sorted = dir_flist->files;
-                       qsort(dir_flist->sorted + dstart, dir_flist->count - dstart,
-                             sizeof (struct file_struct*), (int (*)())file_compare);
+                       fsort(dir_flist->sorted + dstart, dir_flist->count - dstart);
                }
        }
 
-       if (!inc_recurse && f >= 0)
+       if (inc_recurse)
+               flist_done_allocating(flist);
+       else if (f >= 0)
                recv_id_list(f, flist);
 
        clean_flist(flist, relative_paths);
@@ -2017,11 +2059,15 @@ void recv_additional_file_list(int f)
                if (ndx < 0 || ndx >= dir_flist->count) {
                        ndx = NDX_FLIST_OFFSET - ndx;
                        rprintf(FERROR,
-                               "Invalid dir index: %d (%d - %d)\n",
-                               ndx, NDX_FLIST_OFFSET,
-                               NDX_FLIST_OFFSET - dir_flist->count);
+                               "[%s] Invalid dir index: %d (%d - %d)\n",
+                               who_am_i(), ndx, NDX_FLIST_OFFSET,
+                               NDX_FLIST_OFFSET - dir_flist->count + 1);
                        exit_cleanup(RERR_PROTOCOL);
                }
+               if (verbose > 3) {
+                       rprintf(FINFO, "[%s] receiving flist for dir %d\n",
+                               who_am_i(), ndx);
+               }
                flist = recv_file_list(f);
                flist->parent_ndx = ndx;
        }
@@ -2104,34 +2150,42 @@ struct file_list *flist_new(int flags, char *msg)
 
        memset(flist, 0, sizeof flist[0]);
 
-       if (!(flags & FLIST_TEMP)) {
-               if (first_flist) {
-                       flist->ndx_start = first_flist->prev->ndx_start
-                                        + first_flist->prev->count;
-               }
+       if (flags & FLIST_TEMP) {
+               if (!(flist->file_pool = pool_create(SMALL_EXTENT, 0,
+                                               out_of_memory, POOL_INTERN)))
+                       out_of_memory(msg);
+       } else {
                /* This is a doubly linked list with prev looping back to
                 * the end of the list, but the last next pointer is NULL. */
-               if (!first_flist)
+               if (!first_flist) {
+                       flist->file_pool = pool_create(NORMAL_EXTENT, 0,
+                                               out_of_memory, POOL_INTERN);
+                       if (!flist->file_pool)
+                               out_of_memory(msg);
+
                        first_flist = cur_flist = flist->prev = flist;
-               else {
+               } else {
+                       flist->file_pool = first_flist->file_pool;
+
+                       flist->ndx_start = first_flist->prev->ndx_start
+                                        + first_flist->prev->count;
+
                        flist->prev = first_flist->prev;
                        flist->prev->next = first_flist->prev = flist;
                }
+               flist->pool_boundary = pool_boundary(flist->file_pool, 0);
                flist_cnt++;
        }
 
-       if (!(flist->file_pool = pool_create(FILE_EXTENT, 0, out_of_memory, POOL_INTERN)))
-               out_of_memory(msg);
-
        return flist;
 }
 
 /* Free up all elements in a flist. */
 void flist_free(struct file_list *flist)
 {
-       if (!flist->prev)
-               /* Was FLIST_TEMP dir-list. */
-       else if (flist == flist->prev) {
+       if (!flist->prev) {
+               /* Was FLIST_TEMP dir-list. */
+       else if (flist == flist->prev) {
                first_flist = cur_flist = NULL;
                file_total = 0;
                flist_cnt = 0;
@@ -2150,7 +2204,11 @@ void flist_free(struct file_list *flist)
                flist_cnt--;
        }
 
-       pool_destroy(flist->file_pool);
+       if (!flist->prev || !flist_cnt)
+               pool_destroy(flist->file_pool);
+       else
+               pool_free_old(flist->file_pool, flist->pool_boundary);
+
        if (flist->sorted && flist->sorted != flist->files)
                free(flist->sorted);
        free(flist->files);
@@ -2171,8 +2229,7 @@ static void clean_flist(struct file_list *flist, int strip_root)
                return;
        }
 
-       qsort(flist->sorted, flist->count,
-           sizeof flist->sorted[0], (int (*)())file_compare);
+       fsort(flist->sorted, flist->count);
 
        if (!am_sender || inc_recurse) {
                for (i = prev_i = 0; i < flist->count; i++) {
@@ -2206,20 +2263,18 @@ static void clean_flist(struct file_list *flist, int strip_root)
                } else
                        j = -1;
                if (j >= 0) {
-                       struct file_struct *fp = flist->sorted[j];
                        int keep, drop;
                        /* If one is a dir and the other is not, we want to
                         * keep the dir because it might have contents in the
                         * list. */
-                       if (S_ISDIR(file->mode) != S_ISDIR(fp->mode)) {
-                               if (S_ISDIR(file->mode))
+                       if (S_ISDIR(file->mode)) {
+                               struct file_struct *fp = flist->sorted[j];
+                               if (!S_ISDIR(fp->mode))
                                        keep = i, drop = j;
                                else
                                        keep = j, drop = i;
-                       } else if (protocol_version < 27)
+                       } else
                                keep = j, drop = i;
-                       else
-                               keep = i, drop = j;
 
                        if (am_sender)
                                flist->sorted[drop]->flags |= FLAG_DUPLICATE;
@@ -2227,7 +2282,7 @@ static void clean_flist(struct file_list *flist, int strip_root)
                                if (verbose > 1) {
                                        rprintf(FINFO,
                                            "removing duplicate name %s from file list (%d)\n",
-                                           f_name(file, fbuf), drop);
+                                           f_name(file, fbuf), drop + flist->ndx_start);
                                }
                                /* Make sure we don't lose track of a user-specified
                                 * top directory. */
@@ -2369,9 +2424,12 @@ static void output_flist(struct file_list *flist)
                        trail = S_ISDIR(file->mode) ? "/" : "";
                } else
                        root = dir = slash = name = trail = "";
-               rprintf(FINFO, "[%s] i=%d %s %s%s%s%s mode=0%o len=%.0f%s%s flags=%x\n",
-                       who, i, root, dir, slash, name, trail, (int)file->mode,
-                       (double)F_LENGTH(file), uidbuf, gidbuf, file->flags);
+               rprintf(FINFO,
+                       "[%s] i=%d %s %s%s%s%s mode=0%o len=%.0f%s%s flags=%x\n",
+                       who, i + flist->ndx_start,
+                       root, dir, slash, name, trail,
+                       (int)file->mode, (double)F_LENGTH(file),
+                       uidbuf, gidbuf, file->flags);
        }
 }