From 1921b3f2c5b9843328bd1de7583a2794affbbc55 Mon Sep 17 00:00:00 2001 From: Aaron Andersen Date: Sat, 24 May 2025 15:10:03 -0400 Subject: [PATCH 1/5] tmpfiles: refactor into separate executable The tmpfiles.d spec has proven useful in systemd, enough so that some developers are starting to ship tmpfiles.d configuration files with their software. By splitting the tmpfiles functionality in finit out into a separate executable we are providing a useful piece of software that package managers can hook into. --- plugins/bootmisc.c | 3 +-- src/Makefile.am | 10 ++++++++-- src/finit.h | 1 + src/tmpfiles.c | 42 +++++++++++++++++++++++++++++++++++++++--- src/tmpfiles.h | 42 ------------------------------------------ 5 files changed, 49 insertions(+), 49 deletions(-) delete mode 100644 src/tmpfiles.h diff --git a/plugins/bootmisc.c b/plugins/bootmisc.c index e0879fb2..ba4f07af 100644 --- a/plugins/bootmisc.c +++ b/plugins/bootmisc.c @@ -38,7 +38,6 @@ #include "finit.h" #include "helpers.h" #include "plugin.h" -#include "tmpfiles.h" #include "util.h" #include "utmp-api.h" @@ -163,7 +162,7 @@ static void setup(void *arg) kernel_links(); /* Create all system tmpfiles.d(5) */ - tmpfilesd(); + run_interactive(_PATH_TMPFILES, "Creating required directories and files"); /* Set BOOT_TIME UTMP entry */ utmp_set_boot(); diff --git a/src/Makefile.am b/src/Makefile.am index ff3aca05..7f911c9a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -8,7 +8,7 @@ AM_LDFLAGS = -export-dynamic endif sbin_PROGRAMS = finit initctl -pkglibexec_PROGRAMS = getty logit runparts +pkglibexec_PROGRAMS = getty logit runparts tmpfiles if SULOGIN pkglibexec_PROGRAMS += sulogin endif @@ -43,6 +43,13 @@ sulogin_SOURCES = sulogin.c sulogin_CFLAGS = -W -Wall -Wextra -std=gnu99 sulogin_LDADD = -lcrypt +tmpfiles_SOURCES = tmpfiles.c helpers.c util.c exec.c log.c \ + svc.c sig.c sm.c api.c mount.c service.c schedule.c cond.c cond-w.c pid.c plugin.c \ + utmp-api.c client.c cgroup.c mdadm.c tty.c iwatch.c conf.c devmon.c logrotate.c +tmpfiles_CFLAGS = -W -Wall -Wextra -std=gnu99 -D__FINIT__ +tmpfiles_CFLAGS += $(lite_CFLAGS) $(uev_CFLAGS) +tmpfiles_LDADD = $(lite_LIBS) $(uev_LIBS) + logit_SOURCES = logit.c logrotate.c logit_CFLAGS = -W -Wall -Wextra -Wno-unused-parameter -std=gnu99 logit_CFLAGS += $(lite_CFLAGS) @@ -66,7 +73,6 @@ finit_SOURCES = api.c cgroup.c cgroup.h \ sig.c sig.h \ sm.c sm.h \ svc.c svc.h \ - tmpfiles.c tmpfiles.h \ tty.c tty.h \ util.c util.h \ utmp-api.c utmp-api.h diff --git a/src/finit.h b/src/finit.h index b536f887..04ca5597 100644 --- a/src/finit.h +++ b/src/finit.h @@ -59,6 +59,7 @@ #define _PATH_RUNPARTS FINIT_EXECPATH_ "/runparts" #define _PATH_SULOGIN FINIT_EXECPATH_ "/sulogin" #define _PATH_GETTY FINIT_EXECPATH_ "/getty" +#define _PATH_TMPFILES FINIT_EXECPATH_ "/tmpfiles" #define CMD_SIZE 1024 #define LINE_SIZE 1024 diff --git a/src/tmpfiles.c b/src/tmpfiles.c index 671d6838..62e7d97d 100644 --- a/src/tmpfiles.c +++ b/src/tmpfiles.c @@ -23,12 +23,48 @@ #include "config.h" /* Generated by configure script */ +#include +#include +#include +#include +#include +#include #include -#include "finit.h" + +#include +#include +#ifdef _LIBITE_LITE +# include +#else +# include +#endif + +#include "svc.h" #include "helpers.h" -#include "tmpfiles.h" +#include "log.h" #include "util.h" +int debug; +int runlevel; +char *osheading; +svc_t *wdog; +uev_ctx_t *ctx; +int syncsec; +int rescue; +char *network; +int cfglevel; +char *finit_rcsd; +char *sdown; +char *hostname; +int bootstrap; +int single; +int readiness; +char *finit_conf; +char *fstab; +int prevlevel; +int cmdlevel; +int kerndebug; + static int glob_do(const char *path, int (*cb)(const char *)) { int rc = 0; @@ -470,7 +506,7 @@ static void tmpfiles(char *line) * before that to have Finit specific ones sorted first, and * a configure prefix specific one after that for user needs. */ -void tmpfilesd(void) +int main(int argc, char *argv[]) { /* in priority order */ char *dir[] = { diff --git a/src/tmpfiles.h b/src/tmpfiles.h deleted file mode 100644 index 440274bd..00000000 --- a/src/tmpfiles.h +++ /dev/null @@ -1,42 +0,0 @@ -/* Limited tmpfiles.d implementation - * - * Copyright (c) 2024 Joachim Wiberg - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#ifndef TMPFILES_H_ -#define TMPFILES_H_ - -#include -#include -#include -#include - -#include -#include -#ifdef _LIBITE_LITE -# include -#else -# include -#endif - -void tmpfilesd(void); - -#endif /* TMPFILES_H_ */ From ff95ebeb910ad826dc0e1296039446357e666e26 Mon Sep 17 00:00:00 2001 From: Aaron Andersen Date: Sun, 1 Jun 2025 18:54:54 -0400 Subject: [PATCH 2/5] tmpfiles: split up create & remove logic The tmpfiles.d spec contains entry types that should be acted upon in different modes of operation: create, clean, remove, and purge - split up create & remove logic to clarify the modes of operation finit supports. --- src/tmpfiles.c | 42 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/src/tmpfiles.c b/src/tmpfiles.c index 62e7d97d..a6de512d 100644 --- a/src/tmpfiles.c +++ b/src/tmpfiles.c @@ -315,6 +315,43 @@ static void tmpfiles(char *line) strc = stat(path, &st); + // file and directory removal logic + switch (type[0]) { + case 'b': + case 'c': + case 'C': + case 'd': + case 'D': + case 'e': + case 'f': + case 'F': + case 'l': /* Finit extension, like 'L' but only if target exists */ + case 'L': + case 'p': + break; + case 'r': + rc = glob_do(path, erase); + if (rc && errno == ENOENT) + rc = 0; + break; + case 'R': + rc = glob_do(path, rmrf); + break; + case 'w': + break; + case 'X': + case 'x': + dbg("Unsupported x/X command, ignoring %s, no support for clean at runtime.", path); + break; + case 'Z': + case 'z': + break; + default: + errx(1, "Unsupported tmpfiles command '%s'", type); + return; + } + + // file & directory creation logic switch (type[0]) { case 'b': rc = parse_mm(arg, &major, &minor); @@ -448,12 +485,7 @@ static void tmpfiles(char *line) rc = mkfifo(path, mode ?: 0644); break; case 'r': - rc = glob_do(path, erase); - if (rc && errno == ENOENT) - rc = 0; - break; case 'R': - rc = glob_do(path, rmrf); break; case 'w': if (!arg) From a5710125f6ade92f147097784e094e0dc1b047dd Mon Sep 17 00:00:00 2001 From: Aaron Andersen Date: Sun, 1 Jun 2025 19:07:01 -0400 Subject: [PATCH 3/5] tmpfiles: add --create and --remove flags Allow execution of tmpfiles to specify which mode(s) of operation to run in. --- plugins/bootmisc.c | 2 +- src/tmpfiles.c | 452 +++++++++++++++++++++++++-------------------- 2 files changed, 255 insertions(+), 199 deletions(-) diff --git a/plugins/bootmisc.c b/plugins/bootmisc.c index ba4f07af..1084e115 100644 --- a/plugins/bootmisc.c +++ b/plugins/bootmisc.c @@ -162,7 +162,7 @@ static void setup(void *arg) kernel_links(); /* Create all system tmpfiles.d(5) */ - run_interactive(_PATH_TMPFILES, "Creating required directories and files"); + run_interactive(_PATH_TMPFILES " --create", "Creating required directories and files"); /* Set BOOT_TIME UTMP entry */ utmp_set_boot(); diff --git a/src/tmpfiles.c b/src/tmpfiles.c index a6de512d..49860762 100644 --- a/src/tmpfiles.c +++ b/src/tmpfiles.c @@ -25,6 +25,7 @@ #include #include +#include #include #include #include @@ -65,6 +66,9 @@ int prevlevel; int cmdlevel; int kerndebug; +int c_flag = 0; +int r_flag = 0; + static int glob_do(const char *path, int (*cb)(const char *)) { int rc = 0; @@ -316,213 +320,217 @@ static void tmpfiles(char *line) strc = stat(path, &st); // file and directory removal logic - switch (type[0]) { - case 'b': - case 'c': - case 'C': - case 'd': - case 'D': - case 'e': - case 'f': - case 'F': - case 'l': /* Finit extension, like 'L' but only if target exists */ - case 'L': - case 'p': - break; - case 'r': - rc = glob_do(path, erase); - if (rc && errno == ENOENT) - rc = 0; - break; - case 'R': - rc = glob_do(path, rmrf); - break; - case 'w': - break; - case 'X': - case 'x': - dbg("Unsupported x/X command, ignoring %s, no support for clean at runtime.", path); - break; - case 'Z': - case 'z': - break; - default: - errx(1, "Unsupported tmpfiles command '%s'", type); - return; + if (r_flag) { + switch (type[0]) { + case 'b': + case 'c': + case 'C': + case 'd': + case 'D': + case 'e': + case 'f': + case 'F': + case 'l': /* Finit extension, like 'L' but only if target exists */ + case 'L': + case 'p': + break; + case 'r': + rc = glob_do(path, erase); + if (rc && errno == ENOENT) + rc = 0; + break; + case 'R': + rc = glob_do(path, rmrf); + break; + case 'w': + break; + case 'X': + case 'x': + dbg("Unsupported x/X command, ignoring %s, no support for clean at runtime.", path); + break; + case 'Z': + case 'z': + break; + default: + errx(1, "Unsupported tmpfiles command '%s'", type); + return; + } } // file & directory creation logic - switch (type[0]) { - case 'b': - rc = parse_mm(arg, &major, &minor); - if (rc) - break; - if (!strc) { - if (type[1] != '+') - break; - erase(path); - } - mkparent(path, 0755); - rc = blkdev(path, mode ?: 0644, major, minor); - break; - case 'c': - rc = parse_mm(arg, &major, &minor); - if (rc) - break; - if (!strc) { - if (type[1] != '+') - break; - erase(path); - } - mkparent(path, 0755); - rc = chardev(path, mode ?: 0644, major, minor); - break; - case 'C': - if (!arg) { - paste(buf, sizeof(buf), "/usr/share/factory", path); - arg = buf; - } - if (!strc) { - struct dirent **namelist; - int num; - - if (!S_ISDIR(st.st_mode)) - break; - num = scandir(path, &namelist, NULL, NULL); - free(namelist); - if (num >= 3) - break; /* not empty */ - } - if (fisdir(arg) && !fisslashdir(arg)) { - size_t len = strlen(arg) + 2; - - dst = malloc(len); - if (!dst) - break; - snprintf(dst, len, "%s/", arg); - arg = dst; - } - mkparent(path, 0755); - rc = rsync(arg, path, LITE_FOPT_KEEP_MTIME, NULL); - if (rc && errno == ENOENT) - rc = 0; - break; - case 'd': - case 'D': - mkparent(path, 0755); - rc = mksubsys(path, mode ?: 0755, user, group); - break; - case 'e': - if (glob(path, GLOB_NOESCAPE, NULL, &gl)) - break; - - for (size_t i = 0; i < gl.gl_pathc; i++) - rc += mksubsys(gl.gl_pathv[i], mode ?: 0755, user, group); - break; - case 'f': - case 'F': - mkparent(path, 0755); - if (type[1] == '+' || type[0] == 'F') { - /* f+/F will create or truncate the file */ - if (!arg) { - rc = create(path, mode ?: 0644, user, group); + if (c_flag) { + switch (type[0]) { + case 'b': + rc = parse_mm(arg, &major, &minor); + if (rc) break; + if (!strc) { + if (type[1] != '+') + break; + erase(path); } - fp = fopen(path, "w+"); - } else { - /* f will create the file if it doesn't exist */ - if (strc) - fp = fopen(path, "w"); - } - - if (fp) { - write_arg(fp, arg); - rc = fclose(fp); - } - break; - case 'l': /* Finit extension, like 'L' but only if target exists */ - if (!arg) { - paste(buf, sizeof(buf), "/usr/share/factory", path); - if (stat(buf, &ast)) - break; - } else if (arg[0] != '/') { - char *tmp; - - tmp = dirname(strdupa(path)); - paste(buf, sizeof(buf), tmp, arg); - dst = realpath(buf, NULL); - if (!dst) - break; - if (stat(dst, &ast)) - break; - } else { - if (stat(arg, &ast)) - break; - } - /* fallthrough */ - case 'L': - if (!strc) { - if (type[1] != '+') - break; - rmrf(path); - } - mkparent(path, 0755); - if (!arg) { - paste(buf, sizeof(buf), "/usr/share/factory", path); - arg = buf; - } - rc = ln(arg, path); - if (rc && errno == EEXIST) - rc = 0; - break; - case 'p': - if (!strc) { - if (type[1] != '+') - break; - erase(path); - } - mkparent(path, 0755); - rc = mkfifo(path, mode ?: 0644); - break; - case 'r': - case 'R': - break; - case 'w': - if (!arg) + mkparent(path, 0755); + rc = blkdev(path, mode ?: 0644, major, minor); break; - - if (glob(path, GLOB_NOESCAPE, NULL, &gl)) + case 'c': + rc = parse_mm(arg, &major, &minor); + if (rc) + break; + if (!strc) { + if (type[1] != '+') + break; + erase(path); + } + mkparent(path, 0755); + rc = chardev(path, mode ?: 0644, major, minor); break; + case 'C': + if (!arg) { + paste(buf, sizeof(buf), "/usr/share/factory", path); + arg = buf; + } + if (!strc) { + struct dirent **namelist; + int num; + + if (!S_ISDIR(st.st_mode)) + break; + num = scandir(path, &namelist, NULL, NULL); + free(namelist); + if (num >= 3) + break; /* not empty */ + } + if (fisdir(arg) && !fisslashdir(arg)) { + size_t len = strlen(arg) + 2; + + dst = malloc(len); + if (!dst) + break; + snprintf(dst, len, "%s/", arg); + arg = dst; + } + mkparent(path, 0755); + rc = rsync(arg, path, LITE_FOPT_KEEP_MTIME, NULL); + if (rc && errno == ENOENT) + rc = 0; + break; + case 'd': + case 'D': + mkparent(path, 0755); + rc = mksubsys(path, mode ?: 0755, user, group); + break; + case 'e': + if (glob(path, GLOB_NOESCAPE, NULL, &gl)) + break; + + for (size_t i = 0; i < gl.gl_pathc; i++) + rc += mksubsys(gl.gl_pathv[i], mode ?: 0755, user, group); + break; + case 'f': + case 'F': + mkparent(path, 0755); + if (type[1] == '+' || type[0] == 'F') { + /* f+/F will create or truncate the file */ + if (!arg) { + rc = create(path, mode ?: 0644, user, group); + break; + } + fp = fopen(path, "w+"); + } else { + /* f will create the file if it doesn't exist */ + if (strc) + fp = fopen(path, "w"); + } - for (size_t i = 0; i < gl.gl_pathc; i++) { - fp = fopen(gl.gl_pathv[i], type[1] == '+' ? "a" : "w"); if (fp) { write_arg(fp, arg); rc = fclose(fp); } - } - break; - case 'X': - case 'x': - dbg("Unsupported x/X command, ignoring %s, no support for clean at runtime.", path); - break; - case 'Z': - opts = "-R"; - /* fallthrough */ - case 'z': - if (!whichp("restorecon")) - break; - if (glob(path, GLOB_NOESCAPE, NULL, &gl)) break; + case 'l': /* Finit extension, like 'L' but only if target exists */ + if (!arg) { + paste(buf, sizeof(buf), "/usr/share/factory", path); + if (stat(buf, &ast)) + break; + } else if (arg[0] != '/') { + char *tmp; - for (size_t i = 0; i < gl.gl_pathc; i++) { - snprintf(buf, sizeof(buf), "restorecon %s %s", opts, gl.gl_pathv[i]); - run(buf, "restorecon"); + tmp = dirname(strdupa(path)); + paste(buf, sizeof(buf), tmp, arg); + dst = realpath(buf, NULL); + if (!dst) + break; + if (stat(dst, &ast)) + break; + } else { + if (stat(arg, &ast)) + break; + } + /* fallthrough */ + case 'L': + if (!strc) { + if (type[1] != '+') + break; + rmrf(path); + } + mkparent(path, 0755); + if (!arg) { + paste(buf, sizeof(buf), "/usr/share/factory", path); + arg = buf; + } + rc = ln(arg, path); + if (rc && errno == EEXIST) + rc = 0; + break; + case 'p': + if (!strc) { + if (type[1] != '+') + break; + erase(path); + } + mkparent(path, 0755); + rc = mkfifo(path, mode ?: 0644); + break; + case 'r': + case 'R': + break; + case 'w': + if (!arg) + break; + + if (glob(path, GLOB_NOESCAPE, NULL, &gl)) + break; + + for (size_t i = 0; i < gl.gl_pathc; i++) { + fp = fopen(gl.gl_pathv[i], type[1] == '+' ? "a" : "w"); + if (fp) { + write_arg(fp, arg); + rc = fclose(fp); + } + } + break; + case 'X': + case 'x': + dbg("Unsupported x/X command, ignoring %s, no support for clean at runtime.", path); + break; + case 'Z': + opts = "-R"; + /* fallthrough */ + case 'z': + if (!whichp("restorecon")) + break; + if (glob(path, GLOB_NOESCAPE, NULL, &gl)) + break; + + for (size_t i = 0; i < gl.gl_pathc; i++) { + snprintf(buf, sizeof(buf), "restorecon %s %s", opts, gl.gl_pathv[i]); + run(buf, "restorecon"); + } + break; + default: + errx(1, "Unsupported tmpfiles command '%s'", type); + return; } - break; - default: - errx(1, "Unsupported tmpfiles command '%s'", type); - return; } if (dst) @@ -532,14 +540,62 @@ static void tmpfiles(char *line) warn("Failed %s operation on path %s", type, path); } -/* - * Only the three last tmpfiles.d/ directories are defined in - * tmpfiles.d(5) as system search paths. Finit adds two more - * before that to have Finit specific ones sorted first, and - * a configure prefix specific one after that for user needs. - */ +static int usage(int rc) +{ + fprintf(stderr, + "Usage: tmpfiles [COMMAND...]\n" + "\n" + "Commands:\n" + " -c, --create Create files and directories\n" + " -r, --remove Remove files and directories marked for removal\n" + " -h, --help This help text\n" + "\n"); + + return rc; +} + int main(int argc, char *argv[]) { + struct option long_options[] = { + { "create", 0, NULL, 'c' }, + { "remove", 0, NULL, 'r' }, + { "help", 0, NULL, 'h' }, + { NULL, 0, NULL, 0 } + }; + + int c; + + while ((c = getopt_long(argc, argv, "crh?", long_options, NULL)) != EOF) { + switch(c) { + case 'c': + c_flag = 1; + break; + + case 'r': + r_flag = 1; + break; + + case 'h': + case '?': + return usage(0); + + default: + return usage(1); + } + } + + if (c_flag + r_flag == 0) { + fprintf(stderr, "You need to specify at least one of --create or --remove.\n"); + return 1; + } + + /* + * Only the three last tmpfiles.d/ directories are defined in + * tmpfiles.d(5) as system search paths. Finit adds two more + * before that to have Finit specific ones sorted first, and + * a configure prefix specific one after that for user needs. + */ + /* in priority order */ char *dir[] = { FINIT_TMPFILES "/*.conf", From 2957246092abb5ceb730163a81f3ff226687633c Mon Sep 17 00:00:00 2001 From: Aaron Andersen Date: Sun, 1 Jun 2025 19:24:49 -0400 Subject: [PATCH 4/5] tmpfiles: implement remove logic for 'D' > D > Similar to d, but in addition the contents of the directory will be removed when --remove is used. --- src/tmpfiles.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/tmpfiles.c b/src/tmpfiles.c index 49860762..b8365f5c 100644 --- a/src/tmpfiles.c +++ b/src/tmpfiles.c @@ -326,7 +326,12 @@ static void tmpfiles(char *line) case 'c': case 'C': case 'd': + break; case 'D': + if (fisdir(path)) { + nftw(path, do_delete, 20, FTW_DEPTH | FTW_PHYS); + } + break; case 'e': case 'f': case 'F': From 5993b4bb5d4edf16fb27594c2da630ba343d32ae Mon Sep 17 00:00:00 2001 From: Aaron Andersen Date: Thu, 5 Jun 2025 20:53:25 -0400 Subject: [PATCH 5/5] tmpfiles: refactor code into new is_dir_empty function The existing implementation seemed useful enough to warrant a new helper function. Care was taken to ensure the revised code no longer suffers any memory leaks. --- src/helpers.h | 17 +++++++++++++++++ src/tmpfiles.c | 14 ++------------ 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/helpers.h b/src/helpers.h index 247a1e1b..25758da0 100644 --- a/src/helpers.h +++ b/src/helpers.h @@ -26,6 +26,7 @@ #define FINIT_HELPERS_H_ #include +#include #include #include #include @@ -206,6 +207,22 @@ static inline char *fgetval(const char *line, const char *key, char *sep) return realloc(copy, len); } +static inline int is_dir_empty(const char *path) +{ + struct dirent **namelist; + int num; + + num = scandir(path, &namelist, NULL, NULL); + if (num < 0) + return 0; + + for (int i = 0; i < num; i++) + free(namelist[i]); + free(namelist); + + return num >= 3; +} + #endif /* FINIT_HELPERS_H_ */ /** diff --git a/src/tmpfiles.c b/src/tmpfiles.c index b8365f5c..8bd0d34c 100644 --- a/src/tmpfiles.c +++ b/src/tmpfiles.c @@ -23,7 +23,6 @@ #include "config.h" /* Generated by configure script */ -#include #include #include #include @@ -394,17 +393,8 @@ static void tmpfiles(char *line) paste(buf, sizeof(buf), "/usr/share/factory", path); arg = buf; } - if (!strc) { - struct dirent **namelist; - int num; - - if (!S_ISDIR(st.st_mode)) - break; - num = scandir(path, &namelist, NULL, NULL); - free(namelist); - if (num >= 3) - break; /* not empty */ - } + if (fisdir(path) && !is_dir_empty(path)) + break; if (fisdir(arg) && !fisslashdir(arg)) { size_t len = strlen(arg) + 2;