From: Christian Brauner Date: Fri, 1 May 2026 11:37:33 +0000 (+0200) Subject: machinectl: add bind-volume / unbind-volume verbs X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c9f461a8067996c6b0c3ac3bf6f9097aedbf4734;p=thirdparty%2Fsystemd.git machinectl: add bind-volume / unbind-volume verbs 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=':' 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) --- diff --git a/shell-completion/bash/machinectl b/shell-completion/bash/machinectl index 50b46fb2792..d6627e6ba7e 100644 --- a/shell-completion/bash/machinectl +++ b/shell-completion/bash/machinectl @@ -48,7 +48,7 @@ _machinectl() { [MACHINES]='status show start stop login shell enable disable poweroff reboot pause resume terminate kill image-status show-image remove export-tar export-raw' [MACHINES_OR_FILES]='edit cat' - [MACHINE_ONLY]='clone rename set-limit' + [MACHINE_ONLY]='clone rename set-limit bind-volume unbind-volume' [READONLY]='read-only' [FILE]='import-tar import-raw' [MACHINES_AND_FILES]='copy-to copy-from bind' diff --git a/shell-completion/zsh/_machinectl b/shell-completion/zsh/_machinectl index 31ddf4fca57..d61a62ee276 100644 --- a/shell-completion/zsh/_machinectl +++ b/shell-completion/zsh/_machinectl @@ -45,6 +45,8 @@ "copy-to:Copy files from the host to a container" "copy-from:Copy files from a container to the host" "bind:Bind mount a path from the host into a container" + "bind-volume:Attach a storage volume to a running machine" + "unbind-volume:Detach a storage volume from a running machine" "list-images:Show available container and VM images" "image-status:Show image details" @@ -115,6 +117,12 @@ else stop=1 fi ;; + bind-volume|unbind-volume) + if (( CURRENT == 2 )); then _sd_machines + elif (( CURRENT == 3 )); then _message "volume spec" + else stop=1 + fi ;; + read-only) if (( CURRENT == 2 )); then _machinectl_images elif (( CURRENT == 3 )); then _values 'read-only flag' 'true' 'false' diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c index ae5473aa99d..3d7f782a8c7 100644 --- a/src/machine/machinectl.c +++ b/src/machine/machinectl.c @@ -29,16 +29,19 @@ #include "cgroup-util.h" #include "edit-util.h" #include "env-util.h" +#include "fd-util.h" #include "format-ifname.h" #include "format-table.h" #include "format-util.h" #include "hostname-util.h" #include "import-util.h" #include "in-addr-util.h" +#include "json-util.h" #include "label-util.h" #include "log.h" #include "logs-show.h" #include "machine-dbus.h" +#include "machine-util.h" #include "main-func.h" #include "nulstr-util.h" #include "osc-context.h" @@ -55,6 +58,7 @@ #include "ptyfwd.h" #include "runtime-scope.h" #include "stdio-util.h" +#include "storage-util.h" #include "string-table.h" #include "string-util.h" #include "strv.h" @@ -1338,6 +1342,120 @@ static int verb_copy_files(int argc, char *argv[], uintptr_t _data, void *userda return 0; } +static int verb_bind_volume(int argc, char *argv[], uintptr_t _data, void *userdata) { + int r; + + if (arg_transport != BUS_TRANSPORT_LOCAL) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "bind-volume is only supported on the local transport."); + + _cleanup_(bind_volume_freep) BindVolume *bv = NULL; + r = bind_volume_parse(argv[2], &bv); + if (r < 0) + return log_error_errno(r, "Failed to parse bind-volume argument '%s': %m", argv[2]); + + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + + /* Locate and connect to the target machine before acquiring storage, so a missing + * machine doesn't trigger 'create=new' side effects on the StorageProvider. */ + _cleanup_free_ char *address = NULL; + r = machine_get_control_address(argv[1], &address); + if (r == -EOPNOTSUPP) + return log_error_errno(r, "Machine '%s' does not expose a varlink control socket.", argv[1]); + if (r < 0) + return r; + + _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; + r = sd_varlink_connect_address(&vl, address); + if (r < 0) + return log_error_errno(r, "Failed to connect to machine control socket %s: %m", address); + + r = sd_varlink_set_allow_fd_passing_output(vl, true); + if (r < 0) + return log_error_errno(r, "Failed to enable fd passing on varlink connection: %m"); + + _cleanup_(storage_acquire_reply_done) StorageAcquireReply reply = STORAGE_ACQUIRE_REPLY_INIT; + _cleanup_free_ char *acquire_error_id = NULL; + r = storage_acquire_volume(arg_runtime_scope, bv, arg_ask_password, &acquire_error_id, &reply); + if (r < 0) { + if (acquire_error_id) + return log_error_errno(r, "Failed to acquire storage volume '%s:%s' from provider: %s", + bv->provider, bv->volume, acquire_error_id); + return log_error_errno(r, "Failed to acquire storage volume '%s:%s' from provider: %m", + bv->provider, bv->volume); + } + + int fd_index = sd_varlink_push_fd(vl, reply.fd); + if (fd_index < 0) + return log_error_errno(fd_index, "Failed to push storage fd onto varlink connection: %m"); + TAKE_FD(reply.fd); + + _cleanup_free_ char *name = strjoin(bv->provider, ":", bv->volume); + if (!name) + return log_oom(); + + sd_json_variant *vl_reply = NULL; + const char *error_id = NULL; + r = sd_varlink_callbo( + vl, + "io.systemd.MachineInstance.AddStorage", + &vl_reply, &error_id, + SD_JSON_BUILD_PAIR_INTEGER("fileDescriptorIndex", fd_index), + SD_JSON_BUILD_PAIR_STRING("name", name), + JSON_BUILD_PAIR_STRING_NON_EMPTY("config", bv->config)); + if (r < 0) + return log_error_errno(r, "Failed to call io.systemd.MachineInstance.AddStorage: %m"); + if (error_id) + return log_error_errno(sd_varlink_error_to_errno(error_id, vl_reply), + "AddStorage failed for '%s': %s", name, error_id); + + return 0; +} + +static int verb_unbind_volume(int argc, char *argv[], uintptr_t _data, void *userdata) { + int r; + + if (arg_transport != BUS_TRANSPORT_LOCAL) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "unbind-volume is only supported on the local transport."); + + r = machine_storage_name_split(argv[2], /* ret_provider= */ NULL, /* ret_volume= */ NULL); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid unbind-volume name '%s', expected ':'.", argv[2]); + + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + + _cleanup_free_ char *address = NULL; + r = machine_get_control_address(argv[1], &address); + if (r == -EOPNOTSUPP) + return log_error_errno(r, "Machine '%s' does not expose a varlink control socket.", argv[1]); + if (r < 0) + return r; + + _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; + r = sd_varlink_connect_address(&vl, address); + if (r < 0) + return log_error_errno(r, "Failed to connect to machine control socket %s: %m", address); + + sd_json_variant *reply = NULL; + const char *error_id = NULL; + r = sd_varlink_callbo( + vl, + "io.systemd.MachineInstance.RemoveStorage", + &reply, &error_id, + SD_JSON_BUILD_PAIR_STRING("name", argv[2])); + if (r < 0) + return log_error_errno(r, "Failed to call io.systemd.MachineInstance.RemoveStorage: %m"); + if (error_id) + return log_error_errno(sd_varlink_error_to_errno(error_id, reply), + "RemoveStorage failed for '%s': %s", argv[2], error_id); + + return 0; +} + static int verb_bind_mount(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); @@ -2606,6 +2724,8 @@ static int machinectl_main(int argc, char *argv[], sd_bus *bus) { { "login", VERB_ANY, 2, 0, verb_login_machine }, { "shell", VERB_ANY, VERB_ANY, 0, verb_shell_machine }, { "bind", 3, 4, 0, verb_bind_mount }, + { "bind-volume", 3, 3, 0, verb_bind_volume }, + { "unbind-volume", 3, 3, 0, verb_unbind_volume }, { "edit", 2, VERB_ANY, 0, verb_edit_settings }, { "cat", 2, VERB_ANY, 0, verb_cat_settings }, { "copy-to", 3, 4, 0, verb_copy_files },