]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
dissect: add commands for attaching/detaching loopback devices
authorLennart Poettering <lennart@poettering.net>
Mon, 6 Mar 2023 10:59:16 +0000 (11:59 +0100)
committerLennart Poettering <lennart@poettering.net>
Thu, 9 Mar 2023 15:40:55 +0000 (16:40 +0100)
Sometimes it is useful attaching DDIs without mounting them. We could
use "losetup" for that, but doing this in systemd-dissect has various
benefits:

1. we superficially validate the DDI first
2. we set the sector size depending on what we determine
3. we synchronously create the per-partition block devices

man/systemd-dissect.xml
src/dissect/dissect.c

index 7dfad29c35feb3686ef0c12e21c1e37ca96f6047..1d0532713d42398fb7e492d10c610eb7eb18e37f 100644 (file)
     <cmdsynopsis>
       <command>systemd-dissect <arg choice="opt" rep="repeat">OPTIONS</arg> <option>--umount</option> <arg choice="plain"><replaceable>PATH</replaceable></arg></command>
     </cmdsynopsis>
+    <cmdsynopsis>
+      <command>systemd-dissect <arg choice="opt" rep="repeat">OPTIONS</arg> <option>--attach</option> <arg choice="plain"><replaceable>IMAGE</replaceable></arg></command>
+    </cmdsynopsis>
+    <cmdsynopsis>
+      <command>systemd-dissect <arg choice="opt" rep="repeat">OPTIONS</arg> <option>--detach</option> <arg choice="plain"><replaceable>PATH</replaceable></arg></command>
+    </cmdsynopsis>
     <cmdsynopsis>
       <command>systemd-dissect <arg choice="opt" rep="repeat">OPTIONS</arg> <option>--list</option> <arg choice="plain"><replaceable>IMAGE</replaceable></arg></command>
     </cmdsynopsis>
         <listitem><para>This is a shortcut for <option>--umount --rmdir</option>.</para></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><option>--attach</option></term>
+
+        <listitem><para>Attach the specified disk image to an automatically allocated loopback block device,
+        and print the path to the loopback block device to standard output. This is similar to an invocation
+        of <command>losetup --find --show</command>, but will validate the image as DDI before attaching, and
+        derive the correct sector size to use automatically. Moreover, it ensures the per-partition block
+        devices are created before returning. Takes a path to a disk image file.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--detach</option></term>
+
+        <listitem><para>Detach the specified disk image from a loopback block device. This undoes the effect
+        of <option>--attach</option> above. This expects either a path to a loopback block device as an
+        argument, or the path to the backing image file. In the latter case it will automatically determine
+        the right device to detach.</para></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><option>--list</option></term>
         <term><option>-l</option></term>
