core: support stdio fd passing in io.systemd.Unit.StartTransient

Add StandardInputFileDescriptor, StandardOutputFileDescriptor and
StandardErrorFileDescriptor to the Service context of the Varlink
StartTransient() method. Each carries the push-order index of a file descriptor
passed along with the method call (via SCM_RIGHTS), and connects it to the
transient service's standard input/output/error. This is the Varlink equivalent
of the StandardInputFileDescriptor= / StandardOutputFileDescriptor= /
StandardErrorFileDescriptor= D-Bus transient properties behind
"systemd-run --pipe", which had no Varlink counterpart.

The manager Varlink server now enables SD_VARLINK_SERVER_ALLOW_FD_PASSING_INPUT
so clients may attach descriptors to their method calls, matching other
fd-accepting Varlink services (mountfsd, networkd, vmspawn, ...). The indices are
resolved with sd_varlink_peek_dup_fd() after the polkit authorization check and
stored on the Service exactly like the D-Bus path
(bus_set_transient_exec_context_fd()): the fds land in stdin_fd/stdout_fd/
stderr_fd and exec_context.stdio_as_fds is set, so the existing exec-invoke
plumbing wires them to the spawned process unchanged. Unsuitable fds (bad
index, wrong access mode) are rejected as InvalidParameter, other resolution
failures propagate as raw errnos.

Add coverage to TEST-74-AUX-UTILS.varlinkctl-unit.sh, passing regular files as
the stdout/stderr fds and asserting the unit's output lands on them.
This commit is contained in:
Lars Sjöstrom
2026-07-08 08:31:29 +02:00
committed by Michael Vogt
parent d9c0d7974e
commit 231fddc337
4 changed files with 141 additions and 4 deletions
@@ -264,6 +264,25 @@ 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"
# Error cases: verify specific varlink error types
set +o pipefail
varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \