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

Denys Vlasenko vda.linux at googlemail.com
Sun Jun 9 21:22:18 UTC 2019


Looks too big. Please try current git, it has a smaller version.
Please describe unwanted behavior (which you are fixing) more clearly

On Tue, Mar 5, 2019 at 12:06 PM Nicolas Hüppelshäuser
<nicolas.hueppelshaeuser at emlix.com> wrote:
>
> 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
>
> _______________________________________________
> busybox mailing list
> busybox at busybox.net
> http://lists.busybox.net/mailman/listinfo/busybox


More information about the busybox mailing list