]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
dissect: Add systemd-dissect --umount 24141/head
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Wed, 27 Jul 2022 23:55:11 +0000 (01:55 +0200)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Wed, 3 Aug 2022 18:55:32 +0000 (20:55 +0200)
This command takes a mountpoint, unmounts it and makes sure the
underlying partition devices and block device are removed before
exiting.

To mirror the --mount operation, we also add a --rmdir option which
does the opposite of --mkdir, and a -U option which is a shortcut
for --umount --rmdir.

man/systemd-dissect.xml
src/dissect/dissect.c
test/units/testsuite-50.sh

index 4cdac2b013fb757b57c74ebac47e30d109a4bd69..6430501bdf0e1ad756e3359ceb1737bbe85b7317 100644 (file)
@@ -28,6 +28,9 @@
     <cmdsynopsis>
       <command>systemd-dissect <arg choice="opt" rep="repeat">OPTIONS</arg> <option>--mount</option> <arg choice="plain"><replaceable>IMAGE</replaceable></arg> <arg choice="plain"><replaceable>PATH</replaceable></arg></command>
     </cmdsynopsis>
+    <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>--copy-from</option> <arg choice="plain"><replaceable>IMAGE</replaceable></arg> <arg choice="plain"><replaceable>PATH</replaceable></arg> <arg choice="opt"><replaceable>TARGET</replaceable></arg></command>
     </cmdsynopsis>
@@ -40,7 +43,7 @@
     <title>Description</title>
 
     <para><command>systemd-dissect</command> is a tool for introspecting and interacting with file system OS
-    disk images. It supports four different operations:</para>
+    disk images. It supports five different operations:</para>
 
     <orderedlist>
       <listitem><para>Show general OS image information, including the image's
       mount the included partitions according to their designation onto a directory and possibly
       sub-directories.</para></listitem>
 
+      <listitem><para>Unmount an OS image from a local directory. In this mode it will recursively unmount
+      the mounted partitions and remove the underlying loop device, including all the partition sub-devices.
+      </para></listitem>
+
       <listitem><para>Copy files and directories in and out of an OS image.</para></listitem>
     </orderedlist>
 
         multiple nested mounts are established. This command expects two arguments: a path to an image file
         and a path to a directory where to mount the image.</para>
 
-        <para>To unmount an OS image mounted like this use <citerefentry
-        project='man-pages'><refentrytitle>umount</refentrytitle><manvolnum>8</manvolnum></citerefentry>'s
-        <option>-R</option> switch (for recursive operation), so that the OS image and all nested partition
-        mounts are unmounted.</para>
+        <para>To unmount an OS image mounted like this use the <option>--umount</option> operation.</para>
 
         <para>When the OS image contains LUKS encrypted or Verity integrity protected file systems
         appropriate volumes are automatically set up and marked for automatic disassembly when the image is
         <listitem><para>This is a shortcut for <option>--mount --mkdir</option>.</para></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><option>--umount</option></term>
+        <term><option>-u</option></term>
+
+        <listitem><para>Unmount an OS image from the specified directory. This command expects one argument:
+        a directory where an OS image was mounted.</para>
+
+        <para>All mounted partitions will be recursively unmounted, and the underlying loop device will be
+        removed, along with all it's partition sub-devices.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>-U</option></term>
+
+        <listitem><para>This is a shortcut for <option>--umount --rmdir</option>.</para></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><option>--copy-from</option></term>
         <term><option>-x</option></term>
         unmounted again.</para></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><option>--rmdir</option></term>
+
+        <listitem><para>If combined with <option>--umount</option> the specified directory where the OS image
+        is mounted is removed after unmounting the OS image.</para></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><option>--discard=</option></term>
 
index 4e39f959cfe8eb0cf9ca561b4c785fee1f4e9660..d9f3fab835be32e1b4dfa696390ed1dad8b368c0 100644 (file)
@@ -8,7 +8,10 @@
 #include <sys/ioctl.h>
 #include <sys/mount.h>
 
+#include "sd-device.h"
+
 #include "architecture.h"
+#include "blockdev-util.h"
 #include "chase-symlinks.h"
 #include "copy.h"
 #include "dissect-image.h"
@@ -24,6 +27,7 @@
 #include "main-func.h"
 #include "mkdir.h"
 #include "mount-util.h"
+#include "mountpoint-util.h"
 #include "namespace-util.h"
 #include "parse-argument.h"
 #include "parse-util.h"
@@ -40,6 +44,7 @@
 static enum {
         ACTION_DISSECT,
         ACTION_MOUNT,
+        ACTION_UMOUNT,
         ACTION_COPY_FROM,
         ACTION_COPY_TO,
 } arg_action = ACTION_DISSECT;
@@ -58,6 +63,7 @@ static VeritySettings arg_verity_settings = VERITY_SETTINGS_DEFAULT;
 static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
 static PagerFlags arg_pager_flags = 0;
 static bool arg_legend = true;
+static bool arg_rmdir = false;
 
 STATIC_DESTRUCTOR_REGISTER(arg_verity_settings, verity_settings_done);
 
@@ -81,6 +87,7 @@ static int help(void) {
                "     --fsck=BOOL          Run fsck before mounting\n"
                "     --growfs=BOOL        Grow file system to partition size, if marked\n"
                "     --mkdir              Make mount directory before mounting, if missing\n"
+               "     --rmdir              Remove mount directory after unmounting\n"
                "     --discard=MODE       Choose 'discard' mode (disabled, loop, all, crypto)\n"
                "     --root-hash=HASH     Specify root hash for verity\n"
                "     --root-hash-sig=SIG  Specify pkcs7 signature of root hash for verity\n"
@@ -96,6 +103,8 @@ static int help(void) {
                "     --version            Show package version\n"
                "  -m --mount              Mount the image to the specified directory\n"
                "  -M                      Shortcut for --mount --mkdir\n"
+               "  -u --umount             Unmount the image from the specified directory\n"
+               "  -U                      Shortcut for --umount --rmdir\n"
                "  -x --copy-from          Copy files from image to host\n"
                "  -a --copy-to            Copy files from host to image\n"
                "\nSee the %2$s for details.\n",
@@ -122,6 +131,7 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_ROOT_HASH_SIG,
                 ARG_VERITY_DATA,
                 ARG_MKDIR,
+                ARG_RMDIR,
                 ARG_JSON,
         };
 
@@ -131,6 +141,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "no-pager",      no_argument,       NULL, ARG_NO_PAGER      },
                 { "no-legend",     no_argument,       NULL, ARG_NO_LEGEND     },
                 { "mount",         no_argument,       NULL, 'm'               },
+                { "umount",        no_argument,       NULL, 'u'               },
                 { "read-only",     no_argument,       NULL, 'r'               },
                 { "discard",       required_argument, NULL, ARG_DISCARD       },
                 { "fsck",          required_argument, NULL, ARG_FSCK          },
@@ -139,6 +150,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "root-hash-sig", required_argument, NULL, ARG_ROOT_HASH_SIG },
                 { "verity-data",   required_argument, NULL, ARG_VERITY_DATA   },
                 { "mkdir",         no_argument,       NULL, ARG_MKDIR         },
+                { "rmdir",         no_argument,       NULL, ARG_RMDIR         },
                 { "copy-from",     no_argument,       NULL, 'x'               },
                 { "copy-to",       no_argument,       NULL, 'a'               },
                 { "json",          required_argument, NULL, ARG_JSON          },
@@ -150,7 +162,7 @@ static int parse_argv(int argc, char *argv[]) {
         assert(argc >= 0);
         assert(argv);
 
-        while ((c = getopt_long(argc, argv, "hmrMxa", options, NULL)) >= 0) {
+        while ((c = getopt_long(argc, argv, "hmurMUxa", options, NULL)) >= 0) {
 
                 switch (c) {
 
@@ -182,6 +194,20 @@ static int parse_argv(int argc, char *argv[]) {
                         arg_flags |= DISSECT_IMAGE_MKDIR;
                         break;
 
+                case 'u':
+                        arg_action = ACTION_UMOUNT;
+                        break;
+
+                case ARG_RMDIR:
+                        arg_rmdir = true;
+                        break;
+
+                case 'U':
+                        /* Shortcut combination of the above two */
+                        arg_action = ACTION_UMOUNT;
+                        arg_rmdir = true;
+                        break;
+
                 case 'x':
                         arg_action = ACTION_COPY_FROM;
                         arg_flags |= DISSECT_IMAGE_READ_ONLY;
@@ -316,6 +342,14 @@ static int parse_argv(int argc, char *argv[]) {
                 arg_flags |= DISSECT_IMAGE_REQUIRE_ROOT;
                 break;
 
+        case ACTION_UMOUNT:
+                if (optind + 1 != argc)
+                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                               "Expected a mount point path as only argument.");
+
+                arg_path = argv[optind];
+                break;
+
         case ACTION_COPY_FROM:
                 if (argc < optind + 2 || argc > optind + 3)
                         return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
@@ -823,6 +857,82 @@ static int action_copy(DissectedImage *m, LoopDevice *d) {
         return 0;
 }
 
+static int action_umount(const char *path) {
+        _cleanup_close_ int fd = -1;
+        _cleanup_free_ char *canonical = NULL;
+        dev_t devno;
+        const char *devname;
+        _cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
+        _cleanup_(sd_device_unrefp) sd_device *device = NULL;
+        int r, k;
+
+        fd = chase_symlinks_and_open(path, NULL, 0, O_DIRECTORY, &canonical);
+        if (fd == -ENOTDIR)
+                return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "'%s' is not a directory", path);
+        if (fd < 0)
+                return log_error_errno(fd, "Failed to resolve path '%s': %m", path);
+
+        r = fd_is_mount_point(fd, NULL, 0);
+        if (r == 0)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "'%s' is not a mount point", canonical);
+        if (r < 0)
+                return log_error_errno(r, "Failed to determine whether '%s' is a mount point: %m", canonical);
+
+        r = fd_get_whole_disk(fd, /*backing=*/ true, &devno);
+        if (r < 0)
+                return log_error_errno(r, "Failed to find backing block device for '%s': %m", canonical);
+
+        r = sd_device_new_from_devnum(&device, 'b', devno);
+        if (r < 0)
+                return log_error_errno(r, "Failed to create sd-device object for block device %u:%u: %m",
+                                       major(devno), minor(devno));
+
+        r = sd_device_get_devname(device, &devname);
+        if (r < 0)
+                return log_error_errno(r, "Failed to get devname of block device %u:%u: %m",
+                                       major(devno), minor(devno));
+
+        r = loop_device_open(devname, 0, &d);
+        if (r < 0)
+                return log_error_errno(r, "Failed to open loop device '%s': %m", devname);
+
+        r = loop_device_flock(d, LOCK_EX);
+        if (r < 0)
+                return log_error_errno(r, "Failed to lock loop device '%s': %m", devname);
+
+        /* We've locked the loop device, now we're ready to unmount. To allow the unmount to succeed, we have
+         * to close the O_PATH fd we opened earlier. */
+        fd = safe_close(fd);
+
+        r = umount_recursive(canonical, 0);
+        if (r < 0)
+                return log_error_errno(r, "Failed to unmount '%s': %m", canonical);
+
+        /* We managed to lock and unmount successfully? That means we can try to remove the loop device.*/
+        loop_device_unrelinquish(d);
+
+        if (arg_rmdir) {
+                k = RET_NERRNO(rmdir(canonical));
+                if (k < 0)
+                        log_error_errno(k, "Failed to remove mount directory '%s': %m", canonical);
+        } else
+                k = 0;
+
+        /* Before loop_device_unrefp() kicks in, let's explicitly remove all the partition subdevices of the
+         * loop device. We do this to ensure that all traces of the loop device are gone by the time this
+         * command exits. */
+        r = block_device_remove_all_partitions(d->fd);
+        if (r == -EBUSY) {
+                log_error_errno(r, "One or more partitions of '%s' are busy, ignoring", devname);
+                r = 0;
+        }
+        if (r < 0)
+                log_error_errno(r, "Failed to remove one or more partitions of '%s': %m", devname);
+
+
+        return k < 0 ? k : r;
+}
+
 static int run(int argc, char *argv[]) {
         _cleanup_(dissected_image_unrefp) DissectedImage *m = NULL;
         _cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
@@ -835,6 +945,9 @@ static int run(int argc, char *argv[]) {
         if (r <= 0)
                 return r;
 
+        if (arg_action == ACTION_UMOUNT)
+                return action_umount(arg_path);
+
         r = verity_settings_load(
                         &arg_verity_settings,
                         arg_image, NULL, NULL);
index 2f1844ccf7463f717c70bdff87dd6b70e0dea907..31cb52064eaa149a6228856899cb1e8c677b1c4d 100755 (executable)
@@ -58,8 +58,8 @@ if [ "${verity_count}" -lt 1 ]; then
     echo "Verity device ${image}.raw not found in /dev/mapper/"
     exit 1
 fi
-umount "${image_dir}/mount"
-umount "${image_dir}/mount2"
+systemd-dissect --umount "${image_dir}/mount"
+systemd-dissect --umount "${image_dir}/mount2"
 
 systemd-run -P -p RootImage="${image}.raw" cat /usr/lib/os-release | grep -q -F "MARKER=1"
 mv "${image}.verity" "${image}.fooverity"
@@ -207,7 +207,7 @@ systemd-dissect --root-hash "${roothash}" --mount "${image}.gpt" "${image_dir}/m
 grep -q -F -f "$os_release" "${image_dir}/mount/usr/lib/os-release"
 grep -q -F -f "$os_release" "${image_dir}/mount/etc/os-release"
 grep -q -F "MARKER=1" "${image_dir}/mount/usr/lib/os-release"
-umount "${image_dir}/mount"
+systemd-dissect --umount "${image_dir}/mount"
 
 # add explicit -p MountAPIVFS=yes once to test the parser
 systemd-run -P -p RootImage="${image}.gpt" -p RootHash="${roothash}" -p MountAPIVFS=yes cat /usr/lib/os-release | grep -q -F "MARKER=1"
@@ -350,8 +350,8 @@ RemainAfterExit=yes
 EOF
 systemctl start testservice-50f.service
 systemctl is-active testservice-50f.service
-umount "${image_dir}/app0"
-umount "${image_dir}/app1"
+systemd-dissect --umount "${image_dir}/app0"
+systemd-dissect --umount "${image_dir}/app1"
 
 echo OK >/testok