mirror of
https://github.com/systemd/systemd.git
synced 2026-11-06 05:17:24 +00:00
test: split TEST-74-AUX-UTILS.varlink-unit-* coverage tests into focused testcases
Separate EnqueueJob and StartTransient coverage into subtests and use run_testcases for independent fixtures and cleanup.
This commit is contained in:
1 parent
39379e3a2a
commit
4705e24b4e
3 files changed
+949
-799
No files matched your search
+135
@@ -0,0 +1,135 @@
|
||||
#!/usr/bin/env bash
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
set -eux
|
||||
set -o pipefail
|
||||
|
||||
# shellcheck source=test/units/test-control.sh
|
||||
. "$(dirname "$0")"/test-control.sh
|
||||
|
||||
at_exit() {
|
||||
systemctl stop varlink-test-enqueue{,-2}.service varlink-test-slow.service 2>/dev/null || true
|
||||
if [[ -n ${watcher_pid:-} ]]; then
|
||||
kill "$watcher_pid" 2>/dev/null || true
|
||||
wait "$watcher_pid" 2>/dev/null || true
|
||||
fi
|
||||
rm -f /run/systemd/system/varlink-test-enqueue{,-2}.service /run/systemd/system/varlink-test-slow.service
|
||||
rm -f /tmp/enqueue-notifications.json
|
||||
systemctl daemon-reload
|
||||
}
|
||||
|
||||
testcase_transactions() {
|
||||
trap at_exit EXIT
|
||||
|
||||
for unit in varlink-test-enqueue{,-2}.service; do
|
||||
cat >"/run/systemd/system/$unit" <<UNIT
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=true
|
||||
RemainAfterExit=yes
|
||||
UNIT
|
||||
done
|
||||
systemctl daemon-reload
|
||||
|
||||
# Non-streaming start with an explicit job mode.
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.EnqueueJob '{"names": ["varlink-test-enqueue.service"], "jobType": "start", "mode": "fail"}' | jq -e '.context and .runtime and .job.Id and .job.JobType == "start"'
|
||||
timeout 10 bash -c 'until systemctl is-active varlink-test-enqueue.service; do sleep 0.5; done'
|
||||
|
||||
# Streaming restart waits for completion.
|
||||
varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.EnqueueJob '{"names": ["varlink-test-enqueue.service"], "jobType": "restart", "notifyJobChanges": true}' | jq -e 'length > 1 and .[0].job.JobType == "restart" and .[-1].job.Result == "done" and .[-1].context and .[-1].runtime'
|
||||
systemctl is-active varlink-test-enqueue.service
|
||||
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.EnqueueJob '{"names": ["varlink-test-enqueue.service"], "jobType": "stop"}' | jq -e '.job.JobType == "stop"'
|
||||
timeout 10 bash -c 'until systemctl show -P ActiveState varlink-test-enqueue.service | grep inactive >/dev/null; do sleep 0.5; done'
|
||||
|
||||
# Multiple units share a transaction and receive one reply per job.
|
||||
varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.EnqueueJob '{"names": ["varlink-test-enqueue.service", "varlink-test-enqueue-2.service"], "jobType": "start"}' | jq -e 'length == 2 and all(.[]; .context and .runtime and .job.Id) and ([.[].job.Unit] | sort == ["varlink-test-enqueue-2.service", "varlink-test-enqueue.service"])'
|
||||
timeout 10 bash -c 'until systemctl is-active varlink-test-enqueue.service; do sleep 0.5; done'
|
||||
timeout 10 bash -c 'until systemctl is-active varlink-test-enqueue-2.service; do sleep 0.5; done'
|
||||
systemctl stop varlink-test-enqueue{,-2}.service
|
||||
|
||||
# Multiple replies require 'more'.
|
||||
(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.EnqueueJob '{"names": ["varlink-test-enqueue.service", "varlink-test-enqueue-2.service"], "jobType": "start"}')
|
||||
|
||||
# A missing unit rejects the entire transaction.
|
||||
(! varlinkctl call --more /run/systemd/io.systemd.Manager io.systemd.Unit.EnqueueJob '{"names": ["varlink-test-enqueue.service", "non-existent.service"], "jobType": "start"}')
|
||||
(! systemctl is-active varlink-test-enqueue.service)
|
||||
|
||||
# Notifications require 'more' and a single unit.
|
||||
(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.EnqueueJob '{"names": ["varlink-test-enqueue.service"], "jobType": "start", "notifyJobChanges": true}')
|
||||
(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.EnqueueJob '{"names": ["varlink-test-enqueue.service"], "jobType": "start", "notifyUnitChanges": true}')
|
||||
|
||||
(! varlinkctl call --more /run/systemd/io.systemd.Manager io.systemd.Unit.EnqueueJob '{"names": ["varlink-test-enqueue.service", "varlink-test-enqueue-2.service"], "jobType": "start", "notifyJobChanges": true}')
|
||||
|
||||
# Error cases
|
||||
(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.EnqueueJob '{"names": ["invalid-unit-name"], "jobType": "start"}')
|
||||
(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.EnqueueJob '{"names": [], "jobType": "start"}')
|
||||
}
|
||||
|
||||
testcase_coalesced_watcher() {
|
||||
trap at_exit EXIT
|
||||
|
||||
# Coalesced jobs must reject a second watcher.
|
||||
cat >/run/systemd/system/varlink-test-slow.service <<UNIT
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/bin/sleep infinity
|
||||
RemainAfterExit=yes
|
||||
UNIT
|
||||
systemctl daemon-reload
|
||||
|
||||
varlinkctl call --more /run/systemd/io.systemd.Manager io.systemd.Unit.EnqueueJob \
|
||||
'{"names": ["varlink-test-slow.service"], "jobType": "start", "notifyJobChanges": true}' >/dev/null 2>&1 &
|
||||
watcher_pid=$!
|
||||
|
||||
# Wait for the first watcher to attach.
|
||||
timeout 10 bash -c 'until systemctl show -P ActiveState varlink-test-slow.service | grep activating >/dev/null; do sleep 0.5; done'
|
||||
|
||||
set +o pipefail
|
||||
varlinkctl call --more /run/systemd/io.systemd.Manager io.systemd.Unit.EnqueueJob \
|
||||
'{"names": ["varlink-test-slow.service"], "jobType": "start", "notifyJobChanges": true}' |& grep "io.systemd.Unit.AlreadyBeingWatched"
|
||||
set -o pipefail
|
||||
|
||||
systemctl stop varlink-test-slow.service
|
||||
wait "$watcher_pid" 2>/dev/null || true
|
||||
watcher_pid=
|
||||
}
|
||||
|
||||
testcase_nop_subscription() {
|
||||
trap at_exit EXIT
|
||||
|
||||
cat >/run/systemd/system/varlink-test-slow.service <<UNIT
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/bin/sleep infinity
|
||||
RemainAfterExit=yes
|
||||
UNIT
|
||||
systemctl daemon-reload
|
||||
|
||||
# Completing a NOP job must preserve the regular job's unit subscription.
|
||||
varlinkctl --json=short call --more /run/systemd/io.systemd.Manager io.systemd.Unit.EnqueueJob \
|
||||
'{"names": ["varlink-test-slow.service"], "jobType": "start", "notifyUnitChanges": true}' >/tmp/enqueue-notifications.json &
|
||||
watcher_pid=$!
|
||||
|
||||
timeout 10 bash -c 'until systemctl show -P ActiveState varlink-test-slow.service | grep activating >/dev/null; do sleep 0.5; done'
|
||||
|
||||
# The pending start job keeps the stream open; each line is a unit notification.
|
||||
sleep 1
|
||||
notifications=$(wc -l </tmp/enqueue-notifications.json)
|
||||
|
||||
# NOP uses a separate job slot and completes immediately.
|
||||
varlinkctl call --more /run/systemd/io.systemd.Manager io.systemd.Unit.EnqueueJob \
|
||||
'{"names": ["varlink-test-slow.service"], "jobType": "nop", "notifyJobChanges": true}' >/dev/null
|
||||
|
||||
# NOP removal must still trigger a unit notification.
|
||||
for _ in {1..20}; do
|
||||
[[ "$(wc -l </tmp/enqueue-notifications.json)" -gt "$notifications" ]] && break
|
||||
sleep 0.1
|
||||
done
|
||||
[[ "$(wc -l </tmp/enqueue-notifications.json)" -gt "$notifications" ]]
|
||||
|
||||
systemctl stop varlink-test-slow.service
|
||||
wait "$watcher_pid" 2>/dev/null || true
|
||||
watcher_pid=
|
||||
}
|
||||
|
||||
run_testcases
|
||||
@@ -0,0 +1,726 @@
|
||||
#!/usr/bin/env bash
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
set -eux
|
||||
set -o pipefail
|
||||
|
||||
# shellcheck source=test/units/test-control.sh
|
||||
. "$(dirname "$0")"/test-control.sh
|
||||
|
||||
MANAGER_SOCKET="/run/systemd/io.systemd.Manager"
|
||||
|
||||
at_exit() {
|
||||
local u pid
|
||||
|
||||
for u in "${TRANSIENT_UNITS[@]}"; do
|
||||
systemctl stop "$u" 2>/dev/null || true
|
||||
systemctl reset-failed "$u" 2>/dev/null || true
|
||||
done
|
||||
for pid in "${CHILD_PIDS[@]}"; do
|
||||
kill "$pid" 2>/dev/null || true
|
||||
wait "$pid" 2>/dev/null || true
|
||||
done
|
||||
rm -f "${TEMP_FILES[@]}"
|
||||
}
|
||||
|
||||
setup() {
|
||||
TRANSIENT_UNITS=()
|
||||
CHILD_PIDS=()
|
||||
TEMP_FILES=()
|
||||
trap at_exit EXIT
|
||||
}
|
||||
|
||||
defer_transient_cleanup() {
|
||||
TRANSIENT_UNITS+=("$1")
|
||||
}
|
||||
|
||||
# Apply-time and dispatch-time validation errors both surface as
|
||||
# org.varlink.service.InvalidParameter, with the offending field name in the
|
||||
# response parameters. Use --graceful to treat the expected error as success
|
||||
# so jq can assert on the dumped parameters JSON directly.
|
||||
expect_invalid_parameter() {
|
||||
local payload="$1" field="$2"
|
||||
varlinkctl call --graceful=org.varlink.service.InvalidParameter \
|
||||
"$MANAGER_SOCKET" io.systemd.Unit.StartTransient "$payload" \
|
||||
| jq -e --arg f "$field" '.parameter == $f' >/dev/null
|
||||
}
|
||||
|
||||
testcase_basic() {
|
||||
setup
|
||||
|
||||
# Basic oneshot transient service
|
||||
defer_transient_cleanup varlink-transient-test.service
|
||||
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-test.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}')
|
||||
echo "$result" | grep '"context"' >/dev/null
|
||||
echo "$result" | grep '"runtime"' >/dev/null
|
||||
|
||||
# Wait for completion
|
||||
timeout 30 bash -c 'until systemctl show -P ActiveState varlink-transient-test.service | grep inactive >/dev/null; do sleep 0.5; done'
|
||||
systemctl show -P Result varlink-transient-test.service | grep success >/dev/null
|
||||
|
||||
# With explicit mode
|
||||
defer_transient_cleanup varlink-transient-test2.service
|
||||
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-test2.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}},"mode":"fail"}')
|
||||
echo "$result" | grep '"context"' >/dev/null
|
||||
|
||||
# Multiple ExecStart commands (oneshot allows multiple)
|
||||
defer_transient_cleanup varlink-transient-multi.service
|
||||
result=$(varlinkctl call --more "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-multi.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"},{"path":"/bin/true"}]}},"notifyJobChanges":true}')
|
||||
printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .job.Result == "done")' >/dev/null
|
||||
|
||||
# Transient service with Description and RemainAfterExit
|
||||
defer_transient_cleanup varlink-transient-desc.service
|
||||
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-desc.service","Description":"Test description property","CollectMode":"inactive_or_failed","Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}')
|
||||
echo "$result" | jq -e '.context.Description == "Test description property"'
|
||||
echo "$result" | jq -e '.context.CollectMode == "inactive_or_failed"'
|
||||
echo "$result" | jq -e '.context.Service.Type == "oneshot"'
|
||||
echo "$result" | jq -e '.context.Service.RemainAfterExit == true'
|
||||
echo "$result" | jq -e '.context.Service.ExecStart[0].path == "/bin/true"'
|
||||
echo "$result" | jq -e '.runtime'
|
||||
systemctl show -P CollectMode varlink-transient-desc.service | grep '^inactive-or-failed$' >/dev/null
|
||||
|
||||
# Transient service with explicit arguments
|
||||
defer_transient_cleanup varlink-transient-args.service
|
||||
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-args.service","Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/echo","arguments":["/bin/echo","hello"]}]}}}')
|
||||
echo "$result" | jq -e '.context'
|
||||
echo "$result" | jq -e '.runtime'
|
||||
echo "$result" | jq -e '.context.Service.ExecStart[0].path == "/bin/echo"'
|
||||
echo "$result" | jq -e '.context.Service.ExecStart[0].arguments == ["/bin/echo", "hello"]'
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-args.service; do sleep 0.5; done'
|
||||
|
||||
# Verify that omitting arguments defaults argv[0] to the path
|
||||
defer_transient_cleanup varlink-transient-noargs.service
|
||||
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-noargs.service","Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}')
|
||||
echo "$result" | jq -e '.context.Service.ExecStart[0].arguments == ["/bin/true"]'
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-noargs.service; do sleep 0.5; done'
|
||||
|
||||
defer_transient_cleanup varlink-transient-bad.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-bad.service","Service":{"Type":"simple"}}}' \
|
||||
"context"
|
||||
# Invalid ExecStart path: exercises filename_or_absolute_path_is_valid() in transient_service_apply_properties()
|
||||
defer_transient_cleanup varlink-transient-badpath.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-badpath.service","Service":{"Type":"simple","ExecStart":[{"path":""}]}}}' \
|
||||
"Service.ExecStart"
|
||||
|
||||
# Service field declared in the IDL but not yet settable at creation is rejected as PropertyNotSupported,
|
||||
# and the offending sub-property is identified
|
||||
defer_transient_cleanup varlink-transient-unknown-service.service
|
||||
unsupported_service=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-unknown-service.service","Service":{"Type":"oneshot","Restart":"always","ExecStart":[{"path":"/bin/true"}]}}}' 2>&1 || true)
|
||||
echo "$unsupported_service" | grep "io.systemd.Unit.PropertyNotSupported"
|
||||
echo "$unsupported_service" | grep "Service.Restart"
|
||||
}
|
||||
|
||||
testcase_notifications() {
|
||||
setup
|
||||
|
||||
# Streaming with notifyJobChanges: should get intermediate state updates and a final result
|
||||
# Note: use --slurp + any() rather than 'select() -e' because in jq 1.6 (shipped on
|
||||
# CentOS 9) -e checks only the last input record's output, so a select() that filters
|
||||
# out the trailing record makes jq exit non-zero even when earlier records match.
|
||||
defer_transient_cleanup varlink-transient-test3.service
|
||||
result=$(varlinkctl call --more "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-test3.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}},"notifyJobChanges":true}')
|
||||
printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .job.State == "waiting")' >/dev/null
|
||||
printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .job.Result == "done")' >/dev/null
|
||||
|
||||
# Fire-and-forget: --more without notify flags should return immediately with context+runtime
|
||||
defer_transient_cleanup varlink-transient-fireforget.service
|
||||
result=$(varlinkctl call --more "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-fireforget.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}')
|
||||
printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .context)' >/dev/null
|
||||
printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .runtime)' >/dev/null
|
||||
|
||||
# Streaming with notifyUnitChanges: should get unit state change notifications
|
||||
defer_transient_cleanup varlink-transient-unitnotify.service
|
||||
result=$(varlinkctl call --more "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-unitnotify.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}},"notifyUnitChanges":true}')
|
||||
printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .runtime.ActiveState)' >/dev/null
|
||||
|
||||
# Streaming with both notifyJobChanges and notifyUnitChanges
|
||||
defer_transient_cleanup varlink-transient-both.service
|
||||
result=$(varlinkctl call --more "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-both.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}},"notifyJobChanges":true,"notifyUnitChanges":true}')
|
||||
printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .job.State)' >/dev/null
|
||||
printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .runtime.ActiveState)' >/dev/null
|
||||
printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .job.Result == "done")' >/dev/null
|
||||
}
|
||||
|
||||
testcase_exec_properties() {
|
||||
setup
|
||||
|
||||
# Exec.WorkingDirectory and Exec.Environment
|
||||
defer_transient_cleanup varlink-transient-exec.service
|
||||
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-exec.service","Exec":{"WorkingDirectory":{"path":"/tmp","missingOK":false},"Environment":["FOO=bar","BAZ=qux"]},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}')
|
||||
echo "$result" | jq -e '.context.Exec.WorkingDirectory.path == "/tmp"'
|
||||
echo "$result" | jq -e '.context.Exec.Environment | index("FOO=bar") != null'
|
||||
echo "$result" | jq -e '.context.Exec.Environment | index("BAZ=qux") != null'
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-exec.service; do sleep 0.5; done'
|
||||
systemctl show -P WorkingDirectory varlink-transient-exec.service | grep '^/tmp$' >/dev/null
|
||||
systemctl show -P Environment varlink-transient-exec.service | grep 'FOO=bar' >/dev/null
|
||||
systemctl show -P Environment varlink-transient-exec.service | grep 'BAZ=qux' >/dev/null
|
||||
|
||||
# WorkingDirectory with missingOK=true (path does not exist but unit still starts)
|
||||
defer_transient_cleanup varlink-transient-wd-missing.service
|
||||
varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-wd-missing.service","Exec":{"WorkingDirectory":{"path":"/nonexistent/path","missingOK":true}},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}'
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-wd-missing.service; do sleep 0.5; done'
|
||||
|
||||
# WorkingDirectory with home=true, missingOK omitted (defaults to false)
|
||||
defer_transient_cleanup varlink-transient-wd-home.service
|
||||
varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-wd-home.service","Exec":{"WorkingDirectory":{"home":true}},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}'
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-wd-home.service; do sleep 0.5; done'
|
||||
systemctl show -P WorkingDirectory varlink-transient-wd-home.service | grep '^~$' >/dev/null
|
||||
|
||||
# Exec.StandardInput/StandardOutput/StandardError enum properties and Exec.TTYPath
|
||||
defer_transient_cleanup varlink-transient-stdio.service
|
||||
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-stdio.service","Exec":{"StandardInput":"null","StandardOutput":"null","StandardError":"journal","TTYPath":"/dev/tty2"},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}')
|
||||
echo "$result" | jq -e '.context.Exec.StandardInput == "null"'
|
||||
echo "$result" | jq -e '.context.Exec.StandardOutput == "null"'
|
||||
echo "$result" | jq -e '.context.Exec.StandardError == "journal"'
|
||||
echo "$result" | jq -e '.context.Exec.TTYPath == "/dev/tty2"'
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-stdio.service; do sleep 0.5; done'
|
||||
systemctl show -P StandardInput varlink-transient-stdio.service | grep '^null$' >/dev/null
|
||||
systemctl show -P StandardOutput varlink-transient-stdio.service | grep '^null$' >/dev/null
|
||||
systemctl show -P StandardError varlink-transient-stdio.service | grep '^journal$' >/dev/null
|
||||
systemctl show -P TTYPath varlink-transient-stdio.service | grep '^/dev/tty2$' >/dev/null
|
||||
# "null" is also the default for StandardInput=, hence additionally verify it was written explicitly
|
||||
fragment=$(systemctl show -P FragmentPath varlink-transient-stdio.service)
|
||||
grep '^StandardInput=null$' "$fragment" >/dev/null
|
||||
|
||||
# The combined output types are spelled with a "+" in unit files, but with a "_" in the Varlink IDL (and
|
||||
# hence in our own replies). The latter spelling must be accepted, so that replies can be fed back in.
|
||||
defer_transient_cleanup varlink-transient-stdio-combined.service
|
||||
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-stdio-combined.service","Exec":{"StandardOutput":"kmsg_console","StandardError":"journal_console"},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}')
|
||||
echo "$result" | jq -e '.context.Exec.StandardOutput == "kmsg_console"'
|
||||
echo "$result" | jq -e '.context.Exec.StandardError == "journal_console"'
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-stdio-combined.service; do sleep 0.5; done'
|
||||
systemctl show -P StandardOutput varlink-transient-stdio-combined.service | grep '^kmsg+console$' >/dev/null
|
||||
systemctl show -P StandardError varlink-transient-stdio-combined.service | grep '^journal+console$' >/dev/null
|
||||
|
||||
# Exec.User, Exec.Group, Exec.SupplementaryGroups, Exec.Nice
|
||||
# The nobody group is different on different distros so resolve here.
|
||||
NOBODY_GROUP=$(id -gn nobody)
|
||||
defer_transient_cleanup varlink-transient-ids.service
|
||||
ids_payload=$(jq -cn --arg g "$NOBODY_GROUP" \
|
||||
'{context:{ID:"varlink-transient-ids.service",
|
||||
Exec:{User:"nobody",Group:$g,SupplementaryGroups:[$g],Nice:5},
|
||||
Service:{Type:"oneshot",RemainAfterExit:true,
|
||||
ExecStart:[{path:"/bin/true"}]}}}')
|
||||
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient "$ids_payload")
|
||||
echo "$result" | jq -e '.context.Exec.User == "nobody"'
|
||||
echo "$result" | jq --arg g "$NOBODY_GROUP" -e '.context.Exec.Group == $g'
|
||||
echo "$result" | jq --arg g "$NOBODY_GROUP" -e '.context.Exec.SupplementaryGroups == [$g]'
|
||||
echo "$result" | jq -e '.context.Exec.Nice == 5'
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-ids.service; do sleep 0.5; done'
|
||||
systemctl show -P User varlink-transient-ids.service | grep '^nobody$' >/dev/null
|
||||
systemctl show -P Group varlink-transient-ids.service | grep "^${NOBODY_GROUP}$" >/dev/null
|
||||
systemctl show -P SupplementaryGroups varlink-transient-ids.service | grep "${NOBODY_GROUP}" >/dev/null
|
||||
systemctl show -P Nice varlink-transient-ids.service | grep '^5$' >/dev/null
|
||||
|
||||
# Exec.OOMScoreAdjust, Exec.UMask, Exec.NoNewPrivileges, Exec.MemoryDenyWriteExecute
|
||||
# (int+validator, mode_t, two tristate-bool shapes)
|
||||
defer_transient_cleanup varlink-transient-procctl.service
|
||||
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-procctl.service","Exec":{"OOMScoreAdjust":250,"UMask":18,"NoNewPrivileges":true,"MemoryDenyWriteExecute":true},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}')
|
||||
echo "$result" | jq -e '.context.Exec.OOMScoreAdjust == 250'
|
||||
echo "$result" | jq -e '.context.Exec.UMask == 18'
|
||||
echo "$result" | jq -e '.context.Exec.NoNewPrivileges == true'
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-procctl.service; do sleep 0.5; done'
|
||||
systemctl show -P OOMScoreAdjust varlink-transient-procctl.service | grep '^250$' >/dev/null
|
||||
systemctl show -P UMask varlink-transient-procctl.service | grep '^0022$' >/dev/null
|
||||
systemctl show -P NoNewPrivileges varlink-transient-procctl.service | grep '^yes$' >/dev/null
|
||||
systemctl show -P MemoryDenyWriteExecute varlink-transient-procctl.service | grep '^yes$' >/dev/null
|
||||
|
||||
# Exec.RootHashPath / Exec.RootHashSignaturePath: the unit-file directive must be
|
||||
# "RootHash=" / "RootHashSignature=" (not the JSON name), otherwise the next daemon-reload
|
||||
# would drop the setting as an unknown key.
|
||||
defer_transient_cleanup varlink-transient-roothash.service
|
||||
varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-roothash.service","Exec":{"RootHashPath":"/etc/hostname","RootHashSignaturePath":"/etc/machine-id"},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}' >/dev/null
|
||||
fragment=$(systemctl show -P FragmentPath varlink-transient-roothash.service)
|
||||
test -n "$fragment"
|
||||
grep '^RootHash=/etc/hostname$' "$fragment" >/dev/null
|
||||
grep '^RootHashSignature=/etc/machine-id$' "$fragment" >/dev/null
|
||||
|
||||
# Relative WorkingDirectory path is rejected
|
||||
defer_transient_cleanup varlink-transient-bad-wd.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-bad-wd.service","Exec":{"WorkingDirectory":{"path":"relative/path","missingOK":false}},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \
|
||||
"Exec.WorkingDirectory"
|
||||
# Malformed environment entry (not KEY=VALUE)
|
||||
defer_transient_cleanup varlink-transient-bad-env.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-bad-env.service","Exec":{"Environment":["not_an_env_var"]},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \
|
||||
"Exec.Environment"
|
||||
# Invalid User= name is rejected at JSON dispatch time as a parameter error
|
||||
defer_transient_cleanup varlink-transient-bad-user.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-bad-user.service","Exec":{"User":"bad/user"},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \
|
||||
"context"
|
||||
# Out-of-range Nice= value is rejected
|
||||
defer_transient_cleanup varlink-transient-bad-nice.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-bad-nice.service","Exec":{"Nice":100},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \
|
||||
"Exec.Nice"
|
||||
# Out-of-range OOMScoreAdjust= value is rejected
|
||||
defer_transient_cleanup varlink-transient-bad-oom.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-bad-oom.service","Exec":{"OOMScoreAdjust":9999},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \
|
||||
"Exec.OOMScoreAdjust"
|
||||
# Relative RootDirectory path is rejected
|
||||
defer_transient_cleanup varlink-transient-bad-rd.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-bad-rd.service","Exec":{"RootDirectory":"relative/path"},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \
|
||||
"Exec.RootDirectory"
|
||||
# Note that an unparsable enum value (e.g. StandardOutput=bogus) is rejected by the Varlink IDL
|
||||
# validation layer before the JSON dispatch even runs, hence there's no point in testing pid1's
|
||||
# dispatch-level enum handling here — and doing so would trip post.sh's "didn't pass validation"
|
||||
# journal check, since the IDL layer logs about the rejection.
|
||||
# Relative TTYPath is rejected
|
||||
defer_transient_cleanup varlink-transient-bad-ttypath.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-bad-ttypath.service","Exec":{"TTYPath":"relative/tty"},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \
|
||||
"Exec.TTYPath"
|
||||
|
||||
# The file based stdio types need a companion path, and StandardInput=data a companion payload, neither of
|
||||
# which can be passed yet, hence they are rejected
|
||||
for t in file append truncate; do
|
||||
defer_transient_cleanup "varlink-transient-bad-stdout-$t.service"
|
||||
expect_invalid_parameter \
|
||||
"{\"context\":{\"ID\":\"varlink-transient-bad-stdout-$t.service\",\"Exec\":{\"StandardOutput\":\"$t\"},\"Service\":{\"Type\":\"oneshot\",\"ExecStart\":[{\"path\":\"/bin/true\"}]}}}" \
|
||||
"Exec.StandardOutput"
|
||||
done
|
||||
defer_transient_cleanup varlink-transient-bad-stderr.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-bad-stderr.service","Exec":{"StandardError":"file"},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \
|
||||
"Exec.StandardError"
|
||||
for t in file data; do
|
||||
defer_transient_cleanup "varlink-transient-bad-stdin-$t.service"
|
||||
expect_invalid_parameter \
|
||||
"{\"context\":{\"ID\":\"varlink-transient-bad-stdin-$t.service\",\"Exec\":{\"StandardInput\":\"$t\"},\"Service\":{\"Type\":\"oneshot\",\"ExecStart\":[{\"path\":\"/bin/true\"}]}}}" \
|
||||
"Exec.StandardInput"
|
||||
done
|
||||
|
||||
# Unknown field in Exec is rejected as PropertyNotSupported
|
||||
defer_transient_cleanup varlink-transient-unknown-exec.service
|
||||
unsupported_exec=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-unknown-exec.service","Exec":{"AmbientCapabilities":["cap_net_raw"]},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' 2>&1 || true)
|
||||
echo "$unsupported_exec" | grep "io.systemd.Unit.PropertyNotSupported"
|
||||
echo "$unsupported_exec" | grep "Exec.AmbientCapabilities"
|
||||
}
|
||||
|
||||
testcase_exec_sandbox() {
|
||||
setup
|
||||
|
||||
# Exec sandbox scalars: enum and yes/no-string properties round-trip into the reply
|
||||
# context (Varlink spelling), systemctl show and the transient fragment (unit-file
|
||||
# spelling, so the next daemon-reload keeps the setting)
|
||||
defer_transient_cleanup varlink-transient-sandbox.service
|
||||
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-sandbox.service","Exec":{"ProtectSystem":"strict","ProtectHome":"read_only","PrivateTmp":"disconnected","PrivateNetwork":"yes","PrivateUsers":"self"},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}')
|
||||
echo "$result" | jq -e '.context.Exec.ProtectSystem == "strict"'
|
||||
echo "$result" | jq -e '.context.Exec.ProtectHome == "read_only"'
|
||||
echo "$result" | jq -e '.context.Exec.PrivateTmp == "disconnected"'
|
||||
echo "$result" | jq -e '.context.Exec.PrivateNetwork == "yes"'
|
||||
echo "$result" | jq -e '.context.Exec.PrivateUsers == "self"'
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-sandbox.service; do sleep 0.5; done'
|
||||
systemctl show -P ProtectSystem varlink-transient-sandbox.service | grep '^strict$' >/dev/null
|
||||
systemctl show -P ProtectHome varlink-transient-sandbox.service | grep '^read-only$' >/dev/null
|
||||
systemctl show -P PrivateTmpEx varlink-transient-sandbox.service | grep '^disconnected$' >/dev/null
|
||||
systemctl show -P PrivateNetwork varlink-transient-sandbox.service | grep '^yes$' >/dev/null
|
||||
systemctl show -P PrivateUsersEx varlink-transient-sandbox.service | grep '^self$' >/dev/null
|
||||
fragment=$(systemctl show -P FragmentPath varlink-transient-sandbox.service)
|
||||
test -n "$fragment"
|
||||
grep '^ProtectSystem=strict$' "$fragment" >/dev/null
|
||||
grep '^ProtectHome=read-only$' "$fragment" >/dev/null
|
||||
grep '^PrivateTmp=disconnected$' "$fragment" >/dev/null
|
||||
grep '^PrivateNetwork=yes$' "$fragment" >/dev/null
|
||||
grep '^PrivateUsers=self$' "$fragment" >/dev/null
|
||||
|
||||
# The sandbox is live, not just recorded: the unit only succeeds if /usr is read-only
|
||||
# and its network namespace differs from PID 1's. Both readlinks are captured first, so
|
||||
# an unreadable link fails the unit rather than passing it. Comparing namespaces beats
|
||||
# listing interfaces here: the harness runs with --runtime-network=none, and the netns
|
||||
# fallback devices come and go with the loaded modules.
|
||||
defer_transient_cleanup varlink-transient-sandbox-live.service
|
||||
# shellcheck disable=SC2016
|
||||
varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-sandbox-live.service","Exec":{"ProtectSystem":"strict","PrivateNetwork":"yes"},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/sh","arguments":["/bin/sh","-c","p=$(readlink /proc/1/ns/net) && s=$(readlink /proc/self/ns/net) && test \"$s\" != \"$p\" && ! touch /usr/varlink-sandbox-canary"]}]}}}' >/dev/null
|
||||
timeout 30 bash -c 'until systemctl show -P ActiveState varlink-transient-sandbox-live.service | grep -Ex "active|failed" >/dev/null; do sleep 0.5; done'
|
||||
systemctl show -P Result varlink-transient-sandbox-live.service | grep '^success$' >/dev/null
|
||||
test ! -e /usr/varlink-sandbox-canary
|
||||
}
|
||||
|
||||
testcase_kill_properties() {
|
||||
setup
|
||||
|
||||
# Kill.KillMode/Kill.SendSIGHUP
|
||||
defer_transient_cleanup varlink-transient-kill.service
|
||||
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-kill.service","Kill":{"KillMode":"process","SendSIGHUP":true},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}')
|
||||
echo "$result" | jq -e '.context.Kill.KillMode == "process"'
|
||||
echo "$result" | jq -e '.context.Kill.SendSIGHUP == true'
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-kill.service; do sleep 0.5; done'
|
||||
systemctl show -P KillMode varlink-transient-kill.service | grep '^process$' >/dev/null
|
||||
systemctl show -P SendSIGHUP varlink-transient-kill.service | grep '^yes$' >/dev/null
|
||||
|
||||
# Same for a Kill field
|
||||
defer_transient_cleanup varlink-transient-unknown-kill.service
|
||||
unsupported_kill=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-unknown-kill.service","Kill":{"KillSignal":"SIGINT"},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' 2>&1 || true)
|
||||
echo "$unsupported_kill" | grep "io.systemd.Unit.PropertyNotSupported"
|
||||
echo "$unsupported_kill" | grep "Kill.KillSignal"
|
||||
}
|
||||
|
||||
testcase_exec_flags() {
|
||||
setup
|
||||
|
||||
# shellcheck disable=SC2016
|
||||
TEMP_FILES+=(/tmp/varlink-transient-execflags-expanded '/tmp/varlink-transient-execflags-${FLAGTEST}'
|
||||
/tmp/varlink-transient-execflags-shell /run/varlink-transient-execflags-{plain,privileged,nosetuid})
|
||||
|
||||
# ExecStart execution flags, i.e. the Varlink counterparts of the "-", ":", "|", "+" and "!" prefixes of
|
||||
# unit file ExecStart= lines. First the ones observable from an unprivileged unit:
|
||||
# - ignoreFailure: a failing command does not fail the unit
|
||||
# - noEnvExpand: "${VAR}" in the arguments is passed as-is rather than expanded
|
||||
# - viaShell: the command line is handed to the user's shell via "-c" (argv[0] is normalized to "sh")
|
||||
rm -f /tmp/varlink-transient-execflags-*
|
||||
defer_transient_cleanup varlink-transient-execflags.service
|
||||
# shellcheck disable=SC2016
|
||||
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-execflags.service","Exec":{"Environment":["FLAGTEST=expanded"]},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[
|
||||
{"path":"/bin/false","ignoreFailure":true},
|
||||
{"path":"/usr/bin/touch","arguments":["touch","/tmp/varlink-transient-execflags-${FLAGTEST}"]},
|
||||
{"path":"/usr/bin/touch","arguments":["touch","/tmp/varlink-transient-execflags-${FLAGTEST}"],"noEnvExpand":true},
|
||||
{"path":"/bin/sh","arguments":["sh","touch","/tmp/varlink-transient-execflags-shell"],"viaShell":true}]}}}')
|
||||
echo "$result" | jq -e '.context.Service.ExecStart[0].ignoreFailure == true'
|
||||
echo "$result" | jq -e '.context.Service.ExecStart[1].noEnvExpand == false'
|
||||
echo "$result" | jq -e '.context.Service.ExecStart[2].noEnvExpand == true'
|
||||
echo "$result" | jq -e '.context.Service.ExecStart[3].viaShell == true'
|
||||
echo "$result" | jq -e '.context.Service.ExecStart[3].path == "/bin/sh"'
|
||||
echo "$result" | jq -e '.context.Service.ExecStart[3].arguments[0] == "sh"'
|
||||
# The unit only becomes active once all ExecStart= commands succeeded (or were allowed to fail)
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-execflags.service; do sleep 0.5; done'
|
||||
test -e /tmp/varlink-transient-execflags-expanded
|
||||
# shellcheck disable=SC2016
|
||||
test -e '/tmp/varlink-transient-execflags-${FLAGTEST}'
|
||||
test -e /tmp/varlink-transient-execflags-shell
|
||||
# The flags must show up as the matching prefix characters in the transient unit file
|
||||
fragment=$(systemctl show -P FragmentPath varlink-transient-execflags.service)
|
||||
grep -E '^ExecStart=-"/bin/false"$' "$fragment" >/dev/null
|
||||
grep -E '^ExecStart=:' "$fragment" >/dev/null
|
||||
grep -E '^ExecStart=\|' "$fragment" >/dev/null
|
||||
rm -f /tmp/varlink-transient-execflags-*
|
||||
|
||||
# Now the privilege related ones, in a unit running as an unprivileged user:
|
||||
# - privileged/noSetuid: the command runs with full privileges, i.e. can write where the user cannot
|
||||
# - (neither): the command runs as the user, and fails to write there (tolerated via ignoreFailure)
|
||||
rm -f /run/varlink-transient-execflags-*
|
||||
defer_transient_cleanup varlink-transient-execflags-priv.service
|
||||
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-execflags-priv.service","Exec":{"User":"nobody"},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[
|
||||
{"path":"/usr/bin/touch","arguments":["touch","/run/varlink-transient-execflags-plain"],"ignoreFailure":true},
|
||||
{"path":"/usr/bin/touch","arguments":["touch","/run/varlink-transient-execflags-privileged"],"privileged":true},
|
||||
{"path":"/usr/bin/touch","arguments":["touch","/run/varlink-transient-execflags-nosetuid"],"noSetuid":true}]}}}')
|
||||
echo "$result" | jq -e '.context.Service.ExecStart[0].privileged == false'
|
||||
echo "$result" | jq -e '.context.Service.ExecStart[1].privileged == true'
|
||||
echo "$result" | jq -e '.context.Service.ExecStart[2].noSetuid == true'
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-execflags-priv.service; do sleep 0.5; done'
|
||||
test ! -e /run/varlink-transient-execflags-plain
|
||||
test -e /run/varlink-transient-execflags-privileged
|
||||
test -e /run/varlink-transient-execflags-nosetuid
|
||||
fragment=$(systemctl show -P FragmentPath varlink-transient-execflags-priv.service)
|
||||
grep -E '^ExecStart=\+' "$fragment" >/dev/null
|
||||
grep -E '^ExecStart=!' "$fragment" >/dev/null
|
||||
rm -f /run/varlink-transient-execflags-*
|
||||
|
||||
# The 'privileged' and 'noSetuid' execution flags are mutually exclusive
|
||||
defer_transient_cleanup varlink-transient-badflags.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-badflags.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true","privileged":true,"noSetuid":true}]}}}' \
|
||||
"Service.ExecStart"
|
||||
}
|
||||
|
||||
testcase_credentials() {
|
||||
setup
|
||||
|
||||
# Exec.SetCredential: pass a credential and verify the running process can read it
|
||||
defer_transient_cleanup varlink-transient-cred.service
|
||||
CRED_VALUE_B64=$(printf 'secret-value' | base64 -w0)
|
||||
CRED_OUTPUT=$(mktemp)
|
||||
TEMP_FILES+=("$CRED_OUTPUT")
|
||||
varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
"{\"context\":{\"ID\":\"varlink-transient-cred.service\",\"Exec\":{\"SetCredential\":[{\"id\":\"mycred\",\"value\":\"${CRED_VALUE_B64}\"}]},\"Service\":{\"Type\":\"oneshot\",\"RemainAfterExit\":true,\"ExecStart\":[{\"path\":\"/bin/sh\",\"arguments\":[\"/bin/sh\",\"-c\",\"cat \$CREDENTIALS_DIRECTORY/mycred > ${CRED_OUTPUT}\"]}]}}}"
|
||||
timeout 30 bash -c "until systemctl is-active varlink-transient-cred.service; do sleep 0.5; done"
|
||||
grep '^secret-value$' "$CRED_OUTPUT" >/dev/null
|
||||
rm -f "$CRED_OUTPUT"
|
||||
|
||||
# Invalid credential ID
|
||||
defer_transient_cleanup varlink-transient-bad-cred-id.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-bad-cred-id.service","Exec":{"SetCredential":[{"id":"bad/id","value":"YWJj"}]},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \
|
||||
"Exec.SetCredential"
|
||||
# Invalid base64 value for credential (rejected at JSON dispatch time as a parameter error)
|
||||
defer_transient_cleanup varlink-transient-bad-cred-value.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-bad-cred-value.service","Exec":{"SetCredential":[{"id":"mycred","value":"!!!not_base64!!!"}]},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \
|
||||
"context"
|
||||
}
|
||||
|
||||
testcase_file_descriptors() {
|
||||
setup
|
||||
|
||||
# Service.Standard{Output,Error}FileDescriptor: connect passed fds (by push order)
|
||||
# to the unit's stdout/stderr; regular files, so output can be checked after exit.
|
||||
transient_out=$(mktemp)
|
||||
TEMP_FILES+=("$transient_out")
|
||||
transient_err=$(mktemp)
|
||||
TEMP_FILES+=("$transient_err")
|
||||
exec {transient_out_fd}>"$transient_out"
|
||||
exec {transient_err_fd}>"$transient_err"
|
||||
defer_transient_cleanup varlink-transient-fdpass.service
|
||||
varlinkctl --push-fd="$transient_out_fd" --push-fd="$transient_err_fd" \
|
||||
call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-fdpass.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/sh","arguments":["/bin/sh","-c","echo to-stdout; echo to-stderr >&2"]}],"StandardOutputFileDescriptor":0,"StandardErrorFileDescriptor":1}}}' >/dev/null
|
||||
# Close our copies so the unit holds the only remaining write ends.
|
||||
exec {transient_out_fd}>&-
|
||||
exec {transient_err_fd}>&-
|
||||
timeout 30 bash -c 'until systemctl show -P ActiveState varlink-transient-fdpass.service | grep inactive >/dev/null; do sleep 0.5; done'
|
||||
systemctl show -P Result varlink-transient-fdpass.service | grep success >/dev/null
|
||||
grep -x to-stdout "$transient_out" >/dev/null
|
||||
grep -x to-stderr "$transient_err" >/dev/null
|
||||
rm -f "$transient_out" "$transient_err"
|
||||
}
|
||||
|
||||
testcase_scope() {
|
||||
setup
|
||||
|
||||
# Starting a transient scope
|
||||
defer_transient_cleanup varlink-transient-scope.scope
|
||||
sleep infinity &
|
||||
scope_child="$!"
|
||||
CHILD_PIDS+=("$scope_child")
|
||||
varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
"{\"context\":{\"ID\":\"varlink-transient-scope.scope\",\"Scope\":{\"PIDs\":[{\"pid\":${scope_child}}],\"OOMPolicy\":\"kill\", \"TimeoutStopUSec\":12000000}}}"
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-scope.scope; do sleep 0.5; done'
|
||||
systemctl whoami "$scope_child" | grep "^varlink-transient-scope.scope$" >/dev/null
|
||||
systemctl show -P TimeoutStopUSec varlink-transient-scope.scope | grep 12s >/dev/null
|
||||
systemctl show -P OOMPolicy varlink-transient-scope.scope | grep kill >/dev/null
|
||||
|
||||
# Bad PID on a scope
|
||||
defer_transient_cleanup varlink-transient-badpid.scope
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-badpid.scope","Scope":{"PIDs":[{"pid": 1}]}}}' \
|
||||
"Scope.PIDs"
|
||||
}
|
||||
|
||||
testcase_dependencies() {
|
||||
setup
|
||||
|
||||
# Unit dependencies
|
||||
defer_transient_cleanup varlink-transient-deps.service
|
||||
varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-deps.service","After":["varlink-transient-dependency.service"],"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}'
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-deps.service; do sleep 0.5; done'
|
||||
systemctl show -P After varlink-transient-deps.service | grep varlink-transient-dependency.service >/dev/null
|
||||
|
||||
# Dependency on invalid unit (rejected at dispatch time)
|
||||
defer_transient_cleanup varlink-invalid-dependency.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-invalid-dependency.service","Requires":["invalid@.service"],"Service":{"ExecStart":[{"path":"/usr/bin/true"}]}}}' \
|
||||
"context"
|
||||
|
||||
# Same for non-transient dependency types
|
||||
defer_transient_cleanup varlink-non-transient-dependency.service
|
||||
non_transient_dep=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-non-transient-dependency.service","Triggers":["varlink-transient-dependency.service"],"Service":{"ExecStart":[{"path":"/usr/bin/true"}]}}}' 2>&1 || true)
|
||||
echo "$non_transient_dep" | grep "io.systemd.Unit.PropertyNotSupported"
|
||||
echo "$non_transient_dep" | grep "Triggers"
|
||||
}
|
||||
|
||||
testcase_cgroup() {
|
||||
setup
|
||||
|
||||
# CGroup context: scalar resource-control properties round-trip into the reply
|
||||
# context, systemctl show and the transient fragment
|
||||
defer_transient_cleanup varlink-transient-cgroup.service
|
||||
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-cgroup.service","CGroup":{"CPUWeight":200,"StartupCPUWeight":150,"CPUQuotaPerSecUSec":500000,"CPUQuotaPeriodUSec":20000,"MemoryAccounting":true,"MemoryLow":8388608,"StartupMemoryLow":4194304,"MemoryHigh":33554432,"MemoryMax":67108864,"TasksAccounting":true,"TasksMax":{"value":16},"IOAccounting":true,"IOWeight":100},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}')
|
||||
echo "$result" | jq -e '.context.CGroup.CPUWeight == 200'
|
||||
echo "$result" | jq -e '.context.CGroup.StartupCPUWeight == 150'
|
||||
echo "$result" | jq -e '.context.CGroup.CPUQuotaPerSecUSec == 500000'
|
||||
echo "$result" | jq -e '.context.CGroup.CPUQuotaPeriodUSec == 20000'
|
||||
echo "$result" | jq -e '.context.CGroup.StartupMemoryLow == 4194304'
|
||||
echo "$result" | jq -e '.context.CGroup.MemoryMax == 67108864'
|
||||
echo "$result" | jq -e '.context.CGroup.TasksMax == {"value":16,"scale":0}'
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-cgroup.service; do sleep 0.5; done'
|
||||
systemctl show -P CPUWeight varlink-transient-cgroup.service | grep -Fx 200 >/dev/null
|
||||
systemctl show -P CPUQuotaPerSecUSec varlink-transient-cgroup.service | grep -Fx 500ms >/dev/null
|
||||
systemctl show -P CPUQuotaPeriodUSec varlink-transient-cgroup.service | grep -Fx 20ms >/dev/null
|
||||
systemctl show -P MemoryLow varlink-transient-cgroup.service | grep -Fx 8388608 >/dev/null
|
||||
systemctl show -P MemoryHigh varlink-transient-cgroup.service | grep -Fx 33554432 >/dev/null
|
||||
systemctl show -P MemoryMax varlink-transient-cgroup.service | grep -Fx 67108864 >/dev/null
|
||||
systemctl show -P TasksMax varlink-transient-cgroup.service | grep -Fx 16 >/dev/null
|
||||
systemctl show -P IOWeight varlink-transient-cgroup.service | grep -Fx 100 >/dev/null
|
||||
# The fragment must use the unit-file directive spellings (CPUQuota=, not the JSON
|
||||
# property name), otherwise the next daemon-reload would drop the setting.
|
||||
fragment=$(systemctl show -P FragmentPath varlink-transient-cgroup.service)
|
||||
test -n "$fragment"
|
||||
grep -Fx 'CPUQuota=50.00%' "$fragment" >/dev/null
|
||||
grep -Fx 'CPUQuotaPeriodSec=20ms' "$fragment" >/dev/null
|
||||
grep -Fx 'MemoryMax=67108864' "$fragment" >/dev/null
|
||||
grep -Fx 'TasksMax=16' "$fragment" >/dev/null
|
||||
|
||||
# The special values are written as TasksMax=infinity, CPUWeight=idle and an empty
|
||||
# StartupCPUWeight= (UINT64_MAX resets a weight to the default)
|
||||
defer_transient_cleanup varlink-transient-cgroup-special.service
|
||||
varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-cgroup-special.service","CGroup":{"TasksMax":{"value":18446744073709551615},"CPUWeight":0,"StartupCPUWeight":18446744073709551615},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}' >/dev/null
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-cgroup-special.service; do sleep 0.5; done'
|
||||
systemctl show -P TasksMax varlink-transient-cgroup-special.service | grep -Fx infinity >/dev/null
|
||||
fragment=$(systemctl show -P FragmentPath varlink-transient-cgroup-special.service)
|
||||
test -n "$fragment"
|
||||
grep -Fx 'TasksMax=infinity' "$fragment" >/dev/null
|
||||
grep -Fx 'CPUWeight=idle' "$fragment" >/dev/null
|
||||
grep -Fx 'StartupCPUWeight=' "$fragment" >/dev/null
|
||||
|
||||
# Cgroup limits materialize on the live cgroup
|
||||
defer_transient_cleanup varlink-transient-cgroup-live.service
|
||||
varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-cgroup-live.service","CGroup":{"MemoryMax":67108864,"TasksMax":{"value":16}},"Service":{"ExecStart":[{"path":"/usr/bin/sleep","arguments":["/usr/bin/sleep","infinity"]}]}}}' >/dev/null
|
||||
timeout 10 bash -c 'until systemctl is-active varlink-transient-cgroup-live.service; do sleep 0.5; done'
|
||||
cgroup_path=$(systemctl show -P ControlGroup varlink-transient-cgroup-live.service)
|
||||
test -n "$cgroup_path"
|
||||
grep -Fx 67108864 "/sys/fs/cgroup${cgroup_path}/memory.max" >/dev/null
|
||||
grep -Fx 16 "/sys/fs/cgroup${cgroup_path}/pids.max" >/dev/null
|
||||
|
||||
# Slice= places the unit under an implicitly created slice
|
||||
defer_transient_cleanup varlink-transient-slice.service
|
||||
defer_transient_cleanup varlink-test.slice
|
||||
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-slice.service","CGroup":{"Slice":"varlink-test.slice"},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}')
|
||||
echo "$result" | jq -e '.context.CGroup.Slice == "varlink-test.slice"'
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-slice.service; do sleep 0.5; done'
|
||||
systemctl show -P Slice varlink-transient-slice.service | grep -Fx varlink-test.slice >/dev/null
|
||||
|
||||
# Relative forms: TasksMaxScale/MemoryMaxScale (floats, 1.0 == 100%) are input only,
|
||||
# converted internally and written to the fragment as permyriad percentages. An
|
||||
# explicit null is treated as absent (so MemoryMax=null does not conflict with
|
||||
# MemoryMaxScale), and a zero scale is accepted for the min-0 memory properties,
|
||||
# as over D-Bus
|
||||
defer_transient_cleanup varlink-transient-cgroup-scale.service
|
||||
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-cgroup-scale.service","CGroup":{"TasksMaxScale":0.5,"MemoryMax":null,"MemoryMaxScale":0.125,"MemoryMinScale":0.0,"TasksAccounting":null},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}')
|
||||
echo "$result" | jq -e '.context.CGroup.TasksMax == {"value":5000,"scale":10000}'
|
||||
echo "$result" | jq -e '.context.CGroup.TasksMaxScale == null'
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-cgroup-scale.service; do sleep 0.5; done'
|
||||
fragment=$(systemctl show -P FragmentPath varlink-transient-cgroup-scale.service)
|
||||
test -n "$fragment"
|
||||
grep -Fx 'TasksMax=50.00%' "$fragment" >/dev/null
|
||||
grep -Fx 'MemoryMax=12.50%' "$fragment" >/dev/null
|
||||
grep -Fx 'MemoryMin=0.00%' "$fragment" >/dev/null
|
||||
(! grep -F 'TasksAccounting=' "$fragment")
|
||||
|
||||
# A null scale member of TasksMax is absent, not a request for the relative form
|
||||
defer_transient_cleanup varlink-transient-cgroup-nullscale.service
|
||||
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-cgroup-nullscale.service","CGroup":{"TasksMax":{"value":16,"scale":null}},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}')
|
||||
echo "$result" | jq -e '.context.CGroup.TasksMax == {"value":16,"scale":0}'
|
||||
|
||||
# Out-of-range CPUWeight= is rejected
|
||||
defer_transient_cleanup varlink-transient-bad-weight.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-bad-weight.service","CGroup":{"CPUWeight":123456},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \
|
||||
"CGroup.CPUWeight"
|
||||
# IOWeight=0 is rejected, only the CPU weights accept idle
|
||||
defer_transient_cleanup varlink-transient-bad-ioweight.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-bad-ioweight.service","CGroup":{"IOWeight":0},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \
|
||||
"CGroup.IOWeight"
|
||||
# MemoryMax=0 is rejected (minimum 1, matching the D-Bus setter)
|
||||
defer_transient_cleanup varlink-transient-bad-memmax.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-bad-memmax.service","CGroup":{"MemoryMax":0},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \
|
||||
"CGroup.MemoryMax"
|
||||
# The scale member of TasksMax is output only, TasksMaxScale is the input form
|
||||
defer_transient_cleanup varlink-transient-bad-taskscale-member.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-bad-taskscale-member.service","CGroup":{"TasksMax":{"value":5000,"scale":10000}},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \
|
||||
"context"
|
||||
# Out-of-range TasksMaxScale= (>= 100%) is rejected
|
||||
defer_transient_cleanup varlink-transient-bad-tasksscale.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-bad-tasksscale.service","CGroup":{"TasksMaxScale":1.5},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \
|
||||
"CGroup.TasksMaxScale"
|
||||
# Absolute and relative forms of the same limit may not be combined
|
||||
defer_transient_cleanup varlink-transient-bad-taskscombo.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-bad-taskscombo.service","CGroup":{"TasksMax":{"value":16},"TasksMaxScale":0.5},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \
|
||||
"CGroup.TasksMaxScale"
|
||||
# Zero MemoryMaxScale= is rejected
|
||||
defer_transient_cleanup varlink-transient-bad-memscale.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-bad-memscale.service","CGroup":{"MemoryMaxScale":0.0},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \
|
||||
"CGroup.MemoryMaxScale"
|
||||
# Slice= pointing at a non-slice unit is rejected
|
||||
defer_transient_cleanup varlink-transient-bad-slice.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-bad-slice.service","CGroup":{"Slice":"not-a-slice.service"},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \
|
||||
"CGroup.Slice"
|
||||
# CGroup field declared in the IDL but not yet settable at creation is rejected as
|
||||
# PropertyNotSupported, and the offending sub-property is identified
|
||||
defer_transient_cleanup varlink-transient-unknown-cgroup.service
|
||||
unsupported_cgroup=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-unknown-cgroup.service","CGroup":{"DeviceAllow":[]},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' 2>&1 || true)
|
||||
echo "$unsupported_cgroup" | grep "io.systemd.Unit.PropertyNotSupported"
|
||||
echo "$unsupported_cgroup" | grep "CGroup.DeviceAllow"
|
||||
}
|
||||
|
||||
testcase_validation() {
|
||||
setup
|
||||
|
||||
# Creating an already active unit must fail.
|
||||
defer_transient_cleanup varlink-transient-exists.service
|
||||
varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-exists.service","Service":{"ExecStart":[{"path":"/usr/bin/sleep","arguments":["/usr/bin/sleep","infinity"]}]}}}'
|
||||
timeout 10 bash -c 'until systemctl is-active varlink-transient-exists.service; do sleep 0.5; done'
|
||||
|
||||
# Error cases: verify specific varlink error types
|
||||
(
|
||||
set +o pipefail
|
||||
varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-exists.service","Service":{"ExecStart":[{"path":"/usr/bin/sleep","arguments":["/usr/bin/sleep","infinity"]}]}}}' |& grep "io.systemd.Unit.UnitExists"
|
||||
)
|
||||
(
|
||||
set +o pipefail
|
||||
varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-test.target","Description":"test"}}' |& grep "io.systemd.Unit.UnitTypeNotSupported"
|
||||
)
|
||||
|
||||
# Exec on a unit type without an exec context (.slice) is rejected
|
||||
(
|
||||
set +o pipefail
|
||||
varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-exec.slice","Exec":{"WorkingDirectory":{"path":"/tmp","missingOK":false}}}}' |& grep "io.systemd.Unit.UnitTypeNotSupported"
|
||||
)
|
||||
# Same for Kill on a unit type without a kill context
|
||||
(
|
||||
set +o pipefail
|
||||
varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-kill.slice","Kill":{"KillMode":"process"}}}' |& grep "io.systemd.Unit.UnitTypeNotSupported"
|
||||
)
|
||||
}
|
||||
|
||||
run_testcases
|
||||
@@ -3,806 +3,95 @@
|
||||
set -eux
|
||||
set -o pipefail
|
||||
|
||||
# test io.systemd.Unit
|
||||
varlinkctl info /run/systemd/io.systemd.Manager
|
||||
varlinkctl introspect /run/systemd/io.systemd.Manager io.systemd.Unit
|
||||
varlinkctl --more call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}'
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"name": "multi-user.target"}'
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"pid": {"pid": 1}}'
|
||||
(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' |& grep "called without 'more' flag" >/dev/null)
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"name": "init.scope", "pid": {"pid": 1}}'
|
||||
(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"name": ""}')
|
||||
(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"name": "non-existent.service"}')
|
||||
(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"pid": {"pid": -1}}' )
|
||||
(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"name": "multi-user.target", "pid": {"pid": 1}}')
|
||||
set +o pipefail
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.SetProperties '{"runtime": true, "name": "non-existent.service", "properties": {"Markers": ["needs-restart"]}}' |& grep "io.systemd.Unit.NoSuchUnit"
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.SetProperties '{"runtime": true, "name": "systemd-journald.service", "properties": {"LoadState": "foobar"}}' |& grep "io.systemd.Unit.PropertyNotSupported"
|
||||
set -o pipefail
|
||||
# shellcheck source=test/units/test-control.sh
|
||||
. "$(dirname "$0")"/test-control.sh
|
||||
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"cgroup": "/init.scope"}'
|
||||
invocation_id="$(systemctl show -P InvocationID systemd-journald.service)"
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "{\"invocationID\": \"$invocation_id\"}"
|
||||
# test for KillContext
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"pid": {"pid": 0}}' | jq -e '.context.Kill'
|
||||
# test for AutomountContext/Runtime
|
||||
automount_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "automount" and .runtime.LoadState == "loaded") .context.ID // empty' | tail -n 1)
|
||||
test -n "$automount_id"
|
||||
# Use jq to JSON-encode the unit name as it may contain backslash escapes (e.g. \x2d) that
|
||||
# are not valid JSON escape sequences and would be rejected by varlinkctl's JSON parser.
|
||||
automount_params=$(jq -cn --arg name "$automount_id" '{name: $name}')
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$automount_params" | jq -e '.context.Automount'
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$automount_params" | jq -e '.runtime.Automount'
|
||||
# test for MountContext/Runtime (skip volatile run-user-*.mount to avoid GC race)
|
||||
mount_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "mount" and .runtime.LoadState == "loaded" and (.context.ID | startswith("run-user-") | not)) .context.ID // empty' | tail -n 1)
|
||||
test -n "$mount_id"
|
||||
mount_params=$(jq -cn --arg name "$mount_id" '{name: $name}')
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$mount_params" | jq -e '.context.Mount'
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$mount_params" | jq -e '.runtime.Mount'
|
||||
# test for PathContext/Runtime
|
||||
path_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "path" and .runtime.LoadState == "loaded") .context.ID // empty' | tail -n 1)
|
||||
test -n "$path_id"
|
||||
path_params=$(jq -cn --arg name "$path_id" '{name: $name}')
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$path_params" | jq -e '.context.Path'
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$path_params" | jq -e '.runtime.Path'
|
||||
# test for ServiceContext/Runtime (skip volatile user@*/user-runtime-dir@* to avoid GC race)
|
||||
service_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "service" and .runtime.LoadState == "loaded" and (.context.ID | test("^(user|user-runtime-dir)@") | not)) .context.ID // empty' | tail -n 1)
|
||||
test -n "$service_id"
|
||||
service_params=$(jq -cn --arg name "$service_id" '{name: $name}')
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$service_params" | jq -e '.context.Service'
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$service_params" | jq -e '.runtime.Service'
|
||||
# test for ScopeContext/Runtime (skip volatile session-*.scope to avoid GC race)
|
||||
scope_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "scope" and .runtime.LoadState == "loaded" and (.context.ID | startswith("session-") | not)) .context.ID // empty' | tail -n 1)
|
||||
test -n "$scope_id"
|
||||
scope_params=$(jq -cn --arg name "$scope_id" '{name: $name}')
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$scope_params" | jq -e '.context.Scope'
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$scope_params" | jq -e '.runtime.Scope'
|
||||
# test for SocketContext/Runtime
|
||||
socket_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "socket" and .runtime.LoadState == "loaded") .context.ID // empty' | tail -n 1)
|
||||
test -n "$socket_id"
|
||||
socket_params=$(jq -cn --arg name "$socket_id" '{name: $name}')
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$socket_params" | jq -e '.context.Socket'
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$socket_params" | jq -e '.runtime.Socket'
|
||||
# test for SwapContext/Runtime (swap units may not be present on all systems)
|
||||
swap_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "swap" and .runtime.LoadState == "loaded") .context.ID // empty' | tail -n 1)
|
||||
if test -n "$swap_id"; then
|
||||
swap_params=$(jq -cn --arg name "$swap_id" '{name: $name}')
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$swap_params" | jq -e '.context.Swap'
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$swap_params" | jq -e '.runtime.Swap'
|
||||
fi
|
||||
# test for TimerContext/Runtime
|
||||
timer_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "timer" and .runtime.LoadState == "loaded") .context.ID // empty' | tail -n 1)
|
||||
test -n "$timer_id"
|
||||
timer_params=$(jq -cn --arg name "$timer_id" '{name: $name}')
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$timer_params" | jq -e '.context.Timer'
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$timer_params" | jq -e '.runtime.Timer'
|
||||
|
||||
# test io.systemd.Unit.EnqueueJob
|
||||
|
||||
TRANSIENT_UNITS=()
|
||||
at_exit() {
|
||||
systemctl stop varlink-test-enqueue{,-2}.service varlink-test-slow.service 2>/dev/null || true
|
||||
rm -f /run/systemd/system/varlink-test-enqueue{,-2}.service /run/systemd/system/varlink-test-slow.service
|
||||
rm -f /tmp/enqueue-notifications.json
|
||||
for u in "${TRANSIENT_UNITS[@]}"; do
|
||||
systemctl stop "$u" 2>/dev/null || true
|
||||
systemctl reset-failed "$u" 2>/dev/null || true
|
||||
done
|
||||
systemctl daemon-reload
|
||||
}
|
||||
trap at_exit EXIT
|
||||
|
||||
for unit in varlink-test-enqueue{,-2}.service; do
|
||||
cat >"/run/systemd/system/$unit" <<UNIT
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=true
|
||||
RemainAfterExit=yes
|
||||
UNIT
|
||||
done
|
||||
systemctl daemon-reload
|
||||
|
||||
# Non-streaming start with an explicit job mode.
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.EnqueueJob '{"names": ["varlink-test-enqueue.service"], "jobType": "start", "mode": "fail"}' | jq -e '.context and .runtime and .job.Id and .job.JobType == "start"'
|
||||
timeout 10 bash -c 'until systemctl is-active varlink-test-enqueue.service; do sleep 0.5; done'
|
||||
|
||||
# Streaming restart waits for completion.
|
||||
varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.EnqueueJob '{"names": ["varlink-test-enqueue.service"], "jobType": "restart", "notifyJobChanges": true}' | jq -e 'length > 1 and .[0].job.JobType == "restart" and .[-1].job.Result == "done" and .[-1].context and .[-1].runtime'
|
||||
systemctl is-active varlink-test-enqueue.service
|
||||
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.EnqueueJob '{"names": ["varlink-test-enqueue.service"], "jobType": "stop"}' | jq -e '.job.JobType == "stop"'
|
||||
timeout 10 bash -c 'until systemctl show -P ActiveState varlink-test-enqueue.service | grep inactive >/dev/null; do sleep 0.5; done'
|
||||
|
||||
# Multiple units share a transaction and receive one reply per job.
|
||||
varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.EnqueueJob '{"names": ["varlink-test-enqueue.service", "varlink-test-enqueue-2.service"], "jobType": "start"}' | jq -e 'length == 2 and all(.[]; .context and .runtime and .job.Id) and ([.[].job.Unit] | sort == ["varlink-test-enqueue-2.service", "varlink-test-enqueue.service"])'
|
||||
timeout 10 bash -c 'until systemctl is-active varlink-test-enqueue.service; do sleep 0.5; done'
|
||||
timeout 10 bash -c 'until systemctl is-active varlink-test-enqueue-2.service; do sleep 0.5; done'
|
||||
systemctl stop varlink-test-enqueue{,-2}.service
|
||||
|
||||
# Multiple replies require 'more'.
|
||||
(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.EnqueueJob '{"names": ["varlink-test-enqueue.service", "varlink-test-enqueue-2.service"], "jobType": "start"}')
|
||||
|
||||
# A missing unit rejects the entire transaction.
|
||||
(! varlinkctl call --more /run/systemd/io.systemd.Manager io.systemd.Unit.EnqueueJob '{"names": ["varlink-test-enqueue.service", "non-existent.service"], "jobType": "start"}')
|
||||
(! systemctl is-active varlink-test-enqueue.service)
|
||||
|
||||
# Notifications require 'more' and a single unit.
|
||||
(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.EnqueueJob '{"names": ["varlink-test-enqueue.service"], "jobType": "start", "notifyJobChanges": true}')
|
||||
(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.EnqueueJob '{"names": ["varlink-test-enqueue.service"], "jobType": "start", "notifyUnitChanges": true}')
|
||||
|
||||
(! varlinkctl call --more /run/systemd/io.systemd.Manager io.systemd.Unit.EnqueueJob '{"names": ["varlink-test-enqueue.service", "varlink-test-enqueue-2.service"], "jobType": "start", "notifyJobChanges": true}')
|
||||
|
||||
# Error cases
|
||||
(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.EnqueueJob '{"names": ["invalid-unit-name"], "jobType": "start"}')
|
||||
(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.EnqueueJob '{"names": [], "jobType": "start"}')
|
||||
|
||||
# Coalesced jobs must reject a second watcher.
|
||||
cat >/run/systemd/system/varlink-test-slow.service <<UNIT
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/bin/sleep infinity
|
||||
RemainAfterExit=yes
|
||||
UNIT
|
||||
systemctl daemon-reload
|
||||
|
||||
varlinkctl call --more /run/systemd/io.systemd.Manager io.systemd.Unit.EnqueueJob \
|
||||
'{"names": ["varlink-test-slow.service"], "jobType": "start", "notifyJobChanges": true}' >/dev/null 2>&1 &
|
||||
watcher_pid=$!
|
||||
|
||||
# Wait for the first watcher to attach.
|
||||
timeout 10 bash -c 'until systemctl show -P ActiveState varlink-test-slow.service | grep activating >/dev/null; do sleep 0.5; done'
|
||||
|
||||
set +o pipefail
|
||||
varlinkctl call --more /run/systemd/io.systemd.Manager io.systemd.Unit.EnqueueJob \
|
||||
'{"names": ["varlink-test-slow.service"], "jobType": "start", "notifyJobChanges": true}' |& grep "io.systemd.Unit.AlreadyBeingWatched"
|
||||
set -o pipefail
|
||||
|
||||
systemctl stop varlink-test-slow.service
|
||||
wait "$watcher_pid" 2>/dev/null || true
|
||||
|
||||
# Completing a NOP job must preserve the regular job's unit subscription.
|
||||
varlinkctl --json=short call --more /run/systemd/io.systemd.Manager io.systemd.Unit.EnqueueJob \
|
||||
'{"names": ["varlink-test-slow.service"], "jobType": "start", "notifyUnitChanges": true}' >/tmp/enqueue-notifications.json &
|
||||
watcher_pid=$!
|
||||
|
||||
timeout 10 bash -c 'until systemctl show -P ActiveState varlink-test-slow.service | grep activating >/dev/null; do sleep 0.5; done'
|
||||
|
||||
# The pending start job keeps the stream open; each line is a unit notification.
|
||||
sleep 1
|
||||
notifications=$(wc -l </tmp/enqueue-notifications.json)
|
||||
|
||||
# NOP uses a separate job slot and completes immediately.
|
||||
varlinkctl call --more /run/systemd/io.systemd.Manager io.systemd.Unit.EnqueueJob \
|
||||
'{"names": ["varlink-test-slow.service"], "jobType": "nop", "notifyJobChanges": true}' >/dev/null
|
||||
|
||||
# NOP removal must still trigger a unit notification.
|
||||
for _ in {1..20}; do
|
||||
[[ "$(wc -l </tmp/enqueue-notifications.json)" -gt "$notifications" ]] && break
|
||||
sleep 0.1
|
||||
done
|
||||
[[ "$(wc -l </tmp/enqueue-notifications.json)" -gt "$notifications" ]]
|
||||
|
||||
systemctl stop varlink-test-slow.service
|
||||
wait "$watcher_pid" 2>/dev/null || true
|
||||
|
||||
# test io.systemd.Unit in user manager
|
||||
testuser_uid=$(id -u testuser)
|
||||
systemd-run --wait --pipe --user --machine testuser@ \
|
||||
varlinkctl --more call "/run/user/$testuser_uid/systemd/io.systemd.Manager" io.systemd.Unit.List '{}'
|
||||
|
||||
# Test io.systemd.Unit.StartTransient
|
||||
MANAGER_SOCKET="/run/systemd/io.systemd.Manager"
|
||||
|
||||
defer_transient_cleanup() {
|
||||
TRANSIENT_UNITS+=("$1")
|
||||
testcase_list() {
|
||||
# test io.systemd.Unit
|
||||
varlinkctl info /run/systemd/io.systemd.Manager
|
||||
varlinkctl introspect /run/systemd/io.systemd.Manager io.systemd.Unit
|
||||
varlinkctl --more call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}'
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"name": "multi-user.target"}'
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"pid": {"pid": 1}}'
|
||||
(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' |& grep "called without 'more' flag" >/dev/null)
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"name": "init.scope", "pid": {"pid": 1}}'
|
||||
(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"name": ""}')
|
||||
(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"name": "non-existent.service"}')
|
||||
(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"pid": {"pid": -1}}' )
|
||||
(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"name": "multi-user.target", "pid": {"pid": 1}}')
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"cgroup": "/init.scope"}'
|
||||
invocation_id="$(systemctl show -P InvocationID systemd-journald.service)"
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "{\"invocationID\": \"$invocation_id\"}"
|
||||
}
|
||||
|
||||
# Basic oneshot transient service
|
||||
defer_transient_cleanup varlink-transient-test.service
|
||||
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-test.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}')
|
||||
echo "$result" | grep '"context"' >/dev/null
|
||||
echo "$result" | grep '"runtime"' >/dev/null
|
||||
|
||||
# Wait for completion
|
||||
timeout 30 bash -c 'until systemctl show -P ActiveState varlink-transient-test.service | grep inactive >/dev/null; do sleep 0.5; done'
|
||||
systemctl show -P Result varlink-transient-test.service | grep success >/dev/null
|
||||
|
||||
# With explicit mode
|
||||
defer_transient_cleanup varlink-transient-test2.service
|
||||
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-test2.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}},"mode":"fail"}')
|
||||
echo "$result" | grep '"context"' >/dev/null
|
||||
|
||||
# Streaming with notifyJobChanges: should get intermediate state updates and a final result
|
||||
# Note: use --slurp + any() rather than 'select() -e' because in jq 1.6 (shipped on
|
||||
# CentOS 9) -e checks only the last input record's output, so a select() that filters
|
||||
# out the trailing record makes jq exit non-zero even when earlier records match.
|
||||
defer_transient_cleanup varlink-transient-test3.service
|
||||
result=$(varlinkctl call --more "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-test3.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}},"notifyJobChanges":true}')
|
||||
printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .job.State == "waiting")' >/dev/null
|
||||
printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .job.Result == "done")' >/dev/null
|
||||
|
||||
# Fire-and-forget: --more without notify flags should return immediately with context+runtime
|
||||
defer_transient_cleanup varlink-transient-fireforget.service
|
||||
result=$(varlinkctl call --more "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-fireforget.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}')
|
||||
printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .context)' >/dev/null
|
||||
printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .runtime)' >/dev/null
|
||||
|
||||
# Streaming with notifyUnitChanges: should get unit state change notifications
|
||||
defer_transient_cleanup varlink-transient-unitnotify.service
|
||||
result=$(varlinkctl call --more "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-unitnotify.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}},"notifyUnitChanges":true}')
|
||||
printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .runtime.ActiveState)' >/dev/null
|
||||
|
||||
# Streaming with both notifyJobChanges and notifyUnitChanges
|
||||
defer_transient_cleanup varlink-transient-both.service
|
||||
result=$(varlinkctl call --more "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-both.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}},"notifyJobChanges":true,"notifyUnitChanges":true}')
|
||||
printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .job.State)' >/dev/null
|
||||
printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .runtime.ActiveState)' >/dev/null
|
||||
printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .job.Result == "done")' >/dev/null
|
||||
|
||||
# prepare for the error case below: create a long-running service, then try to create it again while it's active
|
||||
defer_transient_cleanup varlink-transient-exists.service
|
||||
varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-exists.service","Service":{"ExecStart":[{"path":"/usr/bin/sleep","arguments":["/usr/bin/sleep","infinity"]}]}}}'
|
||||
timeout 10 bash -c 'until systemctl is-active varlink-transient-exists.service; do sleep 0.5; done'
|
||||
|
||||
# Multiple ExecStart commands (oneshot allows multiple)
|
||||
defer_transient_cleanup varlink-transient-multi.service
|
||||
result=$(varlinkctl call --more "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-multi.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"},{"path":"/bin/true"}]}},"notifyJobChanges":true}')
|
||||
printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .job.Result == "done")' >/dev/null
|
||||
|
||||
# Transient service with Description and RemainAfterExit
|
||||
defer_transient_cleanup varlink-transient-desc.service
|
||||
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-desc.service","Description":"Test description property","CollectMode":"inactive_or_failed","Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}')
|
||||
echo "$result" | jq -e '.context.Description == "Test description property"'
|
||||
echo "$result" | jq -e '.context.CollectMode == "inactive_or_failed"'
|
||||
echo "$result" | jq -e '.context.Service.Type == "oneshot"'
|
||||
echo "$result" | jq -e '.context.Service.RemainAfterExit == true'
|
||||
echo "$result" | jq -e '.context.Service.ExecStart[0].path == "/bin/true"'
|
||||
echo "$result" | jq -e '.runtime'
|
||||
systemctl show -P CollectMode varlink-transient-desc.service | grep '^inactive-or-failed$' >/dev/null
|
||||
|
||||
# Transient service with explicit arguments
|
||||
defer_transient_cleanup varlink-transient-args.service
|
||||
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-args.service","Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/echo","arguments":["/bin/echo","hello"]}]}}}')
|
||||
echo "$result" | jq -e '.context'
|
||||
echo "$result" | jq -e '.runtime'
|
||||
echo "$result" | jq -e '.context.Service.ExecStart[0].path == "/bin/echo"'
|
||||
echo "$result" | jq -e '.context.Service.ExecStart[0].arguments == ["/bin/echo", "hello"]'
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-args.service; do sleep 0.5; done'
|
||||
|
||||
# Verify that omitting arguments defaults argv[0] to the path
|
||||
defer_transient_cleanup varlink-transient-noargs.service
|
||||
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-noargs.service","Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}')
|
||||
echo "$result" | jq -e '.context.Service.ExecStart[0].arguments == ["/bin/true"]'
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-noargs.service; do sleep 0.5; done'
|
||||
|
||||
# Exec.WorkingDirectory and Exec.Environment
|
||||
defer_transient_cleanup varlink-transient-exec.service
|
||||
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-exec.service","Exec":{"WorkingDirectory":{"path":"/tmp","missingOK":false},"Environment":["FOO=bar","BAZ=qux"]},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}')
|
||||
echo "$result" | jq -e '.context.Exec.WorkingDirectory.path == "/tmp"'
|
||||
echo "$result" | jq -e '.context.Exec.Environment | index("FOO=bar") != null'
|
||||
echo "$result" | jq -e '.context.Exec.Environment | index("BAZ=qux") != null'
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-exec.service; do sleep 0.5; done'
|
||||
systemctl show -P WorkingDirectory varlink-transient-exec.service | grep '^/tmp$' >/dev/null
|
||||
systemctl show -P Environment varlink-transient-exec.service | grep 'FOO=bar' >/dev/null
|
||||
systemctl show -P Environment varlink-transient-exec.service | grep 'BAZ=qux' >/dev/null
|
||||
|
||||
# WorkingDirectory with missingOK=true (path does not exist but unit still starts)
|
||||
defer_transient_cleanup varlink-transient-wd-missing.service
|
||||
varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-wd-missing.service","Exec":{"WorkingDirectory":{"path":"/nonexistent/path","missingOK":true}},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}'
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-wd-missing.service; do sleep 0.5; done'
|
||||
|
||||
# WorkingDirectory with home=true, missingOK omitted (defaults to false)
|
||||
defer_transient_cleanup varlink-transient-wd-home.service
|
||||
varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-wd-home.service","Exec":{"WorkingDirectory":{"home":true}},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}'
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-wd-home.service; do sleep 0.5; done'
|
||||
systemctl show -P WorkingDirectory varlink-transient-wd-home.service | grep '^~$' >/dev/null
|
||||
|
||||
# Exec.StandardInput/StandardOutput/StandardError enum properties and Exec.TTYPath
|
||||
defer_transient_cleanup varlink-transient-stdio.service
|
||||
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-stdio.service","Exec":{"StandardInput":"null","StandardOutput":"null","StandardError":"journal","TTYPath":"/dev/tty2"},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}')
|
||||
echo "$result" | jq -e '.context.Exec.StandardInput == "null"'
|
||||
echo "$result" | jq -e '.context.Exec.StandardOutput == "null"'
|
||||
echo "$result" | jq -e '.context.Exec.StandardError == "journal"'
|
||||
echo "$result" | jq -e '.context.Exec.TTYPath == "/dev/tty2"'
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-stdio.service; do sleep 0.5; done'
|
||||
systemctl show -P StandardInput varlink-transient-stdio.service | grep '^null$' >/dev/null
|
||||
systemctl show -P StandardOutput varlink-transient-stdio.service | grep '^null$' >/dev/null
|
||||
systemctl show -P StandardError varlink-transient-stdio.service | grep '^journal$' >/dev/null
|
||||
systemctl show -P TTYPath varlink-transient-stdio.service | grep '^/dev/tty2$' >/dev/null
|
||||
# "null" is also the default for StandardInput=, hence additionally verify it was written explicitly
|
||||
fragment=$(systemctl show -P FragmentPath varlink-transient-stdio.service)
|
||||
grep '^StandardInput=null$' "$fragment" >/dev/null
|
||||
|
||||
# The combined output types are spelled with a "+" in unit files, but with a "_" in the Varlink IDL (and
|
||||
# hence in our own replies). The latter spelling must be accepted, so that replies can be fed back in.
|
||||
defer_transient_cleanup varlink-transient-stdio-combined.service
|
||||
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-stdio-combined.service","Exec":{"StandardOutput":"kmsg_console","StandardError":"journal_console"},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}')
|
||||
echo "$result" | jq -e '.context.Exec.StandardOutput == "kmsg_console"'
|
||||
echo "$result" | jq -e '.context.Exec.StandardError == "journal_console"'
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-stdio-combined.service; do sleep 0.5; done'
|
||||
systemctl show -P StandardOutput varlink-transient-stdio-combined.service | grep '^kmsg+console$' >/dev/null
|
||||
systemctl show -P StandardError varlink-transient-stdio-combined.service | grep '^journal+console$' >/dev/null
|
||||
|
||||
# Kill.KillMode/Kill.SendSIGHUP
|
||||
defer_transient_cleanup varlink-transient-kill.service
|
||||
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-kill.service","Kill":{"KillMode":"process","SendSIGHUP":true},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}')
|
||||
echo "$result" | jq -e '.context.Kill.KillMode == "process"'
|
||||
echo "$result" | jq -e '.context.Kill.SendSIGHUP == true'
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-kill.service; do sleep 0.5; done'
|
||||
systemctl show -P KillMode varlink-transient-kill.service | grep '^process$' >/dev/null
|
||||
systemctl show -P SendSIGHUP varlink-transient-kill.service | grep '^yes$' >/dev/null
|
||||
|
||||
# ExecStart execution flags, i.e. the Varlink counterparts of the "-", ":", "|", "+" and "!" prefixes of
|
||||
# unit file ExecStart= lines. First the ones observable from an unprivileged unit:
|
||||
# - ignoreFailure: a failing command does not fail the unit
|
||||
# - noEnvExpand: "${VAR}" in the arguments is passed as-is rather than expanded
|
||||
# - viaShell: the command line is handed to the user's shell via "-c" (argv[0] is normalized to "sh")
|
||||
rm -f /tmp/varlink-transient-execflags-*
|
||||
defer_transient_cleanup varlink-transient-execflags.service
|
||||
# shellcheck disable=SC2016
|
||||
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-execflags.service","Exec":{"Environment":["FLAGTEST=expanded"]},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[
|
||||
{"path":"/bin/false","ignoreFailure":true},
|
||||
{"path":"/usr/bin/touch","arguments":["touch","/tmp/varlink-transient-execflags-${FLAGTEST}"]},
|
||||
{"path":"/usr/bin/touch","arguments":["touch","/tmp/varlink-transient-execflags-${FLAGTEST}"],"noEnvExpand":true},
|
||||
{"path":"/bin/sh","arguments":["sh","touch","/tmp/varlink-transient-execflags-shell"],"viaShell":true}]}}}')
|
||||
echo "$result" | jq -e '.context.Service.ExecStart[0].ignoreFailure == true'
|
||||
echo "$result" | jq -e '.context.Service.ExecStart[1].noEnvExpand == false'
|
||||
echo "$result" | jq -e '.context.Service.ExecStart[2].noEnvExpand == true'
|
||||
echo "$result" | jq -e '.context.Service.ExecStart[3].viaShell == true'
|
||||
echo "$result" | jq -e '.context.Service.ExecStart[3].path == "/bin/sh"'
|
||||
echo "$result" | jq -e '.context.Service.ExecStart[3].arguments[0] == "sh"'
|
||||
# The unit only becomes active once all ExecStart= commands succeeded (or were allowed to fail)
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-execflags.service; do sleep 0.5; done'
|
||||
test -e /tmp/varlink-transient-execflags-expanded
|
||||
# shellcheck disable=SC2016
|
||||
test -e '/tmp/varlink-transient-execflags-${FLAGTEST}'
|
||||
test -e /tmp/varlink-transient-execflags-shell
|
||||
# The flags must show up as the matching prefix characters in the transient unit file
|
||||
fragment=$(systemctl show -P FragmentPath varlink-transient-execflags.service)
|
||||
grep -E '^ExecStart=-"/bin/false"$' "$fragment" >/dev/null
|
||||
grep -E '^ExecStart=:' "$fragment" >/dev/null
|
||||
grep -E '^ExecStart=\|' "$fragment" >/dev/null
|
||||
rm -f /tmp/varlink-transient-execflags-*
|
||||
|
||||
# Now the privilege related ones, in a unit running as an unprivileged user:
|
||||
# - privileged/noSetuid: the command runs with full privileges, i.e. can write where the user cannot
|
||||
# - (neither): the command runs as the user, and fails to write there (tolerated via ignoreFailure)
|
||||
rm -f /run/varlink-transient-execflags-*
|
||||
defer_transient_cleanup varlink-transient-execflags-priv.service
|
||||
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-execflags-priv.service","Exec":{"User":"nobody"},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[
|
||||
{"path":"/usr/bin/touch","arguments":["touch","/run/varlink-transient-execflags-plain"],"ignoreFailure":true},
|
||||
{"path":"/usr/bin/touch","arguments":["touch","/run/varlink-transient-execflags-privileged"],"privileged":true},
|
||||
{"path":"/usr/bin/touch","arguments":["touch","/run/varlink-transient-execflags-nosetuid"],"noSetuid":true}]}}}')
|
||||
echo "$result" | jq -e '.context.Service.ExecStart[0].privileged == false'
|
||||
echo "$result" | jq -e '.context.Service.ExecStart[1].privileged == true'
|
||||
echo "$result" | jq -e '.context.Service.ExecStart[2].noSetuid == true'
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-execflags-priv.service; do sleep 0.5; done'
|
||||
test ! -e /run/varlink-transient-execflags-plain
|
||||
test -e /run/varlink-transient-execflags-privileged
|
||||
test -e /run/varlink-transient-execflags-nosetuid
|
||||
fragment=$(systemctl show -P FragmentPath varlink-transient-execflags-priv.service)
|
||||
grep -E '^ExecStart=\+' "$fragment" >/dev/null
|
||||
grep -E '^ExecStart=!' "$fragment" >/dev/null
|
||||
rm -f /run/varlink-transient-execflags-*
|
||||
|
||||
# Exec.SetCredential: pass a credential and verify the running process can read it
|
||||
defer_transient_cleanup varlink-transient-cred.service
|
||||
CRED_VALUE_B64=$(printf 'secret-value' | base64 -w0)
|
||||
CRED_OUTPUT=$(mktemp)
|
||||
varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
"{\"context\":{\"ID\":\"varlink-transient-cred.service\",\"Exec\":{\"SetCredential\":[{\"id\":\"mycred\",\"value\":\"${CRED_VALUE_B64}\"}]},\"Service\":{\"Type\":\"oneshot\",\"RemainAfterExit\":true,\"ExecStart\":[{\"path\":\"/bin/sh\",\"arguments\":[\"/bin/sh\",\"-c\",\"cat \$CREDENTIALS_DIRECTORY/mycred > ${CRED_OUTPUT}\"]}]}}}"
|
||||
timeout 30 bash -c "until systemctl is-active varlink-transient-cred.service; do sleep 0.5; done"
|
||||
grep '^secret-value$' "$CRED_OUTPUT" >/dev/null
|
||||
rm -f "$CRED_OUTPUT"
|
||||
|
||||
# Exec.User, Exec.Group, Exec.SupplementaryGroups, Exec.Nice
|
||||
# The nobody group is different on different distros so resolve here.
|
||||
NOBODY_GROUP=$(id -gn nobody)
|
||||
defer_transient_cleanup varlink-transient-ids.service
|
||||
ids_payload=$(jq -cn --arg g "$NOBODY_GROUP" \
|
||||
'{context:{ID:"varlink-transient-ids.service",
|
||||
Exec:{User:"nobody",Group:$g,SupplementaryGroups:[$g],Nice:5},
|
||||
Service:{Type:"oneshot",RemainAfterExit:true,
|
||||
ExecStart:[{path:"/bin/true"}]}}}')
|
||||
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient "$ids_payload")
|
||||
echo "$result" | jq -e '.context.Exec.User == "nobody"'
|
||||
echo "$result" | jq --arg g "$NOBODY_GROUP" -e '.context.Exec.Group == $g'
|
||||
echo "$result" | jq --arg g "$NOBODY_GROUP" -e '.context.Exec.SupplementaryGroups == [$g]'
|
||||
echo "$result" | jq -e '.context.Exec.Nice == 5'
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-ids.service; do sleep 0.5; done'
|
||||
systemctl show -P User varlink-transient-ids.service | grep '^nobody$' >/dev/null
|
||||
systemctl show -P Group varlink-transient-ids.service | grep "^${NOBODY_GROUP}$" >/dev/null
|
||||
systemctl show -P SupplementaryGroups varlink-transient-ids.service | grep "${NOBODY_GROUP}" >/dev/null
|
||||
systemctl show -P Nice varlink-transient-ids.service | grep '^5$' >/dev/null
|
||||
|
||||
# Exec.OOMScoreAdjust, Exec.UMask, Exec.NoNewPrivileges, Exec.MemoryDenyWriteExecute
|
||||
# (int+validator, mode_t, two tristate-bool shapes)
|
||||
defer_transient_cleanup varlink-transient-procctl.service
|
||||
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-procctl.service","Exec":{"OOMScoreAdjust":250,"UMask":18,"NoNewPrivileges":true,"MemoryDenyWriteExecute":true},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}')
|
||||
echo "$result" | jq -e '.context.Exec.OOMScoreAdjust == 250'
|
||||
echo "$result" | jq -e '.context.Exec.UMask == 18'
|
||||
echo "$result" | jq -e '.context.Exec.NoNewPrivileges == true'
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-procctl.service; do sleep 0.5; done'
|
||||
systemctl show -P OOMScoreAdjust varlink-transient-procctl.service | grep '^250$' >/dev/null
|
||||
systemctl show -P UMask varlink-transient-procctl.service | grep '^0022$' >/dev/null
|
||||
systemctl show -P NoNewPrivileges varlink-transient-procctl.service | grep '^yes$' >/dev/null
|
||||
systemctl show -P MemoryDenyWriteExecute varlink-transient-procctl.service | grep '^yes$' >/dev/null
|
||||
|
||||
# Exec.RootHashPath / Exec.RootHashSignaturePath: the unit-file directive must be
|
||||
# "RootHash=" / "RootHashSignature=" (not the JSON name), otherwise the next daemon-reload
|
||||
# would drop the setting as an unknown key.
|
||||
defer_transient_cleanup varlink-transient-roothash.service
|
||||
varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-roothash.service","Exec":{"RootHashPath":"/etc/hostname","RootHashSignaturePath":"/etc/machine-id"},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}' >/dev/null
|
||||
fragment=$(systemctl show -P FragmentPath varlink-transient-roothash.service)
|
||||
test -n "$fragment"
|
||||
grep '^RootHash=/etc/hostname$' "$fragment" >/dev/null
|
||||
grep '^RootHashSignature=/etc/machine-id$' "$fragment" >/dev/null
|
||||
|
||||
# Service.Standard{Output,Error}FileDescriptor: connect passed fds (by push order)
|
||||
# to the unit's stdout/stderr; regular files, so output can be checked after exit.
|
||||
transient_out=$(mktemp)
|
||||
transient_err=$(mktemp)
|
||||
exec {transient_out_fd}>"$transient_out"
|
||||
exec {transient_err_fd}>"$transient_err"
|
||||
defer_transient_cleanup varlink-transient-fdpass.service
|
||||
varlinkctl --push-fd="$transient_out_fd" --push-fd="$transient_err_fd" \
|
||||
call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-fdpass.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/sh","arguments":["/bin/sh","-c","echo to-stdout; echo to-stderr >&2"]}],"StandardOutputFileDescriptor":0,"StandardErrorFileDescriptor":1}}}' >/dev/null
|
||||
# Close our copies so the unit holds the only remaining write ends.
|
||||
exec {transient_out_fd}>&-
|
||||
exec {transient_err_fd}>&-
|
||||
timeout 30 bash -c 'until systemctl show -P ActiveState varlink-transient-fdpass.service | grep inactive >/dev/null; do sleep 0.5; done'
|
||||
systemctl show -P Result varlink-transient-fdpass.service | grep success >/dev/null
|
||||
grep -x to-stdout "$transient_out" >/dev/null
|
||||
grep -x to-stderr "$transient_err" >/dev/null
|
||||
rm -f "$transient_out" "$transient_err"
|
||||
|
||||
# Starting a transient scope
|
||||
defer_transient_cleanup varlink-transient-scope.scope
|
||||
sleep infinity &
|
||||
scope_child="$!"
|
||||
varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
"{\"context\":{\"ID\":\"varlink-transient-scope.scope\",\"Scope\":{\"PIDs\":[{\"pid\":${scope_child}}],\"OOMPolicy\":\"kill\", \"TimeoutStopUSec\":12000000}}}"
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-scope.scope; do sleep 0.5; done'
|
||||
systemctl whoami "$scope_child" | grep "^varlink-transient-scope.scope$" >/dev/null
|
||||
systemctl show -P TimeoutStopUSec varlink-transient-scope.scope | grep 12s >/dev/null
|
||||
systemctl show -P OOMPolicy varlink-transient-scope.scope | grep kill >/dev/null
|
||||
|
||||
# Unit dependencies
|
||||
defer_transient_cleanup varlink-transient-deps.service
|
||||
varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-deps.service","After":["varlink-transient-test.service"],"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}'
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-deps.service; do sleep 0.5; done'
|
||||
systemctl show -P After varlink-transient-deps.service | grep varlink-transient-test.service >/dev/null
|
||||
|
||||
# CGroup context: scalar resource-control properties round-trip into the reply
|
||||
# context, systemctl show and the transient fragment
|
||||
defer_transient_cleanup varlink-transient-cgroup.service
|
||||
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-cgroup.service","CGroup":{"CPUWeight":200,"StartupCPUWeight":150,"CPUQuotaPerSecUSec":500000,"CPUQuotaPeriodUSec":20000,"MemoryAccounting":true,"MemoryLow":8388608,"StartupMemoryLow":4194304,"MemoryHigh":33554432,"MemoryMax":67108864,"TasksAccounting":true,"TasksMax":{"value":16},"IOAccounting":true,"IOWeight":100},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}')
|
||||
echo "$result" | jq -e '.context.CGroup.CPUWeight == 200'
|
||||
echo "$result" | jq -e '.context.CGroup.StartupCPUWeight == 150'
|
||||
echo "$result" | jq -e '.context.CGroup.CPUQuotaPerSecUSec == 500000'
|
||||
echo "$result" | jq -e '.context.CGroup.CPUQuotaPeriodUSec == 20000'
|
||||
echo "$result" | jq -e '.context.CGroup.StartupMemoryLow == 4194304'
|
||||
echo "$result" | jq -e '.context.CGroup.MemoryMax == 67108864'
|
||||
echo "$result" | jq -e '.context.CGroup.TasksMax == {"value":16,"scale":0}'
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-cgroup.service; do sleep 0.5; done'
|
||||
systemctl show -P CPUWeight varlink-transient-cgroup.service | grep -Fx 200 >/dev/null
|
||||
systemctl show -P CPUQuotaPerSecUSec varlink-transient-cgroup.service | grep -Fx 500ms >/dev/null
|
||||
systemctl show -P CPUQuotaPeriodUSec varlink-transient-cgroup.service | grep -Fx 20ms >/dev/null
|
||||
systemctl show -P MemoryLow varlink-transient-cgroup.service | grep -Fx 8388608 >/dev/null
|
||||
systemctl show -P MemoryHigh varlink-transient-cgroup.service | grep -Fx 33554432 >/dev/null
|
||||
systemctl show -P MemoryMax varlink-transient-cgroup.service | grep -Fx 67108864 >/dev/null
|
||||
systemctl show -P TasksMax varlink-transient-cgroup.service | grep -Fx 16 >/dev/null
|
||||
systemctl show -P IOWeight varlink-transient-cgroup.service | grep -Fx 100 >/dev/null
|
||||
# The fragment must use the unit-file directive spellings (CPUQuota=, not the JSON
|
||||
# property name), otherwise the next daemon-reload would drop the setting.
|
||||
fragment=$(systemctl show -P FragmentPath varlink-transient-cgroup.service)
|
||||
test -n "$fragment"
|
||||
grep -Fx 'CPUQuota=50.00%' "$fragment" >/dev/null
|
||||
grep -Fx 'CPUQuotaPeriodSec=20ms' "$fragment" >/dev/null
|
||||
grep -Fx 'MemoryMax=67108864' "$fragment" >/dev/null
|
||||
grep -Fx 'TasksMax=16' "$fragment" >/dev/null
|
||||
|
||||
# The special values are written as TasksMax=infinity, CPUWeight=idle and an empty
|
||||
# StartupCPUWeight= (UINT64_MAX resets a weight to the default)
|
||||
defer_transient_cleanup varlink-transient-cgroup-special.service
|
||||
varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-cgroup-special.service","CGroup":{"TasksMax":{"value":18446744073709551615},"CPUWeight":0,"StartupCPUWeight":18446744073709551615},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}' >/dev/null
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-cgroup-special.service; do sleep 0.5; done'
|
||||
systemctl show -P TasksMax varlink-transient-cgroup-special.service | grep -Fx infinity >/dev/null
|
||||
fragment=$(systemctl show -P FragmentPath varlink-transient-cgroup-special.service)
|
||||
test -n "$fragment"
|
||||
grep -Fx 'TasksMax=infinity' "$fragment" >/dev/null
|
||||
grep -Fx 'CPUWeight=idle' "$fragment" >/dev/null
|
||||
grep -Fx 'StartupCPUWeight=' "$fragment" >/dev/null
|
||||
|
||||
# Cgroup limits materialize on the live cgroup
|
||||
defer_transient_cleanup varlink-transient-cgroup-live.service
|
||||
varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-cgroup-live.service","CGroup":{"MemoryMax":67108864,"TasksMax":{"value":16}},"Service":{"ExecStart":[{"path":"/usr/bin/sleep","arguments":["/usr/bin/sleep","infinity"]}]}}}' >/dev/null
|
||||
timeout 10 bash -c 'until systemctl is-active varlink-transient-cgroup-live.service; do sleep 0.5; done'
|
||||
cgroup_path=$(systemctl show -P ControlGroup varlink-transient-cgroup-live.service)
|
||||
test -n "$cgroup_path"
|
||||
grep -Fx 67108864 "/sys/fs/cgroup${cgroup_path}/memory.max" >/dev/null
|
||||
grep -Fx 16 "/sys/fs/cgroup${cgroup_path}/pids.max" >/dev/null
|
||||
|
||||
# Slice= places the unit under an implicitly created slice
|
||||
defer_transient_cleanup varlink-transient-slice.service
|
||||
defer_transient_cleanup varlink-test.slice
|
||||
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-slice.service","CGroup":{"Slice":"varlink-test.slice"},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}')
|
||||
echo "$result" | jq -e '.context.CGroup.Slice == "varlink-test.slice"'
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-slice.service; do sleep 0.5; done'
|
||||
systemctl show -P Slice varlink-transient-slice.service | grep -Fx varlink-test.slice >/dev/null
|
||||
|
||||
# Exec sandbox scalars: enum and yes/no-string properties round-trip into the reply
|
||||
# context (Varlink spelling), systemctl show and the transient fragment (unit-file
|
||||
# spelling, so the next daemon-reload keeps the setting)
|
||||
defer_transient_cleanup varlink-transient-sandbox.service
|
||||
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-sandbox.service","Exec":{"ProtectSystem":"strict","ProtectHome":"read_only","PrivateTmp":"disconnected","PrivateNetwork":"yes","PrivateUsers":"self"},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}')
|
||||
echo "$result" | jq -e '.context.Exec.ProtectSystem == "strict"'
|
||||
echo "$result" | jq -e '.context.Exec.ProtectHome == "read_only"'
|
||||
echo "$result" | jq -e '.context.Exec.PrivateTmp == "disconnected"'
|
||||
echo "$result" | jq -e '.context.Exec.PrivateNetwork == "yes"'
|
||||
echo "$result" | jq -e '.context.Exec.PrivateUsers == "self"'
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-sandbox.service; do sleep 0.5; done'
|
||||
systemctl show -P ProtectSystem varlink-transient-sandbox.service | grep '^strict$' >/dev/null
|
||||
systemctl show -P ProtectHome varlink-transient-sandbox.service | grep '^read-only$' >/dev/null
|
||||
systemctl show -P PrivateTmpEx varlink-transient-sandbox.service | grep '^disconnected$' >/dev/null
|
||||
systemctl show -P PrivateNetwork varlink-transient-sandbox.service | grep '^yes$' >/dev/null
|
||||
systemctl show -P PrivateUsersEx varlink-transient-sandbox.service | grep '^self$' >/dev/null
|
||||
fragment=$(systemctl show -P FragmentPath varlink-transient-sandbox.service)
|
||||
test -n "$fragment"
|
||||
grep '^ProtectSystem=strict$' "$fragment" >/dev/null
|
||||
grep '^ProtectHome=read-only$' "$fragment" >/dev/null
|
||||
grep '^PrivateTmp=disconnected$' "$fragment" >/dev/null
|
||||
grep '^PrivateNetwork=yes$' "$fragment" >/dev/null
|
||||
grep '^PrivateUsers=self$' "$fragment" >/dev/null
|
||||
|
||||
# The sandbox is live, not just recorded: the unit only succeeds if /usr is read-only
|
||||
# and its network namespace differs from PID 1's. Both readlinks are captured first, so
|
||||
# an unreadable link fails the unit rather than passing it. Comparing namespaces beats
|
||||
# listing interfaces here: the harness runs with --runtime-network=none, and the netns
|
||||
# fallback devices come and go with the loaded modules.
|
||||
defer_transient_cleanup varlink-transient-sandbox-live.service
|
||||
# shellcheck disable=SC2016
|
||||
varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-sandbox-live.service","Exec":{"ProtectSystem":"strict","PrivateNetwork":"yes"},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/sh","arguments":["/bin/sh","-c","p=$(readlink /proc/1/ns/net) && s=$(readlink /proc/self/ns/net) && test \"$s\" != \"$p\" && ! touch /usr/varlink-sandbox-canary"]}]}}}' >/dev/null
|
||||
timeout 30 bash -c 'until systemctl show -P ActiveState varlink-transient-sandbox-live.service | grep -Ex "active|failed" >/dev/null; do sleep 0.5; done'
|
||||
systemctl show -P Result varlink-transient-sandbox-live.service | grep '^success$' >/dev/null
|
||||
test ! -e /usr/varlink-sandbox-canary
|
||||
|
||||
# Relative forms: TasksMaxScale/MemoryMaxScale (floats, 1.0 == 100%) are input only,
|
||||
# converted internally and written to the fragment as permyriad percentages. An
|
||||
# explicit null is treated as absent (so MemoryMax=null does not conflict with
|
||||
# MemoryMaxScale), and a zero scale is accepted for the min-0 memory properties,
|
||||
# as over D-Bus
|
||||
defer_transient_cleanup varlink-transient-cgroup-scale.service
|
||||
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-cgroup-scale.service","CGroup":{"TasksMaxScale":0.5,"MemoryMax":null,"MemoryMaxScale":0.125,"MemoryMinScale":0.0,"TasksAccounting":null},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}')
|
||||
echo "$result" | jq -e '.context.CGroup.TasksMax == {"value":5000,"scale":10000}'
|
||||
echo "$result" | jq -e '.context.CGroup.TasksMaxScale == null'
|
||||
timeout 30 bash -c 'until systemctl is-active varlink-transient-cgroup-scale.service; do sleep 0.5; done'
|
||||
fragment=$(systemctl show -P FragmentPath varlink-transient-cgroup-scale.service)
|
||||
test -n "$fragment"
|
||||
grep -Fx 'TasksMax=50.00%' "$fragment" >/dev/null
|
||||
grep -Fx 'MemoryMax=12.50%' "$fragment" >/dev/null
|
||||
grep -Fx 'MemoryMin=0.00%' "$fragment" >/dev/null
|
||||
(! grep -F 'TasksAccounting=' "$fragment")
|
||||
|
||||
# A null scale member of TasksMax is absent, not a request for the relative form
|
||||
defer_transient_cleanup varlink-transient-cgroup-nullscale.service
|
||||
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-cgroup-nullscale.service","CGroup":{"TasksMax":{"value":16,"scale":null}},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}')
|
||||
echo "$result" | jq -e '.context.CGroup.TasksMax == {"value":16,"scale":0}'
|
||||
|
||||
# Error cases: verify specific varlink error types
|
||||
set +o pipefail
|
||||
varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-exists.service","Service":{"ExecStart":[{"path":"/usr/bin/sleep","arguments":["/usr/bin/sleep","infinity"]}]}}}' |& grep "io.systemd.Unit.UnitExists"
|
||||
varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-test.target","Description":"test"}}' |& grep "io.systemd.Unit.UnitTypeNotSupported"
|
||||
# Apply-time and dispatch-time validation errors both surface as
|
||||
# org.varlink.service.InvalidParameter, with the offending field name in the
|
||||
# response parameters. Use --graceful to treat the expected error as success
|
||||
# so jq can assert on the dumped parameters JSON directly.
|
||||
expect_invalid_parameter() {
|
||||
local payload="$1" field="$2"
|
||||
varlinkctl call --graceful=org.varlink.service.InvalidParameter \
|
||||
"$MANAGER_SOCKET" io.systemd.Unit.StartTransient "$payload" \
|
||||
| jq -e --arg f "$field" '.parameter == $f' >/dev/null
|
||||
testcase_set_properties() {
|
||||
set +o pipefail
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.SetProperties '{"runtime": true, "name": "non-existent.service", "properties": {"Markers": ["needs-restart"]}}' |& grep "io.systemd.Unit.NoSuchUnit"
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.SetProperties '{"runtime": true, "name": "systemd-journald.service", "properties": {"LoadState": "foobar"}}' |& grep "io.systemd.Unit.PropertyNotSupported"
|
||||
set -o pipefail
|
||||
}
|
||||
defer_transient_cleanup varlink-transient-bad.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-bad.service","Service":{"Type":"simple"}}}' \
|
||||
"context"
|
||||
# Invalid ExecStart path: exercises filename_or_absolute_path_is_valid() in transient_service_apply_properties()
|
||||
defer_transient_cleanup varlink-transient-badpath.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-badpath.service","Service":{"Type":"simple","ExecStart":[{"path":""}]}}}' \
|
||||
"Service.ExecStart"
|
||||
# The 'privileged' and 'noSetuid' execution flags are mutually exclusive
|
||||
defer_transient_cleanup varlink-transient-badflags.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-badflags.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true","privileged":true,"noSetuid":true}]}}}' \
|
||||
"Service.ExecStart"
|
||||
# Relative WorkingDirectory path is rejected
|
||||
defer_transient_cleanup varlink-transient-bad-wd.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-bad-wd.service","Exec":{"WorkingDirectory":{"path":"relative/path","missingOK":false}},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \
|
||||
"Exec.WorkingDirectory"
|
||||
# Malformed environment entry (not KEY=VALUE)
|
||||
defer_transient_cleanup varlink-transient-bad-env.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-bad-env.service","Exec":{"Environment":["not_an_env_var"]},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \
|
||||
"Exec.Environment"
|
||||
# Invalid User= name is rejected at JSON dispatch time as a parameter error
|
||||
defer_transient_cleanup varlink-transient-bad-user.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-bad-user.service","Exec":{"User":"bad/user"},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \
|
||||
"context"
|
||||
# Out-of-range Nice= value is rejected
|
||||
defer_transient_cleanup varlink-transient-bad-nice.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-bad-nice.service","Exec":{"Nice":100},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \
|
||||
"Exec.Nice"
|
||||
# Out-of-range OOMScoreAdjust= value is rejected
|
||||
defer_transient_cleanup varlink-transient-bad-oom.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-bad-oom.service","Exec":{"OOMScoreAdjust":9999},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \
|
||||
"Exec.OOMScoreAdjust"
|
||||
# Relative RootDirectory path is rejected
|
||||
defer_transient_cleanup varlink-transient-bad-rd.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-bad-rd.service","Exec":{"RootDirectory":"relative/path"},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \
|
||||
"Exec.RootDirectory"
|
||||
# Note that an unparsable enum value (e.g. StandardOutput=bogus) is rejected by the Varlink IDL
|
||||
# validation layer before the JSON dispatch even runs, hence there's no point in testing pid1's
|
||||
# dispatch-level enum handling here — and doing so would trip post.sh's "didn't pass validation"
|
||||
# journal check, since the IDL layer logs about the rejection.
|
||||
# Relative TTYPath is rejected
|
||||
defer_transient_cleanup varlink-transient-bad-ttypath.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-bad-ttypath.service","Exec":{"TTYPath":"relative/tty"},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \
|
||||
"Exec.TTYPath"
|
||||
# The file based stdio types need a companion path, and StandardInput=data a companion payload, neither of
|
||||
# which can be passed yet, hence they are rejected
|
||||
for t in file append truncate; do
|
||||
defer_transient_cleanup "varlink-transient-bad-stdout-$t.service"
|
||||
expect_invalid_parameter \
|
||||
"{\"context\":{\"ID\":\"varlink-transient-bad-stdout-$t.service\",\"Exec\":{\"StandardOutput\":\"$t\"},\"Service\":{\"Type\":\"oneshot\",\"ExecStart\":[{\"path\":\"/bin/true\"}]}}}" \
|
||||
"Exec.StandardOutput"
|
||||
done
|
||||
defer_transient_cleanup varlink-transient-bad-stderr.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-bad-stderr.service","Exec":{"StandardError":"file"},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \
|
||||
"Exec.StandardError"
|
||||
for t in file data; do
|
||||
defer_transient_cleanup "varlink-transient-bad-stdin-$t.service"
|
||||
expect_invalid_parameter \
|
||||
"{\"context\":{\"ID\":\"varlink-transient-bad-stdin-$t.service\",\"Exec\":{\"StandardInput\":\"$t\"},\"Service\":{\"Type\":\"oneshot\",\"ExecStart\":[{\"path\":\"/bin/true\"}]}}}" \
|
||||
"Exec.StandardInput"
|
||||
done
|
||||
# Invalid credential ID
|
||||
defer_transient_cleanup varlink-transient-bad-cred-id.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-bad-cred-id.service","Exec":{"SetCredential":[{"id":"bad/id","value":"YWJj"}]},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \
|
||||
"Exec.SetCredential"
|
||||
# Invalid base64 value for credential (rejected at JSON dispatch time as a parameter error)
|
||||
defer_transient_cleanup varlink-transient-bad-cred-value.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-bad-cred-value.service","Exec":{"SetCredential":[{"id":"mycred","value":"!!!not_base64!!!"}]},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \
|
||||
"context"
|
||||
# Bad PID on a scope
|
||||
defer_transient_cleanup varlink-transient-badpid.scope
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-badpid.scope","Scope":{"PIDs":[{"pid": 1}]}}}' \
|
||||
"Scope.PIDs"
|
||||
# Dependency on invalid unit (rejected at dispatch time)
|
||||
defer_transient_cleanup varlink-invalid-dependency.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-invalid-dependency.service","Requires":["invalid@.service"],"Service":{"ExecStart":[{"path":"/usr/bin/true"}]}}}' \
|
||||
"context"
|
||||
# Exec on a unit type without an exec context (.slice) is rejected
|
||||
varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-exec.slice","Exec":{"WorkingDirectory":{"path":"/tmp","missingOK":false}}}}' |& grep "io.systemd.Unit.UnitTypeNotSupported"
|
||||
# Same for Kill on a unit type without a kill context
|
||||
varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-kill.slice","Kill":{"KillMode":"process"}}}' |& grep "io.systemd.Unit.UnitTypeNotSupported"
|
||||
# Unknown field in Exec is rejected as PropertyNotSupported
|
||||
defer_transient_cleanup varlink-transient-unknown-exec.service
|
||||
unsupported_exec=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-unknown-exec.service","Exec":{"AmbientCapabilities":["cap_net_raw"]},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' 2>&1 || true)
|
||||
echo "$unsupported_exec" | grep "io.systemd.Unit.PropertyNotSupported"
|
||||
echo "$unsupported_exec" | grep "Exec.AmbientCapabilities"
|
||||
# Service field declared in the IDL but not yet settable at creation is rejected as PropertyNotSupported,
|
||||
# and the offending sub-property is identified
|
||||
defer_transient_cleanup varlink-transient-unknown-service.service
|
||||
unsupported_service=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-unknown-service.service","Service":{"Type":"oneshot","Restart":"always","ExecStart":[{"path":"/bin/true"}]}}}' 2>&1 || true)
|
||||
echo "$unsupported_service" | grep "io.systemd.Unit.PropertyNotSupported"
|
||||
echo "$unsupported_service" | grep "Service.Restart"
|
||||
# Same for a Kill field
|
||||
defer_transient_cleanup varlink-transient-unknown-kill.service
|
||||
unsupported_kill=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-unknown-kill.service","Kill":{"KillSignal":"SIGINT"},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' 2>&1 || true)
|
||||
echo "$unsupported_kill" | grep "io.systemd.Unit.PropertyNotSupported"
|
||||
echo "$unsupported_kill" | grep "Kill.KillSignal"
|
||||
# Same for non-transient dependency types
|
||||
defer_transient_cleanup varlink-non-transient-dependency.service
|
||||
non_transient_dep=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-non-transient-dependency.service","Triggers":["varlink-transient-exists.service"],"Service":{"ExecStart":[{"path":"/usr/bin/true"}]}}}' 2>&1 || true)
|
||||
echo "$non_transient_dep" | grep "io.systemd.Unit.PropertyNotSupported"
|
||||
echo "$non_transient_dep" | grep "Triggers"
|
||||
# Out-of-range CPUWeight= is rejected
|
||||
defer_transient_cleanup varlink-transient-bad-weight.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-bad-weight.service","CGroup":{"CPUWeight":123456},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \
|
||||
"CGroup.CPUWeight"
|
||||
# IOWeight=0 is rejected, only the CPU weights accept idle
|
||||
defer_transient_cleanup varlink-transient-bad-ioweight.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-bad-ioweight.service","CGroup":{"IOWeight":0},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \
|
||||
"CGroup.IOWeight"
|
||||
# MemoryMax=0 is rejected (minimum 1, matching the D-Bus setter)
|
||||
defer_transient_cleanup varlink-transient-bad-memmax.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-bad-memmax.service","CGroup":{"MemoryMax":0},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \
|
||||
"CGroup.MemoryMax"
|
||||
# The scale member of TasksMax is output only, TasksMaxScale is the input form
|
||||
defer_transient_cleanup varlink-transient-bad-taskscale-member.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-bad-taskscale-member.service","CGroup":{"TasksMax":{"value":5000,"scale":10000}},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \
|
||||
"context"
|
||||
# Out-of-range TasksMaxScale= (>= 100%) is rejected
|
||||
defer_transient_cleanup varlink-transient-bad-tasksscale.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-bad-tasksscale.service","CGroup":{"TasksMaxScale":1.5},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \
|
||||
"CGroup.TasksMaxScale"
|
||||
# Absolute and relative forms of the same limit may not be combined
|
||||
defer_transient_cleanup varlink-transient-bad-taskscombo.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-bad-taskscombo.service","CGroup":{"TasksMax":{"value":16},"TasksMaxScale":0.5},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \
|
||||
"CGroup.TasksMaxScale"
|
||||
# Zero MemoryMaxScale= is rejected
|
||||
defer_transient_cleanup varlink-transient-bad-memscale.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-bad-memscale.service","CGroup":{"MemoryMaxScale":0.0},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \
|
||||
"CGroup.MemoryMaxScale"
|
||||
# Slice= pointing at a non-slice unit is rejected
|
||||
defer_transient_cleanup varlink-transient-bad-slice.service
|
||||
expect_invalid_parameter \
|
||||
'{"context":{"ID":"varlink-transient-bad-slice.service","CGroup":{"Slice":"not-a-slice.service"},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \
|
||||
"CGroup.Slice"
|
||||
# CGroup field declared in the IDL but not yet settable at creation is rejected as
|
||||
# PropertyNotSupported, and the offending sub-property is identified
|
||||
defer_transient_cleanup varlink-transient-unknown-cgroup.service
|
||||
unsupported_cgroup=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
|
||||
'{"context":{"ID":"varlink-transient-unknown-cgroup.service","CGroup":{"DeviceAllow":[]},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' 2>&1 || true)
|
||||
echo "$unsupported_cgroup" | grep "io.systemd.Unit.PropertyNotSupported"
|
||||
echo "$unsupported_cgroup" | grep "CGroup.DeviceAllow"
|
||||
set -o pipefail
|
||||
|
||||
testcase_contexts() {
|
||||
# test for KillContext
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"pid": {"pid": 0}}' | jq -e '.context.Kill'
|
||||
# test for AutomountContext/Runtime
|
||||
automount_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "automount" and .runtime.LoadState == "loaded") .context.ID // empty' | tail -n 1)
|
||||
test -n "$automount_id"
|
||||
# Use jq to JSON-encode the unit name as it may contain backslash escapes (e.g. \x2d) that
|
||||
# are not valid JSON escape sequences and would be rejected by varlinkctl's JSON parser.
|
||||
automount_params=$(jq -cn --arg name "$automount_id" '{name: $name}')
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$automount_params" | jq -e '.context.Automount'
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$automount_params" | jq -e '.runtime.Automount'
|
||||
# test for MountContext/Runtime (skip volatile run-user-*.mount to avoid GC race)
|
||||
mount_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "mount" and .runtime.LoadState == "loaded" and (.context.ID | startswith("run-user-") | not)) .context.ID // empty' | tail -n 1)
|
||||
test -n "$mount_id"
|
||||
mount_params=$(jq -cn --arg name "$mount_id" '{name: $name}')
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$mount_params" | jq -e '.context.Mount'
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$mount_params" | jq -e '.runtime.Mount'
|
||||
# test for PathContext/Runtime
|
||||
path_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "path" and .runtime.LoadState == "loaded") .context.ID // empty' | tail -n 1)
|
||||
test -n "$path_id"
|
||||
path_params=$(jq -cn --arg name "$path_id" '{name: $name}')
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$path_params" | jq -e '.context.Path'
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$path_params" | jq -e '.runtime.Path'
|
||||
# test for ServiceContext/Runtime (skip volatile user@*/user-runtime-dir@* to avoid GC race)
|
||||
service_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "service" and .runtime.LoadState == "loaded" and (.context.ID | test("^(user|user-runtime-dir)@") | not)) .context.ID // empty' | tail -n 1)
|
||||
test -n "$service_id"
|
||||
service_params=$(jq -cn --arg name "$service_id" '{name: $name}')
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$service_params" | jq -e '.context.Service'
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$service_params" | jq -e '.runtime.Service'
|
||||
# test for ScopeContext/Runtime (skip volatile session-*.scope to avoid GC race)
|
||||
scope_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "scope" and .runtime.LoadState == "loaded" and (.context.ID | startswith("session-") | not)) .context.ID // empty' | tail -n 1)
|
||||
test -n "$scope_id"
|
||||
scope_params=$(jq -cn --arg name "$scope_id" '{name: $name}')
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$scope_params" | jq -e '.context.Scope'
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$scope_params" | jq -e '.runtime.Scope'
|
||||
# test for SocketContext/Runtime
|
||||
socket_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "socket" and .runtime.LoadState == "loaded") .context.ID // empty' | tail -n 1)
|
||||
test -n "$socket_id"
|
||||
socket_params=$(jq -cn --arg name "$socket_id" '{name: $name}')
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$socket_params" | jq -e '.context.Socket'
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$socket_params" | jq -e '.runtime.Socket'
|
||||
# test for SwapContext/Runtime (swap units may not be present on all systems)
|
||||
swap_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "swap" and .runtime.LoadState == "loaded") .context.ID // empty' | tail -n 1)
|
||||
if test -n "$swap_id"; then
|
||||
swap_params=$(jq -cn --arg name "$swap_id" '{name: $name}')
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$swap_params" | jq -e '.context.Swap'
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$swap_params" | jq -e '.runtime.Swap'
|
||||
fi
|
||||
# test for TimerContext/Runtime
|
||||
timer_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "timer" and .runtime.LoadState == "loaded") .context.ID // empty' | tail -n 1)
|
||||
test -n "$timer_id"
|
||||
timer_params=$(jq -cn --arg name "$timer_id" '{name: $name}')
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$timer_params" | jq -e '.context.Timer'
|
||||
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$timer_params" | jq -e '.runtime.Timer'
|
||||
}
|
||||
|
||||
testcase_user_manager() {
|
||||
# test io.systemd.Unit in user manager
|
||||
testuser_uid=$(id -u testuser)
|
||||
systemd-run --wait --pipe --user --machine testuser@ \
|
||||
varlinkctl --more call "/run/user/$testuser_uid/systemd/io.systemd.Manager" io.systemd.Unit.List '{}'
|
||||
}
|
||||
|
||||
run_testcases
|
||||
Reference in new issue
Block a user