From 69011211757ad63c29a24442664d0c46bb9cc01e Mon Sep 17 00:00:00 2001 From: "Anna (navi) Figueiredo Gomes" Date: Sun, 14 Jul 2024 17:41:47 +0200 Subject: [PATCH 1/2] openrc-run: Allow multiplexed services to live outside init.d. Use the realpath of the target to locate which system directory it's in, using it as a base to load the config file. the service path is then normalized from the symlink without dereferencing it. Introduce a new variable for openrc-run, $RC_SYSCONFDIR, which points to the respective system directory of the base service. Signed-off-by: Anna (navi) Figueiredo Gomes --- man/openrc-run.8 | 11 +-- sh/openrc-run.sh.in | 2 +- src/openrc-run/openrc-run.c | 131 +++++++++++++++++++++++------------- 3 files changed, 91 insertions(+), 53 deletions(-) diff --git a/man/openrc-run.8 b/man/openrc-run.8 index 552fcb91f..577b1e97e 100644 --- a/man/openrc-run.8 +++ b/man/openrc-run.8 @@ -547,6 +547,9 @@ sets the following environment variables for use in the service scripts: Name of the service. .It Va RC_SERVICE Full path to the service. +.It Va RC_SCRIPTDIR +Full path to the system directory the service lives in. For multiplexed +services, it is the directory the main service live in. .It Va RC_RUNLEVEL Current runlevel that OpenRC is in. Note that, in OpenRC, the reboot runlevel is mapped to the shutdown runlevel. This was done because most @@ -584,14 +587,14 @@ should not be unmounted. .El .Sh FILES .Pp -Configuration files, relative to the location of the service. +Configuration files, relative to RC_SYSCONFDIR. If a file ending with .${RC_RUNLEVEL} exists then we use that instead. .Bl -ohang -.It Pa ../conf.d/${RC_SVCNAME%%.*} +.It Pa ${RC_SCRIPTDIR}/conf.d/${RC_SVCNAME%%.*} multiplexed configuration file. Example: if ${RC_SVCNAME} is net.eth1 then look for -.Pa ../conf.d/net . -.It Pa ../conf.d/${RC_SVCNAME} +.Pa ${RC_SCRIPTDIR}/conf.d/net . +.It Pa ${RC_SCRIPTDIR}/conf.d/${RC_SVCNAME} service configuration file. .It Pa /etc/rc.conf host configuration file. diff --git a/sh/openrc-run.sh.in b/sh/openrc-run.sh.in index 5d0eeffa5..1a3cefbea 100644 --- a/sh/openrc-run.sh.in +++ b/sh/openrc-run.sh.in @@ -220,7 +220,7 @@ if [ -d "@SYSCONFDIR@/rc.conf.d" ]; then done fi -_conf_d=${RC_SERVICE%/*}/../conf.d +_conf_d="${RC_SCRIPTDIR}/conf.d" # If we're net.eth0 or openvpn.work then load net or openvpn config _c=${RC_SVCNAME%%.*} if [ -n "$_c" -a "$_c" != "$RC_SVCNAME" ]; then diff --git a/src/openrc-run/openrc-run.c b/src/openrc-run/openrc-run.c index 7c5d50a1b..ed9e18f14 100644 --- a/src/openrc-run/openrc-run.c +++ b/src/openrc-run/openrc-run.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -209,21 +210,21 @@ restore_state(void) if (rc_in_plugin || exclusive_fd == -1) return; - state = rc_service_state(applet); + state = rc_service_state(service); if (state & RC_SERVICE_STOPPING) { if (state & RC_SERVICE_WASINACTIVE) - rc_service_mark(applet, RC_SERVICE_INACTIVE); + rc_service_mark(service, RC_SERVICE_INACTIVE); else - rc_service_mark(applet, RC_SERVICE_STARTED); + rc_service_mark(service, RC_SERVICE_STARTED); if (rc_runlevel_stopping()) - rc_service_mark(applet, RC_SERVICE_FAILED); + rc_service_mark(service, RC_SERVICE_FAILED); } else if (state & RC_SERVICE_STARTING) { if (state & RC_SERVICE_WASINACTIVE) - rc_service_mark(applet, RC_SERVICE_INACTIVE); + rc_service_mark(service, RC_SERVICE_INACTIVE); else - rc_service_mark(applet, RC_SERVICE_STOPPED); + rc_service_mark(service, RC_SERVICE_STOPPED); if (rc_runlevel_starting()) - rc_service_mark(applet, RC_SERVICE_FAILED); + rc_service_mark(service, RC_SERVICE_FAILED); } exclusive_fd = svc_unlock(applet, exclusive_fd); } @@ -1094,17 +1095,73 @@ service_plugable(void) return allow; } +static char * +normalize_path(const char *src) +{ + char *path; + size_t path_len; + size_t src_len = strlen(src); + + if (src_len == 0 || src[0] != '/') { + char pwd[PATH_MAX]; + size_t pwd_len; + + if (getcwd(pwd, sizeof(pwd)) == NULL) + return NULL; + + pwd_len = strlen(pwd); + path = xmalloc(pwd_len + 1 + src_len + 1); + memcpy(path, pwd, pwd_len); + path_len = pwd_len; + } else { + path = xmalloc((src_len > 0 ? src_len : 1) + 1); + path_len = 0; + } + + for (const char *ptr = src, *next; ptr < src + src_len; ptr = next + 1) { + size_t len; + next = strchr(ptr, '/'); + if (!next) + next = src + src_len; + len = next - ptr; + switch (len) { + case 2: + if (ptr[0] == '.' && ptr[1] == '.') { + const char *slash = strrchr(path, '/'); + if (slash) + path_len = slash - path; + continue; + } + break; + case 1: + if (ptr[0] == '.') + continue; + break; + case 0: + continue; + } + path[path_len++] = '/'; + memcpy(&path[path_len], ptr, len); + path_len += len; + } + + if (path_len == 0) + path[path_len++] = '/'; + + path[path_len] = '\0'; + return path; +} + int main(int argc, char **argv) { bool doneone = false; int retval, opt, depoptions = RC_DEP_TRACE; RC_STRING *svc; - char *path = NULL; - char *lnk = NULL; - char *dir, *save = NULL, *saveLnk = NULL; + char service_path[PATH_MAX]; + char *initdir, *scriptdir; + char *save; char *pidstr = NULL; size_t l = 0, ll; - const char *file; struct stat stbuf; /* Show help if insufficient args */ @@ -1113,53 +1170,30 @@ int main(int argc, char **argv) exit(EXIT_FAILURE); } - applet = basename_c(argv[0]); - if (stat(argv[1], &stbuf) != 0) { - fprintf(stderr, "openrc-run `%s': %s\n", - argv[1], strerror(errno)); + fprintf(stderr, "openrc-run '%s': %s\n", argv[1], strerror(errno)); exit(EXIT_FAILURE); } - atexit(cleanup); - /* We need to work out the real full path to our service. - * This works fine, provided that we ONLY allow multiplexed services - * to exist in the same directory as the master link. - * Also, the master link as to be a real file in the init dir. */ - path = realpath(argv[1], NULL); - if (!path) { + * multiplexed services must point to a target in a init dir. */ + if (!realpath(argv[1], service_path)) { fprintf(stderr, "realpath: %s\n", strerror(errno)); exit(EXIT_FAILURE); } - lnk = xmalloc(4096); - memset(lnk, 0, 4096); - if (readlink(argv[1], lnk, 4096-1)) { - dir = dirname(path); - if (strchr(lnk, '/')) { - save = xstrdup(dir); - saveLnk = xstrdup(lnk); - dir = dirname(saveLnk); - if (strcmp(dir, save) == 0) - file = basename_c(argv[1]); - else - file = basename_c(lnk); - dir = save; - } else - file = basename_c(argv[1]); - xasprintf(&service, "%s/%s", dir, file); - if (stat(service, &stbuf) != 0) { - free(service); - service = xstrdup(lnk); - } - free(save); - free(saveLnk); + scriptdir = dirname(service_path); + initdir = strrchr(scriptdir, '/'); + if (!initdir || strcmp(initdir, "/init.d") != 0) { + fprintf(stderr, "%s is not an init dir", service_path); + exit(EXIT_FAILURE); } - free(lnk); - if (!service) - service = xstrdup(path); + *initdir = '\0'; + + service = normalize_path(argv[1]); applet = basename_c(service); + atexit(cleanup); + if (argc < 3) usage(EXIT_FAILURE); @@ -1175,6 +1209,7 @@ int main(int argc, char **argv) setenv("EINFO_LOG", service, 1); setenv("RC_SVCNAME", applet, 1); + setenv("RC_SCRIPTDIR", scriptdir, true); /* Set an env var so that we always know our pid regardless of any subshells the init script may create so that our mark_service_* @@ -1392,7 +1427,7 @@ int main(int argc, char **argv) } else if (strcmp(optarg, "zap") == 0) { einfo("Manually resetting %s to stopped state", applet); - if (!rc_service_mark(applet, + if (!rc_service_mark(service, RC_SERVICE_STOPPED)) eerrorx("rc_service_mark: %s", strerror(errno)); From 1665840dd64cf5c1c2f44bbbe82794aa3b1b5577 Mon Sep 17 00:00:00 2001 From: "Anna (navi) Figueiredo Gomes" Date: Sat, 21 Sep 2024 02:06:56 +0200 Subject: [PATCH 2/2] librc: Add initial support for dynamically multiplexed services. Generate a new directory on the service dir, "multiplexed", which will hold all the symlinks we generate at runtime, and a new librc function, "rc_service_multiplex", which takes a basename or service path, and a variant name, then generated a symlink to the base service from the multiplex dir. This allows multiplexing services at runtime, possibly using data acquired at runtime, such as the name of an user or interface. --- src/librc/librc-depend.c | 1 + src/librc/librc.c | 48 ++++++++++++++++++++++++++++++++++++++-- src/librc/rc.h.in | 5 +++++ 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/librc/librc-depend.c b/src/librc/librc-depend.c index a48a2ada0..8f569ae5f 100644 --- a/src/librc/librc-depend.c +++ b/src/librc/librc-depend.c @@ -697,6 +697,7 @@ static const char *const depdirs[] = RC_SVCDIR "/wasinactive", RC_SVCDIR "/failed", RC_SVCDIR "/hotplugged", + RC_SVCDIR "/multiplexed", RC_SVCDIR "/daemons", RC_SVCDIR "/options", RC_SVCDIR "/exclusive", diff --git a/src/librc/librc.c b/src/librc/librc.c index b54496410..0d114214f 100644 --- a/src/librc/librc.c +++ b/src/librc/librc.c @@ -636,6 +636,12 @@ rc_service_resolve(const char *service) return file; #endif + /* Check dynamically multiplexed services last */ + free(file); + xasprintf(&file, "%s/multiplexed/%s", RC_SVCDIR, service); + if (stat(file, &buf) == 0) + return file; + free(file); return NULL; } @@ -933,6 +939,32 @@ rc_service_state(const char *service) return state; } +char * +rc_service_multiplex(const char *base, const char *variant) +{ + char *service = rc_service_resolve(base); + char *target; + char *name; + + if (!service) + return NULL; + + if (base[0] == '/') + base = basename_c(base); + + xasprintf(&name, "%s.%s", base, variant); + + xasprintf(&target, "%s/multiplexed/%s", RC_SVCDIR, name); + if (!exists(target) && symlink(service, target) == -1) { + free(target); + target = NULL; + } + + free(name); + free(service); + return target; +} + char * rc_service_value_get(const char *service, const char *option) { @@ -1119,6 +1151,8 @@ rc_service_add(const char *runlevel, const char *service) char *path; char *binit = NULL; char *i; + char *multiplex_dir; + int len; if (!rc_runlevel_exists(runlevel)) { errno = ENOENT; @@ -1151,10 +1185,20 @@ rc_service_add(const char *runlevel, const char *service) i = binit; } - xasprintf(&file, "%s/%s/%s", RC_RUNLEVELDIR, runlevel, - basename_c(service)); + /* We shouldn't add any dynamically multiplexed service to a runlevel */ + len = xasprintf(&multiplex_dir, "%s/multiplexed", RC_SVCDIR); + if (strncmp(i, multiplex_dir, len) == 0) { + errno = ENOENT; + retval = false; + goto out; + } + + xasprintf(&file, "%s/%s/%s", RC_RUNLEVELDIR, runlevel, basename_c(service)); retval = (symlink(i, file) == 0); free(file); + +out: + free(multiplex_dir); free(binit); free(init); return retval; diff --git a/src/librc/rc.h.in b/src/librc/rc.h.in index d90943b65..4fdd3ef3a 100644 --- a/src/librc/rc.h.in +++ b/src/librc/rc.h.in @@ -267,6 +267,11 @@ bool rc_service_schedule_clear(const char *); * @return state of the service */ RC_SERVICE rc_service_state(const char *); +/*! Dynamically multiplexes a base service into a variant as basename.variant + * @param basename or full path of service being multiplexed + * @param variant to dynamically multiplex */ +char *rc_service_multiplex(const char *, const char *); + /*! Check if the service started the daemon * @param service to check * @param exec to check