core: add [State|Runtime|Cache|Logs]Directory symlink as second parameter

When combined with a tmpfs on /run or /var/lib, allows to create
arbitrary and ephemeral symlinks for StateDirectory or RuntimeDirectory.
This is especially useful when sharing these directories between
different services, to make the same state/runtime directory 'backend'
appear as different names to each service, so that they can be added/removed
to a sharing agreement transparently, without code changes.

An example (simplified, but real) use case:

foo.service:
StateDirectory=foo

bar.service:
StateDirectory=bar

foo.service.d/shared.conf:
StateDirectory=
StateDirectory=shared:foo

bar.service.d/shared.conf:
StateDirectory=
StateDirectory=shared:bar

foo and bar use respectively /var/lib/foo and /var/lib/bar. Then
the orchestration layer decides to stop this sharing, the drop-in
can be removed. The services won't need any update and will keep
working and being able to store state, transparently.

To keep backward compatibility, new DBUS messages are added.
This commit is contained in:
Luca Boccassi
2021-10-28 10:47:46 +01:00
parent df61e79a5d
commit 211a3d87fb
11 changed files with 713 additions and 157 deletions
+74 -30
View File
@@ -6,45 +6,89 @@ set -o pipefail
systemd-analyze log-level debug
systemd-analyze log-target console
# Set everything up without DynamicUser=1
function test_directory() {
local directory="$1"
local path="$2"
systemd-run --wait -p DynamicUser=0 -p StateDirectory=zzz touch /var/lib/zzz/test
systemd-run --wait -p DynamicUser=0 -p StateDirectory=zzz test -f /var/lib/zzz/test
systemd-run --wait -p DynamicUser=0 -p StateDirectory=zzz -p TemporaryFileSystem=/var/lib test -f /var/lib/zzz/test
systemd-run --wait -p DynamicUser=0 -p StateDirectory=zzz test -f /var/lib/zzz/test-missing \
&& { echo 'unexpected success'; exit 1; }
# Set everything up without DynamicUser=1
test -d /var/lib/zzz
test ! -L /var/lib/zzz
test ! -e /var/lib/private/zzz
test -f /var/lib/zzz/test
test ! -f /var/lib/zzz/test-missing
systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz touch "${path}"/zzz/test
systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz test -f "${path}"/zzz/test
systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz -p TemporaryFileSystem="${path}" test -f "${path}"/zzz/test
systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz:yyy test -f "${path}"/yyy/test
systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz:xxx -p TemporaryFileSystem="${path}" test -f "${path}"/xxx/test
systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz:xxx -p TemporaryFileSystem="${path}":ro test -f "${path}"/xxx/test
systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz test -f "${path}"/zzz/test-missing \
&& { echo 'unexpected success'; exit 1; }
# Convert to DynamicUser=1
test -d "${path}"/zzz
test ! -L "${path}"/zzz
test ! -e "${path}"/private/zzz
test -f "${path}"/zzz/test
test ! -f "${path}"/zzz/test-missing
systemd-run --wait -p DynamicUser=1 -p StateDirectory=zzz test -f /var/lib/zzz/test
systemd-run --wait -p DynamicUser=1 -p StateDirectory=zzz -p TemporaryFileSystem=/var/lib test -f /var/lib/zzz/test
systemd-run --wait -p DynamicUser=1 -p StateDirectory=zzz test -f /var/lib/zzz/test-missing \
&& { echo 'unexpected success'; exit 1; }
# Convert to DynamicUser=1
test -L /var/lib/zzz
test -d /var/lib/private/zzz
systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=1 -p "${directory}"=zzz test -f "${path}"/zzz/test
systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=1 -p "${directory}"=zzz -p TemporaryFileSystem="${path}" test -f "${path}"/zzz/test
systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=1 -p "${directory}"=zzz:yyy test -f "${path}"/yyy/test
systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=1 -p "${directory}"=zzz:xxx -p TemporaryFileSystem="${path}" test -f "${path}"/xxx/test
systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=1 -p "${directory}"=zzz:xxx -p TemporaryFileSystem="${path}":ro test -f "${path}"/xxx/test
systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=1 -p "${directory}"=zzz test -f "${path}"/zzz/test-missing \
&& { echo 'unexpected success'; exit 1; }
test -f /var/lib/zzz/test
test ! -f /var/lib/zzz/test-missing
test -L "${path}"/zzz
test -L "${path}"/yyy
test -d "${path}"/private/zzz
test ! -L "${path}"/private/xxx
test ! -L "${path}"/xxx
# Convert back
test -f "${path}"/zzz/test
test ! -f "${path}"/zzz/test-missing
systemd-run --wait -p DynamicUser=0 -p StateDirectory=zzz test -f /var/lib/zzz/test
systemd-run --wait -p DynamicUser=0 -p StateDirectory=zzz -p TemporaryFileSystem=/var/lib test -f /var/lib/zzz/test
systemd-run --wait -p DynamicUser=0 -p StateDirectory=zzz test -f /var/lib/zzz/test-missing \
&& { echo 'unexpected success'; exit 1; }
# Convert back
test -d /var/lib/zzz
test ! -L /var/lib/zzz
test ! -e /var/lib/private/zzz
test -f /var/lib/zzz/test
test ! -f /var/lib/zzz/test-missing
systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz test -f "${path}"/zzz/test
systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz -p TemporaryFileSystem="${path}" test -f "${path}"/zzz/test
systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz:yyy test -f "${path}"/yyy/test
systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz:xxx -p TemporaryFileSystem="${path}" test -f "${path}"/xxx/test
systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}=zzz:xxx zzz:xxx2" -p TemporaryFileSystem="${path}" bash -c "test -f ${path}/xxx/test && test -f ${path}/xxx2/test"
systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz:xxx -p TemporaryFileSystem="${path}":ro test -f "${path}"/xxx/test
systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz test -f "${path}"/zzz/test-missing \
&& { echo 'unexpected success'; exit 1; }
# Exercise the unit parsing paths too
cat >/run/systemd/system/testservice-34.service <<EOF
[Service]
Type=oneshot
TemporaryFileSystem=${path}
RuntimeDirectoryPreserve=yes
${directory}=zzz:x\:yz zzz:x\:yz2
ExecStart=test -f ${path}/x:yz2/test
ExecStart=test -f ${path}/x:yz/test
ExecStart=test -f ${path}/zzz/test
EOF
systemctl daemon-reload
systemctl start --wait testservice-34.service
test -d "${path}"/zzz
test ! -L "${path}"/xxx
test ! -L "${path}"/xxx2
test ! -L "${path}"/private/xxx
test ! -L "${path}"/private/xxx2
test -L "${path}"/yyy
test ! -L "${path}"/zzz
test ! -e "${path}"/private/zzz
test -f "${path}"/zzz/test
test ! -f "${path}"/zzz/test-missing
test ! -L "${path}"/x:yz
test ! -L "${path}"/x:yz2
}
test_directory "StateDirectory" "/var/lib"
test_directory "RuntimeDirectory" "/run"
test_directory "CacheDirectory" "/var/cache"
test_directory "LogsDirectory" "/var/log"
systemd-analyze log-level info