Mention updated config files.
[rsync.git] / options.c
index 9ffc3cf7a8628e872c80da0fefe84437650cb68e..93bbe7b06cc535deb7e38e7503c41df052bafdd8 100644 (file)
--- a/options.c
+++ b/options.c
@@ -3,7 +3,7 @@
  *
  * Copyright (C) 1998-2001 Andrew Tridgell <tridge@samba.org>
  * Copyright (C) 2000, 2001, 2002 Martin Pool <mbp@samba.org>
- * Copyright (C) 2002-2020 Wayne Davison
+ * Copyright (C) 2002-2023 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
@@ -27,6 +27,8 @@
 extern int module_id;
 extern int local_server;
 extern int sanitize_paths;
+extern int trust_sender_args;
+extern int trust_sender_filter;
 extern unsigned int module_dirlen;
 extern filter_rule_list filter_list;
 extern filter_rule_list daemon_filter_list;
@@ -47,6 +49,7 @@ int append_mode = 0;
 int keep_dirlinks = 0;
 int copy_dirlinks = 0;
 int copy_links = 0;
+int copy_devices = 0;
 int write_devices = 0;
 int preserve_links = 0;
 int preserve_hard_links = 0;
@@ -58,14 +61,18 @@ int preserve_devices = 0;
 int preserve_specials = 0;
 int preserve_uid = 0;
 int preserve_gid = 0;
-int preserve_times = 0;
+int preserve_mtimes = 0;
 int preserve_atimes = 0;
 int preserve_crtimes = 0;
+int omit_dir_times = 0;
+int omit_link_times = 0;
+int trust_sender = 0;
 int update_only = 0;
 int open_noatime = 0;
 int cvs_exclude = 0;
 int dry_run = 0;
 int do_xfers = 1;
+int do_fsync = 0;
 int ignore_times = 0;
 int delete_mode = 0;
 int delete_during = 0;
@@ -88,6 +95,7 @@ int implied_dirs = 1;
 int missing_args = 0; /* 0 = FERROR_XFER, 1 = ignore, 2 = delete */
 int numeric_ids = 0;
 int msgs2stderr = 2; /* Default: send errors to stderr for local & remote-shell transfers */
+int saw_stderr_opt = 0;
 int allow_8bit_chars = 0;
 int force_delete = 0;
 int io_timeout = 0;
@@ -98,6 +106,7 @@ int filesfrom_fd = -1;
 char *filesfrom_host = NULL;
 int eol_nulls = 0;
 int protect_args = -1;
+int old_style_args = -1;
 int human_readable = 1;
 int recurse = 0;
 int mkpath_dest_arg = 0;
@@ -191,6 +200,7 @@ int remote_option_cnt = 0;
 const char **remote_options = NULL;
 const char *checksum_choice = NULL;
 const char *compress_choice = NULL;
+static const char *empty_argv[1];
 
 int quiet = 0;
 int output_motd = 1;
