]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
machinectl: add bind-volume / unbind-volume verbs
authorChristian Brauner <brauner@kernel.org>
Fri, 1 May 2026 11:37:33 +0000 (13:37 +0200)
committerChristian Brauner <brauner@kernel.org>
Wed, 6 May 2026 08:30:17 +0000 (10:30 +0200)
  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>
shell-completion/bash/machinectl
shell-completion/zsh/_machinectl
src/machine/machinectl.c

index 50b46fb27925b6b7283e3f553f7aadf3af04bd45..d6627e6ba7e63f83ad695b796fad0a9f7647ed50 100644 (file)
@@ -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'
index 31ddf4fca571d171ac6b82283e2d3f4ca55e7ab9..d61a62ee27681fafb392b630cfbc8d95f04c95ed 100644 (file)
@@ -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"
             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'
index ae5473aa99d508d5bea63fa5d1ae1a8f238b443a..3d7f782a8c775463051095dd95ef9428fbb48bb1 100644 (file)
 #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 '<provider>:<volume>'.", 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        },