X-Git-Url: https://mattmccutchen.net/rsync/rsync.git/blobdiff_plain/d8d13893489ba8245d2ff1e67fbb5c46dd047ef6..951e826b75c4a4e6bc066e248d7489fb6eba6fde:/options.c diff --git a/options.c b/options.c index d534cec1..1bdea189 100644 --- a/options.c +++ b/options.c @@ -99,8 +99,6 @@ int recurse = 0; int allow_inc_recurse = 1; int xfer_dirs = -1; int am_daemon = 0; -int do_stats = 0; -int do_progress = 0; int connect_timeout = 0; int keep_partial = 0; int safe_symlinks = 0; @@ -178,7 +176,6 @@ static int remote_option_alloc = 0; int remote_option_cnt = 0; const char **remote_options = NULL; -int verbose = 0; int quiet = 0; int output_motd = 1; int log_before_transfer = 0; @@ -199,6 +196,93 @@ char *iconv_opt = ICONV_OPTION; struct chmod_mode_struct *chmod_modes = NULL; +static char *debug_verbosity[] = { + /*0*/ NULL, + /*1*/ NULL, + /*2*/ "bind,cmd,chksum,connect,del,dup,filter,flist", + /*3*/ "acl,backup,chksum2,del2,exit,filter2,flist2,fuzzy,genr,own,recv,send,time", + /*4*/ "cmd2,chksum3,del3,exit2,flist3,iconv,own2,proto,time2", + /*5*/ "chdir,chksum4,flist4,fuzzy2,hlink", +}; + +#define MAX_VERBOSITY ((int)(sizeof debug_verbosity / sizeof debug_verbosity[0]) - 1) + +static char *info_verbosity[MAX_VERBOSITY] = { + /*0*/ NULL, + /*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. */ + +short info_levels[COUNT_INFO], debug_levels[COUNT_DEBUG]; + +#define DEFAULT_PRIORITY 0 /* Default/implied/--verbose set values. */ +#define HELP_PRIORITY 1 /* The help output uses this level. */ +#define USER_PRIORITY 2 /* User-specified via --info or --debug */ +#define LIMIT_PRIORITY 3 /* Overriding priority when limiting values. */ + +#define W_CLI (1<<0) /* client side */ +#define W_SRV (1<<1) /* server side */ +#define W_SND (1<<2) /* sending side */ +#define W_REC (1<<3) /* receiving side */ + +struct output_struct { + char *name; /* The name of the info/debug flag. */ + char *help; /* The description of the info/debug flag. */ + short flag; /* The flag's value, for consistency check. */ + short where; /* Bits indicating where the flag is used. */ + short priority; /* See *_PRIORITY defines. */ +}; + +#define INFO_WORD(flag, where, help) { #flag, help, INFO_##flag, where, 0 } + +static struct output_struct info_words[] = { + INFO_WORD(BACKUP, W_REC, "Mention files backed up"), + 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 (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"), + 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"), + 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 } +}; + +#define DEBUG_WORD(flag, where, help) { #flag, help, DEBUG_##flag, where, 0 } + +static struct output_struct debug_words[] = { + DEBUG_WORD(ACL, W_SND|W_REC, "Debug extra ACL info"), + DEBUG_WORD(BACKUP, W_REC, "Debug backup actions (levels 1-2)"), + DEBUG_WORD(BIND, W_CLI, "Debug socket bind actions"), + DEBUG_WORD(CHDIR, W_CLI|W_SRV, "Debug when the current directory changes"), + DEBUG_WORD(CONNECT, W_CLI, "Debug connection events"), + DEBUG_WORD(CHKSUM, W_SND|W_REC, "Debug delta-transfer checksumming (levels 1-4)"), + DEBUG_WORD(CMD, W_CLI, "Debug commands+options that are issued (levels 1-2)"), + DEBUG_WORD(DEL, W_REC, "Debug delete actions (levels 1-3)"), + DEBUG_WORD(DUP, W_REC, "Debug weeding of duplicate names"), + DEBUG_WORD(EXIT, W_CLI|W_SRV, "Debug exit events (levels 1-2)"), + DEBUG_WORD(FILTER, W_SND|W_REC, "Debug filter actions (levels 1-2)"), + 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"), + DEBUG_WORD(HLINK, W_SND|W_REC, "Debug hard-link actions"), + DEBUG_WORD(ICONV, W_CLI|W_SRV, "Debug iconv (character conversion)"), + 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"), + DEBUG_WORD(SEND, W_SND, "Debug sender functions"), + DEBUG_WORD(TIME, W_REC, "Debug setting of modified times (levels 1-2)"), + { NULL, "--debug", 0, 0, 0 } +}; + +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 F_option_cnt = 0; @@ -216,6 +300,252 @@ static char tmp_partialdir[] = ".~tmp~"; * address, or a hostname. **/ char *bind_address; +static void output_item_help(struct output_struct *words); + +/* This constructs a string that represents all the options set for either + * the --info or --debug setting, skipping any implied options (by -v, etc.). + * This is used both when conveying the user's options to the server, and + * when the help output wants to tell the user what options are implied. */ +static char *make_output_option(struct output_struct *words, short *levels, short where) +{ + char *str = words == info_words ? "--info=" : "--debug="; + int j, counts[MAX_OUT_LEVEL+1], pos, skipped = 0, len = 0, max = 0, lev = 0; + int word_count = words == info_words ? COUNT_INFO : COUNT_DEBUG; + char *buf; + + memset(counts, 0, sizeof counts); + + for (j = 0; words[j].name; j++) { + if (words[j].flag != j) { + rprintf(FERROR, "rsync: internal error on %s%s: %d != %d\n", + words == info_words ? "INFO_" : "DEBUG_", + words[j].name, words[j].flag, j); + exit_cleanup(RERR_UNSUPPORTED); + } + if (!(words[j].where & where)) + continue; + if (words[j].priority == DEFAULT_PRIORITY) { + /* Implied items don't need to be mentioned. */ + skipped++; + continue; + } + len += len ? 1 : strlen(str); + len += strlen(words[j].name); + len += levels[j] == 1 ? 0 : 1; + + if (words[j].priority == HELP_PRIORITY) + continue; /* no abbreviating for help */ + + assert(levels[j] <= MAX_OUT_LEVEL); + if (++counts[levels[j]] > max) { + /* Determine which level has the most items. */ + lev = levels[j]; + max = counts[lev]; + } + } + + /* Sanity check the COUNT_* define against the length of the table. */ + if (j != word_count) { + rprintf(FERROR, "rsync: internal error: %s is wrong! (%d != %d)\n", + words == info_words ? "COUNT_INFO" : "COUNT_DEBUG", + j, word_count); + exit_cleanup(RERR_UNSUPPORTED); + } + + if (!len) + return NULL; + + len++; + if (!(buf = new_array(char, len))) + out_of_memory("make_output_option"); + pos = 0; + + if (skipped || max < 5) + lev = -1; + else { + if (lev == 0) + pos += snprintf(buf, len, "%sNONE", str); + else if (lev == 1) + pos += snprintf(buf, len, "%sALL", str); + else + pos += snprintf(buf, len, "%sALL%d", str, lev); + } + + for (j = 0; words[j].name && pos < len; j++) { + if (words[j].priority == DEFAULT_PRIORITY || levels[j] == lev || !(words[j].where & where)) + continue; + if (pos) + buf[pos++] = ','; + else + pos += strlcpy(buf+pos, str, len-pos); + if (pos < len) + pos += strlcpy(buf+pos, words[j].name, len-pos); + /* Level 1 is implied by the name alone. */ + if (levels[j] != 1 && pos < len) + buf[pos++] = '0' + levels[j]; + } + + buf[pos] = '\0'; + + return buf; +} + +static void parse_output_words(struct output_struct *words, short *levels, + const char *str, int priority) +{ + const char *s; + int j, len, lev; + + if (!str) + return; + + while (*str) { + if ((s = strchr(str, ',')) != NULL) + len = s++ - str; + else + len = strlen(str); + while (len && isDigit(str+len-1)) + len--; + lev = isDigit(str+len) ? atoi(str+len) : 1; + if (lev > MAX_OUT_LEVEL) + lev = MAX_OUT_LEVEL; + if (len == 4 && strncasecmp(str, "help", 4) == 0) { + output_item_help(words); + exit_cleanup(0); + } + if (len == 4 && strncasecmp(str, "none", 4) == 0) + len = lev = 0; + else if (len == 3 && strncasecmp(str, "all", 3) == 0) + len = 0; + for (j = 0; words[j].name; j++) { + if (!len + || (strncasecmp(str, words[j].name, len) == 0 && !words[j].name[len])) { + if (priority >= words[j].priority) { + words[j].priority = priority; + levels[j] = lev; + } + if (len) + break; + } + } + if (len && !words[j].name) { + rprintf(FERROR, "Unknown %s item: %.*s\n", words[j].help, len, str); + exit_cleanup(RERR_SYNTAX); + } + if (!s) + break; + str = s; + } +} + +/* Tell the user what all the info or debug flags mean. */ +static void output_item_help(struct output_struct *words) +{ + short *levels = words == info_words ? info_levels : debug_levels; + char buf[128], *opt, *fmt = "%-10s %s\n"; + int j; + + rprintf(FINFO, "Use OPT or OPT1 for level 1 output, OPT2 for level 2, etc.; OPT0 silences.\n"); + rprintf(FINFO, "\n"); + for (j = 0; words[j].name; j++) + rprintf(FINFO, fmt, words[j].name, words[j].help); + rprintf(FINFO, "\n"); + + snprintf(buf, sizeof buf, "Set all %s options (e.g. all%d)", + words[j].help, MAX_OUT_LEVEL); + rprintf(FINFO, fmt, "ALL", buf); + + snprintf(buf, sizeof buf, "Silence all %s options (same as all0)", + words[j].help); + rprintf(FINFO, fmt, "NONE", buf); + + rprintf(FINFO, fmt, "HELP", "Output this help message"); + rprintf(FINFO, "\n"); + rprintf(FINFO, "Options added for each increase in verbose level:\n"); + + for (j = 1; j <= MAX_VERBOSITY; j++) { + reset_output_levels(); + if (words == info_words) + parse_output_words(info_words, levels, info_verbosity[j], HELP_PRIORITY); + else + parse_output_words(debug_words, levels, debug_verbosity[j], HELP_PRIORITY); + opt = make_output_option(words, levels, W_CLI|W_SRV|W_SND|W_REC); + if (opt) { + rprintf(FINFO, "%d) %s\n", j, strchr(opt, '=')+1); + free(opt); + } + } + + reset_output_levels(); +} + +/* The --verbose option now sets info+debug flags. */ +static void set_output_verbosity(int level, int priority) +{ + int j; + + if (level > MAX_VERBOSITY) + level = MAX_VERBOSITY; + + for (j = 1; 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); + } +} + +/* Limit the info+debug flag levels given a verbose-option level limit. */ +void limit_output_verbosity(int level) +{ + short info_limits[COUNT_INFO], debug_limits[COUNT_DEBUG]; + int j; + + if (level > MAX_VERBOSITY) + return; + + memset(info_limits, 0, sizeof info_limits); + memset(debug_limits, 0, sizeof debug_limits); + + /* Compute the level limits in the above arrays. */ + for (j = 1; 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); + } + + for (j = 0; j < COUNT_INFO; j++) { + if (info_levels[j] > info_limits[j]) + info_levels[j] = info_limits[j]; + } + + for (j = 0; j < COUNT_DEBUG; j++) { + if (debug_levels[j] > debug_limits[j]) + debug_levels[j] = debug_limits[j]; + } +} + +void reset_output_levels(void) +{ + int j; + + memset(info_levels, 0, sizeof info_levels); + memset(debug_levels, 0, sizeof debug_levels); + + for (j = 0; j < COUNT_INFO; j++) + info_words[j].priority = DEFAULT_PRIORITY; + + for (j = 0; j < COUNT_DEBUG; j++) + debug_words[j].priority = DEFAULT_PRIORITY; +} + +void negate_output_levels(void) +{ + int j; + + for (j = 0; j < COUNT_INFO; j++) + info_levels[j] *= -1; + + for (j = 0; j < COUNT_DEBUG; j++) + debug_levels[j] *= -1; +} static void print_rsync_version(enum logcode f) { @@ -318,6 +648,8 @@ void usage(enum logcode F) rprintf(F,"\n"); rprintf(F,"Options\n"); rprintf(F," -v, --verbose increase verbosity\n"); + rprintf(F," --info=FLAGS fine-grained informational verbosity\n"); + rprintf(F," --debug=FLAGS fine-grained debug verbosity\n"); rprintf(F," -q, --quiet suppress non-error messages\n"); rprintf(F," --no-motd suppress daemon-mode MOTD (see manpage caveat)\n"); rprintf(F," -c, --checksum skip based on checksum, not mod-time & size\n"); @@ -451,7 +783,7 @@ enum {OPT_VERSION = 1000, OPT_DAEMON, OPT_SENDER, OPT_EXCLUDE, OPT_EXCLUDE_FROM, OPT_FILTER, OPT_COMPARE_DEST, OPT_COPY_DEST, OPT_LINK_DEST, OPT_HELP, OPT_INCLUDE, OPT_INCLUDE_FROM, OPT_MODIFY_WINDOW, OPT_MIN_SIZE, OPT_CHMOD, OPT_READ_BATCH, OPT_WRITE_BATCH, OPT_ONLY_WRITE_BATCH, OPT_MAX_SIZE, - OPT_NO_D, OPT_APPEND, OPT_NO_ICONV, + OPT_NO_D, OPT_APPEND, OPT_NO_ICONV, OPT_INFO, OPT_DEBUG, OPT_SERVER, OPT_REFUSED_BASE = 9000}; static struct poptOption long_options[] = { @@ -461,6 +793,8 @@ static struct poptOption long_options[] = { {"verbose", 'v', POPT_ARG_NONE, 0, 'v', 0, 0 }, {"no-verbose", 0, POPT_ARG_VAL, &verbose, 0, 0, 0 }, {"no-v", 0, POPT_ARG_VAL, &verbose, 0, 0, 0 }, + {"info", 0, POPT_ARG_STRING, 0, OPT_INFO, 0, 0 }, + {"debug", 0, POPT_ARG_STRING, 0, OPT_DEBUG, 0, 0 }, {"quiet", 'q', POPT_ARG_NONE, 0, 'q', 0, 0 }, {"motd", 0, POPT_ARG_VAL, &output_motd, 1, 0, 0 }, {"no-motd", 0, POPT_ARG_VAL, &output_motd, 0, 0, 0 }, @@ -1254,6 +1588,16 @@ int parse_arguments(int *argc_p, const char ***argv_p) } break; + case OPT_INFO: + arg = poptGetOptArg(pc); + parse_output_words(info_words, info_levels, arg, USER_PRIORITY); + break; + + case OPT_DEBUG: + arg = poptGetOptArg(pc); + parse_output_words(debug_words, debug_levels, arg, USER_PRIORITY); + break; + case OPT_HELP: usage(FINFO); exit_cleanup(0); @@ -1307,6 +1651,13 @@ int parse_arguments(int *argc_p, const char ***argv_p) exit_cleanup(0); } + set_output_verbosity(verbose, DEFAULT_PRIORITY); + + if (do_stats && !am_server) { + parse_output_words(info_words, info_levels, + verbose > 1 ? "stats3" : "stats2", DEFAULT_PRIORITY); + } + #ifdef ICONV_OPTION if (iconv_opt && protect_args != 2) { if (!am_server && strcmp(iconv_opt, "-") == 0) @@ -1523,7 +1874,7 @@ int parse_arguments(int *argc_p, const char ***argv_p) backup_dir_buf[backup_dir_len++] = '/'; backup_dir_buf[backup_dir_len] = '\0'; } - if (verbose > 1 && !am_sender) + if (INFO_GTE(BACKUP, 1) && !am_sender) rprintf(FINFO, "backup_dir is %s\n", backup_dir_buf); } else if (!backup_suffix_len && (!am_server || !am_sender)) { snprintf(err_buf, sizeof err_buf, @@ -1558,11 +1909,10 @@ int parse_arguments(int *argc_p, const char ***argv_p) log_before_transfer = !am_server; } - if (do_progress) { - if (am_server) - do_progress = 0; - else if (!verbose && !log_before_transfer && !am_server) - verbose = 1; + 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); } if (dry_run) @@ -1570,7 +1920,7 @@ int parse_arguments(int *argc_p, const char ***argv_p) set_io_timeout(io_timeout); - if (verbose && !stdout_format) { + if (INFO_GTE(NAME, 1) && !stdout_format) { stdout_format = "%n%L"; log_before_transfer = !am_server; } @@ -1733,6 +2083,7 @@ void server_options(char **args, int *argc_p) { static char argstr[64]; int ac = *argc_p; + short where; char *arg; int i, x; @@ -1860,13 +2211,13 @@ void server_options(char **args, int *argc_p) #endif } - argstr[x] = '\0'; - - if (x > (int)sizeof argstr) { /* Not possible... */ + if (x >= (int)sizeof argstr) { /* Not possible... */ rprintf(FERROR, "argstr overflow in server_options().\n"); exit_cleanup(RERR_MALLOC); } + argstr[x] = '\0'; + args[ac++] = argstr; #ifdef ICONV_OPTION @@ -2049,7 +2400,6 @@ void server_options(char **args, int *argc_p) * and it may be an older version that doesn't know this * option, so don't send it if client is the sender. */ - int i; for (i = 0; i < basis_dir_cnt; i++) { args[ac++] = dest_option; args[ac++] = basis_dir[i]; @@ -2057,6 +2407,16 @@ void server_options(char **args, int *argc_p) } } + /* What flags do we need to send to the other side? */ + where = (am_server ? W_CLI : W_SRV) | (am_sender ? W_REC : W_SND); + arg = make_output_option(info_words, info_levels, where); + if (arg) + args[ac++] = arg; + + arg = make_output_option(debug_words, debug_levels, where); + if (arg) + args[ac++] = arg; + if (append_mode) { if (append_mode > 1) args[ac++] = "--append";