extern int sanitize_paths;
extern int daemon_over_rsh;
extern unsigned int module_dirlen;
-extern struct filter_list_struct filter_list;
-extern struct filter_list_struct daemon_filter_list;
+extern filter_rule_list filter_list;
+extern filter_rule_list daemon_filter_list;
int make_backups = 0;
int am_starting_up = 1;
int relative_paths = -1;
int implied_dirs = 1;
+int missing_args = 0; /* 0 = FERROR_XFER, 1 = ignore, 2 = delete */
int numeric_ids = 0;
int msgs2stderr = 0;
int allow_8bit_chars = 0;
static const char *debug_verbosity[] = {
/*0*/ NULL,
/*1*/ NULL,
- /*2*/ "bind,cmd,connect,del,deltasum,dup,filter,flist,iconv",
- /*3*/ "acl,backup,deltasum2,del2,exit,filter2,flist2,fuzzy,genr,own,recv,send,time",
- /*4*/ "cmd2,deltasum3,del3,exit2,flist3,iconv2,own2,proto,time2",
- /*5*/ "chdir,deltasum4,flist4,fuzzy2,hash,hlink",
+ /*2*/ "BIND,CMD,CONNECT,DEL,DELTASUM,DUP,FILTER,FLIST,ICONV",
+ /*3*/ "ACL,BACKUP,DELTASUM2,DEL2,EXIT,FILTER2,FLIST2,FUZZY,GENR,OWN,RECV,SEND,TIME",
+ /*4*/ "CMD2,DELTASUM3,DEL3,EXIT2,FLIST3,ICONV2,OWN2,PROTO,TIME2",
+ /*5*/ "CHDIR,DELTASUM4,FLIST4,FUZZY2,HASH,HLINK",
};
#define MAX_VERBOSITY ((int)(sizeof debug_verbosity / sizeof debug_verbosity[0]) - 1)
static const char *info_verbosity[1+MAX_VERBOSITY] = {
/*0*/ NULL,
- /*1*/ "copy,del,flist,misc,name,stats,symsafe",
- /*2*/ "backup,mount,name2,remove,skip",
+ /*1*/ "COPY,DEL,FLIST,MISC,NAME,STATS,SYMSAFE",
+ /*2*/ "BACKUP,MISC2,MOUNT,NAME2,REMOVE,SKIP",
};
#define MAX_OUT_LEVEL 4 /* The largest N allowed for any flagN word. */
INFO_WORD(COPY, W_REC, "Mention files copied locally on the receiving side"),
INFO_WORD(DEL, W_REC, "Mention deletions on the receiving side"),
INFO_WORD(FLIST, W_CLI, "Mention file-list receiving/sending (levels 1-2)"),
- INFO_WORD(MISC, W_SND|W_REC, "Mention miscellaneous information"),
+ 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(PROGRESS, W_CLI, "Mention 1) per-file progress or 2) total transfer progress"),
DEBUG_WORD(HASH, W_SND|W_REC, "Debug hashtable code"),
DEBUG_WORD(HLINK, W_SND|W_REC, "Debug hard-link actions"),
DEBUG_WORD(ICONV, W_CLI|W_SRV, "Debug iconv character conversions (levels 1-2)"),
+ DEBUG_WORD(IO, W_CLI|W_SRV, "Debug I/O routines (levels 1-4)"),
DEBUG_WORD(OWN, W_REC, "Debug ownership changes in users & groups (levels 1-2)"),
DEBUG_WORD(PROTO, W_CLI|W_SRV, "Debug protocol information"),
DEBUG_WORD(RECV, W_REC, "Debug receiver functions"),
#ifdef ICONV_OPTION
iconv = "";
#endif
-#if defined HAVE_LUTIMES && defined HAVE_UTIMES
+#ifdef CAN_SET_SYMLINK_TIMES
symtimes = "";
#endif
rprintf(F," --delete-delay find deletions during, delete after\n");
rprintf(F," --delete-after receiver deletes after transfer, not during\n");
rprintf(F," --delete-excluded also delete excluded files from destination dirs\n");
+ rprintf(F," --ignore-missing-args ignore missing source args without error\n");
+ rprintf(F," --delete-missing-args delete missing source args from destination\n");
rprintf(F," --ignore-errors delete even if there are I/O errors\n");
rprintf(F," --force force deletion of directories even if not empty\n");
rprintf(F," --max-delete=NUM don't delete more than NUM files\n");
{"delete-delay", 0, POPT_ARG_VAL, &delete_during, 2, 0, 0 },
{"delete-after", 0, POPT_ARG_NONE, &delete_after, 0, 0, 0 },
{"delete-excluded", 0, POPT_ARG_NONE, &delete_excluded, 0, 0, 0 },
+ {"delete-missing-args",0,POPT_BIT_SET, &missing_args, 2, 0, 0 },
+ {"ignore-missing-args",0,POPT_BIT_SET, &missing_args, 1, 0, 0 },
{"remove-sent-files",0, POPT_ARG_VAL, &remove_source_files, 2, 0, 0 }, /* deprecated */
{"remove-source-files",0,POPT_ARG_VAL, &remove_source_files, 1, 0, 0 },
{"force", 0, POPT_ARG_VAL, &force_delete, 1, 0, 0 },
break;
case OPT_FILTER:
- parse_rule(&filter_list, poptGetOptArg(pc), 0, 0);
+ parse_filter_str(&filter_list, poptGetOptArg(pc),
+ rule_template(0), 0);
break;
case OPT_EXCLUDE:
- parse_rule(&filter_list, poptGetOptArg(pc),
- 0, XFLG_OLD_PREFIXES);
+ parse_filter_str(&filter_list, poptGetOptArg(pc),
+ rule_template(0), XFLG_OLD_PREFIXES);
break;
case OPT_INCLUDE:
- parse_rule(&filter_list, poptGetOptArg(pc),
- MATCHFLG_INCLUDE, XFLG_OLD_PREFIXES);
+ parse_filter_str(&filter_list, poptGetOptArg(pc),
+ rule_template(FILTRULE_INCLUDE), XFLG_OLD_PREFIXES);
break;
case OPT_EXCLUDE_FROM:
goto options_rejected;
}
parse_filter_file(&filter_list, arg,
- opt == OPT_INCLUDE_FROM ? MATCHFLG_INCLUDE : 0,
+ rule_template(opt == OPT_INCLUDE_FROM ? FILTRULE_INCLUDE : 0),
XFLG_FATAL_ERRORS | XFLG_OLD_PREFIXES);
break;
case 'F':
switch (++F_option_cnt) {
case 1:
- parse_rule(&filter_list,": /.rsync-filter",0,0);
+ parse_filter_str(&filter_list,": /.rsync-filter",rule_template(0),0);
break;
case 2:
- parse_rule(&filter_list,"- .rsync-filter",0,0);
+ parse_filter_str(&filter_list,"- .rsync-filter",rule_template(0),0);
break;
}
break;
set_output_verbosity(verbose, DEFAULT_PRIORITY);
- if (do_stats && !am_server) {
+ if (do_stats) {
parse_output_words(info_words, info_levels,
verbose > 1 ? "stats3" : "stats2", DEFAULT_PRIORITY);
}
"--read-batch cannot be used with --files-from\n");
return 0;
}
+ 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;
+ }
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",
list_only |= 1;
if (xfer_dirs >= 4) {
- parse_rule(&filter_list, "- /*/*", 0, 0);
+ parse_filter_str(&filter_list, "- /*/*", rule_template(0), 0);
recurse = xfer_dirs = 1;
} else if (recurse)
xfer_dirs = 1;
}
if (!xfer_dirs && delete_mode) {
snprintf(err_buf, sizeof err_buf,
- "--delete does not work without -r or -d.\n");
+ "--delete does not work without --recursive (-r) or --dirs (-d).\n");
return 0;
}
- if (delete_mode && refused_delete) {
+ 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;
}
backup_dir = sanitize_path(NULL, backup_dir, NULL, 0, SP_DEFAULT);
}
if (daemon_filter_list.head && !am_sender) {
- struct filter_list_struct *elp = &daemon_filter_list;
+ filter_rule_list *elp = &daemon_filter_list;
if (tmpdir) {
char *dir;
if (!*tmpdir)
return 0;
}
if (backup_dir) {
+ while (*backup_dir == '.' && backup_dir[1] == '/')
+ backup_dir += 2;
+ if (*backup_dir == '.' && backup_dir[1] == '\0')
+ backup_dir++;
backup_dir_len = strlcpy(backup_dir_buf, backup_dir, sizeof backup_dir_buf);
backup_dir_remainder = sizeof backup_dir_buf - backup_dir_len;
- if (backup_dir_remainder < 32) {
+ if (backup_dir_remainder < 128) {
snprintf(err_buf, sizeof err_buf,
"the --backup-dir path is WAY too long.\n");
return 0;
}
- if (backup_dir_buf[backup_dir_len - 1] != '/') {
+ if (!backup_dir_len) {
+ backup_dir_len = -1;
+ backup_dir = NULL;
+ } else if (backup_dir_buf[backup_dir_len - 1] != '/') {
backup_dir_buf[backup_dir_len++] = '/';
backup_dir_buf[backup_dir_len] = '\0';
}
- if (INFO_GTE(BACKUP, 1) && !am_sender)
- rprintf(FINFO, "backup_dir is %s\n", backup_dir_buf);
+ }
+ if (backup_dir) {
+ /* No need for a suffix or a protect rule. */
} else if (!backup_suffix_len && (!am_server || !am_sender)) {
snprintf(err_buf, sizeof err_buf,
- "--suffix cannot be a null string without --backup-dir\n");
+ "--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;
} else if (make_backups && delete_mode && !delete_excluded && !am_server) {
snprintf(backup_dir_buf, sizeof backup_dir_buf,
"P *%s", backup_suffix);
- parse_rule(&filter_list, backup_dir_buf, 0, 0);
+ parse_filter_str(&filter_list, backup_dir_buf, rule_template(0), 0);
}
if (make_backups && !backup_dir) {
argstr[x++] = '.';
if (allow_inc_recurse)
argstr[x++] = 'i';
-#if defined HAVE_LUTIMES && defined HAVE_UTIMES
+#ifdef CAN_SET_SYMLINK_TIMES
argstr[x++] = 'L';
#endif
#ifdef ICONV_OPTION
args[ac++] = "--super";
if (size_only)
args[ac++] = "--size-only";
+ if (do_stats)
+ args[ac++] = "--stats";
} else {
if (skip_compress) {
if (asprintf(&arg, "--skip-compress=%s", skip_compress) < 0)
}
}
+ /* --delete-missing-args needs the cooperation of both sides, but
+ * the sender can handle --ignore-missing-args by itself. */
+ if (missing_args == 2)
+ args[ac++] = "--delete-missing-args";
+ else if (missing_args == 1 && !am_sender)
+ args[ac++] = "--ignore-missing-args";
+
if (modify_window_set) {
if (asprintf(&arg, "--modify-window=%d", modify_window) < 0)
goto oom;
out_of_memory("server_options");
}
+/* If str points to a valid hostspec, return allocated memory containing the
+ * [USER@]HOST part of the string, and set the path_start_ptr to the part of
+ * the string after the host part. Otherwise, return NULL. If port_ptr is
+ * non-NULL, we must be parsing an rsync:// URL hostname, and we will set
+ * *port_ptr if a port number is found. Note that IPv6 IPs will have their
+ * (required for parsing) [ and ] chars elided from the returned string. */
+static char *parse_hostspec(char *str, char **path_start_ptr, int *port_ptr)
+{
+ char *s, *host_start = str;
+ int hostlen = 0, userlen = 0;
+ char *ret;
+
+ for (s = str; ; s++) {
+ if (!*s) {
+ /* It is only OK if we run out of string with rsync:// */
+ if (!port_ptr)
+ return NULL;
+ if (!hostlen)
+ hostlen = s - host_start;
+ break;
+ }
+ if (*s == ':' || *s == '/') {
+ if (!hostlen)
+ hostlen = s - host_start;
+ if (*s++ == '/') {
+ if (!port_ptr)
+ return NULL;
+ } else if (port_ptr) {
+ *port_ptr = atoi(s);
+ while (isDigit(s)) s++;
+ if (*s && *s++ != '/')
+ return NULL;
+ }
+ break;
+ }
+ if (*s == '@') {
+ userlen = s - str + 1;
+ host_start = s + 1;
+ } else if (*s == '[') {
+ if (s != host_start++)
+ return NULL;
+ while (*s && *s != ']' && *s != '/') s++; /*SHARED ITERATOR*/
+ hostlen = s - host_start;
+ if (*s != ']' || (s[1] && s[1] != '/' && s[1] != ':') || !hostlen)
+ return NULL;
+ }
+ }
+
+ *path_start_ptr = s;
+ ret = new_array(char, userlen + hostlen + 1);
+ if (userlen)
+ strlcpy(ret, str, userlen + 1);
+ strlcpy(ret + userlen, host_start, hostlen + 1);
+ return ret;
+}
+
/* Look for a HOST specfication of the form "HOST:PATH", "HOST::PATH", or
* "rsync://HOST:PORT/PATH". If found, *host_ptr will be set to some allocated
* memory with the HOST. If a daemon-accessing spec was specified, the value
* "[::ffff:127.0.0.1]") which is returned without the '[' and ']'. */
char *check_for_hostspec(char *s, char **host_ptr, int *port_ptr)
{
- char *p;
- int not_host;
- int hostlen;
+ char *path;
if (port_ptr && strncasecmp(URL_PREFIX, s, strlen(URL_PREFIX)) == 0) {
- char *path;
- s += strlen(URL_PREFIX);
- if ((p = strchr(s, '/')) != NULL) {
- hostlen = p - s;
- path = p + 1;
- } else {
- hostlen = strlen(s);
- path = "";
+ *host_ptr = parse_hostspec(s + strlen(URL_PREFIX), &path, port_ptr);
+ if (*host_ptr) {
+ if (!*port_ptr)
+ *port_ptr = RSYNC_PORT;
+ return path;
}
- if (*s == '[' && (p = strchr(s, ']')) != NULL) {
- s++;
- hostlen = p - s;
- if (p[1] == ':')
- *port_ptr = atoi(p+2);
- } else {
- if ((p = strchr(s, ':')) != NULL && p < s + hostlen) {
- hostlen = p - s;
- *port_ptr = atoi(p+1);
- }
- }
- if (!*port_ptr)
- *port_ptr = RSYNC_PORT;
- *host_ptr = new_array(char, hostlen + 1);
- strlcpy(*host_ptr, s, hostlen + 1);
- return path;
- }
-
- if (*s == '[' && (p = strchr(s, ']')) != NULL && p[1] == ':') {
- s++;
- hostlen = p - s;
- *p = '\0';
- not_host = strchr(s, '/') || !strchr(s, ':');
- *p = ']';
- if (not_host)
- return NULL;
- p++;
- } else {
- if (!(p = strchr(s, ':')))
- return NULL;
- hostlen = p - s;
- *p = '\0';
- not_host = strchr(s, '/') != NULL;
- *p = ':';
- if (not_host)
- return NULL;
}
- *host_ptr = new_array(char, hostlen + 1);
- strlcpy(*host_ptr, s, hostlen + 1);
+ *host_ptr = parse_hostspec(s, &path, NULL);
+ if (!*host_ptr)
+ return NULL;
- if (p[1] == ':') {
+ if (*path == ':') {
if (port_ptr && !*port_ptr)
*port_ptr = RSYNC_PORT;
- return p + 2;
+ return path + 1;
}
if (port_ptr)
*port_ptr = 0;
- return p + 1;
+ return path;
}