index db26645fd9f025e333569d1fa77921282ed9fdd5..6f7b44f0aa3f0a92e685b9b2fac95ea1cd3f7ba0 100644 (file)
@@ -52,6 +52,8 @@ static enum {
         ACTION_DISSECT,
         ACTION_MOUNT,
         ACTION_UMOUNT,
+        ACTION_ATTACH,
+        ACTION_DETACH,
         ACTION_LIST,
         ACTION_MTREE,
         ACTION_WITH,
@@ -94,6 +96,8 @@ static int help(void) {
         printf("%1$s [OPTIONS...] IMAGE\n"
                "%1$s [OPTIONS...] --mount IMAGE PATH\n"
                "%1$s [OPTIONS...] --umount PATH\n"
+               "%1$s [OPTIONS...] --attach IMAGE\n"
+               "%1$s [OPTIONS...] --detach PATH\n"
                "%1$s [OPTIONS...] --list IMAGE\n"
                "%1$s [OPTIONS...] --mtree IMAGE\n"
                "%1$s [OPTIONS...] --with IMAGE [COMMAND…]\n"
@@ -126,6 +130,8 @@ static int help(void) {
                "  -M                      Shortcut for --mount --mkdir\n"
                "  -u --umount             Unmount the image from the specified directory\n"
                "  -U                      Shortcut for --umount --rmdir\n"
+               "     --attach             Attach the disk image to a loopback block device\n"
+               "     --detach             Detach a loopback block device gain\n"
                "  -l --list               List all the files and directories of the specified\n"
                "                          OS image\n"
                "     --mtree              Show BSD mtree manifest of OS image\n"
@@ -206,6 +212,8 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_JSON,
                 ARG_MTREE,
                 ARG_DISCOVER,
+                ARG_ATTACH,
+                ARG_DETACH,
         };
 
         static const struct option options[] = {
@@ -215,6 +223,8 @@ static int parse_argv(int argc, char *argv[]) {
                 { "no-legend",     no_argument,       NULL, ARG_NO_LEGEND     },
                 { "mount",         no_argument,       NULL, 'm'               },
                 { "umount",        no_argument,       NULL, 'u'               },
+                { "attach",        no_argument,       NULL, ARG_ATTACH        },
+                { "detach",        no_argument,       NULL, ARG_DETACH        },
                 { "with",          no_argument,       NULL, ARG_WITH          },
                 { "read-only",     no_argument,       NULL, 'r'               },
                 { "discard",       required_argument, NULL, ARG_DISCARD       },
@@ -291,6 +301,14 @@ static int parse_argv(int argc, char *argv[]) {
                         arg_rmdir = true;
                         break;
 
+                case ARG_ATTACH:
+                        arg_action = ACTION_ATTACH;
+                        break;
+
+                case ARG_DETACH:
+                        arg_action = ACTION_DETACH;
+                        break;
+
                 case 'l':
                         arg_action = ACTION_LIST;
                         arg_flags |= DISSECT_IMAGE_READ_ONLY;
@@ -454,6 +472,22 @@ static int parse_argv(int argc, char *argv[]) {
                 arg_path = argv[optind];
                 break;
 
+        case ACTION_ATTACH:
+                if (optind + 1 != argc)
+                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                               "Expected an image file path as only argument.");
+
+                arg_image = argv[optind];
+                break;
+
+        case ACTION_DETACH:
+                if (optind + 1 != argc)
+                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                               "Expected an image file path or loopback device as only argument.");
+
+                arg_image = argv[optind];
+                break;
+
         case ACTION_LIST:
         case ACTION_MTREE:
                 if (optind + 1 != argc)
@@ -1486,6 +1520,113 @@ static int action_discover(void) {
         return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
 }
 
+static int action_attach(DissectedImage *m, LoopDevice *d) {
+        int r;
+
+        assert(m);
+        assert(d);
+
+        r = loop_device_set_autoclear(d, false);
+        if (r < 0)
+                return log_error_errno(r, "Failed to disable auto-clear logic on loopback device: %m");
+
+        r = dissected_image_relinquish(m);
+        if (r < 0)
+                return log_error_errno(r, "Failed to relinquish DM and loopback block devices: %m");
+
+        puts(d->node);
+        return 0;
+}
+
+static int action_detach(const char *path) {
+        _cleanup_(loop_device_unrefp) LoopDevice *loop = NULL;
+        _cleanup_close_ int fd = -EBADF;
+        struct stat st;
+        int r;
+
+        fd = open(path, O_PATH|O_CLOEXEC);
+        if (fd < 0)
+                return log_error_errno(errno, "Failed to open '%s': %m", path);
+
+        if (fstat(fd, &st) < 0)
+                return log_error_errno(errno, "Failed to stat '%s': %m", path);
+
+        if (S_ISBLK(st.st_mode)) {
+                r = loop_device_open_from_fd(fd, O_RDONLY, LOCK_EX, &loop);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to open '%s' as loopback block device: %m", path);
+
+        } else if (S_ISREG(st.st_mode)) {
+                _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
+                sd_device *d;
+
+                /* If a regular file is specified, search for a loopback block device that is backed by it */
+
+                r = sd_device_enumerator_new(&e);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to allocate enumerator: %m");
+
+                r = sd_device_enumerator_add_match_subsystem(e, "block", true);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to match block devices: %m");
+
+                r = sd_device_enumerator_add_match_sysname(e, "loop*");
+                if (r < 0)
+                        return log_error_errno(r, "Failed to match loopback block devices: %m");
+
+                (void) sd_device_enumerator_allow_uninitialized(e);
+
+                FOREACH_DEVICE(e, d) {
+                        _cleanup_(loop_device_unrefp) LoopDevice *entry_loop = NULL;
+                        const char *name, *devtype;
+
+                        r = sd_device_get_sysname(d, &name);
+                        if (r < 0) {
+                                log_warning_errno(r, "Failed to get enumerated device's sysname, skipping: %m");
+                                continue;
+                        }
+
+                        r = sd_device_get_devtype(d, &devtype);
+                        if (r < 0) {
+                                log_warning_errno(r, "Failed to get devtype of '%s', skipping: %m", name);
+                                continue;
+                        }
+
+                        if (!streq(devtype, "disk")) /* Filter out partition block devices */
+                                continue;
+
+                        r = loop_device_open(d, O_RDONLY, LOCK_SH, &entry_loop);
+                        if (r < 0) {
+                                log_warning_errno(r, "Failed to open loopback block device '%s', skipping: %m", name);
+                                continue;
+                        }
+
+                        if (entry_loop->backing_devno == st.st_dev && entry_loop->backing_inode == st.st_ino) {
+                                /* Found it! The kernel allows attaching a single file to multiple loopback
+                                 * devices. Let's destruct them in reverse order, i.e. find the last matching
+                                 * loopback device here, rather than the first. */
+
+                                loop_device_unref(loop);
+                                loop = TAKE_PTR(entry_loop);
+                        }
+                }
+
+                if (!loop)
+                        return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "No loopback block device backed by '%s' found.", path);
+
+                r = loop_device_flock(loop, LOCK_EX);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to upgrade device lock: %m");
+        }
+
+        r = loop_device_set_autoclear(loop, true);
+        if (r < 0)
+                log_warning_errno(r, "Failed to enable autoclear logic on '%s', ignoring: %m", loop->node);
+
+        loop_device_unrelinquish(loop);
+        return 0;
+}
+
 static int run(int argc, char *argv[]) {
         _cleanup_(dissected_image_unrefp) DissectedImage *m = NULL;
         _cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
@@ -1503,6 +1644,8 @@ static int run(int argc, char *argv[]) {
 
         if (arg_action == ACTION_UMOUNT)
                 return action_umount(arg_path);
+        if (arg_action == ACTION_DETACH)
+                return action_detach(arg_image);
         if (arg_action == ACTION_DISCOVER)
                 return action_discover();
 
@@ -1537,6 +1680,9 @@ static int run(int argc, char *argv[]) {
         if (r < 0)
                 return r;
 
+        if (arg_action == ACTION_ATTACH)
+                return action_attach(m, d);
+
         r = dissected_image_load_verity_sig_partition(
                         m,
                         d->fd,