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 <[email protected]>
---
 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

_______________________________________________
busybox mailing list
[email protected]
http://lists.busybox.net/mailman/listinfo/busybox

Reply via email to