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 <troglobit@gmail.com>
This commit is contained in:
Joachim Wiberg
2024-10-16 06:10:39 +02:00
parent 886d83933f
commit 464e0bcb92
18 changed files with 823 additions and 430 deletions
+3
View File
@@ -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
+8
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
sha256 d2f96418893ac66156d0e691cda189b0d85ae1d814065d1d9aa1845383100f46 LICENSE
+21
View File
@@ -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))
+1 -1
View File
@@ -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:
+31
View File
@@ -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
+13
View File
@@ -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.
+22
View File
@@ -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)
+2
View File
@@ -0,0 +1,2 @@
* Add bash completion to 'copy' + 'erase', use 'files'
* https://github.com/kernelkit/infix/issues/373
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
autoreconf -W portability -vifm
+8
View File
@@ -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
+70
View File
@@ -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 <<EOF
------------------ Summary ------------------
$PACKAGE_NAME version $PACKAGE_VERSION
Prefix................: $prefix
Exec prefix...........: $eprefix
Sysconfdir............: `eval echo $sysconfdir`
Localstatedir.........: `eval echo $localstatedir`
Default fstab.........: `eval echo $fstab`
System environment....: ${sysconfig_path:-${sysconfig}}
C Compiler............: $CC $CFLAGS $CPPFLAGS $LDFLAGS $LIBS
Linker................: $LD $LLDP_LDFLAGS $LLDP_BIN_LDFLAGS $LDFLAGS $LIBS
CFLAGS................: $libite_CFLAGS $sysrepo_CFLAGS
LIBS..................: $libite_LIBS $sysrepo_LIBS
------------- Compiler version --------------
$($CC --version || true)
-------------- Linker version ---------------
$($LD --version || true)
---------------------------------------------
Check the above options and compile with:
${MAKE-make}
EOF
+347
View File
@@ -0,0 +1,347 @@
/* SPDX-License-Identifier: ISC */
#include "config.h"
#include <getopt.h>
#include <pwd.h>
#include <stdio.h>
#include <stdarg.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sysrepo.h>
#include <sysrepo/netconf_acm.h>
#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);
}
+76
View File
@@ -0,0 +1,76 @@
/* SPDX-License-Identifier: ISC */
#include "config.h"
#include <alloca.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#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++]);
}
+80
View File
@@ -0,0 +1,80 @@
/* SPDX-License-Identifier: ISC */
#include "config.h"
#include <dirent.h>
#include <getopt.h>
#include <stdio.h>
#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);
}
+115
View File
@@ -0,0 +1,115 @@
/* SPDX-License-Identifier: ISC */
#include "config.h"
#include <dirent.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <termios.h>
#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;
}
+17
View File
@@ -0,0 +1,17 @@
/* SPDX-License-Identifier: ISC */
#ifndef BIN_UTIL_H_
#define BIN_UTIL_H_
#include <stdio.h>
#include <libite/lite.h>
#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_ */
+5 -429
View File
@@ -1,12 +1,9 @@
#include <assert.h>
#include <dirent.h>
#include <errno.h>
#include <pwd.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <sys/types.h>
#include <sys/wait.h>
@@ -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)