lschp: Support for structured output

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 <vunal@linux.ibm.com>
Reviewed-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
This commit is contained in:
Volkan Unal
2025-12-10 16:29:01 +00:00
committed by Jan Höppner
parent 3083f5dc85
commit 591eb30854
2 changed files with 173 additions and 3 deletions

View File

@@ -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)

View File

@@ -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;