@@ -227,7 +237,7 @@ static const char *debug_verbosity[] = {
 #define MAX_VERBOSITY ((int)(sizeof debug_verbosity / sizeof debug_verbosity[0]) - 1)
 
 static const char *info_verbosity[1+MAX_VERBOSITY] = {
-       /*0*/ NULL,
+       /*0*/ "NONREG",
        /*1*/ "COPY,DEL,FLIST,MISC,NAME,STATS,SYMSAFE",
        /*2*/ "BACKUP,MISC2,MOUNT,NAME2,REMOVE,SKIP",
 };
@@ -265,9 +275,10 @@ static struct output_struct info_words[COUNT_INFO+1] = {
        INFO_WORD(MISC, W_SND|W_REC, "Mention miscellaneous information (levels 1-2)"),
        INFO_WORD(MOUNT, W_SND|W_REC, "Mention mounts that were found or skipped"),
        INFO_WORD(NAME, W_SND|W_REC, "Mention 1) updated file/dir names, 2) unchanged names"),
+       INFO_WORD(NONREG, W_REC, "Mention skipped non-regular files (default 1, 0 disables)"),
        INFO_WORD(PROGRESS, W_CLI, "Mention 1) per-file progress or 2) total transfer progress"),
        INFO_WORD(REMOVE, W_SND, "Mention files removed on the sending side"),
-       INFO_WORD(SKIP, W_REC, "Mention files that are skipped due to options used (levels 1-2)"),
+       INFO_WORD(SKIP, W_REC, "Mention files skipped due to transfer overrides (levels 1-2)"),
        INFO_WORD(STATS, W_CLI|W_SRV, "Mention statistics at end of run (levels 1-3)"),
        INFO_WORD(SYMSAFE, W_SND|W_REC, "Mention symlinks that are unsafe"),
        { NULL, "--info", 0, 0, 0, 0 }
@@ -286,7 +297,7 @@ static struct output_struct debug_words[COUNT_DEBUG+1] = {
        DEBUG_WORD(DELTASUM, W_SND|W_REC, "Debug delta-transfer checksumming (levels 1-4)"),
        DEBUG_WORD(DUP, W_REC, "Debug weeding of duplicate names"),
        DEBUG_WORD(EXIT, W_CLI|W_SRV, "Debug exit events (levels 1-3)"),
-       DEBUG_WORD(FILTER, W_SND|W_REC, "Debug filter actions (levels 1-2)"),
+       DEBUG_WORD(FILTER, W_SND|W_REC, "Debug filter actions (levels 1-3)"),
        DEBUG_WORD(FLIST, W_SND|W_REC, "Debug file-list operations (levels 1-4)"),
        DEBUG_WORD(FUZZY, W_REC, "Debug fuzzy scoring (levels 1-2)"),
        DEBUG_WORD(GENR, W_REC, "Debug generator functions"),
@@ -307,8 +318,6 @@ static int verbose = 0;
 static int do_stats = 0;
 static int do_progress = 0;
 static int daemon_opt;   /* sets am_daemon after option error-reporting */
-static int omit_dir_times = 0;
-static int omit_link_times = 0;
 static int F_option_cnt = 0;
 static int modify_window_set;
 static int itemize_changes = 0;
@@ -487,9 +496,9 @@ static void output_item_help(struct output_struct *words)
 
        rprintf(FINFO, fmt, "HELP", "Output this help message");
        rprintf(FINFO, "\n");
-       rprintf(FINFO, "Options added for each increase in verbose level:\n");
+       rprintf(FINFO, "Options added at each level of verbosity:\n");
 
-       for (j = 1; j <= MAX_VERBOSITY; j++) {
+       for (j = 0; j <= MAX_VERBOSITY; j++) {
                parse_output_words(words, levels, verbosity[j], HELP_PRIORITY);
                opt = make_output_option(words, levels, W_CLI|W_SRV|W_SND|W_REC);
                if (opt) {
@@ -508,7 +517,7 @@ static void set_output_verbosity(int level, uchar priority)
        if (level > MAX_VERBOSITY)
                level = MAX_VERBOSITY;
 
-       for (j = 1; j <= level; j++) {
+       for (j = 0; j <= level; j++) {
                parse_output_words(info_words, info_levels, info_verbosity[j], priority);
                parse_output_words(debug_words, debug_levels, debug_verbosity[j], priority);
        }
@@ -527,7 +536,7 @@ void limit_output_verbosity(int level)
        memset(debug_limits, 0, sizeof debug_limits);
 
        /* Compute the level limits in the above arrays. */
-       for (j = 1; j <= level; j++) {
+       for (j = 0; j <= level; j++) {
                parse_output_words(info_words, info_limits, info_verbosity[j], LIMIT_PRIORITY);
                parse_output_words(debug_words, debug_limits, debug_verbosity[j], LIMIT_PRIORITY);
        }
@@ -574,7 +583,7 @@ enum {OPT_SERVER = 1000, OPT_DAEMON, OPT_SENDER, OPT_EXCLUDE, OPT_EXCLUDE_FROM,
       OPT_READ_BATCH, OPT_WRITE_BATCH, OPT_ONLY_WRITE_BATCH, OPT_MAX_SIZE,
       OPT_NO_D, OPT_APPEND, OPT_NO_ICONV, OPT_INFO, OPT_DEBUG, OPT_BLOCK_SIZE,
       OPT_USERMAP, OPT_GROUPMAP, OPT_CHOWN, OPT_BWLIMIT, OPT_STDERR,
-      OPT_OLD_COMPRESS, OPT_NEW_COMPRESS, OPT_NO_COMPRESS,
+      OPT_OLD_COMPRESS, OPT_NEW_COMPRESS, OPT_NO_COMPRESS, OPT_OLD_ARGS,
       OPT_STOP_AFTER, OPT_STOP_AT,
       OPT_REFUSED_BASE = 9000};
 
@@ -621,9 +630,9 @@ static struct poptOption long_options[] = {
   {"xattrs",          'X', POPT_ARG_NONE,   0, 'X', 0, 0 },
   {"no-xattrs",        0,  POPT_ARG_VAL,    &preserve_xattrs, 0, 0, 0 },
   {"no-X",             0,  POPT_ARG_VAL,    &preserve_xattrs, 0, 0, 0 },
-  {"times",           't', POPT_ARG_VAL,    &preserve_times, 1, 0, 0 },
-  {"no-times",         0,  POPT_ARG_VAL,    &preserve_times, 0, 0, 0 },
-  {"no-t",             0,  POPT_ARG_VAL,    &preserve_times, 0, 0, 0 },
+  {"times",           't', POPT_ARG_VAL,    &preserve_mtimes, 1, 0, 0 },
+  {"no-times",         0,  POPT_ARG_VAL,    &preserve_mtimes, 0, 0, 0 },
+  {"no-t",             0,  POPT_ARG_VAL,    &preserve_mtimes, 0, 0, 0 },
   {"atimes",          'U', POPT_ARG_NONE,   0, 'U', 0, 0 },
   {"no-atimes",        0,  POPT_ARG_VAL,    &preserve_atimes, 0, 0, 0 },
   {"no-U",             0,  POPT_ARG_VAL,    &preserve_atimes, 0, 0, 0 },
@@ -652,6 +661,7 @@ static struct poptOption long_options[] = {
   {"no-D",             0,  POPT_ARG_NONE,   0, OPT_NO_D, 0, 0 },
   {"devices",          0,  POPT_ARG_VAL,    &preserve_devices, 1, 0, 0 },
   {"no-devices",       0,  POPT_ARG_VAL,    &preserve_devices, 0, 0, 0 },
+  {"copy-devices",     0,  POPT_ARG_NONE,   &copy_devices, 0, 0, 0 },
   {"write-devices",    0,  POPT_ARG_VAL,    &write_devices, 1, 0, 0 },
   {"no-write-devices", 0,  POPT_ARG_VAL,    &write_devices, 0, 0, 0 },
   {"specials",         0,  POPT_ARG_VAL,    &preserve_specials, 1, 0, 0 },
@@ -777,9 +787,14 @@ static struct poptOption long_options[] = {
   {"files-from",       0,  POPT_ARG_STRING, &files_from, 0, 0, 0 },
   {"from0",           '0', POPT_ARG_VAL,    &eol_nulls, 1, 0, 0},
   {"no-from0",         0,  POPT_ARG_VAL,    &eol_nulls, 0, 0, 0},
-  {"protect-args",    's', POPT_ARG_VAL,    &protect_args, 1, 0, 0},
+  {"old-args",         0,  POPT_ARG_NONE,   0, OPT_OLD_ARGS, 0, 0},
+  {"no-old-args",      0,  POPT_ARG_VAL,    &old_style_args, 0, 0, 0},
+  {"secluded-args",   's', POPT_ARG_VAL,    &protect_args, 1, 0, 0},
+  {"no-secluded-args", 0,  POPT_ARG_VAL,    &protect_args, 0, 0, 0},
+  {"protect-args",     0,  POPT_ARG_VAL,    &protect_args, 1, 0, 0},
   {"no-protect-args",  0,  POPT_ARG_VAL,    &protect_args, 0, 0, 0},
   {"no-s",             0,  POPT_ARG_VAL,    &protect_args, 0, 0, 0},
+  {"trust-sender",     0,  POPT_ARG_VAL,    &trust_sender, 1, 0, 0},
   {"numeric-ids",      0,  POPT_ARG_VAL,    &numeric_ids, 1, 0, 0 },
   {"no-numeric-ids",   0,  POPT_ARG_VAL,    &numeric_ids, 0, 0, 0 },
   {"usermap",          0,  POPT_ARG_STRING, 0, OPT_USERMAP, 0, 0 },
@@ -789,6 +804,7 @@ static struct poptOption long_options[] = {
   {"no-timeout",       0,  POPT_ARG_VAL,    &io_timeout, 0, 0, 0 },
   {"contimeout",       0,  POPT_ARG_INT,    &connect_timeout, 0, 0, 0 },
   {"no-contimeout",    0,  POPT_ARG_VAL,    &connect_timeout, 0, 0, 0 },
+  {"fsync",            0,  POPT_ARG_NONE,   &do_fsync, 0, 0, 0 },
   {"stop-after",       0,  POPT_ARG_STRING, 0, OPT_STOP_AFTER, 0, 0 },
   {"time-limit",       0,  POPT_ARG_STRING, 0, OPT_STOP_AFTER, 0, 0 }, /* earlier stop-after name */
   {"stop-at",          0,  POPT_ARG_STRING, 0, OPT_STOP_AT, 0, 0 },
@@ -937,11 +953,12 @@ static void set_refuse_options(void)
                if (!am_daemon
                 || op->shortName == 'e' /* Required for compatibility flags */
                 || op->shortName == '0' /* --from0 just modifies --files-from, so refuse that instead (or not) */
-                || op->shortName == 's' /* --protect-args is always OK */
+                || op->shortName == 's' /* --secluded-args is always OK */
                 || op->shortName == 'n' /* --dry-run is always OK */
                 || strcmp("iconv", longName) == 0
                 || strcmp("no-iconv", longName) == 0
                 || strcmp("checksum-seed", longName) == 0
+                || strcmp("copy-devices", longName) == 0 /* disable wild-match (it gets refused below) */
                 || strcmp("write-devices", longName) == 0 /* disable wild-match (it gets refused below) */
                 || strcmp("log-format", longName) == 0 /* aka out-format (NOT log-file-format) */
                 || strcmp("sender", longName) == 0
@@ -953,6 +970,7 @@ static void set_refuse_options(void)
        assert(list_end != NULL);
 
        if (am_daemon) { /* Refused by default, but can be accepted via a negated exact match. */
+               parse_one_refuse_match(0, "copy-devices", list_end);
                parse_one_refuse_match(0, "write-devices", list_end);
        }
 
@@ -1330,7 +1348,7 @@ char *alt_dest_opt(int type)
  **/
 int parse_arguments(int *argc_p, const char ***argv_p)
 {
-       static poptContext pc;
+       poptContext pc;
        const char *arg, **argv = *argv_p;
        int argc = *argc_p;
        int opt, want_dest_type;
@@ -1350,10 +1368,6 @@ int parse_arguments(int *argc_p, const char ***argv_p)
 
        /* TODO: Call poptReadDefaultConfig; handle errors. */
 
-       /* The context leaks in case of an error, but if there's a
-        * problem we always exit anyhow. */
-       if (pc)
-               poptFreeContext(pc);
        pc = poptGetContext(RSYNC_NAME, argc, argv, long_options, 0);
        if (!am_server) {
                poptReadDefaultConfig(pc, 0);
@@ -1396,7 +1410,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
                                strlcpy(err_buf,
                                        "Attempt to hack rsync thwarted!\n",
                                        sizeof err_buf);
-                               return 0;
+                               goto cleanup;
                        }
 #ifdef ICONV_OPTION
                        iconv_opt = NULL;
@@ -1442,7 +1456,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
                        if (tmpdir && strlen(tmpdir) >= MAXPATHLEN - 10) {
                                snprintf(err_buf, sizeof err_buf,
                                         "the --temp-dir path is WAY too long.\n");
-                               return 0;
+                               goto cleanup;
                        }
 
                        if (!daemon_opt) {
@@ -1452,8 +1466,16 @@ int parse_arguments(int *argc_p, const char ***argv_p)
                                exit_cleanup(RERR_SYNTAX);
                        }
 
-                       *argv_p = argv = poptGetArgs(pc);
-                       *argc_p = argc = count_args(argv);
+                       argv = poptGetArgs(pc);
+                       argc = count_args(argv);
+                       if (!argc) {
+                               *argv_p = empty_argv;
+                               *argc_p = 0;
+                       } else if (poptDupArgv(argc, argv, argc_p, argv_p) != 0)
+                               out_of_memory("parse_arguments");
+                       argv = *argv_p;
+                       poptFreeContext(pc);
+
                        am_starting_up = 0;
                        daemon_opt = 0;
                        am_daemon = 1;
@@ -1508,7 +1530,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
                case 'a':
                        if (refused_archive_part) {
                                create_refuse_error(refused_archive_part);
-                               return 0;
+                               goto cleanup;
                        }
                        if (!recurse) /* preserve recurse == 2 */
                                recurse = 1;
@@ -1516,7 +1538,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
                        preserve_links = 1;
 #endif
                        preserve_perms = 1;
-                       preserve_times = 1;
+                       preserve_mtimes = 1;
                        preserve_gid = 1;
                        preserve_uid = 1;
                        preserve_devices = 1;
@@ -1578,7 +1600,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
                case 'P':
                        if (refused_partial || refused_progress) {
                                create_refuse_error(refused_partial ? refused_partial : refused_progress);
-                               return 0;
+                               goto cleanup;
                        }
                        do_progress = 1;
                        keep_partial = 1;
@@ -1601,12 +1623,19 @@ int parse_arguments(int *argc_p, const char ***argv_p)
                        compress_choice = NULL;
                        break;
 
+               case OPT_OLD_ARGS:
+                       if (old_style_args <= 0)
+                               old_style_args = 1;
+                       else
+                               old_style_args++;
+                       break;
+
                case 'M':
                        arg = poptGetOptArg(pc);
                        if (*arg != '-') {
                                snprintf(err_buf, sizeof err_buf,
                                        "Remote option must start with a dash: %s\n", arg);
-                               return 0;
+                               goto cleanup;
                        }
                        if (remote_option_cnt+2 >= remote_option_alloc) {
                                remote_option_alloc += 16;
@@ -1648,27 +1677,27 @@ int parse_arguments(int *argc_p, const char ***argv_p)
                        ssize_t size;
                        arg = poptGetOptArg(pc);
                        if ((size = parse_size_arg(arg, 'b', "block-size", 0, max_blength, False)) < 0)
-                               return 0;
+                               goto cleanup;
                        block_size = (int32)size;
                        break;
                }
 
                case OPT_MAX_SIZE:
                        if ((max_size = parse_size_arg(max_size_arg, 'b', "max-size", 0, -1, False)) < 0)
-                               return 0;
+                               goto cleanup;
                        max_size_arg = strdup(do_big_num(max_size, 0, NULL));
                        break;
 
                case OPT_MIN_SIZE:
                        if ((min_size = parse_size_arg(min_size_arg, 'b', "min-size", 0, -1, False)) < 0)
-                               return 0;
+                               goto cleanup;
                        min_size_arg = strdup(do_big_num(min_size, 0, NULL));
                        break;
 
                case OPT_BWLIMIT: {
                        ssize_t size = parse_size_arg(bwlimit_arg, 'K', "bwlimit", 512, -1, True);
                        if (size < 0)
-                               return 0;
+                               goto cleanup;
                        bwlimit_arg = strdup(do_big_num(size, 0, NULL));
                        bwlimit = (size + 512) / 1024;
                        break;
@@ -1697,7 +1726,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
                                snprintf(err_buf, sizeof err_buf,
                                        "ERROR: the %s option conflicts with the %s option\n",
                                        alt_dest_opt(want_dest_type), alt_dest_opt(0));
-                               return 0;
+                               goto cleanup;
                        }
                        alt_dest_type = want_dest_type;
 
@@ -1705,7 +1734,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
                                snprintf(err_buf, sizeof err_buf,
                                        "ERROR: at most %d %s args may be specified\n",
                                        MAX_BASIS_DIRS, alt_dest_opt(0));
-                               return 0;
+                               goto cleanup;
                        }
                        /* We defer sanitizing this arg until we know what
                         * our destination directory is going to be. */
@@ -1718,7 +1747,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
                                snprintf(err_buf, sizeof err_buf,
                                        "Invalid argument passed to --chmod (%s)\n",
                                        arg);
-                               return 0;
+                               goto cleanup;
                        }
                        break;
 
@@ -1737,14 +1766,15 @@ int parse_arguments(int *argc_p, const char ***argv_p)
                                if (usermap_via_chown) {
                                        snprintf(err_buf, sizeof err_buf,
                                                "--usermap conflicts with prior --chown.\n");
-                                       return 0;
+                                       goto cleanup;
                                }
                                snprintf(err_buf, sizeof err_buf,
                                        "You can only specify --usermap once.\n");
-                               return 0;
+                               goto cleanup;
                        }
                        usermap = (char *)poptGetOptArg(pc);
                        usermap_via_chown = False;
+                       preserve_uid = 1;
                        break;
 
                case OPT_GROUPMAP:
@@ -1752,14 +1782,15 @@ int parse_arguments(int *argc_p, const char ***argv_p)
                                if (groupmap_via_chown) {
                                        snprintf(err_buf, sizeof err_buf,
                                                "--groupmap conflicts with prior --chown.\n");
-                                       return 0;
+                                       goto cleanup;
                                }
                                snprintf(err_buf, sizeof err_buf,
                                        "You can only specify --groupmap once.\n");
-                               return 0;
+                               goto cleanup;
                        }
                        groupmap = (char *)poptGetOptArg(pc);
                        groupmap_via_chown = False;
+                       preserve_gid = 1;
                        break;
 
                case OPT_CHOWN: {
@@ -1774,30 +1805,32 @@ int parse_arguments(int *argc_p, const char ***argv_p)
                                        if (!usermap_via_chown) {
                                                snprintf(err_buf, sizeof err_buf,
                                                        "--chown conflicts with prior --usermap.\n");
-                                               return 0;
+                                               goto cleanup;
                                        }
                                        snprintf(err_buf, sizeof err_buf,
                                                "You can only specify a user-affecting --chown once.\n");
-                                       return 0;
+                                       goto cleanup;
                                }
                                if (asprintf(&usermap, "*:%.*s", len, chown) < 0)
                                        out_of_memory("parse_arguments");
                                usermap_via_chown = True;
+                               preserve_uid = 1;
                        }
                        if (arg && *arg) {
                                if (groupmap) {
                                        if (!groupmap_via_chown) {
                                                snprintf(err_buf, sizeof err_buf,
                                                        "--chown conflicts with prior --groupmap.\n");
-                                               return 0;
+                                               goto cleanup;
                                        }
                                        snprintf(err_buf, sizeof err_buf,
                                                "You can only specify a group-affecting --chown once.\n");
-                                       return 0;
+                                       goto cleanup;
                                }
                                if (asprintf(&groupmap, "*:%s", arg) < 0)
                                        out_of_memory("parse_arguments");
                                groupmap_via_chown = True;
+                               preserve_gid = 1;
                        }
                        break;
                }
@@ -1820,7 +1853,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
                        snprintf(err_buf,sizeof(err_buf),
                                 "ACLs are not supported on this %s\n",
                                 am_server ? "server" : "client");
-                       return 0;
+                       goto cleanup;
 #endif
 
                case 'X':
@@ -1831,7 +1864,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
                        snprintf(err_buf,sizeof(err_buf),
                                 "extended attributes are not supported on this %s\n",
                                 am_server ? "server" : "client");
-                       return 0;
+                       goto cleanup;
 #endif
 
                case OPT_STOP_AFTER: {
@@ -1840,7 +1873,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
                        stop_at_utime = time(NULL);
                        if ((val = atol(arg) * 60) <= 0 || LONG_MAX - val < stop_at_utime || (long)(time_t)val != val) {
                                snprintf(err_buf, sizeof err_buf, "invalid --stop-after value: %s\n", arg);
-                               return 0;
+                               goto cleanup;
                        }
                        stop_at_utime += val;
                        break;
@@ -1851,11 +1884,11 @@ int parse_arguments(int *argc_p, const char ***argv_p)
                        arg = poptGetOptArg(pc);
                        if ((stop_at_utime = parse_time(arg)) == (time_t)-1) {
                                snprintf(err_buf, sizeof err_buf, "invalid --stop-at format: %s\n", arg);
-                               return 0;
+                               goto cleanup;
                        }
                        if (stop_at_utime <= time(NULL)) {
                                snprintf(err_buf, sizeof err_buf, "--stop-at time is not in the future: %s\n", arg);
-                               return 0;
+                               goto cleanup;
                        }
                        break;
 #endif
@@ -1873,8 +1906,9 @@ int parse_arguments(int *argc_p, const char ***argv_p)
                        else {
                                snprintf(err_buf, sizeof err_buf,
                                        "--stderr mode \"%s\" is not one of errors, all, or client\n", arg);
-                               return 0;
+                               goto cleanup;
                        }
+                       saw_stderr_opt = 1;
                        break;
                }
 
@@ -1883,18 +1917,21 @@ int parse_arguments(int *argc_p, const char ***argv_p)
                         * turned this option off. */
                        if (opt >= OPT_REFUSED_BASE) {
                                create_refuse_error(opt);
-                               return 0;
+                               goto cleanup;
                        }
                        snprintf(err_buf, sizeof err_buf, "%s%s: %s\n",
                                 am_server ? "on remote machine: " : "",
                                 poptBadOption(pc, POPT_BADOPTION_NOALIAS),
                                 poptStrerror(opt));
-                       return 0;
+                       goto cleanup;
                }
        }
 
+       if (msgs2stderr != 2)
+               saw_stderr_opt = 1;
+
        if (version_opt_cnt) {
-               print_rsync_version(FINFO);
+               print_rsync_version(version_opt_cnt > 1 && !am_server ? FNONE : FINFO);
                exit_cleanup(0);
        }
 
@@ -1906,17 +1943,32 @@ int parse_arguments(int *argc_p, const char ***argv_p)
        if (max_alloc_arg) {
                ssize_t size = parse_size_arg(max_alloc_arg, 'B', "max-alloc", 1024*1024, -1, True);
                if (size < 0)
-                       return 0;
+                       goto cleanup;
                max_alloc = size;
        }
 
+       if (old_style_args < 0) {
+               if (!am_server && protect_args <= 0 && (arg = getenv("RSYNC_OLD_ARGS")) != NULL && *arg) {
+                       protect_args = 0;
+                       old_style_args = atoi(arg);
+               } else
+                       old_style_args = 0;
+       } else if (old_style_args) {
+               if (protect_args > 0) {
+                       snprintf(err_buf, sizeof err_buf,
+                                "--secluded-args conflicts with --old-args.\n");
+                       goto cleanup;
+               }
+               protect_args = 0;
+       }
+
        if (protect_args < 0) {
                if (am_server)
                        protect_args = 0;
                else if ((arg = getenv("RSYNC_PROTECT_ARGS")) != NULL && *arg)
                        protect_args = atoi(arg) ? 1 : 0;
                else {
-#ifdef RSYNC_USE_PROTECTED_ARGS
+#ifdef RSYNC_USE_SECLUDED_ARGS
                        protect_args = 1;
 #else
                        protect_args = 0;
@@ -1950,7 +2002,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
                        do_compression = CPRES_AUTO;
                if (do_compression && refused_compress) {
                        create_refuse_error(refused_compress);
-                       return 0;
+                       goto cleanup;
                }
        }
 
@@ -1975,7 +2027,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
                default:
                        snprintf(err_buf, sizeof err_buf,
                                "Invalid --outbuf setting -- specify N, L, or B.\n");
-                       return 0;
+                       goto cleanup;
                }
                setvbuf(stdout, (char *)NULL, mode, 0);
        }
@@ -2003,7 +2055,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
        }
        if (refused_no_iconv && !iconv_opt) {
                create_refuse_error(refused_no_iconv);
-               return 0;
+               goto cleanup;
        }
 #endif
 
@@ -2014,18 +2066,30 @@ int parse_arguments(int *argc_p, const char ***argv_p)
        if (orig_protect_args == 2 && am_server)
                protect_args = orig_protect_args;
 
-       if (protect_args == 1 && am_server)
+       if (protect_args == 1 && am_server) {
+               poptFreeContext(pc);
                return 1;
+       }
 
-       *argv_p = argv = poptGetArgs(pc);
-       *argc_p = argc = count_args(argv);
+       /* Because popt 1.19 has started to free the returned args data, we now
+        * make a copy of the array and then do an immediate cleanup. */
+       argv = poptGetArgs(pc);
+       argc = count_args(argv);
+       if (!argc) {
+               *argv_p = empty_argv;
+               *argc_p = 0;
+       } else if (poptDupArgv(argc, argv, argc_p, argv_p) != 0)
+               out_of_memory("parse_arguments");
+       argv = *argv_p;
+       poptFreeContext(pc);
+       pc = NULL;
 
 #ifndef SUPPORT_LINKS
        if (preserve_links && !am_sender) {
                snprintf(err_buf, sizeof err_buf,
                         "symlinks are not supported on this %s\n",
                         am_server ? "server" : "client");
-               return 0;
+               goto cleanup;
        }
 #endif
 
@@ -2034,7 +2098,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
                snprintf(err_buf, sizeof err_buf,
                         "hard links are not supported on this %s\n",
                         am_server ? "server" : "client");
-               return 0;
+               goto cleanup;
        }
 #endif
 
@@ -2042,20 +2106,20 @@ int parse_arguments(int *argc_p, const char ***argv_p)
        if (am_root < 0 && preserve_xattrs > 1) {
                snprintf(err_buf, sizeof err_buf,
                         "--fake-super conflicts with -XX\n");
-               return 0;
+               goto cleanup;
        }
 #else
        if (am_root < 0) {
                snprintf(err_buf, sizeof err_buf,
                         "--fake-super requires an rsync with extended attributes enabled\n");
-               return 0;
+               goto cleanup;
        }
 #endif
 
        if (write_batch && read_batch) {
                snprintf(err_buf, sizeof err_buf,
                        "--write-batch and --read-batch can not be used together\n");
-               return 0;
+               goto cleanup;
        }
        if (write_batch > 0 || read_batch) {
                if (am_server) {
@@ -2074,25 +2138,25 @@ int parse_arguments(int *argc_p, const char ***argv_p)
        if (read_batch && files_from) {
                snprintf(err_buf, sizeof err_buf,
                        "--read-batch cannot be used with --files-from\n");
-               return 0;
+               goto cleanup;
        }
        if (read_batch && remove_source_files) {
                snprintf(err_buf, sizeof err_buf,
                        "--read-batch cannot be used with --remove-%s-files\n",
                        remove_source_files == 1 ? "source" : "sent");
-               return 0;
+               goto cleanup;
        }
        if (batch_name && strlen(batch_name) > MAX_BATCH_NAME_LEN) {
                snprintf(err_buf, sizeof err_buf,
                        "the batch-file name must be %d characters or less.\n",
                        MAX_BATCH_NAME_LEN);
-               return 0;
+               goto cleanup;
        }
 
        if (tmpdir && strlen(tmpdir) >= MAXPATHLEN - 10) {
                snprintf(err_buf, sizeof err_buf,
                         "the --temp-dir path is WAY too long.\n");
-               return 0;
+               goto cleanup;
        }
 
        if (max_delete < 0 && max_delete != INT_MIN) {
@@ -2126,7 +2190,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
        if (delete_before + !!delete_during + delete_after > 1) {
                snprintf(err_buf, sizeof err_buf,
                        "You may not combine multiple --delete-WHEN options.\n");
-               return 0;
+               goto cleanup;
        }
        if (delete_before || delete_during || delete_after)
                delete_mode = 1;
@@ -2137,7 +2201,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
                                delete_during = 1;
                        else {
                                create_refuse_error(refused_delete_before);
-                               return 0;
+                               goto cleanup;
                        }
                } else if (refused_delete_during)
                        delete_before = 1;
@@ -2146,14 +2210,14 @@ int parse_arguments(int *argc_p, const char ***argv_p)
        if (!xfer_dirs && delete_mode) {
                snprintf(err_buf, sizeof err_buf,
                        "--delete does not work without --recursive (-r) or --dirs (-d).\n");
-               return 0;
+               goto cleanup;
        }
 
        if (missing_args == 3) /* simplify if both options were specified */
                missing_args = 2;
        if (refused_delete && (delete_mode || missing_args == 2)) {
                create_refuse_error(refused_delete);
-               return 0;
+               goto cleanup;
        }
 
        if (remove_source_files) {
@@ -2162,7 +2226,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
                 * options. */
                if (refused_delete && am_sender) {
                        create_refuse_error(refused_delete);
-                       return 0;
+                       goto cleanup;
                }
                need_messages_from_generator = 1;
        }
@@ -2216,7 +2280,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
                snprintf(err_buf, sizeof err_buf,
                        "--suffix cannot contain slashes: %s\n",
                        backup_suffix);
-               return 0;
+               goto cleanup;
        }
        if (backup_dir) {
                size_t len;
@@ -2229,7 +2293,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
                if (len > sizeof backup_dir_buf - 128) {
                        snprintf(err_buf, sizeof err_buf,
                                "the --backup-dir path is WAY too long.\n");
-                       return 0;
+                       goto cleanup;
                }
                backup_dir_len = (int)len;
                if (!backup_dir_len) {
@@ -2248,27 +2312,15 @@ int parse_arguments(int *argc_p, const char ***argv_p)
                        "--suffix cannot be empty %s\n", backup_dir_len < 0
                        ? "when --backup-dir is the same as the dest dir"
                        : "without a --backup-dir");
-               return 0;
+               goto cleanup;
        } else if (make_backups && delete_mode && !delete_excluded && !am_server) {
                snprintf(backup_dir_buf, sizeof backup_dir_buf,
                        "P *%s", backup_suffix);
                parse_filter_str(&filter_list, backup_dir_buf, rule_template(0), 0);
        }
 
-       if (preserve_times) {
-               preserve_times = PRESERVE_FILE_TIMES;
-               if (!omit_dir_times)
-                       preserve_times |= PRESERVE_DIR_TIMES;
-#ifdef CAN_SET_SYMLINK_TIMES
-               if (!omit_link_times)
-                       preserve_times |= PRESERVE_LINK_TIMES;
-#endif
-       }
-
-       if (make_backups && !backup_dir) {
-               omit_dir_times = 0; /* Implied, so avoid -O to sender. */
-               preserve_times &= ~PRESERVE_DIR_TIMES;
-       }
+       if (make_backups && !backup_dir)
+               omit_dir_times = -1; /* Implied, so avoid -O to sender. */
 
        if (stdout_format) {
                if (am_server && log_format_has(stdout_format, 'I'))
@@ -2288,7 +2340,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
        if (do_progress && !am_server) {
                if (!log_before_transfer && INFO_EQ(NAME, 0))
                        parse_output_words(info_words, info_levels, "name", DEFAULT_PRIORITY);
-               parse_output_words(info_words, info_levels, "flist2,progress", DEFAULT_PRIORITY);
+               parse_output_words(info_words, info_levels, "FLIST2,PROGRESS", DEFAULT_PRIORITY);
        }
 
        if (dry_run)
@@ -2329,11 +2381,11 @@ int parse_arguments(int *argc_p, const char ***argv_p)
                if (whole_file > 0) {
                        snprintf(err_buf, sizeof err_buf,
                                 "--append cannot be used with --whole-file\n");
-                       return 0;
+                       goto cleanup;
                }
                if (refused_inplace) {
                        create_refuse_error(refused_inplace);
-                       return 0;
+                       goto cleanup;
                }
                inplace = 1;
        }
@@ -2341,7 +2393,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
        if (write_devices) {
                if (refused_inplace) {
                        create_refuse_error(refused_inplace);
-                       return 0;
+                       goto cleanup;
                }
                inplace = 1;
        }
@@ -2356,13 +2408,13 @@ int parse_arguments(int *argc_p, const char ***argv_p)
                                 "--%s cannot be used with --%s\n",
                                 append_mode ? "append" : "inplace",
                                 delay_updates ? "delay-updates" : "partial-dir");
-                       return 0;
+                       goto cleanup;
                }
                /* --inplace implies --partial for refusal purposes, but we
                 * clear the keep_partial flag for internal logic purposes. */
                if (refused_partial) {
                        create_refuse_error(refused_partial);
-                       return 0;
+                       goto cleanup;
                }
                keep_partial = 0;
 #else
@@ -2370,7 +2422,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
                         "--%s is not supported on this %s\n",
                         append_mode ? "append" : "inplace",
                         am_server ? "server" : "client");
-               return 0;
+               goto cleanup;
 #endif
        } else {
                if (keep_partial && !partial_dir && !am_server) {
@@ -2384,7 +2436,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
                                partial_dir = NULL;
                        if (!partial_dir && refused_partial) {
                                create_refuse_error(refused_partial);
-                               return 0;
+                               goto cleanup;
                        }
                        keep_partial = 1;
                }
@@ -2405,14 +2457,14 @@ int parse_arguments(int *argc_p, const char ***argv_p)
                        if (am_server) {
                                snprintf(err_buf, sizeof err_buf,
                                        "The --files-from sent to the server cannot specify a host.\n");
-                               return 0;
+                               goto cleanup;
                        }
                        files_from = p;
                        filesfrom_host = h;
                        if (strcmp(files_from, "-") == 0) {
                                snprintf(err_buf, sizeof err_buf,
                                        "Invalid --files-from remote filename\n");
-                               return 0;
+                               goto cleanup;
                        }
                } else {
                        if (sanitize_paths)
@@ -2431,11 +2483,16 @@ int parse_arguments(int *argc_p, const char ***argv_p)
                                snprintf(err_buf, sizeof err_buf,
                                        "failed to open files-from file %s: %s\n",
                                        files_from, strerror(errno));
-                               return 0;
+                               goto cleanup;
                        }
                }
        }
 
