conf: adopt the systemd semantics for per-service directories

Aaron Andersen points out in the #492 discussion that the *Directory
settings carry more contract than create-and-chown: per-directory
modes, specific ownership rules, and cleanup toggles.  Without them
config-dir was chowned to the service user, which systemd never does,
an existing directory with drifted ownership was left wrong, and the
runtime directory could not survive a restart.

Now matching systemd.exec(5), and where the man page is vague, the
code in setup_exec_directory():

  - each directory takes a matching -mode key, octal with the leading
    zero, default 0755.  The mode of the named directory is locked
    down again on every start, also when it already exists
  - config-dir is created but never chowned
  - the contents of an existing directory are left alone as long as
    the owner is right; on drift everything under it is chowned back
  - runtime-dir-preserve = no | restart | yes maps
    RuntimeDirectoryPreserve=.  A service still qualified to run when
    the runtime directory would be removed is restarting, not
    stopping, which is what svc_enabled() answers

The dir mechanics move to mksubsysd(), taking resolved ids, with
mksubsys() reduced to a name-resolving wrapper for the dbus plugin.
The child resolves uid/gid once for both directory setup and
privilege drop.

The symlink form, RuntimeDirectory=foo:bar, is not adopted.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
Joachim Wiberg
2026-07-30 15:23:38 +02:00
parent f0d7257374
commit 6b03a1ec8f
8 changed files with 191 additions and 44 deletions
+40
View File
@@ -28,6 +28,15 @@ service escape {
runlevel = \"S12345\"
runtime-dir = \"../escape\"
command = \"serv -np -i escape\"
}
service modes {
runlevel = \"S12345\"
user = \"daemon\"
runtime-dir = \"modes\"
runtime-dir-mode = 0700
runtime-dir-preserve = \"restart\"
config-dir = \"modes\"
command = \"/sbin/serv -np -P /run/modes/serv.pid -i modes\"
}"
# ls output is empty for an empty directory, so test -d instead
@@ -78,3 +87,34 @@ assert_dir /run/owned
say 'A path escaping the base directory is refused, service still runs'
retry 'assert_status escape running'
assert_nodir /escape
say 'runtime-dir-mode sets the mode, config-dir is never chowned'
retry 'assert_status modes running'
assert "mode is 0700" "$(texec stat -c %a /run/modes)" = "700"
assert_owner /run/modes daemon:root
assert_owner /etc/modes root:root
say 'runtime-dir-preserve restart keeps the directory across a restart'
run "touch /run/modes/keepsake" || texec touch /run/modes/keepsake
run "initctl restart modes"
retry 'assert_status modes running'
assert_file_exists /run/modes/keepsake
say 'but a real stop still removes it'
run "initctl stop modes"
retry 'assert_status modes stopped'
assert_nodir /run/modes
say 'An existing directory with the wrong owner is chowned back, and'
say 'its mode is locked down again'
run "initctl stop owned"
retry 'assert_status owned stopped'
texec mkdir -p /var/lib/owned/sub
texec touch /var/lib/owned/sub/file
texec chown -R 0:0 /var/lib/owned
texec chmod 0700 /var/lib/owned
run "initctl start owned"
retry 'assert_status owned running'
assert "mode locked down to 0755" "$(texec stat -c %a /var/lib/owned)" = "755"
assert_owner /var/lib/owned daemon:daemon
assert_owner /var/lib/owned/sub/file daemon:daemon