Util-Linux package development
 help / color / mirror / Atom feed
* [PATCH v2 2/2] libmount: support explicit loop direct I/O requests
  2026-10-04  2:41 [PATCH v2 0/2] libmount: preserve reused loop devices and request direct I/O Tianyi Chen
@ 2026-09-21  7:34 ` Tianyi Chen
  2026-10-02 12:17 ` [PATCH v2 1/2] libmount: keep reused loop devices attached on mount failure Tianyi Chen
  1 sibling, 0 replies; 3+ messages in thread
From: Tianyi Chen @ 2026-09-21  7:34 UTC (permalink / raw)
  To: kzak; +Cc: util-linux

Allow X-loopdev.direct-io=on|off to select the direct I/O mode when
mounting a file through a loop device. Pass the flag to the existing
loop setup path and require a matching mode when reusing a device.
Omitting the option preserves the current behavior.

Add an explicit diagnostic for direct I/O mode conflicts and document
that the option is a request whose result should be checked with
losetup. Parse the boolean with ul_strtobool and cover the option with
the standard loop-device test cleanup helpers.

Fixes: https://github.com/util-linux/util-linux/issues/3464
Signed-off-by: Tianyi Chen <hi@tychen.cc>
---
 libmount/src/hook_loopdev.c         | 34 +++++++++++-
 sys-utils/mount.8.adoc              | 13 ++++-
 tests/expected/mount/loop-direct-io |  6 +++
 tests/ts/mount/loop-direct-io       | 84 +++++++++++++++++++++++++++++
 4 files changed, 134 insertions(+), 3 deletions(-)
 create mode 100644 tests/expected/mount/loop-direct-io
 create mode 100755 tests/ts/mount/loop-direct-io

