From 0efa63f2e601808cae02313311a7331af66a2313 Mon Sep 17 00:00:00 2001 From: Wayne Davison Date: Sat, 10 Sep 2022 11:07:01 -0700 Subject: [PATCH] Use JSON output if --version (-V) is repeated (client side only). --- NEWS.md | 8 +++ options.c | 2 +- rsync.1.md | 13 ++-- support/json-rsync-version | 59 ++++++++++++++++++ usage.c | 119 +++++++++++++++++++++++++++++-------- 5 files changed, 170 insertions(+), 31 deletions(-) create mode 100755 support/json-rsync-version diff --git a/NEWS.md b/NEWS.md index 3535e21b..81d66b63 100644 --- a/NEWS.md +++ b/NEWS.md @@ -17,6 +17,14 @@ overly-long checksums are at the lowest priority in the normal checksum negotation list. +- If the `--version` option is repeated (e.g. `-VV`) then the information is + output in a (still human-readable) JSON format (client side only). + +- The script `support/json-rsync-version` is available to get the JSON style + version output from any rsync. The script accepts the version output on + stdin **or** the name of an rsync to run as an arg. If the text isn't + already in JSON format, the text is translated into equivalent JSON. + ### PACKAGING RELATED: - The checksum code now uses openssl's EVP methods, which gets rid of various diff --git a/options.c b/options.c index 3f8d5d08..d38bbe8d 100644 --- a/options.c +++ b/options.c @@ -1926,7 +1926,7 @@ int parse_arguments(int *argc_p, const char ***argv_p) 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); } diff --git a/rsync.1.md b/rsync.1.md index bd9182a7..7271839d 100644 --- a/rsync.1.md +++ b/rsync.1.md @@ -580,11 +580,14 @@ expand it. 0. `--version`, `-V` - Print the rsync version plus other info and exit. - - The output includes the default list of checksum algorithms, the default - list of compression algorithms, a list of compiled-in capabilities, a link - to the rsync web site, and some license/copyright info. + Print the rsync version plus other info and exit. When repeated, the + information is output is a JSON format that is still hum-readable (client + side only). + + The output includes a list of compiled-in capabilities, a list of + optimizations, the default list of checksum algorithms, the default list of + compression algorithms, the default list of daemon auth digests, a link to + the rsync web site, and a few other items. 0. `--verbose`, `-v` diff --git a/support/json-rsync-version b/support/json-rsync-version new file mode 100755 index 00000000..79050e48 --- /dev/null +++ b/support/json-rsync-version @@ -0,0 +1,59 @@ +#!/usr/bin/python3 + +import sys, re, argparse, subprocess, json + +def main(): + if not args.rsync or args.rsync == '-': + ver_out = sys.stdin.read().strip() + else: + ver_out = subprocess.check_output([args.rsync, '--version', '--version'], encoding='utf-8').strip() + if ver_out.startswith('{'): + print(ver_out) + return + info = { } + for line in ver_out.splitlines(): + if line.startswith('rsync '): + prog, vstr, ver, pstr, vstr2, proto = line.split() + info['program'] = prog + if ver.startswith('v'): + ver = ver[1:] + info[vstr] = ver + if '.' not in proto: + proto += '.0' + else: + proto = proto.replace('.PR', '.') + info[pstr] = proto + elif line.startswith('Copyright '): + info['copyright'] = line[10:] + elif line.startswith('Web site: '): + info['url'] = line[10:] + elif line.startswith(' '): + if not saw_comma and ',' in line: + saw_comma = True + if saw_comma: + lst = line.strip(' ,').split(', ') + else: + lst = [ x for x in line.split() if not x.startswith('(') ] + info[sect_name] += lst + elif line == '': + break + else: + sect_name = line.strip(" \n:").replace(' ', '_').lower() + info[sect_name] = [ ] + saw_comma = False + for chk in 'checksum_list compress_list daemon_auth_list'.split(): + if chk not in info: + info[chk] = [ ] + info['license'] = 'GPL3' + info['caveat'] = 'rsync comes with ABSOLUTELY NO WARRANTY' + print(json.dumps(info)) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description="Output rsync's version data in JSON format, even if the rsync doesn't support a native json-output method.", add_help=False) + parser.add_argument('rsync', nargs='?', help="Specify an rsync command to run. Otherwise stdin is consumed.") + parser.add_argument("--help", "-h", action="help", help="Output this help message and exit.") + args = parser.parse_args() + main() + +# vim: sw=4 et diff --git a/usage.c b/usage.c index 253f6660..dc66288f 100644 --- a/usage.c +++ b/usage.c @@ -22,6 +22,7 @@ #include "latest-year.h" #include "git-version.h" #include "default-cvsignore.h" +#include "itypes.h" extern struct name_num_obj valid_checksums, valid_compressions, valid_auth_checksums; @@ -36,7 +37,8 @@ static char *istring(const char *fmt, int val) static void print_info_flags(enum logcode f) { STRUCT_STAT *dumstat; - char line_buf[75]; + BOOL as_json = f == FNONE ? 1 : 0; /* We use 1 == first attribute, 2 == need closing array */ + char line_buf[75], *quot = as_json ? "\"" : ""; int line_len, j; char *info_flags[] = { @@ -163,50 +165,115 @@ static void print_info_flags(enum logcode f) for (line_len = 0, j = 0; ; j++) { char *str = info_flags[j], *next_nfo = str ? info_flags[j+1] : NULL; - int str_len = str && *str != '*' ? strlen(str) : 1000; + int str_len = str && *str != '*' ? strlen(str) + (as_json ? 2 : 0) : 1000; int need_comma = next_nfo && *next_nfo != '*' ? 1 : 0; if (line_len && line_len + 1 + str_len + need_comma >= (int)sizeof line_buf) { - rprintf(f, " %s\n", line_buf); + if (as_json) + printf(" %s\n", line_buf); + else + rprintf(f, " %s\n", line_buf); line_len = 0; } if (!str) break; if (*str == '*') { - rprintf(f, "%s:\n", str+1); + if (as_json) { + if (as_json == 2) + printf(" ]"); + else + as_json = 2; + printf(",\n \"%c%s\": [\n", toLower(str+1), str+2); + } else + rprintf(f, "%s:\n", str+1); continue; } - line_len += snprintf(line_buf+line_len, sizeof line_buf - line_len, " %s%s", str, need_comma ? "," : ""); + line_len += snprintf(line_buf+line_len, sizeof line_buf - line_len, + " %s%s%s%s", quot, str, quot, need_comma ? "," : ""); } + if (as_json == 2) + printf(" ]"); } -void print_rsync_version(enum logcode f) +static void output_nno_list(enum logcode f, const char *name, struct name_num_obj *nno) { - char tmpbuf[256], *subprotocol = ""; + char namebuf[64], tmpbuf[256]; + char *tok, *next_tok, *comma = ","; + char *cp; + + /* Using '(' ensures that we get a trailing "none" but also includes aliases. */ + get_default_nno_list(nno, tmpbuf, sizeof tmpbuf - 1, '('); + if (f != FNONE) { + rprintf(f, "%s:\n", name); + rprintf(f, " %s\n", tmpbuf); + return; + } + + strlcpy(namebuf, name, sizeof namebuf); + for (cp = namebuf; *cp; cp++) { + if (*cp == ' ') + *cp = '_'; + else if (isUpper(cp)) + *cp = toLower(cp); + } + + printf(",\n \"%s\": [\n ", namebuf); + + for (tok = strtok(tmpbuf, " "); tok; tok = next_tok) { + next_tok = strtok(NULL, " "); + if (*tok != '(') /* Ignore the alises in the JSON output */ + printf(" \"%s\"%s", tok, comma + (next_tok ? 0 : 1)); + } + + printf("\n ]"); +} +/* A request of f == FNONE wants json on stdout. */ +void print_rsync_version(enum logcode f) +{ + char copyright[] = "(C) 1996-" LATEST_YEAR " by Andrew Tridgell, Wayne Davison, and others."; + char url[] = "https://rsync.samba.org/"; + BOOL first_line = 1; + +#define json_line(name, value) \ + do { \ + printf("%c\n \"%s\": \"%s\"", first_line ? '{' : ',', name, value); \ + first_line = 0; \ + } while (0) + + if (f == FNONE) { + char verbuf[32]; + json_line("program", RSYNC_NAME); + json_line("version", rsync_version()); + snprintf(verbuf, sizeof verbuf, "%d.%d", PROTOCOL_VERSION, SUBPROTOCOL_VERSION); + json_line("protocol", verbuf); + json_line("copyright", copyright); + json_line("url", url); + } else { #if SUBPROTOCOL_VERSION != 0 - subprotocol = istring(".PR%d", SUBPROTOCOL_VERSION); + char *subprotocol = istring(".PR%d", SUBPROTOCOL_VERSION); +#else + char *subprotocol = ""; #endif - rprintf(f, "%s version %s protocol version %d%s\n", - RSYNC_NAME, rsync_version(), PROTOCOL_VERSION, subprotocol); - - rprintf(f, "Copyright (C) 1996-" LATEST_YEAR " by Andrew Tridgell, Wayne Davison, and others.\n"); - rprintf(f, "Web site: https://rsync.samba.org/\n"); + rprintf(f, "%s version %s protocol version %d%s\n", + RSYNC_NAME, rsync_version(), PROTOCOL_VERSION, subprotocol); + rprintf(f, "Copyright %s\n", copyright); + rprintf(f, "Web site: %s\n", url); + } print_info_flags(f); init_checksum_choices(); - rprintf(f, "Checksum list:\n"); - get_default_nno_list(&valid_checksums, tmpbuf, sizeof tmpbuf, '('); - rprintf(f, " %s\n", tmpbuf); + output_nno_list(f, "Checksum list", &valid_checksums); + output_nno_list(f, "Compress list", &valid_compressions); + output_nno_list(f, "Daemon auth list", &valid_auth_checksums); - rprintf(f, "Compress list:\n"); - get_default_nno_list(&valid_compressions, tmpbuf, sizeof tmpbuf, '('); - rprintf(f, " %s\n", tmpbuf); - - rprintf(f, "Daemon auth list:\n"); - get_default_nno_list(&valid_auth_checksums, tmpbuf, sizeof tmpbuf, '('); - rprintf(f, " %s\n", tmpbuf); + if (f == FNONE) { + json_line("license", "GPL3"); + json_line("caveat", "rsync comes with ABSOLUTELY NO WARRANTY"); + printf("\n}\n"); + return; + } #ifdef MAINTAINER_MODE rprintf(f, "Panic Action: \"%s\"\n", get_panic_action()); @@ -268,11 +335,13 @@ void daemon_usage(enum logcode F) const char *rsync_version(void) { + char *ver; #ifdef RSYNC_GITVER - return RSYNC_GITVER; + ver = RSYNC_GITVER; #else - return RSYNC_VERSION; + ver = RSYNC_VERSION; #endif + return *ver == 'v' ? ver+1 : ver; } const char *default_cvsignore(void) -- 2.34.1