diff --git a/man/rules/meson.build b/man/rules/meson.build
index 2bf5936d64..ff4794b528 100644
--- a/man/rules/meson.build
+++ b/man/rules/meson.build
@@ -902,7 +902,7 @@ manpages = [
['systemd-debug-generator', '8', [], ''],
['systemd-delta', '1', [], ''],
['systemd-detect-virt', '1', [], ''],
- ['systemd-dissect', '1', [], 'HAVE_BLKID'],
+ ['systemd-dissect', '1', ['mount.ddi'], 'HAVE_BLKID'],
['systemd-environment-d-generator',
'8',
['30-systemd-environment-d-generator'],
diff --git a/man/systemd-dissect.xml b/man/systemd-dissect.xml
index 6549aa92c8..01eda2702d 100644
--- a/man/systemd-dissect.xml
+++ b/man/systemd-dissect.xml
@@ -18,6 +18,7 @@
systemd-dissect
+ mount.ddiDissect Discoverable Disk Images (DDIs)
@@ -99,6 +100,14 @@
what it operates with when mounting the image. To display the complete list of partitions use a tool such
as fdisk8.
+
+ The systemd-dissect command may be invoked as mount.ddi in
+ which case it implements the mount8 "external
+ helper" interface. This ensures disk images compatible with systemd-dissect can be
+ mounted directly by mount and fstab5. For
+ details see below.
@@ -132,7 +141,13 @@
All mounted file systems are checked with the appropriate fsck8
implementation in automatic fixing mode, unless explicitly turned off () or
- read-only operation is requested ().
+ read-only operation is requested ().
+
+ Note that this functionality is also available in mount8 via a
+ command such as mount -t ddi myimage.raw targetdir/, as well as in fstab5. For
+ details, see below.
@@ -359,6 +374,35 @@
command is used the exit status of the invoked command is propagated.
+
+ Invocation as /sbin/mount.ddi
+
+ The systemd-dissect executable may be symlinked to
+ /sbin/mount.ddi. If invoked through that it implements mount8's
+ "external helper" interface for the (pseudo) file system type ddi. This means
+ conformant disk images may be mounted directly via
+
+ # mount -t ddi myimage.raw targetdir/
+
+ in a fashion mostly equivalent to:
+
+ # systemd-dissect --mount myimage.raw targetdir/
+
+ Note that since a single DDI may contain multiple file systems it should later be unmounted with
+ umount -R targetdir/, for recursive operation.
+
+ This functionality is particularly useful to mount DDIs automatically at boot via simple
+ /etc/fstab entries. For example:
+
+ /path/to/myimage.raw /images/myimage/ ddi defaults 0 0
+
+ When invoked this way the mount options ro, rw,
+ discard, nodiscard map to the corresponding options listed above
+ (i.e. , ,
+ ). Mount options are not generically passed on to
+ the file systems inside the images.
+ Examples
@@ -366,7 +410,7 @@
Generate a tarball from an OS disk image
- $ systemd-dissect --with foo.raw tar cz . >foo.tar.gz
+ # systemd-dissect --with foo.raw tar cz . >foo.tar.gz
@@ -377,6 +421,7 @@
systemd-nspawn1,
systemd.exec5,
Discoverable Partitions Specification,
+ mount8,
umount8,
fdisk8
diff --git a/meson.build b/meson.build
index a274de5601..ff8706d94c 100644
--- a/meson.build
+++ b/meson.build
@@ -2478,6 +2478,10 @@ if conf.get('HAVE_BLKID') == 1
dependencies : [versiondep],
install_rpath : rootpkglibdir,
install : true)
+
+ meson.add_install_script(meson_make_symlink,
+ bindir / 'systemd-dissect',
+ rootsbindir / 'mount.ddi')
endif
if conf.get('ENABLE_RESOLVE') == 1
diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c
index f08e745a55..db26645fd9 100644
--- a/src/dissect/dissect.c
+++ b/src/dissect/dissect.c
@@ -522,6 +522,80 @@ static int parse_argv(int argc, char *argv[]) {
return 1;
}
+static int parse_argv_as_mount_helper(int argc, char *argv[]) {
+ const char *options = NULL;
+ bool fake = false;
+ int c, r;
+
+ /* Implements util-linux "external helper" command line interface, as per mount(8) man page. */
+
+ while ((c = getopt(argc, argv, "sfnvN:o:t:")) >= 0) {
+ switch(c) {
+
+ case 'f':
+ fake = true;
+ break;
+
+ case 'o':
+ options = optarg;
+ break;
+
+ case 't':
+ if (!streq(optarg, "ddi"))
+ log_debug("Unexpected file system type '%s', ignoring.", optarg);
+ break;
+
+ case 's': /* sloppy mount options */
+ case 'n': /* aka --no-mtab */
+ case 'v': /* aka --verbose */
+ log_debug("Ignoring option -%c, not implemented.", c);
+ break;
+
+ case 'N': /* aka --namespace= */
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Option -%c is not implemented, refusing.", c);
+
+ case '?':
+ return -EINVAL;
+ }
+ }
+
+ if (optind + 2 != argc)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Expected an image file path and target directory as only argument.");
+
+ for (const char *p = options;;) {
+ _cleanup_free_ char *word = NULL;
+
+ r = extract_first_word(&p, &word, ",", EXTRACT_KEEP_QUOTE);
+ if (r < 0)
+ return log_error_errno(r, "Failed to extract mount option: %m");
+ if (r == 0)
+ break;
+
+ if (streq(word, "ro"))
+ SET_FLAG(arg_flags, DISSECT_IMAGE_READ_ONLY, true);
+ else if (streq(word, "rw"))
+ SET_FLAG(arg_flags, DISSECT_IMAGE_READ_ONLY, false);
+ else if (streq(word, "discard"))
+ SET_FLAG(arg_flags, DISSECT_IMAGE_DISCARD_ANY, true);
+ else if (streq(word, "nodiscard"))
+ SET_FLAG(arg_flags, DISSECT_IMAGE_DISCARD_ANY, false);
+ else
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Unknown mount option '%s'.", word);
+ }
+
+ if (fake)
+ return 0;
+
+ arg_image = argv[optind];
+ arg_path = argv[optind+1];
+
+ arg_flags |= DISSECT_IMAGE_REQUIRE_ROOT;
+ arg_action = ACTION_MOUNT;
+ return 1;
+}
+
static int strv_pair_to_json(char **l, JsonVariant **ret) {
_cleanup_strv_free_ char **jl = NULL;
@@ -1420,7 +1494,10 @@ static int run(int argc, char *argv[]) {
log_setup();
- r = parse_argv(argc, argv);
+ if (invoked_as(argv, "mount.ddi"))
+ r = parse_argv_as_mount_helper(argc, argv);
+ else
+ r = parse_argv(argc, argv);
if (r <= 0)
return r;
diff --git a/test/units/testsuite-50.sh b/test/units/testsuite-50.sh
index a072305739..cb99b811c9 100755
--- a/test/units/testsuite-50.sh
+++ b/test/units/testsuite-50.sh
@@ -424,6 +424,12 @@ grep -q -F '{"name":"b","type":"raw","class":"portable","ro":false,"path":"/run/
grep -q -F '{"name":"c","type":"raw","class":"extension","ro":false,"path":"/run/extensions/c.raw"' /tmp/discover.json
rm /tmp/discover.json /run/machines/a.raw /run/portables/b.raw /run/extensions/c.raw
+# Check that the /sbin/mount.ddi helper works
+T="/tmp/mounthelper.$RANDOM"
+mount -t ddi "${image}.gpt" "$T" -o ro,X-mount.mkdir,discard
+umount -R "$T"
+rmdir "$T"
+
echo OK >/testok
exit 0