+       if (trust_sender || am_server || read_batch)
+               trust_sender_args = trust_sender_filter = 1;
+       else if (old_style_args || filesfrom_host != NULL)
+               trust_sender_args = 1;
+
        am_starting_up = 0;
 
        return 1;
@@ -2443,10 +2500,80 @@ int parse_arguments(int *argc_p, const char ***argv_p)
   options_rejected:
        snprintf(err_buf, sizeof err_buf,
                "Your options have been rejected by the server.\n");
+  cleanup:
+       if (pc)
+               poptFreeContext(pc);
        return 0;
 }
 
 
+static char SPLIT_ARG_WHEN_OLD[1];
+
+/**
+ * Do backslash quoting of any weird chars in "arg", append the resulting
+ * string to the end of the "opt" (which gets a "=" appended if it is not
+ * an empty or NULL string), and return the (perhaps malloced) result.
+ * If opt is NULL, arg is considered a filename arg that allows wildcards.
+ * If it is "" or any other value, it is considered an option.
+ **/
+char *safe_arg(const char *opt, const char *arg)
+{
+#define SHELL_CHARS "!#$&;|<>(){}\"'` \t\\"
+#define WILD_CHARS  "*?[]" /* We don't allow remote brace expansion */
+       BOOL is_filename_arg = !opt;
+       char *escapes = is_filename_arg ? SHELL_CHARS : WILD_CHARS SHELL_CHARS;
+       BOOL escape_leading_dash = is_filename_arg && *arg == '-';
+       BOOL escape_leading_tilde = 0;
+       int len1 = opt && *opt ? strlen(opt) + 1 : 0;
+       int len2 = strlen(arg);
+       int extras = escape_leading_dash ? 2 : 0;
+       char *ret;
+       if (!protect_args && old_style_args < 2 && (!old_style_args || (!is_filename_arg && opt != SPLIT_ARG_WHEN_OLD))) {
+               const char *f;
+               if (*arg == '~' && is_filename_arg && !am_sender && !trust_sender_args
+                && ((relative_paths && !strstr(arg, "/./"))
+                 || !strchr(arg, '/'))) {
+                       extras++;
+                       escape_leading_tilde = 1;
+               }
+               for (f = arg; *f; f++) {
+                       if (strchr(escapes, *f))
+                               extras++;
+               }
+       }
+       if (!len1 && !extras)
+               return (char*)arg;
+       ret = new_array(char, len1 + len2 + extras + 1);
+       if (len1) {
+               memcpy(ret, opt, len1-1);
+               ret[len1-1] = '=';
+       }
+       if (escape_leading_dash) {
+               ret[len1++] = '.';
+               ret[len1++] = '/';
+               extras -= 2;
+       }
+       if (!extras)
+               memcpy(ret + len1, arg, len2);
+       else {
+               const char *f = arg;
+               char *t = ret + len1;
+               if (escape_leading_tilde)
+                       *t++ = '\\';
+               while (*f) {
+                        if (*f == '\\') {
+                               if (!is_filename_arg || !strchr(WILD_CHARS, f[1]))
+                                       *t++ = '\\';
+                       } else if (strchr(escapes, *f))
+                               *t++ = '\\';
+                       *t++ = *f++;
+               }
+       }
+       ret[len1+len2+extras] = '\0';
+       return ret;
+}
+
+
 /**
  * Construct a filtered list of options to pass through from the
  * client to the server.
@@ -2496,7 +2623,7 @@ void server_options(char **args, int *argc_p)
                        argstr[x++] = 'K';
                if (prune_empty_dirs)
                        argstr[x++] = 'm';
-               if (omit_dir_times)
+               if (omit_dir_times > 0)
                        argstr[x++] = 'O';
                if (omit_link_times)
                        argstr[x++] = 'J';
@@ -2529,7 +2656,7 @@ void server_options(char **args, int *argc_p)
                argstr[x++] = 'g';
        if (preserve_devices) /* ignore preserve_specials here */
                argstr[x++] = 'D';
