From 58619fc534b60e50a16a75dcf3883dbe1a859cef Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Thu, 8 Jan 2026 14:36:55 +0100 Subject: [PATCH] bin: make DST in copy command optional and use in CLI Since sysrepocfg does not do any NACM based on the UNIX user, we expand the scope of the copy tool slightly to allow outputting JSON to stdout. This allows us to replace all sysrepocfg commands in the CLI used to show runnining/startup/factory. Signed-off-by: Joachim Wiberg --- src/bin/copy.c | 46 +++++++++++++++++++++++----- src/klish-plugin-infix/xml/infix.xml | 6 ++-- 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/src/bin/copy.c b/src/bin/copy.c index f84b6b20..d542568b 100644 --- a/src/bin/copy.c +++ b/src/bin/copy.c @@ -158,6 +158,16 @@ static bool is_uri(const char *str) return strstr(str, "://") != NULL; } +static bool is_stdout(const char *path) +{ + if (!path) + return 1; + + return !strcmp(path, "-") || + !strcmp(path, "/dev/stdout") || + !strcmp(path, "/dev/fd/1"); +} + static char *mktmp(void) { mode_t oldmask; @@ -413,6 +423,23 @@ static int curl_download(const char *uri, const char *dstpath) return 0; } +static int cat(const char *srcpath) +{ + char *argv[] = { "cat", NULL, NULL }; + int err; + + argv[1] = strdup(srcpath); + if (!argv[1]) + return 1; + + err = subprocess(argv); + if (err) + warnx("failed writing to stdout, exit code %d", err); + + free(argv[1]); + return err; +} + static int cp(const char *srcpath, const char *dstpath) { char *argv[] = { @@ -441,6 +468,8 @@ static int put(const char *srcpath, const char *dst, if (ds) err = sysrepo_import(ds, srcpath); + else if (is_stdout(dst)) + err = cat(srcpath); else if (is_uri(dst)) err = curl_upload(srcpath, dst); @@ -494,6 +523,9 @@ static int resolve_src(const char **src, const struct infix_ds **ds, char **path static int resolve_dst(const char **dst, const struct infix_ds **ds, char **path) { + if (is_stdout(*dst) || is_uri(*dst)) + return 0; + *dst = infix_ds(*dst, ds); if (*ds) { @@ -506,8 +538,6 @@ static int resolve_dst(const char **dst, const struct infix_ds **ds, char **path return 0; *path = strdup((*ds)->path); - } else if (is_uri(*dst)) { - return 0; } else { *path = cfg_adjust(*dst, NULL, sanitize); } @@ -536,7 +566,7 @@ static int copy(const char *src, const char *dst) /* rw for user and group only */ oldmask = umask(0006); - if (!strcmp(src, dst)) { + if (dst && !strcmp(src, dst)) { warn("source and destination are the same, aborting."); goto err; } @@ -562,7 +592,8 @@ err: if (rmsrc) rmtmp(srcpath); - free(dstpath); + if (dstpath) + free(dstpath); free(srcpath); sync(); @@ -572,7 +603,7 @@ err: static int usage(int rc) { - printf("Usage: %s [OPTIONS] SRC DST\n" + printf("Usage: %s [OPTIONS] SRC [DST]\n" "\n" "Options:\n" " -h This help text\n" @@ -584,7 +615,8 @@ static int usage(int rc) "\n" "Files:\n" " SRC JSON configuration file, or a datastore\n" - " DST A file or datastore, except factory-config\n" + " DST Optiional file or datastore, except factory-config,\n" + " when omitted output goes to stdout\n" "\n" "Datastores:\n" " running-config The running datastore, current active config\n" @@ -627,7 +659,7 @@ int main(int argc, char *argv[]) if (timeout < 0) timeout = 120; - if (argc - optind != 2) + if (argc - optind < 1) return usage(1); src = argv[optind++]; diff --git a/src/klish-plugin-infix/xml/infix.xml b/src/klish-plugin-infix/xml/infix.xml index 38676fbc..75f1cc68 100644 --- a/src/klish-plugin-infix/xml/infix.xml +++ b/src/klish-plugin-infix/xml/infix.xml @@ -628,15 +628,15 @@ - jq -C . /etc/factory-config.cfg |pager + copy factory | jq -C . |pager - sysrepocfg -X -f json | jq -C . |pager + copy running | jq -C . |pager - jq -C . /cfg/startup-config.cfg |pager + copy startup | jq -C . |pager