diff --git a/libmount/src/hook_loopdev.c b/libmount/src/hook_loopdev.c
index 809d64d8..981ad6c1 100644
--- a/libmount/src/hook_loopdev.c
+++ b/libmount/src/hook_loopdev.c
@@ -139,7 +139,7 @@ static int setup_loopdev(struct libmnt_context *cxt,
 {
 	const char *backing_file, *loopdev = NULL;
 	struct loopdev_cxt lc;
-	int rc = 0, lo_flags = 0;
+	int rc = 0, lo_flags = 0, direct_io = -1;
 	uint64_t offset = 0, sizelimit = 0;
 	bool reuse = FALSE;
 	struct libmnt_opt *opt, *loopopt = NULL;
@@ -155,6 +155,23 @@ static int setup_loopdev(struct libmnt_context *cxt,
 		lo_flags |= LO_FLAGS_READ_ONLY;
 	}
 
+	/*
+	 * X-loopdev.direct-io=
+	 */
+	opt = mnt_optlist_get_named(ol, "X-loopdev.direct-io", cxt->map_userspace);
+	if (opt) {
+		const char *value = mnt_opt_get_value(opt);
+		bool enabled;
+
+		if (ul_strtobool(value, &enabled) != 0) {
+			rc = -MNT_ERR_MOUNTOPT;
+			goto done_no_deinit;
+		}
+		direct_io = enabled;
+		if (enabled)
+			lo_flags |= LO_FLAGS_DIRECT_IO;
+	}
+
 	/*
 	 * loop=
 	 */
@@ -258,6 +275,18 @@ static int setup_loopdev(struct libmnt_context *cxt,
 				goto done;
 			}
 
+			if (direct_io != -1 && !!loopcxt_is_dio(&lc) != direct_io) {
+				const char *dev = loopcxt_get_device(&lc);
+
+				DBG_OBJ(LOOP, cxt, ul_debug("%s has incompatible direct I/O mode",
+						dev));
+				/* TRANSLATORS: Do not translate "e ". It is a message classifier. */
+				mnt_context_sprintf_mesg(cxt,
+						_("e loop device %s has incompatible direct I/O mode"), dev);
+				rc = -MNT_ERR_LOOPOVERLAP;
+				goto done;
+			}
+
 			/* This is no more supported, but check to be safe. */
 			if (loopcxt_get_encrypt_type(&lc, &lc_encrypt_type) == 0
 			    && lc_encrypt_type != LO_CRYPT_NONE) {
@@ -454,7 +483,8 @@ static int is_loopdev_required(struct libmnt_context *cxt, struct libmnt_optlist
 		return 0;
 
 	/* loop= (sizelimit= or offset=) explicitly specified */
-	if (flags & (MNT_MS_LOOP | MNT_MS_OFFSET | MNT_MS_SIZELIMIT)) {
+	if (flags & (MNT_MS_LOOP | MNT_MS_OFFSET | MNT_MS_SIZELIMIT)
+	    || mnt_optlist_get_named(ol, "X-loopdev.direct-io", cxt->map_userspace)) {
 		DBG_OBJ(LOOP, cxt, ul_debug("loopdev specific options detected"));
 		return 1;
 	}
diff --git a/sys-utils/mount.8.adoc b/sys-utils/mount.8.adoc
index 922ecda7..6261bac3 100644
--- a/sys-utils/mount.8.adoc
+++ b/sys-utils/mount.8.adoc
@@ -796,6 +796,17 @@ ____
 mount -t auto -o'X-mount.auto-fstypes="noext2,ext3"'  /dev/sdc1 /mnt/test
 ____
 
+*X-loopdev.direct-io*=*on*|*off*::
+Request enabling or disabling direct I/O when setting up a loop device. The
+kernel may fall back to buffered I/O, for example when the backing file
+offset is unaligned or the block size is too small. Direct I/O is a request,
+not a guarantee; check the effective mode with *losetup --list -O NAME,DIO*. If a loop device already exists for
+the same file, offset and size limit, its direct I/O mode must match; otherwise
+the mount fails without changing the existing device. Without this option, new
+loop devices use buffered I/O and existing devices can be reused in either
+mode. This option does not change an existing mount or apply when no loop
+device is needed. See also *losetup*(8) *--direct-io*.
+
 *X-mount.mkdir*[**=**_mode_]::
 Allow to make a target directory (mountpoint) if it does not exist yet. The optional argument _mode_ specifies the filesystem access mode used for *mkdir*(2) in octal notation. The default mode is 0755. This functionality is supported only for root users or when *mount* is executed without suid permissions. The option is also supported as *x-mount.mkdir*, but this notation is deprecated since v2.30. See also *--mkdir* command line option.
 
@@ -1708,7 +1719,7 @@ ____
 *mount -t ext4 /tmp/disk.img /mnt*
 ____
 
-This type of mount knows about three options, namely *loop*, *offset* and *sizelimit*, that are really options to *losetup*(8). (These options can be used in addition to those specific to the filesystem type.)
+Loop device setup accepts *loop*, *offset*, *sizelimit* and *X-loopdev.direct-io*, corresponding to options of *losetup*(8). (These options can be used in addition to those specific to the filesystem type.)
 
 Since Linux 2.6.25 auto-destruction of loop devices is supported, meaning that any loop device allocated by *mount* will be freed by *umount* independently of _/etc/mtab_.
 
diff --git a/tests/expected/mount/loop-direct-io b/tests/expected/mount/loop-direct-io
new file mode 100644
index 00000000..990eda84
--- /dev/null
+++ b/tests/expected/mount/loop-direct-io
@@ -0,0 +1,6 @@
+invalid options: OK
+new device (on): OK
+new device (off): OK
+new device (default): OK
+reuse and failed mount (on): OK
+reuse and failed mount (off): OK
diff --git a/tests/ts/mount/loop-direct-io b/tests/ts/mount/loop-direct-io
new file mode 100755
index 00000000..c5cf9723
--- /dev/null
+++ b/tests/ts/mount/loop-direct-io
@@ -0,0 +1,84 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+TS_TOPDIR="${0%/*}/../.."
+TS_DESC="loop direct I/O"
+
+. "$TS_TOPDIR"/functions.sh
+ts_init "$*"
+
+ts_check_test_command "$TS_CMD_MOUNT"
+ts_check_test_command "$TS_CMD_UMOUNT"
+ts_skip_nonroot
+ts_check_losetup
+ts_check_prog "mkfs.ext2"
+
+IMAGE=$(ts_image_init)
+
+mkfs.ext2 -F "$IMAGE" &> /dev/null || ts_die "Cannot make filesystem"
+mkdir -p "$TS_MOUNTPOINT"
+
+for option in X-loopdev.direct-io X-loopdev.direct-io= X-loopdev.direct-io=invalid; do
+	$TS_CMD_MOUNT -o "$option" "$IMAGE" "$TS_MOUNTPOINT" &> /dev/null &&
+		ts_die "Accepted invalid option: $option"
+done
+[ -z "$($TS_CMD_LOSETUP -j "$IMAGE")" ] || ts_die "Invalid option left a loop device"
+ts_log "invalid options: OK"
+
+DEVICE=$($TS_CMD_LOSETUP --direct-io=on --find --show "$IMAGE" 2>> "$TS_ERRLOG") ||
+	ts_skip "backing filesystem does not support direct I/O"
+ts_register_loop_device "$DEVICE"
+[ "$(cat "/sys/class/block/${DEVICE##*/}/loop/dio")" = 1 ] ||
+	ts_skip "loop direct I/O not supported"
+ts_cleanup_on_exit
+
+for mode in on off default; do
+	options=loop
+	expected=0
+	[ "$mode" = default ] || options+=",X-loopdev.direct-io=$mode"
+	[ "$mode" != on ] || expected=1
+	$TS_CMD_MOUNT -o "$options" "$IMAGE" "$TS_MOUNTPOINT" >> "$TS_ERRLOG" 2>&1 ||
+		ts_die "Cannot mount with mode $mode"
+	DEVICE=$($TS_CMD_LOSETUP -j "$IMAGE" | cut -d: -f1)
+	ts_register_loop_device "$DEVICE"
+	[ "$(cat "/sys/class/block/${DEVICE##*/}/loop/dio")" = "$expected" ] ||
+		ts_die "Wrong direct I/O mode: $mode"
+	$TS_CMD_UMOUNT "$TS_MOUNTPOINT" || ts_die "Cannot unmount"
+	ts_cleanup_on_exit
+	$TS_CMD_MOUNT -t ext2 -o "$options,invalid-fs-option" "$IMAGE" "$TS_MOUNTPOINT" &> /dev/null &&
+		ts_die "Accepted invalid filesystem option on new device"
+	[ -z "$($TS_CMD_LOSETUP -j "$IMAGE")" ] || ts_die "Failed mount left a new loop device"
+	ts_log "new device ($mode): OK"
+done
+
+for mode in on off; do
+	DEVICE=$($TS_CMD_LOSETUP --direct-io="$mode" --find --show "$IMAGE") ||
+		ts_die "Cannot set up device"
+	ts_register_loop_device "$DEVICE"
+	alias=yes
+	[ "$mode" != off ] || alias=no
+	for options in "loop,X-loopdev.direct-io=$mode" "X-loopdev.direct-io=$alias" loop; do
+		$TS_CMD_MOUNT -o "$options" "$IMAGE" "$TS_MOUNTPOINT" >> "$TS_ERRLOG" 2>&1 ||
+			ts_die "Cannot reuse device: $options"
+		$TS_CMD_UMOUNT "$TS_MOUNTPOINT" || ts_die "Cannot unmount reused device"
+	done
+	other=on
+	[ "$mode" != on ] || other=off
+	$TS_CMD_MOUNT -o "X-loopdev.direct-io=$other" "$IMAGE" "$TS_MOUNTPOINT" > "$TS_OUTDIR/mode-error" 2>&1 &&
+		ts_die "Reused device with incompatible direct I/O mode"
+	grep -q "incompatible direct I/O mode" "$TS_OUTDIR/mode-error" ||
+		ts_die "Missing direct I/O diagnostic"
+	$TS_CMD_MOUNT -t ext2 -o "X-loopdev.direct-io=$mode,invalid-fs-option" \
+		"$IMAGE" "$TS_MOUNTPOINT" &> /dev/null &&
+		ts_die "Accepted invalid filesystem option"
+	[ "$($TS_CMD_LOSETUP -j "$IMAGE" | cut -d: -f1)" = "$DEVICE" ] ||
+		ts_die "Failed mount detached or duplicated reused device"
+	expected=0
+	[ "$mode" != on ] || expected=1
+	[ "$(cat "/sys/class/block/${DEVICE##*/}/loop/dio")" = "$expected" ] ||
+		ts_die "Reuse changed direct I/O mode"
+	ts_cleanup_on_exit
+	ts_log "reuse and failed mount ($mode): OK"
+done
+
+ts_finalize
-- 
2.55.0



^ permalink raw reply related	[flat|nested] 3+ messages in thread

* [PATCH v2 1/2] libmount: keep reused loop devices attached on mount failure
  2026-10-04  2:41 [PATCH v2 0/2] libmount: preserve reused loop devices and request direct I/O Tianyi Chen
  2026-09-21  7:34 ` [PATCH v2 2/2] libmount: support explicit loop direct I/O requests Tianyi Chen
@ 2026-10-02 12:17 ` Tianyi Chen
  1 sibling, 0 replies; 3+ messages in thread
From: Tianyi Chen @ 2026-10-02 12:17 UTC (permalink / raw)
  To: kzak; +Cc: util-linux

A loop device found by overlap detection may have been set up by the
caller. Do not detach it when a later mount step fails; only close the
reference acquired by this mount.

Add a regression test using a preconfigured device and an invalid
filesystem option.

Signed-off-by: Tianyi Chen <hi@tychen.cc>
---
 libmount/src/hook_loopdev.c     |  7 +++++++
 tests/expected/mount/loop-reuse |  1 +
 tests/ts/mount/loop-reuse       | 23 +++++++++++++++++++++++
 3 files changed, 31 insertions(+)
 create mode 100644 tests/expected/mount/loop-reuse
 create mode 100755 tests/ts/mount/loop-reuse

diff --git a/libmount/src/hook_loopdev.c b/libmount/src/hook_loopdev.c
index b2291340..809d64d8 100644
--- a/libmount/src/hook_loopdev.c
+++ b/libmount/src/hook_loopdev.c
@@ -21,6 +21,7 @@
 
 struct hook_data {
 	int loopdev_fd;
+	bool reused;
 };
 
 static int delete_loopdev(struct libmnt_context *cxt, struct hook_data *hd);
@@ -350,6 +351,7 @@ static int setup_loopdev(struct libmnt_context *cxt,
 	} while (1);
 
 success:
+	hd->reused = reuse;
 	if (!rc)
 		rc = mnt_fs_set_source(cxt->fs, loopcxt_get_device(&lc));
 
@@ -408,6 +410,11 @@ static int delete_loopdev(struct libmnt_context *cxt, struct hook_data *hd)
 		hd->loopdev_fd = -1;
 	}
 
+	if (hd && hd->reused) {
+		DBG_OBJ(LOOP, cxt, ul_debug("keeping reused device"));
+		return 0;
+	}
+
 	rc = loopdev_detach(src);	/* see lib/loopdev.c */
 
 	DBG_OBJ(LOOP, cxt, ul_debug("deleted [rc=%d]", rc));
diff --git a/tests/expected/mount/loop-reuse b/tests/expected/mount/loop-reuse
new file mode 100644
index 00000000..9b132bb6
--- /dev/null
+++ b/tests/expected/mount/loop-reuse
@@ -0,0 +1 @@
+reused device preserved: OK
diff --git a/tests/ts/mount/loop-reuse b/tests/ts/mount/loop-reuse
new file mode 100755
index 00000000..704b3378
--- /dev/null
+++ b/tests/ts/mount/loop-reuse
@@ -0,0 +1,23 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+TS_TOPDIR="${0%/*}/../.."
+TS_DESC="keep reused loop device on failure"
+
+. "$TS_TOPDIR"/functions.sh
+ts_init "$*"
+ts_check_test_command "$TS_CMD_MOUNT"
+ts_check_test_command "$TS_CMD_UMOUNT"
+ts_skip_nonroot
+ts_check_losetup
+ts_check_prog "mkfs.ext2"
+
+ts_device_init
+mkfs.ext2 -F "$TS_LODEV" &> /dev/null || ts_die "Cannot make filesystem"
+mkdir -p "$TS_MOUNTPOINT"
+$TS_CMD_MOUNT -t ext2 -o loop,invalid-fs-option "$TS_OUTDIR/$TS_TESTNAME.img" \
+	"$TS_MOUNTPOINT" &> /dev/null && ts_die "Accepted invalid filesystem option"
+[ "$($TS_CMD_LOSETUP -j "$TS_OUTDIR/$TS_TESTNAME.img" | cut -d: -f1)" = "$TS_LODEV" ] ||
+	ts_die "Failed mount detached reused device"
+ts_log "reused device preserved: OK"
+ts_finalize
-- 
2.55.0



^ permalink raw reply related	[flat|nested] 3+ messages in thread

* [PATCH v2 0/2] libmount: preserve reused loop devices and request direct I/O
@ 2026-10-04  2:41 Tianyi Chen
  2026-09-21  7:34 ` [PATCH v2 2/2] libmount: support explicit loop direct I/O requests Tianyi Chen
  2026-10-02 12:17 ` [PATCH v2 1/2] libmount: keep reused loop devices attached on mount failure Tianyi Chen
  0 siblings, 2 replies; 3+ messages in thread
From: Tianyi Chen @ 2026-10-04  2:41 UTC (permalink / raw)
  To: kzak; +Cc: util-linux

Hi Karel,

This revision addresses your review of PR #4646. The reused-device fix
is now separate from the direct-I/O option.

Changes in v2:
- Add a specific diagnostic for direct-I/O mode conflicts.
- Use ul_strtobool() and the test-suite cleanup helpers.
- Document buffered-I/O fallback and how to check the effective mode.
- Add the option comment header and reused-device debug message.
- Remove the AI attribution trailer as requested.

Built mount, umount, losetup and findmnt with Meson. As root in a private
mount namespace, loop-reuse, loop-direct-io, regfile and fstab-loop all
passed. The direct-I/O test also checks boolean aliases and the conflict
diagnostic.

Thanks,
Tianyi

Tianyi Chen (2):
  libmount: keep reused loop devices attached on mount failure
  libmount: support explicit loop direct I/O requests

 libmount/src/hook_loopdev.c         | 41 +++++++++++++-
 sys-utils/mount.8.adoc              | 13 ++++-
 tests/expected/mount/loop-direct-io |  6 +++
 tests/expected/mount/loop-reuse     |  1 +
 tests/ts/mount/loop-direct-io       | 84 +++++++++++++++++++++++++++++
 tests/ts/mount/loop-reuse           | 23 ++++++++
 6 files changed, 165 insertions(+), 3 deletions(-)
 create mode 100644 tests/expected/mount/loop-direct-io
 create mode 100644 tests/expected/mount/loop-reuse
 create mode 100755 tests/ts/mount/loop-direct-io
 create mode 100755 tests/ts/mount/loop-reuse


base-commit: 55e3c4dba6101a47ecaae71c91edbe10c59b8d56
-- 
2.55.0



^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-10-04  2:42 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-04  2:41 [PATCH v2 0/2] libmount: preserve reused loop devices and request direct I/O Tianyi Chen
2026-09-21  7:34 ` [PATCH v2 2/2] libmount: support explicit loop direct I/O requests Tianyi Chen
2026-10-02 12:17 ` [PATCH v2 1/2] libmount: keep reused loop devices attached on mount failure Tianyi Chen

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox