From 22adc58187805b779386dc92d03713f1afd3de0c Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Tue, 15 Dec 2020 14:43:01 +0100 Subject: [PATCH] * New process-control RPC feature in clixon-lib.yang to manage processes * This is an alternative to manage a clixon daemon via sudtemd, containerd or other * One important special case is starting the clixon-restconf daemon internally * This is how it works: * Register a process via `clixon_process_register(h, name, namespace, argv, argc)` * Use process-control RPC defined in clixon-lib.yang to start/stop/restart or query status on that process * Example code in the main example --- CHANGELOG.md | 7 + apps/backend/backend_client.c | 49 ++- apps/backend/backend_main.c | 9 +- apps/restconf/restconf_main_evhtp.c | 1 - configure | 7 +- configure.ac | 3 +- doc/DEVELOP.md | 2 +- doc/FAQ.md | 2 + doc/ROADMAP.md | 11 - example/main/example_backend.c | 39 +- include/clixon_config.h.in | 21 + lib/clixon/clixon.h.in | 1 + lib/clixon/clixon_proc.h | 51 +++ lib/clixon/clixon_sig.h | 9 +- lib/src/Makefile.in | 2 +- lib/src/clixon_proc.c | 559 +++++++++++++++++++++++++ lib/src/clixon_sig.c | 51 ++- test/test_restconf_rpc.sh | 188 +++++++++ yang/clixon/clixon-lib@2020-12-08.yang | 6 +- 19 files changed, 971 insertions(+), 47 deletions(-) delete mode 100644 doc/ROADMAP.md create mode 100644 lib/clixon/clixon_proc.h create mode 100644 lib/src/clixon_proc.c create mode 100755 test/test_restconf_rpc.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index bd97e6dc..a1d2a6d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,13 @@ ### New features +* New process-control RPC feature in clixon-lib.yang to manage processes + * This is an alternative to manage a clixon daemon via sudtemd, containerd or other + * One important special case is starting the clixon-restconf daemon internally + * This is how it works: + * Register a process via `clixon_process_register(h, name, namespace, argv, argc)` + * Use process-control RPC defined in clixon-lib.yang to start/stop/restart or query status on that process + * Example code in the main example * More YANG extension functionality, * See [Augment auto-cli for hiding/modifying cli syntax #156](https://github.com/clicon/clixon/issues/156) and [hiding auto-generated CLI entries #153](https://github.com/clicon/clixon/issues/153) * Extensions can be used in augmentations diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index ff5972ca..f634167b 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -1560,7 +1560,49 @@ from_client_restart_plugin(clicon_handle h, return retval; } -/*! +/*! Control a specific process or daemon: start/stop, etc + * @param[in] h Clicon handle + * @param[in] xe Request: + * @param[out] cbret Return xml tree, eg ..., %s", + NETCONF_BASE_NAMESPACE, + CLIXON_LIB_NS, + status?"true":"false"); + else + cprintf(cbret, "", NETCONF_BASE_NAMESPACE); + retval = 0; + done: + return retval; +} + +/*! Clixon hello to check liveness * @retval 0 OK * @retval -1 Error */ @@ -1569,7 +1611,6 @@ from_client_hello(clicon_handle h, cxobj *x, struct client_entry *ce, cbuf *cbret) - { int retval = -1; uint32_t id; @@ -1592,6 +1633,7 @@ from_client_hello(clicon_handle h, return retval; } + /*! An internal clicon message has arrived from a client. Receive and dispatch. * @param[in] h Clicon handle * @param[in] s Socket where message arrived. read from this. @@ -1922,6 +1964,9 @@ backend_rpc_init(clicon_handle h) if (rpc_callback_register(h, from_client_restart_plugin, NULL, CLIXON_LIB_NS, "restart-plugin") < 0) goto done; + if (rpc_callback_register(h, from_client_process_control, NULL, + CLIXON_LIB_NS, "process-control") < 0) + goto done; retval =0; done: return retval; diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c index 6ac1a240..dbf25745 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -126,7 +126,10 @@ backend_terminate(clicon_handle h) /* Delete all backend plugin RPC callbacks */ rpc_callback_delete_all(h); /* Delete all backend plugin upgrade callbacks */ - upgrade_callback_delete_all(h); + upgrade_callback_delete_all(h); + /* Delete all process-control entries */ + clixon_process_delete_all(h); + xpath_optimize_exit(); if (pidfile) @@ -529,6 +532,10 @@ main(int argc, usage(h, argv[0]); goto done; } + /* Add some specific options from autotools configure NOT config file */ + clicon_option_str_set(h, "CLICON_WWWUSER", WWWUSER); + clicon_option_str_set(h, "CLICON_WWWDIR", WWWDIR); + /* External NACM file? */ nacm_mode = clicon_option_str(h, "CLICON_NACM_MODE"); if (nacm_mode && strcmp(nacm_mode, "external") == 0) diff --git a/apps/restconf/restconf_main_evhtp.c b/apps/restconf/restconf_main_evhtp.c index 956e417e..18538f53 100644 --- a/apps/restconf/restconf_main_evhtp.c +++ b/apps/restconf/restconf_main_evhtp.c @@ -1196,7 +1196,6 @@ restconf_config(clicon_handle h, xml_free(xconfig2); if (nsc) cvec_free(nsc); - clicon_debug(1, "restconf_main_evhtp done"); return retval; } diff --git a/configure b/configure index 5a64c060..a6126b64 100755 --- a/configure +++ b/configure @@ -5397,6 +5397,11 @@ cat >>confdefs.h <<_ACEOF #define WWWUSER "$wwwuser" _ACEOF + +cat >>confdefs.h <<_ACEOF +#define WWWDIR "$wwwdir" +_ACEOF + fi # Set default config file location @@ -5560,7 +5565,7 @@ fi fi # -for ac_func in inet_aton sigaction sigvec strlcpy strsep strndup alphasort versionsort getpeereid +for ac_func in inet_aton sigaction sigvec strlcpy strsep strndup alphasort versionsort getpeereid setns do : as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" diff --git a/configure.ac b/configure.ac index 9f320fbc..73591e45 100644 --- a/configure.ac +++ b/configure.ac @@ -250,6 +250,7 @@ if test "x${with_restconf}" != "x"; then fi AC_MSG_RESULT(www user is $wwwuser) AC_DEFINE_UNQUOTED(WWWUSER, "$wwwuser", [WWW user for restconf daemon]) + AC_DEFINE_UNQUOTED(WWWDIR, "$wwwdir", [WWW dir for restconf daemon]) fi # Set default config file location @@ -272,7 +273,7 @@ if test "${with_libxml2}"; then fi # -AC_CHECK_FUNCS(inet_aton sigaction sigvec strlcpy strsep strndup alphasort versionsort getpeereid) +AC_CHECK_FUNCS(inet_aton sigaction sigvec strlcpy strsep strndup alphasort versionsort getpeereid setns) # Checks for getsockopt options for getting unix socket peer credentials on # Linux diff --git a/doc/DEVELOP.md b/doc/DEVELOP.md index 5cd5f5a6..ba00a9eb 100644 --- a/doc/DEVELOP.md +++ b/doc/DEVELOP.md @@ -38,7 +38,7 @@ However, releases are made periodically (ca every 1 month) which is more tested. A release branch can be made, eg release-4.0 where 4.0.0, 4.0.1 are tagged -## How the meta-configure stuff works +## How the autotools stuff works ``` configure.ac --. | .------> autoconf* -----> configure diff --git a/doc/FAQ.md b/doc/FAQ.md index 7872721b..d105af3f 100644 --- a/doc/FAQ.md +++ b/doc/FAQ.md @@ -1,5 +1,7 @@ # Clixon FAQ +This FAQ may be outdated. Main [Clixon documentation](clixon-docs.readthedocs.io/) contains more detailed information. + * [What is Clixon?](#what-is-clixon) * [Why should I use Clixon?](#why-should-i-use-clixon) * [What license is available?](#what-license-is-available) diff --git a/doc/ROADMAP.md b/doc/ROADMAP.md deleted file mode 100644 index fb830b64..00000000 --- a/doc/ROADMAP.md +++ /dev/null @@ -1,11 +0,0 @@ -# Clixon roadmap - -- Support for restconf call-home (RFC 8071) -- NETCONF - - Support for additional Netconf [edit-config modes](https://github.com/clicon/clixon/issues/53) - - Netconf [framing](https://github.com/clicon/clixon/issues/50) - - [Child ordering](https://github.com/clicon/clixon/issues/22) -- [gRPC](https://github.com/clicon/clixon/issues/43) - - - diff --git a/example/main/example_backend.c b/example/main/example_backend.c index f1ca0b35..ea811b18 100644 --- a/example/main/example_backend.c +++ b/example/main/example_backend.c @@ -66,7 +66,7 @@ #include /* Command line options to be passed to getopt(3) */ -#define BACKEND_EXAMPLE_OPTS "rsS:iuUt:v:" +#define BACKEND_EXAMPLE_OPTS "rsS:iuUt:v:n:" /*! Variable to control if reset code is run. * The reset code inserts "extra XML" which assumes ietf-interfaces is @@ -119,6 +119,11 @@ static int _transaction_log = 0; static char *_validate_fail_xpath = NULL; static int _validate_fail_toggle = 0; /* fail at validate and commit */ +/* -n + * Network namespace for starting restconf process in another namespace +*/ +static char *_proc_netns = NULL; + /* forward */ static int example_stream_timer_setup(clicon_handle h); @@ -1079,6 +1084,9 @@ clixon_plugin_init(clicon_handle h) case 'v': /* validate fail */ _validate_fail_xpath = optarg; break; + case 'n': /* process restconf namespace*/ + _proc_netns = optarg; + break; } /* Example stream initialization: @@ -1142,7 +1150,36 @@ clixon_plugin_init(clicon_handle h) else if (upgrade_callback_register(h, xml_changelog_upgrade, NULL, NULL) < 0) goto done; + { + char **argv = NULL; + int i; + int nr; + char dbgstr[8]; + char wwwstr[64]; + nr = 4; + if (clicon_debug_get() != 0) + nr += 2; + if ((argv = calloc(nr, sizeof(char *))) == NULL){ + clicon_err(OE_UNIX, errno, "calloc"); + goto done; + } + i = 0; + snprintf(wwwstr, sizeof(wwwstr)-1, "%s/clixon_restconf", clicon_option_str(h, "CLICON_WWWDIR")); + argv[i++] = wwwstr; + argv[i++] = "-f"; + argv[i++] = clicon_option_str(h, "CLICON_CONFIGFILE"); + if (clicon_debug_get() != 0){ + argv[i++] = "-D"; + snprintf(dbgstr, sizeof(dbgstr)-1, "%d", clicon_debug_get()); + argv[i++] = dbgstr; + } + argv[i++] = NULL; + if (clixon_process_register(h, "restconf", _proc_netns, argv, nr) < 0) + goto done; + if (argv != NULL) + free(argv); + } /* Return plugin API */ return &api; done: diff --git a/include/clixon_config.h.in b/include/clixon_config.h.in index 59ed08e8..16421fa9 100644 --- a/include/clixon_config.h.in +++ b/include/clixon_config.h.in @@ -39,12 +39,21 @@ /* Define to 1 if you have the `cligen' library (-lcligen). */ #undef HAVE_LIBCLIGEN +/* Define to 1 if you have the `crypto' library (-lcrypto). */ +#undef HAVE_LIBCRYPTO + /* Define to 1 if you have the `curl' library (-lcurl). */ #undef HAVE_LIBCURL /* Define to 1 if you have the `dl' library (-ldl). */ #undef HAVE_LIBDL +/* Define to 1 if you have the `event' library (-levent). */ +#undef HAVE_LIBEVENT + +/* Define to 1 if you have the `event_openssl' library (-levent_openssl). */ +#undef HAVE_LIBEVENT_OPENSSL + /* Define to 1 if you have the `evhtp' library (-levhtp). */ #undef HAVE_LIBEVHTP @@ -54,15 +63,24 @@ /* Define to 1 if you have the `m' library (-lm). */ #undef HAVE_LIBM +/* Define to 1 if you have the `pthread' library (-lpthread). */ +#undef HAVE_LIBPTHREAD + /* Define to 1 if you have the `socket' library (-lsocket). */ #undef HAVE_LIBSOCKET +/* Define to 1 if you have the `ssl' library (-lssl). */ +#undef HAVE_LIBSSL + /* Define to 1 if you have the `xml2' library (-lxml2). */ #undef HAVE_LIBXML2 /* Define to 1 if you have the header file. */ #undef HAVE_MEMORY_H +/* Define to 1 if you have the `setns' function. */ +#undef HAVE_SETNS + /* Define to 1 if you have the `sigaction' function. */ #undef HAVE_SIGACTION @@ -129,6 +147,9 @@ /* Restconf package */ #undef WITH_RESTCONF +/* WWW dir for restconf daemon */ +#undef WWWDIR + /* WWW user for restconf daemon */ #undef WWWUSER diff --git a/lib/clixon/clixon.h.in b/lib/clixon/clixon.h.in index 8d2bfec0..27a44328 100644 --- a/lib/clixon/clixon.h.in +++ b/lib/clixon/clixon.h.in @@ -77,6 +77,7 @@ extern "C" { #include #include #include +#include #include #include #include diff --git a/lib/clixon/clixon_proc.h b/lib/clixon/clixon_proc.h new file mode 100644 index 00000000..ba1a65b8 --- /dev/null +++ b/lib/clixon/clixon_proc.h @@ -0,0 +1,51 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren + Copyright (C) 2017-2019 Olof Hagsand + Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate) + + This file is part of CLIXON. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the "GPL"), + in which case the provisions of the GPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of the GPL, and not to allow others to + use your version of this file under the terms of Apache License version 2, + indicate your decision by deleting the provisions above and replace them with + the notice and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the Apache License version 2 or the GPL. + + ***** END LICENSE BLOCK ***** + + */ + +#ifndef _CLIXON_PROC_H_ +#define _CLIXON_PROC_H_ + +/* + * Prototypes + */ +int clixon_proc_run(char **argv, void (outcb)(char *), int doerr); +int clixon_proc_background(char **argv, char *netns, pid_t *pid); +int clixon_proc_daemon(char **argv, pid_t *pid); +int clixon_process_register(clicon_handle h, const char *name, const char *netns, char **argv, int argc); +int clixon_process_delete_all(clicon_handle h); +int clixon_process_operation(clicon_handle h, char *name, char *op, int *status); + +#endif /* _CLIXON_PROC_H_ */ diff --git a/lib/clixon/clixon_sig.h b/lib/clixon/clixon_sig.h index 51c29341..a09c0d99 100644 --- a/lib/clixon/clixon_sig.h +++ b/lib/clixon/clixon_sig.h @@ -44,12 +44,13 @@ typedef void (*sigfn_t)(int); /* * Prototypes */ -int set_signal(int signo, void (*handler)(int), void (**oldhandler)(int)); +int set_signal(int signo, void (*handler)(int), void (**oldhandler)(int)); void clicon_signal_block(int); void clicon_signal_unblock(int); -int pidfile_get(char *pidfile, pid_t *pid0); -int pidfile_write(char *pidfile); -int pidfile_zapold(pid_t pid); +int pidfile_get_fd(FILE *f, pid_t *pid0); +int pidfile_get(char *pidfile, pid_t *pid0); +int pidfile_write(char *pidfile); +int pidfile_zapold(pid_t pid); #endif /* _CLIXON_SIG_H_ */ diff --git a/lib/src/Makefile.in b/lib/src/Makefile.in index d9ab327d..c4a9520b 100644 --- a/lib/src/Makefile.in +++ b/lib/src/Makefile.in @@ -75,7 +75,7 @@ INCLUDES = -I. @INCLUDES@ -I$(top_srcdir)/lib/clixon -I$(top_srcdir)/include -I$ SRC = clixon_sig.c clixon_uid.c clixon_log.c clixon_err.c clixon_event.c \ clixon_string.c clixon_regex.c clixon_handle.c clixon_file.c \ clixon_xml.c clixon_xml_io.c clixon_xml_sort.c clixon_xml_map.c clixon_xml_vec.c \ - clixon_xml_bind.c clixon_json.c \ + clixon_xml_bind.c clixon_json.c clixon_proc.c \ clixon_yang.c clixon_yang_type.c clixon_yang_module.c clixon_yang_parse_lib.c \ clixon_yang_cardinality.c clixon_xml_changelog.c clixon_xml_nsctx.c \ clixon_path.c clixon_validate.c \ diff --git a/lib/src/clixon_proc.c b/lib/src/clixon_proc.c new file mode 100644 index 00000000..5000fc49 --- /dev/null +++ b/lib/src/clixon_proc.c @@ -0,0 +1,559 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren + Copyright (C) 2017-2019 Olof Hagsand + Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate) + + This file is part of CLIXON. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the "GPL"), + in which case the provisions of the GPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of the GPL, and not to allow others to + use your version of this file under the terms of Apache License version 2, + indicate your decision by deleting the provisions above and replace them with + the notice and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the Apache License version 2 or the GPL. + + ***** END LICENSE BLOCK ***** + + * Processes daemons + */ + +#ifdef HAVE_CONFIG_H +#include "clixon_config.h" +#endif + + +#ifdef HAVE_SETNS /* linux network namespaces */ +#define _GNU_SOURCE +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_SETNS /* linux network namespaces */ +#include /* setns / unshare */ +#endif +#include +#include +#include +#include +#include +#include +#include + +#include + +/* clicon */ +#include "clixon_err.h" +#include "clixon_log.h" +#include "clixon_queue.h" +#include "clixon_hash.h" +#include "clixon_handle.h" +#include "clixon_sig.h" +#include "clixon_string.h" +#include "clixon_queue.h" +#include "clixon_proc.h" + +/* + * Child process ID + * XXX Really shouldn't be a global variable + */ +static int _clicon_proc_child = 0; + +/* + * Make sure child is killed by ctrl-C + */ +static void +clixon_proc_sigint(int sig) +{ + if (_clicon_proc_child > 0) + kill(_clicon_proc_child, SIGINT); +} + +/*! Fork a child process, setup a pipe between parent and child. + * Allowing parent to read the output of the child. + * @param[in] doerr If non-zero, stderr will be directed to the pipe as well. + * The pipe for the parent to write + * to the child is closed and cannot be used. + * + * When child process is done with the pipe setup, execute the specified + * command, execv(argv[0], argv). + * + * When parent is done with the pipe setup it will read output from the child + * until eof. The read output will be sent to the specified output callback, + * 'outcb' function. + * + * @param[in] argv NULL-terminated Argument vector + * @param[in] outcb + * @param[in] doerr + * @retval number Matches (processes affected). + * @retval -1 Error. + */ +int +clixon_proc_run(char **argv, + void (outcb)(char *), + int doerr) +{ + int retval = -1; + char buf[512]; + int outfd[2] = { -1, -1 }; + int n; + int status; + pid_t child; + sigfn_t oldhandler = NULL; + sigset_t oset; + int sig = 0; + + if (argv == NULL){ + clicon_err(OE_UNIX, EINVAL, "argv is NULL"); + goto done; + } + if (pipe(outfd) == -1){ + clicon_err(OE_UNIX, errno, "pipe"); + goto done; + } + sigprocmask(0, NULL, &oset); + set_signal(SIGINT, clixon_proc_sigint, &oldhandler); + sig++; + + if ((child = fork()) < 0) { + clicon_err(OE_UNIX, errno, "fork"); + goto done; + } + + if (child == 0) { /* Child */ + + /* Unblock all signals except TSTP */ + clicon_signal_unblock(0); + signal(SIGTSTP, SIG_IGN); + + close(outfd[0]); /* Close unused read ends */ + outfd[0] = -1; + + /* Divert stdout and stderr to pipes */ + dup2(outfd[1], STDOUT_FILENO); + if (doerr) + dup2(outfd[1], STDERR_FILENO); + + execvp(argv[0], argv); + perror("execvp"); /* Shouldnt reach here */ + exit(-1); + } + + /* Parent */ + + /* Close unused write ends */ + close(outfd[1]); + outfd[1] = -1; + + /* Read from pipe */ + while ((n = read(outfd[0], buf, sizeof(buf)-1)) != 0) { + if (n < 0) { + if (errno == EINTR) + continue; + break; + } + buf[n] = '\0'; + /* Pass read data to callback function is defined */ + if (outcb) + outcb(buf); + } + + /* Wait for child to finish */ + if(waitpid(child, &status, 0) == child) + retval = WEXITSTATUS(status); + done: + /* Clean up all pipes */ + if (outfd[0] != -1) + close(outfd[0]); + if (outfd[1] != -1) + close(outfd[1]); + if (sig){ /* Restore sigmask and fn */ + sigprocmask(SIG_SETMASK, &oset, NULL); + set_signal(SIGINT, oldhandler, NULL); + } + return retval; +} + +/*! Fork and exec a sub-process, let it run and return pid + * + * @param[in] argv NULL-terminated Argument vector + * @param[in] netns Network namespace (or NULL) + * @param[out] pid + * @retval 0 OK + * @retval -1 Error. + * @see clixon_proc_daemon + * @note SIGCHLD is set to IGN here. Maybe it should be done in main? + */ +int +clixon_proc_background(char **argv, + char *netns, + pid_t *pid0) +{ + int retval = -1; + pid_t child; + int i; + sigfn_t oldhandler = NULL; + sigset_t oset; + struct rlimit rlim = {0, }; + + clicon_debug(1, "%s netns:%s", __FUNCTION__, netns); + if (argv == NULL){ + clicon_err(OE_UNIX, EINVAL, "argv is NULL"); + goto quit; + } + /* Before here call quit on error */ + sigprocmask(0, NULL, &oset); + set_signal(SIGINT, clixon_proc_sigint, &oldhandler); + /* Now call done on error */ + + if ((child = fork()) < 0) { + clicon_err(OE_UNIX, errno, "fork"); + goto done; + } + if (child == 0) { /* Child */ + char nsfile[PATH_MAX]; + int nsfd; + + clicon_debug(1, "%s child", __FUNCTION__); + clicon_signal_unblock(0); + signal(SIGTSTP, SIG_IGN); + if (chdir("/") < 0){ + clicon_err(OE_UNIX, errno, "chdirq"); + exit(1); + } + /* Close open descriptors */ + if ( ! getrlimit(RLIMIT_NOFILE, &rlim)) + for (i = 0; i < rlim.rlim_cur; i++) + close(i); +#ifdef HAVE_SETNS /* linux network namespaces */ + /* If network namespace is defined, let child join it + * XXX: this is work-in-progress + */ + if (netns != NULL) { + snprintf(nsfile, PATH_MAX, "/var/run/netns/%s", netns); /* see man setns / ip netns */ + clicon_debug(1, "%s nsfile:%s", __FUNCTION__, nsfile); + /* Change network namespace */ + if ((nsfd = open(nsfile, O_RDONLY | O_CLOEXEC)) < 0){ + clicon_err(OE_UNIX, errno, "open"); + exit(1); + } + if (setns(nsfd, 0) < 0){ /* Join that namespace */ + clicon_err(OE_UNIX, errno, "setns"); + exit(1); + } + close(nsfd); + if (unshare(CLONE_NEWNS) < 0){ + clicon_err(OE_UNIX, errno, "unshare"); + exit(1); + } + } +#endif /* HAVE_SETNS */ + clicon_debug(1, "%s argv0:%s", __FUNCTION__, argv[0]); + if (execv(argv[0], argv) < 0) { + clicon_err(OE_UNIX, errno, "execv"); + exit(1); + } + /* Not reached */ + } + done: + sigprocmask(SIG_SETMASK, &oset, NULL); + set_signal(SIGINT, oldhandler, NULL); + /* Ensure reap proc child in same session */ + if (set_signal(SIGCHLD, SIG_IGN, NULL) < 0) + goto quit; + *pid0 = child; + retval = 0; + quit: + clicon_debug(1, "%s retval:%d child:%d", __FUNCTION__, retval, child); + return retval; +} + +/*! Double fork and exec a sub-process as a daemon, let it run and return pid + * + * @param[in] argv NULL-terminated Argument vector + * @param[out] pid + * @retval 0 OK + * @retval -1 Error. + * @see clixon_proc_background + */ +int +clixon_proc_daemon(char **argv, + pid_t *pid0) +{ + int retval = -1; + int status; + pid_t child; + pid_t pid; + int i; + struct rlimit rlim = {0, }; + FILE *fpid = NULL; + + if (argv == NULL){ + clicon_err(OE_UNIX, EINVAL, "argv is NULL"); + goto done; + } + if ((fpid = tmpfile()) == NULL){ + clicon_err(OE_UNIX, errno, "tmpfile"); + goto done; + } + if ((child = fork()) < 0) { + clicon_err(OE_UNIX, errno, "fork"); + goto done; + } + if (child == 0) { /* Child */ + clicon_signal_unblock(0); + if ((pid = fork()) < 0) { + clicon_err(OE_UNIX, errno, "fork"); + return -1; + } + if (pid == 0) { /* Grandchild, create new session */ + setsid(); + if (chdir("/") < 0){ + clicon_err(OE_UNIX, errno, "chdirq"); + exit(1); + } + /* Close open descriptors */ + if ( ! getrlimit(RLIMIT_NOFILE, &rlim)) + for (i = 0; i < rlim.rlim_cur; i++) + close(i); + if (execv(argv[0], argv) < 0) { + clicon_err(OE_UNIX, errno, "execv"); + exit(1); + } + /* Not reached */ + } + if (fprintf(fpid, "%ld\n", (long) pid) < 1){ + clicon_err(OE_DAEMON, errno, "fprintf Could not write pid"); + goto done; + } + fclose(fpid); + // waitpid(pid, &status2, 0); + exit(pid); + } + + if (waitpid(child, &status, 0) > 0){ + pidfile_get_fd(fpid, pid0); + retval = 0; + } + fclose(fpid); + done: + clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); + return retval; +} + +/*--------------------------------------------------------------------------------* + * Process management: start/stop registered processes for internal use + */ + +/* + * Types + */ +typedef struct { + qelem_t pe_qelem; /* List header */ + char *pe_name; /* Name of process used for internal use */ + char *pe_netns; /* Network namespace */ + char **pe_argv; /* argv with command as element 0 and NULL-terminated */ + pid_t pe_pid; +} process_entry_t; + +/* List of process callback entries */ +static process_entry_t *proc_entry_list = NULL; + +/*! Register an internal process + * + * @param[in] h Clixon handle + * @param[in] name Process name + * @param[in] netns Namespace netspace (or NULL) + * @param[in] argv NULL-terminated vector of vectors + * @retval 0 OK + * @retval -1 Error + * @note name, netns, argv and its elements are all copied / re-alloced. + */ +int +clixon_process_register(clicon_handle h, + const char *name, + const char *netns, + char **argv, + int argc) +{ + int retval = -1; + process_entry_t *pe = NULL; + int i; + + if (name == NULL){ + clicon_err(OE_DB, EINVAL, "name is NULL"); + goto done; + } + if (argv == NULL){ + clicon_err(OE_DB, EINVAL, "argv is NULL"); + goto done; + } + if ((pe = malloc(sizeof(process_entry_t))) == NULL) { + clicon_err(OE_DB, errno, "malloc"); + goto done; + } + memset(pe, 0, sizeof(*pe)); + if ((pe->pe_name = strdup(name)) == NULL){ + clicon_err(OE_DB, errno, "strdup name"); + goto done; + } + if (netns && (pe->pe_netns = strdup(netns)) == NULL){ + clicon_err(OE_DB, errno, "strdup netns"); + goto done; + } + if ((pe->pe_argv = calloc(argc, sizeof(char *))) == NULL){ + clicon_err(OE_UNIX, errno, "calloc"); + goto done; + } + for (i=0; ipe_argv[i] = strdup(argv[i])) == NULL){ + clicon_err(OE_UNIX, errno, "strdup"); + } + } + ADDQ(pe, proc_entry_list); + retval = 0; + done: + return retval; +} + +/*! Delete all Upgrade callbacks + */ +int +clixon_process_delete_all(clicon_handle h) +{ + process_entry_t *pe; + char **pa; + + while((pe = proc_entry_list) != NULL) { + DELQ(pe, proc_entry_list, process_entry_t *); + if (pe->pe_name) + free(pe->pe_name); + if (pe->pe_netns) + free(pe->pe_netns); + if (pe->pe_argv){ + for (pa = pe->pe_argv; *pa != NULL; pa++){ + if (*pa) + free(*pa); + } + free(pe->pe_argv); + } + free(pe); + } + return 0; +} + +static int +proc_op_run(process_entry_t *pe, + int *runp) +{ + int retval = -1; + int run; + pid_t pid; + + run = 0; + if ((pid = pe->pe_pid) != 0){ /* if 0 stopped */ + /* Check if lives */ + run = 1; + if ((kill(pid, 0)) < 0){ + if (errno == ESRCH){ + run = 0; + } + else{ + clicon_err(OE_UNIX, errno, "kill(%d)", pid); + goto done; + } + } + } + if (runp) + *runp = run; + retval = 0; + done: + return retval; +} + +/*! Upgrade specific module identified by namespace, search matching callbacks + * + * @param[in] h clicon handle + * @param[in] name Name of process + * @param[in] op start, stop. + * @param[out] status true if process is running / false if not running on entry + * @retval -1 Error + * @retval 0 OK + * @see upgrade_callback_reg_fn which registers the callbacks + */ +int +clixon_process_operation(clicon_handle h, + char *name, + char *op, + int *status) +{ + int retval = -1; + process_entry_t *pe; + int run; + + clicon_debug(1, "%s name:%s op:%s", __FUNCTION__, name, op); + if (proc_entry_list == NULL) + goto ok; + pe = proc_entry_list; + do { + if (strcmp(pe->pe_name, name) == 0){ + /* Check if running */ + if (proc_op_run(pe, &run) < 0) + goto done; + if (status) /* Store as output parameter */ + *status = run; + if (strcmp(op, "stop") == 0 || + strcmp(op, "restart") == 0){ + if (run) + pidfile_zapold(pe->pe_pid); /* Ensures its dead */ + pe->pe_pid = 0; /* mark as dead */ + run = 0; + } + if (strcmp(op, "start") == 0 || + strcmp(op, "restart") == 0){ + if (run == 1){ + ; /* Already runs */ + } + else{ + if (clixon_proc_background(pe->pe_argv, pe->pe_netns, &pe->pe_pid) < 0) + goto done; + } + } + else if (strcmp(op, "status") == 0){ + ; /* status already set */ + } + break; /* hit break here */ + } + pe = NEXTQ(process_entry_t *, pe); + } while (pe != proc_entry_list); + ok: + retval = 0; + done: + clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); + return retval; +} diff --git a/lib/src/clixon_sig.c b/lib/src/clixon_sig.c index 70971613..5722f91b 100644 --- a/lib/src/clixon_sig.c +++ b/lib/src/clixon_sig.c @@ -80,7 +80,6 @@ set_signal(int signo, #endif } - /*! Block signal. * @param[in] sig Signal number to block, If 0, block all signals */ @@ -117,34 +116,50 @@ clicon_signal_unblock (int sig) sigprocmask (SIG_UNBLOCK, &set, NULL); } +/*! Read pidfile and return pid using file descriptor + * + * @param[in] pidfile Name of pidfile + * @param[out] pid Process id of (eventual) existing daemon process + * @retval 0 OK. if pid > 0 old process exists w that pid + * @retval -1 Error, and clicon_err() called + */ +int +pidfile_get_fd(FILE *f, + pid_t *pid0) +{ + char *ptr; + char buf[32]; + pid_t pid; + + *pid0 = 0; + ptr = fgets(buf, sizeof(buf), f); + if (ptr != NULL && (pid = atoi(ptr)) > 1) { + if (kill(pid, 0) == 0 || errno != ESRCH) { + /* Yes there is a process */ + *pid0 = pid; + } + } + return 0; +} + /*! Read pidfile and return pid, if any * * @param[in] pidfile Name of pidfile - * @param[out] pid0 Process id of (eventual) existing daemon process + * @param[out] pid Process id of (eventual) existing daemon process * @retval 0 OK. if pid > 0 old process exists w that pid * @retval -1 Error, and clicon_err() called */ int pidfile_get(char *pidfile, - pid_t *pid0) + pid_t *pid) { FILE *f; - char *ptr; - char buf[32]; - pid_t pid; - if ((f = fopen (pidfile, "r")) != NULL){ - ptr = fgets(buf, sizeof(buf), f); - fclose (f); - if (ptr != NULL && (pid = atoi (ptr)) > 1) { - if (kill (pid, 0) == 0 || errno != ESRCH) { - /* Yes there is a process */ - *pid0 = pid; - return 0; - } - } + *pid = 0; + if ((f = fopen(pidfile, "r")) != NULL){ + pidfile_get_fd(f, pid); + fclose(f); } - *pid0 = 0; return 0; } @@ -169,7 +184,7 @@ pidfile_zapold(pid_t pid) clicon_err(OE_UNIX, errno, "usleep"); goto done; } - if ((kill (pid, 0)) < 0){ + if ((kill(pid, 0)) < 0){ if (errno != ESRCH){ clicon_err(OE_DAEMON, errno, "Killing old demon"); goto done; diff --git a/test/test_restconf_rpc.sh b/test/test_restconf_rpc.sh new file mode 100755 index 00000000..2c9a99fd --- /dev/null +++ b/test/test_restconf_rpc.sh @@ -0,0 +1,188 @@ +#!/usr/bin/env bash +# Restconf direct start/stop using RPC (as alternative to systemd or other) +# Also try ip netns + +# Magic line must be first in script (see README.md) +s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi + +APPNAME=example + +cfg=$dir/conf.xml + +# Cant get it to work in the general case, single tests work fine +# More specifically, if mem.sh background netconf, netconf crashes +if [ $valgrindtest -ne 0 ]; then + echo "...skipped " + return 0 # skip +fi + +cat < $cfg + + $cfg + /usr/local/share/clixon + $IETFRFC + $dir + /usr/local/lib/$APPNAME/clispec + /usr/local/lib/$APPNAME/backend + example_backend.so$ + /usr/local/lib/$APPNAME/restconf + false + /usr/local/lib/$APPNAME/cli + $APPNAME + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile + /usr/local/var/$APPNAME + true + $RESTCONFIG + +EOF + +new "test params: -f $cfg" +if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -z -f $cfg + if [ $? -ne 0 ]; then + err + fi + new "start backend -s init -f $cfg" + start_backend -s init -f $cfg + + new "waiting" + wait_backend +fi + +new "kill old restconf" +stop_restconf_pre + +new "1)check no restconf" +ps=$(ps aux|grep "$WWWDIR/clixon_restconf" | grep -v grep) +if [ -n "$ps" ]; then + err "No restconf running" "$ps" +fi + +new "2)check status off" +expecteof "$clixon_netconf -qf $cfg" 0 "restconfstatus]]>]]>" "false]]>]]>" + +new "start restconf" +expecteof "$clixon_netconf -qf $cfg" 0 "restconfstart]]>]]>" "]]>]]>" + +new "3)check restconf on" +ps=$(ps aux|grep "$WWWDIR/clixon_restconf -f $cfg" | grep -v grep) +if [ -z "$ps" ]; then + err "restconf running" +fi + +new "4)check status on" +expecteof "$clixon_netconf -qf $cfg" 0 "restconfstatus]]>]]>" "true]]>]]>" + +new "stop restconf" +expecteof "$clixon_netconf -qf $cfg" 0 "restconfstop]]>]]>" "]]>]]>" + +new "start restconf again" +expecteof "$clixon_netconf -qf $cfg" 0 "restconfstart]]>]]>" "]]>]]>" + +new "5)check restconf on" +ps=$(ps aux|grep "$WWWDIR/clixon_restconf -f $cfg" | grep -v grep) +if [ -z "$ps" ]; then + err "A restconf running" +fi + +new "kill restconf" +stop_restconf_pre + +new "6)check no restconf" +ps=$(ps aux|grep "$WWWDIR/clixon_restconf" | grep -v grep) +if [ -n "$ps" ]; then + err "No restconf running" "$ps" +fi + +new "restart restconf" +expecteof "$clixon_netconf -qf $cfg" 0 "restconfrestart]]>]]>" "]]>]]>" + +new "7)check status on" +expecteof "$clixon_netconf -qf $cfg" 0 "restconfstatus]]>]]>" "true]]>]]>" + +pid0=$(pgrep clixon_restconf) + +new "restart restconf" +expecteof "$clixon_netconf -qf $cfg" 0 "restconfrestart]]>]]>" "]]>]]>" + +new "8)check status on" +expecteof "$clixon_netconf -qf $cfg" 0 "restconfstatus]]>]]>" "true]]>]]>" + +new "9)check new pid" +pid1=$(pgrep clixon_restconf) +if [ $pid0 -eq $pid1 ]; then + err "Different pids" "pid0:$pid0 = pid1:$pid1" +fi + +if [ $BE -ne 0 ]; then + new "Kill backend" + # Check if premature kill + pid=$(pgrep -u root -f clixon_backend) + if [ -z "$pid" ]; then + err "backend already dead" + fi + # kill backend + stop_backend -f $cfg + + new "10)check no restconf" + ps=$(ps aux|grep "$WWWDIR/clixon_restconf" | grep -v grep) + if [ -n "$ps" ]; then + err "No restconf running" "$ps" + fi +fi + +if false; then # Work in progress +#------------------------------- +# Now in a separate network namespace +new "restconf rpc in network namespace" +netns=xxx +sudo ip netns delete $netns +#sudo ip netns add $netns + +new "test params: -f $cfg" +if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -z -f $cfg + if [ $? -ne 0 ]; then + err + fi + new "start backend -s init -f $cfg -- -n $netns" + start_backend -s init -f $cfg -- -n $netns + + new "waiting" + wait_backend +fi + +new "kill old restconf" +stop_restconf_pre + +new "netconf start restconf" +expecteof "$clixon_netconf -qf $cfg" 0 "restconfstart]]>]]>" "]]>]]>" + +new "10)check status on" +expecteof "$clixon_netconf -qf $cfg" 0 "restconfstatus]]>]]>" "true]]>]]>" + +new "stop restconf" +expecteof "$clixon_netconf -qf $cfg" 0 "restconfstop]]>]]>" "]]>]]>" + +if [ $BE -ne 0 ]; then + new "Kill backend" + # Check if premature kill + pid=$(pgrep -u root -f clixon_backend) + if [ -z "$pid" ]; then + err "backend already dead" + fi + # kill backend + stop_backend -f $cfg + + new "11)check no restconf" + ps=$(ps aux|grep "$WWWDIR/clixon_restconf" | grep -v grep) +fi + +sudo ip netns delete $netns + +fi # namespaces + +rm -rf $dir diff --git a/yang/clixon/clixon-lib@2020-12-08.yang b/yang/clixon/clixon-lib@2020-12-08.yang index 70e18fd5..43a6d90a 100644 --- a/yang/clixon/clixon-lib@2020-12-08.yang +++ b/yang/clixon/clixon-lib@2020-12-08.yang @@ -164,14 +164,10 @@ module clixon-lib { description "One of the strings 'start', 'stop', 'restart', or 'status'."; } - leaf namespace { - type string; - description - "Network namespace."; - } } output { leaf status { + description "For status: is the process running?"; type boolean; } }