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