-       if (preserve_times)
+       if (preserve_mtimes)
                argstr[x++] = 't';
        if (preserve_atimes) {
                argstr[x++] = 'U';
@@ -2590,9 +2717,7 @@ void server_options(char **args, int *argc_p)
                        set++;
                else
                        set = iconv_opt;
-               if (asprintf(&arg, "--iconv=%s", set) < 0)
-                       goto oom;
-               args[ac++] = arg;
+               args[ac++] = safe_arg("--iconv", set);
        }
 #endif
 
@@ -2658,33 +2783,24 @@ void server_options(char **args, int *argc_p)
        }
 
        if (backup_dir) {
+               /* This split idiom allows for ~/path expansion via the shell. */
                args[ac++] = "--backup-dir";
-               args[ac++] = backup_dir;
+               args[ac++] = safe_arg("", backup_dir);
        }
 
        /* Only send --suffix if it specifies a non-default value. */
-       if (strcmp(backup_suffix, backup_dir ? "" : BACKUP_SUFFIX) != 0) {
-               /* We use the following syntax to avoid weirdness with '~'. */
-               if (asprintf(&arg, "--suffix=%s", backup_suffix) < 0)
-                       goto oom;
-               args[ac++] = arg;
-       }
+       if (strcmp(backup_suffix, backup_dir ? "" : BACKUP_SUFFIX) != 0)
+               args[ac++] = safe_arg("--suffix", backup_suffix);
 
