From 591eb30854b5c4e5fb16bd6111328763ec2455ed Mon Sep 17 00:00:00 2001 From: Volkan Unal Date: Wed, 10 Dec 2025 16:29:01 +0000 Subject: [PATCH] lschp: Support for structured output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce a new command line flag ("--format") to specify the output format. Valid options are "pairs" for shell-compatible key value pairs, "csv" for CSV, "json" for a formatted JSON document, and "json-seq" for a stream of JSON text sequences as per RFC7464[1]. [1]: https://datatracker.ietf.org/doc/html/rfc7464 Signed-off-by: Volkan Unal Reviewed-by: Peter Oberparleiter Signed-off-by: Jan Höppner --- zconf/chp/lschp.8 | 73 ++++++++++++++++++++++++++++++++ zconf/chp/lschp.c | 103 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 173 insertions(+), 3 deletions(-) diff --git a/zconf/chp/lschp.8 b/zconf/chp/lschp.8 index 9c29f84e..dbb290e4 100644 --- a/zconf/chp/lschp.8 +++ b/zconf/chp/lschp.8 @@ -11,6 +11,8 @@ lschp \- list information about available channel\-paths. .B lschp .RB [ \-h|\-\-help ] .RB [ \-v|\-\-version ] +.RB [ \-\-format +.IR FORMAT ] .RB [ .I CHPID ] @@ -108,6 +110,77 @@ Print a short help text, then exit. Print version number, then exit. .RE +.br +.B \-\-format \fIFORMAT\fR +.RS +Specify the output format. Supported formats include +\fIjson\fR, \fIjson-seq\fR, \fIpairs\fR and \fIcsv\fR. +See section "OUTPUT FORMAT" for more details. +.RE + + +.SH "OUTPUT FORMAT" +This section contains additional information for json output format. + +.SS json + +JSON output consists of a top-level object with the following properties +(key-value pairs): + +.IP \(bu 3 +.BR meta : +Tool meta-data including API level, version, host name, and time of invocation +.PP +.IP \(bu 3 +.BR channel-paths : +Information about available Channel\-path +.PP + +Example JSON output for channel-path with all properties: + +.nf +{ + "meta": { + "api_level": 1, + "version": "2.39.0-build-20260119", + "host": "localhost", + "time_epoch": 1769092556, + "time": "2026-01-22 15:35:56+0100" + }, + "channel_paths": [ + { + "chpid": "0.32", + "vary": 1, + "cfg": 1, + "type": 27, + "cmg": 2, + "shared": 1, + "chid": 576, + "chid_external": true + }, + { + "chpid": "0.34", + "vary": 1, + "cfg": 0, + "type": 27, + "cmg": null, + "shared": null, + "chid": 608, + "chid_external": true + }, + { + "chpid": "0.f8", + "vary": 1, + "cfg": 1, + "type": 36, + "cmg": 3, + "shared": 1, + "chid": 2040, + "chid_external": false + } + ] +} +.fi .SH SEE ALSO .BR chchp (8) diff --git a/zconf/chp/lschp.c b/zconf/chp/lschp.c index ce77fed3..0165176c 100644 --- a/zconf/chp/lschp.c +++ b/zconf/chp/lschp.c @@ -16,6 +16,7 @@ #include "lib/util_base.h" #include "lib/util_file.h" +#include "lib/util_fmt.h" #include "lib/util_opt.h" #include "lib/util_path.h" #include "lib/util_prg.h" @@ -31,6 +32,11 @@ #define CHP_CMG "cmg" #define CHP_ID "chpid" +static enum util_fmt_t fmt; +static bool fmt_specified; + +#define OPT_FORMAT 262 /* --format */ + /* * Program configuration */ @@ -52,9 +58,85 @@ static const struct util_prg prg = { static struct util_opt opt_vec[] = { UTIL_OPT_HELP, UTIL_OPT_VERSION, + { + .option = { "format", required_argument, NULL, OPT_FORMAT}, + .argument = "FORMAT", + .desc = "Output format (" FMT_TYPE_NAMES ")", + .flags = UTIL_OPT_FLAG_NOSHORT, + }, UTIL_OPT_END }; +/** + * Set the "--format" option + * + * @param[in] str Format for structured output + */ +static void fmt_set(const char *str) +{ + if (!util_fmt_name_to_type(str, &fmt)) { + errx(EXIT_FAILURE, "Unknown format '%s', supported formats: %s\n", str, + FMT_TYPE_NAMES); + } +} + +/** + * Print buffer structure as formatted + * + * @param[in] rec The buffer structure, where results are written to + */ +static void fmt_rec_print_formatted(struct util_rec *rec) +{ + const char *str_val, *key; + struct util_rec_fld *fld; + bool is_chid_external; + int ival = -1; + + util_fmt_obj_start(FMT_ROW, "entry"); + util_list_iterate(__util_rec_get_list(rec), fld) { + key = util_rec_fld_get_key(fld); + str_val = util_rec_get(rec, key); + if (!strcmp(str_val, "-")) { + util_fmt_pair(FMT_QUOTE | FMT_INVAL, key, ""); + } else if (!strcmp(key, CHP_PCHID)) { + is_chid_external = (str_val[0] != '('); + sscanf(str_val, is_chid_external ? "%x" : "(%x)", &ival); + util_fmt_pair(FMT_DEFAULT, "chid", "%d", ival); + util_fmt_pair(FMT_DEFAULT, "chid_external", "%s", is_chid_external ? + "true" : "false"); + } else if (!strcmp(key, CHP_ID)) { + util_fmt_pair(FMT_DEFAULT | FMT_QUOTE, key, "%s", str_val); + } else if (!strcmp(key, CHP_TYPE)) { + sscanf(str_val, "%x", &ival); + util_fmt_pair(FMT_DEFAULT, key, "%d", ival); + } else { + ival = atoi(str_val); + util_fmt_pair(FMT_DEFAULT, key, "%d", ival); + } + } + util_fmt_obj_end(); /* entry */ +} + +static void fmt_init(void) +{ + unsigned int flags = FMT_HANDLEINT | FMT_KEEPINVAL; + + if (!fmt_specified) + return; + if (fmt == FMT_CSV) + flags |= FMT_NOMETA | FMT_QUOTEALL; + else + flags |= FMT_DEFAULT; + util_fmt_init(stdout, fmt, flags, 1); +} + +static void fmt_end(void) +{ + if (!fmt_specified) + return; + util_fmt_exit(); +} + /** * Read all attributes of a desired directory * @@ -133,8 +215,12 @@ static void print_chpid(const char *chp_dir, unsigned int css_id, } else { util_rec_set(rec, CHP_PCHID, "%s", "-"); } - if (!strlen(chp) || strcmp(util_rec_get(rec, CHP_ID), chp) == 0) - util_rec_print(rec); + if (!strlen(chp) || strcmp(util_rec_get(rec, CHP_ID), chp) == 0) { + if (fmt_specified) + fmt_rec_print_formatted(rec); + else + util_rec_print(rec); + } free(path); } @@ -196,6 +282,7 @@ static void cmd_lschp(char *chp) int i, count; char *path; + fmt_init(); rec = util_rec_new_wide("="); util_rec_def(rec, CHP_ID, UTIL_REC_ALIGN_LEFT, 6, "CHPID"); util_rec_def(rec, CHP_STATE, UTIL_REC_ALIGN_LEFT, 5, "Vary"); @@ -205,17 +292,23 @@ static void cmd_lschp(char *chp) util_rec_def(rec, CHP_SHARED, UTIL_REC_ALIGN_LEFT, 6, "Shared"); util_rec_def(rec, CHP_PCHID, UTIL_REC_ALIGN_LEFT, 6, " PCHID"); - util_rec_print_hdr(rec); + if (!fmt_specified) + util_rec_print_hdr(rec); /* * Iterate over each "/sys/devices/css.*" */ path = util_path_sysfs("devices"); count = util_scandir(&de_vec, alphasort, path, "^css[[:xdigit:]]{1,2}$"); + if (fmt_specified) + util_fmt_obj_start(FMT_LIST, "channel_paths"); for (i = 0; i < count; i++) print_css(de_vec[i]->d_name, rec, chp); + if (fmt_specified) + util_fmt_obj_end(); /* channel_paths */ util_ptr_vec_free((void **) de_vec, count); free(path); util_rec_free(rec); + fmt_end(); } #define CHP_LEN 4 @@ -242,6 +335,10 @@ int main(int argc, char *argv[]) case 'v': util_prg_print_version(); return EXIT_SUCCESS; + case OPT_FORMAT: + fmt_specified = true; + fmt_set(argv[optind - 1]); + break; default: util_opt_print_parse_error(c, argv); return EXIT_FAILURE;