Fix #492: add per-service directories, systemd RuntimeDirectory style

A service that drops privileges cannot create its own PID file in
/run, root owns it.  Finit can create the file with pidfile-create,
but the daemon still cannot touch it to confirm a SIGHUP.

Five new settings, block format only: runtime-dir, state-dir,
cache-dir, logs-dir, and config-dir.  The value is a directory name,
resolved under /run, /var/lib, /var/cache, /var/log, and /etc,
respectively.  The directory is created before the service starts,
mode 0755 owned by user/group, and the full path is exported to the
process as RUNTIME_DIRECTORY, STATE_DIRECTORY, CACHE_DIRECTORY,
LOGS_DIRECTORY, and CONFIGURATION_DIRECTORY.  Mode and ownership are
asserted at creation only, a daemon may tighten them afterwards.

The runtime directory is removed when the unit stops, after any
exec-stop-post script, like systemd with RuntimeDirectoryPreserve=no.
A completed run/task counts as stopped unless remain-after-exit keeps
it up.  The other four persist across restarts.

These are the first settings with no legacy token: they are validated
by service_set_dir() and stored on the svc that service_register()
now returns.  systemd accepts a list of directories per setting; this
is a single name for now, widening later is compatible since
libconfuse accepts a bare value for a list option.

The test sysroot gains libnss_files.so.2, which ldd cannot see, glibc
dlopen()s it.  Without it getpwnam() fails inside the chroot, so
user/group settings never resolved and directory ownership could not
be tested.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
Joachim Wiberg committed 2026-07-30 15:23:37 +02:00
1 parent 7f8a64dd39
commit f0d7257374
8 files changed
+299 -2

No files matched your search

+35 -1
View File
@@ -269,6 +269,11 @@ static cfg_opt_t svc_opts[] = {
CFG_STR_LIST("capabilities", NULL, CFGF_NODEFAULT),
CFG_STR_LIST("caps", NULL, CFGF_NODEFAULT), /* alias */
CFG_STR ("runtime-dir", NULL, CFGF_NODEFAULT),
CFG_STR ("state-dir", NULL, CFGF_NODEFAULT),
CFG_STR ("cache-dir", NULL, CFGF_NODEFAULT),
CFG_STR ("logs-dir", NULL, CFGF_NODEFAULT),
CFG_STR ("config-dir", NULL, CFGF_NODEFAULT),
CFG_STR_LIST("conflicts", NULL, CFGF_NODEFAULT),
CFG_STR ("if", NULL, CFGF_NODEFAULT),
CFG_STR ("tty", NULL, CFGF_NODEFAULT),
@@ -1289,6 +1294,30 @@ static int if_translate(const char *str, char *buf, size_t len, char *file, cons
return 0;
}
/*
* These have no legacy token, they are validated by service_set_dir()
* and stored directly on the registered svc. Empty means unset,
* service_register() has already cleared the fields.
*/
static void dirs_translate(cfg_t *sec, svc_t *svc, char *file)
{
int i;
for (i = 0; i < NUM_SVCDIRS; i++) {
const char *str;
str = sec_getstr(sec, svcdirs[i].key, NULL);
if (!str || !str[0])
continue;
if (service_set_dir(svc, &svcdirs[i], str))
logit(LOG_ERR, "%s: %s: %s '%s' %s, ignoring",
file, cfg_title(sec), svcdirs[i].key, str,
errno == ENAMETOOLONG ? "is too long"
: "must be relative, no '..'");
}
}
static void svc_translate(cfg_t *sec, int type, struct rlimit rlimit[], char *file)
{
struct rlimit local_rlimit[RLIMIT_NLIMITS];
@@ -1299,6 +1328,7 @@ static void svc_translate(cfg_t *sec, int type, struct rlimit rlimit[], char *fi
long num;
char buf[512];
char nm[80];
svc_t *svc;
char *id;
cmd = sec_getstr(sec, "command", NULL);
@@ -1505,7 +1535,11 @@ static void svc_translate(cfg_t *sec, int type, struct rlimit rlimit[], char *fi
addtok(line, sizeof(line), "-- %s", str);
dbg("translated: %s", line);
service_register(type, line, rlimit, file);
svc = service_register(type, line, rlimit, file);
if (!svc)
return;
dirs_translate(sec, svc, file);
}
/*