From ef1b1b1d1a2ef77b4afb5ca1da71017f69405eeb Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 16 Oct 2024 06:02:49 +0200 Subject: [PATCH 1/4] statd: add missing dependency Signed-off-by: Joachim Wiberg --- package/statd/statd.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/statd/statd.mk b/package/statd/statd.mk index f71da3161..9efd8515e 100644 --- a/package/statd/statd.mk +++ b/package/statd/statd.mk @@ -10,7 +10,7 @@ STATD_SITE = $(BR2_EXTERNAL_INFIX_PATH)/src/statd STATD_LICENSE = BSD-3-Clause STATD_LICENSE_FILES = LICENSE STATD_REDISTRIBUTE = NO -STATD_DEPENDENCIES = sysrepo libev libsrx jansson python-statd libyang +STATD_DEPENDENCIES = sysrepo libev libsrx jansson python-statd libyang libite STATD_AUTORECONF = YES define STATD_CONF_ENV From 886d83933fe5a89f6b6b8af261fde9e397e3dc95 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Tue, 15 Oct 2024 14:38:01 +0200 Subject: [PATCH 2/4] keyack: minor, whitespace and constification Signed-off-by: Joachim Wiberg --- src/keyack/keyack.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/keyack/keyack.c b/src/keyack/keyack.c index d28d18639..819bf3aa3 100644 --- a/src/keyack/keyack.c +++ b/src/keyack/keyack.c @@ -43,7 +43,7 @@ bool wait_key_state(FILE *fp, int code, int val) int main(int argc, char **argv) { - char *dev = "/dev/input/event0"; + const char *dev = "/dev/input/event0"; int opt, code = KEY_RESTART; bool success; FILE *fp; @@ -59,7 +59,6 @@ int main(int argc, char **argv) case 'k': code = strtol(optarg, NULL, 0); break; - default: fprintf(stderr, "unknown option '%c'\n", opt); usage(); exit(1); From 464e0bcb92dd36ae259d1e695e1f0ed13bfa9887 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 16 Oct 2024 06:04:28 +0200 Subject: [PATCH 3/4] bin: factor out copy, erase, and files from klish-plugin-infix Relocate C code from klish-plugin-infix to stand-alone binaries we can use also from Bash. Signed-off-by: Joachim Wiberg --- package/Config.in | 3 + package/bin/Config.in | 8 + package/bin/bin.hash | 1 + package/bin/bin.mk | 21 ++ src/Makefile | 2 +- src/bin/.gitignore | 31 +++ src/bin/LICENSE | 13 + src/bin/Makefile.am | 22 ++ src/bin/TODO | 2 + src/bin/autogen.sh | 3 + src/bin/check.mk | 8 + src/bin/configure.ac | 70 +++++ src/bin/copy.c | 347 +++++++++++++++++++++++ src/bin/erase.c | 76 +++++ src/bin/files.c | 80 ++++++ src/bin/util.c | 115 ++++++++ src/bin/util.h | 17 ++ src/klish-plugin-infix/src/infix.c | 434 +---------------------------- 18 files changed, 823 insertions(+), 430 deletions(-) create mode 100644 package/bin/Config.in create mode 100644 package/bin/bin.hash create mode 100644 package/bin/bin.mk create mode 100644 src/bin/.gitignore create mode 100644 src/bin/LICENSE create mode 100644 src/bin/Makefile.am create mode 100644 src/bin/TODO create mode 100755 src/bin/autogen.sh create mode 100644 src/bin/check.mk create mode 100644 src/bin/configure.ac create mode 100644 src/bin/copy.c create mode 100644 src/bin/erase.c create mode 100644 src/bin/files.c create mode 100644 src/bin/util.c create mode 100644 src/bin/util.h diff --git a/package/Config.in b/package/Config.in index 1b6320f33..b38995622 100644 --- a/package/Config.in +++ b/package/Config.in @@ -1,4 +1,6 @@ menu "Packages" + +source "$BR2_EXTERNAL_INFIX_PATH/package/bin/Config.in" source "$BR2_EXTERNAL_INFIX_PATH/package/confd/Config.in" source "$BR2_EXTERNAL_INFIX_PATH/package/confd-test-mode/Config.in" source "$BR2_EXTERNAL_INFIX_PATH/package/curios-httpd/Config.in" @@ -34,4 +36,5 @@ source "$BR2_EXTERNAL_INFIX_PATH/package/sysrepo-cpp/Config.in" source "$BR2_EXTERNAL_INFIX_PATH/package/rousette/Config.in" source "$BR2_EXTERNAL_INFIX_PATH/package/nghttp2-asio/Config.in" source "$BR2_EXTERNAL_INFIX_PATH/package/date-cpp/Config.in" + endmenu diff --git a/package/bin/Config.in b/package/bin/Config.in new file mode 100644 index 000000000..ee14b8e95 --- /dev/null +++ b/package/bin/Config.in @@ -0,0 +1,8 @@ +config BR2_PACKAGE_BIN + bool "bin" + select BR2_PACKAGE_SYSREPO + select BR2_PACKAGE_LIBITE + help + Misc. tools for CLI and shell users. + + https://github.com/kernelkit/infix diff --git a/package/bin/bin.hash b/package/bin/bin.hash new file mode 100644 index 000000000..6442d73b0 --- /dev/null +++ b/package/bin/bin.hash @@ -0,0 +1 @@ +sha256 d2f96418893ac66156d0e691cda189b0d85ae1d814065d1d9aa1845383100f46 LICENSE diff --git a/package/bin/bin.mk b/package/bin/bin.mk new file mode 100644 index 000000000..5ac94ac9e --- /dev/null +++ b/package/bin/bin.mk @@ -0,0 +1,21 @@ +################################################################################ +# +# bin +# +################################################################################ + +BIN_VERSION = 1.0 +BIN_SITE_METHOD = local +BIN_SITE = $(BR2_EXTERNAL_INFIX_PATH)/src/bin +BIN_LICENSE = BSD-3-Clause +BIN_LICENSE_FILES = LICENSE +BIN_REDISTRIBUTE = NO +BIN_DEPENDENCIES = sysrepo libite +BIN_CONF_OPTS = --prefix= --disable-silent-rules +BIN_AUTORECONF = YES + +define BIN_CONF_ENV +CFLAGS="$(INFIX_CFLAGS)" +endef + +$(eval $(autotools-package)) diff --git a/src/Makefile b/src/Makefile index b295eaed5..cd00fc320 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,5 +1,5 @@ # Host build of critical components, for Coverity Scan mostly -APPS = confd execd factory keyack statd +APPS = bin confd execd factory keyack statd .PHONY: all all: diff --git a/src/bin/.gitignore b/src/bin/.gitignore new file mode 100644 index 000000000..88967e2be --- /dev/null +++ b/src/bin/.gitignore @@ -0,0 +1,31 @@ +*~ +*.o +copy +erase +files + +/aclocal.m4 +/autom4te.cache/ +/aux +/clixon.xml +/compile +/config.h +/config.h.in +/config.guess +/config.log +/config.status +/config.sub +/configure +/depcomp +/.deps/ +/install-sh +/libtool +/ltmain.sh +/m4 +/missing + +GPATH +GRTAGS +GTAGS +Makefile +Makefile.in diff --git a/src/bin/LICENSE b/src/bin/LICENSE new file mode 100644 index 000000000..f9b6d6c12 --- /dev/null +++ b/src/bin/LICENSE @@ -0,0 +1,13 @@ +Copyright (c) 2024 The KernelKit Authors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/src/bin/Makefile.am b/src/bin/Makefile.am new file mode 100644 index 000000000..15ede0cf1 --- /dev/null +++ b/src/bin/Makefile.am @@ -0,0 +1,22 @@ +DISTCLEANFILES = *~ *.d +ACLOCAL_AMFLAGS = -I m4 + +bin_PROGRAMS = copy erase files + +copy_SOURCES = copy.c util.c util.h +copy_CPPFLAGS = -D_DEFAULT_SOURCE -D_GNU_SOURCE +copy_CFLAGS = -W -Wall -Wextra +copy_CFLAGS += $(libite_CFLAGS) $(sysrepo_CFLAGS) +copy_LDADD = $(libite_LIBS) $(sysrepo_LIBS) + +erase_SOURCES = erase.c util.c util.h +erase_CPPFLAGS = -D_DEFAULT_SOURCE -D_GNU_SOURCE +erase_CFLAGS = -W -Wall -Wextra +erase_CFLAGS += $(libite_CFLAGS) $(sysrepo_CFLAGS) +erase_LDADD = $(libite_LIBS) $(sysrepo_LIBS) + +files_SOURCES = files.c util.c util.h +files_CPPFLAGS = -D_DEFAULT_SOURCE -D_GNU_SOURCE +files_CFLAGS = -W -Wall -Wextra +files_CFLAGS += $(libite_CFLAGS) $(sysrepo_CFLAGS) +files_LDADD = $(libite_LIBS) $(sysrepo_LIBS) diff --git a/src/bin/TODO b/src/bin/TODO new file mode 100644 index 000000000..f26e1a1af --- /dev/null +++ b/src/bin/TODO @@ -0,0 +1,2 @@ +* Add bash completion to 'copy' + 'erase', use 'files' +* https://github.com/kernelkit/infix/issues/373 diff --git a/src/bin/autogen.sh b/src/bin/autogen.sh new file mode 100755 index 000000000..69ad0e189 --- /dev/null +++ b/src/bin/autogen.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +autoreconf -W portability -vifm diff --git a/src/bin/check.mk b/src/bin/check.mk new file mode 100644 index 000000000..efb18771b --- /dev/null +++ b/src/bin/check.mk @@ -0,0 +1,8 @@ +# Used by ../Makefile for 'make check' and Coverity Scan +export PKG_CONFIG_PATH = $(CURDIR)/../staging/lib/pkgconfig + +all: + ./autogen.sh + ./configure + make V=1 all + make distclean diff --git a/src/bin/configure.ac b/src/bin/configure.ac new file mode 100644 index 000000000..10aaf0a8f --- /dev/null +++ b/src/bin/configure.ac @@ -0,0 +1,70 @@ +AC_PREREQ(2.61) +AC_INIT([bin], [1.0], [https://github.com/kernelkit/infix/issues]) +AM_INIT_AUTOMAKE(1.11 foreign subdir-objects) +AM_SILENT_RULES(yes) + +AC_CONFIG_HEADERS([config.h]) +AC_CONFIG_FILES([ + Makefile +]) + +AC_PROG_CC +AC_PROG_INSTALL + +# Check for pkg-config first, warn if it's not installed +PKG_PROG_PKG_CONFIG + +PKG_CHECK_MODULES([libite], [libite >= 2.5.0]) +PKG_CHECK_MODULES([sysrepo], [sysrepo >= 2.2.36]) + +# Misc variable replacements for below Summary +test "x$prefix" = xNONE && prefix=$ac_default_prefix +test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' + +DATAROOTDIR=`eval echo $datarootdir` +DATAROOTDIR=`eval echo $DATAROOTDIR` +AC_SUBST(DATAROOTDIR) + +LIBDIR=`eval echo $libdir` +LIBDIR=`eval echo $LIBDIR` +AC_SUBST(LIBDIR) + +LOCALSTATEDIR=`eval echo $localstatedir` +LOCALSTATEDIR=`eval echo $LOCALSTATEDIR` +AC_SUBST(LOCALSTATEDIR) + +RUNSTATEDIR=`eval echo $runstatedir` +RUNSTATEDIR=`eval echo $RUNSTATEDIR` +AC_SUBST(RUNSTATEDIR) + +SYSCONFDIR=`eval echo $sysconfdir` +SYSCONFDIR=`eval echo $SYSCONFDIR` +AC_SUBST(SYSCONFDIR) + +AC_OUTPUT + +cat < +#include +#include +#include + +#include +#include + +#include +#include + +#include "util.h" + +struct infix_ds { + char *name; /* startup-config, etc. */ + char *sysrepocfg; /* ds name in sysrepocfg */ + int datastore; /* sr_datastore_t and -1 */ + int rw; /* read-write:1 or not:0 */ + char *path; /* local path or NULL */ +}; + +struct infix_ds infix_config[] = { + { "startup-config", "startup", SR_DS_STARTUP, 1, "/cfg/startup-config.cfg" }, + { "running-config", "running", SR_DS_RUNNING, 1, NULL }, + { "candidate-config", "candidate", SR_DS_CANDIDATE, 1, NULL }, + { "operational-config", "operational", SR_DS_OPERATIONAL, 1, NULL }, + { "factory-config", "factory-default", SR_DS_FACTORY_DEFAULT, 0, NULL } +}; + +static const char *prognm = "copy"; + + +/* + * Print sysrepo session errors followed by an optional string. + */ +static void emsg(sr_session_ctx_t *sess, const char *fmt, ...) +{ + const sr_error_info_t *err = NULL; + va_list ap; + size_t i; + int rc; + + if (!sess) + goto end; + + rc = sr_session_get_error(sess, &err); + if ((rc != SR_ERR_OK) || !err) + goto end; + + // Show the first error only. Because probably next errors are + // originated from internal sysrepo code but is not from subscribers. +// for (i = 0; i < err->err_count; i++) + for (i = 0; i < (err->err_count < 1 ? err->err_count : 1); i++) + fprintf(stderr, ERRMSG "%s\n", err->err[i].message); +end: + if (fmt) { + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + } +} + +static char *getuser(void) +{ + const struct passwd *pw; + uid_t uid; + + uid = getuid(); + pw = getpwuid(uid); + if (!pw) { + perror("getpwuid"); + exit(1); + } + + return pw->pw_name; +} + +static void set_owner(const char *fn, const char *user) +{ + struct passwd *pw; + + pw = getpwnam(user); + if (!pw) { + fprintf(stderr, ERRMSG "setting owner %s on %s: %s\n", fn, user, strerror(errno)); + return; + } + + chmod(fn, 0660); + chown(fn, pw->pw_uid, pw->pw_gid); +} + +static const char *infix_ds(const char *text, struct infix_ds **ds) +{ + size_t i, len = strlen(text); + + for (i = 0; i < NELEMS(infix_config); i++) { + if (!strncmp(infix_config[i].name, text, len)) { + *ds = &infix_config[i]; + return infix_config[i].name; + } + } + + return text; +} + + +static int copy(const char *src, const char *dst, const char *user) +{ + struct infix_ds *srcds = NULL, *dstds = NULL; + char temp_file[20] = "/tmp/copy.XXXXXX"; + const char *tmpfn = NULL; + sr_session_ctx_t *sess; + const char *fn = NULL; + const char *username; + sr_conn_ctx_t *conn; + char adjust[256]; + mode_t oldmask; + int rc = 0; + + oldmask = umask(0660); + + src = infix_ds(src, &srcds); + if (!src) + goto err; + dst = infix_ds(dst, &dstds); + if (!dst) + goto err; + + if (!strcmp(src, dst)) { + fprintf(stderr, ERRMSG "source and destination are the same, aborting."); + goto err; + } + + username = getuser(); + + /* 1. Regular ds copy */ + if (srcds && dstds) { + /* Ensure the dst ds is writable */ + if (!dstds->rw) { + fprintf(stderr, ERRMSG "not possible to write to \"%s\", skipping.\n", dst); + rc = 1; + goto err; + } + + if (sr_connect(SR_CONN_DEFAULT, &conn)) { + fprintf(stderr, ERRMSG "connection to datastore failed\n"); + rc = 1; + goto err; + } + + sr_log_syslog("klishd", SR_LL_WRN); + + if (sr_session_start(conn, dstds->datastore, &sess)) { + fprintf(stderr, ERRMSG "unable to open transaction to %s\n", dst); + } else { + sr_nacm_set_user(sess, username); + rc = sr_copy_config(sess, NULL, srcds->datastore, 0); + if (rc) + emsg(sess, ERRMSG "unable to copy configuration, err %d: %s\n", + rc, sr_strerror(rc)); + else + set_owner(dstds->path, username); + } + rc = sr_disconnect(conn); + + if (!srcds->path || !dstds->path) + goto err; /* done, not an error */ + + /* allow copy factory startup */ + } + + if (srcds) { + /* 2. Export from a datastore somewhere else */ + if (strstr(dst, "://")) { + if (srcds->path) + fn = srcds->path; + else { + snprintf(adjust, sizeof(adjust), "/tmp/%s.cfg", srcds->name); + fn = tmpfn = adjust; + rc = systemf("sysrepocfg -d %s -X%s -f json", srcds->sysrepocfg, fn); + } + + if (rc) + fprintf(stderr, ERRMSG "failed exporting %s to %s\n", src, fn); + else { + rc = systemf("curl %s -LT %s %s", user, fn, dst); + if (rc) + fprintf(stderr, ERRMSG "failed uploading %s to %s\n", src, dst); + else + set_owner(dst, username); + } + goto err; + } + + if (dstds && dstds->path) + fn = dstds->path; + else + fn = cfg_adjust(dst, src, adjust, sizeof(adjust)); + + if (!fn) { + fprintf(stderr, ERRMSG "invalid destination path.\n"); + rc = -1; + goto err; + } + + if (!access(fn, F_OK) && !yorn("Overwrite existing file %s", fn)) { + fprintf(stderr, "OK, aborting.\n"); + return 0; + } + + if (srcds->path) + rc = systemf("cp %s %s", srcds->path, fn); + else + rc = systemf("sysrepocfg -d %s -X%s -f json", srcds->sysrepocfg, fn); + if (rc) + fprintf(stderr, ERRMSG "failed copy %s to %s\n", src, fn); + else + set_owner(fn, username); + } else if (dstds) { + if (!dstds->sysrepocfg) { + fprintf(stderr, ERRMSG "not possible to import to this datastore.\n"); + rc = 1; + goto err; + } + if (!dstds->rw) { + fprintf(stderr, ERRMSG "not possible to write to %s", dst); + goto err; + } + + /* 3. Import from somewhere to a datastore */ + if (strstr(src, "://")) { + tmpfn = mktemp(temp_file); + fn = tmpfn; + } else { + fn = cfg_adjust(src, NULL, adjust, sizeof(adjust)); + if (!fn) { + fprintf(stderr, ERRMSG "invalid source file location.\n"); + rc = 1; + goto err; + } + } + + if (tmpfn) + rc = systemf("curl %s -Lo %s %s", user, fn, src); + if (rc) { + fprintf(stderr, ERRMSG "failed downloading %s", src); + } else { + rc = systemf("sysrepocfg -d %s -I%s -f json", dstds->sysrepocfg, fn); + if (rc) + fprintf(stderr, ERRMSG "failed loading %s from %s", dst, src); + } + } else { + if (strstr(src, "://") && strstr(dst, "://")) { + fprintf(stderr, ERRMSG "copy from remote to remote is not supported.\n"); + goto err; + } + + if (strstr(src, "://")) { + fn = cfg_adjust(dst, src, adjust, sizeof(adjust)); + if (!fn) { + fprintf(stderr, ERRMSG "invalid destination file location.\n"); + rc = 1; + goto err; + } + + if (!access(fn, F_OK)) { + if (!yorn("Overwrite existing file %s", fn)) { + fprintf(stderr, "OK, aborting.\n"); + return 0; + } + } + + rc = systemf("curl %s -Lo %s %s", user, fn, src); + } else if (strstr(dst, "://")) { + fn = cfg_adjust(src, NULL, adjust, sizeof(adjust)); + if (!fn) { + fprintf(stderr, ERRMSG "invalid source file location.\n"); + rc = 1; + goto err; + } + + if (access(fn, F_OK)) + fprintf(stderr, ERRMSG "no such file %s, aborting.", fn); + else + rc = systemf("curl %s -LT %s %s", user, fn, dst); + } else { + if (!access(dst, F_OK)) { + if (!yorn("Overwrite existing file %s", dst)) { + fprintf(stderr, "OK, aborting.\n"); + return 0; + } + } + rc = systemf("cp %s %s", src, dst); + } + } + +err: + if (tmpfn) + rc = remove(tmpfn); + + sync(); /* ensure command is flushed to disk */ + umask(oldmask); + + return rc; +} + +static int usage(int rc) +{ + printf("Usage: %s [OPTIONS] SRC DST\n" + "\n" + "Options:\n" + " -h This help text\n" + " -u USER Username for remote commands, like scp\n" + " -v Show version\n", prognm); + + return rc; +} + +int main(int argc, char *argv[]) +{ + const char *user = NULL, *src = NULL, *dst = NULL; + int c; + + while ((c = getopt(argc, argv, "hu:v")) != EOF) { + switch(c) { + case 'h': + return usage(0); + case 'u': + user = optarg; + break; + case 'v': + puts(PACKAGE_VERSION); + return 0; + } + } + + if (optind >= argc) + return usage(1); + + src = argv[optind++]; + dst = argv[optind++]; + + return copy(src, dst, user); +} diff --git a/src/bin/erase.c b/src/bin/erase.c new file mode 100644 index 000000000..1c5e36019 --- /dev/null +++ b/src/bin/erase.c @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: ISC */ +#include "config.h" + +#include +#include +#include +#include +#include + +#include "util.h" + +static const char *prognm = "erase"; + + +static int do_erase(const char *path) +{ + char *fn; + + if (access(path, F_OK)) { + size_t len = strlen(path) + 10; + + fn = alloca(len); + if (!fn) { + fprintf(stderr, ERRMSG "failed allocating memory.\n"); + return -1; + } + + cfg_adjust(path, NULL, fn, len); + if (access(fn, F_OK)) { + fprintf(stderr, "No such file: %s\n", fn); + return -1; + } + } else + fn = (char *)path; + + if (!yorn("Remove %s, are you sure", fn)) + return 0; + + if (remove(fn)) { + fprintf(stderr, ERRMSG "failed removing %s: %s\n", fn, strerror(errno)); + return -1; + } + + return 0; +} + +static int usage(int rc) +{ + printf("Usage: %s [OPTIONS] PATH\n" + "\n" + "Options:\n" + " -h This help text\n" + " -v Show version\n", prognm); + + return rc; +} + +int main(int argc, char *argv[]) +{ + int c; + + while ((c = getopt(argc, argv, "hv")) != EOF) { + switch(c) { + case 'h': + return usage(0); + case 'v': + puts(PACKAGE_VERSION); + return 0; + } + } + + if (optind >= argc) + return usage(1); + + return do_erase(argv[optind++]); +} diff --git a/src/bin/files.c b/src/bin/files.c new file mode 100644 index 000000000..85f8066d9 --- /dev/null +++ b/src/bin/files.c @@ -0,0 +1,80 @@ +/* SPDX-License-Identifier: ISC */ +#include "config.h" + +#include +#include +#include + +#include "util.h" + +static const char *prognm = "files"; + + +int files(const char *path, const char *stripext) +{ + const struct dirent *d; + DIR *dir; + + dir = opendir(path); + if (!dir) { + fprintf(stderr, ERRMSG "%s", strerror(errno)); + return -1; + } + + while ((d = readdir(dir))) { + char name[sizeof(d->d_name) + 1]; + + /* only list regular files, skip dirs and dotfiles */ + if (d->d_type != DT_REG || d->d_name[0] == '.') + continue; + + strlcpy(name, d->d_name, sizeof(name)); + if (stripext) { + size_t pos = has_ext(name, stripext); + + if (pos) + name[pos] = 0; + } + + printf("%s\n", name); + } + + return closedir(dir); +} + + +static int usage(int rc) +{ + printf("Usage: %s [OPTIONS] PATH [EXT]\n" + "\n" + "Options:\n" + " -h This help text\n" + " -v Show version\n", prognm); + + return rc; +} + +int main(int argc, char *argv[]) +{ + const char *path = NULL, *ext = NULL; + int c; + + while ((c = getopt(argc, argv, "hv")) != EOF) { + switch(c) { + case 'h': + return usage(0); + case 'v': + puts(PACKAGE_VERSION); + return 0; + } + } + + if (optind >= argc) + return usage(1); + + path = argv[optind++]; + if (optind < argc) + ext = argv[optind++]; + + return files(path, ext); +} diff --git a/src/bin/util.c b/src/bin/util.c new file mode 100644 index 000000000..3b212655f --- /dev/null +++ b/src/bin/util.c @@ -0,0 +1,115 @@ +/* SPDX-License-Identifier: ISC */ +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include "util.h" + + +static char rawgetch(void) +{ + struct termios saved, c; + char key; + + if (tcgetattr(fileno(stdin), &saved) < 0) + return -1; + + c = saved; + c.c_lflag &= ~ICANON; + c.c_lflag &= ~ECHO; + c.c_cc[VMIN] = 1; + c.c_cc[VTIME] = 0; + + if (tcsetattr(fileno(stdin), TCSANOW, &c) < 0) { + tcsetattr(fileno(stdin), TCSANOW, &saved); + return -1; + } + + key = getchar(); + tcsetattr(fileno(stdin), TCSANOW, &saved); + + return key; +} + +int yorn(const char *fmt, ...) +{ + va_list ap; + char ch; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + fprintf(stderr, " (y/N)? "); + ch = rawgetch(); + fprintf(stderr, "%c\n", ch); + if (ch != 'y' && ch != 'Y') + return 0; + + return 1; +} + +int has_ext(const char *fn, const char *ext) +{ + size_t pos = strlen(fn); + size_t len = strlen(ext); + + if (len < pos && !strcmp(&fn[pos - len], ext)) + return pos - len; + return 0; +} + +static const char *basenm(const char *fn) +{ + const char *ptr; + + if (!fn) + return ""; + + ptr = strrchr(fn, '/'); + if (!ptr) + ptr = fn; + + return ptr; +} + +char *cfg_adjust(const char *fn, const char *tmpl, char *buf, size_t len) +{ + if (strstr(fn, "../")) + return NULL; /* relative paths not allowed */ + + if (fn[0] == '/') { + strlcpy(buf, fn, len); + return buf; /* allow absolute paths */ + } + + /* Files in /cfg must end in .cfg */ + if (!strncmp(fn, "/cfg/", 5)) { + strlcpy(buf, fn, len); + if (!has_ext(fn, ".cfg")) + strlcat(buf, ".cfg", len); + + return buf; + } + + /* Files ending with .cfg belong in /cfg */ + if (has_ext(fn, ".cfg")) { + snprintf(buf, len, "/cfg/%s", fn); + return buf; + } + + if (strlen(fn) > 0 && fn[0] == '.' && tmpl) { + if (fn[1] == '/' && fn[2] != 0) + strlcpy(buf, fn, len); + else + strlcpy(buf, basenm(tmpl), len); + } else + strlcpy(buf, fn, len); + + return buf; +} diff --git a/src/bin/util.h b/src/bin/util.h new file mode 100644 index 000000000..6c5ded9d8 --- /dev/null +++ b/src/bin/util.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: ISC */ +#ifndef BIN_UTIL_H_ +#define BIN_UTIL_H_ +#include +#include + +#define ERRMSG "Error: " +#define INFMSG "Note: " + +int yorn (const char *fmt, ...); + +int files (const char *path, const char *stripext); + +int has_ext (const char *fn, const char *ext); +char *cfg_adjust (const char *fn, const char *tmpl, char *buf, size_t len); + +#endif /* BIN_UTIL_H_ */ diff --git a/src/klish-plugin-infix/src/infix.c b/src/klish-plugin-infix/src/infix.c index 255028b60..ff6c94965 100644 --- a/src/klish-plugin-infix/src/infix.c +++ b/src/klish-plugin-infix/src/infix.c @@ -1,12 +1,9 @@ -#include -#include #include #include #include #include #include #include -#include #include #include @@ -31,22 +28,6 @@ const uint8_t kplugin_infix_major = 1; const uint8_t kplugin_infix_minor = 0; -struct infix_ds { - char *name; /* startup-config, etc. */ - char *sysrepocfg; /* ds name in sysrepocfg */ - int datastore; /* sr_datastore_t and -1 */ - int rw; /* read-write:1 or not:0 */ - char *path; /* local path or NULL */ -}; - -struct infix_ds infix_config[] = { - { "startup-config", "startup", SR_DS_STARTUP, 1, "/cfg/startup-config.cfg" }, - { "running-config", "running", SR_DS_RUNNING, 1, NULL }, - { "candidate-config", "candidate", SR_DS_CANDIDATE, 1, NULL }, - { "operational-config", "operational", SR_DS_OPERATIONAL, 1, NULL }, - { "factory-config", "factory-default", SR_DS_FACTORY_DEFAULT, 0, NULL } -}; - static void cd_home(kcontext_t *ctx) { const char *user = "root"; @@ -64,123 +45,6 @@ static void cd_home(kcontext_t *ctx) chdir(pw->pw_dir); } -static void set_owner(const char *fn, const char *user) -{ - struct passwd *pw; - - pw = getpwnam(user); - if (!pw) { - fprintf(stderr, ERRMSG "setting owner %s on %s: %s\n", fn, user, strerror(errno)); - return; - } - - chmod(fn, 0660); - chown(fn, pw->pw_uid, pw->pw_gid); -} - -static int has_ext(const char *fn, const char *ext) -{ - size_t pos = strlen(fn); - size_t len = strlen(ext); - - if (len < pos && !strcmp(&fn[pos - len], ext)) - return pos - len; - return 0; -} - -static const char *basenm(const char *fn) -{ - const char *ptr; - - if (!fn) - return ""; - - ptr = strrchr(fn, '/'); - if (!ptr) - ptr = fn; - - return ptr; -} - -static char *cfg_adjust(const char *fn, const char *tmpl, char *buf, size_t len) -{ - if (strstr(fn, "../")) - return NULL; /* relative paths not allowed */ - - if (fn[0] == '/') { - strncpy(buf, fn, len); - return buf; /* allow absolute paths */ - } - - /* Files in /cfg must end in .cfg */ - if (!strncmp(fn, "/cfg/", 5)) { - snprintf(buf, len, "%s", fn); - if (!has_ext(fn, ".cfg")) - strcat(buf, ".cfg"); - - return buf; - } - - /* Files ending with .cfg belong in /cfg */ - if (has_ext(fn, ".cfg")) { - snprintf(buf, len, "/cfg/%s", fn); - return buf; - } - - if (strlen(fn) > 0 && fn[0] == '.' && tmpl) { - if (fn[1] == '/' && fn[1] != 0) - strncpy(buf, fn, len); - else - snprintf(buf, len, "%s", basenm(tmpl)); - } else - strncpy(buf, fn, len); - - return buf; -} - -static char rawgetch(void) -{ - struct termios saved, c; - char key; - - if (tcgetattr(fileno(stdin), &saved) < 0) - return -1; - - c = saved; - c.c_lflag &= ~ICANON; - c.c_lflag &= ~ECHO; - c.c_cc[VMIN] = 1; - c.c_cc[VTIME] = 0; - - if (tcsetattr(fileno(stdin), TCSANOW, &c) < 0) { - tcsetattr(fileno(stdin), TCSANOW, &saved); - return -1; - } - - key = getchar(); - tcsetattr(fileno(stdin), TCSANOW, &saved); - - return key; -} - -static int yorn(const char *fmt, ...) -{ - va_list ap; - char ch; - - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); - - fprintf(stderr, " (y/N)? "); - ch = rawgetch(); - fprintf(stderr, "%c\n", ch); - if (ch != 'y' && ch != 'Y') - return 0; - - return 1; -} - static int systemf(const char *fmt, ...) { va_list ap; @@ -219,68 +83,6 @@ static int systemf(const char *fmt, ...) return rc; } -/* - * Print sysrepo session errors followed by an optional string. - */ -static void emsg(sr_session_ctx_t *sess, const char *fmt, ...) -{ - const sr_error_info_t *err = NULL; - va_list ap; - size_t i; - int rc; - - if (!sess) - goto end; - - rc = sr_session_get_error(sess, &err); - if ((rc != SR_ERR_OK) || !err) - goto end; - - // Show the first error only. Because probably next errors are - // originated from internal sysrepo code but is not from subscribers. -// for (i = 0; i < err->err_count; i++) - for (i = 0; i < (err->err_count < 1 ? err->err_count : 1); i++) - fprintf(stderr, ERRMSG "%s\n", err->err[i].message); -end: - if (fmt) { - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); - } -} - -static int files(const char *path, const char *stripext) -{ - struct dirent *d; - DIR *dir; - - dir = opendir(path); - if (!dir) { - fprintf(stderr, ERRMSG "%s", strerror(errno)); - return -1; - } - - while ((d = readdir(dir))) { - char name[sizeof(d->d_name) + 1]; - - /* only list regular files, skip dirs and dotfiles */ - if (d->d_type != DT_REG || d->d_name[0] == '.') - continue; - - strncpy(name, d->d_name, sizeof(name)); - if (stripext) { - size_t pos = has_ext(name, stripext); - - if (pos) - name[pos] = 0; - } - - printf("%s\n", name); - } - - return closedir(dir); -} - int infix_datastore(kcontext_t *ctx) { const char *ds; @@ -300,14 +102,13 @@ int infix_datastore(kcontext_t *ctx) } done: - return files("/cfg", ".cfg"); + return systemf("files /cfg .cfg"); } int infix_erase(kcontext_t *ctx) { kpargv_t *pargv = kcontext_pargv(ctx); const char *path; - char *fn; path = kparg_value(kpargv_find(pargv, "file")); if (!path) { @@ -316,32 +117,8 @@ int infix_erase(kcontext_t *ctx) } cd_home(ctx); - if (access(path, F_OK)) { - size_t len = strlen(path) + 10; - - fn = alloca(len); - if (!fn) { - fprintf(stderr, ERRMSG "failed allocating memory.\n"); - return -1; - } - - cfg_adjust(path, NULL, fn, len); - if (access(fn, F_OK)) { - fprintf(stderr, "No such file: %s\n", fn); - return -1; - } - } else - fn = (char *)path; - - if (!yorn("Remove %s, are you sure", fn)) - return 0; - - if (remove(fn)) { - fprintf(stderr, ERRMSG "failed removing %s: %s\n", fn, strerror(errno)); - return -1; - } - return 0; + return systemf("erase %s", path); } int infix_files(kcontext_t *ctx) @@ -355,7 +132,7 @@ int infix_files(kcontext_t *ctx) return -1; } - return files(path, NULL); + return systemf("files %s", path); } int infix_ifaces(kcontext_t *ctx) @@ -365,228 +142,27 @@ int infix_ifaces(kcontext_t *ctx) return 0; } -static const char *infix_ds(const char *text, const char *type, struct infix_ds **ds) -{ - size_t i, len = strlen(text); - - for (i = 0; i < NELEMS(infix_config); i++) { - if (!strncmp(infix_config[i].name, text, len)) { - *ds = &infix_config[i]; - return infix_config[i].name; - } - } - - return text; -} - int infix_copy(kcontext_t *ctx) { - struct infix_ds *srcds = NULL, *dstds = NULL; - char temp_file[20] = "/tmp/copy.XXXXXX"; kpargv_t *pargv = kcontext_pargv(ctx); - const char *tmpfn = NULL; - sr_session_ctx_t *sess; - const char *fn = NULL; const char *src, *dst; const char *username; - sr_conn_ctx_t *conn; char user[256] = ""; - char adjust[256]; kparg_t *parg; - int rc = 0; src = kparg_value(kpargv_find(pargv, "src")); dst = kparg_value(kpargv_find(pargv, "dst")); if (!src || !dst) - goto err; + return -1; parg = kpargv_find(pargv, "user"); if (parg) snprintf(user, sizeof(user), "-u %s", kparg_value(parg)); - src = infix_ds(src, "source", &srcds); - if (!src) - goto err; - dst = infix_ds(dst, "destination", &dstds); - if (!dst) - goto err; - - if (!strcmp(src, dst)) { - fprintf(stderr, ERRMSG "source and destination are the same, aborting."); - goto err; - } - cd_home(ctx); username = ksession_user(kcontext_session(ctx)); - umask(0660); - - /* 1. Regular ds copy */ - if (srcds && dstds) { - /* Ensure the dst ds is writable */ - if (!dstds->rw) { - fprintf(stderr, ERRMSG "not possible to write to \"%s\", skipping.\n", dst); - rc = 1; - goto err; - } - - if (sr_connect(SR_CONN_DEFAULT, &conn)) { - fprintf(stderr, ERRMSG "connection to datastore failed\n"); - rc = 1; - goto err; - } - - sr_log_syslog("klishd", SR_LL_WRN); - - if (sr_session_start(conn, dstds->datastore, &sess)) { - fprintf(stderr, ERRMSG "unable to open transaction to %s\n", dst); - } else { - sr_nacm_set_user(sess, username); - rc = sr_copy_config(sess, NULL, srcds->datastore, 0); - if (rc) - emsg(sess, ERRMSG "unable to copy configuration, err %d: %s\n", - rc, sr_strerror(rc)); - else - set_owner(dstds->path, username); - } - rc = sr_disconnect(conn); - - if (!srcds->path || !dstds->path) - goto err; /* done, not an error */ - - /* allow copy factory startup */ - } - - if (srcds) { - /* 2. Export from a datastore somewhere else */ - if (strstr(dst, "://")) { - if (srcds->path) - fn = srcds->path; - else { - snprintf(adjust, sizeof(adjust), "/tmp/%s.cfg", srcds->name); - fn = tmpfn = adjust; - rc = systemf("sysrepocfg -d %s -X%s -f json", srcds->sysrepocfg, fn); - } - - if (rc) - fprintf(stderr, ERRMSG "failed exporting %s to %s\n", src, fn); - else { - rc = systemf("curl %s -LT %s %s", user, fn, dst); - if (rc) - fprintf(stderr, ERRMSG "failed uploading %s to %s\n", src, dst); - else - set_owner(dst, username); - } - goto err; - } - - if (dstds && dstds->path) - fn = dstds->path; - else - fn = cfg_adjust(dst, src, adjust, sizeof(adjust)); - - if (!fn) { - fprintf(stderr, ERRMSG "invalid destination path.\n"); - rc = -1; - goto err; - } - if (!access(fn, F_OK) && !yorn("Overwrite existing file %s", fn)) { - fprintf(stderr, "OK, aborting.\n"); - return 0; - } - - if (srcds->path) - rc = systemf("cp %s %s", srcds->path, fn); - else - rc = systemf("sysrepocfg -d %s -X%s -f json", srcds->sysrepocfg, fn); - if (rc) - fprintf(stderr, ERRMSG "failed copy %s to %s\n", src, fn); - else - set_owner(fn, username); - } else if (dstds) { - if (!dstds->sysrepocfg) { - fprintf(stderr, ERRMSG "not possible to import to this datastore.\n"); - rc = 1; - goto err; - } - if (!dstds->rw) { - fprintf(stderr, ERRMSG "not possible to write to %s", dst); - goto err; - } - - /* 3. Import from somewhere to a datastore */ - if (strstr(src, "://")) { - tmpfn = mktemp(temp_file); - fn = tmpfn; - } else { - fn = cfg_adjust(src, NULL, adjust, sizeof(adjust)); - if (!fn) { - fprintf(stderr, ERRMSG "invalid source file location.\n"); - rc = 1; - goto err; - } - } - - if (tmpfn) - rc = systemf("curl %s -Lo %s %s", user, fn, src); - if (rc) { - fprintf(stderr, ERRMSG "failed downloading %s", src); - } else { - rc = systemf("sysrepocfg -d %s -I%s -f json", dstds->sysrepocfg, fn); - if (rc) - fprintf(stderr, ERRMSG "failed loading %s from %s", dst, src); - } - } else { - if (strstr(src, "://") && strstr(dst, "://")) { - fprintf(stderr, ERRMSG "copy from remote to remote is not supported.\n"); - goto err; - } - - if (strstr(src, "://")) { - fn = cfg_adjust(dst, src, adjust, sizeof(adjust)); - if (!fn) { - fprintf(stderr, ERRMSG "invalid destination file location.\n"); - rc = 1; - goto err; - } - - if (!access(fn, F_OK)) { - if (!yorn("Overwrite existing file %s", fn)) { - fprintf(stderr, "OK, aborting.\n"); - return 0; - } - } - - rc = systemf("curl %s -Lo %s %s", user, fn, src); - } else if (strstr(dst, "://")) { - fn = cfg_adjust(src, NULL, adjust, sizeof(adjust)); - if (!fn) { - fprintf(stderr, ERRMSG "invalid source file location.\n"); - rc = 1; - goto err; - } - - if (access(fn, F_OK)) - fprintf(stderr, ERRMSG "no such file %s, aborting.", fn); - else - rc = systemf("curl %s -LT %s %s", user, fn, dst); - } else { - if (!access(dst, F_OK)) { - if (!yorn("Overwrite existing file %s", dst)) { - fprintf(stderr, "OK, aborting.\n"); - return 0; - } - } - rc = systemf("cp %s %s", src, dst); - } - } - -err: - if (tmpfn) - rc = remove(tmpfn); - sync(); /* ensure command is flushed to disk */ - - return rc; + return systemf("sudo -u %s copy %s %s %s", username, user, src, dst); } int infix_shell(kcontext_t *ctx) From 343b453c7465cc8cf921c5759ae7f23db20fb063 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 16 Oct 2024 05:57:40 +0200 Subject: [PATCH 4/4] bin: use CONFD_TIMEOUT for all copy operations Fixes #701 Signed-off-by: Joachim Wiberg --- src/bin/copy.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/bin/copy.c b/src/bin/copy.c index 9740b2a2c..a157106a0 100644 --- a/src/bin/copy.c +++ b/src/bin/copy.c @@ -31,6 +31,7 @@ struct infix_ds infix_config[] = { }; static const char *prognm = "copy"; +static int timeout; /* @@ -157,7 +158,7 @@ static int copy(const char *src, const char *dst, const char *user) fprintf(stderr, ERRMSG "unable to open transaction to %s\n", dst); } else { sr_nacm_set_user(sess, username); - rc = sr_copy_config(sess, NULL, srcds->datastore, 0); + rc = sr_copy_config(sess, NULL, srcds->datastore, timeout * 1000); if (rc) emsg(sess, ERRMSG "unable to copy configuration, err %d: %s\n", rc, sr_strerror(rc)); @@ -314,7 +315,8 @@ static int usage(int rc) "Options:\n" " -h This help text\n" " -u USER Username for remote commands, like scp\n" - " -v Show version\n", prognm); + " -t SEEC Timeout for the operation, or default %d sec\n" + " -v Show version\n", prognm, timeout); return rc; } @@ -324,10 +326,15 @@ int main(int argc, char *argv[]) const char *user = NULL, *src = NULL, *dst = NULL; int c; - while ((c = getopt(argc, argv, "hu:v")) != EOF) { + timeout = fgetint("/etc/default/confd", "=", "CONFD_TIMEOUT"); + + while ((c = getopt(argc, argv, "ht:u:v")) != EOF) { switch(c) { case 'h': return usage(0); + case 't': + timeout = atoi(optarg); + break; case 'u': user = optarg; break; @@ -337,6 +344,9 @@ int main(int argc, char *argv[]) } } + if (timeout < 0) + timeout = 120; + if (optind >= argc) return usage(1);