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..60be21e57 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,20 @@ 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; + const int *dirfds; 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); - } /* 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 +704,22 @@ 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); - 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); } - 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 +780,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 +1032,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 +1047,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 d71378720..acb448a01 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,101 +46,123 @@ /* 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; + 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_SYSCONF: + return dirfds[dir] = open(rc_sysconfdir(), flags); + case RC_DIR_USRCONF: + 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]); + 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]; +} #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; @@ -348,13 +371,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; @@ -379,8 +410,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; @@ -402,47 +432,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; @@ -452,7 +468,6 @@ rc_runlevel_get(void) *runlevel = '\0'; fclose(fp); } - free(softlevel); if (!runlevel || !*runlevel) { free(runlevel); @@ -465,35 +480,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; } @@ -630,9 +634,15 @@ 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; + + for (size_t i = 0; i < RC_DIR_MAX; i++) { + if (dirfds[i] == -1) + continue; + close(dirfds[i]); + dirfds[i] = -1; + } } const char * const * @@ -643,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) { @@ -669,20 +701,20 @@ 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 -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; @@ -703,7 +735,6 @@ char * rc_service_resolve(const char *service) { char *buffer; - char *file = NULL; struct stat buf; if (!service) @@ -713,29 +744,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; } @@ -756,8 +779,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; } @@ -868,16 +891,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) @@ -885,21 +902,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) { @@ -909,82 +922,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; @@ -993,44 +992,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; @@ -1051,56 +1042,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; } @@ -1108,48 +1098,32 @@ 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) { + 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(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; } /* 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 * @@ -1174,35 +1148,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; } @@ -1281,33 +1244,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 260720ee9..65c11cea7 100644 --- a/src/librc/librc.h +++ b/src/librc/librc.h @@ -55,4 +55,23 @@ #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", +}; + +RC_STRINGLIST *config_list(int dirfd, const char *pathname); + #endif diff --git a/src/librc/rc.h.in b/src/librc/rc.h.in index 82bbdb917..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 */ @@ -190,6 +196,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. 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; } 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; } 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); 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.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(); } 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} }; /*