Mention updated config files.
[rsync.git] / main.c
diff --git a/main.c b/main.c
index 3132aa9a762e7fa82248f5a20848a6cb2c66a0cd..0c60b86d134e3e466e222a66c749418182e3ca8b 100644 (file)
--- a/main.c
+++ b/main.c
@@ -4,7 +4,7 @@
  * Copyright (C) 1996-2001 Andrew Tridgell <tridge@samba.org>
  * Copyright (C) 1996 Paul Mackerras
  * Copyright (C) 2001, 2002 Martin Pool <mbp@samba.org>
- * Copyright (C) 2003-2015 Wayne Davison
+ * Copyright (C) 2003-2022 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
 
 #include "rsync.h"
 #include "inums.h"
+#include "ifuncs.h"
 #include "io.h"
 #if defined CONFIG_LOCALE && defined HAVE_LOCALE_H
 #include <locale.h>
 #endif
+#include <popt.h>
+#ifdef __TANDEM
+#include <floss.h(floss_execlp)>
+#endif
 
 extern int dry_run;
 extern int list_only;
@@ -39,9 +44,11 @@ extern int blocking_io;
 extern int always_checksum;
 extern int remove_source_files;
 extern int output_needs_newline;
+extern int called_from_signal_handler;
 extern int need_messages_from_generator;
 extern int kluge_around_eof;
 extern int got_xfer_error;
+extern int old_style_args;
 extern int msgs2stderr;
 extern int module_id;
 extern int read_only;
@@ -51,6 +58,7 @@ extern int copy_unsafe_links;
 extern int keep_dirlinks;
 extern int preserve_hard_links;
 extern int protocol_version;
+extern int mkpath_dest_arg;
 extern int file_total;
 extern int recurse;
 extern int xfer_dirs;
@@ -75,35 +83,42 @@ extern pid_t cleanup_child_pid;
 extern size_t bwlimit_writemax;
 extern unsigned int module_dirlen;
 extern BOOL flist_receiving_enabled;
+extern BOOL want_progress_now;
 extern BOOL shutting_down;
 extern int backup_dir_len;
 extern int basis_dir_cnt;
+extern int default_af_hint;
+extern int stdout_format_has_i;
+extern int trust_sender_filter;
+extern int trust_sender_args;
 extern struct stats stats;
 extern char *stdout_format;
 extern char *logfile_format;
 extern char *filesfrom_host;
 extern char *partial_dir;
-extern char *dest_option;
 extern char *rsync_path;
 extern char *shell_cmd;
-extern char *batch_name;
 extern char *password_file;
 extern char *backup_dir;
+extern char *copy_as;
+extern char *tmpdir;
 extern char curr_dir[MAXPATHLEN];
 extern char backup_dir_buf[MAXPATHLEN];
 extern char *basis_dir[MAX_BASIS_DIRS+1];
 extern struct file_list *first_flist;
-extern filter_rule_list daemon_filter_list;
+extern filter_rule_list daemon_filter_list, implied_filter_list;
 
 uid_t our_uid;
 gid_t our_gid;
 int am_receiver = 0;  /* Only set to 1 after the receiver/generator fork. */
 int am_generator = 0; /* Only set to 1 after the receiver/generator fork. */
 int local_server = 0;
-int daemon_over_rsh = 0;
+int daemon_connection = 0; /* 0 = no daemon, 1 = daemon via remote shell, -1 = daemon via socket */
 mode_t orig_umask = 0;
 int batch_gen_fd = -1;
 int sender_keeps_checksum = 0;
+int raw_argc, cooked_argc;
+char **raw_argv, **cooked_argv;
 
 /* There's probably never more than at most 2 outstanding child processes,
  * but set it higher, just in case. */
@@ -154,6 +169,27 @@ pid_t wait_process(pid_t pid, int *status_ptr, int flags)
        return waited_pid;
 }
 
+int shell_exec(const char *cmd)
+{
+       char *shell = getenv("RSYNC_SHELL");
+       int status;
+       pid_t pid;
+
+       if (!shell)
+               return system(cmd);
+
+       if ((pid = fork()) < 0)
+               return -1;
+
+       if (pid == 0) {
+               execlp(shell, shell, "-c", cmd, NULL);
+               _exit(1);
+       }
+
+       int ret = wait_process(pid, &status, 0);
+       return ret < 0 ? -1 : status;
+}
+
 /* Wait for a process to exit, calling io_flush while waiting. */
 static void wait_process_with_flush(pid_t pid, int *exit_code_ptr)
 {
@@ -210,6 +246,74 @@ void read_del_stats(int f)
        stats.deleted_files += stats.deleted_specials = read_varint(f);
 }
 
