From 2b326e7cf2bdeee47e24f8ef372960d429b49081 Mon Sep 17 00:00:00 2001 From: Karel Zak Date: Fri, 23 Nov 2007 15:13:22 +0100 Subject: [PATCH] losetup: support unlimited number of loops Old implementation: - supports 256 loop devices only - doesn't support gaps in list of loop devices (e.g. loop0, loop1, loop3 -- loop3 is invisible) Kernel 2.6.21 removes artificial maximum 256 loop device. Now the maximum of loop devices could be really huge (depends on limit of MINOR numbers). It means we need a better way how work with loop devices than blindly call stat(2) for all 0-1048575 devices. This patch uses three methods: a) scan /sys/block/loopN (used for losetup -a only). This method is probably the fastest way how found used loop device on machine with huge number of devices in /dev. b) classic way, stat(2) for all loop[0-7] devices (default number of loop devices). This cheap method is sufficient for 99% of all machines. c) scan all /dev/loopN or /dev/loop/N Signed-off-by: Karel Zak --- mount/lomount.c | 427 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 318 insertions(+), 109 deletions(-) diff --git a/mount/lomount.c b/mount/lomount.c index 56d7234d..43c33516 100644 --- a/mount/lomount.c +++ b/mount/lomount.c @@ -1,7 +1,4 @@ /* Originally from Ted's losetup.c */ - -#define LOOPMAJOR 7 - /* * losetup.c - setup and control loop devices */ @@ -18,6 +15,7 @@ #include #include #include +#include #include "loop.h" #include "lomount.h" @@ -61,20 +59,260 @@ loop_info64_to_old(const struct loop_info64 *info64, struct loop_info *info) return 0; } +#define DEV_LOOP_PATH "/dev/loop" +#define DEV_PATH "/dev" +#define SYSFS_BLOCK_PATH "/sys/block" +#define LOOPMAJOR 7 +#define NLOOPS_DEFAULT 8 /* /dev/loop[0-7] */ + +struct looplist { + int flag; /* scanning options */ + int ndef; /* number of tested default devices */ + struct dirent **names; /* scandir-like list of loop devices */ + int nnames; /* number of items in names */ + int ncur; /* current possition in direcotry */ + char name[32]; /* device name */ + int ct_perm; /* count permission problems */ + int ct_succ; /* count number of successfully + detected devices */ +}; + +#define LLFLG_USEDONLY (1 << 1) /* return used devices only */ +#define LLFLG_FREEONLY (1 << 2) /* return non-used devices */ +#define LLFLG_DONE (1 << 3) /* all is done */ +#define LLFLG_SYSFS (1 << 4) /* try to use /sys/block */ +#define LLFLG_SUBDIR (1 << 5) /* /dev/loop/N */ +#define LLFLG_DFLT (1 << 6) /* directly try to check default loops */ + +int +is_loop_device (const char *device) { + struct stat st; + + return (stat(device, &st) == 0 && + S_ISBLK(st.st_mode) && + major(st.st_rdev) == LOOPMAJOR); +} + +static int +is_loop_used(int fd) +{ + struct loop_info li; + return ioctl (fd, LOOP_GET_STATUS, &li) == 0; +} + +static char * +looplist_mk_devname(struct looplist *ll, int num) +{ + if (ll->flag & LLFLG_SUBDIR) + snprintf(ll->name, sizeof(ll->name), + DEV_LOOP_PATH "/%d", num); + else + snprintf(ll->name, sizeof(ll->name), + DEV_PATH "/loop%d", num); + + return is_loop_device(ll->name) ? ll->name : NULL; +} + +/* ignores all non-loop devices, default loop devices */ +static int +filter_loop(const struct dirent *d) +{ + return strncmp(d->d_name, "loop", 4) == 0; +} + +/* all loops exclude default loops */ +static int +filter_loop_ndflt(const struct dirent *d) +{ + int mn; + + if (strncmp(d->d_name, "loop", 4) == 0 && + sscanf(d->d_name, "loop%d", &mn) == 1 && + mn >= NLOOPS_DEFAULT) + return 1; + return 0; +} + +static int +filter_loop_num(const struct dirent *d) +{ + char *end = NULL; + int mn = strtol(d->d_name, &end, 10); + + if (mn >= NLOOPS_DEFAULT && end && *end == '\0') + return 1; + return 0; +} + +static int +looplist_open(struct looplist *ll, int flag) +{ + struct stat st; + + memset(ll, 0, sizeof(*ll)); + ll->flag = flag; + ll->ndef = -1; + ll->ncur = -1; + + if (stat(DEV_PATH, &st) == -1 || (!S_ISDIR(st.st_mode))) + return -1; /* /dev doesn't exist */ + + if (stat(DEV_LOOP_PATH, &st) == 0 && S_ISDIR(st.st_mode)) + ll->flag |= LLFLG_SUBDIR; /* /dev/loop/ exists */ + + if ((ll->flag & LLFLG_USEDONLY) && + stat(SYSFS_BLOCK_PATH, &st) == 0 && + S_ISDIR(st.st_mode)) + ll->flag |= LLFLG_SYSFS; /* try to use /sys/block/loopN */ + + ll->flag |= LLFLG_DFLT; /* required! */ + return 0; +} + +static void +looplist_close(struct looplist *ll) +{ + if (ll->names) { + for(++ll->ncur; ll->ncur < ll->nnames; ll->ncur++) + free(ll->names[ll->ncur]); + + free(ll->names); + ll->names = NULL; + ll->nnames = 0; + } + ll->ncur = -1; + ll->flag |= LLFLG_DONE; +} + +static int +looplist_is_wanted(struct looplist *ll, int fd) +{ + int ret; + + if (!(ll->flag & (LLFLG_USEDONLY | LLFLG_FREEONLY))) + return 1; + ret = is_loop_used(fd); + + if ((ll->flag & LLFLG_USEDONLY) && ret == 0) + return 0; + if ((ll->flag & LLFLG_FREEONLY) && ret == 1) + return 0; + + return 1; +} + +static int +looplist_next(struct looplist *ll) +{ + int fd; + int ret; + char *dirname, *dev; + + if (ll->flag & LLFLG_DONE) + return -1; + + /* A) try to use /sys/block/loopN devices (for losetup -a only) + */ + if (ll->flag & LLFLG_SYSFS) { + int mn; + + if (!ll->nnames) { + ll->nnames = scandir(SYSFS_BLOCK_PATH, &ll->names, + filter_loop, versionsort); + ll->ncur = -1; + } + for(++ll->ncur; ll->ncur < ll->nnames; ll->ncur++) { + ret = sscanf(ll->names[ll->ncur]->d_name, "loop%d", &mn); + free(ll->names[ll->ncur]); + if (ret != 1) + continue; + dev = looplist_mk_devname(ll, mn); + if (dev) { + ll->ct_succ++; + if ((fd = open(dev, O_RDONLY)) > -1) { + if (looplist_is_wanted(ll, fd)) + return fd; + close(fd); + } else if (errno == EACCES) + ll->ct_perm++; + } + } + if (ll->nnames) + free(ll->names); + ll->names = NULL; + ll->ncur = -1; + ll->nnames = 0; + ll->flag &= ~LLFLG_SYSFS; + goto done; + } + + /* B) Classic way, try first eight loop devices (default number + * of loop devices). This is enough for 99% of all cases. + */ + if (ll->flag & LLFLG_DFLT) { + for (++ll->ncur; ll->ncur < NLOOPS_DEFAULT; ll->ncur++) { + dev = looplist_mk_devname(ll, ll->ncur); + if (dev) { + ll->ct_succ++; + if ((fd = open(dev, O_RDONLY)) > -1) { + if (looplist_is_wanted(ll, fd)) + return fd; + close(fd); + } else if (errno == EACCES) + ll->ct_perm++; + } + } + ll->flag &= ~LLFLG_DFLT; + ll->ncur = -1; + } + + + /* C) the worst posibility, scan all /dev or /dev/loop + */ + dirname = ll->flag & LLFLG_SUBDIR ? DEV_LOOP_PATH : DEV_PATH; + + if (!ll->nnames) { + ll->nnames = scandir(dirname, &ll->names, + ll->flag & LLFLG_SUBDIR ? + filter_loop_num : filter_loop_ndflt, + versionsort); + ll->ncur = -1; + } + + for(++ll->ncur; ll->ncur < ll->nnames; ll->ncur++) { + struct stat st; + + snprintf(ll->name, sizeof(ll->name), + "%s/%s", dirname, ll->names[ll->ncur]->d_name); + free(ll->names[ll->ncur]); + ret = stat(ll->name, &st); + + if (ret == 0 && S_ISBLK(st.st_mode) && + major(st.st_rdev) == LOOPMAJOR && + minor(st.st_rdev) >= NLOOPS_DEFAULT) { + ll->ct_succ++; + fd = open(ll->name, O_RDONLY); + + if (fd != -1) { + if (looplist_is_wanted(ll, fd)) + return fd; + close(fd); + } else if (errno == EACCES) + ll->ct_perm++; + } + } +done: + looplist_close(ll); + return -1; +} + #ifdef MAIN static int -show_loop(char *device) { +show_loop_fd(int fd, char *device) { struct loop_info loopinfo; struct loop_info64 loopinfo64; - int fd, errsv; - - if ((fd = open(device, O_RDONLY)) < 0) { - int errsv = errno; - fprintf(stderr, _("loop: can't open device %s: %s\n"), - device, strerror (errsv)); - return 2; - } + int errsv; if (ioctl(fd, LOOP_GET_STATUS64, &loopinfo64) == 0) { @@ -102,7 +340,6 @@ show_loop(char *device) { e, loopinfo64.lo_encrypt_type); } printf("\n"); - close (fd); return 0; } @@ -119,52 +356,55 @@ show_loop(char *device) { loopinfo.lo_encrypt_type); printf("\n"); - close (fd); return 0; } errsv = errno; fprintf(stderr, _("loop: can't get info on device %s: %s\n"), device, strerror (errsv)); - close (fd); return 1; } +static int +show_loop(char *device) { + int ret, fd; + + if ((fd = open(device, O_RDONLY)) < 0) { + int errsv = errno; + fprintf(stderr, _("loop: can't open device %s: %s\n"), + device, strerror (errsv)); + return 2; + } + ret = show_loop_fd(fd, device); + close(fd); + return ret; +} + + static int show_used_loop_devices (void) { - char dev[20]; - char *loop_formats[] = { "/dev/loop%d", "/dev/loop/%d" }; - int i, j, fd, permission = 0, somedev = 0; - struct stat statbuf; - struct loop_info loopinfo; + struct looplist ll; + int fd; - for (j = 0; j < SIZE(loop_formats); j++) { - for(i = 0; i < 256; i++) { - snprintf(dev, sizeof(dev), loop_formats[j], i); - if (stat (dev, &statbuf) == 0 && S_ISBLK(statbuf.st_mode)) { - fd = open (dev, O_RDONLY); - if (fd >= 0) { - if(ioctl (fd, LOOP_GET_STATUS, &loopinfo) == 0) - show_loop(dev); - close (fd); - somedev++; - } else if (errno == EACCES) - permission++; - continue; /* continue trying as long as devices exist */ - } - break; - } + if (looplist_open(&ll, LLFLG_USEDONLY) == -1) { + error(_("%s: /dev directory does not exist."), progname); + return 1; + } + + while((fd = looplist_next(&ll)) != -1) { + show_loop_fd(fd, ll.name); + close(fd); } + looplist_close(&ll); - if (somedev==0 && permission) { + if (ll.ct_succ && ll.ct_perm) { error(_("%s: no permission to look at /dev/loop#"), progname); return 1; } return 0; } - -#endif +#endif /* MAIN */ /* check if the loopfile is already associated with the same given * parameters. @@ -204,37 +444,32 @@ is_associated(int dev, struct stat *file, unsigned long long offset) */ char * loopfile_used (const char *filename, unsigned long long offset) { - char dev[20]; - char *loop_formats[] = { "/dev/loop%d", "/dev/loop/%d" }; - int i, j, fd; - struct stat devstat, filestat; - struct loop_info loopinfo; + struct looplist ll; + char *devname = NULL; + struct stat filestat; + int fd; if (stat(filename, &filestat) == -1) { perror(filename); return NULL; } - for (j = 0; j < SIZE(loop_formats); j++) { - for(i = 0; i < 256; i++) { - snprintf(dev, sizeof(dev), loop_formats[j], i); - if (stat (dev, &devstat) == 0 && S_ISBLK(devstat.st_mode)) { - fd = open (dev, O_RDONLY); - if (fd >= 0) { - int res = 0; + if (looplist_open(&ll, LLFLG_USEDONLY) == -1) { + error(_("%s: /dev directory does not exist."), progname); + return NULL; + } - if(ioctl (fd, LOOP_GET_STATUS, &loopinfo) == 0) - res = is_associated(fd, &filestat, offset); - close (fd); - if (res == 1) - return xstrdup(dev); - } - continue; /* continue trying as long as devices exist */ + while((fd = looplist_next(&ll)) != -1) { + int res = is_associated(fd, &filestat, offset); + close(fd); + if (res == 1) { + devname = xstrdup(ll.name); + break; } - break; - } } - return NULL; + looplist_close(&ll); + + return devname; } int @@ -262,62 +497,36 @@ loopfile_used_with(char *devname, const char *filename, unsigned long long offse return ret; } -int -is_loop_device (const char *device) { - struct stat statbuf; - - return (stat(device, &statbuf) == 0 && - S_ISBLK(statbuf.st_mode) && - major(statbuf.st_rdev) == LOOPMAJOR); -} - char * find_unused_loop_device (void) { - /* Just creating a device, say in /tmp, is probably a bad idea - - people might have problems with backup or so. - So, we just try /dev/loop[0-7]. */ - char dev[20]; - char *loop_formats[] = { "/dev/loop%d", "/dev/loop/%d" }; - int i, j, fd, somedev = 0, someloop = 0, permission = 0; - struct stat statbuf; - struct loop_info loopinfo; + struct looplist ll; + char *devname = NULL; + int fd; - for (j = 0; j < SIZE(loop_formats); j++) { - for(i = 0; i < 256; i++) { - sprintf(dev, loop_formats[j], i); - if (stat (dev, &statbuf) == 0 && S_ISBLK(statbuf.st_mode)) { - somedev++; - fd = open (dev, O_RDONLY); - if (fd >= 0) { - if(ioctl (fd, LOOP_GET_STATUS, &loopinfo) == 0) - someloop++; /* in use */ - else if (errno == ENXIO) { - close (fd); - return xstrdup(dev);/* probably free */ - } - close (fd); - } else if (errno == EACCES) - permission++; + if (looplist_open(&ll, LLFLG_FREEONLY) == -1) { + error(_("%s: /dev directory does not exist."), progname); + return NULL; + } - continue;/* continue trying as long as devices exist */ - } - break; - } + if ((fd = looplist_next(&ll)) != -1) { + close(fd); + devname = xstrdup(ll.name); } + looplist_close(&ll); + if (devname) + return devname; - if (!somedev) - error(_("%s: could not find any device /dev/loop#"), progname); - else if (!someloop && permission) + if (ll.ct_succ && ll.ct_perm) error(_("%s: no permission to look at /dev/loop#"), progname); - else if (!someloop) + else if (ll.ct_succ) + error(_("%s: could not find any free loop device"), progname); + else error(_( "%s: Could not find any loop device. Maybe this kernel " "does not know\n" " about the loop device? (If so, recompile or " "`modprobe loop'.)"), progname); - else - error(_("%s: could not find any free loop device"), progname); - return 0; + return NULL; } /* @@ -531,7 +740,7 @@ mutter(void) { fprintf(stderr, _("This mount was compiled without loop support. " "Please recompile.\n")); -} +} int set_loop (const char *device, const char *file, unsigned long long offset, @@ -552,7 +761,7 @@ find_unused_loop_device (void) { return 0; } -#endif +#endif /* !LOOP_SET_FD */ #ifdef MAIN @@ -728,5 +937,5 @@ main(int argc, char **argv) { "Please recompile.\n")); return -1; } -#endif -#endif +#endif /* !LOOP_SET_FD*/ +#endif /* MAIN */ -- 2.39.5