From 17c69fef3d96d00783ed662d304201ff580a29d5 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 18 Dec 2022 14:43:16 +0100 Subject: [PATCH] Fix #185: add devmon support, condition provider Signed-off-by: Joachim Wiberg --- src/Makefile.am | 1 + src/cond.h | 2 + src/conf.c | 15 +-- src/devmon.c | 275 ++++++++++++++++++++++++++++++++++++++++++++++++ src/devmon.h | 32 ++++++ src/finit.c | 2 + src/service.c | 7 ++ 7 files changed, 328 insertions(+), 6 deletions(-) create mode 100644 src/devmon.c create mode 100644 src/devmon.h diff --git a/src/Makefile.am b/src/Makefile.am index 1d84909e..c947f7b4 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -45,6 +45,7 @@ finit_SOURCES = api.c cgroup.c cgroup.h \ client.c client.h \ cond.c cond-w.c cond.h \ conf.c conf.h \ + devmon.c devmon.h \ exec.c finit.c finit.h \ stty.c \ helpers.c helpers.h \ diff --git a/src/cond.h b/src/cond.h index f727efec..819d2dbf 100644 --- a/src/cond.h +++ b/src/cond.h @@ -6,11 +6,13 @@ #include "svc.h" #define COND_BASE "finit/cond" +#define COND_DEV "dev/" #define COND_PID "pid/" #define COND_SYS "sys/" #define COND_USR "usr/" #define _PATH_COND _PATH_VARRUN COND_BASE "/" +#define _PATH_CONDDEV _PATH_COND COND_DEV #define _PATH_CONDPID _PATH_COND COND_PID #define _PATH_CONDSYS _PATH_COND COND_SYS #define _PATH_CONDUSR _PATH_COND COND_USR diff --git a/src/conf.c b/src/conf.c index bbcf5e68..b833ce3b 100644 --- a/src/conf.c +++ b/src/conf.c @@ -40,6 +40,7 @@ #include "finit.h" #include "cond.h" +#include "devmon.h" #include "iwatch.h" #include "private.h" #include "service.h" @@ -469,6 +470,7 @@ void conf_parse_cond(svc_t *svc, char *cond) { size_t i = 0; char *ptr; + char *c; if (!svc) { errx(1, "Invalid service pointer"); @@ -496,16 +498,17 @@ void conf_parse_cond(svc_t *svc, char *cond) ptr[i] = 0; if (i >= sizeof(svc->cond)) { - logit(LOG_WARNING, "Too long event list in declaration of %s: %s", svc_ident(svc, NULL, 0), ptr); + logit(LOG_WARNING, "%s: too long list of conditions: %s", svc_ident(svc, NULL, 0), ptr); return; } - if (!strncmp(ptr, "svc/", 4)) { - logit(LOG_ERR, "Unsupported cond syntax for %s: <%s", svc_ident(svc, NULL, 0), ptr); - return; + svc->cond[0] = 0; + for (i = 0, c = strtok(ptr, ","); c; c = strtok(NULL, ","), i++) { + devmon_add_cond(c); + if (i) + strlcat(svc->cond, ",", sizeof(svc->cond)); + strlcat(svc->cond, c, sizeof(svc->cond)); } - - strlcpy(svc->cond, ptr, sizeof(svc->cond)); } struct rlimit_name { diff --git a/src/devmon.c b/src/devmon.c new file mode 100644 index 00000000..5edaa6b3 --- /dev/null +++ b/src/devmon.c @@ -0,0 +1,275 @@ +/* Device node monitor for the Finit condition engine + * + * Copyright (c) 2022 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 +#include +#include + +#include "finit.h" +#include "cond.h" +#include "helpers.h" +#include "pid.h" +#include "plugin.h" +#include "service.h" +#include "iwatch.h" + +static struct iwatch iw_devmon; +static uev_t devw; +static int fd; + +struct dev_node { + TAILQ_ENTRY(dev_node) link; + char *name; + size_t refcnt; +}; + +static TAILQ_HEAD(, dev_node) dev_node_list = TAILQ_HEAD_INITIALIZER(dev_node_list); + +static struct dev_node *find_node(const char *cond) +{ + struct dev_node *node, *tmp; + + TAILQ_FOREACH_SAFE(node, &dev_node_list, link, tmp) { + if (string_compare(node->name, cond)) + return node; + } + + return NULL; +} + +static void drop_node(struct dev_node *node) +{ + if (!node) + return; + + node->refcnt--; + if (node->refcnt) + return; + + TAILQ_REMOVE(&dev_node_list, node, link); + free(node->name); + free(node); +} + +void devmon_add_cond(const char *cond) +{ + struct dev_node *node; + + if (!cond || strncmp(cond, "dev/", 4)) { + dbg("no match"); + return; + } + + dbg("%s", cond); + node = find_node(cond); + if (node) { + node->refcnt++; + return; + } + + node = malloc(sizeof(*node)); + if (!node) { + fail: + warn("failed allocating node %s", cond); + return; + } + + node->name = strdup(cond); + if (!node->name) { + free(node); + goto fail; + } + node->refcnt = 1; + + TAILQ_INSERT_TAIL(&dev_node_list, node, link); +} + +void devmon_del_cond(const char *cond) +{ + if (!cond || strcmp(cond, "dev/")) + return; + + drop_node(find_node(cond)); +} + +static int devmon_add_path(struct iwatch *iw, char *path) +{ + char *ptr; + + ptr = strstr(path, "dev/"); + if (ptr) { + char *slash; + + ptr += 4; + slash = strchr(ptr, '/'); + if (slash) { + ptr = slash++; + slash = strchr(ptr, '/'); + if (slash) { + dbg("Path too deep, skipping."); + return -1; + } + } + } + + return iwatch_add1(iw, path, IN_CREATE | IN_DELETE); +} + +static void devmon_update_conds(char *dir, char *name, uint32_t mask) +{ + char fn[PATH_MAX]; + char *cond; + + paste(fn, sizeof(fn), dir, name); + cond = &fn[1]; + + dbg("path: %s, mask: %08x, cond: %s", fn, mask, cond); + if (!find_node(cond)) { + dbg("unregistered, skipping %s", cond); + return; + } + + if (mask & IN_CREATE) + cond_set(cond); + else if (mask & IN_DELETE) + cond_clear(cond); +} + +/* synthesize events in case of new run dirs */ +static void devmon_scandir(struct iwatch *iw, char *dir, int len) +{ + char path[len + 6]; + glob_t gl; + size_t i; + int rc; + + snprintf(path, sizeof(path), "%s/*", dir); + rc = glob(path, GLOB_NOSORT, NULL, &gl); + if (rc && rc != GLOB_NOMATCH) + return; + + for (i = 0; i < gl.gl_pathc; i++) { + dbg("scan found %s", gl.gl_pathv[i]); + devmon_update_conds(dir, gl.gl_pathv[i], IN_CREATE); + } + globfree(&gl); +} + +/* + * create/remove sub-directory in monitored directory + */ +static void devmon_handle_dir(struct iwatch *iw, char *dir, char *name, int mask) +{ + char path[strlen(dir) + strlen(name) + 2]; + struct iwatch_path *iwp; + + paste(path, sizeof(path), dir, name); + dbg("path: %s", path); + + iwp = iwatch_find_by_path(iw, path); + + if (mask & IN_CREATE) { + if (!iwp) { + if (!devmon_add_path(iw, path)) + devmon_scandir(iw, path, sizeof(path)); + } + } else if (mask & IN_DELETE) { + if (iwp) + iwatch_del(&iw_devmon, iwp); + } +} + +static void devmon_cb(uev_t *w, void *arg, int events) +{ + static char ev_buf[8 *(sizeof(struct inotify_event) + NAME_MAX + 1) + 1]; + struct inotify_event *ev; + ssize_t sz; + size_t off; + + sz = read(w->fd, ev_buf, sizeof(ev_buf) - 1); + if (sz <= 0) { + err(1, "invalid inotify event"); + return; + } + ev_buf[sz] = 0; + + for (off = 0; off < (size_t)sz; off += sizeof(*ev) + ev->len) { + struct iwatch_path *iwp; + + if (off + sizeof(*ev) > (size_t)sz) + break; + + ev = (struct inotify_event *)&ev_buf[off]; + if (off + sizeof(*ev) + ev->len > (size_t)sz) + break; + + if (!ev->mask) + continue; + + /* Find base path for this event */ + iwp = iwatch_find_by_wd(&iw_devmon, ev->wd); + if (!iwp) + continue; + + if (ev->mask & IN_ISDIR) { + devmon_handle_dir(&iw_devmon, iwp->path, ev->name, ev->mask); + continue; + } + + dbg("Got %s", ev->name); + if (ev->mask & (IN_CREATE | IN_DELETE)) + devmon_update_conds(iwp->path, ev->name, ev->mask); + } +} + +void devmon_init(void) +{ + char dir[MAX_ARG_LEN]; + + fd = iwatch_init(&iw_devmon); + if (fd < 0) + return; + + if (mkpath(pid_runpath(_PATH_CONDDEV, dir, sizeof(dir)), 0755) && errno != EEXIST) { + err(1, "Failed creating %s condition directory, %s", COND_DEV, _PATH_CONDDEV); + return; + } + + if (uev_io_init(ctx, &devw, devmon_cb, NULL, fd, UEV_READ)) { + err(1, "Failed setting up I/O callback for /dev watcher"); + close(fd); + return; + } + + strlcpy(dir, _PATH_DEV, sizeof(dir)); + if (devmon_add_path(&iw_devmon, dir)) + iwatch_exit(&iw_devmon); +} + +/** + * Local Variables: + * indent-tabs-mode: t + * c-file-style: "linux" + * End: + */ diff --git a/src/devmon.h b/src/devmon.h new file mode 100644 index 00000000..b7a9fcb7 --- /dev/null +++ b/src/devmon.h @@ -0,0 +1,32 @@ +/* Device node monitor for the Finit condition engine + * + * Copyright (c) 2022 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_DEVMON_H_ +#define FINIT_DEVMON_H_ + +void devmon_add_cond(const char *cond); +void devmon_del_cond(const char *cond); + +void devmon_init(void); + +#endif /* FINIT_DEVMON_H_ */ diff --git a/src/finit.c b/src/finit.c index 50534b4c..c1399458 100644 --- a/src/finit.c +++ b/src/finit.c @@ -48,6 +48,7 @@ #include "cgroup.h" #include "cond.h" #include "conf.h" +#include "devmon.h" #include "helpers.h" #include "private.h" #include "plugin.h" @@ -801,6 +802,7 @@ int main(int argc, char *argv[]) * Initialize .conf system and load static /etc/finit.conf then * tell the world what we used. */ + devmon_init(); conf_init(&loop); conf_saverc(); diff --git a/src/service.c b/src/service.c index 73ae3554..371cd705 100644 --- a/src/service.c +++ b/src/service.c @@ -43,6 +43,7 @@ #include "client.h" #include "conf.h" #include "cond.h" +#include "devmon.h" #include "finit.h" #include "helpers.h" #include "pid.h" @@ -1696,10 +1697,16 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file) */ void service_unregister(svc_t *svc) { + char *c; + if (!svc) return; service_stop(svc); + + for (c = strtok(svc->cond, ","); c; c = strtok(NULL, ",")) + devmon_del_cond(c); + svc_del(svc); }