From f8449cec7eb0b988d8948d07947024abae1bdda7 Mon Sep 17 00:00:00 2001 From: "Anna (navi) Figueiredo Gomes" Date: Fri, 28 Mar 2025 02:23:19 +0100 Subject: [PATCH 1/9] librc: add rc_dirfd function this allows us to do atomic operations in the runtime directories used by openrc, while at the same time avoiding unnecessary path allocations and reducing syscalls the function "lazy loads" the fds by only opening them once when requested, with O_CLOEXEC set so no cleanup is required --- src/librc/librc.c | 31 +++++++++++++++++++++++++++++++ src/librc/librc.h | 17 +++++++++++++++++ src/librc/rc.h.in | 28 ++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+) diff --git a/src/librc/librc.c b/src/librc/librc.c index d71378720..6d953cdfa 100644 --- a/src/librc/librc.c +++ b/src/librc/librc.c @@ -15,6 +15,7 @@ * except according to the terms contained in the LICENSE file. */ +#include #include #include #include @@ -45,6 +46,36 @@ /* File stream used for plugins to write environ vars to */ FILE *rc_environ_fd = NULL; +static int dirfds[RC_DIR_MAX]; + +int rc_dirfd(enum rc_dir dir) { + int flags = O_RDONLY | O_CLOEXEC | O_DIRECTORY; + if (dir >= RC_DIR_MAX) + return -1; + if (dirfds[dir] <= 0) { + switch (dir) { + case RC_DIR_INVALID: + return -1; + case RC_DIR_SVCDIR: + dirfds[dir] = open(rc_svcdir(), flags); + break; + case RC_DIR_RUNLEVEL: + dirfds[dir] = open(rc_runleveldir(), flags); + break; + case RC_DIR_SYSCONF: + dirfds[dir] = open(rc_sysconfdir(), flags); + break; + case RC_DIR_USRCONF: + dirfds[dir] = open(rc_usrconfdir(), flags); + break; + default: + assert(dirnames[dir]); + dirfds[dir] = openat(rc_dirfd(RC_DIR_SVCDIR), dirnames[dir], flags); + break; + } + } + return dirfds[dir]; +} #define LS_INITD 0x01 #define LS_DIR 0x02 diff --git a/src/librc/librc.h b/src/librc/librc.h index 260720ee9..10e7c1b11 100644 --- a/src/librc/librc.h +++ b/src/librc/librc.h @@ -55,4 +55,21 @@ #include "rc.h" #include "misc.h" +static const char *const dirnames[RC_DIR_SYS_MAX] = +{ + [RC_DIR_STARTING] = "starting", + [RC_DIR_STARTED] = "started", + [RC_DIR_STOPPING] = "stopping", + [RC_DIR_INACTIVE] = "inactive", + [RC_DIR_WASINACTIVE] = "wasinactive", + [RC_DIR_FAILED] = "failed", + [RC_DIR_HOTPLUGGED] = "hotplugged", + [RC_DIR_DAEMONS] = "daemons", + [RC_DIR_OPTIONS] = "options", + [RC_DIR_EXCLUSIVE] = "exclusive", + [RC_DIR_SCHEDULED] = "scheduled", + [RC_DIR_INITD] = "init.d", + [RC_DIR_TMP] = "tmp", +}; + #endif diff --git a/src/librc/rc.h.in b/src/librc/rc.h.in index 82bbdb917..47c44f948 100644 --- a/src/librc/rc.h.in +++ b/src/librc/rc.h.in @@ -190,6 +190,34 @@ bool rc_runlevel_starting(void); * @return true if yes, otherwise false */ bool rc_runlevel_stopping(void); +enum rc_dir { + RC_DIR_SVCDIR, + RC_DIR_STARTING, + RC_DIR_STARTED, + RC_DIR_STOPPING, + RC_DIR_INACTIVE, + RC_DIR_WASINACTIVE, + RC_DIR_FAILED, + RC_DIR_HOTPLUGGED, + RC_DIR_DAEMONS, + RC_DIR_OPTIONS, + RC_DIR_EXCLUSIVE, + RC_DIR_SCHEDULED, + RC_DIR_INITD, + RC_DIR_TMP, + RC_DIR_SYS_MAX, + RC_DIR_SYSCONF = RC_DIR_SYS_MAX, + RC_DIR_USRCONF, + RC_DIR_RUNLEVEL, + RC_DIR_MAX, + + RC_DIR_INVALID = -1 +}; + +/*! @param runtime rc directory + * @return an open file descriptor, which should not be closed */ +int rc_dirfd(enum rc_dir); + /*! @name RC * A service can be given as a full path or just its name. * If it's just a name then we try to resolve the service to a full path. From a5b0438f6dee6aba272219e9bfe16b05e05d6239 Mon Sep 17 00:00:00 2001 From: "Anna (navi) Figueiredo Gomes" Date: Fri, 28 Mar 2025 02:55:26 +0100 Subject: [PATCH 2/9] librc: use rc_dirfd internally this also fixes the behaviour of rc_service_scheduled_by, returning a stringlist of service names, instead of the script that is being scheduled. --- src/librc/librc-daemon.c | 96 ++++---- src/librc/librc-depend.c | 139 ++++------- src/librc/librc-misc.c | 104 ++++---- src/librc/librc.c | 497 +++++++++++++++------------------------ src/librc/librc.h | 2 + src/shared/helpers.h | 65 +++++ src/shared/misc.h | 23 +- 7 files changed, 414 insertions(+), 512 deletions(-) diff --git a/src/librc/librc-daemon.c b/src/librc/librc-daemon.c index a4ef5de4a..c1c9bb66b 100644 --- a/src/librc/librc-daemon.c +++ b/src/librc/librc-daemon.c @@ -329,17 +329,17 @@ rc_find_pids(const char *exec, const char *const *argv, uid_t uid, pid_t pid) #endif static bool -_match_daemon(const char *path, const char *file, RC_STRINGLIST *match) +_match_daemon(const char *svcname, const char *instance, RC_STRINGLIST *match) { char *line = NULL; size_t len = 0; - char *ffile = NULL; FILE *fp; RC_STRING *m; + char *daemon; - xasprintf(&ffile, "%s/%s", path, file); - fp = fopen(ffile, "r"); - free(ffile); + xasprintf(&daemon, "%s/%s", svcname, instance); + fp = do_fopenat(rc_dirfd(RC_DIR_DAEMONS), daemon, O_RDONLY); + free(daemon); if (!fp) return false; @@ -393,7 +393,6 @@ rc_service_daemon_set(const char *service, const char *exec, const char *const *argv, const char *pidfile, bool started) { - char *dirpath = NULL; char *file = NULL; int nfiles = 0; char oldfile[PATH_MAX] = { '\0' }; @@ -403,23 +402,22 @@ rc_service_daemon_set(const char *service, const char *exec, RC_STRINGLIST *match, *renamelist; int i = 0; FILE *fp; + const char *base = basename_c(service); if (!exec && !pidfile) { errno = EINVAL; return false; } - xasprintf(&dirpath, "%s/daemons/%s", rc_svcdir(), basename_c(service)); - /* Regardless, erase any existing daemon info */ - if ((dp = opendir(dirpath))) { + if ((dp = do_opendirat(rc_dirfd(RC_DIR_DAEMONS), base))) { match = _match_list(exec, argv, pidfile); renamelist = rc_stringlist_new(); while ((d = readdir(dp))) { if (d->d_name[0] == '.') continue; - xasprintf(&file, "%s/%s", dirpath, d->d_name); + xasprintf(&file, "%s/daemons/%s/%s", rc_svcdir(), base, d->d_name); if (rc_stringlist_find(renamelist, file)) { free(file); continue; @@ -428,7 +426,7 @@ rc_service_daemon_set(const char *service, const char *exec, nfiles++; if (!*oldfile) { - if (_match_daemon(dirpath, d->d_name, match)) { + if (_match_daemon(base, d->d_name, match)) { unlink(file); strlcpy(oldfile, file, sizeof(oldfile)); nfiles--; @@ -448,30 +446,29 @@ rc_service_daemon_set(const char *service, const char *exec, } /* Now store our daemon info */ - if (started) { - if (mkdir(dirpath, 0755) == 0 || errno == EEXIST) { - xasprintf(&file, "%s/%03d", dirpath, nfiles + 1); - if ((fp = fopen(file, "w"))) { - fprintf(fp, "exec="); - if (exec) - fprintf(fp, "%s", exec); - while (argv && argv[i]) { - fprintf(fp, "\nargv_%d=%s", i, argv[i]); - i++; - } - fprintf(fp, "\npidfile="); - if (pidfile) - fprintf(fp, "%s", pidfile); - fprintf(fp, "\n"); - fclose(fp); - retval = true; + if (!started) + return true; + + if (mkdirat(rc_dirfd(RC_DIR_DAEMONS), base, 0755) == 0 || errno == EEXIST) { + xasprintf(&file, "%s/daemons/%s/%03d", rc_svcdir(), base, nfiles + 1); + if ((fp = fopen(file, "w"))) { + fprintf(fp, "exec="); + if (exec) + fprintf(fp, "%s", exec); + while (argv && argv[i]) { + fprintf(fp, "\nargv_%d=%s", i, argv[i]); + i++; } - free(file); + fprintf(fp, "\npidfile="); + if (pidfile) + fprintf(fp, "%s", pidfile); + fprintf(fp, "\n"); + fclose(fp); + retval = true; } - } else - retval = true; + free(file); + } - free(dirpath); return retval; } @@ -479,7 +476,7 @@ bool rc_service_started_daemon(const char *service, const char *exec, const char *const *argv, int indx) { - char *dirpath = NULL; + const char *base = basename_c(service); char *file = NULL; RC_STRINGLIST *match; bool retval = false; @@ -489,38 +486,31 @@ rc_service_started_daemon(const char *service, if (!service || !exec) return false; - xasprintf(&dirpath, "%s/daemons/%s", rc_svcdir(), basename_c(service)); match = _match_list(exec, argv, NULL); if (indx > 0) { xasprintf(&file, "%03d", indx); - retval = _match_daemon(dirpath, file, match); + retval = _match_daemon(base, file, match); free(file); - } else { - if ((dp = opendir(dirpath))) { - while ((d = readdir(dp))) { - if (d->d_name[0] == '.') - continue; - retval = _match_daemon(dirpath, d->d_name, match); - if (retval) - break; - } - closedir(dp); + } else if ((dp = do_opendirat(rc_dirfd(RC_DIR_DAEMONS), base))) { + while ((d = readdir(dp))) { + if (d->d_name[0] == '.') + continue; + if ((retval = _match_daemon(base, d->d_name, match))) + break; } + closedir(dp); } rc_stringlist_free(match); - free(dirpath); return retval; } bool rc_service_daemons_crashed(const char *service) { - char dirpath[PATH_MAX]; DIR *dp; struct dirent *d; - char *path = dirpath; FILE *fp; char *line = NULL; size_t len = 0; @@ -541,20 +531,14 @@ rc_service_daemons_crashed(const char *service) char *ch_root; char *spidfile; - path += snprintf(dirpath, sizeof(dirpath), - "%s/daemons/%s", rc_svcdir(), basename_c(service)); - - if (!(dp = opendir(dirpath))) + if (!(dp = do_opendirat(rc_dirfd(RC_DIR_DAEMONS), basename_c(service)))) return false; while ((d = readdir(dp))) { if (d->d_name[0] == '.') continue; - snprintf(path, sizeof(dirpath) - (path - dirpath), "/%s", - d->d_name); - fp = fopen(dirpath, "r"); - if (!fp) + if (!(fp = do_fopenat(dirfd(dp), d->d_name, O_RDONLY))) break; while (xgetline(&line, &len, fp) != -1) { diff --git a/src/librc/librc-depend.c b/src/librc/librc-depend.c index e7bee4e9a..f76fe3ed5 100644 --- a/src/librc/librc-depend.c +++ b/src/librc/librc-depend.c @@ -145,22 +145,9 @@ make_deptree(void) { return deptree; } -RC_DEPTREE * -rc_deptree_load(void) { - char *deptree_cache; - RC_DEPTREE *deptree; - - xasprintf(&deptree_cache, "%s/deptree", rc_svcdir()); - deptree = rc_deptree_load_file(deptree_cache); - free(deptree_cache); - - return deptree; -} - -RC_DEPTREE * -rc_deptree_load_file(const char *deptree_file) +static RC_DEPTREE * +deptree_load_file(int dirfd, const char *pathname) { - FILE *fp; RC_DEPTREE *deptree; RC_DEPINFO *depinfo = NULL; RC_DEPTYPE *deptype = NULL; @@ -170,8 +157,9 @@ rc_deptree_load_file(const char *deptree_file) char *p; char *e; int i; + FILE *fp; - if (!(fp = fopen(deptree_file, "r"))) + if (!(fp = do_fopenat(dirfd, pathname, O_RDONLY))) return NULL; deptree = make_deptree(); @@ -205,12 +193,24 @@ rc_deptree_load_file(const char *deptree_file) deptype = make_deptype(depinfo, type); rc_stringlist_add(deptype->services, e); } - fclose(fp); free(line); + fclose(fp); return deptree; } +RC_DEPTREE * +rc_deptree_load(void) +{ + return deptree_load_file(rc_dirfd(RC_DIR_SVCDIR), "deptree"); +} + +RC_DEPTREE * +rc_deptree_load_file(const char *deptree_file) +{ + return deptree_load_file(AT_FDCWD, deptree_file); +} + static bool valid_service(const char *runlevel, const char *service, const char *type) { @@ -584,18 +584,15 @@ rc_deptree_order(const RC_DEPTREE *deptree, const char *runlevel, int options) oldest (or newest) found. */ static bool -deep_mtime_check(const char *target, bool newer, - time_t *rel, char *file) +deep_mtime_check(int target_dir, const char *target, bool newer, time_t *rel, char *file) { struct stat buf; bool retval = true; DIR *dp; struct dirent *d; - char path[PATH_MAX]; - int serrno = errno; /* If target does not exist, return true to mimic shell test */ - if (stat(target, &buf) != 0) + if (fstatat(target_dir, target, &buf, 0) != 0) return true; if (newer) { @@ -616,21 +613,18 @@ deep_mtime_check(const char *target, bool newer, } } - /* If not a dir then reset errno */ - if (!(dp = opendir(target))) { - errno = serrno; + if (!S_ISDIR(buf.st_mode) || !(dp = do_opendirat(target_dir, target))) return retval; - } /* Check all the entries in the dir */ while ((d = readdir(dp))) { if (d->d_name[0] == '.') continue; - snprintf(path, sizeof(path), "%s/%s", target, d->d_name); - if (!deep_mtime_check(path, newer, rel, file)) { + if (!deep_mtime_check(dirfd(dp), d->d_name, newer, rel, file)) { retval = false; } } + closedir(dp); return retval; } @@ -640,8 +634,7 @@ deep_mtime_check(const char *target, bool newer, * the return value arguments are non-null). */ static bool -mtime_check(const char *source, const char *target, bool newer, - time_t *rel, char *file) +mtime_check(int dirfd, const char *source, const char *target, bool newer, time_t *rel, char *file) { struct stat buf; time_t mtime; @@ -652,7 +645,7 @@ mtime_check(const char *source, const char *target, bool newer, return false; mtime = buf.st_mtime; - retval = deep_mtime_check(target,newer,&mtime,file); + retval = deep_mtime_check(dirfd, target,newer,&mtime,file); if (rel) { *rel = mtime; } @@ -660,18 +653,16 @@ mtime_check(const char *source, const char *target, bool newer, } bool -rc_newer_than(const char *source, const char *target, - time_t *newest, char *file) +rc_newer_than(const char *source, const char *target, time_t *newest, char *file) { - return mtime_check(source, target, true, newest, file); + return mtime_check(AT_FDCWD, source, target, true, newest, file); } bool -rc_older_than(const char *source, const char *target, - time_t *oldest, char *file) +rc_older_than(const char *source, const char *target, time_t *oldest, char *file) { - return mtime_check(source, target, false, oldest, file); + return mtime_check(AT_FDCWD, source, target, false, oldest, file); } typedef struct deppair @@ -690,51 +681,30 @@ static const DEPPAIR deppairs[] = { { NULL, NULL } }; -static const char *const depdirs[] = -{ - "", - "starting", - "started", - "stopping", - "inactive", - "wasinactive", - "failed", - "hotplugged", - "daemons", - "options", - "exclusive", - "scheduled", - "init.d", - "tmp", - NULL -}; - bool rc_deptree_update_needed(time_t *newest, char *file) { bool newer = false; RC_STRINGLIST *config; RC_STRING *s; - int i; struct stat buf; time_t mtime; char *path; - char *deptree_cache, *depconfig; const char *service_dir = rc_svcdir(); /* Create base directories if needed */ - for (i = 0; depdirs[i]; i++) { - xasprintf(&path, "%s/%s", service_dir, depdirs[i]); - if (mkdir(path, 0755) != 0 && errno != EEXIST) - fprintf(stderr, "mkdir `%s': %s\n", depdirs[i], strerror(errno)); - free(path); + if (mkdir(service_dir, 0755) != 0 && errno != EEXIST) + fprintf(stderr, "mkdir '%s': %s\n", service_dir, strerror(errno)); + + for (size_t i = 1; i < RC_DIR_SYS_MAX; i++) { + if (mkdirat(rc_dirfd(RC_DIR_SVCDIR), dirnames[i], 0755) != 0 && errno != EEXIST) + fprintf(stderr, "mkdir `%s': %s\n", dirnames[i], strerror(errno)); } /* Quick test to see if anything we use has changed and we have * data in our deptree. */ - xasprintf(&deptree_cache, "%s/deptree", service_dir); - if (stat(deptree_cache, &buf) == 0) { + if (fstatat(rc_dirfd(RC_DIR_SVCDIR), "deptree", &buf, 0) == 0) { mtime = buf.st_mtime; } else { /* No previous cache found. @@ -744,36 +714,26 @@ rc_deptree_update_needed(time_t *newest, char *file) newer = true; mtime = time(NULL); } - free(deptree_cache); for (const char * const *dirs = rc_scriptdirs(); *dirs; dirs++) { static const char *subdirs[] = { "init.d", "conf.d", NULL }; for (const char **subdir = subdirs; *subdir; subdir++) { xasprintf(&path, "%s/%s", *dirs, *subdir); - newer |= !deep_mtime_check(path, true, &mtime, file); + newer |= !deep_mtime_check(AT_FDCWD, path, true, &mtime, file); free(path); } } - xasprintf(&path, "%s/rc.conf", rc_sysconfdir()); - newer |= !deep_mtime_check(path, true, &mtime, file); - free(path); - - if (rc_is_user()) { - xasprintf(&path, "%s/rc.conf", rc_usrconfdir()); - newer |= !deep_mtime_check(path, true, &mtime, file); - free(path); - } + newer |= !deep_mtime_check(rc_dirfd(RC_DIR_SYSCONF), "rc.conf", true, &mtime, file); + if (rc_is_user()) + newer |= !deep_mtime_check(rc_dirfd(RC_DIR_USRCONF), "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. */ - xasprintf(&depconfig, "%s/depconfig", service_dir); - config = rc_config_list(depconfig); - TAILQ_FOREACH(s, config, entries) { - newer |= !deep_mtime_check(s->value, true, &mtime, file); - } + config = config_list(rc_dirfd(RC_DIR_SVCDIR), "depconfig"); + TAILQ_FOREACH(s, config, entries) + newer |= !deep_mtime_check(AT_FDCWD, s->value, true, &mtime, file); rc_stringlist_free(config); - free(depconfig); /* Return newest file time, if requested */ if ((newer) && (newest != NULL)) { @@ -834,7 +794,6 @@ rc_deptree_update(void) char *line = NULL; size_t size; char *depend, *depends, *service, *type; - char *deptree_cache, *depconfig; size_t i, l; bool retval = true; const char *sys = rc_sys(); @@ -1087,8 +1046,7 @@ 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 */ - xasprintf(&deptree_cache, "%s/deptree", rc_svcdir()); - if ((fp = fopen(deptree_cache, "w"))) { + if ((fp = do_fopenat(rc_dirfd(RC_DIR_SVCDIR), "deptree", O_WRONLY | O_CREAT | O_TRUNC))) { i = 0; TAILQ_FOREACH(depinfo, deptree, entries) { fprintf(fp, "depinfo_%zu_service='%s'\n", i, depinfo->service); @@ -1103,26 +1061,23 @@ rc_deptree_update(void) } fclose(fp); } else { - fprintf(stderr, "fopen '%s': %s\n", deptree_cache, strerror(errno)); + fprintf(stderr, "fopen '%s/deptree': %s\n", rc_svcdir(), strerror(errno)); retval = false; } - free(deptree_cache); /* Save our external config files to disk */ - xasprintf(&depconfig, "%s/depconfig", rc_svcdir()); if (TAILQ_FIRST(config)) { - if ((fp = fopen(depconfig, "w"))) { + if ((fp = do_fopenat(rc_dirfd(RC_DIR_SVCDIR), "depconfig", O_WRONLY | O_CREAT | O_TRUNC))) { TAILQ_FOREACH(s, config, entries) fprintf(fp, "%s\n", s->value); fclose(fp); } else { - fprintf(stderr, "fopen '%s': %s\n", depconfig, strerror(errno)); + fprintf(stderr, "fopen '%s/depconfig': %s\n", rc_svcdir(), strerror(errno)); retval = false; } } else { - unlink(depconfig); + unlinkat(rc_dirfd(RC_DIR_SVCDIR), "depconfig", 0); } - free(depconfig); rc_stringlist_free(config); rc_deptree_free(deptree); diff --git a/src/librc/librc-misc.c b/src/librc/librc-misc.c index b309396c8..29f935141 100644 --- a/src/librc/librc-misc.c +++ b/src/librc/librc-misc.c @@ -146,7 +146,7 @@ rc_proc_getent(const char *ent RC_UNUSED) } RC_STRINGLIST * -rc_config_list(const char *file) +config_list(int dirfd, const char *pathname) { FILE *fp; char *buffer = NULL; @@ -155,7 +155,7 @@ rc_config_list(const char *file) char *token; RC_STRINGLIST *list = rc_stringlist_new(); - if (!(fp = fopen(file, "r"))) + if (!(fp = do_fopenat(dirfd, pathname, O_RDONLY))) return list; while (xgetline(&buffer, &len, fp) != -1) { @@ -177,12 +177,18 @@ rc_config_list(const char *file) } } } - fclose(fp); free(buffer); + fclose(fp); return list; } +RC_STRINGLIST * +rc_config_list(const char *file) +{ + return config_list(AT_FDCWD, file); +} + static void rc_config_set_value(RC_STRINGLIST *config, char *value) { RC_STRING *cline; @@ -301,51 +307,52 @@ static RC_STRINGLIST *rc_config_kcl(RC_STRINGLIST *config) return config; } -static RC_STRINGLIST * rc_config_directory(RC_STRINGLIST *config, const char *dir) +static void +rc_config_directory(RC_STRINGLIST *config, int targetfd, const char *dir) { DIR *dp; struct dirent *d; RC_STRINGLIST *rc_conf_d_files; RC_STRING *fname; RC_STRINGLIST *rc_conf_d_list; - char path[PATH_MAX]; RC_STRING *line; - if ((dp = opendir(dir)) != NULL) { - rc_conf_d_files = rc_stringlist_new(); - while ((d = readdir(dp)) != NULL) { - if (fnmatch("*.conf", d->d_name, FNM_PATHNAME) == 0) { - rc_stringlist_addu(rc_conf_d_files, d->d_name); - } - } - closedir(dp); - - rc_stringlist_sort(&rc_conf_d_files); - TAILQ_FOREACH(fname, rc_conf_d_files, entries) { - if (!fname->value) - continue; - sprintf(path, "%s/%s", dir, fname->value); - rc_conf_d_list = rc_config_list(path); - TAILQ_FOREACH(line, rc_conf_d_list, entries) - if (line->value) - rc_config_set_value(config, line->value); - rc_stringlist_free(rc_conf_d_list); + if (!(dp = do_opendirat(targetfd, dir))) + return; + + rc_conf_d_files = rc_stringlist_new(); + while ((d = readdir(dp)) != NULL) { + if (fnmatch("*.conf", d->d_name, FNM_PATHNAME) == 0) { + rc_stringlist_addu(rc_conf_d_files, d->d_name); } + } + + rc_stringlist_sort(&rc_conf_d_files); + TAILQ_FOREACH(fname, rc_conf_d_files, entries) { + if (!fname->value) + continue; - rc_stringlist_free(rc_conf_d_files); + rc_conf_d_list = config_list(dirfd(dp), fname->value); + TAILQ_FOREACH(line, rc_conf_d_list, entries) + if (line->value) + rc_config_set_value(config, line->value); + rc_stringlist_free(rc_conf_d_list); } - return config; + rc_stringlist_free(rc_conf_d_files); + closedir(dp); + + return; } -RC_STRINGLIST * -rc_config_load(const char *file) +static RC_STRINGLIST * +config_load(int dirfd, const char *pathname) { RC_STRINGLIST *list; RC_STRINGLIST *config; RC_STRING *line; - list = rc_config_list(file); + list = config_list(dirfd, pathname); config = rc_stringlist_new(); TAILQ_FOREACH(line, list, entries) { rc_config_set_value(config, line->value); @@ -355,6 +362,12 @@ rc_config_load(const char *file) return config; } +RC_STRINGLIST * +rc_config_load(const char *file) +{ + return config_load(AT_FDCWD, file); +} + char * rc_config_value(RC_STRINGLIST *list, const char *entry) { @@ -384,9 +397,9 @@ _free_rc_conf(void) } static void -rc_conf_append(const char *file) +rc_conf_append(enum rc_dir dir) { - RC_STRINGLIST *conf = rc_config_load(file); + RC_STRINGLIST *conf = config_load(rc_dirfd(dir), "rc.conf"); TAILQ_CONCAT(rc_conf, conf, entries); rc_stringlist_free(conf); } @@ -394,10 +407,7 @@ rc_conf_append(const char *file) char * rc_conf_value(const char *setting) { - const char *sysconfdir = rc_sysconfdir(); - const char *usrconfdir = rc_usrconfdir(); RC_STRING *s; - char *conf; if (rc_conf) return rc_config_value(rc_conf, setting); @@ -407,27 +417,21 @@ rc_conf_value(const char *setting) /* Load user configurations first, as they should override * system wide configs. */ - if (usrconfdir) { - xasprintf(&conf, "%s/%s", usrconfdir, "rc.conf"); - rc_conf_append(conf); - free(conf); - - xasprintf(&conf, "%s/%s", usrconfdir, "rc.conf.d"); - rc_conf = rc_config_directory(rc_conf, conf); - free(conf); + if (rc_is_user()) { + rc_conf_append(RC_DIR_USRCONF); + rc_config_directory(rc_conf, rc_dirfd(RC_DIR_USRCONF), "rc.conf.d"); } - xasprintf(&conf, "%s/%s", sysconfdir, "rc.conf"); - rc_conf_append(conf); - free(conf); + rc_conf_append(RC_DIR_SYSCONF); /* Support old configs. */ - if (exists(RC_CONF_OLD)) - rc_conf_append(RC_CONF_OLD); + if (exists(RC_CONF_OLD)) { + RC_STRINGLIST *old_conf = config_load(AT_FDCWD, RC_CONF_OLD); + TAILQ_CONCAT(rc_conf, old_conf, entries); + rc_stringlist_free(old_conf); + } - xasprintf(&conf, "%s/%s", sysconfdir, "rc.conf.d"); - rc_conf = rc_config_directory(rc_conf, conf); - free(conf); + rc_config_directory(rc_conf, rc_dirfd(RC_DIR_SYSCONF), "rc.conf.d"); rc_conf = rc_config_kcl(rc_conf); diff --git a/src/librc/librc.c b/src/librc/librc.c index 6d953cdfa..9b9107c90 100644 --- a/src/librc/librc.c +++ b/src/librc/librc.c @@ -80,97 +80,82 @@ int rc_dirfd(enum rc_dir dir) { #define LS_INITD 0x01 #define LS_DIR 0x02 static RC_STRINGLIST * -ls_dir(const char *dir, int options) +ls_dir(int targetfd, const char *dir, int options) { DIR *dp; struct dirent *d; RC_STRINGLIST *list = NULL; struct stat buf; size_t l; - char *file; - int r; list = rc_stringlist_new(); - if ((dp = opendir(dir)) == NULL) + + if (!(dp = do_opendirat(targetfd, dir))) return list; + while (((d = readdir(dp)) != NULL)) { - if (d->d_name[0] != '.') { - if (options & LS_INITD) { - /* Check that our file really exists. - * This is important as a service maybe in a - * runlevel, but could have been removed. */ - xasprintf(&file, "%s/%s", dir, d->d_name); - r = stat(file, &buf); - free(file); - if (r != 0) - continue; - - /* .sh files are not init scripts */ - l = strlen(d->d_name); - if (l > 2 && d->d_name[l - 3] == '.' && - d->d_name[l - 2] == 's' && - d->d_name[l - 1] == 'h') - continue; - } - if (options & LS_DIR) { - xasprintf(&file, "%s/%s", dir, d->d_name); - r = stat(file, &buf); - free(file); - if (r != 0 || !S_ISDIR(buf.st_mode)) - continue; - } - rc_stringlist_add(list, d->d_name); + if (d->d_name[0] == '.') + continue; + if (options & LS_INITD) { + /* Check that our file really exists. + * This is important as a service maybe in a + * runlevel, but could have been removed. */ + if (fstatat(dirfd(dp), d->d_name, &buf, 0) != 0) + continue; + + /* .sh files are not init scripts */ + l = strlen(d->d_name); + if (l > 2 && d->d_name[l - 3] == '.' && + d->d_name[l - 2] == 's' && + d->d_name[l - 1] == 'h') + continue; + } + if (options & LS_DIR) { + if (fstatat(dirfd(dp), d->d_name, &buf, 0) != 0 || !S_ISDIR(buf.st_mode)) + continue; } + rc_stringlist_add(list, d->d_name); } closedir(dp); return list; } static bool -rm_dir(const char *pathname, bool top) +rm_dir(int targetfd, const char *pathname, bool top) { - DIR *dp; + bool retval = true; struct dirent *d; - char *file = NULL; struct stat s; - bool retval = true; + DIR *dp; - if ((dp = opendir(pathname)) == NULL) + if (!(dp = do_opendirat(targetfd, pathname))) return false; - errno = 0; - while (((d = readdir(dp)) != NULL) && errno == 0) { - if (strcmp(d->d_name, ".") != 0 && - strcmp(d->d_name, "..") != 0) - { - xasprintf(&file, "%s/%s", pathname, d->d_name); - if (stat(file, &s) != 0) { + while (!(d = readdir(dp))) { + if (strcmp(d->d_name, ".") == 0 || strcmp(d->d_name, "..") == 0) + continue; + if (fstatat(dirfd(dp), pathname, &s, 0) != 0) { + retval = false; + break; + } + if (S_ISDIR(s.st_mode)) { + if (!rm_dir(dirfd(dp), d->d_name, true)) { retval = false; break; } - if (S_ISDIR(s.st_mode)) { - if (!rm_dir(file, true)) - { - retval = false; - break; - } - } else { - if (unlink(file)) { - retval = false; - break; - } + } else { + if (unlinkat(dirfd(dp), d->d_name, 0) != 0) { + retval = false; + break; } - free(file); - file = NULL; } } - free(file); closedir(dp); if (!retval) return false; - if (top && rmdir(pathname) != 0) + if (top && unlinkat(targetfd, pathname, AT_REMOVEDIR) != 0) return false; return true; @@ -379,13 +364,21 @@ rc_parse_service_state(RC_SERVICE state) return NULL; } +static int +rc_parse_service_state_dirfd(RC_SERVICE state) +{ + for (size_t i = 0; rc_service_state_names[i].name; i++) + if (rc_service_state_names[i].state == state) + return rc_service_state_names[i].dir; + return -1; +} + /* Returns a list of all the chained runlevels used by the * specified runlevel in dependency order, including the * specified runlevel. */ static void get_runlevel_chain(const char *runlevel, RC_STRINGLIST *level_list, RC_STRINGLIST *ancestor_list) { - char *path; RC_STRINGLIST *dirs; RC_STRING *d, *parent; const char *nextlevel; @@ -410,8 +403,7 @@ get_runlevel_chain(const char *runlevel, RC_STRINGLIST *level_list, RC_STRINGLIS * We can now do exactly the above procedure for our chained * runlevels. */ - xasprintf(&path, "%s/%s", rc_runleveldir(), runlevel); - dirs = ls_dir(path, LS_DIR); + dirs = ls_dir(rc_dirfd(RC_DIR_RUNLEVEL), runlevel, LS_DIR); TAILQ_FOREACH(d, dirs, entries) { nextlevel = d->value; @@ -433,47 +425,33 @@ get_runlevel_chain(const char *runlevel, RC_STRINGLIST *level_list, RC_STRINGLIS rc_stringlist_delete(ancestor_list, nextlevel); } rc_stringlist_free(dirs); - free(path); -} - -static bool -svcdir_subpath_exists(const char *subdir) -{ - char *path; - bool found; - xasprintf(&path, "%s/%s", rc_svcdir(), subdir); - found = exists(path); - free(path); - return found; } bool rc_runlevel_starting(void) { - return svcdir_subpath_exists("rc.starting"); + return existsat(rc_dirfd(RC_DIR_SVCDIR), "rc.starting"); } bool rc_runlevel_stopping(void) { - return svcdir_subpath_exists("rc.stopping"); + return existsat(rc_dirfd(RC_DIR_SVCDIR), "rc.stopping"); } RC_STRINGLIST *rc_runlevel_list(void) { - return ls_dir(rc_runleveldir(), LS_DIR); + return ls_dir(AT_FDCWD, rc_runleveldir(), LS_DIR); } char * rc_runlevel_get(void) { FILE *fp; - char *softlevel; char *runlevel = NULL; size_t i; - xasprintf(&softlevel, "%s/softlevel", rc_svcdir()); - if ((fp = fopen(softlevel, "r"))) { + if ((fp = do_fopenat(rc_dirfd(RC_DIR_SVCDIR), "softlevel", O_RDONLY))) { runlevel = xmalloc(sizeof(char) * PATH_MAX); if (fgets(runlevel, PATH_MAX, fp)) { i = strlen(runlevel) - 1; @@ -483,7 +461,6 @@ rc_runlevel_get(void) *runlevel = '\0'; fclose(fp); } - free(softlevel); if (!runlevel || !*runlevel) { free(runlevel); @@ -496,35 +473,24 @@ rc_runlevel_get(void) bool rc_runlevel_set(const char *runlevel) { - char *softlevel; - bool ret = false; FILE *fp; - xasprintf(&softlevel, "%s/softlevel", rc_svcdir()); - if ((fp = fopen(softlevel, "w"))) { - fprintf(fp, "%s", runlevel); - fclose(fp); - ret = true; - } + if (!(fp = do_fopenat(rc_dirfd(RC_DIR_SVCDIR), "softlevel", O_WRONLY | O_CREAT | O_TRUNC))) + return false; - free(softlevel); - return ret; + fputs(runlevel, fp); + fclose(fp); + return true; } bool rc_runlevel_exists(const char *runlevel) { - char *path; struct stat buf; - int r; - if (!runlevel || strcmp(runlevel, "") == 0 || strcmp(runlevel, ".") == 0 || - strcmp(runlevel, "..") == 0) + if (!runlevel || strcmp(runlevel, "") == 0 || strcmp(runlevel, ".") == 0 || strcmp(runlevel, "..") == 0) return false; - xasprintf(&path, "%s/%s", rc_runleveldir(), runlevel); - r = stat(path, &buf); - free(path); - if (r == 0 && S_ISDIR(buf.st_mode)) + if (fstatat(rc_dirfd(RC_DIR_RUNLEVEL), runlevel, &buf, 0) == 0 && S_ISDIR(buf.st_mode)) return true; return false; } @@ -661,7 +627,6 @@ rc_set_user(void) xasprintf(&rc_dirs.svcdir, "%s/openrc", env); atexit(free_rc_dirs); - rc_dirs.scriptdirs[SCRIPTDIR_USR] = rc_dirs.usrconfdir; rc_dirs.scriptdirs[SCRIPTDIR_SVC] = rc_dirs.svcdir; } @@ -706,14 +671,14 @@ rc_svcdir(void) } static ssize_t -safe_readlink(const char *path, char **buf, size_t bufsiz) +safe_readlink(int targetfd, const char *path, char **buf, size_t bufsiz) { char *buffer; ssize_t r; for (;; bufsiz += PATH_MAX) { buffer = xmalloc(bufsiz); - r = readlink(path, buffer, bufsiz); + r = readlinkat(targetfd, path, buffer, bufsiz); if (r < 0) { free(buffer); return -errno; @@ -734,7 +699,6 @@ char * rc_service_resolve(const char *service) { char *buffer; - char *file = NULL; struct stat buf; if (!service) @@ -744,29 +708,21 @@ rc_service_resolve(const char *service) return xstrdup(service); /* First check started services */ - xasprintf(&file, "%s/%s/%s", rc_svcdir(), "started", service); - if (lstat(file, &buf) || !S_ISLNK(buf.st_mode)) { - free(file); - xasprintf(&file, "%s/%s/%s", rc_svcdir(), "inactive", service); - if (lstat(file, &buf) || !S_ISLNK(buf.st_mode)) { - free(file); - file = NULL; - } - } - - if (file != NULL && safe_readlink(file, &buffer, buf.st_size + 1) >= 0) { - free(file); - return buffer; - } + if (fstatat(rc_dirfd(RC_DIR_STARTED), service, &buf, AT_SYMLINK_NOFOLLOW) == 0 && S_ISLNK(buf.st_mode)) + if (safe_readlink(rc_dirfd(RC_DIR_STARTED), service, &buffer, buf.st_size + 1) >= 0) + return buffer; + if (fstatat(rc_dirfd(RC_DIR_INACTIVE), service, &buf, AT_SYMLINK_NOFOLLOW) == 0 && S_ISLNK(buf.st_mode)) + if (safe_readlink(rc_dirfd(RC_DIR_INACTIVE), service, &buffer, buf.st_size + 1) >= 0) + return buffer; for (const char * const *dirs = rc_scriptdirs(); *dirs; dirs++) { - free(file); + char *file = NULL; xasprintf(&file, "%s/init.d/%s", *dirs, service); if (stat(file, &buf) == 0) return file; + free(file); } - free(file); return NULL; } @@ -787,8 +743,8 @@ rc_service_exists(const char *service) /* .sh files are not init scripts */ if (len > 2 && service[len - 3] == '.' && - service[len - 2] == 's' && - service[len - 1] == 'h') { + service[len - 2] == 's' && + service[len - 1] == 'h') { errno = EINVAL; return false; } @@ -899,16 +855,10 @@ rc_service_in_runlevel(const char *service, const char *runlevel) bool rc_service_mark(const char *service, const RC_SERVICE state) { - char *file, *was; int i = 0; - int skip_state = -1; const char *base; char *init = rc_service_resolve(service); - const char *svcdir = rc_svcdir(); bool skip_wasinactive = false; - int s; - RC_STRINGLIST *dirs; - RC_STRING *dir; int serrno; if (!init) @@ -916,21 +866,17 @@ rc_service_mark(const char *service, const RC_SERVICE state) base = basename_c(service); if (state != RC_SERVICE_STOPPED) { + int state_dirfd = rc_dirfd(rc_parse_service_state_dirfd(state)); if (!exists(init)) { free(init); return false; } - xasprintf(&file, "%s/%s/%s", svcdir, rc_parse_service_state(state), base); - if (exists(file)) - unlink(file); - i = symlink(init, file); - free(file); - if (i != 0) { + unlinkat(state_dirfd, base, 0); + if ((symlinkat(init, state_dirfd, base)) != 0) { free(init); return false; } - skip_state = state; } if (state == RC_SERVICE_HOTPLUGGED || state == RC_SERVICE_FAILED) { @@ -940,82 +886,68 @@ rc_service_mark(const char *service, const RC_SERVICE state) /* Remove any old states now */ for (i = 0; rc_service_state_names[i].name; i++) { - s = rc_service_state_names[i].state; - - if ((s != skip_state && - s != RC_SERVICE_STOPPED && - s != RC_SERVICE_HOTPLUGGED && - s != RC_SERVICE_SCHEDULED) && - (!skip_wasinactive || s != RC_SERVICE_WASINACTIVE)) - { - xasprintf(&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) - { - xasprintf(&was, "%s/%s/%s", svcdir, - rc_parse_service_state(RC_SERVICE_WASINACTIVE), base); - i = symlink(init, was); - free(was); - if (i == -1) { - free(file); - free(init); - return false; - } - skip_wasinactive = true; - } - if (unlink(file) == -1) { - free(file); + int state_dirfd = rc_dirfd(rc_service_state_names[i].dir); + RC_SERVICE s = rc_service_state_names[i].state; + + if (s == state) + continue; + + switch (s) { + case RC_SERVICE_STOPPED: + case RC_SERVICE_HOTPLUGGED: + case RC_SERVICE_SCHEDULED: + continue; + case RC_SERVICE_WASINACTIVE: + if (skip_wasinactive) + continue; + /* fall-through */ + default: + if (!existsat(state_dirfd, base)) + continue; + if ((state == RC_SERVICE_STARTING || state == RC_SERVICE_STOPPING) && s == RC_SERVICE_INACTIVE) { + if (symlinkat(init, rc_dirfd(RC_DIR_WASINACTIVE), base) == -1) { free(init); return false; } + skip_wasinactive = true; + } + if (unlinkat(state_dirfd, base, 0) == -1) { + free(init); + return false; } - free(file); } } /* Remove the exclusive state if we're inactive */ - if (state == RC_SERVICE_STARTED || - state == RC_SERVICE_STOPPED || - state == RC_SERVICE_INACTIVE) - { - xasprintf(&file, "%s/%s/%s", svcdir, "exclusive", base); - unlink(file); - free(file); - } + if (state == RC_SERVICE_STARTED || state == RC_SERVICE_STOPPED || state == RC_SERVICE_INACTIVE) + unlinkat(rc_dirfd(RC_DIR_EXCLUSIVE), base, 0); /* Remove any options and daemons the service may have stored */ if (state == RC_SERVICE_STOPPED) { - xasprintf(&file, "%s/%s/%s", svcdir, "options", base); - rm_dir(file, true); - free(file); - - xasprintf(&file, "%s/%s/%s", svcdir, "daemons", base); - rm_dir(file, true); - free(file); - + rm_dir(rc_dirfd(RC_DIR_OPTIONS), base, true); + rm_dir(rc_dirfd(RC_DIR_DAEMONS), base, true); rc_service_schedule_clear(service); } /* These are final states, so remove us from scheduled */ if (state == RC_SERVICE_STARTED || state == RC_SERVICE_STOPPED) { - xasprintf(&file, "%s/%s", svcdir, "scheduled"); - dirs = ls_dir(file, 0); - TAILQ_FOREACH(dir, dirs, entries) { - xasprintf(&was, "%s/%s/%s", file, dir->value, base); - unlink(was); - free(was); - - /* Try and remove the dir; we don't care about errors */ - xasprintf(&was, "%s/%s", file, dir->value); - serrno = errno; - rmdir(was); - errno = serrno; - free(was); + DIR *dp = do_dopendir(rc_dirfd(RC_DIR_SCHEDULED)); + if (dp) { + struct dirent *d; + while ((d = readdir(dp))) { + char *was; + + xasprintf(&was, "%s/%s", d->d_name, base); + unlinkat(dirfd(dp), was, 0); + free(was); + + serrno = errno; + unlinkat(dirfd(dp), d->d_name, AT_REMOVEDIR); + errno = serrno; + } + + closedir(dp); } - free(file); - rc_stringlist_free(dirs); } free(init); return true; @@ -1024,44 +956,36 @@ rc_service_mark(const char *service, const RC_SERVICE state) RC_SERVICE rc_service_state(const char *service) { - int i; - int state = RC_SERVICE_STOPPED; - char *file; - RC_STRINGLIST *dirs; - RC_STRING *dir; const char *base = basename_c(service); - const char *svcdir = rc_svcdir(); + int state = RC_SERVICE_STOPPED; + int i; for (i = 0; rc_service_state_names[i].name; i++) { - xasprintf(&file, "%s/%s/%s", svcdir, rc_service_state_names[i].name, base); - if (exists(file)) { + if (existsat(rc_dirfd(rc_service_state_names[i].dir), base)) { if (rc_service_state_names[i].state <= 0x10) state = rc_service_state_names[i].state; else state |= rc_service_state_names[i].state; } - free(file); } - if (state & RC_SERVICE_STARTED) { + if (state & RC_SERVICE_STARTED) if (rc_service_daemons_crashed(service) && errno != EACCES) state |= RC_SERVICE_CRASHED; - } + if (state & RC_SERVICE_STOPPED) { - char *sched_dir; - xasprintf(&sched_dir, "%s/scheduled", svcdir); - dirs = ls_dir(sched_dir, 0); - TAILQ_FOREACH(dir, dirs, entries) { - xasprintf(&file, "%s/scheduled/%s/%s", svcdir, dir->value, service); - i = exists(file); - free(file); - if (i) { - state |= RC_SERVICE_SCHEDULED; - break; + DIR *dp = do_dopendir(rc_dirfd(RC_DIR_SCHEDULED)); + if (dp) { + struct dirent *d; + while ((d = readdir(dp))) { + if (existsat(dirfd(dp), service)) { + state |= RC_SERVICE_SCHEDULED; + break; + } } + + closedir(dp); } - rc_stringlist_free(dirs); - free(sched_dir); } return state; @@ -1082,56 +1006,55 @@ rc_service_value_get(const char *service, const char *option) } bool -rc_service_value_set(const char *service, const char *option, - const char *value) +rc_service_value_set(const char *service, const char *option, const char *value) { + bool ret = true; + int optdirfd; FILE *fp; - char *file1, *file2; - xasprintf(&file1, "%s/options/%s", rc_svcdir(), service); - if (mkdir(file1, 0755) != 0 && errno != EEXIST) { - free(file1); + if (mkdirat(rc_dirfd(RC_DIR_OPTIONS), service, 0755) != 0 && errno != EEXIST) return false; - } - xasprintf(&file2, "%s/%s", file1, option); - free(file1); - if (value) { - fp = fopen(file2, "w"); - free(file2); - if (fp == NULL) - return false; + if ((optdirfd = openat(rc_dirfd(RC_DIR_OPTIONS), service, O_RDONLY | O_DIRECTORY)) == -1) + return false; + + if (!value) { + unlinkat(optdirfd, option, 0); + } else if ((fp = do_fopenat(optdirfd, option, O_WRONLY | O_CREAT | O_TRUNC))) { fprintf(fp, "%s", value); fclose(fp); } else { - unlink(file2); - free(file2); + ret = false; } - return true; + + close(optdirfd); + return ret; } bool rc_service_schedule_start(const char *service, const char *service_to_start) { - char *file1, *file2; - char *init; + const char *base_to_start = basename_c(service_to_start); + const char *base = basename_c(service); bool retval; + int schedfd; + char *init; /* service may be a provided service, like net */ if (!service || !rc_service_exists(service_to_start)) return false; - xasprintf(&file1, "%s/scheduled/%s", rc_svcdir(), basename_c(service)); - if (mkdir(file1, 0755) != 0 && errno != EEXIST) { - free(file1); + if (mkdirat(rc_dirfd(RC_DIR_SCHEDULED), base, 0755) != 0 && errno != EEXIST) + return false; + + if ((schedfd = openat(rc_dirfd(RC_DIR_SCHEDULED), base, O_RDONLY)) == -1) return false; - } init = rc_service_resolve(service_to_start); - xasprintf(&file2, "%s/%s", file1, basename_c(service_to_start)); - free(file1); - retval = (exists(file2) || symlink(init, file2) == 0); - free(file2); + + retval = existsat(schedfd, base_to_start) == 0 || symlinkat(init, schedfd, base_to_start) == 0; + + close(schedfd); free(init); return retval; } @@ -1139,21 +1062,12 @@ rc_service_schedule_start(const char *service, const char *service_to_start) bool rc_service_schedule_clear(const char *service) { - char *dir; - bool r; - - xasprintf(&dir, "%s/scheduled/%s", rc_svcdir(), basename_c(service)); - r = rm_dir(dir, true); - free(dir); - if (!r && errno == ENOENT) - return true; - return false; + return !rm_dir(rc_dirfd(RC_DIR_SCHEDULED), basename_c(service), true) && errno == ENOENT; } RC_STRINGLIST * rc_services_in_runlevel(const char *runlevel) { - char *dir; RC_STRINGLIST *list = NULL; if (!runlevel) { @@ -1163,7 +1077,7 @@ rc_services_in_runlevel(const char *runlevel) RC_STRINGLIST *svcs; xasprintf(&initd, "%s/init.d", *dirs); - svcs = ls_dir(initd, LS_INITD); + svcs = ls_dir(AT_FDCWD, initd, LS_INITD); TAILQ_CONCAT(list, svcs, entries); rc_stringlist_free(svcs); @@ -1173,14 +1087,10 @@ rc_services_in_runlevel(const char *runlevel) } /* These special levels never contain any services */ - if (strcmp(runlevel, RC_LEVEL_SINGLE) != 0) { - xasprintf(&dir, "%s/%s", rc_runleveldir(), runlevel); - list = ls_dir(dir, LS_INITD); - free(dir); - } - if (!list) - list = rc_stringlist_new(); - return list; + if (strcmp(runlevel, RC_LEVEL_SINGLE) == 0) + return rc_stringlist_new(); + + return ls_dir(rc_dirfd(RC_DIR_RUNLEVEL), runlevel, LS_INITD); } RC_STRINGLIST * @@ -1205,35 +1115,24 @@ rc_services_in_state(RC_SERVICE state) { RC_STRINGLIST *services; RC_STRINGLIST *list; - RC_STRINGLIST *dirs; - RC_STRING *d; - char *dir, *dir2; - - xasprintf(&dir, "%s/%s", rc_svcdir(), rc_parse_service_state(state)); - if (state != RC_SERVICE_SCHEDULED) { - dirs = ls_dir(dir, LS_INITD); - free(dir); - return dirs; - } + struct dirent *d; + DIR *dp; + + if (state != RC_SERVICE_SCHEDULED) + return ls_dir(rc_dirfd(RC_DIR_SVCDIR), rc_parse_service_state(state), LS_INITD); - dirs = ls_dir(dir, 0); list = rc_stringlist_new(); - if (!dirs) { - free(dir); + if (!(dp = do_opendirat(rc_dirfd(RC_DIR_SVCDIR), "scheduled"))) return list; - } - TAILQ_FOREACH(d, dirs, entries) { - xasprintf(&dir2, "%s/%s", dir, d->value); - services = ls_dir(dir2, LS_INITD); - free(dir2); - if (services) { - TAILQ_CONCAT(list, services, entries); - rc_stringlist_free(services); - } + while ((d = readdir(dp))) { + if (!(services = ls_dir(dirfd(dp), d->d_name, LS_INITD))) + continue; + TAILQ_CONCAT(list, services, entries); + rc_stringlist_free(services); } - rc_stringlist_free(dirs); - free(dir); + closedir(dp); + return list; } @@ -1312,33 +1211,25 @@ RC_STRINGLIST * rc_services_scheduled_by(const char *service) { RC_STRINGLIST *list = rc_stringlist_new(); - RC_STRINGLIST *dirs; - RC_STRING *dir; - char *sched_dir; - char *file; + struct dirent *d; + DIR *dp; - xasprintf(&sched_dir, "%s/scheduled", rc_svcdir()); - dirs = ls_dir(sched_dir, 0); + if (!(dp = do_dopendir(rc_dirfd(RC_DIR_SCHEDULED)))) + return list; - TAILQ_FOREACH(dir, dirs, entries) { - xasprintf(&file, "%s/%s/%s", sched_dir, dir->value, service); - if (exists(file)) - rc_stringlist_add(list, file); + while ((d = readdir(dp))) { + char *file; + xasprintf(&file, "%s/%s", d->d_name, service); + if (existsat(dirfd(dp), file)) + rc_stringlist_add(list, d->d_name); free(file); } - rc_stringlist_free(dirs); - free(sched_dir); + return list; } RC_STRINGLIST * rc_services_scheduled(const char *service) { - char *dir; - RC_STRINGLIST *list; - - xasprintf(&dir, "%s/scheduled/%s", rc_svcdir(), basename_c(service)); - list = ls_dir(dir, LS_INITD); - free(dir); - return list; + return ls_dir(rc_dirfd(RC_DIR_SCHEDULED), basename_c(service), LS_INITD); } diff --git a/src/librc/librc.h b/src/librc/librc.h index 10e7c1b11..65c11cea7 100644 --- a/src/librc/librc.h +++ b/src/librc/librc.h @@ -72,4 +72,6 @@ static const char *const dirnames[RC_DIR_SYS_MAX] = [RC_DIR_TMP] = "tmp", }; +RC_STRINGLIST *config_list(int dirfd, const char *pathname); + #endif diff --git a/src/shared/helpers.h b/src/shared/helpers.h index 6690badd5..62daee064 100644 --- a/src/shared/helpers.h +++ b/src/shared/helpers.h @@ -26,6 +26,9 @@ #include #include #include +#include +#include +#include #define ERRX do { fprintf (stderr, "out of memory\n"); exit (1); } while (0) @@ -121,6 +124,13 @@ RC_UNUSED static const char *basename_c(const char *path) return (path); } +RC_UNUSED static bool existsat(int dirfd, const char *pathname) +{ + struct stat buf; + + return (fstatat(dirfd, pathname, &buf, 0) == 0); +} + RC_UNUSED static bool exists(const char *pathname) { struct stat buf; @@ -135,6 +145,61 @@ RC_UNUSED static bool existss(const char *pathname) return (stat(pathname, &buf) == 0 && buf.st_size != 0); } +RC_UNUSED static FILE *do_fopenat(int dirfd, const char *pathname, int mode) +{ + int fd = openat(dirfd, pathname, mode, 0666); + const char *fmode; + FILE *fp; + + if (fd == -1) + return NULL; + + /* O_CREAT and O_TRUNC in modes 'a', 'w+', 'a+' don't + * really matter after the file has been opened. + * + * Some implementations have O_RDONLY defined to zero, + * thus making it impossible to detect, so, we default + * to "r" if no other mask fully matches. */ + if ((mode & O_RDWR) == O_RDWR) + fmode = "r+"; + else if ((mode & O_WRONLY) == O_WRONLY) + fmode = "w"; + else + fmode = "r"; + + if (!(fp = fdopen(fd, fmode))) + close(fd); + return fp; +} + +RC_UNUSED static DIR *do_opendirat(int dirfd, const char *pathname) +{ + int fd = openat(dirfd, pathname, O_RDONLY | O_DIRECTORY); + DIR *dp; + + if (fd == -1) + return NULL; + + if (!(dp = fdopendir(fd))) + close(fd); + + return dp; +} + +RC_UNUSED static DIR *do_dopendir(int dirfd) +{ + int fd = dup(dirfd); + DIR *dp; + + if (fd == -1) + return NULL; + + if (!(dp = fdopendir(fd))) + close(fd); + + return dp; +} + /* * This is an OpenRC specific version of the asprintf() function. * We do this to avoid defining the _GNU_SOURCE feature test macro on diff --git a/src/shared/misc.h b/src/shared/misc.h index 2a0153c06..e5cf01606 100644 --- a/src/shared/misc.h +++ b/src/shared/misc.h @@ -47,23 +47,24 @@ pid_t exec_service(const char *, const char *); typedef struct rc_service_state_name { RC_SERVICE state; + enum rc_dir dir; const char *const name; } rc_service_state_name_t; /* We MUST list the states below 0x10 first * The rest can be in any order */ static const rc_service_state_name_t rc_service_state_names[] = { - { RC_SERVICE_STARTED, "started" }, - { RC_SERVICE_STOPPED, "stopped" }, - { RC_SERVICE_STARTING, "starting" }, - { RC_SERVICE_STOPPING, "stopping" }, - { RC_SERVICE_INACTIVE, "inactive" }, - { RC_SERVICE_WASINACTIVE, "wasinactive" }, - { RC_SERVICE_HOTPLUGGED, "hotplugged" }, - { RC_SERVICE_FAILED, "failed" }, - { RC_SERVICE_SCHEDULED, "scheduled"}, - { RC_SERVICE_CRASHED, "crashed"}, - { 0, NULL} + { RC_SERVICE_STARTED, RC_DIR_STARTED, "started" }, + { RC_SERVICE_STOPPED, RC_DIR_INVALID, "stopped" }, + { RC_SERVICE_STARTING, RC_DIR_STARTING, "starting" }, + { RC_SERVICE_STOPPING, RC_DIR_STOPPING, "stopping" }, + { RC_SERVICE_INACTIVE, RC_DIR_INACTIVE, "inactive" }, + { RC_SERVICE_WASINACTIVE, RC_DIR_WASINACTIVE, "wasinactive" }, + { RC_SERVICE_HOTPLUGGED, RC_DIR_HOTPLUGGED, "hotplugged" }, + { RC_SERVICE_FAILED, RC_DIR_FAILED, "failed" }, + { RC_SERVICE_SCHEDULED, RC_DIR_SCHEDULED, "scheduled"}, + { RC_SERVICE_CRASHED, RC_DIR_INVALID, "crashed"}, + { 0, -1, NULL} }; /* From c2cd07109a94c260333fc531c1beea30fd2b5ba5 Mon Sep 17 00:00:00 2001 From: "Anna (navi) Figueiredo Gomes" Date: Fri, 28 Mar 2025 03:03:19 +0100 Subject: [PATCH 3/9] openrc-run: use rc_dirfd --- src/openrc-run/openrc-run.c | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/src/openrc-run/openrc-run.c b/src/openrc-run/openrc-run.c index 5348f2782..9a7f55f3a 100644 --- a/src/openrc-run/openrc-run.c +++ b/src/openrc-run/openrc-run.c @@ -167,12 +167,8 @@ handle_signal(int sig) static void unhotplug(void) { - char *file = NULL; - - xasprintf(&file, "%s/hotplugged/%s", rc_svcdir(), applet); - if (exists(file) && unlink(file) != 0) - eerror("%s: unlink `%s': %s", applet, file, strerror(errno)); - free(file); + if (unlinkat(rc_dirfd(RC_DIR_HOTPLUGGED), applet, 0) != 0 && errno != ENOENT) + eerror("%s: unlink '%s/hotplugged/%s': %s", applet, rc_svcdir(), applet, strerror(errno)); } static void @@ -286,7 +282,6 @@ write_prefix(const char *buffer, size_t bytes, bool *prefixed) size_t i, j; const char *ec = ecolor(ECOLOR_HILITE); const char *ec_normal = ecolor(ECOLOR_NORMAL); - char *prefix_lock; ssize_t ret = 0; int fd = fileno(stdout), lock_fd = -1; @@ -294,9 +289,7 @@ write_prefix(const char *buffer, size_t bytes, bool *prefixed) * Lock the prefix. * open() may fail here when running as user, as RC_SVCDIR may not be writable. */ - xasprintf(&prefix_lock, "%s/prefix.lock", rc_svcdir()); - lock_fd = open(prefix_lock, O_WRONLY | O_CREAT, 0664); - free(prefix_lock); + lock_fd = openat(rc_dirfd(RC_DIR_SVCDIR), "prefix.lock", O_WRONLY | O_CREAT, 0664); if (lock_fd != -1) { while (flock(lock_fd, LOCK_EX) != 0) { @@ -503,11 +496,11 @@ svc_exec(const char *arg1, const char *arg2) static bool svc_wait(const char *svc) { - char *file = NULL; int fd; bool forever = false; RC_STRINGLIST *keywords; struct timespec timeout, warn; + const char *base = basename_c(svc); /* Some services don't have a timeout, like fsck */ keywords = rc_deptree_depend(deptree, svc, "keyword"); @@ -516,30 +509,24 @@ svc_wait(const char *svc) forever = true; rc_stringlist_free(keywords); - xasprintf(&file, "%s/exclusive/%s", rc_svcdir(), basename_c(svc)); - timeout.tv_sec = WAIT_TIMEOUT; timeout.tv_nsec = 0; warn.tv_sec = WARN_TIMEOUT; warn.tv_nsec = 0; for (;;) { - fd = open(file, O_RDONLY | O_NONBLOCK); + fd = openat(rc_dirfd(RC_DIR_EXCLUSIVE), base, O_RDONLY | O_NONBLOCK); if (fd != -1) { if (flock(fd, LOCK_SH | LOCK_NB) == 0) { close(fd); - free(file); return true; } close(fd); } if (errno == ENOENT) { - free(file); return true; } if (errno != EWOULDBLOCK) { - eerror("%s: open `%s': %s", applet, file, - strerror(errno)); - free(file); + eerror("%s: open '%s/exclusive/%s': %s", applet, rc_svcdir(), base, strerror(errno)); exit(EXIT_FAILURE); } if (nanosleep(&interval, NULL) == -1) { @@ -560,7 +547,6 @@ svc_wait(const char *svc) } } finish: - free(file); return false; } From ac2be70f281b08d3b5d459985d703d833ad53e15 Mon Sep 17 00:00:00 2001 From: "Anna (navi) Figueiredo Gomes" Date: Fri, 4 Apr 2025 00:16:25 +0200 Subject: [PATCH 4/9] openrc: use rc_dirfd --- src/openrc/rc.c | 82 +++++++++++++------------------------------------ 1 file changed, 22 insertions(+), 60 deletions(-) diff --git a/src/openrc/rc.c b/src/openrc/rc.c index ab494670d..6e1c4e246 100644 --- a/src/openrc/rc.c +++ b/src/openrc/rc.c @@ -93,37 +93,26 @@ clean_failed(void) { DIR *dp; struct dirent *d; - char *path; - char *failed_dir; - - xasprintf(&failed_dir, "%s/failed", rc_svcdir()); /* Clean the failed services state dir now */ - if ((dp = opendir(failed_dir))) { - while ((d = readdir(dp))) { - if (d->d_name[0] == '.' && - (d->d_name[1] == '\0' || - (d->d_name[1] == '.' && d->d_name[2] == '\0'))) - continue; + if (!(dp = do_opendirat(rc_dirfd(RC_DIR_SVCDIR), "failed"))) + return; - xasprintf(&path, "%s/%s", failed_dir, d->d_name); - if (unlink(path)) - eerror("%s: unlink `%s': %s", - applet, path, strerror(errno)); - free(path); - } - closedir(dp); - } + while ((d = readdir(dp))) { + if (strcmp(d->d_name, ".") == 0 || strcmp(d->d_name, "..") == 0) + continue; - free(failed_dir); + if (unlinkat(dirfd(dp), d->d_name, 0) == -1) + eerror("%s: unlink '%s/failed/%s': %s", applet, rc_svcdir(), d->d_name, strerror(errno)); + } + closedir(dp); } static void cleanup(void) { + int svcdirfd = rc_dirfd(RC_DIR_SVCDIR); RC_PID *p, *tmp; - const char *svcdir = rc_svcdir(); - static const char *subdirs[] = { "rc.starting", "rc.stopping" }; if (!rc_in_logger && !rc_in_plugin && applet && (strcmp(applet, "rc") == 0 || strcmp(applet, "openrc") == 0)) @@ -139,12 +128,8 @@ cleanup(void) } /* Clean runlevel start, stop markers */ - for (size_t i = 0; i < ARRAY_SIZE(subdirs); i++) { - char *path; - xasprintf(&path, "%s/%s", svcdir, subdirs[i]); - rmdir(path); - free(path); - } + unlinkat(svcdirfd, "rc.starting", AT_REMOVEDIR); + unlinkat(svcdirfd, "rc.stopping", AT_REMOVEDIR); clean_failed(); rc_logger_close(); @@ -223,13 +208,9 @@ static void mark_interactive(void) { FILE *fp; - char *dir; - xasprintf(&dir, "%s/interactive", rc_svcdir()); - if ((fp = fopen(dir, "w"))) + if ((fp = do_fopenat(rc_dirfd(RC_DIR_SVCDIR), "interactive", O_WRONLY | O_CREAT | O_TRUNC))) fclose(fp); - - free(dir); } static void @@ -670,11 +651,9 @@ do_start_services(const RC_STRINGLIST *start_services, bool parallel) bool interactive = false; RC_SERVICE state; bool crashed = false; - char *interactive_dir; - xasprintf(&interactive_dir, "%s/interactive", rc_svcdir()); if (!rc_yesno(getenv("EINFO_QUIET"))) - interactive = exists(interactive_dir); + interactive = existsat(rc_dirfd(RC_DIR_SVCDIR), "interactive"); errno = 0; crashed = rc_conf_yesno("rc_crashed_start"); if (errno == ENOENT) @@ -733,9 +712,7 @@ do_start_services(const RC_STRINGLIST *start_services, bool parallel) strcmp(runlevel, getenv("RC_BOOTLEVEL")) == 0)) mark_interactive(); else - if (exists(interactive_dir)) - unlink(interactive_dir); - free(interactive_dir); + unlinkat(rc_dirfd(RC_DIR_SVCDIR), "interactive", 0); } #ifdef RC_DEBUG @@ -774,9 +751,6 @@ int main(int argc, char **argv) RC_STRING *service; bool going_down = false; int depoptions = RC_DEP_STRICT | RC_DEP_TRACE; - const char *svcdir; - char *rc_starting, *rc_stopping; - char *deptree_skewed; char *krunlevel = NULL; const char *workingdir = "/"; char *pidstr = NULL; @@ -865,8 +839,6 @@ int main(int argc, char **argv) env_filter(); env_config(); - svcdir = rc_svcdir(); - newlevel = argv[optind++]; /* To make life easier, we only have the shutdown runlevel as * nothing really needs to know that we're rebooting. @@ -988,17 +960,14 @@ int main(int argc, char **argv) if ((main_deptree = _rc_deptree_load(0, ®en)) == NULL) eerrorx("failed to load deptree"); - xasprintf(&deptree_skewed, "%s/clock-skewed", svcdir); - if (exists(deptree_skewed)) + if (existsat(rc_dirfd(RC_DIR_SVCDIR), "clock-skewed")) ewarn("WARNING: clock skew detected!"); - free(deptree_skewed); /* Clean the failed services state dir */ clean_failed(); - xasprintf(&rc_stopping, "%s/rc.stopping", svcdir); - if (mkdir(rc_stopping, 0755) != 0) - eerrorx("%s: failed to create stopping dir '%s': %s", applet, rc_stopping, strerror(errno)); + if (mkdirat(rc_dirfd(RC_DIR_SVCDIR), "rc.stopping", 0755) != 0) + eerrorx("%s: failed to create stopping dir '%s/rc.stopping': %s", applet, rc_svcdir(), strerror(errno)); /* Create a list of all services which we could stop (assuming * they won't be active in the new or current runlevel) including @@ -1079,8 +1048,7 @@ int main(int argc, char **argv) going_down ? newlevel : runlevel); hook_out = 0; - rmdir(rc_stopping); - free(rc_stopping); + unlinkat(rc_dirfd(RC_DIR_SVCDIR), "rc.stopping", AT_REMOVEDIR); /* Store the new runlevel */ if (newlevel) { @@ -1097,9 +1065,7 @@ int main(int argc, char **argv) rc_logger_close(); #endif - xasprintf(&rc_starting, "%s/rc.starting", svcdir); - mkdir(rc_starting, 0755); - free(rc_starting); + mkdirat(rc_dirfd(RC_DIR_SVCDIR), "rc.starting", 0755); rc_plugin_run(RC_HOOK_RUNLEVEL_START_IN, runlevel); hook_out = RC_HOOK_RUNLEVEL_START_OUT; @@ -1169,12 +1135,8 @@ int main(int argc, char **argv) * we need to delete them so that they are regenerated again in the * default runlevel as they may depend on things that are now * available */ - if (regen && strcmp(runlevel, bootlevel) == 0) { - char *deptree_cache; - xasprintf(&deptree_cache, "%s/deptree", svcdir); - unlink(deptree_cache); - free(deptree_cache); - } + if (regen && strcmp(runlevel, bootlevel) == 0) + unlinkat(rc_dirfd(RC_DIR_SVCDIR), "deptree", 0); return EXIT_SUCCESS; } From 5994dcdb14e063661708b062cca779f43db0af7b Mon Sep 17 00:00:00 2001 From: "Anna (navi) Figueiredo Gomes" Date: Mon, 7 Apr 2025 16:05:42 +0200 Subject: [PATCH 5/9] librc: attempt to create svcdirs on demand we would no longer need to create runtime directories during the (conditional) dependency update needed check, so we can remove the checks from there and instead rely on the directory apis --- src/librc/librc-depend.c | 11 ----------- src/librc/librc.c | 33 ++++++++++++++++++++------------- 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/src/librc/librc-depend.c b/src/librc/librc-depend.c index f76fe3ed5..4b77786aa 100644 --- a/src/librc/librc-depend.c +++ b/src/librc/librc-depend.c @@ -689,17 +689,6 @@ rc_deptree_update_needed(time_t *newest, char *file) RC_STRING *s; struct stat buf; time_t mtime; - char *path; - const char *service_dir = rc_svcdir(); - - /* Create base directories if needed */ - if (mkdir(service_dir, 0755) != 0 && errno != EEXIST) - fprintf(stderr, "mkdir '%s': %s\n", service_dir, strerror(errno)); - - for (size_t i = 1; i < RC_DIR_SYS_MAX; i++) { - if (mkdirat(rc_dirfd(RC_DIR_SVCDIR), dirnames[i], 0755) != 0 && errno != EEXIST) - fprintf(stderr, "mkdir `%s': %s\n", dirnames[i], strerror(errno)); - } /* Quick test to see if anything we use has changed and we have * data in our deptree. */ diff --git a/src/librc/librc.c b/src/librc/librc.c index 9b9107c90..9c79c860b 100644 --- a/src/librc/librc.c +++ b/src/librc/librc.c @@ -50,29 +50,36 @@ static int dirfds[RC_DIR_MAX]; int rc_dirfd(enum rc_dir dir) { int flags = O_RDONLY | O_CLOEXEC | O_DIRECTORY; + int dirfd = AT_FDCWD; + const char *dirname; + if (dir >= RC_DIR_MAX) return -1; if (dirfds[dir] <= 0) { switch (dir) { case RC_DIR_INVALID: return -1; - case RC_DIR_SVCDIR: - dirfds[dir] = open(rc_svcdir(), flags); - break; - case RC_DIR_RUNLEVEL: - dirfds[dir] = open(rc_runleveldir(), flags); - break; case RC_DIR_SYSCONF: - dirfds[dir] = open(rc_sysconfdir(), flags); - break; + return dirfds[dir] = open(rc_sysconfdir(), flags); case RC_DIR_USRCONF: - dirfds[dir] = open(rc_usrconfdir(), flags); + return dirfds[dir] = open(rc_usrconfdir(), flags); + case RC_DIR_RUNLEVEL: + return dirfds[dir] = open(rc_runleveldir(), flags); + case RC_DIR_SVCDIR: + dirname = rc_svcdir(); break; default: assert(dirnames[dir]); - dirfds[dir] = openat(rc_dirfd(RC_DIR_SVCDIR), dirnames[dir], flags); + dirname = dirnames[dir]; + dirfd = rc_dirfd(RC_DIR_SVCDIR); break; } + + if ((dirfds[dir] = openat(dirfd, dirname, flags)) == -1 && errno == ENOENT) { + if (mkdirat(dirfd, dirname, 0755) == -1 && errno != EEXIST) + return -1; + dirfds[dir] = openat(dirfd, dirname, flags); + } } return dirfds[dir]; } @@ -665,9 +672,9 @@ rc_runleveldir(void) const char * rc_svcdir(void) { - if (rc_dirs.set) - return rc_dirs.svcdir; - return RC_SVCDIR; + const char *path = rc_dirs.set ? rc_dirs.svcdir : RC_SVCDIR; + mkdir(path, 0755); + return path; } static ssize_t From eeb80c394dc86eeb9700439bd06aa6674286156b Mon Sep 17 00:00:00 2001 From: "Anna (navi) Figueiredo Gomes" Date: Thu, 10 Apr 2025 11:50:14 +0200 Subject: [PATCH 6/9] librc: clear dirfds when switching to user mode --- src/librc/librc.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/librc/librc.c b/src/librc/librc.c index 9c79c860b..8d4a7410f 100644 --- a/src/librc/librc.c +++ b/src/librc/librc.c @@ -636,6 +636,13 @@ rc_set_user(void) rc_dirs.scriptdirs[SCRIPTDIR_USR] = rc_dirs.usrconfdir; rc_dirs.scriptdirs[SCRIPTDIR_SVC] = rc_dirs.svcdir; + + for (size_t i = 0; i < RC_DIR_MAX; i++) { + if (dirfds[i] == -1) + continue; + close(dirfds[i]); + dirfds[i] = -1; + } } const char * const * From 62438866ce65b6e756cb67cffa7200a09591f203 Mon Sep 17 00:00:00 2001 From: "Anna (navi) Figueiredo Gomes" Date: Tue, 15 Apr 2025 01:32:58 +0200 Subject: [PATCH 7/9] librc: add rc_scriptdirfds api the fd list is only populated with scriptdirs that exist, since some might not be in use. --- src/librc/librc-depend.c | 11 ++++------- src/librc/librc.c | 31 +++++++++++++++++++++++++------ src/librc/rc.h.in | 6 ++++++ 3 files changed, 35 insertions(+), 13 deletions(-) diff --git a/src/librc/librc-depend.c b/src/librc/librc-depend.c index 4b77786aa..60be21e57 100644 --- a/src/librc/librc-depend.c +++ b/src/librc/librc-depend.c @@ -685,6 +685,7 @@ bool rc_deptree_update_needed(time_t *newest, char *file) { bool newer = false; + const int *dirfds; RC_STRINGLIST *config; RC_STRING *s; struct stat buf; @@ -704,13 +705,9 @@ rc_deptree_update_needed(time_t *newest, char *file) mtime = time(NULL); } - for (const char * const *dirs = rc_scriptdirs(); *dirs; dirs++) { - static const char *subdirs[] = { "init.d", "conf.d", NULL }; - for (const char **subdir = subdirs; *subdir; subdir++) { - xasprintf(&path, "%s/%s", *dirs, *subdir); - newer |= !deep_mtime_check(AT_FDCWD, path, true, &mtime, file); - free(path); - } + for (size_t i = 0, count = rc_scriptdirfds(&dirfds); i < count; i++) { + newer |= !deep_mtime_check(dirfds[i], "init.d", true, &mtime, file); + newer |= !deep_mtime_check(dirfds[i], "conf.d", true, &mtime, file); } newer |= !deep_mtime_check(rc_dirfd(RC_DIR_SYSCONF), "rc.conf", true, &mtime, file); diff --git a/src/librc/librc.c b/src/librc/librc.c index 8d4a7410f..acb448a01 100644 --- a/src/librc/librc.c +++ b/src/librc/librc.c @@ -653,6 +653,28 @@ rc_scriptdirs(void) return scriptdirs; } +size_t +rc_scriptdirfds(const int **fds) +{ + static int scriptdirfds[SCRIPTDIR_MAX]; + static size_t len = 0; + + if (len == 0) { + const char * const *current_scriptdirs = rc_scriptdirs(); + for (size_t i = 0; current_scriptdirs[i]; i++) { + int fd = open(current_scriptdirs[i], O_RDONLY | O_DIRECTORY | O_CLOEXEC); + + if (fd == -1) + continue; + + scriptdirfds[len++] = fd; + } + } + + *fds = scriptdirfds; + return len; +} + const char * rc_sysconfdir(void) { @@ -1085,17 +1107,14 @@ rc_services_in_runlevel(const char *runlevel) RC_STRINGLIST *list = NULL; if (!runlevel) { + const int *scriptdirfds; list = rc_stringlist_new(); - for (const char * const *dirs = rc_scriptdirs(); *dirs; dirs++) { - char *initd; + for (size_t i = 0, count = rc_scriptdirfds(&scriptdirfds); i < count; i++) { RC_STRINGLIST *svcs; - xasprintf(&initd, "%s/init.d", *dirs); - svcs = ls_dir(AT_FDCWD, initd, LS_INITD); + svcs = ls_dir(scriptdirfds[i], "init.d", LS_INITD); TAILQ_CONCAT(list, svcs, entries); - rc_stringlist_free(svcs); - free(initd); } return list; } diff --git a/src/librc/rc.h.in b/src/librc/rc.h.in index 47c44f948..e34373de3 100644 --- a/src/librc/rc.h.in +++ b/src/librc/rc.h.in @@ -130,6 +130,12 @@ bool rc_is_user(void); * @return NULL terminated array of directory paths. */ const char * const *rc_scriptdirs(void); +/*! Get a list of fds for existing script directories + * containing a init.d and conf.d + * @param out pointer to store the list. + * @return number of fds in the list. */ +size_t rc_scriptdirfds(const int **fds); + /*! The system configuration directory is where rc.conf * and other configuration files are stored. * @return Path to the system configuration directory */ From 794fb81f259a1f555ff4f1ef8770a25043fd3ad4 Mon Sep 17 00:00:00 2001 From: "Anna (navi) Figueiredo Gomes" Date: Tue, 15 Apr 2025 02:17:48 +0200 Subject: [PATCH 8/9] pam_openrc: use rc_dirfd --- src/pam_openrc/pam_openrc.c | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/pam_openrc/pam_openrc.c b/src/pam_openrc/pam_openrc.c index 0e32b76eb..5db0db3b3 100644 --- a/src/pam_openrc/pam_openrc.c +++ b/src/pam_openrc/pam_openrc.c @@ -69,21 +69,17 @@ exec_openrc(pam_handle_t *pamh, bool opening) } if (!(script = rc_service_resolve(svc_name))) { - char *user_script = rc_service_resolve("user"); - if (!user_script) { + if (!(script = rc_service_resolve("user"))) { elog(LOG_ERR, "Failed to resolve %s.", svc_name); ret = PAM_SESSION_ERR; goto out; } - xasprintf(&script, "%s/init.d/%s", rc_svcdir(), svc_name); - if (symlink(user_script, script) != 0) { + if (symlinkat(script, rc_dirfd(RC_DIR_INITD), svc_name) != 0) { elog(LOG_ERR, "symlink: %s", strerror(errno)); ret = PAM_SESSION_ERR; - free(user_script); goto out; } - free(user_script); } logins = rc_service_value_get(svc_name, "logins"); @@ -93,14 +89,14 @@ exec_openrc(pam_handle_t *pamh, bool opening) if (opening) { if (count == 0) { - pid = service_start(script); - rc_service_mark(script, RC_SERVICE_HOTPLUGGED); + pid = service_start(svc_name); + rc_service_mark(svc_name, RC_SERVICE_HOTPLUGGED); } count++; } else { count--; if (count == 0) - pid = service_stop(script); + pid = service_stop(svc_name); } elog(LOG_INFO, "%d sessions", count); From 68b85e1a33de1cdf4b250674c7f21498b6797bae Mon Sep 17 00:00:00 2001 From: "Anna (navi) Figueiredo Gomes" Date: Tue, 15 Apr 2025 02:18:05 +0200 Subject: [PATCH 9/9] shared/misc: use rc_dirfd --- src/shared/misc.c | 62 ++++++++++++++++++----------------------------- 1 file changed, 23 insertions(+), 39 deletions(-) diff --git a/src/shared/misc.c b/src/shared/misc.c index b1df43fdb..be8cc5f33 100644 --- a/src/shared/misc.c +++ b/src/shared/misc.c @@ -284,12 +284,8 @@ signal_setup_restart(int sig, void (*handler)(int)) int svc_lock(const char *applet, bool ignore_lock_failure) { - char *file = NULL; - int fd; + int fd = openat(rc_dirfd(RC_DIR_EXCLUSIVE), applet, O_WRONLY | O_CREAT | O_NONBLOCK, 0664); - xasprintf(&file, "%s/exclusive/%s", rc_svcdir(), applet); - fd = open(file, O_WRONLY | O_CREAT | O_NONBLOCK, 0664); - free(file); if (fd == -1) return -1; if (flock(fd, LOCK_EX | LOCK_NB) == -1) { @@ -311,12 +307,8 @@ svc_lock(const char *applet, bool ignore_lock_failure) int svc_unlock(const char *applet, int fd) { - char *file = NULL; - - xasprintf(&file, "%s/exclusive/%s", rc_svcdir(), applet); + unlinkat(rc_dirfd(RC_DIR_EXCLUSIVE), applet, 0); close(fd); - unlink(file); - free(file); return -1; } @@ -421,59 +413,51 @@ RC_DEPTREE * _rc_deptree_load(int force, int *regen) int merrno; time_t t; char file[PATH_MAX]; - const char *svcdir = rc_svcdir(); + int svcdirfd = rc_dirfd(RC_DIR_SVCDIR); struct stat st; - struct utimbuf ut; FILE *fp; t = 0; if (rc_deptree_update_needed(&t, file) || force != 0) { - char *deptree_cache, *deptree_skewed; - xasprintf(&deptree_cache, "%s/deptree", svcdir); - /* Test if we have permission to update the deptree */ - fd = open(deptree_cache, O_WRONLY); + fd = openat(svcdirfd, "deptree", O_WRONLY); merrno = errno; errno = serrno; if (fd == -1 && merrno == EACCES) - goto out; + return rc_deptree_load(); close(fd); if (regen) *regen = 1; ebegin("Caching service dependencies"); retval = rc_deptree_update() ? 0 : -1; - eend (retval, "Failed to update the dependency tree"); + eend(retval, "Failed to update the dependency tree"); if (retval == 0) { - if (stat(deptree_cache, &st) != 0) { - eerror("stat(%s): %s", deptree_cache, strerror(errno)); - free(deptree_cache); + if (fstatat(svcdirfd, "deptree", &st, 0) != 0) { + eerror("stat(%s): %s/deptree", rc_svcdir(), strerror(errno)); return NULL; } - xasprintf(&deptree_skewed, "%s/clock-skewed", svcdir); - if (st.st_mtime < t) { - eerror("Clock skew detected with '%s'", file); - eerrorn("Adjusting mtime of '%s' to %s", deptree_cache, ctime(&t)); - fp = fopen(deptree_skewed, "w"); - if (fp != NULL) { - fprintf(fp, "%s\n", file); - fclose(fp); - } - ut.actime = t; - ut.modtime = t; - utime(deptree_cache, &ut); - } else { - if (exists(deptree_skewed)) - unlink(deptree_skewed); + + if (st.st_mtime >= t) { + unlinkat(svcdirfd, "clock-skewed", 0); + goto out; + } + + eerror("Clock skew detected with '%s/clock-skewed'", rc_svcdir()); + eerrorn("Adjusting mtime of '%s/deptree' to %s", rc_svcdir(), ctime(&t)); + if ((fp = do_fopenat(svcdirfd, "clock-skewed", O_WRONLY | O_CREAT | O_TRUNC))) { + fprintf(fp, "%s\n", file); + fclose(fp); + futimens(fileno(fp), (struct timespec[]) {{ .tv_sec = t }, { .tv_sec = t}}); } - free(deptree_skewed); } + +out: if (force == -1 && regen != NULL) *regen = retval; -out: - free(deptree_cache); } + return rc_deptree_load(); }