[PATCH] losetup/mount: Revise to use /dev/loop-control and thereby fix 'losetup -f'.

Nicolas Hüppelshäuser nicolas.hueppelshaeuser at emlix.com
Tue Mar 5 10:56:21 UTC 2019


Fixed 'losetup -f' and 'losetup -f <file>' issue
=================================================

With FEATURE_MOUNT_LOOP_CREATE:

On a system with mounted devtmpfs /dev there might be less loop nodes in
existence than required.

(1) If all existing nodes are in use, 'losetup -f' would correctly return
the next free node, but it would not create that node.

(2) If all existing nodes are in use, 'losetup -f <file>' would also not
create the required node file.

Why revise to use /dev/loop-control
====================================

(1) Because using /dev/loop-control puts the Linux kernel in charge to
ensure the returned free node is an existing file and busybox does not
need to create new files below /dev.

(2) Because libbb/loop.c:set_loop() already contained a "to be done"
note to switch from iterating the existing nodes to using
/dev/loop-control.

This fix/feature is activated via config option
FEATURE_MOUNT_LOOP_LOOP_CONTROL (enabled by default).

Signed-off-by: Nicolas Hüppelshäuser <nicolas.hueppelshaeuser at emlix.com>
---
 include/libbb.h       |   3 +
 libbb/loop.c          | 165 +++++++++++++++++++++++++++++++++++-------
 util-linux/Config.src |  14 +++-
 util-linux/losetup.c  |  16 +++-
 4 files changed, 169 insertions(+), 29 deletions(-)

diff --git a/include/libbb.h b/include/libbb.h
index daa96728b..12b73b7d3 100644
--- a/include/libbb.h
+++ b/include/libbb.h
@@ -1447,6 +1447,9 @@ extern void bb_warn_ignoring_args(char *arg) FAST_FUNC;
 extern int get_linux_version_code(void) FAST_FUNC;
 
 extern char *query_loop(const char *device) FAST_FUNC;
+#if ENABLE_FEATURE_MOUNT_LOOP_LOOP_CONTROL
+extern char *get_loop(void) FAST_FUNC;
+#endif
 extern int del_loop(const char *device) FAST_FUNC;
 /*
  * If *devname is not NULL, use that name, otherwise try to find free one,
diff --git a/libbb/loop.c b/libbb/loop.c
index c78535a20..3f7315ca1 100644
--- a/libbb/loop.c
+++ b/libbb/loop.c
@@ -65,6 +65,28 @@ char* FAST_FUNC query_loop(const char *device)
 	return dev;
 }
 
+#if ENABLE_FEATURE_MOUNT_LOOP_LOOP_CONTROL
+/* Obtain an unused loop device node */
+char* FAST_FUNC get_loop(void)
+{
+	int fd;
+	int loopdevno;
+
+	fd = open("/dev/loop-control", O_RDWR | O_CLOEXEC);
+	if (fd == -1) {
+		return NULL;
+	}
+
+	loopdevno = ioctl(fd, LOOP_CTL_GET_FREE);
+	close(fd);
+	if (loopdevno == -1) {
+		return NULL;
+	}
+
+	return xasprintf(LOOP_FORMAT, loopdevno);
+}
+#endif
+
 int FAST_FUNC del_loop(const char *device)
 {
 	int fd, rc;
@@ -78,6 +100,120 @@ int FAST_FUNC del_loop(const char *device)
 	return rc;
 }
 