-       if (checksum_choice) {
-               if (asprintf(&arg, "--checksum-choice=%s", checksum_choice) < 0)
-                       goto oom;
-               args[ac++] = arg;
-       }
+       if (checksum_choice)
+               args[ac++] = safe_arg("--checksum-choice", checksum_choice);
 
        if (do_compression == CPRES_ZLIBX)
                args[ac++] = "--new-compress";
        else if (compress_choice && do_compression == CPRES_ZLIB)
                args[ac++] = "--old-compress";
-       else if (compress_choice) {
-               if (asprintf(&arg, "--compress-choice=%s", compress_choice) < 0)
-                       goto oom;
-               args[ac++] = arg;
-       }
+       else if (compress_choice)
+               args[ac++] = safe_arg("--compress-choice", compress_choice);
 
        if (am_sender) {
                if (max_delete > 0) {
@@ -2693,14 +2809,10 @@ void server_options(char **args, int *argc_p)
                        args[ac++] = arg;
                } else if (max_delete == 0)
                        args[ac++] = "--max-delete=-1";
-               if (min_size >= 0) {
-                       args[ac++] = "--min-size";
-                       args[ac++] = min_size_arg;
-               }
-               if (max_size >= 0) {
-                       args[ac++] = "--max-size";
-                       args[ac++] = max_size_arg;
-               }
+               if (min_size >= 0)
+                       args[ac++] = safe_arg("--min-size", min_size_arg);
+               if (max_size >= 0)
+                       args[ac++] = safe_arg("--max-size", max_size_arg);
                if (delete_before)
                        args[ac++] = "--delete-before";
                else if (delete_during == 2)
@@ -2724,17 +2836,12 @@ void server_options(char **args, int *argc_p)
                if (do_stats)
                        args[ac++] = "--stats";
        } else {
-               if (skip_compress) {
-                       if (asprintf(&arg, "--skip-compress=%s", skip_compress) < 0)
-                               goto oom;
-                       args[ac++] = arg;
-               }
+               if (skip_compress)
+                       args[ac++] = safe_arg("--skip-compress", skip_compress);
        }
 
