diff --git a/plugins/bootmisc.c b/plugins/bootmisc.c index b25fffa6..fd56eb09 100644 --- a/plugins/bootmisc.c +++ b/plugins/bootmisc.c @@ -167,6 +167,8 @@ static void setup(void *arg) makedir("/var/run/pluto", 0755); /* Needed by Openswan */ makedir("/var/run/quagga", 0755); /* Needed by Quagga */ makedir("/var/log/quagga", 0755); /* Needed by Quagga */ + makedir("/var/run/frr", 0755); /* Needed by FRR */ + makedir("/var/log/frr", 0755); /* Needed by FRR */ makedir("/var/run/sshd", 01755); /* OpenSSH */ gid = getgroup("tty"); diff --git a/plugins/pidfile.c b/plugins/pidfile.c index 277351af..e2a55023 100644 --- a/plugins/pidfile.c +++ b/plugins/pidfile.c @@ -22,11 +22,10 @@ * THE SOFTWARE. */ +#include #include #include #include -#include -#include #include "finit.h" #include "cond.h" @@ -34,28 +33,15 @@ #include "pid.h" #include "plugin.h" #include "service.h" +#include "iwatch.h" -struct wd_entry { - TAILQ_ENTRY(wd_entry) link; - char *path; - int fd; -}; +static struct iwatch iw_pidfile; -struct context { - int fd; - TAILQ_HEAD(, wd_entry) wd_list; -}; -static struct context pidfile_ctx; - -static int watcher_add(struct context *ctx, char *path) +static int pidfile_add_path(struct iwatch *iw, char *path) { - struct wd_entry *wd; - uint32_t mask = IN_ONLYDIR | IN_CREATE | IN_ATTRIB | IN_DELETE | IN_MODIFY | IN_MOVED_TO; char *ptr; - int fd; - _d("pidfile: Adding new watcher for path %s", path); ptr = strstr(path, "run/"); if (ptr) { char *slash; @@ -66,44 +52,16 @@ static int watcher_add(struct context *ctx, char *path) ptr = slash++; slash = strchr(ptr, '/'); if (slash) { - _d("pidfile: Path too deep, skipping."); + _d("Path too deep, skipping."); return -1; } } } - fd = inotify_add_watch(ctx->fd, path, mask); - if (fd < 0) { - _pe("inotify_add_watch()"); - return -1; - } - - wd = malloc(sizeof(struct wd_entry)); - if (!wd) { - _pe("Failed allocating new `struct wd_entry`"); - inotify_rm_watch(ctx->fd, fd); - return -1; - } - - wd->path = path; - wd->fd = fd; - TAILQ_INSERT_HEAD(&ctx->wd_list, wd, link); - - return 0; + return iwatch_add(iw, path, IN_ONLYDIR); } -static int watcher_del(struct context *ctx, struct wd_entry *wde) -{ - _d("pidfile: Removing watcher for removed path %s", wde->path); - TAILQ_REMOVE(&ctx->wd_list, wde, link); - inotify_rm_watch(ctx->fd, wde->fd); - free(wde->path); - free(wde); - - return 0; -} - -static void update_conds(char *dir, char *name, uint32_t mask) +static void pidfile_update_conds(char *dir, char *name, uint32_t mask) { char cond[MAX_COND_LEN]; char fn[PATH_MAX]; @@ -112,13 +70,16 @@ static void update_conds(char *dir, char *name, uint32_t mask) _d("Got dir: %s, name: %s, mask: %08x", dir, name, mask); snprintf(fn, sizeof(fn), "%s/%s", dir, name); + if (fnmatch("*\\.pid", fn, 0) && fnmatch("*/pid", fn, 0)) + return; + svc = svc_find_by_pidfile(fn); if (!svc) { - _d("pidfile: No matching svc for %s", fn); + _d("No matching svc for %s", fn); return; } - _d("pidfile: Found svc %s for %s with pid %d", svc->name, fn, svc->pid); + _d("Found svc %s for %s with pid %d", svc->name, fn, svc->pid); mkcond(svc, cond, sizeof(cond)); if (mask & (IN_CREATE | IN_ATTRIB | IN_MODIFY | IN_MOVED_TO)) { @@ -138,7 +99,7 @@ static void update_conds(char *dir, char *name, uint32_t mask) } /* synthesize events in case of new run dirs */ -static void scan_for_pidfiles(struct context *ctx, char *dir, int len) +static void pidfile_scandir(struct iwatch *iw, char *dir, int len) { glob_t gl; size_t i; @@ -157,47 +118,33 @@ static void scan_for_pidfiles(struct context *ctx, char *dir, int len) for (i = 0; i < gl.gl_pathc; i++) { _d("scan found %s", gl.gl_pathv[i]); - update_conds(dir, gl.gl_pathv[i], IN_CREATE); + pidfile_update_conds(dir, gl.gl_pathv[i], IN_CREATE); } globfree(&gl); } -static void handle_dir(struct context *ctx, struct wd_entry *wde, char *name, int mask) +/* + * create/remove sub-directory in monitored directory + */ +static void pidfile_handle_dir(struct iwatch *iw, char *dir, char *name, int mask) { - char *path; - int plen; - int exist = 0; + char path[strlen(dir) + strlen(name) + 2]; + struct iwatch_path *iwp; - plen = snprintf(NULL, 0, "%s/%s", wde->path, name) + 1; - path = malloc(plen); - if (!path) { - _pe("Failed allocating path buffer for watcher"); - return; - } - snprintf(path, plen, "%s/%s", wde->path, name); - _d("pidfile: path is %s", path); + snprintf(path, sizeof(path), "%s/%s", dir, name); + _d("path: %s", path); - TAILQ_FOREACH(wde, &ctx->wd_list, link) { - if (!strcmp(wde->path, path)) { - exist = 1; - break; - } - } + iwp = iwatch_find_by_path(iw, path); if (mask & IN_CREATE) { - if (!exist) { - int rc = watcher_add(ctx, path); - - scan_for_pidfiles(ctx, path, plen); - if (!rc) - return; + if (!iwp) { + pidfile_add_path(iw, path); + pidfile_scandir(iw, path, sizeof(path)); } } else if (mask & IN_DELETE) { - if (exist) - watcher_del(&pidfile_ctx, wde); + if (iwp) + iwatch_del(&iw_pidfile, iwp); } - - free(path); } static void pidfile_callback(void *arg, int fd, int events) @@ -213,43 +160,42 @@ static void pidfile_callback(void *arg, int fd, int events) return; } - _d("pidfile: Entering ... reading %zu bytes into ev_buf[]", buflen - 1); + _d("Entering ... reading %zu bytes into ev_buf[]", buflen - 1); sz = read(fd, buf, buflen - 1); if (sz <= 0) { _pe("invalid inotify event"); goto done; } buf[sz] = 0; - _d("pidfile: Read %zd bytes, processing ...", sz); + _d("Read %zd bytes, processing ...", sz); off = 0; for (off = 0; off < sz; off += sizeof(*ev) + ev->len) { - struct wd_entry *wde; + struct iwatch_path *iwp; ev = (struct inotify_event *)&buf[off]; - _d("pidfile: path %s, event: 0x%08x", ev->name, ev->mask); + _d("path %s, event: 0x%08x", ev->name, ev->mask); if (!ev->mask) continue; /* Find base path for this event */ - TAILQ_FOREACH(wde, &pidfile_ctx.wd_list, link) { - if (wde->fd == ev->wd) - break; - } + iwp = iwatch_find_by_wd(&iw_pidfile, ev->wd); + if (!iwp) + continue; if (ev->mask & IN_ISDIR) { - handle_dir(&pidfile_ctx, wde, ev->name, ev->mask); + pidfile_handle_dir(&iw_pidfile, iwp->path, ev->name, ev->mask); continue; } if (ev->mask & IN_DELETE) { - _d("pidfile %s/%s removed ...", wde->path, ev->name); + _d("pidfile %s/%s removed ...", iwp->path, ev->name); continue; } if (ev->mask & (IN_CREATE | IN_ATTRIB | IN_MODIFY | IN_MOVED_TO)) - update_conds(wde->path, ev->name, ev->mask); + pidfile_update_conds(iwp->path, ev->name, ev->mask); } done: free(buf); @@ -305,14 +251,10 @@ static void pidfile_init(void *arg) return; } - TAILQ_INIT(&pidfile_ctx.wd_list); - if (watcher_add(&pidfile_ctx, path)) - close(pidfile_ctx.fd); - _d("pidfile monitor active"); + if (pidfile_add_path(&iw_pidfile, path)) + iwatch_exit(&iw_pidfile); } -static struct context pidfile_ctx; - /* * When performing an `initctl reload` with one (unchanged) service * depending on, e.g. `net/iface/lo`, its condition will not be set @@ -331,36 +273,27 @@ static plugin_t plugin = { .name = __FILE__, .hook[HOOK_BASEFS_UP] = { .cb = pidfile_init }, .hook[HOOK_SVC_RECONF] = { .cb = pidfile_reconf }, - .io = { - .cb = pidfile_callback, - .flags = PLUGIN_IO_READ, - }, .depends = { "netlink" }, }; PLUGIN_INIT(plugin_init) { - pidfile_ctx.fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); - if (pidfile_ctx.fd < 0) { - _pe("inotify_init()"); + int fd; + + fd = iwatch_init(&iw_pidfile); + if (fd < 0) return; - } + + plugin.io.fd = fd; + plugin.io.cb = pidfile_callback; + plugin.io.flags = PLUGIN_IO_READ; - plugin.io.fd = pidfile_ctx.fd; plugin_register(&plugin); } PLUGIN_EXIT(plugin_exit) { - struct wd_entry *wde, *tmp; - - TAILQ_FOREACH_SAFE(wde, &pidfile_ctx.wd_list, link, tmp) { - inotify_rm_watch(pidfile_ctx.fd, wde->fd); - free(wde->path); - free(wde); - } - close(pidfile_ctx.fd); - + iwatch_exit(&iw_pidfile); plugin_unregister(&plugin); } diff --git a/src/Makefile.am b/src/Makefile.am index 491efa6c..c3198a80 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -26,6 +26,7 @@ finit_SOURCES = api.c cgroup.c cgroup.h \ exec.c finit.c finit.h \ getty.c stty.c \ helpers.c helpers.h \ + iwatch.c iwatch.h \ log.c log.h \ mdadm.c mount.c \ pid.c pid.h \ diff --git a/src/initctl.c b/src/initctl.c index fd12d5dd..3efbb3b2 100644 --- a/src/initctl.c +++ b/src/initctl.c @@ -494,6 +494,7 @@ static int show_status(char *arg) printf("Service : %s\n", svc->cmd); printf("Description : %s\n", svc->desc); printf("Identity : %s\n", svc_ident(svc, ident, sizeof(ident))); + printf("Environment : %s\n", svc->env); printf("PID : %d\n", svc->pid); printf("Uptime : %s\n", svc->pid ? uptime(now - svc->start_time, buf, sizeof(buf)) : buf); printf("Runlevels : %s\n", runlevel_string(runlevel, svc->runlevels)); diff --git a/src/iwatch.c b/src/iwatch.c new file mode 100644 index 00000000..48bedea1 --- /dev/null +++ b/src/iwatch.c @@ -0,0 +1,164 @@ +/* inotify watcher for files or directories + * + * Copyright (c) 2015-2016 Tobias Waldekranz + * Copyright (c) 2016-2021 Joachim Wiberg + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#include "finit.h" +#include "iwatch.h" +#include "log.h" + +/* + * iwatch is initialized and used mainly by the pidfile plugin, which is + * one of the cornerstones in the condition subsystem. Other parts of + * Finit may use iwatch too, like env: watchers, but are disabled if the + * pidfile plugin hasn't called iwatch_init() -- this is by design. + */ +static int initialized = 0; + + +int iwatch_init(struct iwatch *iw) +{ + if (!iw) { + errno = EINVAL; + return -1; + } + + TAILQ_INIT(&iw->iwp_list); + + iw->fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); + if (iw->fd < 0) { + _pe("inotify_init()"); + return -1; + } + initialized = 1; + + return iw->fd; +} + +void iwatch_exit(struct iwatch *iw) +{ + struct iwatch_path *iwp, *tmp; + + TAILQ_FOREACH_SAFE(iwp, &iw->iwp_list, link, tmp) { + TAILQ_REMOVE(&iw->iwp_list, iwp, link); + inotify_rm_watch(iw->fd, iwp->wd); + free(iwp->path); + free(iwp); + } + + close(iw->fd); + initialized = 0; +} + +int iwatch_add(struct iwatch *iw, char *file, uint32_t mask) +{ + struct iwatch_path *iwp; + char *path; + int wd; + + if (!initialized) + return -1; + + _d("Adding new watcher for path %s", file); + + path = strdup(file); + if (!path) { + _pe("Out of memory"); + return -1; + } + + wd = inotify_add_watch(iw->fd, path, IWATCH_MASK | mask); + if (wd < 0) { + _pe("inotify_add_watch(%s)", path); + free(path); + return -1; + } + + iwp = malloc(sizeof(struct iwatch_path)); + if (!iwp) { + _pe("Failed allocating new `struct iwatch_path`"); + inotify_rm_watch(iw->fd, wd); + free(path); + return -1; + } + + iwp->path = path; + iwp->wd = wd; + TAILQ_INSERT_HEAD(&iw->iwp_list, iwp, link); + + return 0; +} + +int iwatch_del(struct iwatch *iw, struct iwatch_path *iwp) +{ + if (!initialized) + return -1; + + _d("Removing watcher for removed path %s", iwp->path); + + TAILQ_REMOVE(&iw->iwp_list, iwp, link); + inotify_rm_watch(iw->fd, iwp->wd); + free(iwp->path); + free(iwp); + + return 0; +} + +struct iwatch_path *iwatch_find_by_wd(struct iwatch *iw, int wd) +{ + struct iwatch_path *iwp; + + if (!initialized) + return NULL; + + TAILQ_FOREACH(iwp, &iw->iwp_list, link) { + if (iwp->wd == wd) + return iwp; + } + + return NULL; +} + +struct iwatch_path *iwatch_find_by_path(struct iwatch *iw, const char *path) +{ + struct iwatch_path *iwp; + + if (!initialized) + return NULL; + + TAILQ_FOREACH(iwp, &iw->iwp_list, link) { + if (!strcmp(iwp->path, path)) + return iwp; + } + + return NULL; +} + +/** + * Local Variables: + * indent-tabs-mode: t + * c-file-style: "linux" + * End: + */ + diff --git a/src/iwatch.h b/src/iwatch.h new file mode 100644 index 00000000..b59c3fb9 --- /dev/null +++ b/src/iwatch.h @@ -0,0 +1,64 @@ +/* inotify watcher for files or directories + * + * Copyright (c) 2015-2016 Tobias Waldekranz + * Copyright (c) 2016-2021 Joachim Wiberg + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef FINIT_IWATCH_H_ +#define FINIT_IWATCH_H_ + +#include +#include +#include + + +/* Monitors changes to both directories and files by default */ +#define IWATCH_MASK (IN_CREATE | IN_DELETE | IN_MODIFY | IN_ATTRIB | IN_MOVE) + +struct iwatch_path { + TAILQ_ENTRY(iwatch_path) link; + char *path; + int wd; +}; + +struct iwatch { + int fd; + TAILQ_HEAD(, iwatch_path) iwp_list; +}; + + +int iwatch_init (struct iwatch *iw); +void iwatch_exit (struct iwatch *iw); + +int iwatch_add (struct iwatch *iw, char *file, uint32_t mask); +int iwatch_del (struct iwatch *iw, struct iwatch_path *iwp); + +struct iwatch_path *iwatch_find_by_wd (struct iwatch *iw, int wd); +struct iwatch_path *iwatch_find_by_path (struct iwatch *iw, const char *path); + +#endif /* FINIT_IWATCH_H_ */ + +/** + * Local Variables: + * indent-tabs-mode: t + * c-file-style: "linux" + * End: + */ diff --git a/src/service.c b/src/service.c index 10d34055..25452d60 100644 --- a/src/service.c +++ b/src/service.c @@ -32,6 +32,7 @@ #include #include #include +#include #include "cgroup.h" #include "conf.h" @@ -261,6 +262,121 @@ static int redirect(svc_t *svc) return 0; } +/* + * non-zero env, no checking if file exists or not + */ +static char *svc_getenv(svc_t *svc) +{ + int v = 0; + + if (!svc->env[0]) + return NULL; + + if (svc->env[0] == '-') + v = 1; + + return &svc->env[v]; +} + +/* + * Check if svc has env, if env file exists returns true + * if it doesn't exist, but has '-', also true. if svc + * doesn't have env, also true. + */ +static int check_env(svc_t *svc) +{ + char *env = svc_getenv(svc); + + if (!env) + return 1; + + return fexist(env); +} + +/* + * Source environment file, if it exists + * Note: must be called from privsepped child + */ +static void source_env(svc_t *svc) +{ + char buf[LINE_SIZE]; + char *line; + size_t len; + FILE *fp; + char *fn; + + fn = svc_getenv(svc); + if (!fn) + return; + + /* Warning in service_start() after check_env() */ + fp = fopen(fn, "r"); + if (!fp) + return; + + line = buf; + len = sizeof(buf); + while (fgets(line, len, fp)) { + char *key = chomp(line); + char *value, *end; + + /* skip any leading whitespace */ + while (isspace(*key)) + key++; + + /* skip comments */ + if (*key == '#' || *key == ';') + continue; + + /* find end of line */ + end = key; + while (*end) + end++; + + /* strip trailing whitespace */ + if (end > key) { + end--; + while (isspace(*end)) + *end-- = 0; + } + + value = strchr(key, '='); + if (!value) + continue; + *value++ = 0; + + /* strip leading whitespace from value */ + while (isspace(*value)) + value++; + + /* unquote value, if quoted */ + if (value[0] == '"' || value[0] == '\'') { + char q = value[0]; + + if (*end == q) { + value = &value[1]; + *end = 0; + } + } + + /* find end of key */ + end = key; + while (*end) + end++; + + /* strip trailing whitespace */ + if (end > key) { + end--; + while (isspace(*end)) + *end-- = 0; + } + + setenv(key, value, 1); + } + + fclose(fp); +} + static int is_norespawn(void) { return sig_stopped() || @@ -277,9 +393,10 @@ static int is_norespawn(void) */ static int service_start(svc_t *svc) { - int i, result = 0, do_progress = 1; - pid_t pid; + int result = 0, do_progress = 1; sigset_t nmask, omask; + pid_t pid; + size_t i; if (!svc) return 1; @@ -290,11 +407,17 @@ static int service_start(svc_t *svc) /* Don't try and start service if it doesn't exist. */ if (!whichp(svc->cmd)) { - print(1, "Service %s does not exist", svc->cmd); + print(1, "%s: does not exist", svc->cmd); svc_missing(svc); return 1; } + /* Unlike systemd we do not allow starting service if env is missing, unless - */ + if (!check_env(svc)) { + print(1, "%s: environment file %s does not exist (yet)", svc->cmd, svc->env); + return 1; + } + if (svc_is_sysv(svc)) logit(LOG_CONSOLE | LOG_NOTICE, "Calling '%s start' ...", svc->cmd); @@ -355,12 +478,45 @@ static int service_start(svc_t *svc) } } + /* Source any environment from env:/path/to/file */ + source_env(svc); + if (!svc_is_sysv(svc)) { + wordexp_t we = { 0 }; + int rc; + + if ((rc = wordexp(svc->cmd, &we, 0))) { + nomem: + _e("%s: failed wordexp(): %d", svc->cmd, rc); + wordfree(&we); + _exit(1); + } + for (i = 0; i < MAX_NUM_SVC_ARGS; i++) { if (strlen(svc->args[i]) == 0) break; + + if ((rc = wordexp(svc->args[i], &we, WRDE_APPEND))) + goto nomem; + } + + if (we.we_wordc > MAX_NUM_SVC_ARGS) { + logit(LOG_ERR, "%s: too man args after expansion.", svc->cmd); + goto nomem; + } + + for (i = 0; i < we.we_wordc; i++) { + if (strlen(we.we_wordv[i]) >= sizeof(svc->args[i])) { + logit(LOG_ERR, "%s: expanded arg. '%s' too long", we.we_wordv[i]); + rc = WRDE_NOSPACE; + goto nomem; + } + + /* overwrite the child's svc with expanded args */ + strlcpy(svc->args[i], we.we_wordv[i], sizeof(svc->args[i])); args[i] = svc->args[i]; } + wordfree(&we); } else { i = 0; args[i++] = svc->cmd; @@ -372,12 +528,11 @@ static int service_start(svc_t *svc) sig_unblock(); /* - * The setsid() call is the most humble of all in this - * function. It takes care to detach the process from - * its controlling terminal, preventing daemons from - * leaking to the console, and allowing us to run such - * programs like `lxc-start -F` in the foreground to - * properly monitor them. + * The setsid() call takes care to detach the process + * from its controlling terminal, preventing daemons + * from leaking to the console, and allowing us to run + * such programs like `lxc-start -F` in the foreground + * to properly monitor them. * * If you find yourself here wanting to fix the output * to the console at boot, for debugging or similar, @@ -386,9 +541,9 @@ static int service_start(svc_t *svc) setsid(); if (svc_is_runtask(svc)) - status = exec_runtask(svc->cmd, args); + status = exec_runtask(args[0], &args[1]); else - status = execvp(svc->cmd, args); + status = execvp(args[0], &args[1]); _exit(status); } else if (debug) { @@ -396,7 +551,7 @@ static int service_start(svc_t *svc) for (i = 0; i < MAX_NUM_SVC_ARGS; i++) { if (strlen(svc->args[i]) == 0) - continue; + break; if (buf[0]) strlcat(buf, " ", sizeof(buf)); strlcat(buf, svc->args[i], sizeof(buf)); @@ -661,6 +816,19 @@ static void parse_log(svc_t *svc, char *arg) } } +static void parse_env(svc_t *svc, char *env) +{ + if (!env) + return; + + if (strlen(env) >= sizeof(svc->env)) { + _e("%s: env file is too long (>%d chars)", svc->cmd, sizeof(svc->env)); + return; + } + + strlcpy(svc->env, env, sizeof(svc->env)); +} + static void parse_sighalt(svc_t *svc, char *arg) { int signo; @@ -798,7 +966,7 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file) char *cmd, *desc, *runlevels = NULL, *cond = NULL; char *username = NULL, *log = NULL, *pid = NULL; char *name = NULL, *halt = NULL, *delay = NULL; - char *id = NULL; + char *id = NULL, *env = NULL; int levels = 0; int manual = 0; char *line; @@ -860,6 +1028,8 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file) halt = &cmd[5]; else if (!strncasecmp(cmd, "kill:", 5)) delay = &cmd[5]; + else if (!strncasecmp(cmd, "env:", 4)) + env = &cmd[4]; else break; @@ -925,6 +1095,8 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file) parse_log(svc, log); if (desc) strlcpy(svc->desc, desc, sizeof(svc->desc)); + if (env) + parse_env(svc, env); /* Set configured limits */ memcpy(svc->rlimit, rlimit, sizeof(svc->rlimit)); diff --git a/src/svc.c b/src/svc.c index 61b0efa7..b3eb29a0 100644 --- a/src/svc.c +++ b/src/svc.c @@ -395,7 +395,7 @@ svc_t *svc_find_by_nameid(char *name, char *id) } /** - * svc_find_by_plidfile - Find an service object by its PID file + * svc_find_by_plidfile - Find a service by its PID file * @fn: PID file, can be absolute path or relative to /run * * This function is primarily used by the pidfile plugin to track the @@ -412,32 +412,13 @@ svc_t *svc_find_by_nameid(char *name, char *id) svc_t *svc_find_by_pidfile(char *fn) { svc_t *svc, *iter = NULL; - char adjpath[strlen(fn) + 10]; /* + sizeof("/var/run/") */ - char fnpath[PATH_MAX]; + pid_t pid; - pid_runpath(fn, adjpath, sizeof(adjpath)); - if (!realpath(adjpath, fnpath)) { - if (errno != ENOENT) - _pe("adjpath: %s, errno %d", adjpath, errno); + pid = pid_file_read(fn); + if (pid == -1) return NULL; - } for (svc = svc_iterator(&iter, 1); svc; svc = svc_iterator(&iter, 0)) { - char path[PATH_MAX]; - char *pidfn; - pid_t pid; - - pidfn = pid_file(svc); - if (!pidfn || !realpath(pidfn, path)) { - if (errno != ENOENT) - _pe("pidfn: %s, errno %d", pidfn ?: "", errno); - continue; - } - - if (!string_compare(fnpath, path)) - continue; - - pid = pid_file_read(path); if (svc->pid != pid) continue; diff --git a/src/svc.h b/src/svc.h index 83f3cd68..23cd5a81 100644 --- a/src/svc.h +++ b/src/svc.h @@ -134,6 +134,7 @@ typedef struct svc { char cmd[MAX_ARG_LEN]; char args[MAX_NUM_SVC_ARGS][MAX_ARG_LEN]; char desc[MAX_STR_LEN]; + char env[MAX_ARG_LEN]; /* * Used to forcefully kill services that won't shutdown on diff --git a/test/Makefile.am b/test/Makefile.am index ba325e35..d8cf90c7 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -1,19 +1,24 @@ EXTRA_DIST = lib.sh testenv_exec.sh testenv_start.sh -EXTRA_DIST += test_root/Makefile test_root/busybox-x86_64.md5 -EXTRA_DIST += test_root/bin/chrootsetup.sh +EXTRA_DIST += test-root/Makefile test-root/busybox-x86_64.md5 +EXTRA_DIST += test-root/bin/chrootsetup.sh +EXTRA_DIST += setup-root.sh teardown-root.sh +EXTRA_DIST += common/service.conf common/service.sh EXTRA_DIST += checkself.sh -EXTRA_DIST += setup_root.sh -EXTRA_DIST += teardown_root.sh -EXTRA_DIST += test_start_stop_service/test.sh -EXTRA_DIST += test_start_stop_service/service.sh +EXTRA_DIST += add-remove-dynamic-service/test.sh +EXTRA_DIST += add-remove-dynamic-service-sub-config/test.sh +EXTRA_DIST += start-stop-service/test.sh +EXTRA_DIST += start-stop-service-sub-config/test.sh -AM_TESTS_ENVIRONMENT = TESTS_ROOT='$(builddir)/test_root/'; +AM_TESTS_ENVIRONMENT = TESTS_ROOT='$(abs_builddir)/test-root/'; AM_TESTS_ENVIRONMENT += export TESTS_ROOT; AM_TESTS_ENVIRONMENT += export LD_LIBRARY_PATH; AM_TESTS_ENVIRONMENT += PREFIX=$(prefix); AM_TESTS_ENVIRONMENT += export PREFIX; TESTS = checkself.sh -TESTS += setup_root.sh -TESTS += test_start_stop_service/test.sh -TESTS += teardown_root.sh +TESTS += setup-root.sh +TESTS += add-remove-dynamic-service/test.sh +TESTS += add-remove-dynamic-service-sub-config/test.sh +TESTS += start-stop-service/test.sh +TESTS += start-stop-service-sub-config/test.sh +TESTS += teardown-root.sh diff --git a/test/add-remove-dynamic-service-sub-config/test.sh b/test/add-remove-dynamic-service-sub-config/test.sh new file mode 100755 index 00000000..af90b983 --- /dev/null +++ b/test/add-remove-dynamic-service-sub-config/test.sh @@ -0,0 +1,36 @@ +#!/bin/sh + +set -eu + +TEST_DIR=$(dirname "$0")/.. + +# shellcheck source=/dev/null +. "$TEST_DIR/lib.sh" + +test_teardown() { + say "Test done $(date)" + say "Running test teardown." + + texec rm -f "$FINIT_RCSD/service.conf" + texec rm -f /test_assets/service.sh +} + +say "Test start $(date)" + +cp "$TEST_DIR"/common/service.sh "$TESTS_ROOT"/test_assets/ + +say "Add a dynamic service in $FINIT_RCSD/service.conf" +texec sh -c "echo 'service [2345] kill:20 log /test_assets/service.sh' > $FINIT_RCSD/service.conf" + +say 'Reload Finit' +texec sh -c "initctl reload" + +retry 'assert_num_children 1 service.sh' + +say 'Remove the dynamic service from /etc/finit.d/service.conf' +texec sh -c "echo > $FINIT_RCSD/service.conf" + +say 'Reload Finit' +texec sh -c "initctl reload" + +retry 'assert_num_children 0 service.sh' diff --git a/test/add-remove-dynamic-service/test.sh b/test/add-remove-dynamic-service/test.sh new file mode 100755 index 00000000..e4b19c66 --- /dev/null +++ b/test/add-remove-dynamic-service/test.sh @@ -0,0 +1,36 @@ +#!/bin/sh + +set -eu + +TEST_DIR=$(dirname "$0")/.. + +# shellcheck source=/dev/null +. "$TEST_DIR/lib.sh" + +test_teardown() { + say "Test done $(date)" + say "Running test teardown." + + texec rm -f "$FINIT_CONF" + texec rm -f /test_assets/service.sh +} + +say "Test start $(date)" + +cp "$TEST_DIR"/common/service.sh "$TESTS_ROOT"/test_assets/ + +say "Add a dynamic service in $FINIT_CONF" +texec sh -c "echo 'service [2345] kill:20 log /test_assets/service.sh' > $FINIT_CONF" + +say 'Reload Finit' +texec sh -c "initctl reload" + +retry 'assert_num_children 1 service.sh' + +say 'Remove the dynamic service from /etc/finit.conf' +texec sh -c "echo > $FINIT_CONF" + +say 'Reload Finit' +texec sh -c "initctl reload" + +retry 'assert_num_children 0 service.sh' diff --git a/test/common/service.conf b/test/common/service.conf new file mode 100644 index 00000000..3fd97586 --- /dev/null +++ b/test/common/service.conf @@ -0,0 +1 @@ +service [2345] kill:20 log /test_assets/service.sh diff --git a/test/test_start_stop_service/service.sh b/test/common/service.sh similarity index 100% rename from test/test_start_stop_service/service.sh rename to test/common/service.sh diff --git a/test/lib.sh b/test/lib.sh index 8356976f..228680eb 100755 --- a/test/lib.sh +++ b/test/lib.sh @@ -1,8 +1,12 @@ #!/bin/sh +assert_num_children() { + assert "$1 services are running" "$(texec pgrep -P 1 "$2" | wc -l)" -eq "$1" +} + texec() { # shellcheck disable=SC2154 - TESTS_ROOT=. "$TEST_DIR/testenv_exec.sh" "$finit_pid" "$@" + "$TEST_DIR/testenv_exec.sh" "$finit_pid" "$@" } pause() { @@ -21,7 +25,7 @@ fg_red='\e[1;31m' fg_green='\e[1;32m' fg_yellow='\e[1;33m' log() { - printf "< TEST > %b%b%b %s\n" "$1" "$2" "$color_reset" "$3" + printf "< $TEST_NAME > %b%b%b %s\n" "$1" "$2" "$color_reset" "$3" } assert() { @@ -69,6 +73,11 @@ say() { teardown() { test_status="$?" + + if type test_teardown > /dev/null 2>&1 ; then + test_teardown + fi + log "$color_reset" '--' '' if [ "$test_status" -eq 0 ]; then log "$fg_green" 'TEST PASS' '' @@ -80,6 +89,7 @@ teardown() { fi wait + rm -f "$TESTS_ROOT"/running_test.pid } trap teardown EXIT @@ -87,12 +97,22 @@ trap teardown EXIT # shellcheck source=/dev/null . "$TESTS_ROOT/../test.env" -log "$color_reset" 'Test start' '' -log "$color_reset" '--' '' +TEST_NAME="$(dirname "$0")" +TEST_NAME=${TEST_NAME#*/} +export TEST_NAME -# Setup +# Setup test environment + +# Setup test environment "$TEST_DIR/testenv_start.sh" finit & finit_ppid=$! +echo "$finit_ppid" > "$TESTS_ROOT"/running_test.pid + +>&2 echo "Hint: Execute 'test/testenv_enter.sh' to enter the test namespace" +>&2 echo "finit conf '$FINIT_CONF'" +>&2 echo "finit_conf dir '$FINIT_RCSD'" +log "$color_reset" 'Setup of test environment done' '' + finit_pid=$(retry "pgrep -P $finit_ppid") tty=/dev/$(texec cat /sys/class/tty/console/active) diff --git a/test/setup_root.sh b/test/setup-root.sh similarity index 58% rename from test/setup_root.sh rename to test/setup-root.sh index 4666d9d7..7f9ead5a 100755 --- a/test/setup_root.sh +++ b/test/setup-root.sh @@ -6,18 +6,18 @@ set -eu if [ ! -f "$srcdir"/../config.status ]; then "$srcdir"/../configure --prefix=/ make - make DESTDIR="$(pwd)/test_root/" install + make DESTDIR="$(pwd)/test-root/" install - mkdir -p test_root/bin - cp "${srcdir}/test_root/bin/chrootsetup.sh" test_root/bin - cp "${srcdir}/test_root/busybox-x86_64.md5" test_root/ - cp "${srcdir}/test_root/Makefile" test_root/ - make -C test_root/ + mkdir -p test-root/bin + cp "${srcdir}/test-root/bin/chrootsetup.sh" test-root/bin + cp "${srcdir}/test-root/busybox-x86_64.md5" test-root/ + cp "${srcdir}/test-root/Makefile" test-root/ + make -C test-root/ config_h='./config.h' echo "export PATH=/bin:/sbin" > test.env else - make -C ../ DESTDIR="$(pwd)/test_root/" install - FINITBIN=../../src/finit make -C test_root/ + make -C ../ DESTDIR="$(pwd)/test-root/" install + FINITBIN=../../src/finit make -C test-root/ config_h='../config.h' echo "export PATH=/bin:/sbin:$PREFIX/sbin:$PREFIX/bin" > test.env fi diff --git a/test/start-stop-service-sub-config/test.sh b/test/start-stop-service-sub-config/test.sh new file mode 100755 index 00000000..0fe91094 --- /dev/null +++ b/test/start-stop-service-sub-config/test.sh @@ -0,0 +1,38 @@ +#!/bin/sh + +set -eu + +TEST_DIR=$(dirname "$0")/.. + +# shellcheck source=/dev/null +. "$TEST_DIR/lib.sh" + +test_teardown() { + say "Test done $(date)" + say "Running test teardown." + + texec rm -f "$FINIT_RCSD/service.conf" + texec rm -f /test_assets/service.sh +} + +say "Test start $(date)" + +cp "$TEST_DIR"/common/service.sh "$TESTS_ROOT"/test_assets/ + +say "Add service stanza in $FINIT_RCSD/service.conf" +texec sh -c "echo 'service [2345] kill:20 log /test_assets/service.sh' > $FINIT_RCSD/service.conf" + +say 'Reload Finit' +texec sh -c "initctl reload" + +retry 'assert_num_children 1 service.sh' + +say 'Stop the service' +texec sh -c "initctl stop service.sh" + +retry 'assert_num_children 0 service.sh' + +say 'Start the service again' +texec sh -c "initctl start service.sh" + +retry 'assert_num_children 1 service.sh' diff --git a/test/start-stop-service/test.sh b/test/start-stop-service/test.sh new file mode 100755 index 00000000..8125f6df --- /dev/null +++ b/test/start-stop-service/test.sh @@ -0,0 +1,38 @@ +#!/bin/sh + +set -eu + +TEST_DIR=$(dirname "$0")/.. + +# shellcheck source=/dev/null +. "$TEST_DIR/lib.sh" + +test_teardown() { + say "Test done $(date)" + say "Running test teardown." + + texec rm -f "$FINIT_CONF" + texec rm -f /test_assets/service.sh +} + +say "Test start $(date)" + +cp "$TEST_DIR"/common/service.sh "$TESTS_ROOT"/test_assets/ + +say "Add service stanza in $FINIT_CONF" +texec sh -c "echo 'service [2345] kill:20 log /test_assets/service.sh' > $FINIT_CONF" + +say 'Reload Finit' +texec sh -c "initctl reload" + +retry 'assert_num_children 1 service.sh' + +say 'Stop the service' +texec sh -c "initctl stop service.sh" + +retry 'assert_num_children 0 service.sh' + +say 'Start the service again' +texec sh -c "initctl start service.sh" + +retry 'assert_num_children 1 service.sh' diff --git a/test/teardown-root.sh b/test/teardown-root.sh new file mode 100755 index 00000000..88609938 --- /dev/null +++ b/test/teardown-root.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +set -eu + +if [ -d test-root/var/lock ]; then + chmod +r test-root/var/lock +fi +make -C test-root/ clean +rm -f test.env diff --git a/test/teardown_root.sh b/test/teardown_root.sh deleted file mode 100755 index e0649cce..00000000 --- a/test/teardown_root.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh - -set -eu - -if [ -d test_root/var/lock ]; then - chmod +r test_root/var/lock -fi -make -C test_root/ clean -rm -f test.env diff --git a/test/test_root/.gitignore b/test/test-root/.gitignore similarity index 100% rename from test/test_root/.gitignore rename to test/test-root/.gitignore diff --git a/test/test_root/Makefile b/test/test-root/Makefile similarity index 100% rename from test/test_root/Makefile rename to test/test-root/Makefile diff --git a/test/test_root/bin/chrootsetup.sh b/test/test-root/bin/chrootsetup.sh similarity index 95% rename from test/test_root/bin/chrootsetup.sh rename to test/test-root/bin/chrootsetup.sh index e4810294..22581683 100755 --- a/test/test_root/bin/chrootsetup.sh +++ b/test/test-root/bin/chrootsetup.sh @@ -17,7 +17,7 @@ mount -n -t tmpfs none /dev mount -t proc none /proc mount -t sysfs none /sys # mount -t tmpfs none /tmp -mount -t tmpfs none /etc +# mount -t tmpfs none /etc # mkdir -p /dev/pts # mount -t devpts none /dev/pts diff --git a/test/test_root/busybox-x86_64.md5 b/test/test-root/busybox-x86_64.md5 similarity index 100% rename from test/test_root/busybox-x86_64.md5 rename to test/test-root/busybox-x86_64.md5 diff --git a/test/test_start_stop_service/test.sh b/test/test_start_stop_service/test.sh deleted file mode 100755 index 68eb7e1b..00000000 --- a/test/test_start_stop_service/test.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/sh - -set -eu - -TEST_DIR=$(dirname "$0")/.. -# shellcheck source=/dev/null -. "$TEST_DIR/lib.sh" - -assert_num_children() { - assert "$1 services are running" "$(texec pgrep -P 1 "$2" | wc -l)" -eq "$1" -} - -# Test -say 'Set up a service' -cp "$TEST_DIR"/test_start_stop_service/service.sh "$TESTS_ROOT"/test_assets/ -texec sh -c "echo 'service [2345] kill:20 log /test_assets/service.sh' > $FINIT_CONF" - -say 'Reload Finit' -texec kill -SIGHUP 1 - -retry 'assert_num_children 1 service.sh' - -say 'Remove the service' -texec sh -c "echo > $FINIT_CONF" - -say 'Reload Finit' -texec kill -SIGHUP 1 - -retry 'assert_num_children 0 service.sh' - -# Teardown -rm -f "$TESTS_ROOT"/bin/service.sh diff --git a/test/testenv_enter.sh b/test/testenv_enter.sh new file mode 100755 index 00000000..8a04fa43 --- /dev/null +++ b/test/testenv_enter.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +set -eu + +cmd="" +if [ "$#" -lt 1 ]; then + cmd="/bin/sh" +else + cmd=$1 + shift +fi + +PID_FILE=$(dirname "$0")/test-root/running_test.pid + +if [ -f "$PID_FILE" ] ; then + target=$(cat "$PID_FILE") + target=$(pgrep -P "$target") +else + echo "No test running!" + exit 1 +fi + +"$(dirname "$0")"/testenv_exec.sh "$target" "$cmd" "$@" diff --git a/test/testenv_exec.sh b/test/testenv_exec.sh index 72e1db70..bb2494e1 100755 --- a/test/testenv_exec.sh +++ b/test/testenv_exec.sh @@ -11,7 +11,7 @@ target="$1" shift TEST_DIR=$(dirname "$0") -TESTS_ROOT="${TESTS_ROOT:-$(pwd)/${TEST_DIR}/test_root}" +TESTS_ROOT="${TESTS_ROOT:-$(pwd)/${TEST_DIR}/test-root}" nsenter=$(command -v nsenter) chroot=$(command -v chroot) diff --git a/test/testenv_start.sh b/test/testenv_start.sh index 04278de6..fb384027 100755 --- a/test/testenv_start.sh +++ b/test/testenv_start.sh @@ -3,9 +3,7 @@ set -eu TEST_DIR=$(dirname "$0") -TESTS_ROOT="${TESTS_ROOT:-$(pwd)/${TEST_DIR}/test_root}" - ->&2 echo "Hint: Execute '${TEST_DIR}/testenv_exec.sh \$(pgrep -P $$) /bin/sh; reset' to enter the test namespace" +TESTS_ROOT="${TESTS_ROOT:-$(pwd)/${TEST_DIR}/test-root}" unshare=$(command -v unshare) chroot=$(command -v chroot)