+/*
+ * Local helper function used by both implementations of set_loop()
+ * to associate a file with a loop device.
+ * loopfd	file descriptor of loop device
+ * filefd	file descriptor of file to bind to loop device
+ * file		file name of filefd
+ * flags	flags used to open() file with
+ * offset	to be fed into loop_info struct
+ * Returns 0 on success, -1 on error.
+ */
+static int FAST_FUNC associate_with_loop(int loopfd, int filefd, const char *file, unsigned flags, unsigned long long offset)
+{
+	bb_loop_info loopinfo;
+	int rc = -1;
+
+	/* Associate free loop device with file.  */
+	if (ioctl(loopfd, LOOP_SET_FD, filefd) == 0) {
+		memset(&loopinfo, 0, sizeof(loopinfo));
+		safe_strncpy((char *)loopinfo.lo_file_name, file, LO_NAME_SIZE);
+		loopinfo.lo_offset = offset;
+		/*
+		 * Used by mount to set LO_FLAGS_AUTOCLEAR.
+		 * LO_FLAGS_READ_ONLY is not set because RO is controlled by open type of the file.
+		 * Note that closing LO_FLAGS_AUTOCLEARed loopfd before mount
+		 * is wrong (would free the loop device!)
+		 */
+		loopinfo.lo_flags = (flags & ~BB_LO_FLAGS_READ_ONLY);
+		rc = ioctl(loopfd, BB_LOOP_SET_STATUS, &loopinfo);
+		if (rc != 0 && (loopinfo.lo_flags & BB_LO_FLAGS_AUTOCLEAR)) {
+			/* Old kernel, does not support LO_FLAGS_AUTOCLEAR? */
+			/* (this code path is not tested) */
+			loopinfo.lo_flags -= BB_LO_FLAGS_AUTOCLEAR;
+			rc = ioctl(loopfd, BB_LOOP_SET_STATUS, &loopinfo);
+		}
+		if (rc != 0) {
+			ioctl(loopfd, LOOP_CLR_FD, 0);
+		}
+	}
+	return rc;
+}
+
+/* two implementations of set_loop() depending on ENABLE_FEATURE_MOUNT_LOOP_LOOP_CONTROL: */
+#if ENABLE_FEATURE_MOUNT_LOOP_LOOP_CONTROL
+/* Returns opened fd to the loop device, <0 on error.
+ * *device is loop device to use, or if *device==NULL finds a loop device to
+ * mount it on and sets *device to a strdup of that loop device name.
+ */
+int FAST_FUNC set_loop(char **device, const char *file, unsigned long long offset, unsigned flags)
+{
+	char *try;
+	bb_loop_info loopinfo;
+	int dfd, ffd, mode, rc;
+
+	rc = dfd = -1;
+
+	/* Open the file.  Barf if this doesn't work.  */
+	mode = (flags & BB_LO_FLAGS_READ_ONLY) ? O_RDONLY : O_RDWR;
+ open_ffd:
+	ffd = open(file, mode);
+	if (ffd < 0) {
+		if (mode != O_RDONLY) {
+			mode = O_RDONLY;
+			goto open_ffd;
+		}
+		return -errno;
+	}
+
+	/*
+	 * If caller did not provide an explicit loop node file name
+	 * we obtain a free loop node; otherwise there is no need
+	 * to check for existence of the loop node since the Linux
+	 * kernel should have provided one.
+	 */
+	try = *device;
+	if (try == NULL) {
+		try = get_loop();
+		if (try == NULL) {
+			close(ffd);
+			return -1;
+		}
+	}
+
+	/* Open the sucker and check its loopiness.  */
+	dfd = open(try, mode);
+	if (dfd < 0 && errno == EROFS) {
+		mode = O_RDONLY;
+		dfd = open(try, mode);
+	}
+	if (dfd < 0) {
+		close(ffd);
+		return -1;
+	}
+
+	/* If device is free, claim it (LOOP_GET_STATUS returns error number ENXIO in this case). */
+	rc = ioctl(dfd, BB_LOOP_GET_STATUS, &loopinfo);
+	if (rc && errno == ENXIO) {
+		rc = associate_with_loop(dfd, ffd, file, flags, offset);
+	} else {
+		rc = -1;
+	}
+
+	close(ffd);
+	if (rc == 0) {
+		if (!*device)
+			*device = try;
+		return dfd;
+	} else {
+		close(dfd);
+	}
+	return rc;
+}
+
+#else /* if not ENABLE_FEATURE_MOUNT_LOOP_LOOP_CONTROL */
+
 /* Returns opened fd to the loop device, <0 on error.
  * *device is loop device to use, or if *device==NULL finds a loop device to
  * mount it on and sets *device to a strdup of that loop device name.  This
@@ -106,10 +242,6 @@ int FAST_FUNC set_loop(char **device, const char *file, unsigned long long offse
 		return -errno;
 	}
 
-//TODO: use LOOP_CTL_GET_FREE instead of trying every loopN in sequence? a-la:
-// fd = open("/dev/loop-control", O_RDWR);
-// loopN = ioctl(fd, LOOP_CTL_GET_FREE);
-//
 	/* Find a loop device.  */
 	try = *device ? *device : dev;
 	/* 1048575 (0xfffff) is a max possible minor number in Linux circa 2010 */
