Commit Graph
6644 Commits
Author SHA1 Message Date
Zbigniew Jędrzejewski-Szmek d71c1e7d89 Convert machinectl to option and verb macros (#42117) 2026-05-16 17:18:10 +02:00
Zbigniew Jędrzejewski-Szmek 3b37439afb machinectl: convert to OPTION and VERB macros
For the shell verb we want switches specified after the program name to
be passed to the program to execute, not processed by us. Mirror the
approach in 'userdbctl ssh-authorized-keys': start with
OPTION_PARSER_RETURN_POSITIONAL_ARGS, then lates switch to
STOP_AT_FIRST_NONOPTION for "shell" or NORMAL otherwise.

VERB declarations are placed directly above each function; functions
that dispatch multiple verb names get stacked VERB() declarations.
chainload_importctl() now takes the args strv instead of relying on the
global optind.

--help output is mostly the same.
--no-pager/--no-legend/--no-ask-password/-q/--quiet are now at the end.
bind-volume/unbind-volume are documented.

Fixes c9f461a806.

Co-developed-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 15:59:15 +02:00
Luca Boccassi 446b0393ff test-network: retry networkctl status in wait_operstate()
networkctl status may transiently fail right after start_networkd() because networkd has not yet picked up the freshly-created link from the kernel. The retry loop in wait_operstate() did not catch the resulting subprocess.CalledProcessError, so the test aborted on the first attempt instead of retrying for the configured timeout.

Observed in TEST-85-NETWORK-NetworkdBridgeTests, subtest test_bridge_configure_without_carrier[no-slave]:

  [   19.600156] systemd-networkd-tests.py[526]: Failed to issue io.systemd.Network.Link.Describe() varlink call: Invalid argument
  [   53.124982] systemd[1]: systemd-networkd.service: Changed start -> running
  [   53.336167] systemd-networkd-tests.py[526]: ERROR: test_bridge_configure_without_carrier (__main__.NetworkdBridgeTests.test_bridge_configure_without_carrier) (test='no-slave')
  [   53.336167] systemd-networkd-tests.py[526]:     self.wait_operstate('bridge99', operstate=r'(no-carrier|routable)', setup_state=None, setup_timeout=30)
  [   53.336167] systemd-networkd-tests.py[526]: subprocess.CalledProcessError: Command '['/usr/bin/networkctl', '-n', '0', 'status', 'bridge99']' returned non-zero exit status 1.

Co-developed-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 05:08:35 +09:00
Luca Boccassi c10c2674f3 LUO: add support for preserving third party sessions
LUO sessions cannot be nested under other sessions. This means we need
to handle them explicitly, and held them open in the shutdown binary
like we do with our own internal session, to allow services to create
their own.

The requirement to support third party sessions comes from VMMs that
wish to preserve VM(s) state(s) across kexec, as some file descriptors
(KVM's vmfd from the KVM_CREATE_VM ioctl) cannot be transfered between
processes via SCM_RIGHTS, so they cannot be stashed in the FD Store
directly. Also some file descriptors have to be handled all together or
not at all, again to do with KVM and devices that are all part of the
same vm.
2026-05-15 13:46:08 +01:00
Luca Boccassi 14f773ce5e test: add integration test for LUO 2026-05-15 13:46:08 +01:00
Luca Boccassi 9de91f0f4c nspawn: support forwarding FDs from payloads to managers
When there is a NOTIFY_SOCKET, and FDs are received from the
payload following the FD Store protocol, forward them up the
chain to the service manager that is managing nspawn.

This allows FD Store persistence across container restarts,
and can chain up for user managers as well to survive restarting
those, or reexecs, and in the future reboots too via LUO.

Add a new test case to exercise the PID1 -> user session -> nspawn -> payload
chain.
2026-05-15 13:46:08 +01:00
Zbigniew Jędrzejewski-Szmek dad6c71728 core: add WorkingDirectory, Environment and SetCredential{,Encrypted} to io.systemd.Unit.StartTransient (#41874)
This PR adds some more properties to the io.systemd.Unit.StartTransient
varlink interface: WorkingDirectory, Environment and
SetCredential{,Encrypted}. Its also hopefully a useful starting point to
establish a pattern to add even more.
2026-05-15 11:29:19 +02:00
Luca Boccassi f33fa0910d Implement Service Context/Runtime for io.systemd.Unit.List (#42098)
The PR implements the following objects + tests for
io.systemd.Unit.List:
- ServiceContext
- ServiceRuntime

It's hopefully the last PR of the long sequence of:

* https://github.com/systemd/systemd/pull/37432
* https://github.com/systemd/systemd/pull/37646
* https://github.com/systemd/systemd/pull/38032
* https://github.com/systemd/systemd/pull/38212
* https://github.com/systemd/systemd/pull/39391
* https://github.com/systemd/systemd/pull/41980
* https://github.com/systemd/systemd/pull/42057
2026-05-15 10:20:56 +01:00
Ivan Kruglov 3fcb483bca test: add ServiceContext/Runtime enum and integration tests
Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-15 00:02:01 -07:00
Philip Withnall b47ed6246d test: Add a sysupdate test for files which are a prefix match of each other
This tests whether the pattern matching code checks it’s matched the
whole string and not just a prefix (see commit 4ffb60319b).

In particular, this tests a setup which KDE currently use in their
sysupdate images, where two regular file transfers are done, one of a
`foo.erofs` file, and the other `foo.erofs.caibx`. As one is a prefix of
the other, they were hitting this bug.

See:
 - https://files.kde.org/kde-linux/sysupdate/v2/
 - https://github.com/KDE/kde-linux/tree/master/mkosi.extra/usr/lib/sysupdate.d

Signed-off-by: Philip Withnall <pwithnall@gnome.org>
Fixes: https://github.com/systemd/systemd/issues/38605
Fixes: https://github.com/systemd/systemd/issues/41288
2026-05-14 20:08:31 +01:00
noxiouz bb233dcb4b coredump: add JSON output support to coredumpctl info
Implement support for the --json= flag in the info subcommand
(issue #38844). Previously, coredumpctl info always produced
human-readable text output regardless of --json=.

Add a CoredumpFields struct that holds all journal fields extracted
for a coredump entry, along with coredump_fields_done() to release
member resources and coredump_fields_load() to populate the struct
from a journal entry. Both print_info() and the new print_info_json()
use this shared loader, eliminating the duplicate RETRIEVE loop.

print_info_json() builds a JSON object with the same fields shown by
print_info(). Missing fields are omitted via SD_JSON_BUILD_PAIR_CONDITION,
matching the tolerant behavior of print_info() rather than skipping the
entry entirely. Signal/Reason handling mirrors print_info(): normal
coredumps (MESSAGE_ID == SD_MESSAGE_COREDUMP_STR) emit a numeric Signal
field; non-normal entries (kernel oops, etc.) emit a Reason field with
the raw text from COREDUMP_SIGNAL.

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-14 15:53:13 +01:00
Daan De Meyer 6649a47eda nspawn: split boot parameters into env vars and argv
When the kernel hands the command line to PID 1, any KEY=VALUE assignment
whose KEY does not contain a '.' is exported as an environment variable
(with '-' replaced by '_') rather than passed as an argument. Mimic the
same split in --boot mode so kernel-cmdline-style arguments passed after
the container path behave as they would on a real boot.
2026-05-14 23:37:34 +09:00
Yu Watanabe d6c79c13ff Implement Socket Context/Runtime for io.systemd.Unit.List (#42057)
The PR implements the following objects + tests for
io.systemd.Unit.List:
- SocketContext
- Socket Runtime

It's a continuation of the following PRs:

* https://github.com/systemd/systemd/pull/37432
* https://github.com/systemd/systemd/pull/37646
* https://github.com/systemd/systemd/pull/38032
* https://github.com/systemd/systemd/pull/38212
* https://github.com/systemd/systemd/pull/39391
* https://github.com/systemd/systemd/pull/41980
2026-05-14 23:29:47 +09:00
Zbigniew Jędrzejewski-Szmek a1e6f4a15b Convert systemctl to option and verb macros (#42088)
This one was non-trivial, so it'd benefit from a close review.
2026-05-14 11:33:09 +02:00
Zbigniew Jędrzejewski-Szmek 440d701f3c fuzz-systemctl-parse-argv: add two corpus files to test compat parsers
Looking at the corpus examples, I'm not sure the fuzzer even went into
the compat parsers. None of the files have argv[0] that'd cause
invoked_as() to go into the compat paths. So add the files to provide
a quick test and possibly bias the fuzzer search into the right
direction.
2026-05-14 09:38:10 +02:00
Michael Vogt de2ad965c2 core: add SetCredentials{,Encrypted} to varlink Unit.StartTransient
This commit adds support to set `SetCredentials` and
`SetCredentialsEncrypted` in the `io.systemd.Unit.StartTransient`
varlink call.
2026-05-13 19:28:35 +02:00
Michael Vogt 27fa92aa3c core: add support for Environment in io.systemd.Unit.StartTransient
This commit adds support to set `Environment` in the
`io.systemd.Unit.StartTransient` varlink call.

The behavior is similar to D-Bus, i.e. a `null` or `[]` clears the
environment. This is not needed for StartTransient() as there the env
always starts empty but it seems a good property to have if this is
reused.
2026-05-13 19:28:35 +02:00
Michael Vogt a2aa3a7450 core: add WorkingDirectory to Unit.StartTransient
This commit adds setting the WorkingDirectory to the
`io.systemd.Unit.StartTransient` varlink call. This is a
first step towards more complete StartTransient in varlink.

The goal is to be as close as possible to the D-Bus parameters.
The exception is WorkingDirectory which is an object here so that
we avoid the `-` prefixes and use a more type-safe approach by
making it an explicit `missingOK` parameter.

The key names stay the same as the D-Bus properties (PascalCase).
If there are no equivalent D-Bus properties the native varlink
convention of camelCase is used.
2026-05-13 19:28:35 +02:00
Daan De Meyer 74d742fbfc TEST-64-UDEV-STORAGE: Drop number of nvme devices to 12
qemu by default only has 30 PCI slots and with vmspawn now reserving
some of those for its hotplug features, we go over the limit for the 
nvme test.

Let's drop the number of nvme devices to 12 to fix the conflict.
2026-05-13 18:13:37 +02:00
Zbigniew Jędrzejewski-Szmek 3061ec2707 meson: move systemd-sysupdate to /usr/bin/
Let's make systemd-sysupdate easy to call. It was added in 2021
and it's around to stay and not "experimental" in any way.
2026-05-13 15:09:27 +01:00
Chris Down c301966df7 core: do not leak resources when handling stale alias state on reload (#41986)
The fix for the corrupted state when units become aliased on reload
leaks the now-aliased unit's resources, which become untracked and
essentially lost.

While fixing the state corruption is of course necessary, leaking
processes/etc. is not ideal for a system and service manager, so
instead attempt to keep track of them by creating stub units
on-the-fly.
This way resources are not leaked, there are clear indications of
where they moved, and all state can be tracked as expected.
2026-05-13 21:25:08 +09:00
Christian Brauner e28aab8840 RestrictFileSystemAccess= — dm-verity filesystem access enforcement via BPF LSM (#41340)
This series adds a new `RestrictFileSystemAccess=` setting in the
`[Manager]` section of `system.conf` that enforces a deny-default
execution policy: only binaries residing on signed dm-verity block
devices (and the initramfs during early boot) are permitted to execute.
Everything else — tmpfs, procfs, sysfs, anonymous executable mappings,
unsigned dm-verity devices — is denied.

The directive takes the values `no` (default), `exec` (lock down
execution), and accepts `yes` as an alias for `exec`. The name is
deliberately broader than what the initial values cover so the same
setting can grow to restrict other filesystem access categories in the
future (e.g. `any` to deny all access from untrusted filesystems, not
just execution).

### How it works

The BPF program is entirely self-contained; PID1 loads it and the kernel
does the rest. When dm-verity brings up a device, the kernel calls
`security_bdev_setintegrity()` twice during `verity_preresume()`: once
with the root hash and once with the signature validity status. Our
`lsm/bdev_setintegrity` hook captures the second call and records the
device number in a BPF hash map if the signature is valid. When a device
is torn down, `lsm/bdev_free_security` cleans up the map entry. No
userspace map population is needed at any point.

The enforcement side hooks `bprm_check_security` (execve), `mmap_file`
(PROT_EXEC mappings including shared libraries), and `file_mprotect`
(W→X transitions like JIT and libffi). Each hook resolves the file's
backing device via `file->f_inode->i_sb->s_dev` and looks it up in the
verity device map. For block-backed filesystems, `s_dev` equals
`s_bdev->bd_dev`, which avoids an extra pointer chase and NULL check on
`s_bdev` — non-block filesystems simply miss in the map and get denied
by the default policy.

During early boot the initramfs needs to be trusted as well, since it
runs before any dm-verity volume is mounted. PID1 writes the initramfs
superblock's device number into a BPF global before attaching the
programs, and clears it after `switch_root` to close the trust window.
As a prerequisite, PID1 also verifies that
`dm_verity.require_signatures=1` is active — without it, unsigned
dm-verity devices could be created, which would weaken the security
model even though the BPF program would correctly deny execution from
them.

### Surviving daemon-reexec

The BPF programs and their verity device map must survive PID1
re-execution (daemon-reexec, switch_root, soft-reboot). Without
preservation, `manager_free()` would destroy the skeleton, the link FDs
would close, programs would detach, and the map would be freed. After
exec, a fresh skeleton would have an empty map — but existing dm-verity
devices have already signaled their integrity and won't do so again. A
deny-default policy plus an empty map means all execution denied and the
system is bricked.

We solve this by serializing the raw BPF link FDs and the `.bss` map FD
across exec using systemd's existing `serialize_fd` / `fdset_cloexec` /
`deserialize_fd` infrastructure. The kernel reference chain (link FD →
`struct bpf_link` → `struct bpf_prog` → `struct bpf_map`) keeps programs
attached and map data intact as long as the dup'd FDs survive. After
exec, PID1 detects the deserialized FDs and skips skeleton re-creation
entirely. If switching root, it uses the deserialized `.bss` map FD to
clear `initramfs_s_dev` via a targeted `mmap()` write, preserving the
other guard globals in `.bss`.

We intentionally avoid bpffs pinning. Pinned objects are discoverable
and manipulable by any process with sufficient privileges
(`BPF_OBJ_GET`, unlink). FD serialization keeps everything private to
PID1 with no external attack surface.

### Self-protection

BPF LSM programs attached via the tracing trampoline (`BPF_LSM_MAC`) are
inherently tamper-resistant — `bpf_tracing_link_lops` has no
`.update_prog` and no `.detach` callbacks, so the kernel rejects
`BPF_LINK_UPDATE` with `-EINVAL` and `BPF_LINK_DETACH` with
`-EOPNOTSUPP`. Once attached, our programs cannot be modified or
detached through the `bpf()` syscall.

The remaining attack vector is map injection: `BPF_MAP_GET_FD_BY_ID` to
obtain an FD to `verity_devices`, then `BPF_MAP_UPDATE_ELEM` to insert a
fake trusted device. The self-protection guard blocks this with three
hooks. `lsm/bpf_map` fires inside `bpf_map_new_fd()`, the chokepoint for
all code paths that produce a map FD, and denies access to our map IDs
from any process other than PID1 (identified via `tgid == 1`, which is
unspoofable — `bpf_get_current_pid_tgid()` reads `current->tgid` from
`pid->numbers[0].nr`, the init-namespace PID). `lsm/bpf_prog` provides
analogous protection for program FDs as defense-in-depth. `lsm/bpf`
handles `BPF_LINK_GET_FD_BY_ID` at the command level since there is no
`security_bpf_link()` hook in the kernel.

The guard starts inactive — all protected IDs default to 0 in `.bss`,
and no real BPF object has ID 0 — so there is no window where it
interferes with PID1's own setup. After attaching all programs, PID1
queries the kernel-assigned IDs via `bpf_obj_get_info_by_fd()` and
writes them into the guard's globals. From that point on, the guard is
active. The guard has zero collateral damage: it only denies access to
our specific object IDs, leaving bpftrace, bpftool,
`RestrictFileSystems=`, and all other BPF usage completely unaffected.

Additionally, a ptrace guard (`lsm/ptrace_access_check`) blocks
`PTRACE_MODE_ATTACH` to PID1 from other processes, preventing extraction
of sensitive state from PID1's address space via ptrace, `/proc/1/mem`,
`process_vm_readv()`, or `pidfd_getfd()`. `PTRACE_MODE_READ` is allowed
so that monitoring tools and `systemctl` continue to work normally.

### Limitations

- The enforcement hooks resolve trust by looking at
`file->f_inode->i_sb->s_dev` — the device number of the superblock that
owns the file's inode. This works correctly for files directly on a
dm-verity block device, but it does not see through overlayfs. When a
file is accessed on an overlay mount, `f_inode` points to the overlay
inode, and `i_sb->s_dev` is the overlay superblock's anonymous device
number — not the underlying dm-verity device. The overlay superblock has
no backing block device, so the lookup misses in the verity map and
execution is denied by the default policy.

This means that overlayfs mounts whose lower layers are on
dm-verity-protected volumes will currently have execution blocked, even
though the actual data is integrity-protected. The correct fix requires
a kernel extension that allows the BPF program to call something like
`d_real_inode()` to resolve through the overlay to the real inode on the
underlying filesystem, and then check that inode's superblock device
number against the verity map. I plan to add a BPF kfunc exposing this
functionality in a follow-up kernel series.

- Multi-device filesystems such as btrfs use entirely synthetic device
numbers and there is no way to reach the actual device backing the inode
from the inode itself. So `RestrictFileSystemAccess=` only works
reliably with a subset of filesystems. In practice this isn't a problem
because the feature is tailored to erofs; using it on arbitrary
filesystems requires careful vetting of the actual filesystem behaviour.

- The initial implementation also blocks JIT-style execution that relies
on memory mapped executable. This is part of `exec` semantics today and
can be loosened later by introducing finer-grained values (a common
pattern in systemd — following the precedent of `ProtectSystem=`, which
started as a boolean and later grew `auto`/`yes`/`full`/`strict`
semantics).

- The configuration is a system-wide setting with no per-unit opt-out.
This is intentional for the initial implementation: a global invariant
is easier to reason about and harder to accidentally weaken. Per-unit
relaxation can be added later if a concrete need arises.

### Testing

The series includes unit tests and integration tests covering both the
core enforcement logic and the self-protection guard. The unit test
loads the skeleton, attaches programs, populates guard globals, and
verifies that protected IDs are set correctly. The integration tests
exercise the guard by attempting `BPF_MAP_GET_FD_BY_ID` and
`BPF_PROG_GET_FD_BY_ID` from a non-PID1 process and verifying that
access is denied.

What we cannot currently test end-to-end is actual execution enforcement
against a dm-verity-signed root filesystem. The systemd test suite does
not yet have infrastructure for booting a VM with a signed dm-verity
rootfs image — the existing mkosi-based test framework lacks the ability
to produce and boot such images. This will hopefully change soon when
Daan integrates barrage into the test suite.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-05-13 13:58:46 +02:00
Daan De Meyer 7cc5d161e6 test: Modernize btrfs tests
Convert test-btrfs to use the test framework and
assertions, merge the physical offset test into it
and beef it up to include what TEST-83-BTRFS does and
finally get rid of TEST-83-BTRFS as it is unneeded now.
2026-05-13 13:47:18 +02:00
Christian Brauner 5439911f59 test: add integration tests for RestrictFileSystemAccess= BPF LSM
Add TEST-90-RESTRICT-FSACCESS with two subtests:

config subtest — Tests PID1's RestrictFileSystemAccess= configuration parsing and
failure modes via system.conf drop-ins and daemon-reexec:
 - Default RestrictFileSystemAccess=no produces no log messages
 - RestrictFileSystemAccess=yes without BPF LSM logs appropriate warning
 - RestrictFileSystemAccess=yes without require_signatures is correctly rejected
   by the test helper binary's precondition check

enforce subtest — Tests actual BPF LSM enforcement using a test helper
binary (test-bpf-restrict-fsaccess) that loads the BPF skeleton with
initramfs_s_dev set to the rootfs s_dev, pins BPF links, and exits:
 - Execution from rootfs continues to work (trusted via initramfs_s_dev)
 - Execution from tmpfs is blocked with EPERM
 - Execution from a signed dm-verity device is allowed, driven via
   systemd-run -p RootImage= against the pre-built signed minimal_0
   images that mkosi ships and signs at image build time (no on-the-fly
   squashfs / verity hash tree / signature build required)
 - After BPF detach, enforcement is lifted

All tests skip gracefully when prerequisites are not met (BPF LSM, BPF
framework, dm-verity tools, signing keys).

Signed-off-by: Christian Brauner <brauner@kernel.org>
2026-05-13 10:36:12 +02:00
Christian Brauner fe18d94610 vmspawn: add io.systemd.MachineInstance.ReplaceStorage (#42017)
A follow-up to the AddStorage / RemoveStorage series. ReplaceStorage
swaps the *backing file* of an already-attached storage device on a
running vmspawn-managed VM, leaving the guest-visible device frontend
(virtio-blk, virtio-scsi, nvme, scsi-cd) and every other property of
the device untouched. The intended use is to point an existing disk
at a new image without the guest seeing a hot-unplug/hot-plug cycle.

The signature mirrors AddStorage minus the 'config' field: the
device frontend doesn't change, only the backing behind it. Read-
only / read-write is derived from the new fd's O_ACCMODE; scsi-cd is
forced read-only to match the boot-time policy. S_ISBLK on the new
fd selects host_device vs file driver, matching AddStorage.

The QMP primitive is blockdev-reopen. It cannot change a file /
host_device node's 'filename' so we can't just point the existing
file node at a new fd, but it can swap a format node's 'file' child
to a different existing monitor-owned node by node-name reference
(case 3 in qemu/qapi/block-core.json:5034-5040). The chain is:

  add-fd          (host fd → new fdset)
  blockdev-add    (new file node, filename=/dev/fdset/N — fd-only)
  remove-fd       (release monitor's ref; new file holds the dup)
  blockdev-reopen (format node, file = new file node-name)
  blockdev-del    (old file node; its dup release frees old fdset)

The reopen options must restate every option the original blockdev-
add emitted on the format node — blockdev-reopen resets any
unspecified option to its driver default. The 'file' field is a
node-name string reference, never a path.

No new errors and no new IDL types beyond the method itself;
everything is built on the existing NoSuchStorage / StorageImmutable
/ NotConnected / EBUSY vocabulary.

The series is:

  vmspawn: split blockdev-add into separate file and format calls
      Preparatory refactor. qemu/blockdev.c:3440 only marks the
      top-level BDS returned by blockdev-add as monitor-owned;
      inline children are NOT, so blockdev-del later rejects them
      with "Node X is not owned by the monitor". Split into two
      blockdev-add calls so the file node is independently
      deletable. DriveInfo gains qmp_file_node_name and a
      file_generation counter; the teardown helper deletes format
      then file (file-first is rejected as "node used as 'file'
      of Y"). The ephemeral path was already structured this way;
      only the regular add path changes. Drops the now-unused
      qmp_build_blockdev_add_inline().

  shared/varlink-io.systemd.MachineInstance: add ReplaceStorage method
      IDL only: ReplaceStorage(fileDescriptorIndex, name). No new
      errors.

  vmspawn: implement io.systemd.MachineInstance.ReplaceStorage
      vmspawn_qmp_replace_block_device() entry point, ReplaceCtx
      (refcounted, ReplaceCtxStateFlags for partial-state tracking)
      and four async callbacks plus an idempotent replace_fail.
      file_generation is bumped before issuing blockdev-add so
      retries don't collide on node-name.
      BLOCK_DEVICE_STATE_REPLACE_PENDING gates concurrent
      Replace / Remove on the same drive. On reopen success the
      trailing blockdev-del of the old file node fires from the
      reopen callback; its failure logs a warning and still replies
      success (the swap already committed; the orphan resolves at VM
      exit). QMP disconnect mid-replace routes via
      qmp_client_fail_pending → replace_fail → NotConnected.

  test: integration test for io.systemd.MachineInstance.ReplaceStorage
      TEST-87-AUX-UTILS-VM.replace-storage covers happy-path replace,
      successive replaces (file_generation rotation), StorageImmutable
      rejection on the boot-time drive, NoSuchStorage on unknown
      names, InvalidParameter on malformed names, and clean
      RemoveStorage after a replace (proves the new file node is
      monitor-owned and the teardown order works). Backing files are
      passed via 'varlinkctl --push-fd'; no machinectl front-end is
      added in this round.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-05-13 08:48:38 +02:00
Zbigniew Jędrzejewski-Szmek 735a5c10c5 report-basic: expose os-release fields as a metric (#41988)
Add io.systemd.Basic.OSRelease metric family that reports all the fields
in os-release.
2026-05-13 08:07:27 +02:00
Frantisek Sumsal 7cecff8c99 sd-bus: handle non-string keys in dictionaries in JSON dump
JSON only supports string keys in objects, but D-Bus specification is a
bit more lenient and allows dict entries to have any basic type as key.
Let's stringify allowed non-string keys so that we can represent them as
JSON objects.

Relevant snippet from the D-Bus specification:

  A DICT_ENTRY works exactly like a struct, but rather than parentheses
  it uses curly braces, and it has more restrictions. The restrictions
  are: it occurs only as an array element type; it has exactly two
  single complete types inside the curly braces; the first single
  complete type (the "key") must be a basic type rather than a container
  type. Implementations must not accept dict entries outside of arrays,
  must not accept dict entries with zero, one, or more than two fields,
  and must not accept dict entries with non-basic-typed keys. A dict
  entry is always a key-value pair.

Resolves: #32904
2026-05-13 07:50:32 +09:00
Luca Boccassi 4f7089f0c7 core: do not leak resources when handling stale alias state on reload
The fix for the corrupted state when units become aliased on reload
leaks the now-aliased unit's resources, which become untracked and
essentially lost.

While fixing the state corruption is of course necessary, leaking
processes/etc. is not ideal for a system and service manager, so
instead attempt to keep track of them by creating stub units
on-the-fly as replacements.
This way resources are not leaked, there are clear indications of
where they moved, and all state can be tracked as expected.

Ignore timers/sockets/paths as those are internal resources/triggers.

Follow-up for a77c7a8224
2026-05-12 23:38:52 +01:00
Zbigniew Jędrzejewski-Szmek e27c382bf0 report-basic: expose os-release fields as metrics
Add io.systemd.Basic.OSRelease metric families that reports select fields
from os-release.

$ cat /etc/os-release
NAME="Fedora Linux"
VERSION="45 (Cloud Edition Prerelease)"
RELEASE_TYPE=development
ID=fedora
VERSION_ID=45
VERSION_CODENAME=""
PRETTY_NAME="Fedora Linux 45 (Cloud Edition Prerelease)"
ANSI_COLOR="0;38;2;60;110;180"
LOGO=fedora-logo-icon
CPE_NAME="cpe:/o:fedoraproject:fedora:45"
HOME_URL="https://fedoraproject.org/"
DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora/rawhide/"
SUPPORT_URL="https://ask.fedoraproject.org/"
BUG_REPORT_URL="https://bugzilla.redhat.com/"
REDHAT_BUGZILLA_PRODUCT="Fedora"
REDHAT_BUGZILLA_PRODUCT_VERSION=rawhide
REDHAT_SUPPORT_PRODUCT="Fedora"
REDHAT_SUPPORT_PRODUCT_VERSION=rawhide
SUPPORT_END=2027-11-24
VARIANT="Cloud Edition"
VARIANT_ID=cloud

$ varlinkctl call --more ./build/systemd-report-basic io.systemd.Metrics.List {} | jq --seq -c
...
{"name":"io.systemd.Basic.OSRelease.NAME","value":"Fedora Linux 44 (Workstation Edition)"}
{"name":"io.systemd.Basic.OSRelease.ID","value":"fedora"}
{"name":"io.systemd.Basic.OSRelease.CPE_NAME","value":"cpe:/o:fedoraproject:fedora:44"}
{"name":"io.systemd.Basic.OSRelease.VARIANT_ID","value":"workstation"}
{"name":"io.systemd.Basic.OSRelease.VERSION_ID","value":"44"}
{"name":"io.systemd.Basic.OSRelease.SUPPORT_END","value":"2027-05-19"}

I picked the fields that contain useful information about the specific
version/image/variant/experiment/flavour of the system. Also, either
NAME or PRETTY_NAME is included. This one is intended for human readers
to be able to identify the OS version easily.
2026-05-12 22:56:06 +02:00
Christian Brauner 1360117a64 test: integration test for io.systemd.MachineInstance.ReplaceStorage
Modelled on TEST-87-AUX-UTILS-VM.bind-volume.sh. Boots vmspawn with
one boot-time bind-volume, hot-adds a runtime volume via machinectl
bind-volume, then exercises ReplaceStorage:

  1. happy-path replace of a runtime drive
  2. successive replace (verify file_generation rotation — no
     node-name collisions on the second swap)
  3. replace of the boot-time drive must fail with StorageImmutable
  4. replace of an unknown name must fail with NoSuchStorage
  5. invalid name (no provider:volume separator) must fail with
     InvalidParameter
  6. unbind-volume after replace must succeed — proves the new file
     node is monitor-owned and the format-then-file teardown order
     in vmspawn_qmp_block_device_teardown() correctly cleans up both
     blockdev nodes

Pushes the new backing file via varlinkctl --push-fd; the file is a
plain truncate'd image. Auto-discovered by run_subtests in
TEST-87-AUX-UTILS-VM.sh.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-05-12 22:54:07 +02:00
Daan De Meyer 53f0ac20b2 repart: Add BtrfsReplace= (#41109)
This is a series of commits which adds a feature needed by GNOME OS'
installer. This was show during All Systems Go 2025 talk:
https://cfp.all-systems-go.io/all-systems-go-2025/talk/QRJVL3/

To sum up this PR, this changes first systemd-repart to use BLKPG
partition instead of loop devices when possible. We need then to always
rescan the partitions to try remove partitions if it failed. We allow
encrypted partitions to stay activated and with a chosen name. And we
add a new partition configuration `BtrfsReplace=`.

Note that "replace" comes from the command `btrfs replace`. But in the
case of systemd-repart, maybe "inplace" or "move" would make more sense.
I open to suggestions.

If it is better I can split this into several PRs.

The commits:

## repart: Reuse the backing fd for fdisk

Because fdisk_assign_device tries to open block devices with O_EXCL,
when it does it blocks cryptsetup from using partition block devices for
the same disk.

Since we already have a file descriptor for the device, we can just
share it and use fdisk_assign_device_by_fd instead.

## repart: Use blkpg partitions instead of loop devices when possible

We will want to allow future features to keep some devices mounted or
active. So in order to avoid leaving a mess of many loop devices, we can
just already use the partition block device already.

## repart: Rescan disk on failure if we create blkpg partitions on the
fly

Since we did not write the partition table, then the created partitions
should get removed on error.

## repart: Allow keeping luks2 volumes opened

## repart: Add BtrfsReplace=

BtrfsReplace=/mntpnt will move the btrfs filesystem from mount point to
the partition created. After moving, it will resize to take the whole
partition.
This is useful for OS installers that move a live system into a disk and
do not require a reboot.

## repart: Add VolumeName=

When a luks2 device mapper is to be kept alive after execution of
systemd-cryptsetup, the name of the volume will be taken from this
value.

## test: Add test for repart's BtrfsReplace
2026-05-12 22:24:51 +02:00
Luca Boccassi 9f08885b0e test: make TEST-75-RESOLVED robust against journald metadata race
Even after switching the wait loop to a polling `journalctl --grep`, the
test still fails intermittently because the very first messages emitted by
the freshly-spawned systemd-networkd-wait-online process can carry stale
journald metadata. journald associates `_SYSTEMD_UNIT=` (and friends) with
each entry by reading `/proc/$pid/cgroup` of the originating PID; if those
messages are produced before journald notices the cgroup migration into the
new service, they get tagged with `_SYSTEMD_UNIT=init.scope`. The
`-u $unit` filter then fails to match them.

Capture a journal cursor before launching the unit, and grep using
`--after-cursor=` plus `SYSLOG_IDENTIFIER=systemd-networkd-wait-online`
instead of `-u $unit`. SYSLOG_IDENTIFIER is set by the program itself, so
it's not subject to the cgroup-discovery race. The cursor bounds the search
to entries produced by this invocation, so prior wait-online runs in
earlier testcases don't interfere.

Logs from the failing run showing the messages exist but are tagged with
the wrong unit:

  [ 2570.948554] TEST-75-RESOLVED.sh[2178]: + unit=wait-online-dns-ede81407-b93b-459d-8e5d-69292b42d2ae.service
  [ 2571.023162] TEST-75-RESOLVED.sh[2178]: + systemd-run -u wait-online-dns-ede81407-b93b-459d-8e5d-69292b42d2ae.service ...
  [ 2571.049189] TEST-75-RESOLVED.sh[2178]: + timeout 30 bash -c 'until journalctl -b -u wait-online-dns-ede81407-b93b-459d-8e5d-69292b42d2ae.service --grep ...'
  [ 2571.964986] systemd-networkd-wait-online[2190]: dns0: No DNS server is accessible.
  [ 2601.051088] TEST-75-RESOLVED.sh[2178]: ++ cleanup

And for that 2571.964986 entry:

      _SYSTEMD_CGROUP=/init.scope
      _SYSTEMD_UNIT=init.scope
      _EXE=/usr/lib/systemd/systemd-executor
      _CMDLINE=/usr/lib/systemd/systemd-executor --deserialize 68 ...
      SYSLOG_IDENTIFIER=systemd-networkd-wait-online
      MESSAGE=dns0: No DNS server is accessible.

Follow-up for d4bc62713e

Co-developed-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 18:30:28 +01:00
Valentin David 120f906c8b test: Add test for repart's BlockDeviceReplace 2026-05-12 18:31:27 +02:00
Ivan Kruglov cf6e46c863 test: add SocketContext/Runtime enum and integration tests
Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-12 08:55:38 -07:00
Zbigniew Jędrzejewski-Szmek 143f5911b1 core: when skipping state deserializing units, also skip job subsections (#41957)
If a unit has active jobs, when it gets serialized there are job
subsections, each with their own empty line marker. The skipping
function ignores this and skips until the marker, but then leaves
the job in place, breaking deserialization.
Consume jobs subsections too.

This shows up now that there's TEST-07-PID1.alias-corruption,
which occasionally fails when the aliased unit happens to
still have a job when the reexec happens.

```
[  967.551630] TEST-07-PID1.sh[179]: + echo 'Testing with: systemctl daemon-reexec'
[  967.551630] TEST-07-PID1.sh[179]: Testing with: systemctl daemon-reexec
[  968.405274] TEST-07-PID1.sh[179]: + echo '--- Attempt 1/3 ---'
[  968.405274] TEST-07-PID1.sh[179]: --- Attempt 1/3 ---
[  968.698641] TEST-07-PID1.sh[179]: + echo 'Running daemon-reexec...'
[  968.698641] TEST-07-PID1.sh[179]: Running daemon-reexec...
[  969.130261] TEST-07-PID1.sh[179]: + echo 'legit.service PID remains 1282. Attempt 1 passed.'
[  969.130261] TEST-07-PID1.sh[179]: legit.service PID remains 1282. Attempt 1 passed.
[  970.870456] TEST-07-PID1.sh[179]: + echo '--- Attempt 2/3 ---'
[  970.870456] TEST-07-PID1.sh[179]: --- Attempt 2/3 ---
[  971.267205] TEST-07-PID1.sh[179]: + echo 'Running daemon-reexec...'
[  971.267205] TEST-07-PID1.sh[179]: Running daemon-reexec...
[  971.715743] TEST-07-PID1.sh[179]: + echo 'legit.service PID changed from 1282 to 1643!'
[  971.715743] TEST-07-PID1.sh[179]: legit.service PID changed from 1282 to 1643!
```


https://github.com/systemd/systemd/actions/runs/25376867873/job/74414201255
2026-05-12 17:12:59 +02:00
Lennart Poettering 219d894db9 Implement Path/Scope/Swap/Timer Context/Runtime for io.systemd.Unit.List (#41980)
The PR implements the following objects + tests for
io.systemd.Unit.List:
* PathContext
* PathRuntime
* ScopeContext
* ScopeRuntime
* SwapContext
* SwapRuntime
* TimerContext
* TimerRuntime

It's a continuation of the following PRs:
* https://github.com/systemd/systemd/pull/37432
* https://github.com/systemd/systemd/pull/37646
* https://github.com/systemd/systemd/pull/38032
* https://github.com/systemd/systemd/pull/38212
* https://github.com/systemd/systemd/pull/39391
2026-05-12 16:12:36 +02:00
Luca Boccassi 53b16b68e1 test: start systemd-report-basic.socket again
SUSE uses a different preset, so don't just assert in the test,
instead just start the socket in case it is not enabled

TEST-74-AUX-UTILS.sh[1594]: ++ systemctl is-enabled systemd-report-basic.socket
TEST-74-AUX-UTILS.sh[1540]: + [[ disabled == enabled ]]
TEST-74-AUX-UTILS.sh[120]: + echo 'Subtest /usr/lib/systemd/tests/testdata/units/TEST-74-AUX-UTILS.report.sh failed'

Follow-up for 4409e52494
2026-05-12 11:18:50 +01:00
Lennart Poettering e902a9346c sysupdate: Allow vacuuming partially downloaded updates and improve error reporting in that case (#41775)
See the commit messages for details.

Fixes: https://github.com/systemd/systemd/issues/41502
2026-05-11 17:27:05 +02:00
Michael Vogt 4409e52494 units: enable systemd-report-basic.socket by default
In https://github.com/systemd/systemd/pull/41688 we merged metrics
and facts for systemd-report. However while some metric sources
are enabled by default (like `io.systemd.{Manager,Network}`) the
`io.systemd.Basic` service is not enabled by default.

This commit changes this and enables it by default.

We could also enable the systemd-report-cgroup.socket but that sends
a lot more data not sure that is a good default.
2026-05-11 17:09:14 +02:00
Luca Boccassi f68fa99a07 TEST-67-INTEGRITY: pre-load crypto modules and skip unsupported algorithms
The test occasionally fails on GHA CI when formatting with xxhash64
because dm-integrity's crypto_alloc_shash() -> request_module() path
flakily fails to load the algorithm:

[   29.172664] TEST-67-INTEGRITY.sh[447]: + for a in crc32c crc32 xxhash64 sha1 sha256
[   29.172664] TEST-67-INTEGRITY.sh[447]: + [[ xxhash64 == crc32 ]]
[   29.172664] TEST-67-INTEGRITY.sh[447]: + test_one xxhash64 0
[   29.172664] TEST-67-INTEGRITY.sh[447]: + integritysetup format /dev/loop0 --batch-mode -I xxhash64 ''
[   29.223383] TEST-67-INTEGRITY.sh[1220]: device-mapper: reload ioctl on temporary-cryptsetup-fa8bebe3-1d87-4796-91e8-abc02c487bb5 (254:0) failed: No such file or directory
[   29.226916] kernel: device-mapper: table: 254:0: integrity: Invalid internal hash (-ENOENT)
[   29.227415] kernel: device-mapper: ioctl: error adding target to table
[   29.231586] TEST-67-INTEGRITY.sh[1220]: Cannot format integrity for device /dev/loop0.

Preload each algorithm's crypto module before use, and skip algorithms
that are not registered in /proc/crypto.

Co-developed-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 14:37:08 +01:00
Ivan Kruglov cb3a1a1b2f test: use jq // empty instead of grep -v null in Unit.List tests
Replace `grep -v null` with jq's `// empty` alternative operator when filtering unit IDs. With `set -o pipefail`, `grep` returns 1 when no lines match, which aborts the script before conditional guards can run. The `// empty` operator suppresses null output directly in jq without risking a pipeline failure.
2026-05-11 04:32:14 -07:00
Ivan Kruglov ae619b1224 core: implement TimerContext/Runtime for io.systemd.Unit.List + tests
Add varlink context and runtime builders for .timer units:

TimerContext: MonotonicTimers (with MonotonicTimerBase enum), CalendarTimers, Unit, OnClockChange, OnTimezoneChange, AccuracyUSec, RandomizedDelayUSec, RandomizedOffsetUSec, FixedRandomDelay, Persistent, WakeSystem, RemainAfterElapse, DeferReactivation
TimerRuntime: Result (TimerResult enum), NextElapseUSecRealtime, NextElapseUSecMonotonic, LastTriggerUSec

MonotonicTimerSpec and CalendarTimerSpec are separate types since they have different value types (int vs string). MonotonicTimerBase and TimerResult are proper varlink enum types.

Compared to the old io-systemd-Unit-List branch, this adds RandomizedOffsetUSec and DeferReactivation (both present in D-Bus but previously missing), and adds full runtime fields.

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-11 04:32:14 -07:00
Ivan Kruglov ed4740a0dc core: implement SwapContext/Runtime for io.systemd.Unit.List + tests
Add varlink context and runtime builders for .swap units:

SwapContext: What, Priority, Options, TimeoutUSec, ExecActivate, ExecDeactivate
SwapRuntime: ControlPID, Result, CleanResult, UID, GID (SwapResult enum)

SwapResult is exposed as a proper varlink enum type. Runtime follows the same pattern as MountRuntime (ControlPID, Result, CleanResult, ref UID/GID).

The integration test is conditional since swap units may not be present on all systems.

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-11 04:32:14 -07:00
Ivan Kruglov 412b2d3101 core: implement ScopeContext/Runtime for io.systemd.Unit.List + tests
Add varlink context and runtime builders for .scope units:

ScopeContext: OOMPolicy (enum), RuntimeMaxUSec, RuntimeRandomizedExtraUSec, TimeoutStopUSec
ScopeRuntime: Result (ScopeResult enum)

Both OOMPolicy and ScopeResult are exposed as proper varlink enum types.

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-11 04:32:03 -07:00
Ivan Kruglov 8787415123 core: implement PathContext/Runtime for io.systemd.Unit.List + tests
Add varlink context and runtime builders for .path units:

PathContext: Paths (array of PathType enum + path specs), Unit, MakeDirectory, DirectoryMode, TriggerLimit
PathRuntime: Result (PathResult enum)

Both PathType and PathResult are exposed as proper varlink enum types.

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-11 03:03:32 -07:00
Luca Boccassi 0bf094b763 test: bump TEST-58-REPART timeouts with sanitizers
The test is flaky under sanitizers as the timeouts seem to be too short,
bump them like we do in other tests to try and make it more robust when
running with sanitizers
2026-05-11 11:15:35 +02:00
Luca Boccassi d3f436aa10 test: fix flaky TEST-07-PID1.socket-defer.sh
The socket's SubState transitions from 'running' to 'listening' shortly
after the triggered service becomes inactive, so the assert can race and
observe the stale 'running' state:

[ 1882.425335] systemd[1]: TEST-07-PID1-socket-defer-23279.service: Changed dead -> running
[ 1882.495150] TEST-07-PID1.sh[20535]: ++ systemctl show TEST-07-PID1-socket-defer-23279.socket -P SubState
[ 1882.514239] TEST-07-PID1.sh[20509]: + assert_eq running listening
[ 1882.510529] systemd[1]: TEST-07-PID1-socket-defer-23279.socket: Flushing socket before listening.
[ 1882.510559] systemd[1]: TEST-07-PID1-socket-defer-23279.socket: Changed running -> listening

Poll for 30s instead of directly asserting to try and make it more robust
2026-05-11 10:59:00 +02:00
Luca Boccassi cd57308303 test: workaroud flaky TEST-53-TIMER.restart-trigger against journald cgroup attribution race
The restart-trigger subtest occasionally fails on CI with:

    + assert_eq 0 1
    FAIL: expected: '1' actual: '0'

even though the timer fires correctly and the echo message is in fact
written to the journal. The failure happens because the test relies on
`journalctl --unit=$UNIT_NAME` to find the message, and that filter is
based on the cgroup journald looks up for the writer PID at the time
the stdout message is received.

For very short-lived processes spawned via systemd-executor (like
`echo`), that lookup is racy: the writer's `/proc/$PID/cgroup` can
still resolve to `/init.scope` (systemd-executor's own cgroup) rather
than the service's cgroup, so the message ends up attributed to
`init.scope` and `--unit=` filtering misses it.

    __CURSOR=s=6f90ff5b6a0e47c3a527a9b4892af965;i=f8ed;b=3dad0cc689a04781879e4dd846d24432;m=17703dc;t=6513be1be2506;x=8d3009a687724b5e
    __REALTIME_TIMESTAMP=1778167492519174
    __MONOTONIC_TIMESTAMP=24576988
    __SEQNUM=63725
    __SEQNUM_ID=6f90ff5b6a0e47c3a527a9b4892af965
    _BOOT_ID=3dad0cc689a04781879e4dd846d24432
    _HOSTNAME=H
    PRIORITY=6
    SYSLOG_FACILITY=3
    _UID=0
    _GID=0
    _CAP_EFFECTIVE=1ffffffffff
    _SYSTEMD_CGROUP=/init.scope
    _SYSTEMD_UNIT=init.scope
    _SYSTEMD_SLICE=-.slice
    _EXE=/usr/lib/systemd/systemd-executor
    _TRANSPORT=stdout
    _COMM=18
    _MACHINE_ID=89ef83adc0bc4a33a83a227201b57203
    _RUNTIME_SCOPE=system
    _PID=816
    _CMDLINE=/usr/lib/systemd/systemd-executor --deserialize 50 --log-level debug,console:info --log-target journal-or-kmsg
    _STREAM_ID=8e8e4166c99e40afaa58bcd04a50a7f4
    SYSLOG_IDENTIFIER=echo
    MESSAGE=Hello from timer 29581

Note _SYSTEMD_UNIT=init.scope / _SYSTEMD_CGROUP=/init.scope on the
echo output: this is what causes `--unit=timer-restart-14362` to
return 0 hits. The test failure logs from the same run confirm this:

    + JOURNAL_TS=1778160292
    + journalctl -p info --since=@1778160292 --unit=timer-restart-14362 '--grep=Hello from timer 29581'
    -- No entries --
    + systemctl restart timer-restart-14362.timer
    ...
    + date '--set=+2 hours'
    Thu May  7 15:24:52 UTC 2026
    + sleep 1
    ...
    echo[816]: Hello from timer 29581
    ...
    ++ journalctl -q -p info --since=@1778160292 --unit=timer-restart-14362 '--grep=Hello from timer 29581'
    ++ wc -l
    + assert_eq 0 1
    FAIL: expected: '1' actual: '0'

For comparison, in a passing local run the same message is attributed
correctly to the service unit (_SYSTEMD_UNIT=timer-restart-24147.service),
so `--unit=` matches.

Work around the underlying journald race in the test by setting an
explicit `SyslogIdentifier=` on the service and matching with `-t` plus
the unique grep pattern: `SyslogIdentifier` is carried over the stdout
stream protocol and is not affected by the cgroup lookup race.

Co-developed-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 10:57:46 +02:00
Christian Brauner 5f99e126dc test: make TEST-07-PID1.user-namespace-path more robust (#42001)
The test occasionally fails because lsns returns empty output for the
transient unit, even though the process is running. e.g.:

```
    [ 1843.556046] TEST-07-PID1.sh[8560]: + systemd-run --unit=newservice --property=Type=exec --property=UserNamespacePath=/proc/8608/ns/user --property=NetworkNamespacePath=/proc/8608/ns/net sleep 3600
    [ 1844.205927] TEST-07-PID1.sh[8616]: ++ systemctl show newservice -p MainPID
    [ 1844.221425] TEST-07-PID1.sh[8618]: ++ lsns -p 8608 -o NS -t net -n
    [ 1844.229653] TEST-07-PID1.sh[8619]: ++ lsns -p 8614 -o NS -t net -n
    [ 1844.235563] TEST-07-PID1.sh[8620]: FAIL: expected: '' actual: '4026532522'
```

This could be a race, so switch to Type=notify to try and make it more
robust.

eg:
https://github.com/systemd/systemd/actions/runs/25485775345/job/74780837128?pr=41974
2026-05-11 10:16:12 +02:00
Valentin David 89148192f8 tmpfiles: Add commands for file capabilites 2026-05-09 11:36:55 +02:00