machinectl bind-volume MACHINE PROVIDER:VOLUME[:CONFIG][:K=V,...]
machinectl unbind-volume MACHINE PROVIDER:VOLUME
For bind-volume, machinectl parses the SPEC with the shared
bind_volume_parse(), Acquires the storage volume from the named
provider on the machinectl side, locates the target machine's
io.systemd.MachineInstance control socket via
machine_get_control_address(), pushes the fd across, and calls
io.systemd.MachineInstance.AddStorage with name='<provider>:<volume>'
and the user-supplied config string.
For unbind-volume, machinectl just forwards the name string to
io.systemd.MachineInstance.RemoveStorage.
Volumes attached at machine startup (e.g. via systemd-vmspawn's
--bind-volume=) are rejected with StorageImmutable when the user
attempts to unbind them at runtime.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Wire up the runtime hotplug Varlink methods on the per-VM control
socket:
AddStorage → take fd from the link, look up the DiskType from the
'config' field, build a DriveInfo flagged
QMP_DRIVE_REMOVABLE, dispatch to
vmspawn_qmp_add_block_device(). Reply delivered async
by on_add_device_add_complete() once the guest sees
the device.
RemoveStorage → forward the user-visible name to
vmspawn_qmp_remove_block_device(); the existing
device_del / DEVICE_DELETED / blockdev-del chain
replies on the link.
Add SD_VARLINK_SERVER_ALLOW_FD_PASSING_INPUT to the server flags so
clients can push storage fds across via sd_varlink_push_fd().
Maps -EEXIST → StorageExists and -EOPNOTSUPP/-EINVAL →
ConfigNotSupported in the AddStorage handler so callers see the
specific MachineInstance errors.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
systemd-vmspawn --bind-volume=PROVIDER:VOLUME[:CONFIG][:K=V,...]
For each --bind-volume passed at startup, vmspawn calls Acquire() on
the named StorageProvider and attaches the resulting fd to the VM as
an additional drive. The drive is identified by the user-visible name
'<provider>:<volume>' on the bridge — that is also the handle used
later when machinectl unbind-volume detaches drives at runtime
(though boot-time drives like these are NOT removable; that is the
StorageImmutable behaviour added earlier).
The colon grammar is parsed by the shared bind_volume_parse() helper.
The 3rd 'config' field selects the guest device type from the
disk_type_table[] vocabulary (virtio-blk, virtio-scsi, nvme, scsi-cd);
empty defaults to virtio-blk per the TASK grammar.
Wiring lives next to the existing --extra-drive setup: parse_argv()
appends a parsed BindVolume to arg_bind_volumes, and prepare_device_info()
hands the array to vmspawn_bind_volume_prepare_boot() which Acquires
each volume and pushes a DriveInfo onto the existing drives array.
PCIe port assignment (assign_pcie_ports()) and the QMP setup loop pick
them up automatically.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
This is vmspawn's per-backend code for the StorageProvider integration.
Other backends (future systemd-nspawn, future service-manager
BindVolume=) consume the same shared parser and Acquire helper but
each provides its own attach/detach glue; this is vmspawn's.
- disk_type_from_bind_volume_config() turns the opaque BindVolume
'config' field (e.g. "scsi-cd") into a DiskType. Empty defaults to
virtio-blk to match the --bind-volume CLI grammar.
- vmspawn_bind_volume_acquire() takes a parsed BindVolume, calls
storage_acquire_volume() for the fd, and builds a DriveInfo ready
for vmspawn_qmp_setup_drives() (boot) or vmspawn_qmp_add_block_device()
(hotplug). Rejects directory-typed volumes (vmspawn block devices
need a regular file or a host block device).
- vmspawn_bind_volume_attach_fd() is the runtime path: takes a fd
that was already pushed across by an AddStorage caller plus the
name+config it specified, builds the DriveInfo with
QMP_DRIVE_REMOVABLE set and a varlink link, and dispatches to
vmspawn_qmp_add_block_device(). Reply is delivered asynchronously
by the existing on_add_device_add_complete() callback.
- vmspawn_bind_volume_prepare_boot() is a thin loop the boot-time
path uses to populate DriveInfos.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Drives attached at boot via the existing CLI options (--image,
--extra-drive) must not be detachable at runtime via the upcoming
RemoveStorage Varlink method, while drives added at runtime via
AddStorage must be. Track this distinction with a new QMP_DRIVE_REMOVABLE
property flag — placed alongside QMP_DRIVE_BLOCK_DEVICE, not in the
transient BlockDeviceStateFlags state-machine, since "may be removed"
is a permanent property of the drive.
vmspawn_qmp_remove_block_device() now early-rejects unknown ids with
io.systemd.MachineInstance.NoSuchStorage and immutable drives with
io.systemd.MachineInstance.StorageImmutable.
vmspawn_qmp_add_block_device() loses its 'static' qualifier and gets a
declaration in the header, so the runtime hotplug path
(vmspawn-bind-volume.c, next) can dispatch into it directly.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Define two new methods on the generic 'MachineInstance' Varlink
interface that systemd-vmspawn (this series) and (future)
systemd-nspawn implement on their per-machine control sockets:
AddStorage(fileDescriptorIndex, name, config?) -> ()
Attach a storage volume — the caller passes an fd previously
acquired from a StorageProvider, plus a unique name of the form
'<provider>:<volume>' that identifies this binding for later
removal, plus a backend-specific 'config' field (vmspawn: guest
device type; future nspawn: mount path).
RemoveStorage(name) -> ()
Detach a previously-added storage volume.
Plus errors NoSuchStorage, StorageExists, StorageImmutable (the volume
was attached at boot and cannot be removed), BadConfig, and
ConfigNotSupported. Names follow the io.systemd.StorageProvider
vocabulary (NoSuchVolume, BadTemplate, TypeNotSupported, etc.) so the
two interfaces are visually consistent.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Drop the inline socket-build + sd_varlink_callbo() + reply-dispatch
+ take_fd block from run_as_mount_helper() in favour of the shared
helper. Preserves the type-fallback retry (TypeNotSupported / WrongType
re-tries with requestAs="blk") and the per-error-id message mapping;
the helper just reports the io.systemd.StorageProvider.* error name
back to the caller.
Net effect: ~50 lines of dedup, no functional change.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
storagectl's mount.storage helper bundles "open StorageProvider socket
+ Acquire() + dispatch reply + take fd" inline. Future consumers
(systemd-vmspawn boot-time --bind-volume, machinectl bind-volume) need
the same dance.
Factor it into a single libshared helper that takes the Acquire()
parameters by value and returns the fd plus the actual type/read-only
flags. Library code, so no logging — varlink errors are surfaced via
sd_varlink_error_to_errno() and the StorageProvider error_id is
returned to the caller via reterr_error_id (caller decides how to
format messages).
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Add a universal parser for the colon-separated grammar
'PROVIDER:VOLUME[:CONFIG][:K=V,K=V,…]' that backs --bind-volume on
systemd-vmspawn (next), machinectl bind-volume, and the future nspawn
+ service-manager BindVolume= integrations.
The 'config' field is opaque to shared code and interpreted per
backend (vmspawn: a DiskType name, future nspawn: a mount path). The
trailing key=value list is parsed into the io.systemd.StorageProvider
.Acquire() parameters (template, create, read-only/ro, size/create-size
and request-as), with values validated against the existing
storage-util enums and validators. Provider/volume names are checked
with storage_provider_name_is_valid() and storage_volume_name_is_valid();
the combined "<provider>:<volume>" string is also validated as
string_is_safe so it is safe to use as a QEMU device id.
Add a test-machine-util unit test covering the happy paths plus a
handful of malformed inputs.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
The storage backend providers (block, fs) and storagectl currently each
extract storage-util.c into their target. Several upcoming consumers
(machine-util's BindVolume parser, vmspawn's hotplug glue, machinectl's
new bind-volume verbs) need the StorageProvider type/string-table
helpers and a future shared Acquire client helper.
Move storage-util.{c,h} to src/shared so libshared exports the symbols
once and every consumer (storage providers, storagectl, libshared
itself) picks them up by linking libshared. Drop the now-redundant
'extract'/'objects' wiring in src/storage/meson.build.
No code changes; this is purely a relocation.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
The mount.storage helper open-codes the conventional 64K UID/GID
delegation block size as 0x10000 / 0x10000U in four places. Several
other places in the tree do the same (nspawn's arg_uid_range default,
homed's mount setup, …), but with no shared name.
Add USERNS_RANGE_SIZE in user-util.h alongside UID_NOBODY and friends,
and switch storagectl over to it. Other call sites can adopt it
incrementally.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
In https://gcc.gnu.org/PR121911 `gcc` started enforcing the type of
`__stack_chk_guard` to `uintptr_t` and broke `systemd` build as:
```
../src/boot/efi-log.c:136:17: error: conflicting types for '__stack_chk_guard'; have 'intptr_t' {aka 'long int'}
136 | _used_ intptr_t __stack_chk_guard = (intptr_t) 0x70f6967de78acae3;
| ^~~~~~~~~~~~~~~~~
cc1: note: previous declaration of '__stack_chk_guard' with type 'long unsigned int'
../src/boot/efi-log.c:136:17: error: declaration of '__stack_chk_guard' shadows a global declaration [-Werror=shadow]
136 | _used_ intptr_t __stack_chk_guard = (intptr_t) 0x70f6967de78acae3;
| ^~~~~~~~~~~~~~~~~
```
Let's match the declaration to unsigned type as suggested by upstream in
https://gcc.gnu.org/PR121911#c6.
The prompt for groups is nice. The prompt for a shell could use some
love. Looking at this is much easier if we can invoke the code outside
in isolation.
I wrote this when looking at https://github.com/systemd/systemd/pull/41947,
where I wanted to see how the homectl prompt works with the changes.
An error snuck into the pattern parsing of the `MatchPattern` key in the
sysupdate transfer files. If there's two files "part1-v2.raw", and
"part1-v2.raw.tar" in the source folder, and MatchPattern="part1-@v.raw",
sysupdate will incorrectly choose "part1-v2.raw.tar" instead of
"part1-v2.raw".
While the pattern matching works perfectly fine, after the full pattern
is successfully matched to the string, we don't ensure that the string
actually ends when the pattern just did.
This means we can end up choosing a wrong file for the update, if the
filename/path happens to start with the same MatchPattern.
Fix it by ensuring the string ends after our match pattern ended.
We removed the concept of facts, so we need to update the existing
io.systemd.Basic facts provider to metrics. This commit does just
that. Its mostly mechanical.
This also means that facts.{c,h} and varlink-io.systemd.Facts.{c,h}
are gone now.
When a report upload fails the backend often provides useful
details via the varlink error. Show them as part of the upload
error message. For now we just dump the json because we have
no structure that the backends should follow. We may want to
consider adding one (like check for an "error_message" key in
the json). But for now this is a nice step forward.
Two new verbs are added: "generate" and "upload". The first one just
creates a "report", i.e. puts the metrics into a structured JSON object
that in the future is intended to carry additional data like a
signature:
$ build/systemd-report generate io.systemd.Manager.UnitsTotal
{
"mediaType" : "application/vnd.io.systemd.report",
"timestamp" : "Tue 2026-04-28 22:30:09 UTC",
"metrics" : [
{
"name" : "io.systemd.Manager.UnitsTotal",
"value" : 520
}
]
}
The second verb can be used to upload or otherwise process the report.
It builds on the code added in 0a8560eed8.
In /run/systemd/metrics-upload/ we expect a set of sockets. We'll call
out to each one of them. This allows the data to be processed in custom
ways, incl. writing to storage or sending over the network.
Each socket must provide a single interface:
io.systemd.Metrics.Upload {"report":$data}
If all entries of a menu prompt start with the same prefix, let's
preselect the prefix to enhance user experience.
This is particularly relevant when prompting for a disk to install
things on, as typically they all start with the same prefix /dev/, and
if there's only a single target medium discoverable, then we can even
fill it out fully.
The test asserts that pgscan is 0, but under sanitizers this sometimes
fails and shows up as 1. We cannot control what the kernel scans, and
with sanitizers the runtime can be slow enough it's possible that the
kernel does a pass on the cgroup of the unit test.
Instead of asserting that it's 0, assert that it's between 0 and 9,
which seems a reasonable range.
Fixes https://github.com/systemd/systemd/issues/37710
With SD_VARLINK_SERVER_ROOT_ONLY, we refuse all unprivileged operations.
This is silly, the user can and should be able to do anything that doesn't
require privileges.
E.g.:
$ SYSTEMD_LOG_LEVEL=debug varlinkctl introspect /usr/lib/systemd/systemd-pcrextend
Forking off Varlink child process '/usr/lib/systemd/systemd-pcrextend'.
Successfully forked off '(sd-vlexec)' as PID 568993.
varlink: Setting state idle-client
json-stream: Sending message: {"method":"org.varlink.service.GetInterfaceDescription","parameters":{"interface":"io.systemd.PCRExtend"}}
Skipping PR_SET_MM, as we don't have privileges.
varlink: Changing state idle-client → calling
varlink: Unprivileged client attempted connection, refusing.
Failed to run Varlink event loop: Operation not permitted
json-stream: Got POLLHUP from socket.
varlink: Changing state calling → pending-disconnect
varlink: Connection was closed.
Failed to issue org.varlink.service.GetInterfaceDescription() varlink call: Connection reset by peer
This and similar commands now work, e.g.
$ SYSTEMD_LOG_LEVEL=debug varlinkctl call --more ./build/bootctl io.systemd.BootControl.ListBootEntries {}
...
Failed to open directory "/efi": No such file or directory
File system "/boot" is not a FAT EFI System Partition (ESP) file system.
...
Method call failed: Permission denied
{
"origin" : "linux",
"errno" : 13,
"errnoName" : "EACCES"
}
Which is fine — we lack privileges to actually return a useful answer, but the
call itself should go through.
I didn't touch udevd, which refuses to run if it is not root, and does a lot of
privileged setup, so would refuse to start even if the check was removed.
drive_info_add_fail() calls bridge_unregister_drive() followed by
drive_info_unref(), then continues to access the DriveInfo object.
While all current callers hold their own reference, it is a bit
fragile and it trips static analyzers. Take a local reference.
CID#1655804
Follow-up for 1d0a8e5dbd
option_get_synopsis() returns a heap-allocated string. Capture it in
a _cleanup_free_ variable so it is freed after being used in the log
message.
CID#1657828
Follow-up for 05fea7df1b
I thought that this would require some bigger changes, but it turns out
that the existing functionality is good enough with some minor
adjustments if used appropriately.
The behaviour of help_section() is changed to simplify all callers.
partition_is_luks2_integrity() was returning -EINVAL when it
encountered a non-LUKS2 header (e.g. LUKS v1), which caused the
caller to abort the entire disk dissection. A LUKS v1 partition
simply isn't LUKS2-with-integrity, so return 0 instead and let
dissection continue normally.
Add ReloadCount to the io.systemd.Metrics family table so it can be
queried alongside other manager-level metrics via systemd-report.
Also extend the existing integration test to cross-check the value
returned by systemd-report against the D-Bus and Varlink transports
on every assertion.
Co-developed-by: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: Simon Lucido <simonlucido@meta.com>
The logic that was tested in the previous commit is used to implement
the behaviour for unit-shell and other verbs without changes.
The compare-versions synopsis is shortened to "V1 [OP] V2" to make the
verb synopsis fit. Unusual capitalizaition of "Command" is changed to
"COMMAND" (it's a replace arg, not a fixed string), and some help
strings are adjusted. The order of options in --help is based on the
existing order in parse_argv(). The old order in --help was mostly
random. I think it might be good to figure out something more rational
here, but I'm leaving that as a separate step.
The urlification of dot(1) in the --help string is lost. It's hard to
do this with the help string being stored in a read-only section.
I think this is not worth the trouble to reimplement in the current
scheme.
The search domain limit is already enforced by dns_search_domain_new(),
but in this case it's way too late. Let's enforce it during the first
loop to avoid unnecessary parsing.
---
Also, set a similar limit for NTAs - introduce a new constant, since
there's no pre-existing limit. I pulled the value from a thin air since
there's (AFAIK) no mandated maximum/minimum for NTAs, but given they're
supposed to be a manual and _temporary_ workarounds, hopefully 2K of
NTAs will be more than enough (if not, please yell).
Also note: the newly added error messages don't have the trailing "."
and similarly the newly introduced constant doesn't have the "u" suffix
to match the style of the surrounding code (and I didn't want to fix the
surrounding code to make the diff minimal). If this is not desirable,
please also yell.
A segfault is observed when both key_file and tpm2-device are
simultaneously passed to systemd-cryptsetup, e.g.:
systemd-cryptsetup attach test_data /vol /my-pass tpm2-device=auto
The crash appears after commit 5c6aad9 but the flaw in the logic was
pre-existing.
Fixes#41867
The search domain limit is already enforced by dns_search_domain_new(),
but in this case it's way too late. Let's enforce it during the first
loop to avoid unnecessary parsing.
On initrds where nfnetlink module is missing, trying to open
a NETLINK_NETFILTER netlink socket takes a lot of time then fails.
This makes boot noticibly slower. Even though probably no
unit in an initrd need netfilter.
So here we delay opening the socket until we know we need it.
When a keyfile is passed with tpm2-device=, e.g.,
systemd-cryptsetup attach test_data /vol /my-pass tpm2-device=auto
the logic in attach_luks_or_plain_or_bitlk_by_tpm2() tries to process it as a
TPM blob first. This did not work properly because it passes n_blobs=1 to
acquire_tpm2_key(), and the key_file is only read when n_blobs == 0. As a
result, the code ends up calling tpm2_unseal(..., blobs=NULL, n_blobs=1, ...).
Before commit 5c6aad9 ("cryptsetup-tokens: Print tpm2-primary-alg: only when it
is known"), the segfault was not observed because tpm2_unseal() was bailing out
early when primary_alg == 0. However, after that change, it attempts to process
the blob (which is NULL) and crashes.
Fix this logic by passing n_blobs=0 to acquire_tpm2_key() so that it actually
reads the key_file. Additionally, assert 'blobs' in tpm2_unseal() as a
safeguard.
Fixes#41867
Let's cap the number of states/patterns per query to something
reasonable, i.e. max 256 states and 4K patterns per query.
---
Also, use the env assignments limit in a couple more places.
When the source buffer is NULL, the firmware is supposed to try to load the UKI
with simple filesystem protocol then load file 2 protocol. But it seems
on some versions of AMI, it does not use simple filesystem protocol,
and then fails to load if the ESP was loaded from an El Torito boot
catalog. Trying to load the source buffer from the simple filesystem protocol
protocols seems work around this limitation.
Shim for example, also loads the source buffer before calling LoadImage. So it
seems to be a safe thing to do. We could also maybe in the future use load file
2 protocol if simple filesystem failed in the first place.
Introduce a counter that tracks how many configuration reloads have
been successfully completed by the manager. The increment lives in
manager_reload() right after the "point of no return", so failed
reload attempts that bail out earlier (e.g. during serialization)
do not bump the counter.
It is accessible as a new ReloadCount property to
org.freedesktop.systemd1.Manager (D-Bus) and ReloadCount to
io.systemd.Manager.Describe (Varlink).
Also add an integration test for ReloadCount
that verifies that the new ReloadCount property increments by one per
daemon-reload, accumulates correctly across multiple reloads, and that
D-Bus and Varlink return identical values. Also tests that the counter
reset after a reexec.
Co-developed-by: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: Simon Lucido <simonlucido@meta.com>