@@ -150,29 +282,7 @@ int FAST_FUNC set_loop(char **device, const char *file, unsigned long long offse
 
 		/* If device is free, claim it.  */
 		if (rc && errno == ENXIO) {
-			/* Associate free loop device with file.  */
-			if (ioctl(dfd, LOOP_SET_FD, ffd) == 0) {
-				memset(&loopinfo, 0, sizeof(loopinfo));
-				safe_strncpy((char *)loopinfo.lo_file_name, file, LO_NAME_SIZE);
-				loopinfo.lo_offset = offset;
-				/*
-				 * Used by mount to set LO_FLAGS_AUTOCLEAR.
-				 * LO_FLAGS_READ_ONLY is not set because RO is controlled by open type of the file.
-				 * Note that closing LO_FLAGS_AUTOCLEARed dfd before mount
-				 * is wrong (would free the loop device!)
-				 */
-				loopinfo.lo_flags = (flags & ~BB_LO_FLAGS_READ_ONLY);
-				rc = ioctl(dfd, BB_LOOP_SET_STATUS, &loopinfo);
-				if (rc != 0 && (loopinfo.lo_flags & BB_LO_FLAGS_AUTOCLEAR)) {
-					/* Old kernel, does not support LO_FLAGS_AUTOCLEAR? */
-					/* (this code path is not tested) */
-					loopinfo.lo_flags -= BB_LO_FLAGS_AUTOCLEAR;
-					rc = ioctl(dfd, BB_LOOP_SET_STATUS, &loopinfo);
-				}
-				if (rc != 0) {
-					ioctl(dfd, LOOP_CLR_FD, 0);
-				}
-			}
+			rc = associate_with_loop(dfd, ffd, file, flags, offset);
 		} else {
 			rc = -1;
 		}
@@ -190,3 +300,4 @@ int FAST_FUNC set_loop(char **device, const char *file, unsigned long long offse
 	}
 	return rc;
 }
+#endif /* if ENABLE_FEATURE_MOUNT_LOOP_LOOP_CONTROL */
diff --git a/util-linux/Config.src b/util-linux/Config.src
index 0fad3e5c0..5f5121ccf 100644
--- a/util-linux/Config.src
+++ b/util-linux/Config.src
@@ -27,9 +27,21 @@ config FEATURE_MOUNT_LOOP
 	specify an offset or cryptographic options to the loopback device.
 	(If you don't want umount to free the loop device, use "umount -D".)
 
+config FEATURE_MOUNT_LOOP_LOOP_CONTROL
+	bool "Use Linux kernel /dev/loop-control for handling loopback devices"
+	default y
+	depends on FEATURE_MOUNT_LOOP && !FEATURE_MOUNT_LOOP_CREATE
+	help
+	Linux kernels >= 3.1 provide the /dev/loop-control device,
+	which permits an application to dynamically find a free device, and
+	to add and remove loop devices from the system.
+
+	This feature lets mount (and losetup) use /dev/loop-control to find
+	and add free loop devices.
+
 config FEATURE_MOUNT_LOOP_CREATE
 	bool "Create new loopback devices if needed"
-	default y
+	default n
 	depends on FEATURE_MOUNT_LOOP
 	help
 	Linux kernels >= 2.6.24 support unlimited loopback devices. They are
diff --git a/util-linux/losetup.c b/util-linux/losetup.c
index bf480e9bf..8b7124d29 100644
--- a/util-linux/losetup.c
+++ b/util-linux/losetup.c
@@ -99,8 +99,20 @@ int losetup_main(int argc UNUSED_PARAM, char **argv)
 	/* contains -f */
 	if (opt & OPT_f) {
 		char *s;
-		int n = 0;
 
+#if ENABLE_FEATURE_MOUNT_LOOP_LOOP_CONTROL
+		/* obtain free loop device */
+		s = get_loop();
+		if (s == NULL) {
+			bb_error_msg_and_die("no free loop devices");
+		}
+		if (strlen(s) >= sizeof(dev)) {
+			bb_error_msg_and_die("internal error: LOOP_NAMESIZE too small");
+		}
+		strcpy(dev, s);
+		free(s);
+#else /* if not ENABLE_FEATURE_MOUNT_LOOP_LOOP_CONTROL */
+		int n = 0;
 		do {
 			if (n > MAX_LOOP_NUM)
 				bb_error_msg_and_die("no free loop devices");
@@ -108,6 +120,8 @@ int losetup_main(int argc UNUSED_PARAM, char **argv)
 			s = query_loop(dev);
 			free(s);
 		} while (s);
+#endif /* ENABLE_FEATURE_MOUNT_LOOP_LOOP_CONTROL */
+
 		/* now: dev is next free "/dev/loopN" */
 		if ((opt == OPT_f) && !argv[0]) {
 			puts(dev);
-- 
2.17.1



More information about the busybox mailing list