+ * duplicate names can cause corruption because of the pipelining
+ */
+static void clean_flist(struct file_list *flist, int strip_root, int no_dups)
+{
+ char fbuf[MAXPATHLEN];
+ int i, prev_i = 0;
+
+ if (!flist)
+ return;
+ if (flist->count == 0) {
+ flist->high = -1;
+ return;
+ }
+
+ qsort(flist->files, flist->count,
+ sizeof flist->files[0], (int (*)())file_compare);
+
+ for (i = no_dups? 0 : flist->count; i < flist->count; i++) {
+ if (F_IS_ACTIVE(flist->files[i])) {
+ prev_i = i;
+ break;
+ }
+ }
+ flist->low = prev_i;
+ while (++i < flist->count) {
+ int j;
+ struct file_struct *file = flist->files[i];
+
+ if (!F_IS_ACTIVE(file))
+ continue;
+ if (f_name_cmp(file, flist->files[prev_i]) == 0)
+ j = prev_i;
+ else if (protocol_version >= 29 && S_ISDIR(file->mode)) {
+ int save_mode = file->mode;
+ /* Make sure that this directory doesn't duplicate a
+ * non-directory earlier in the list. */
+ flist->high = prev_i;
+ file->mode = S_IFREG;
+ j = flist_find(flist, file);
+ file->mode = save_mode;
+ } else
+ j = -1;
+ if (j >= 0) {
+ struct file_struct *fp = flist->files[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))
+ keep = i, drop = j;
+ else
+ keep = j, drop = i;
+ } else
+ keep = j, drop = i;
+ if (verbose > 1 && !am_server) {
+ rprintf(FINFO,
+ "removing duplicate name %s from file list (%d)\n",
+ f_name(file, fbuf), drop);
+ }
+ /* 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_XFER_DIR);
+
+ clear_file(flist->files[drop]);
+
+ if (keep == i) {
+ if (flist->low == drop) {
+ for (j = drop + 1;
+ j < i && !F_IS_ACTIVE(flist->files[j]);
+ j++) {}
+ flist->low = j;
+ }
+ prev_i = i;
+ }
+ } else
+ prev_i = i;
+ }
+ flist->high = no_dups ? prev_i : flist->count - 1;
+
+ if (strip_root) {
+ /* We need to strip off the leading slashes for relative
+ * paths, but this must be done _after_ the sorting phase. */
+ for (i = flist->low; i <= flist->high; i++) {
+ struct file_struct *file = flist->files[i];
+
+ if (!file->dirname)
+ continue;
+ 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 F_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) && F_DEPTH(file)) {
+ /* Dump empty dirs when coming back down. */
+ for (j = prev_depth; j >= F_DEPTH(file); j--) {
+ fp = flist->files[prev_i];
+ if (F_DEPTH(fp) >= 0)
+ break;
+ prev_i = -F_DEPTH(fp)-1;
+ clear_file(fp);
+ }
+ prev_depth = F_DEPTH(file);
+ 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 (F_DEPTH(fp) >= 0)
+ break;
+ prev_i = -F_DEPTH(fp)-1;
+ F_DEPTH(fp) = j;
+ }
+ } else
+ F_DEPTH(file) = -prev_i-1;
+ prev_i = i;
+ } else {
+ /* Keep dirs through this non-dir. */
+ for (j = prev_depth; ; j--) {
+ fp = flist->files[prev_i];
+ if (F_DEPTH(fp) >= 0)
+ break;
+ prev_i = -F_DEPTH(fp)-1;
+ F_DEPTH(fp) = j;
+ }
+ }
+ }
+ /* Dump empty all remaining empty dirs. */
+ while (1) {
+ struct file_struct *fp = flist->files[prev_i];
+ if (F_DEPTH(fp) >= 0)
+ break;
+ prev_i = -F_DEPTH(fp)-1;
+ clear_file(fp);
+ }
+
+ for (i = flist->low; i <= flist->high; i++) {
+ if (F_IS_ACTIVE(flist->files[i]))
+ break;
+ }
+ flist->low = i;
+ for (i = flist->high; i >= flist->low; i--) {
+ if (F_IS_ACTIVE(flist->files[i]))
+ break;
+ }
+ flist->high = i;
+ }
+}
+
+static void output_flist(struct file_list *flist)
+{
+ char uidbuf[16], gidbuf[16], depthbuf[16];
+ struct file_struct *file;
+ const char *root, *dir, *slash, *name, *trail;
+ const char *who = who_am_i();
+ int i;
+
+ for (i = 0; i < flist->count; i++) {
+ file = flist->files[i];
+ if ((am_root || am_sender) && preserve_uid) {
+ snprintf(uidbuf, sizeof uidbuf, " uid=%ld",
+ (long)F_UID(file));
+ } else
+ *uidbuf = '\0';
+ if (preserve_gid && F_GID(file) != GID_NONE) {
+ snprintf(gidbuf, sizeof gidbuf, " gid=%ld",
+ (long)F_GID(file));
+ } else
+ *gidbuf = '\0';
+ if (!am_sender)
+ snprintf(depthbuf, sizeof depthbuf, "%d", F_DEPTH(file));
+ if (F_IS_ACTIVE(file)) {
+ root = am_sender ? NS(F_ROOTDIR(file)) : depthbuf;
+ if ((dir = file->dirname) == NULL)
+ dir = slash = "";
+ else
+ slash = "/";
+ name = file->basename;
+ 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);
+ }
+}
+
+enum fnc_state { s_DIR, s_SLASH, s_BASE, s_TRAILING };
+enum fnc_type { t_PATH, t_ITEM };
+
+/* Compare the names of two file_struct entities, similar to how strcmp()
+ * would do if it were operating on the joined strings.
+ *
+ * Some differences beginning with protocol_version 29: (1) directory names
+ * are compared with an assumed trailing slash so that they compare in a
+ * way that would cause them to sort immediately prior to any content they
+ * may have; (2) a directory of any name compares after a non-directory of
+ * any name at the same depth; (3) a directory with name "." compares prior
+ * to anything else. These changes mean that a directory and a non-dir
+ * with the same name will not compare as equal (protocol_version >= 29).
+ *
+ * The dirname component can be an empty string, but the basename component
+ * cannot (and never is in the current codebase). The basename component
+ * may be NULL (for a removed item), in which case it is considered to be
+ * after any existing item. */
+int f_name_cmp(struct file_struct *f1, struct file_struct *f2)
+{
+ int dif;
+ const uchar *c1, *c2;
+ enum fnc_state state1, state2;
+ enum fnc_type type1, type2;
+ enum fnc_type t_path = protocol_version >= 29 ? t_PATH : t_ITEM;
+
+ if (!f1 || !F_IS_ACTIVE(f1)) {
+ if (!f2 || !F_IS_ACTIVE(f2))
+ return 0;
+ return -1;
+ }
+ if (!f2 || !F_IS_ACTIVE(f2))
+ return 1;
+
+ c1 = (uchar*)f1->dirname;
+ c2 = (uchar*)f2->dirname;
+ if (c1 == c2)
+ c1 = c2 = NULL;
+ if (!c1) {
+ type1 = S_ISDIR(f1->mode) ? t_path : t_ITEM;
+ c1 = (const uchar*)f1->basename;
+ if (type1 == t_PATH && *c1 == '.' && !c1[1]) {
+ type1 = t_ITEM;
+ state1 = s_TRAILING;
+ c1 = (uchar*)"";
+ } else
+ state1 = s_BASE;
+ } else {
+ type1 = t_path;
+ state1 = s_DIR;
+ }
+ if (!c2) {
+ type2 = S_ISDIR(f2->mode) ? t_path : t_ITEM;
+ c2 = (const uchar*)f2->basename;
+ if (type2 == t_PATH && *c2 == '.' && !c2[1]) {
+ type2 = t_ITEM;
+ state2 = s_TRAILING;
+ c2 = (uchar*)"";
+ } else
+ state2 = s_BASE;
+ } else {
+ type2 = t_path;
+ state2 = s_DIR;
+ }
+
+ if (type1 != type2)
+ return type1 == t_PATH ? 1 : -1;
+
+ do {
+ if (!*c1) {
+ switch (state1) {
+ case s_DIR:
+ state1 = s_SLASH;
+ c1 = (uchar*)"/";
+ break;
+ case s_SLASH:
+ type1 = S_ISDIR(f1->mode) ? t_path : t_ITEM;
+ c1 = (const uchar*)f1->basename;
+ if (type1 == t_PATH && *c1 == '.' && !c1[1]) {
+ type1 = t_ITEM;
+ state1 = s_TRAILING;
+ c1 = (uchar*)"";
+ } else
+ state1 = s_BASE;
+ break;
+ case s_BASE:
+ state1 = s_TRAILING;
+ if (type1 == t_PATH) {
+ c1 = (uchar*)"/";
+ break;
+ }
+ /* FALL THROUGH */
+ case s_TRAILING:
+ type1 = t_ITEM;
+ break;
+ }
+ if (*c2 && type1 != type2)
+ return type1 == t_PATH ? 1 : -1;
+ }
+ if (!*c2) {
+ switch (state2) {
+ case s_DIR:
+ state2 = s_SLASH;
+ c2 = (uchar*)"/";
+ break;
+ case s_SLASH:
+ type2 = S_ISDIR(f2->mode) ? t_path : t_ITEM;
+ c2 = (const uchar*)f2->basename;
+ if (type2 == t_PATH && *c2 == '.' && !c2[1]) {
+ type2 = t_ITEM;
+ state2 = s_TRAILING;
+ c2 = (uchar*)"";
+ } else
+ state2 = s_BASE;
+ break;
+ case s_BASE:
+ state2 = s_TRAILING;
+ if (type2 == t_PATH) {
+ c2 = (uchar*)"/";
+ break;
+ }
+ /* FALL THROUGH */
+ case s_TRAILING:
+ if (!*c1)
+ return 0;
+ type2 = t_ITEM;
+ break;
+ }
+ if (type1 != type2)
+ return type1 == t_PATH ? 1 : -1;
+ }
+ } while ((dif = (int)*c1++ - (int)*c2++) == 0);
+
+ return dif;
+}
+
+char *f_name_buf(void)
+{
+ static char names[5][MAXPATHLEN];
+ static unsigned int n;
+
+ n = (n + 1) % (sizeof names / sizeof names[0]);
+
+ return names[n];
+}
+
+/* Return a copy of the full filename of a flist entry, using the indicated
+ * buffer or one of 5 static buffers if fbuf is NULL. No size-checking is
+ * done because we checked the size when creating the file_struct entry.