+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, short 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 **verbosity = words == info_words ? info_verbosity : debug_verbosity;
+ char buf[128], *opt, *fmt = "%-10s %s\n";
+ int j;
+
+ reset_output_levels();
+
+ 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++) {
+ 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) {
+ 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, short 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;
+}