-       if (max_alloc_arg && max_alloc != DEFAULT_MAX_ALLOC) {
-               args[ac++] = "--max-alloc";
-               args[ac++] = max_alloc_arg;
-       }
+       if (max_alloc_arg && max_alloc != DEFAULT_MAX_ALLOC)
+               args[ac++] = safe_arg("--max-alloc", max_alloc_arg);
 
        /* --delete-missing-args needs the cooperation of both sides, but
         * the sender can handle --ignore-missing-args by itself. */
@@ -2759,7 +2866,7 @@ void server_options(char **args, int *argc_p)
        if (partial_dir && am_sender) {
                if (partial_dir != tmp_partialdir) {
                        args[ac++] = "--partial-dir";
-                       args[ac++] = partial_dir;
+                       args[ac++] = safe_arg("", partial_dir);
                }
                if (delay_updates)
                        args[ac++] = "--delay-updates";
@@ -2782,17 +2889,11 @@ void server_options(char **args, int *argc_p)
                args[ac++] = "--use-qsort";
 
        if (am_sender) {
-               if (usermap) {
-                       if (asprintf(&arg, "--usermap=%s", usermap) < 0)
-                               goto oom;
-                       args[ac++] = arg;
-               }
+               if (usermap)
+                       args[ac++] = safe_arg("--usermap", usermap);
 
-               if (groupmap) {
-                       if (asprintf(&arg, "--groupmap=%s", groupmap) < 0)
-                               goto oom;
-                       args[ac++] = arg;
-               }
+               if (groupmap)
+                       args[ac++] = safe_arg("--groupmap", groupmap);
 
                if (ignore_existing)
                        args[ac++] = "--ignore-existing";
@@ -2803,9 +2904,12 @@ void server_options(char **args, int *argc_p)
 
                if (tmpdir) {
                        args[ac++] = "--temp-dir";
-                       args[ac++] = tmpdir;
+                       args[ac++] = safe_arg("", tmpdir);
                }
 
+               if (do_fsync)
+                       args[ac++] = "--fsync";
+
                if (basis_dir[0]) {
                        /* the server only needs this option if it is not the sender,
                         *   and it may be an older version that doesn't know this
@@ -2813,7 +2917,7 @@ void server_options(char **args, int *argc_p)
                         */
                        for (i = 0; i < basis_dir_cnt; i++) {
                                args[ac++] = alt_dest_opt(0);
-                               args[ac++] = basis_dir[i];
+                               args[ac++] = safe_arg("", basis_dir[i]);
                        }
                }
        }
@@ -2828,13 +2932,17 @@ void server_options(char **args, int *argc_p)
                if (append_mode > 1)
                        args[ac++] = "--append";
                args[ac++] = "--append";
-       } else if (inplace)
+       } else if (inplace) {
                args[ac++] = "--inplace";
+               /* Work around a bug in older rsync versions (on the remote side) for --inplace --sparse */
+               if (sparse_files && !whole_file && am_sender)
+                       args[ac++] = "--no-W";
+       }
 
        if (files_from && (!am_sender || filesfrom_host)) {
                if (filesfrom_host) {
                        args[ac++] = "--files-from";
-                       args[ac++] = files_from;
+                       args[ac++] = safe_arg("", files_from);
                        if (eol_nulls)
                                args[ac++] = "--from0";
                } else {
@@ -2856,6 +2964,9 @@ void server_options(char **args, int *argc_p)
        else if (remove_source_files)
                args[ac++] = "--remove-sent-files";
 
+       if (copy_devices && !am_sender)
+               args[ac++] = "--copy-devices";
+
        if (preallocate_files && am_sender)
                args[ac++] = "--preallocate";
 
@@ -2877,7 +2988,7 @@ void server_options(char **args, int *argc_p)
                        exit_cleanup(RERR_SYNTAX);
                }
                for (j = 1; j <= remote_option_cnt; j++)
-                       args[ac++] = (char*)remote_options[j];
+                       args[ac++] = safe_arg(SPLIT_ARG_WHEN_OLD, remote_options[j]);
        }
 
        *argc_p = ac;