From 9efa44579fd1f60a4c9ace264bb0b968ccb0f7ea Mon Sep 17 00:00:00 2001 From: "Anna (navi) Figueiredo Gomes" Date: Tue, 14 Mar 2023 19:03:52 -0300 Subject: openrc: Add support for user services. Modifies many functions where filesystem paths were hardcoded. In non-user-services mode, they still are. In user-services mode, they are allocated, since XDG_ dirs are to be set via environment variables. Signed-off-by: Anna (navi) Figueiredo Gomes --- src/librc/librc-daemon.c | 45 +++- src/librc/librc-depend.c | 133 ++++++++-- src/librc/librc.c | 420 ++++++++++++++++++++++++++++---- src/openrc-run/meson.build | 4 +- src/openrc-run/openrc-run.c | 77 +++++- src/openrc/meson.build | 4 +- src/openrc/rc.c | 137 +++++++++-- src/rc-service/meson.build | 2 +- src/rc-service/rc-service.c | 16 +- src/rc-status/meson.build | 2 +- src/rc-status/rc-status.c | 13 + src/rc-update/meson.build | 2 +- src/rc-update/rc-update.c | 15 +- src/shared/_usage.h | 10 + src/shared/misc.c | 91 ++++++- src/shared/misc.h | 12 +- src/supervise-daemon/supervise-daemon.c | 23 +- 17 files changed, 866 insertions(+), 140 deletions(-) diff --git a/src/librc/librc-daemon.c b/src/librc/librc-daemon.c index 50245129..10bf883a 100644 --- a/src/librc/librc-daemon.c +++ b/src/librc/librc-daemon.c @@ -405,13 +405,26 @@ rc_service_daemon_set(const char *service, const char *exec, RC_STRINGLIST *match; int i = 0; FILE *fp; + char *svcdir = RC_SVCDIR; +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + svcdir = rc_user_svcdir(); + } +#endif + if (!exec && !pidfile) { errno = EINVAL; return false; } - xasprintf(&dirpath, RC_SVCDIR "/daemons/%s", basename_c(service)); + xasprintf(&dirpath, "%s/daemons/%s", svcdir, basename_c(service)); + +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + free(svcdir); + } +#endif /* Regardless, erase any existing daemon info */ if ((dp = opendir(dirpath))) { @@ -477,13 +490,25 @@ rc_service_started_daemon(const char *service, bool retval = false; DIR *dp; struct dirent *d; + char *svcdir = RC_SVCDIR; +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + svcdir = rc_user_svcdir(); + } +#endif if (!service || !exec) return false; - xasprintf(&dirpath, RC_SVCDIR "/daemons/%s", basename_c(service)); + xasprintf(&dirpath, "%s/daemons/%s", svcdir, basename_c(service)); match = _match_list(exec, argv, NULL); +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + free(svcdir); + } +#endif + if (indx > 0) { xasprintf(&file, "%03d", indx); retval = _match_daemon(dirpath, file, match); @@ -532,9 +557,21 @@ rc_service_daemons_crashed(const char *service) size_t i; char *ch_root; char *spidfile; + char *svcdir = RC_SVCDIR; +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + svcdir = rc_user_svcdir(); + } +#endif + + path += snprintf(dirpath, sizeof(dirpath), "%s/daemons/%s", + svcdir, basename_c(service)); - path += snprintf(dirpath, sizeof(dirpath), RC_SVCDIR "/daemons/%s", - basename_c(service)); +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + free(svcdir); + } +#endif if (!(dp = opendir(dirpath))) return false; diff --git a/src/librc/librc-depend.c b/src/librc/librc-depend.c index d584af2c..dfb275bf 100644 --- a/src/librc/librc-depend.c +++ b/src/librc/librc-depend.c @@ -35,7 +35,8 @@ #define GENDEP RC_LIBEXECDIR "/sh/gendepends.sh" -#define RC_DEPCONFIG RC_SVCDIR "/depconfig" +#define RC_DEPCONFIG_FILE "/depconfig" +#define RC_DEPCONFIG RC_SVCDIR RC_DEPCONFIG_FILE static const char *bootlevel = NULL; @@ -121,7 +122,26 @@ get_deptype(const RC_DEPINFO *depinfo, const char *type) RC_DEPTREE * rc_deptree_load(void) { - return rc_deptree_load_file(RC_DEPTREE_CACHE); + + char *cache = RC_DEPTREE_CACHE; +#ifdef RC_USER_SERVICES + char *user_svcdir; + RC_DEPTREE *tree; + + if (rc_is_user()) { + user_svcdir = rc_user_svcdir(); + xasprintf(&cache, "%s/%s", user_svcdir, RC_DEPTREE_CACHE_FILE); + + tree = rc_deptree_load_file(cache); + + free(cache); + free(user_svcdir); + + return tree; + } +#endif + + return rc_deptree_load_file(cache); } RC_DEPTREE * @@ -668,19 +688,19 @@ static const DEPPAIR deppairs[] = { static const char *const depdirs[] = { - RC_SVCDIR, - RC_SVCDIR "/starting", - RC_SVCDIR "/started", - RC_SVCDIR "/stopping", - RC_SVCDIR "/inactive", - RC_SVCDIR "/wasinactive", - RC_SVCDIR "/failed", - RC_SVCDIR "/hotplugged", - RC_SVCDIR "/daemons", - RC_SVCDIR "/options", - RC_SVCDIR "/exclusive", - RC_SVCDIR "/scheduled", - RC_SVCDIR "/tmp", + "", + "/starting", + "/started", + "/stopping", + "/inactive", + "/wasinactive", + "/failed", + "/hotplugged", + "/daemons", + "/options", + "/exclusive", + "/scheduled", + "/tmp", NULL }; @@ -693,16 +713,31 @@ rc_deptree_update_needed(time_t *newest, char *file) int i; struct stat buf; time_t mtime; + char *depconfig = RC_DEPCONFIG, *cache = RC_DEPTREE_CACHE; + char *depdir = NULL; + char *svcdir = RC_SVCDIR; +#ifdef RC_USER_SERVICES + char *user_sysconfdir, *tmp; + + if (rc_is_user()) { + svcdir = rc_user_svcdir(); + user_sysconfdir = rc_user_sysconfdir(); + xasprintf(&cache, "%s/%s", svcdir, RC_DEPTREE_CACHE_FILE); + } +#endif /* Create base directories if needed */ - for (i = 0; depdirs[i]; i++) - if (mkdir(depdirs[i], 0755) != 0 && errno != EEXIST) - fprintf(stderr, "mkdir `%s': %s\n", depdirs[i], strerror(errno)); + for (i = 0; depdirs[i]; i++) { + xasprintf(&depdir, "%s/%s", svcdir, depdirs[i]); + if (mkdir(depdir, 0755) != 0 && errno != EEXIST) + fprintf(stderr, "mkdir `%s': %s\n", depdir, strerror(errno)); + free(depdir); + } /* Quick test to see if anything we use has changed and we have * data in our deptree. */ - if (stat(RC_DEPTREE_CACHE, &buf) == 0) { + if (stat(cache, &buf) == 0) { mtime = buf.st_mtime; } else { /* No previous cache found. @@ -726,12 +761,39 @@ rc_deptree_update_needed(time_t *newest, char *file) #endif #ifdef RC_LOCAL_CONFDIR newer |= !deep_mtime_check(RC_LOCAL_CONFDIR,true,&mtime,file); +#endif +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + newer |= !deep_mtime_check(RC_SYS_USER_INITDIR,true,&mtime,file); + newer |= !deep_mtime_check(RC_SYS_USER_CONFDIR,true,&mtime,file); + + xasprintf(&tmp, "%s/%s", user_sysconfdir, RC_USER_INITDIR_FOLDER); + newer |= !deep_mtime_check(tmp,true,&mtime,file); + free(tmp); + + xasprintf(&tmp, "%s/%s", user_sysconfdir, RC_USER_CONFDIR_FOLDER); + newer |= !deep_mtime_check(tmp,true,&mtime,file); + free(tmp); + + free(user_sysconfdir); + } #endif newer |= !deep_mtime_check(RC_CONF,true,&mtime,file); /* Some init scripts dependencies change depending on config files * outside of baselayout, like syslog-ng, so we check those too. */ - config = rc_config_list(RC_DEPCONFIG); +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + xasprintf(&depconfig, "%s/%s", svcdir, RC_DEPCONFIG_FILE); + free(svcdir); + } +#endif + config = rc_config_list(depconfig); +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + free(depconfig); + } +#endif TAILQ_FOREACH(s, config, entries) { newer |= !deep_mtime_check(s->value, true, &mtime, file); } @@ -768,6 +830,11 @@ rc_deptree_update(void) char *line = NULL; size_t len = 0; char *depend, *depends, *service, *type, *nosys, *onosys; + char *cache = RC_DEPTREE_CACHE; + char *depconfig = RC_DEPCONFIG; +#ifdef RC_USER_SERVICES + char *user_svcdir; +#endif size_t i, k, l; bool retval = true; const char *sys = rc_sys(); @@ -1048,7 +1115,16 @@ rc_deptree_update(void) This works and should be entirely shell parseable provided that depend names don't have any non shell variable characters in */ - if ((fp = fopen(RC_DEPTREE_CACHE, "w"))) { + +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + user_svcdir = rc_user_svcdir(); + xasprintf(&cache, "%s/%s", user_svcdir, RC_DEPTREE_CACHE_FILE); + xasprintf(&depconfig, "%s/%s", user_svcdir, RC_DEPCONFIG_FILE); + free(user_svcdir); + } +#endif + if ((fp = fopen(cache, "w"))) { i = 0; TAILQ_FOREACH(depinfo, deptree, entries) { fprintf(fp, "depinfo_%zu_service='%s'\n", @@ -1067,25 +1143,32 @@ rc_deptree_update(void) fclose(fp); } else { fprintf(stderr, "fopen `%s': %s\n", - RC_DEPTREE_CACHE, strerror(errno)); + cache, strerror(errno)); retval = false; } /* Save our external config files to disk */ if (TAILQ_FIRST(config)) { - if ((fp = fopen(RC_DEPCONFIG, "w"))) { + if ((fp = fopen(depconfig, "w"))) { TAILQ_FOREACH(s, config, entries) fprintf(fp, "%s\n", s->value); fclose(fp); } else { fprintf(stderr, "fopen `%s': %s\n", - RC_DEPCONFIG, strerror(errno)); + depconfig, strerror(errno)); retval = false; } } else { - unlink(RC_DEPCONFIG); + unlink(depconfig); } +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + free(cache); + free(depconfig); + } +#endif + rc_stringlist_free(config); free(deptree); return retval; diff --git a/src/librc/librc.c b/src/librc/librc.c index b3c98adb..9063b934 100644 --- a/src/librc/librc.c +++ b/src/librc/librc.c @@ -40,7 +40,8 @@ # include #endif -#define RC_RUNLEVEL RC_SVCDIR "/softlevel" +#define RC_RUNLEVEL_FOLDER "/softlevel" +#define RC_RUNLEVEL RC_SVCDIR RC_RUNLEVEL_FOLDER #ifndef S_IXUGO # define S_IXUGO (S_IXUSR | S_IXGRP | S_IXOTH) @@ -477,6 +478,7 @@ get_runlevel_chain(const char *runlevel, RC_STRINGLIST *level_list, RC_STRINGLIS RC_STRINGLIST *dirs; RC_STRING *d, *parent; const char *nextlevel; + char *runlevel_dir = RC_RUNLEVELDIR; /* * If we haven't been passed a runlevel or a level list, or @@ -498,7 +500,15 @@ get_runlevel_chain(const char *runlevel, RC_STRINGLIST *level_list, RC_STRINGLIS * We can now do exactly the above procedure for our chained * runlevels. */ - snprintf(path, sizeof(path), "%s/%s", RC_RUNLEVELDIR, runlevel); +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + char *user_sysconf = rc_user_sysconfdir(); + xasprintf(&runlevel_dir, "%s/%s", user_sysconf, RC_USER_RUNLEVELS_FOLDER); + free(user_sysconf); + } +#endif + + snprintf(path, sizeof(path), "%s/%s", runlevel_dir, runlevel); dirs = ls_dir(path, LS_DIR); TAILQ_FOREACH(d, dirs, entries) { nextlevel = d->value; @@ -521,33 +531,78 @@ get_runlevel_chain(const char *runlevel, RC_STRINGLIST *level_list, RC_STRINGLIS rc_stringlist_delete(ancestor_list, nextlevel); } rc_stringlist_free(dirs); + +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + free(runlevel_dir); + } +#endif } bool rc_runlevel_starting(void) { - return exists(RC_STARTING); + char *rc_starting = RC_STARTING; +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + char *user_svcdir = rc_user_svcdir(); + xasprintf(&rc_starting, "%s/%s", user_svcdir, RC_STARTING_FOLDER); + free(user_svcdir); + } +#endif + return exists(rc_starting); } bool rc_runlevel_stopping(void) { - return exists(RC_STOPPING); + char *rc_stopping = RC_STOPPING; +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + char *user_svcdir = rc_user_svcdir(); + xasprintf(&rc_stopping, "%s/%s", user_svcdir, RC_STOPPING_FOLDER); + free(user_svcdir); + } +#endif + return exists(rc_stopping); } RC_STRINGLIST *rc_runlevel_list(void) { - return ls_dir(RC_RUNLEVELDIR, LS_DIR); + char *runlevel_dir = RC_RUNLEVELDIR; + RC_STRINGLIST *runlevels; +#ifdef RC_USER_SERVICES + char *user_sysconfdir; + if (rc_is_user()) { + user_sysconfdir = rc_user_sysconfdir(); + xasprintf(&runlevel_dir, "%s/%s", user_sysconfdir, RC_USER_RUNLEVELS_FOLDER); + } +#endif + runlevels = ls_dir(runlevel_dir, LS_DIR); +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + free(runlevel_dir); + } +#endif + return runlevels; } char * rc_runlevel_get(void) { FILE *fp; - char *runlevel = NULL; + char *runlevel = NULL, *runlevel_path = RC_RUNLEVEL; size_t i; - if ((fp = fopen(RC_RUNLEVEL, "r"))) { +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + char *path = rc_user_svcdir(); + xasprintf(&runlevel_path, "%s/%s", path, RC_RUNLEVEL_FOLDER); + free(path); + } +#endif + + if ((fp = fopen(runlevel_path, "r"))) { runlevel = xmalloc(sizeof(char) * PATH_MAX); if (fgets(runlevel, PATH_MAX, fp)) { i = strlen(runlevel) - 1; @@ -558,6 +613,13 @@ rc_runlevel_get(void) fclose(fp); } +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + free(runlevel_path); + } +#endif + + if (!runlevel || !*runlevel) { free(runlevel); runlevel = xstrdup(RC_LEVEL_SYSINIT); @@ -569,12 +631,30 @@ rc_runlevel_get(void) bool rc_runlevel_set(const char *runlevel) { - FILE *fp = fopen(RC_RUNLEVEL, "w"); + char *runlevel_path = RC_RUNLEVEL; + FILE *fp; + +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + char *path = rc_user_svcdir(); + xasprintf(&runlevel_path, "%s/%s", path, RC_RUNLEVEL_FOLDER); + free(path); + } +#endif + + fp = fopen(runlevel_path, "w"); if (!fp) return false; fprintf(fp, "%s", runlevel); fclose(fp); + + +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + free(runlevel_path); + } +#endif return true; } @@ -587,7 +667,19 @@ rc_runlevel_exists(const char *runlevel) if (!runlevel || strcmp(runlevel, "") == 0 || strcmp(runlevel, ".") == 0 || strcmp(runlevel, "..") == 0) return false; - snprintf(path, sizeof(path), "%s/%s", RC_RUNLEVELDIR, runlevel); + +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + char *user_path = rc_user_sysconfdir(); + snprintf(path, sizeof(path), "%s/%s/%s", + user_path, RC_USER_RUNLEVELS_FOLDER, runlevel); + } else { +#endif + snprintf(path, sizeof(path), "%s/%s", RC_RUNLEVELDIR, runlevel); +#ifdef RC_USER_SERVICES + } +#endif + if (stat(path, &buf) == 0 && S_ISDIR(buf.st_mode)) return true; return false; @@ -597,11 +689,28 @@ bool rc_runlevel_stack(const char *dst, const char *src) { char d[PATH_MAX], s[PATH_MAX]; + char *runlevel_dir = RC_RUNLEVELDIR; if (!rc_runlevel_exists(dst) || !rc_runlevel_exists(src)) return false; + +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + char *user_sysconf = rc_user_sysconfdir(); + xasprintf(&runlevel_dir, "%s/%s", user_sysconf, RC_USER_RUNLEVELS_FOLDER); + free(user_sysconf); + } +#endif + snprintf(s, sizeof(s), "../%s", src); - snprintf(d, sizeof(s), "%s/%s/%s", RC_RUNLEVELDIR, dst, src); + snprintf(d, sizeof(s), "%s/%s/%s", runlevel_dir, dst, src); + +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + free(runlevel_dir); + } +#endif + return (symlink(s, d) == 0 ? true : false); } @@ -609,8 +718,23 @@ bool rc_runlevel_unstack(const char *dst, const char *src) { char path[PATH_MAX]; + char *runlevel_dir = RC_RUNLEVELDIR; + +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + char *user_sysconf = rc_user_sysconfdir(); + xasprintf(&runlevel_dir, "%s/%s", user_sysconf, RC_USER_RUNLEVELS_FOLDER); + free(user_sysconf); + } +#endif + + snprintf(path, sizeof(path), "%s/%s/%s", runlevel_dir, dst, src); - snprintf(path, sizeof(path), "%s/%s/%s", RC_RUNLEVELDIR, dst, src); +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + free(runlevel_dir); + } +#endif return (unlink(path) == 0 ? true : false); } @@ -632,9 +756,16 @@ rc_service_resolve(const char *service) { char buffer[PATH_MAX]; char file[PATH_MAX]; + char *svcdir = RC_SVCDIR; int r; struct stat buf; +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + svcdir = rc_user_svcdir(); + } +#endif + if (!service) return NULL; @@ -642,14 +773,20 @@ rc_service_resolve(const char *service) return xstrdup(service); /* First check started services */ - snprintf(file, sizeof(file), RC_SVCDIR "/%s/%s", "started", service); + snprintf(file, sizeof(file), "%s/%s/%s", svcdir, "started", service); if (lstat(file, &buf) || !S_ISLNK(buf.st_mode)) { - snprintf(file, sizeof(file), RC_SVCDIR "/%s/%s", - "inactive", service); + snprintf(file, sizeof(file), "%s/%s/%s", + svcdir, "inactive", service); if (lstat(file, &buf) || !S_ISLNK(buf.st_mode)) *file = '\0'; } +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + free(svcdir); + } +#endif + if (*file) { memset(buffer, 0, sizeof(buffer)); r = readlink(file, buffer, sizeof(buffer)-1); @@ -657,6 +794,24 @@ rc_service_resolve(const char *service) return xstrdup(buffer); } +#ifdef RC_USER_SERVICES + /* If we're in user services mode, try user services*/ + if (rc_is_user()) { + /* User defined services have preference to system provided */ + char *user_sysconfdir = rc_user_sysconfdir(); + snprintf(file, sizeof(file), "%s/%s/%s", + user_sysconfdir, RC_USER_INITDIR_FOLDER, service); + free(user_sysconfdir); + if (stat(file, &buf) == 0) + return xstrdup(file); + + snprintf(file, sizeof(file), "%s/%s", + RC_SYS_USER_INITDIR, service); + if (stat(file, &buf) == 0) + return xstrdup(file); + } +#endif + #ifdef RC_LOCAL_INITDIR /* Nope, so lets see if the user has written it */ snprintf(file, sizeof(file), RC_LOCAL_INITDIR "/%s", service); @@ -791,9 +946,24 @@ bool rc_service_in_runlevel(const char *service, const char *runlevel) { char file[PATH_MAX]; + char *runlevel_dir = RC_RUNLEVELDIR; + +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + char *user_sysconf = rc_user_sysconfdir(); + xasprintf(&runlevel_dir, "%s/%s", user_sysconf, RC_USER_RUNLEVELS_FOLDER); + free(user_sysconf); + } +#endif + snprintf(file, sizeof(file), "%s/%s/%s", + runlevel_dir, runlevel, basename_c(service)); + +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + free(runlevel_dir); + } +#endif - snprintf(file, sizeof(file), RC_RUNLEVELDIR "/%s/%s", - runlevel, basename_c(service)); return exists(file); } @@ -811,6 +981,12 @@ rc_service_mark(const char *service, const RC_SERVICE state) RC_STRINGLIST *dirs; RC_STRING *dir; int serrno; + char *svcdir = RC_SVCDIR; +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + svcdir = rc_user_svcdir(); + } +#endif if (!init) return false; @@ -822,8 +998,8 @@ rc_service_mark(const char *service, const RC_SERVICE state) return false; } - snprintf(file, sizeof(file), RC_SVCDIR "/%s/%s", - rc_parse_service_state(state), base); + snprintf(file, sizeof(file), "%s/%s/%s", + svcdir,rc_parse_service_state(state), base); if (exists(file)) unlink(file); i = symlink(init, file); @@ -849,15 +1025,16 @@ rc_service_mark(const char *service, const RC_SERVICE state) s != RC_SERVICE_SCHEDULED) && (!skip_wasinactive || s != RC_SERVICE_WASINACTIVE)) { - snprintf(file, sizeof(file), RC_SVCDIR "/%s/%s", - rc_service_state_names[i].name, base); + snprintf(file, sizeof(file), "%s/%s/%s", + svcdir, rc_service_state_names[i].name, base); if (exists(file)) { if ((state == RC_SERVICE_STARTING || state == RC_SERVICE_STOPPING) && s == RC_SERVICE_INACTIVE) { snprintf(was, sizeof(was), - RC_SVCDIR "/%s/%s", + "%s/%s/%s", + svcdir, rc_parse_service_state(RC_SERVICE_WASINACTIVE), base); if (symlink(init, was) == -1) { @@ -879,19 +1056,19 @@ rc_service_mark(const char *service, const RC_SERVICE state) state == RC_SERVICE_STOPPED || state == RC_SERVICE_INACTIVE) { - snprintf(file, sizeof(file), RC_SVCDIR "/%s/%s", - "exclusive", base); + snprintf(file, sizeof(file), "%s/%s/%s", + svcdir, "exclusive", base); unlink(file); } /* Remove any options and daemons the service may have stored */ if (state == RC_SERVICE_STOPPED) { - snprintf(file, sizeof(file), RC_SVCDIR "/%s/%s", - "options", base); + snprintf(file, sizeof(file), "%s/%s/%s", + svcdir, "options", base); rm_dir(file, true); - snprintf(file, sizeof(file), RC_SVCDIR "/%s/%s", - "daemons", base); + snprintf(file, sizeof(file), "%s/%s/%s", + svcdir, "daemons", base); rm_dir(file, true); rc_service_schedule_clear(service); @@ -899,7 +1076,7 @@ rc_service_mark(const char *service, const RC_SERVICE state) /* These are final states, so remove us from scheduled */ if (state == RC_SERVICE_STARTED || state == RC_SERVICE_STOPPED) { - snprintf(file, sizeof(file), RC_SVCDIR "/%s", "scheduled"); + snprintf(file, sizeof(file), "%s/%s", svcdir, "scheduled"); dirs = ls_dir(file, 0); TAILQ_FOREACH(dir, dirs, entries) { snprintf(was, sizeof(was), "%s/%s/%s", @@ -927,10 +1104,16 @@ rc_service_state(const char *service) RC_STRINGLIST *dirs; RC_STRING *dir; const char *base = basename_c(service); + char *svcdir = RC_SVCDIR; +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + svcdir = rc_user_svcdir(); + } +#endif for (i = 0; rc_service_state_names[i].name; i++) { - snprintf(file, sizeof(file), RC_SVCDIR "/%s/%s", - rc_service_state_names[i].name, base); + snprintf(file, sizeof(file), "%s/%s/%s", + svcdir, rc_service_state_names[i].name, base); if (exists(file)) { if (rc_service_state_names[i].state <= 0x10) state = rc_service_state_names[i].state; @@ -944,11 +1127,14 @@ rc_service_state(const char *service) state |= RC_SERVICE_CRASHED; } if (state & RC_SERVICE_STOPPED) { - dirs = ls_dir(RC_SVCDIR "/scheduled", 0); + char *scheduled; + xasprintf(&scheduled, "%s/%s", svcdir, "/scheduled"); + dirs = ls_dir(scheduled, 0); + free(scheduled); TAILQ_FOREACH(dir, dirs, entries) { snprintf(file, sizeof(file), - RC_SVCDIR "/scheduled/%s/%s", - dir->value, service); + "%s/scheduled/%s/%s", + svcdir, dir->value, service); if (exists(file)) { state |= RC_SERVICE_SCHEDULED; break; @@ -966,9 +1152,15 @@ rc_service_value_get(const char *service, const char *option) char *buffer = NULL; size_t len = 0; char file[PATH_MAX]; + char *svcdir = RC_SVCDIR; +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + svcdir = rc_user_svcdir(); + } +#endif - snprintf(file, sizeof(file), RC_SVCDIR "/options/%s/%s", - service, option); + snprintf(file, sizeof(file), "%s/options/%s/%s", + svcdir, service, option); rc_getfile(file, &buffer, &len); return buffer; @@ -981,8 +1173,14 @@ rc_service_value_set(const char *service, const char *option, FILE *fp; char file[PATH_MAX]; char *p = file; + char *svcdir = RC_SVCDIR; +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + svcdir = rc_user_svcdir(); + } +#endif - p += snprintf(file, sizeof(file), RC_SVCDIR "/options/%s", service); + p += snprintf(file, sizeof(file), "%s/options/%s", svcdir, service); if (mkdir(file, 0755) != 0 && errno != EEXIST) return false; @@ -1006,13 +1204,19 @@ rc_service_schedule_start(const char *service, const char *service_to_start) char *p = file; char *init; bool retval; + char *svcdir = RC_SVCDIR; +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + svcdir = rc_user_svcdir(); + } +#endif /* service may be a provided service, like net */ if (!service || !rc_service_exists(service_to_start)) return false; - p += snprintf(file, sizeof(file), RC_SVCDIR "/scheduled/%s", - basename_c(service)); + p += snprintf(file, sizeof(file), "%s/scheduled/%s", + svcdir, basename_c(service)); if (mkdir(file, 0755) != 0 && errno != EEXIST) return false; @@ -1028,9 +1232,15 @@ bool rc_service_schedule_clear(const char *service) { char dir[PATH_MAX]; + char *svcdir = RC_SVCDIR; +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + svcdir = rc_user_svcdir(); + } +#endif - snprintf(dir, sizeof(dir), RC_SVCDIR "/scheduled/%s", - basename_c(service)); + snprintf(dir, sizeof(dir), "%s/scheduled/%s", + svcdir, basename_c(service)); if (!rm_dir(dir, true) && errno == ENOENT) return true; return false; @@ -1041,6 +1251,7 @@ rc_services_in_runlevel(const char *runlevel) { char dir[PATH_MAX]; RC_STRINGLIST *list = NULL; + char *runlevel_dir = RC_RUNLEVELDIR; if (!runlevel) { #ifdef RC_PKG_INITDIR @@ -1049,9 +1260,28 @@ rc_services_in_runlevel(const char *runlevel) #ifdef RC_LOCAL_INITDIR RC_STRINGLIST *local = ls_dir(RC_LOCAL_INITDIR, LS_INITD); #endif +#ifdef RC_USER_SERVICES + RC_STRINGLIST *local_user; + RC_STRINGLIST *sys_user; + char *user_sysconf; + char *user_initdir; + if (rc_is_user()) { + user_sysconf = rc_user_sysconfdir(); + xasprintf(&user_initdir, "%s/%s", user_sysconf, RC_USER_INITDIR_FOLDER); + + local_user = ls_dir(user_initdir, LS_INITD); + sys_user = ls_dir(RC_SYS_USER_INITDIR, LS_INITD); + + list = rc_stringlist_new(); + } else { +#endif list = ls_dir(RC_INITDIR, LS_INITD); +#ifdef RC_USER_SERVICES + } +#endif + #ifdef RC_PKG_INITDIR TAILQ_CONCAT(list, pkg, entries); rc_stringlist_free(pkg); @@ -1059,14 +1289,38 @@ rc_services_in_runlevel(const char *runlevel) #ifdef RC_LOCAL_INITDIR TAILQ_CONCAT(list, local, entries); rc_stringlist_free(local); +#endif +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + TAILQ_CONCAT(list, local_user, entries); + TAILQ_CONCAT(list, sys_user, entries); + rc_stringlist_free(local_user); + rc_stringlist_free(sys_user); + free(user_sysconf); + free(user_initdir); + } #endif return list; } /* These special levels never contain any services */ if (strcmp(runlevel, RC_LEVEL_SINGLE) != 0) { - snprintf(dir, sizeof(dir), RC_RUNLEVELDIR "/%s", runlevel); +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + char *user_sysconf = rc_user_sysconfdir(); + xasprintf(&runlevel_dir, "%s/%s", user_sysconf, RC_USER_RUNLEVELS_FOLDER); + free(user_sysconf); + } +#endif + snprintf(dir, sizeof(dir), "%s/%s", runlevel_dir, runlevel); list = ls_dir(dir, LS_INITD); + +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + free(runlevel_dir); + } +#endif + } if (!list) list = rc_stringlist_new(); @@ -1099,9 +1353,16 @@ rc_services_in_state(RC_SERVICE state) RC_STRING *d; char dir[PATH_MAX]; char *p = dir; + char *svcdir = RC_SVCDIR; - p += snprintf(dir, sizeof(dir), RC_SVCDIR "/%s", - rc_parse_service_state(state)); +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + svcdir = rc_user_svcdir(); + } +#endif + + p += snprintf(dir, sizeof(dir), "%s/%s", + svcdir, rc_parse_service_state(state)); if (state != RC_SERVICE_SCHEDULED) return ls_dir(dir, LS_INITD); @@ -1120,6 +1381,13 @@ rc_services_in_state(RC_SERVICE state) } } rc_stringlist_free(dirs); + +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + free(svcdir); + } +#endif + return list; } @@ -1131,7 +1399,15 @@ rc_service_add(const char *runlevel, const char *service) char file[PATH_MAX]; char path[MAXPATHLEN] = { '\0' }; char binit[PATH_MAX]; + char *runlevel_dir = RC_RUNLEVELDIR; char *i; +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + char *user_sysconf = rc_user_sysconfdir(); + xasprintf(&runlevel_dir, "%s/%s", user_sysconf, RC_USER_RUNLEVELS_FOLDER); + free(user_sysconf); + } +#endif if (!rc_runlevel_exists(runlevel)) { errno = ENOENT; @@ -1144,8 +1420,14 @@ rc_service_add(const char *runlevel, const char *service) } i = init = rc_service_resolve(service); - snprintf(file, sizeof(file), RC_RUNLEVELDIR "/%s/%s", - runlevel, basename_c(service)); + snprintf(file, sizeof(file), "%s/%s/%s", + runlevel_dir, runlevel, basename_c(service)); + +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + free(runlevel_dir); + } +#endif /* We need to ensure that only things in /etc/init.d are added * to the boot runlevel */ @@ -1172,9 +1454,25 @@ bool rc_service_delete(const char *runlevel, const char *service) { char file[PATH_MAX]; + char *runlevel_dir = RC_RUNLEVELDIR; + +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + char *user_sysconf = rc_user_sysconfdir(); + xasprintf(&runlevel_dir, "%s/%s", user_sysconf, RC_USER_RUNLEVELS_FOLDER); + free(user_sysconf); + } +#endif + + snprintf(file, sizeof(file), "%s/%s/%s", + runlevel_dir, runlevel, basename_c(service)); + +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + free(runlevel_dir); + } +#endif - snprintf(file, sizeof(file), RC_RUNLEVELDIR "/%s/%s", - runlevel, basename_c(service)); if (unlink(file) == 0) return true; return false; @@ -1183,14 +1481,25 @@ rc_service_delete(const char *runlevel, const char *service) RC_STRINGLIST * rc_services_scheduled_by(const char *service) { - RC_STRINGLIST *dirs = ls_dir(RC_SVCDIR "/scheduled", 0); + RC_STRINGLIST *dirs; RC_STRINGLIST *list = rc_stringlist_new(); RC_STRING *dir; char file[PATH_MAX]; + char *scheduled; + char *svcdir = RC_SVCDIR; + +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + svcdir = rc_user_svcdir(); + } +#endif + xasprintf(&scheduled, "%s/%s", svcdir, "/scheduled"); + dirs = ls_dir(scheduled, 0); + free(scheduled); TAILQ_FOREACH(dir, dirs, entries) { - snprintf(file, sizeof(file), RC_SVCDIR "/scheduled/%s/%s", - dir->value, service); + snprintf(file, sizeof(file), "%s/scheduled/%s/%s", + svcdir, dir->value, service); if (exists(file)) rc_stringlist_add(list, file); } @@ -1202,8 +1511,15 @@ RC_STRINGLIST * rc_services_scheduled(const char *service) { char dir[PATH_MAX]; + char *svcdir = RC_SVCDIR; + +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + svcdir = rc_user_svcdir(); + } +#endif - snprintf(dir, sizeof(dir), RC_SVCDIR "/scheduled/%s", - basename_c(service)); + snprintf(dir, sizeof(dir), "%s/scheduled/%s", + svcdir, basename_c(service)); return ls_dir(dir, LS_INITD); } diff --git a/src/openrc-run/meson.build b/src/openrc-run/meson.build index 2e4b29a0..5de60456 100644 --- a/src/openrc-run/meson.build +++ b/src/openrc-run/meson.build @@ -1,6 +1,6 @@ executable('openrc-run', ['openrc-run.c', misc_c, plugin_c, selinux_c, usage_c, version_h], - c_args : [cc_audit_flags, cc_branding_flags, cc_pam_flags, cc_selinux_flags], + c_args : [cc_audit_flags, cc_branding_flags, cc_pam_flags, cc_selinux_flags, cc_user_services_flags], link_with: [libeinfo, librc], dependencies: [audit_dep, dl_dep, pam_dep, pam_misc_dep, selinux_dep, util_dep, crypt_dep], include_directories: [incdir, einfo_incdir, rc_incdir], @@ -9,7 +9,7 @@ executable('openrc-run', executable('runscript', ['openrc-run.c', misc_c, plugin_c, selinux_c, usage_c, version_h], - c_args : [cc_audit_flags, cc_branding_flags, cc_pam_flags, cc_selinux_flags], + c_args : [cc_audit_flags, cc_branding_flags, cc_pam_flags, cc_selinux_flags, cc_user_services_flags], link_with: [libeinfo, librc], dependencies: [audit_dep, dl_dep, pam_dep, pam_misc_dep, selinux_dep, util_dep, crypt_dep], include_directories: [incdir, einfo_incdir, rc_incdir], diff --git a/src/openrc-run/openrc-run.c b/src/openrc-run/openrc-run.c index 6b28a809..6ce5ddec 100644 --- a/src/openrc-run/openrc-run.c +++ b/src/openrc-run/openrc-run.c @@ -54,7 +54,7 @@ #include "_usage.h" #include "helpers.h" -#define PREFIX_LOCK RC_SVCDIR "/prefix.lock" +#define PREFIX_LOCK_FILE "/prefix.lock" #define WAIT_INTERVAL 20000000 /* usecs to poll the lock file */ #define WAIT_TIMEOUT 60 /* seconds until we timeout */ @@ -164,8 +164,21 @@ static void unhotplug(void) { char *file = NULL; + char *svcdir = RC_SVCDIR; +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + svcdir = rc_user_svcdir(); + } +#endif + + xasprintf(&file, "%s/hotplugged/%s", svcdir, applet); + +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + free(svcdir); + } +#endif - xasprintf(&file, RC_SVCDIR "/hotplugged/%s", applet); if (exists(file) && unlink(file) != 0) eerror("%s: unlink `%s': %s", applet, file, strerror(errno)); free(file); @@ -286,12 +299,25 @@ write_prefix(const char *buffer, size_t bytes, bool *prefixed) const char *ec_normal = ecolor(ECOLOR_NORMAL); ssize_t ret = 0; int fd = fileno(stdout), lock_fd = -1; + char *prefix_lock = RC_SVCDIR PREFIX_LOCK_FILE; +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + char *svcdir = rc_user_svcdir(); + xasprintf(&prefix_lock, "%s/%s", svcdir, PREFIX_LOCK_FILE); + free(svcdir); + } +#endif /* * Lock the prefix. - * open() may fail here when running as user, as RC_SVCDIR may not be writable. */ - lock_fd = open(PREFIX_LOCK, O_WRONLY | O_CREAT, 0664); + lock_fd = open(prefix_lock, O_WRONLY | O_CREAT, 0664); + +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + free(prefix_lock); + } +#endif if (lock_fd != -1) { while (flock(lock_fd, LOCK_EX) != 0) { @@ -350,6 +376,13 @@ svc_exec(const char *arg1, const char *arg2) int slave_tty; sigset_t sigchldmask; sigset_t oldmask; + char *runsh; + char *svcdir = RC_SVCDIR; +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + svcdir = rc_user_svcdir(); + } +#endif /* Setup our signal pipe */ if (pipe(signal_pipe) == -1) @@ -390,20 +423,19 @@ svc_exec(const char *arg1, const char *arg2) dup2(slave_tty, STDERR_FILENO); } - if (exists(RC_SVCDIR "/openrc-run.sh")) { + xasprintf(&runsh, "%s/%s", svcdir, "openrc-run.sh"); + if (exists(runsh)) { if (arg2) einfov("Executing: %s %s %s %s %s", - RC_SVCDIR "/openrc-run.sh", RC_SVCDIR "/openrc-run.sh", + runsh, runsh, service, arg1, arg2); else einfov("Executing: %s %s %s %s", - RC_SVCDIR "/openrc-run.sh", RC_SVCDIR "/openrc-run.sh", + runsh, runsh, service, arg1); - execl(RC_SVCDIR "/openrc-run.sh", - RC_SVCDIR "/openrc-run.sh", - service, arg1, arg2, (char *) NULL); - eerror("%s: exec `" RC_SVCDIR "/openrc-run.sh': %s", - service, strerror(errno)); + execl(runsh, runsh, service, arg1, arg2, (char *) NULL); + eerror("%s: exec '%s': %s", + service, runsh, strerror(errno)); _exit(EXIT_FAILURE); } else { if (arg2) @@ -423,8 +455,15 @@ svc_exec(const char *arg1, const char *arg2) service, strerror(errno)); _exit(EXIT_FAILURE); } + free(runsh); } +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + free(svcdir); + } +#endif + buffer = xmalloc(sizeof(char) * BUFSIZ); fd[0].fd = signal_pipe[0]; fd[0].events = fd[1].events = POLLIN; @@ -500,6 +539,12 @@ svc_wait(const char *svc) bool forever = false; RC_STRINGLIST *keywords; struct timespec interval, timeout, warn; + char *svcdir = RC_SVCDIR; +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + svcdir = rc_user_svcdir(); + } +#endif /* Some services don't have a timeout, like fsck */ keywords = rc_deptree_depend(deptree, svc, "keyword"); @@ -508,7 +553,13 @@ svc_wait(const char *svc) forever = true; rc_stringlist_free(keywords); - xasprintf(&file, RC_SVCDIR "/exclusive/%s", basename_c(svc)); + xasprintf(&file, "%s/exclusive/%s", svcdir, basename_c(svc)); + +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + free(svcdir); + } +#endif interval.tv_sec = 0; interval.tv_nsec = WAIT_INTERVAL; diff --git a/src/openrc/meson.build b/src/openrc/meson.build index 9ab32ef4..3ad0d62b 100644 --- a/src/openrc/meson.build +++ b/src/openrc/meson.build @@ -1,6 +1,6 @@ executable('openrc', ['rc.c', 'rc-logger.c', misc_c, plugin_c, usage_c, version_h], - c_args : cc_branding_flags, + c_args : [cc_branding_flags, cc_user_services_flags], link_with: [libeinfo, librc], dependencies: [dl_dep, util_dep], include_directories: [incdir, einfo_incdir, rc_incdir], @@ -9,7 +9,7 @@ executable('openrc', executable('rc', ['rc.c', 'rc-logger.c', misc_c, plugin_c, usage_c, version_h], - c_args : cc_branding_flags, + c_args : [cc_branding_flags, cc_user_services_flags], link_with: [libeinfo, librc], dependencies: [dl_dep, util_dep], include_directories: [incdir, einfo_incdir, rc_incdir], diff --git a/src/openrc/rc.c b/src/openrc/rc.c index 36b2e2e2..dca94a7f 100644 --- a/src/openrc/rc.c +++ b/src/openrc/rc.c @@ -49,12 +49,20 @@ #include "helpers.h" const char *extraopts = NULL; -const char getoptstring[] = "a:no:s:S" getoptstring_COMMON; +const char getoptstring[] = "a:no:s:S" getoptstring_COMMON +#ifdef RC_USER_SERVICES +getoptstring_USER_SERVICES +#endif +; + const struct option longopts[] = { { "no-stop", 0, NULL, 'n' }, { "override", 1, NULL, 'o' }, { "service", 1, NULL, 's' }, { "sys", 0, NULL, 'S' }, +#ifdef RC_USER_SERVICES + longopts_USER_SERVICES +#endif longopts_COMMON }; const char * const longopts_help[] = { @@ -62,6 +70,9 @@ const char * const longopts_help[] = { "override the next runlevel to change into\nwhen leaving single user or boot runlevels", "runs the service specified with the rest\nof the arguments", "output the RC system type, if any", +#ifdef RC_USER_SERVICES + longopts_help_USER_SERVICES +#endif longopts_help_COMMON }; const char *usagestring = "" \ @@ -70,7 +81,8 @@ const char *usagestring = "" \ #define INITSH RC_LIBEXECDIR "/sh/init.sh" #define INITEARLYSH RC_LIBEXECDIR "/sh/init-early.sh" -#define INTERACTIVE RC_SVCDIR "/interactive" +#define INTERACTIVE_FILE "/interactive" +#define INTERACTIVE RC_SVCDIR INTERACTIVE_FILE #define DEVBOOT "/dev/.rcboot" @@ -95,16 +107,24 @@ clean_failed(void) DIR *dp; struct dirent *d; char *path; + char *svcdir = RC_SVCDIR; + char *failed_path; +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + svcdir = rc_user_svcdir(); + } +#endif + xasprintf(&failed_path, "%s/%s", svcdir, "/failed"); /* Clean the failed services state dir now */ - if ((dp = opendir(RC_SVCDIR "/failed"))) { + if ((dp = opendir(failed_path))) { while ((d = readdir(dp))) { if (d->d_name[0] == '.' && (d->d_name[1] == '\0' || (d->d_name[1] == '.' && d->d_name[2] == '\0'))) continue; - xasprintf(&path, RC_SVCDIR "/failed/%s", d->d_name); + xasprintf(&path, "%s/%s", failed_path, d->d_name); if (unlink(path)) eerror("%s: unlink `%s': %s", applet, path, strerror(errno)); @@ -112,12 +132,24 @@ clean_failed(void) } closedir(dp); } + free(failed_path); +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + free(svcdir); + } +#endif } static void cleanup(void) { RC_PID *p, *tmp; + char *rc_starting = RC_STARTING; + char *rc_stopping = RC_STOPPING; + +#ifdef RC_USER_SERVICES + char *user_svcdir; +#endif if (!rc_in_logger && !rc_in_plugin && applet && (strcmp(applet, "rc") == 0 || strcmp(applet, "openrc") == 0)) @@ -132,11 +164,27 @@ cleanup(void) free(termios_orig); } +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + user_svcdir = rc_user_svcdir(); + xasprintf(&rc_starting, "%s/%s", user_svcdir, RC_STARTING_FOLDER); + xasprintf(&rc_stopping, "%s/%s", user_svcdir, RC_STOPPING_FOLDER); + free(user_svcdir); + } +#endif + /* Clean runlevel start, stop markers */ - rmdir(RC_STARTING); - rmdir(RC_STOPPING); + rmdir(rc_starting); + rmdir(rc_stopping); clean_failed(); rc_logger_close(); + +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + free(rc_starting); + free(rc_stopping); + } +#endif } LIST_FOREACH_SAFE(p, &service_pids, entries, tmp) { @@ -211,9 +259,24 @@ want_interactive(void) static void mark_interactive(void) { - FILE *fp = fopen(INTERACTIVE, "w"); + FILE *fp; + char *interactive_path = INTERACTIVE; +#ifdef RC_USER_SERVICES + char *svcdir; + if (rc_is_user()) { + svcdir = rc_user_svcdir(); + xasprintf(&interactive_path, "%s/%s", svcdir, INTERACTIVE_FILE); + free(svcdir); + } +#endif + fp = fopen(interactive_path, "w"); if (fp) fclose(fp); +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + free(interactive_path); + } +#endif } static void @@ -649,9 +712,18 @@ do_start_services(const RC_STRINGLIST *start_services, bool parallel) bool interactive = false; RC_SERVICE state; bool crashed = false; + char *interactive_path = INTERACTIVE; +#ifdef RC_USER_SERVICES + char *svcdir; + if (rc_is_user()) { + svcdir = rc_user_svcdir(); + xasprintf(&interactive_path, "%s/%s", svcdir, INTERACTIVE_FILE); + free(svcdir); + } +#endif if (!rc_yesno(getenv("EINFO_QUIET"))) - interactive = exists(INTERACTIVE); + interactive = exists(interactive_path); errno = 0; crashed = rc_conf_yesno("rc_crashed_start"); if (errno == ENOENT) @@ -710,10 +782,15 @@ do_start_services(const RC_STRINGLIST *start_services, bool parallel) strcmp(runlevel, getenv("RC_BOOTLEVEL")) == 0)) mark_interactive(); else { - if (exists(INTERACTIVE)) - unlink(INTERACTIVE); + if (exists(interactive_path)) + unlink(interactive_path); } +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + free(interactive_path); + } +#endif } #ifdef RC_DEBUG @@ -754,10 +831,16 @@ int main(int argc, char **argv) int depoptions = RC_DEP_STRICT | RC_DEP_TRACE; char *krunlevel = NULL; char *pidstr = NULL; + char *rc_starting = RC_STARTING; + char *rc_stopping = RC_STOPPING; int opt; bool parallel; int regen = 0; bool nostop = false; +#ifdef RC_USER_SERVICES + char *user_svcdir = NULL; + char *user_sysconfdir = NULL; +#endif #ifdef __linux__ char *proc; char *p; @@ -784,11 +867,6 @@ int main(int argc, char **argv) if (chdir("/") == -1) eerror("chdir: %s", strerror(errno)); - /* Ensure our environment is pure - * Also, add our configuration to it */ - env_filter(); - env_config(); - /* complain about old configuration settings if they exist */ if (exists(RC_CONF_OLD)) { ewarn("%s still exists on your system and should be removed.", @@ -833,10 +911,18 @@ int main(int argc, char **argv) printf("%s\n", systype); exit(EXIT_SUCCESS); /* NOTREACHED */ +#ifdef RC_USER_SERVICES + case_RC_USER_SERVICES +#endif case_RC_COMMON_GETOPT } } + /* Ensure our environment is pure + * Also, add our configuration to it */ + env_filter(); + env_config(); + if (strcmp(applet, "rc") == 0) ewarn("rc is deprecated, please use openrc instead."); newlevel = argv[optind++]; @@ -858,6 +944,15 @@ int main(int argc, char **argv) setenv("RC_PID", pidstr, 1); free(pidstr); +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + user_svcdir = rc_user_svcdir(); + xasprintf(&rc_stopping, "%s/%s", user_svcdir, RC_STOPPING_FOLDER); + xasprintf(&rc_starting, "%s/%s", user_svcdir, RC_STARTING_FOLDER); + free(user_svcdir); + } +#endif + /* Create a list of all services which should be started for the new or * current runlevel including those in boot, sysinit and hotplugged * runlevels. Clearly, some of these will already be started so we @@ -960,7 +1055,7 @@ int main(int argc, char **argv) /* Clean the failed services state dir */ clean_failed(); - if (mkdir(RC_STOPPING, 0755) != 0) { + if (mkdir(rc_stopping, 0755) != 0) { if (errno == EACCES) eerrorx("%s: superuser access required", applet); eerrorx("%s: failed to create stopping dir `%s': %s", @@ -1046,7 +1141,7 @@ int main(int argc, char **argv) going_down ? newlevel : runlevel); hook_out = 0; - rmdir(RC_STOPPING); + rmdir(rc_stopping); /* Store the new runlevel */ if (newlevel) { @@ -1063,7 +1158,7 @@ int main(int argc, char **argv) rc_logger_close(); #endif - mkdir(RC_STARTING, 0755); + mkdir(rc_starting, 0755); rc_plugin_run(RC_HOOK_RUNLEVEL_START_IN, runlevel); hook_out = RC_HOOK_RUNLEVEL_START_OUT; @@ -1111,6 +1206,12 @@ int main(int argc, char **argv) } rc_stringlist_free(runlevel_chain); } +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + free(rc_stopping); + free(rc_starting); + } +#endif #ifdef __linux__ /* If the "noinit" parameter was passed on the kernel command line then diff --git a/src/rc-service/meson.build b/src/rc-service/meson.build index 3a109d8e..543daf45 100644 --- a/src/rc-service/meson.build +++ b/src/rc-service/meson.build @@ -1,6 +1,6 @@ executable('rc-service', ['rc-service.c', misc_c, usage_c, version_h], - c_args : cc_branding_flags, + c_args : [cc_branding_flags, cc_user_services_flags], link_with: [libeinfo, librc], include_directories: [incdir, einfo_incdir, rc_incdir], install: true, diff --git a/src/rc-service/rc-service.c b/src/rc-service/rc-service.c index 3d2eb730..5127e75f 100644 --- a/src/rc-service/rc-service.c +++ b/src/rc-service/rc-service.c @@ -31,7 +31,11 @@ const char *applet = NULL; const char *extraopts = NULL; -const char getoptstring[] = "cdDe:ilr:INsSZ" getoptstring_COMMON; +const char getoptstring[] = "cdDe:ilr:INsSZ" getoptstring_COMMON +#ifdef RC_USER_SERVICES +getoptstring_USER_SERVICES +#endif +; const struct option longopts[] = { { "debug", 0, NULL, 'd' }, { "nodeps", 0, NULL, 'D' }, @@ -45,6 +49,9 @@ const struct option longopts[] = { { "list", 0, NULL, 'l' }, { "resolve", 1, NULL, 'r' }, { "dry-run", 0, NULL, 'Z' }, +#ifdef RC_USER_SERVICES + longopts_USER_SERVICES +#endif longopts_COMMON }; const char * const longopts_help[] = { @@ -60,6 +67,9 @@ const char * const longopts_help[] = { "list all available services", "resolve the service name to an init script", "dry run (show what would happen)", +#ifdef RC_USER_SERVICES + longopts_help_USER_SERVICES +#endif longopts_help_COMMON }; const char *usagestring = "" \ @@ -141,7 +151,9 @@ int main(int argc, char **argv) case 'Z': setenv("IN_DRYRUN", "yes", 1); break; - +#ifdef RC_USER_SERVICES + case_RC_USER_SERVICES +#endif case_RC_COMMON_GETOPT } } diff --git a/src/rc-status/meson.build b/src/rc-status/meson.build index 7377d837..ee3f3782 100644 --- a/src/rc-status/meson.build +++ b/src/rc-status/meson.build @@ -1,6 +1,6 @@ executable('rc-status', ['rc-status.c', misc_c, usage_c, version_h], - c_args : cc_branding_flags, + c_args : [cc_branding_flags, cc_user_services_flags], link_with: [libeinfo, librc], dependencies: [util_dep], include_directories: [incdir, einfo_incdir, rc_incdir], diff --git a/src/rc-status/rc-status.c b/src/rc-status/rc-status.c index 78d5a6af..d56fe830 100644 --- a/src/rc-status/rc-status.c +++ b/src/rc-status/rc-status.c @@ -39,7 +39,11 @@ enum format_t { const char *applet = NULL; const char *extraopts = NULL; +#ifdef RC_USER_SERVICES +const char getoptstring[] = "acf:lmrsSu" getoptstring_COMMON getoptstring_USER_SERVICES; +#else const char getoptstring[] = "acf:lmrsSu" getoptstring_COMMON; +#endif const struct option longopts[] = { {"all", 0, NULL, 'a'}, {"crashed", 0, NULL, 'c'}, @@ -50,6 +54,9 @@ const struct option longopts[] = { {"servicelist", 0, NULL, 's'}, {"supervised", 0, NULL, 'S'}, {"unused", 0, NULL, 'u'}, +#ifdef RC_USER_SERVICES + longopts_USER_SERVICES +#endif longopts_COMMON }; const char * const longopts_help[] = { @@ -62,6 +69,9 @@ const char * const longopts_help[] = { "Show service list", "show supervised services", "Show services not assigned to any runlevel", +#ifdef RC_USER_SERVICES + longopts_help_USER_SERVICES +#endif longopts_help_COMMON }; const char *usagestring = "" \ @@ -354,6 +364,9 @@ int main(int argc, char **argv) goto exit; /* NOTREACHED */ +#ifdef RC_USER_SERVICES + case_RC_USER_SERVICES +#endif case_RC_COMMON_GETOPT } diff --git a/src/rc-update/meson.build b/src/rc-update/meson.build index 7d284979..4d3b9015 100644 --- a/src/rc-update/meson.build +++ b/src/rc-update/meson.build @@ -1,6 +1,6 @@ executable('rc-update', ['rc-update.c', misc_c, usage_c, version_h], - c_args : cc_branding_flags, + c_args : [cc_branding_flags, cc_user_services_flags], link_with: [libeinfo, librc], include_directories: [incdir, einfo_incdir, rc_incdir], install: true, diff --git a/src/rc-update/rc-update.c b/src/rc-update/rc-update.c index 64d781a5..f4bbbe88 100644 --- a/src/rc-update/rc-update.c +++ b/src/rc-update/rc-update.c @@ -36,17 +36,27 @@ const char *usagestring = "" \ "Usage: rc-update [options] add [...]\n" \ " or: rc-update [options] del [...]\n" \ " or: rc-update [options] [show [...]]"; -const char getoptstring[] = "asu" getoptstring_COMMON; +const char getoptstring[] = "asu" getoptstring_COMMON +#ifdef RC_USER_SERVICES +getoptstring_USER_SERVICES +#endif +; const struct option longopts[] = { { "all", 0, NULL, 'a' }, { "stack", 0, NULL, 's' }, { "update", 0, NULL, 'u' }, +#ifdef RC_USER_SERVICES + longopts_USER_SERVICES +#endif longopts_COMMON }; const char * const longopts_help[] = { "Process all runlevels", "Stack a runlevel instead of a service", "Force an update of the dependency tree", +#ifdef RC_USER_SERVICES + longopts_help_USER_SERVICES +#endif longopts_help_COMMON }; @@ -236,6 +246,9 @@ int main(int argc, char **argv) if (deptree) rc_deptree_free(deptree); return ret; +#ifdef RC_USER_SERVICES + case_RC_USER_SERVICES +#endif case_RC_COMMON_GETOPT } diff --git a/src/shared/_usage.h b/src/shared/_usage.h index 91b956e0..3264ad6f 100644 --- a/src/shared/_usage.h +++ b/src/shared/_usage.h @@ -45,6 +45,16 @@ case 'q': case_RC_COMMON_getopt_case_q; break; \ default: case_RC_COMMON_getopt_default; break; +#ifdef RC_USER_SERVICES +#define case_RC_USER_SERVICES \ + case 'U': rc_set_user(); break; +#define getoptstring_USER_SERVICES "U" +#define longopts_USER_SERVICES \ + { "user", 0, NULL, 'U'}, +#define longopts_help_USER_SERVICES \ + "use user facing services", +#endif + extern const char *applet; extern const char *extraopts; extern const char getoptstring[]; diff --git a/src/shared/misc.c b/src/shared/misc.c index 28f4e235..6970513f 100644 --- a/src/shared/misc.c +++ b/src/shared/misc.c @@ -79,6 +79,16 @@ env_filter(void) /* Add the user defined list of vars */ env_allow = rc_stringlist_split(rc_conf_value("rc_env_allow"), " "); + +#ifdef RC_USER_SERVICES + /* Needed for local user services to be found */ + if (rc_is_user()) { + rc_stringlist_addu(env_allow, "HOME"); + rc_stringlist_addu(env_allow, "XDG_RUNTIME_DIR"); + rc_stringlist_addu(env_allow, "XDG_CONFIG_HOME"); + rc_stringlist_addu(env_allow, "RC_USER_SERVICES"); + } +#endif /* * If '*' is an entry in rc_env_allow, do nothing as we are to pass * through all environment variables. @@ -247,8 +257,22 @@ svc_lock(const char *applet, bool ignore_lock_failure) { char *file = NULL; int fd; + char *svcdir = RC_SVCDIR; + +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + svcdir = rc_user_svcdir(); + } +#endif + + xasprintf(&file, "%s/exclusive/%s", svcdir, applet); + +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + free(svcdir); + } +#endif - xasprintf(&file, RC_SVCDIR "/exclusive/%s", applet); fd = open(file, O_WRONLY | O_CREAT | O_NONBLOCK, 0664); free(file); if (fd == -1) @@ -273,8 +297,22 @@ int svc_unlock(const char *applet, int fd) { char *file = NULL; + char *svcdir = RC_SVCDIR; + +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + svcdir = rc_user_svcdir(); + } +#endif + + xasprintf(&file, "%s/exclusive/%s", svcdir, applet); + +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + free(svcdir); + } +#endif - xasprintf(&file, RC_SVCDIR "/exclusive/%s", applet); close(fd); unlink(file); free(file); @@ -385,15 +423,33 @@ RC_DEPTREE * _rc_deptree_load(int force, int *regen) struct stat st; struct utimbuf ut; FILE *fp; + char *cache = RC_DEPTREE_CACHE; + char *skew = RC_DEPTREE_SKEWED; +#ifdef RC_USER_SERVICES + char *user_svcdir; + if (rc_is_user()) { + user_svcdir = rc_user_svcdir(); + xasprintf(&cache, "%s%s", user_svcdir, RC_DEPTREE_CACHE_FILE); + xasprintf(&skew, "%s%s", user_svcdir, RC_DEPTREE_SKEWED_FILE); + free(user_svcdir); + } +#endif t = 0; if (rc_deptree_update_needed(&t, file) || force != 0) { /* Test if we have permission to update the deptree */ - fd = open(RC_DEPTREE_CACHE, O_WRONLY); + fd = open(cache, O_WRONLY); merrno = errno; errno = serrno; - if (fd == -1 && merrno == EACCES) + if (fd == -1 && merrno == EACCES) { +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + free(cache); + free(skew); + } +#endif return rc_deptree_load(); + } close(fd); if (regen) @@ -403,30 +459,41 @@ RC_DEPTREE * _rc_deptree_load(int force, int *regen) eend (retval, "Failed to update the dependency tree"); if (retval == 0) { - if (stat(RC_DEPTREE_CACHE, &st) != 0) { - eerror("stat(%s): %s", RC_DEPTREE_CACHE, strerror(errno)); + if (stat(cache, &st) != 0) { + eerror("stat(%s): %s", cache, strerror(errno)); +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + free(cache); + free(skew); + } +#endif return NULL; } if (st.st_mtime < t) { eerror("Clock skew detected with `%s'", file); - eerrorn("Adjusting mtime of `" RC_DEPTREE_CACHE - "' to %s", ctime(&t)); - fp = fopen(RC_DEPTREE_SKEWED, "w"); + eerrorn("Adjusting mtime of '%s' to %s", cache, ctime(&t)); + fp = fopen(skew, "w"); if (fp != NULL) { fprintf(fp, "%s\n", file); fclose(fp); } ut.actime = t; ut.modtime = t; - utime(RC_DEPTREE_CACHE, &ut); + utime(cache, &ut); } else { - if (exists(RC_DEPTREE_SKEWED)) - unlink(RC_DEPTREE_SKEWED); + if (exists(skew)) + unlink(skew); } } if (force == -1 && regen != NULL) *regen = retval; } +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + free(cache); + free(skew); + } +#endif return rc_deptree_load(); } diff --git a/src/shared/misc.h b/src/shared/misc.h index b158a786..132f4b80 100644 --- a/src/shared/misc.h +++ b/src/shared/misc.h @@ -33,11 +33,15 @@ #define RC_LEVEL_BOOT "boot" #define RC_LEVEL_DEFAULT "default" -#define RC_DEPTREE_CACHE RC_SVCDIR "/deptree" -#define RC_DEPTREE_SKEWED RC_SVCDIR "/clock-skewed" +#define RC_DEPTREE_CACHE_FILE "/deptree" +#define RC_DEPTREE_CACHE RC_SVCDIR RC_DEPTREE_CACHE_FILE +#define RC_DEPTREE_SKEWED_FILE "/clock-skewed" +#define RC_DEPTREE_SKEWED RC_SVCDIR RC_DEPTREE_SKEWED_FILE #define RC_KRUNLEVEL RC_SVCDIR "/krunlevel" -#define RC_STARTING RC_SVCDIR "/rc.starting" -#define RC_STOPPING RC_SVCDIR "/rc.stopping" +#define RC_STARTING_FOLDER "/rc.starting" +#define RC_STARTING RC_SVCDIR RC_STARTING_FOLDER +#define RC_STOPPING_FOLDER "/rc.stopping" +#define RC_STOPPING RC_SVCDIR RC_STOPPING_FOLDER #define RC_SVCDIR_STARTING RC_SVCDIR "/starting" #define RC_SVCDIR_INACTIVE RC_SVCDIR "/inactive" diff --git a/src/supervise-daemon/supervise-daemon.c b/src/supervise-daemon/supervise-daemon.c index adf35630..845b118c 100644 --- a/src/supervise-daemon/supervise-daemon.c +++ b/src/supervise-daemon/supervise-daemon.c @@ -817,6 +817,19 @@ int main(int argc, char **argv) char **child_argv = NULL; char *str = NULL; char *cmdline = NULL; + char *svcdir = RC_SVCDIR; + char *pidfile_path = "/var/run"; +#ifdef RC_USER_SERVICES + char *env; + if (rc_is_user()) { + svcdir = rc_user_svcdir(); + if ((env = getenv("XDG_RUNTIME_DIR"))) { + pidfile_path = env; + } else { + pidfile_path = "/tmp/" + } + } +#endif applet = basename_c(argv[0]); atexit(cleanup); @@ -1085,8 +1098,14 @@ int main(int argc, char **argv) umask(numask); if (!pidfile) - xasprintf(&pidfile, "/var/run/supervise-%s.pid", svcname); - xasprintf(&fifopath, "%s/supervise-%s.ctl", RC_SVCDIR, svcname); + xasprintf(&pidfile, "%s/supervise-%s.pid", pidfile_path, svcname); + xasprintf(&fifopath, "%s/supervise-%s.ctl", svcdir, svcname); +#ifdef RC_USER_SERVICES + if (rc_is_user()) { + free(svcdir); + } +#endif + if (mkfifo(fifopath, 0600) == -1 && errno != EEXIST) eerrorx("%s: unable to create control fifo: %s", applet, strerror(errno)); -- cgit v1.2.3