+static void become_copy_as_user()
+{
+       char *gname;
+       uid_t uid;
+       gid_t gid;
+
+       if (!copy_as)
+               return;
+
+       if (DEBUG_GTE(CMD, 2))
+               rprintf(FINFO, "[%s] copy_as=%s\n", who_am_i(), copy_as);
+
+       if ((gname = strchr(copy_as, ':')) != NULL)
+               *gname++ = '\0';
+
+       if (!user_to_uid(copy_as, &uid, True)) {
+               rprintf(FERROR, "Invalid copy-as user: %s\n", copy_as);
+               exit_cleanup(RERR_SYNTAX);
+       }
+
+       if (gname) {
+               if (!group_to_gid(gname, &gid, True)) {
+                       rprintf(FERROR, "Invalid copy-as group: %s\n", gname);
+                       exit_cleanup(RERR_SYNTAX);
+               }
+       } else {
+               struct passwd *pw;
+               if ((pw = getpwuid(uid)) == NULL) {
+                       rsyserr(FERROR, errno, "getpwuid failed");
+                       exit_cleanup(RERR_SYNTAX);
+               }
+               gid = pw->pw_gid;
+       }
+
+       if (setgid(gid) < 0) {
+               rsyserr(FERROR, errno, "setgid failed");
+               exit_cleanup(RERR_SYNTAX);
+       }
+#ifdef HAVE_SETGROUPS
+       if (setgroups(1, &gid)) {
+               rsyserr(FERROR, errno, "setgroups failed");
+               exit_cleanup(RERR_SYNTAX);
+       }
+#endif
+#ifdef HAVE_INITGROUPS
+       if (!gname && initgroups(copy_as, gid) < 0) {
+               rsyserr(FERROR, errno, "initgroups failed");
+               exit_cleanup(RERR_SYNTAX);
+       }
+#endif
+
+       if (setuid(uid) < 0
+#ifdef HAVE_SETEUID
+        || seteuid(uid) < 0
+#endif
+       ) {
+               rsyserr(FERROR, errno, "setuid failed");
+               exit_cleanup(RERR_SYNTAX);
+       }
+
+       our_uid = MY_UID();
+       our_gid = MY_GID();
+       am_root = (our_uid == ROOT_UID);
+
+       if (gname)
+               gname[-1] = ':';
+}
+
 /* This function gets called from all 3 processes.  We want the client side
  * to actually output the text, but the sender is the only process that has
  * all the stats we need.  So, if we're a client sender, we do the report.
@@ -366,38 +470,33 @@ static void output_summary(void)
  **/
 static void show_malloc_stats(void)
 {
-#ifdef HAVE_MALLINFO
-       struct mallinfo mi;
-
-       mi = mallinfo();
+#ifdef MEM_ALLOC_INFO
+       struct MEM_ALLOC_INFO mi = MEM_ALLOC_INFO(); /* mallinfo or mallinfo2 */
 
        rprintf(FCLIENT, "\n");
        rprintf(FINFO, RSYNC_NAME "[%d] (%s%s%s) heap statistics:\n",
                (int)getpid(), am_server ? "server " : "",
                am_daemon ? "daemon " : "", who_am_i());
-       rprintf(FINFO, "  arena:     %10ld   (bytes from sbrk)\n",
-               (long)mi.arena);
-       rprintf(FINFO, "  ordblks:   %10ld   (chunks not in use)\n",
-               (long)mi.ordblks);
-       rprintf(FINFO, "  smblks:    %10ld\n",
-               (long)mi.smblks);
-       rprintf(FINFO, "  hblks:     %10ld   (chunks from mmap)\n",
-               (long)mi.hblks);
-       rprintf(FINFO, "  hblkhd:    %10ld   (bytes from mmap)\n",
-               (long)mi.hblkhd);
-       rprintf(FINFO, "  allmem:    %10ld   (bytes from sbrk + mmap)\n",
-               (long)mi.arena + mi.hblkhd);
-       rprintf(FINFO, "  usmblks:   %10ld\n",
-               (long)mi.usmblks);
-       rprintf(FINFO, "  fsmblks:   %10ld\n",
-               (long)mi.fsmblks);
-       rprintf(FINFO, "  uordblks:  %10ld   (bytes used)\n",
-               (long)mi.uordblks);
-       rprintf(FINFO, "  fordblks:  %10ld   (bytes free)\n",
-               (long)mi.fordblks);
-       rprintf(FINFO, "  keepcost:  %10ld   (bytes in releasable chunk)\n",
-               (long)mi.keepcost);
-#endif /* HAVE_MALLINFO */
+
+#define PRINT_ALLOC_NUM(title, descr, num) \
+       rprintf(FINFO, "  %-11s%10" SIZE_T_FMT_MOD "d   (" descr ")\n", \
+               title ":", (SIZE_T_FMT_CAST)(num));
+
+       PRINT_ALLOC_NUM("arena", "bytes from sbrk", mi.arena);
+       PRINT_ALLOC_NUM("ordblks", "chunks not in use", mi.ordblks);
+       PRINT_ALLOC_NUM("smblks", "free fastbin blocks", mi.smblks);
+       PRINT_ALLOC_NUM("hblks", "chunks from mmap", mi.hblks);
+       PRINT_ALLOC_NUM("hblkhd", "bytes from mmap", mi.hblkhd);
+       PRINT_ALLOC_NUM("allmem", "bytes from sbrk + mmap", mi.arena + mi.hblkhd);
+       PRINT_ALLOC_NUM("usmblks", "always 0", mi.usmblks);
+       PRINT_ALLOC_NUM("fsmblks", "bytes in freed fastbin blocks", mi.fsmblks);
+       PRINT_ALLOC_NUM("uordblks", "bytes used", mi.uordblks);
+       PRINT_ALLOC_NUM("fordblks", "bytes free", mi.fordblks);
+       PRINT_ALLOC_NUM("keepcost", "bytes in releasable chunk", mi.keepcost);
+
+#undef PRINT_ALLOC_NUM
+
+#endif /* MEM_ALLOC_INFO */
 }
 
 
