+struct file_list *send_file_list(int f, int argc, char *argv[])
+{
+ int len;
+ STRUCT_STAT st;
+ char *p, *dir;
+ char lastpath[MAXPATHLEN] = "";
+ struct file_list *flist;
+ struct timeval start_tv, end_tv;
+ int64 start_write;
+ int use_ff_fd = 0;
+ int flags, disable_buffering;
+
+ rprintf(FLOG, "building file list\n");
+ if (show_filelist_p())
+ start_filelist_progress("building file list");
+ else if (inc_recurse && verbose && !am_server)
+ rprintf(FCLIENT, "sending incremental file list\n");
+
+ start_write = stats.total_written;
+ gettimeofday(&start_tv, NULL);
+
+#ifdef SUPPORT_HARD_LINKS
+ if (preserve_hard_links && protocol_version >= 30 && !cur_flist)
+ init_hard_links();
+#endif
+
+ flist = cur_flist = flist_new(0, "send_file_list");
+ if (inc_recurse) {
+ dir_flist = flist_new(FLIST_TEMP, "send_file_list");
+ flags = FLAG_DIVERT_DIRS;
+ } else {
+ dir_flist = cur_flist;
+ flags = 0;
+ }
+
+ disable_buffering = io_start_buffering_out(f);
+ if (filesfrom_fd >= 0) {
+ if (argv[0] && !push_dir(argv[0], 0)) {
+ rsyserr(FERROR, errno, "push_dir %s failed in %s",
+ full_fname(argv[0]), curr_dir);
+ exit_cleanup(RERR_FILESELECT);
+ }
+ use_ff_fd = 1;
+ }
+
+ while (1) {
+ char fbuf[MAXPATHLEN];
+ char *fn;
+ int is_dot_dir;
+
+ if (use_ff_fd) {
+ if (read_filesfrom_line(filesfrom_fd, fbuf) == 0)
+ break;
+ sanitize_path(fbuf, fbuf, "", 0, NULL);
+ } else {
+ if (argc-- == 0)
+ break;
+ strlcpy(fbuf, *argv++, MAXPATHLEN);
+ if (sanitize_paths)
+ sanitize_path(fbuf, fbuf, "", 0, NULL);
+ }
+
+ len = strlen(fbuf);
+ if (relative_paths) {
+ /* We clean up fbuf below. */
+ is_dot_dir = 0;
+ } else if (!len || fbuf[len - 1] == '/') {
+ if (len == 2 && fbuf[0] == '.') {
+ /* Turn "./" into just "." rather than "./." */
+ fbuf[1] = '\0';
+ } else {
+ if (len + 1 >= MAXPATHLEN)
+ overflow_exit("send_file_list");
+ fbuf[len++] = '.';
+ fbuf[len] = '\0';
+ }
+ is_dot_dir = 1;
+ } else if (len > 1 && fbuf[len-1] == '.' && fbuf[len-2] == '.'
+ && (len == 2 || fbuf[len-3] == '/')) {
+ if (len + 2 >= MAXPATHLEN)
+ overflow_exit("send_file_list");
+ fbuf[len++] = '/';
+ fbuf[len++] = '.';
+ fbuf[len] = '\0';
+ is_dot_dir = 1;
+ } else {
+ is_dot_dir = fbuf[len-1] == '.'
+ && (len == 1 || fbuf[len-2] == '/');
+ }
+
+ dir = NULL;
+
+ if (!relative_paths) {
+ p = strrchr(fbuf, '/');
+ if (p) {
+ *p = '\0';
+ if (p == fbuf)
+ dir = "/";
+ else
+ dir = fbuf;
+ len -= p - fbuf + 1;
+ fn = p + 1;
+ } else
+ fn = fbuf;
+ } else {
+ if ((p = strstr(fbuf, "/./")) != NULL) {
+ *p = '\0';
+ if (p == fbuf)
+ dir = "/";
+ else
+ dir = fbuf;
+ len -= p - fbuf + 3;
+ fn = p + 3;
+ } else
+ fn = fbuf;
+ /* Get rid of trailing "/" and "/.". */
+ while (len) {
+ if (fn[len - 1] == '/') {
+ is_dot_dir = 1;
+ if (!--len && !dir) {
+ len++;
+ break;
+ }
+ }
+ else if (len >= 2 && fn[len - 1] == '.'
+ && fn[len - 2] == '/') {
+ is_dot_dir = 1;
+ if (!(len -= 2) && !dir) {
+ len++;
+ break;
+ }
+ } else
+ break;
+ }
+ if (len == 1 && fn[0] == '/')
+ fn[len++] = '.';
+ fn[len] = '\0';
+ /* Reject a ".." dir in the active part of the path. */
+ for (p = fn; (p = strstr(p, "..")) != NULL; p += 2) {
+ if ((p[2] == '/' || p[2] == '\0')
+ && (p == fn || p[-1] == '/')) {
+ rprintf(FERROR,
+ "found \"..\" dir in relative path: %s\n",
+ fbuf);
+ exit_cleanup(RERR_SYNTAX);
+ }
+ }
+ }
+
+ if (!*fn) {
+ len = 1;
+ fn = ".";
+ }
+
+ if (dir && *dir) {
+ static const char *lastdir;
+ static int lastdir_len = -1;
+ int len = strlen(dir);
+
+ if (len != lastdir_len || memcmp(lastdir, dir, len) != 0) {
+ if (!push_flist_dir(strdup(dir), len))
+ goto push_error;
+ lastdir = flist_dir;
+ lastdir_len = flist_dir_len;
+ } else if (!push_flist_dir(lastdir, lastdir_len)) {
+ push_error:
+ io_error |= IOERR_GENERAL;
+ rsyserr(FERROR, errno, "push_dir %s failed in %s",
+ full_fname(dir), curr_dir);
+ continue;
+ }
+ }
+
+ if (fn != fbuf)
+ memmove(fbuf, fn, len + 1);
+
+ if (link_stat(fbuf, &st, copy_dirlinks) != 0) {
+ io_error |= IOERR_GENERAL;
+ rsyserr(FERROR, errno, "link_stat %s failed",
+ full_fname(fbuf));
+ continue;
+ }
+
+ if (S_ISDIR(st.st_mode) && !xfer_dirs) {
+ rprintf(FINFO, "skipping directory %s\n", fbuf);
+ continue;
+ }
+
+ if (implied_dirs && (p=strrchr(fbuf,'/')) && p != fbuf) {
+ /* Send the implied directories at the start of the
+ * source spec, so we get their permissions right. */
+ char *lp = lastpath, *slash = fbuf;
+ *p = '\0';
+ /* Skip any initial directories in our path that we
+ * have in common with lastpath. */
+ for (fn = fbuf; *fn && *lp == *fn; lp++, fn++) {
+ if (*fn == '/')
+ slash = fn;
+ }
+ *p = '/';
+ if (fn != p || (*lp && *lp != '/')) {
+ int save_copy_links = copy_links;
+ int save_xfer_dirs = xfer_dirs;
+ int dir_flags = inc_recurse ? FLAG_DIVERT_DIRS : 0;
+ copy_links |= copy_unsafe_links;
+ xfer_dirs = 1;
+ while ((slash = strchr(slash+1, '/')) != 0) {
+ *slash = '\0';
+ send_file_name(f, flist, fbuf, NULL,
+ dir_flags, ALL_FILTERS);
+ *slash = '/';
+ }
+ copy_links = save_copy_links;
+ xfer_dirs = save_xfer_dirs;
+ *p = '\0';
+ strlcpy(lastpath, fbuf, sizeof lastpath);
+ *p = '/';
+ }
+ }
+
+ if (one_file_system)
+ filesystem_dev = st.st_dev;
+
+ if (recurse || (xfer_dirs && is_dot_dir)) {
+ struct file_struct *file;
+ int top_flags = FLAG_TOP_DIR | FLAG_XFER_DIR
+ | (is_dot_dir ? 0 : flags)
+ | (inc_recurse ? FLAG_DIVERT_DIRS : 0);
+ file = send_file_name(f, flist, fbuf, &st,
+ top_flags, ALL_FILTERS);
+ if (file && !inc_recurse)
+ send_if_directory(f, flist, file, fbuf, len, flags);
+ } else
+ send_file_name(f, flist, fbuf, &st, flags, ALL_FILTERS);
+ }
+
+ gettimeofday(&end_tv, NULL);
+ stats.flist_buildtime = (int64)(end_tv.tv_sec - start_tv.tv_sec) * 1000
+ + (end_tv.tv_usec - start_tv.tv_usec) / 1000;
+ if (stats.flist_buildtime == 0)
+ stats.flist_buildtime = 1;
+ start_tv = end_tv;
+
+ write_byte(f, 0); /* Indicate end of file list */
+
+#ifdef SUPPORT_HARD_LINKS
+ if (preserve_hard_links && protocol_version >= 30 && !inc_recurse)
+ idev_destroy();
+#endif
+
+ if (show_filelist_p())
+ finish_filelist_progress(flist);
+
+ gettimeofday(&end_tv, NULL);
+ stats.flist_xfertime = (int64)(end_tv.tv_sec - start_tv.tv_sec) * 1000
+ + (end_tv.tv_usec - start_tv.tv_usec) / 1000;
+
+ /* Sort the list without removing any duplicates in non-incremental
+ * mode. This allows the receiving side to ask for whatever name it
+ * kept. For incremental mode, the sender also removes duplicates
+ * in this initial file-list so that it avoids re-sending duplicated
+ * directories. */
+ clean_flist(flist, 0, inc_recurse);
+ file_total += flist->count;
+
+ if (!numeric_ids && !inc_recurse)
+ send_uid_list(f);
+
+ /* send the io_error flag */
+ if (protocol_version < 30)
+ write_int(f, ignore_errors ? 0 : io_error);
+ else if (io_error && !ignore_errors)
+ send_msg_int(MSG_IO_ERROR, io_error);
+
+ if (disable_buffering)
+ io_end_buffering_out();
+
+ stats.flist_size = stats.total_written - start_write;
+ stats.num_files = flist->count;
+
+ if (verbose > 3)
+ output_flist(flist);
+
+ if (verbose > 2)
+ rprintf(FINFO, "send_file_list done\n");
+
+ if (inc_recurse) {
+ add_dirs_to_tree(-1, 0, dir_flist->count - 1);
+ if (send_dir_ndx < 0) {
+ write_ndx(f, NDX_FLIST_EOF);
+ flist_eof = 1;
+ }
+ else if (file_total == 1) {
+ /* If we're creating incremental file-lists and there
+ * was just 1 item in the first file-list, send 1 more
+ * file-list to check if this is a 1-file xfer. */
+ send_extra_file_list(f, 1);
+ }
+ }
+
+ return flist;
+}