@@ -418,8 +517,6 @@ static pid_t do_cmd(char *cmd, char *machine, char *user, char **remote_argv, in
                if (!cmd)
                        cmd = RSYNC_RSH;
                cmd = need_to_free = strdup(cmd);
-               if (!cmd)
-                       goto oom;
 
                for (t = f = cmd; *f; f++) {
                        if (*f == ' ')
@@ -432,8 +529,8 @@ static pid_t do_cmd(char *cmd, char *machine, char *user, char **remote_argv, in
                                if (!*f) {
                                        if (in_quote) {
                                                rprintf(FERROR,
-                                                   "Missing trailing-%c in remote-shell command.\n",
-                                                   in_quote);
+                                                       "Missing trailing-%c in remote-shell command.\n",
+                                                       in_quote);
                                                exit_cleanup(RERR_SYNTAX);
                                        }
                                        f--;
@@ -454,8 +551,13 @@ static pid_t do_cmd(char *cmd, char *machine, char *user, char **remote_argv, in
                        *t++ = '\0';
                }
 
-               /* check to see if we've already been given '-l user' in
-                * the remote-shell command */
+               /* NOTE: must preserve t == start of command name until the end of the args handling! */
+               if ((t = strrchr(cmd, '/')) != NULL)
+                       t++;
+               else
+                       t = cmd;
+
+               /* Check to see if we've already been given '-l user' in the remote-shell command. */
                for (i = 0; i < argc-1; i++) {
                        if (!strcmp(args[i], "-l") && args[i+1][0] != '-')
                                dash_l_set = 1;
@@ -464,31 +566,36 @@ static pid_t do_cmd(char *cmd, char *machine, char *user, char **remote_argv, in
 #ifdef HAVE_REMSH
                /* remsh (on HPUX) takes the arguments the other way around */
                args[argc++] = machine;
-               if (user && !(daemon_over_rsh && dash_l_set)) {
+               if (user && !(daemon_connection && dash_l_set)) {
                        args[argc++] = "-l";
                        args[argc++] = user;
                }
 #else
-               if (user && !(daemon_over_rsh && dash_l_set)) {
+               if (user && !(daemon_connection && dash_l_set)) {
                        args[argc++] = "-l";
                        args[argc++] = user;
                }
+#ifdef AF_INET
+               if (default_af_hint == AF_INET && strcmp(t, "ssh") == 0)
+                       args[argc++] = "-4"; /* we're using ssh so we can add a -4 option */
+#endif
+#ifdef AF_INET6
+               if (default_af_hint == AF_INET6 && strcmp(t, "ssh") == 0)
+                       args[argc++] = "-6"; /* we're using ssh so we can add a -6 option */
+#endif
                args[argc++] = machine;
 #endif
 
                args[argc++] = rsync_path;
 
-               if (blocking_io < 0) {
-                       char *cp;
-                       if ((cp = strrchr(cmd, '/')) != NULL)
-                               cp++;
-                       else
-                               cp = cmd;
-                       if (strcmp(cp, "rsh") == 0 || strcmp(cp, "remsh") == 0)
-                               blocking_io = 1;
-               }
+               if (blocking_io < 0 && (strcmp(t, "rsh") == 0 || strcmp(t, "remsh") == 0))
+                       blocking_io = 1;
 
-               server_options(args,&argc);
+               if (daemon_connection > 0) {
+                       args[argc++] = "--server";
+                       args[argc++] = "--daemon";
+               } else
+                       server_options(args, &argc);
 
                if (argc >= MAX_ARGS - 2)
                        goto arg_overflow;
@@ -496,18 +603,14 @@ static pid_t do_cmd(char *cmd, char *machine, char *user, char **remote_argv, in
 
        args[argc++] = ".";
 
-       if (!daemon_over_rsh) {
+       if (!daemon_connection) {
                while (remote_argc > 0) {
                        if (argc >= MAX_ARGS - 1) {
                          arg_overflow:
                                rprintf(FERROR, "internal: args[] overflowed in do_cmd()\n");
                                exit_cleanup(RERR_SYNTAX);
                        }
-                       if (**remote_argv == '-') {
-                               if (asprintf(args + argc++, "./%s", *remote_argv++) < 0)
-                                       out_of_memory("do_cmd");
-                       } else
-                               args[argc++] = *remote_argv++;
+                       args[argc++] = safe_arg(NULL, *remote_argv++);
                        remote_argc--;
                }
        }
@@ -549,7 +652,7 @@ static pid_t do_cmd(char *cmd, char *machine, char *user, char **remote_argv, in
 #ifdef ICONV_CONST
                setup_iconv();
 #endif
-               if (protect_args && !daemon_over_rsh)
+               if (protect_args && !daemon_connection)
                        send_protected_args(*f_out_p, args);
        }
 
@@ -557,10 +660,16 @@ static pid_t do_cmd(char *cmd, char *machine, char *user, char **remote_argv, in
                free(need_to_free);
 
        return pid;
+}
 
-  oom:
-       out_of_memory("do_cmd");
-       return 0; /* not reached */
+/* Older versions turn an empty string as a reference to the current directory.
+ * We now treat this as an error unless --old-args was used. */
+static char *dot_dir_or_error()
+{
+       if (old_style_args || am_server)
+               return ".";
+       rprintf(FERROR, "Empty destination arg specified (use \".\" or see --old-args).\n");
+       exit_cleanup(RERR_SYNTAX);
 }
 
 /* The receiving side operates in one of two modes:
@@ -579,7 +688,7 @@ static pid_t do_cmd(char *cmd, char *machine, char *user, char **remote_argv, in
 static char *get_local_name(struct file_list *flist, char *dest_path)
 {
        STRUCT_STAT st;
-       int statret;
+       int statret, trailing_slash;
        char *cp;
 
        if (DEBUG_GTE(RECV, 1)) {
@@ -590,9 +699,8 @@ static char *get_local_name(struct file_list *flist, char *dest_path)
        if (!dest_path || list_only)
                return NULL;
 
-       /* Treat an empty string as a copy into the current directory. */
        if (!*dest_path)
-           dest_path = ".";
+               dest_path = dot_dir_or_error();
 
        if (daemon_filter_list.head) {
                char *slash = strrchr(dest_path, '/');
@@ -612,7 +720,29 @@ static char *get_local_name(struct file_list *flist, char *dest_path)
        }
 
        /* See what currently exists at the destination. */
-       if ((statret = do_stat(dest_path, &st)) == 0) {
+       statret = do_stat(dest_path, &st);
+       cp = strrchr(dest_path, '/');
+       trailing_slash = cp && !cp[1];
+
+       if (mkpath_dest_arg && statret < 0 && (cp || file_total > 1)) {
+               int save_errno = errno;
+               int ret = make_path(dest_path, file_total > 1 && !trailing_slash ? 0 : MKP_DROP_NAME);
+               if (ret < 0)
+                       goto mkdir_error;
+               if (ret && (INFO_GTE(NAME, 1) || stdout_format_has_i)) {
+                       if (file_total == 1 || trailing_slash)
+                               *cp = '\0';
+                       rprintf(FINFO, "created %d director%s for %s\n", ret, ret == 1 ? "y" : "ies", dest_path);
+                       if (file_total == 1 || trailing_slash)
+                               *cp = '/';
+               }
+               if (ret)
+                       statret = do_stat(dest_path, &st);
+               else
+                       errno = save_errno;
+       }
+
+       if (statret == 0) {
                /* If the destination is a dir, enter it and use mode 1. */
                if (S_ISDIR(st.st_mode)) {
                        if (!change_dir(dest_path, CD_NORMAL)) {
@@ -642,23 +772,20 @@ static char *get_local_name(struct file_list *flist, char *dest_path)
                exit_cleanup(RERR_FILESELECT);
        }
 
-       cp = strrchr(dest_path, '/');
-
        /* If we need a destination directory because the transfer is not
         * of a single non-directory or the user has requested one via a
         * destination path ending in a slash, create one and use mode 1. */
-       if (file_total > 1 || (cp && !cp[1])) {
-               /* Lop off the final slash (if any). */
-               if (cp && !cp[1])
-                       *cp = '\0';
+       if (file_total > 1 || trailing_slash) {
+               if (trailing_slash)
+                       *cp = '\0'; /* Lop off the final slash (if any). */
 
                if (statret == 0) {
-                       rprintf(FERROR,
-                           "ERROR: destination path is not a directory\n");
+                       rprintf(FERROR, "ERROR: destination path is not a directory\n");
                        exit_cleanup(RERR_SYNTAX);
                }
 
                if (do_mkdir(dest_path, ACCESSPERMS) != 0) {
+                   mkdir_error:
                        rsyserr(FERROR, errno, "mkdir %s failed",
                                full_fname(dest_path));
                        exit_cleanup(RERR_FILEIO);
@@ -668,7 +795,7 @@ static char *get_local_name(struct file_list *flist, char *dest_path)
                 && strcmp(flist->files[flist->low]->basename, ".") == 0)
                        flist->files[0]->flags |= FLAG_DIR_CREATED;
 
-               if (INFO_GTE(NAME, 1))
+               if (INFO_GTE(NAME, 1) || stdout_format_has_i)
                        rprintf(FINFO, "created directory %s\n", dest_path);
 
                if (dry_run) {
@@ -725,24 +852,22 @@ static void check_alt_basis_dirs(void)
                if (dry_run > 1 && *bdir != '/') {
                        int len = curr_dir_len + 1 + bd_len + 1;
                        char *new = new_array(char, len);
-                       if (!new)
-                               out_of_memory("check_alt_basis_dirs");
                        if (slash && strncmp(bdir, "../", 3) == 0) {
-                           /* We want to remove only one leading "../" prefix for
-                            * the directory we couldn't create in dry-run mode:
-                            * this ensures that any other ".." references get
-                            * evaluated the same as they would for a live copy. */
-                           *slash = '\0';
-                           pathjoin(new, len, curr_dir, bdir + 3);
-                           *slash = '/';
+                               /* We want to remove only one leading "../" prefix for
+                                * the directory we couldn't create in dry-run mode:
+                                * this ensures that any other ".." references get
+                                * evaluated the same as they would for a live copy. */
+                               *slash = '\0';
+                               pathjoin(new, len, curr_dir, bdir + 3);
+                               *slash = '/';
                        } else
-                           pathjoin(new, len, curr_dir, bdir);
+                               pathjoin(new, len, curr_dir, bdir);
                        basis_dir[j] = bdir = new;
                }
                if (do_stat(bdir, &st) < 0)
-                       rprintf(FWARNING, "%s arg does not exist: %s\n", dest_option, bdir);
+                       rprintf(FWARNING, "%s arg does not exist: %s\n", alt_dest_opt(0), bdir);
                else if (!S_ISDIR(st.st_mode))
-                       rprintf(FWARNING, "%s arg is not a dir: %s\n", dest_option, bdir);
+                       rprintf(FWARNING, "%s arg is not a dir: %s\n", alt_dest_opt(0), bdir);
        }
 }
 
@@ -783,7 +908,7 @@ static void read_final_goodbye(int f_in, int f_out)
 static void do_server_sender(int f_in, int f_out, int argc, char *argv[])
 {
        struct file_list *flist;
-       char *dir = argv[0];
+       char *dir;
 
        if (DEBUG_GTE(SEND, 1))
                rprintf(FINFO, "server_sender starting pid=%d\n", (int)getpid());
@@ -791,16 +916,21 @@ static void do_server_sender(int f_in, int f_out, int argc, char *argv[])
        if (am_daemon && lp_write_only(module_id)) {
                rprintf(FERROR, "ERROR: module is write only\n");
                exit_cleanup(RERR_SYNTAX);
-               return;
        }
        if (am_daemon && read_only && remove_source_files) {
                rprintf(FERROR,
-                   "ERROR: --remove-%s-files cannot be used with a read-only module\n",
-                   remove_source_files == 1 ? "source" : "sent");
+                       "ERROR: --remove-%s-files cannot be used with a read-only module\n",
+                       remove_source_files == 1 ? "source" : "sent");
+               exit_cleanup(RERR_SYNTAX);
+       }
+       if (argc < 1) {
+               rprintf(FERROR, "ERROR: do_server_sender called without args\n");
                exit_cleanup(RERR_SYNTAX);
-               return;
        }
 
+       become_copy_as_user();
+
+       dir = argv[0];
        if (!relative_paths) {
                if (!change_dir(dir, CD_NORMAL)) {
                        rsyserr(FERROR, errno, "change_dir#3 %s failed",
@@ -880,6 +1010,23 @@ static int do_recv(int f_in, int f_out, char *local_name)
                        backup_dir_buf[backup_dir_len-1] = '/';
        }
 
+       if (tmpdir) {
+               STRUCT_STAT st;
+               int ret = do_stat(tmpdir, &st);
+               if (ret < 0 || !S_ISDIR(st.st_mode)) {
+                       if (ret == 0) {
+                               rprintf(FERROR, "The temp-dir is not a directory: %s\n", tmpdir);
+                               exit_cleanup(RERR_SYNTAX);
+                       }
+                       if (errno == ENOENT) {
+                               rprintf(FERROR, "The temp-dir does not exist: %s\n", tmpdir);
+                               exit_cleanup(RERR_SYNTAX);
+                       }
+                       rprintf(FERROR, "Failed to stat temp-dir %s: %s\n", tmpdir, strerror(errno));
+                       exit_cleanup(RERR_FILEIO);
+               }
+       }
+
        io_flush(FULL_FLUSH);
 
        if ((pid = do_fork()) == -1) {
@@ -940,6 +1087,7 @@ static int do_recv(int f_in, int f_out, char *local_name)
        }
 
        am_generator = 1;
+       implied_filter_list.head = implied_filter_list.tail = NULL;
        flist_receiving_enabled = True;
 
        io_end_multiplex_in(MPLX_SWITCHING);
@@ -986,7 +1134,7 @@ static void do_server_recv(int f_in, int f_out, int argc, char *argv[])
        char *local_name = NULL;
        int negated_levels;
 
-       if (filesfrom_fd >= 0 && !msgs2stderr && protocol_version < 31) {
+       if (filesfrom_fd >= 0 && msgs2stderr != 1 && protocol_version < 31) {
                /* We can't mix messages with files-from data on the socket,
                 * so temporarily turn off info/debug messages. */
                negate_output_levels();
@@ -1003,6 +1151,8 @@ static void do_server_recv(int f_in, int f_out, int argc, char *argv[])
                return;
        }
 
+       become_copy_as_user();
+
        if (argc > 0) {
                char *dir = argv[0];
                argc--;
@@ -1069,8 +1219,7 @@ static void do_server_recv(int f_in, int f_out, int argc, char *argv[])
                if (partial_dir && *partial_dir == '/'
                 && check_filter(elp, FLOG, partial_dir + module_dirlen, 1) < 0) {
                    options_rejected:
-                       rprintf(FERROR,
-                               "Your options have been rejected by the server.\n");
+                       rprintf(FERROR, "Your options have been rejected by the server.\n");
                        exit_cleanup(RERR_SYNTAX);
                }
        }
@@ -1162,6 +1311,9 @@ int client_run(int f_in, int f_out, pid_t pid, int argc, char *argv[])
 
                if (write_batch && !am_server)
                        start_write_batch(f_out);
+
+               become_copy_as_user();
+
                flist = send_file_list(f_out, argc, argv);
                if (DEBUG_GTE(FLIST, 3))
                        rprintf(FINFO,"file list sent\n");
@@ -1195,6 +1347,8 @@ int client_run(int f_in, int f_out, pid_t pid, int argc, char *argv[])
                        io_start_buffering_out(f_out);
        }
 
+       become_copy_as_user();
+
        send_filter_list(read_batch ? -1 : f_out);
 
        if (filesfrom_fd >= 0) {
@@ -1229,22 +1383,6 @@ int client_run(int f_in, int f_out, pid_t pid, int argc, char *argv[])
        return MAX(exit_code, exit_code2);
 }
 
-static int copy_argv(char *argv[])
-{
-       int i;
-
-       for (i = 0; argv[i]; i++) {
-               if (!(argv[i] = strdup(argv[i]))) {
-                       rprintf (FERROR, "out of memory at %s(%d)\n",
-                                __FILE__, __LINE__);
-                       return RERR_MALLOC;
-               }
-       }
-
-       return 0;
-}
-
-
 /* Start a client for either type of remote connection.  Work out
  * whether the arguments request a remote shell or rsyncd connection,
  * and call the appropriate connection function, then run_client.
@@ -1255,16 +1393,11 @@ static int start_client(int argc, char *argv[])
 {
        char *p, *shell_machine = NULL, *shell_user = NULL;
        char **remote_argv;
-       int remote_argc;
+       int remote_argc, env_port = rsync_port;
        int f_in, f_out;
        int ret;
        pid_t pid;
 
-       /* Don't clobber argv[] so that ps(1) can still show the right
-        * command line. */
-       if ((ret = copy_argv(argv)) != 0)
-               return ret;
-
        if (!read_batch) { /* for read_batch, NO source is specified */
                char *path = check_for_hostspec(argv[0], &shell_machine, &rsync_port);
                if (path) { /* source is remote */
@@ -1284,20 +1417,21 @@ static int start_client(int argc, char *argv[])
                                remote_argc--; /* don't count dest */
                                argc = 1;
                        }
-                       if (filesfrom_host && *filesfrom_host
-                           && strcmp(filesfrom_host, shell_machine) != 0) {
+                       if (filesfrom_host && *filesfrom_host && strcmp(filesfrom_host, shell_machine) != 0) {
                                rprintf(FERROR,
                                        "--files-from hostname is not the same as the transfer hostname\n");
                                exit_cleanup(RERR_SYNTAX);
                        }
                        am_sender = 0;
                        if (rsync_port)
-                               daemon_over_rsh = shell_cmd ? 1 : -1;
+                               daemon_connection = shell_cmd ? 1 : -1;
                } else { /* source is local, check dest arg */
                        am_sender = 1;
 
                        if (argc > 1) {
                                p = argv[--argc];
+                               if (!*p)
+                                       p = dot_dir_or_error();
                                remote_argv = argv + argc;
                        } else {
                                static char *dotarg[1] = { "." };
@@ -1307,8 +1441,7 @@ static int start_client(int argc, char *argv[])
                        remote_argc = 1;
 
                        path = check_for_hostspec(p, &shell_machine, &rsync_port);
-                       if (path && filesfrom_host && *filesfrom_host
-                           && strcmp(filesfrom_host, shell_machine) != 0) {
+                       if (path && filesfrom_host && *filesfrom_host && strcmp(filesfrom_host, shell_machine) != 0) {
                                rprintf(FERROR,
                                        "--files-from hostname is not the same as the transfer hostname\n");
                                exit_cleanup(RERR_SYNTAX);
@@ -1321,10 +1454,11 @@ static int start_client(int argc, char *argv[])
                                        exit_cleanup(RERR_SYNTAX);
                                }
                                shell_machine = NULL;
+                               rsync_port = 0;
                        } else { /* hostspec was found, so dest is remote */
                                argv[argc] = path;
                                if (rsync_port)
-                                       daemon_over_rsh = shell_cmd ? 1 : -1;
+                                       daemon_connection = shell_cmd ? 1 : -1;
                        }
                }
        } else {  /* read_batch */
@@ -1335,6 +1469,13 @@ static int start_client(int argc, char *argv[])
                }
                remote_argv = argv += argc - 1;
                remote_argc = argc = 1;
+               rsync_port = 0;
+       }
+
+       /* A local transfer doesn't unbackslash anything, so leave the args alone. */
+       if (local_server) {
+               old_style_args = 2;
+               trust_sender_args = trust_sender_filter = 1;
        }
 
        if (!rsync_port && remote_argc && !**remote_argv) /* Turn an empty arg into a dot dir. */
@@ -1344,8 +1485,15 @@ static int start_client(int argc, char *argv[])
                char *dummy_host;
                int dummy_port = rsync_port;
                int i;
+               if (!argv[0][0])
+                       goto invalid_empty;
                /* For local source, extra source args must not have hostspec. */
                for (i = 1; i < argc; i++) {
+                       if (!argv[i][0]) {
+                           invalid_empty:
+                               rprintf(FERROR, "Empty source arg specified.\n");
+                               exit_cleanup(RERR_SYNTAX);
+                       }
                        if (check_for_hostspec(argv[i], &dummy_host, &dummy_port)) {
                                rprintf(FERROR, "Unexpected remote arg: %s\n", argv[i]);
                                exit_cleanup(RERR_SYNTAX);
@@ -1355,6 +1503,8 @@ static int start_client(int argc, char *argv[])
                char *dummy_host;
                int dummy_port = rsync_port;
                int i;
+               if (filesfrom_fd < 0)
+                       add_implied_include(remote_argv[0], daemon_connection);
                /* For remote source, any extra source args must have either
                 * the same hostname or an empty hostname. */
                for (i = 1; i < remote_argc; i++) {
@@ -1378,13 +1528,19 @@ static int start_client(int argc, char *argv[])
                        if (!rsync_port && !*arg) /* Turn an empty arg into a dot dir. */
                                arg = ".";
                        remote_argv[i] = arg;
+                       add_implied_include(arg, daemon_connection);
                }
        }
 
-       if (daemon_over_rsh < 0)
+       if (rsync_port < 0)
+               rsync_port = RSYNC_PORT;
+       else
+               env_port = rsync_port;
+
+       if (daemon_connection < 0)
                return start_socket_client(shell_machine, remote_argc, remote_argv, argc, argv);
 
-       if (password_file && !daemon_over_rsh) {
+       if (password_file && !daemon_connection) {
                rprintf(FERROR, "The --password-file option may only be "
                                "used when accessing an rsync daemon.\n");
                exit_cleanup(RERR_SYNTAX);
@@ -1411,12 +1567,18 @@ static int start_client(int argc, char *argv[])
                        NS(remote_argv[0]));
        }
 
-       pid = do_cmd(shell_cmd, shell_machine, shell_user, remote_argv, remote_argc,
-                    &f_in, &f_out);
+#ifdef HAVE_PUTENV
+       if (daemon_connection)
+               set_env_num("RSYNC_PORT", env_port);
+#else
+       (void)env_port;
+#endif
+
+       pid = do_cmd(shell_cmd, shell_machine, shell_user, remote_argv, remote_argc, &f_in, &f_out);
 
        /* if we're running an rsync server on the remote host over a
         * remote shell command, we need to do the RSYNCD protocol first */
-       if (daemon_over_rsh) {
+       if (daemon_connection) {
                int tmpret;
                tmpret = start_inband_exchange(f_in, f_out, shell_user, remote_argc, remote_argv);
                if (tmpret < 0)
@@ -1434,6 +1596,7 @@ static int start_client(int argc, char *argv[])
 
 static void sigusr1_handler(UNUSED(int val))
 {
+       called_from_signal_handler = 1;
        exit_cleanup(RERR_SIGNAL1);
 }
 
@@ -1447,6 +1610,14 @@ static void sigusr2_handler(UNUSED(int val))
        _exit(0);
 }
 
+#if defined SIGINFO || defined SIGVTALRM
+static void siginfo_handler(UNUSED(int val))
+{
+       if (!am_server && !INFO_GTE(PROGRESS, 1))
+               want_progress_now = True;
+}
+#endif
+
 void remember_children(UNUSED(int val))
 {
 #ifdef WNOHANG
@@ -1473,7 +1644,6 @@ void remember_children(UNUSED(int val))
 #endif
 }
 
-
 /**
  * This routine catches signals and tries to send them to gdb.
  *
@@ -1494,12 +1664,9 @@ const char *get_panic_action(void)
 
        if (cmd_fmt)
                return cmd_fmt;
-       else
-               return "xterm -display :0 -T Panic -n Panic "
-                       "-e gdb /proc/%d/exe %d";
+       return "xterm -display :0 -T Panic -n Panic -e gdb /proc/%d/exe %d";
 }
 
-
 /**
  * Handle a fatal signal by launching a debugger, controlled by $RSYNC_PANIC_ACTION.
  *
@@ -1517,18 +1684,36 @@ static void rsync_panic_handler(UNUSED(int whatsig))
 
        /* Unless we failed to execute gdb, we allow the process to
         * continue.  I'm not sure if that's right. */
-       ret = system(cmd_buf);
+       ret = shell_exec(cmd_buf);
        if (ret)
                _exit(ret);
 }
 #endif
 
+static void unset_env_var(const char *var)
+{
+#ifdef HAVE_UNSETENV
+       unsetenv(var);
+#else
+#ifdef HAVE_PUTENV
+       char *mem;
+       if (asprintf(&mem, "%s=", var) < 0)
+               out_of_memory("unset_env_var");
+       putenv(mem);
+#else
+       (void)var;
+#endif
+#endif
+}
+
 
 int main(int argc,char *argv[])
 {
        int ret;
-       int orig_argc = argc;
-       char **orig_argv = argv;
+
+       raw_argc = argc;
+       raw_argv = argv;
+
 #ifdef HAVE_SIGACTION
 # ifdef HAVE_SIGPROCMASK
        sigset_t sigmask;
@@ -1546,14 +1731,37 @@ int main(int argc,char *argv[])
        SIGACTMASK(SIGABRT, rsync_panic_handler);
        SIGACTMASK(SIGBUS, rsync_panic_handler);
 #endif
+#ifdef SIGINFO
+       SIGACTMASK(SIGINFO, siginfo_handler);
+#endif
+#ifdef SIGVTALRM
+       SIGACTMASK(SIGVTALRM, siginfo_handler);
+#endif
 
        starttime = time(NULL);
        our_uid = MY_UID();
        our_gid = MY_GID();
-       am_root = our_uid == 0;
+       am_root = our_uid == ROOT_UID;
+
+       unset_env_var("DISPLAY");
+
+#if defined USE_OPENSSL && defined SET_OPENSSL_CONF
+#define TO_STR2(x) #x
+#define TO_STR(x) TO_STR2(x)
+       /* ./configure --with-openssl-conf=/etc/ssl/openssl-rsync.cnf
+        * defines SET_OPENSSL_CONF as that unquoted pathname. */
+       if (!getenv("OPENSSL_CONF")) /* Don't override it if it's already set. */
+               set_env_str("OPENSSL_CONF", TO_STR(SET_OPENSSL_CONF));
+#undef TO_STR
+#undef TO_STR2
+#endif
 
        memset(&stats, 0, sizeof(stats));
 
+       /* Even a non-daemon runs needs the default config values to be set, e.g.
+        * lp_dont_compress() is queried when no --skip-compress option is set. */
+       reset_daemon_vars();
+
        if (argc < 2) {
                usage(FERROR);
                exit_cleanup(RERR_SYNTAX);
@@ -1566,14 +1774,16 @@ int main(int argc,char *argv[])
 
 #if defined CONFIG_LOCALE && defined HAVE_SETLOCALE
        setlocale(LC_CTYPE, "");
+       setlocale(LC_NUMERIC, "");
 #endif
 
        if (!parse_arguments(&argc, (const char ***) &argv)) {
-               /* FIXME: We ought to call the same error-handling
-                * code here, rather than relying on getopt. */
                option_error();
                exit_cleanup(RERR_SYNTAX);
        }
+       if (write_batch
+        && poptDupArgv(argc, (const char **)argv, &cooked_argc, (const char ***)&cooked_argv) != 0)
+               out_of_memory("main");
 
        SIGACTMASK(SIGINT, sig_int);
        SIGACTMASK(SIGHUP, sig_int);
@@ -1595,24 +1805,8 @@ int main(int argc,char *argv[])
         * that implement getcwd that way "pwd" can't be found after chroot. */
        change_dir(NULL, CD_NORMAL);
 
-       init_flist();
-
        if ((write_batch || read_batch) && !am_server) {
-               if (write_batch)
-                       write_batch_shell_file(orig_argc, orig_argv, argc);
-
-               if (read_batch && strcmp(batch_name, "-") == 0)
-                       batch_fd = STDIN_FILENO;
-               else {
-                       batch_fd = do_open(batch_name,
-                                  write_batch ? O_WRONLY | O_CREAT | O_TRUNC
-                                  : O_RDONLY, S_IRUSR | S_IWUSR);
-               }
-               if (batch_fd < 0) {
-                       rsyserr(FERROR, errno, "Batch file %s open error",
-                               full_fname(batch_name));
-                       exit_cleanup(RERR_FILEIO);
-               }
+               open_batch_files(); /* sets batch_fd */
                if (read_batch)
                        read_stream_flags(batch_fd);
                else