From edb68d0ca36c54fa5996bfd917eb236780f0ff80 Mon Sep 17 00:00:00 2001 From: Karel Zak Date: Fri, 1 Aug 2008 12:18:03 +0200 Subject: [PATCH] losetup: looplist_* refactoring, remove scandir() This patch replaces scandir-based implementation with readdir(). The readdir(3) is less expensive and more portable (to non-glibc environment). The patch also replaces sysfs-based solution with simpler /proc/partitions parsing. The /proc/partitions includes all used loop devices on all systems (include 2.4). This solution seems faster than scandir(/sys/block/) too. Summary, the losetup (with this patch) uses three methods to found a loop device: a) parse /proc/partitions to found already used loop devices (for loserup -a) b) stat(2) for all loop[0-7] devices (default number of loop devices). This is classic method from util-linux <= 2.13. This method is good enough for standard Linux machines with default number of loop devices. c) scan all /dev or /dev/loop/ for loop devices. This is useful for crazy people who need more than 8 loop devices. Signed-off-by: Karel Zak --- include/pathnames.h | 1 + mount/lomount.c | 302 ++++++++++++++++++++++---------------------- 2 files changed, 154 insertions(+), 149 deletions(-) diff --git a/include/pathnames.h b/include/pathnames.h index e171d936..ad35bf91 100644 --- a/include/pathnames.h +++ b/include/pathnames.h @@ -73,6 +73,7 @@ #define _PATH_PROC_SWAPS "/proc/swaps" #define _PATH_PROC_FILESYSTEMS "/proc/filesystems" #define _PATH_PROC_MOUNTS "/proc/mounts" +#define _PATH_PROC_PARTITIONS "/proc/partitions" #ifndef _PATH_MOUNTED # ifdef MOUNTED /* deprecated */ diff --git a/mount/lomount.c b/mount/lomount.c index de28a265..5cf50f88 100644 --- a/mount/lomount.c +++ b/mount/lomount.c @@ -2,7 +2,6 @@ /* * losetup.c - setup and control loop devices */ - #include #include #include @@ -24,6 +23,7 @@ #include "sundries.h" #include "xmalloc.h" #include "realpath.h" +#include "pathnames.h" #define SIZE(a) (sizeof(a)/sizeof(a[0])) @@ -63,17 +63,16 @@ loop_info64_to_old(const struct loop_info64 *info64, struct loop_info *info) #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 */ + FILE *proc; /* /proc/partitions */ + int ncur; /* current possition */ + int *minors; /* ary of minor numbers (when scan whole /dev) */ + int nminors; /* number of items in *minors */ + char name[128]; /* device name */ int ct_perm; /* count permission problems */ int ct_succ; /* count number of successfully detected devices */ @@ -82,7 +81,7 @@ struct looplist { #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_PROCFS (1 << 4) /* try to found used devices in /proc/partitions */ #define LLFLG_SUBDIR (1 << 5) /* /dev/loop/N */ #define LLFLG_DFLT (1 << 6) /* directly try to check default loops */ @@ -132,50 +131,6 @@ is_loop_autoclear(const char *device) return rc; } -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) { @@ -183,7 +138,6 @@ looplist_open(struct looplist *ll, int flag) 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))) @@ -193,9 +147,8 @@ looplist_open(struct looplist *ll, int flag) 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 */ + stat(_PATH_PROC_PARTITIONS, &st) == 0) + ll->flag |= LLFLG_PROCFS; /* try /proc/partitions */ ll->flag |= LLFLG_DFLT; /* required! */ return 0; @@ -204,135 +157,184 @@ looplist_open(struct looplist *ll, int flag) 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; - } + if (ll->minors) + free(ll->minors); + if (ll->proc) + fclose(ll->proc); + ll->minors = NULL; + ll->proc = NULL; ll->ncur = -1; ll->flag |= LLFLG_DONE; } static int -looplist_is_wanted(struct looplist *ll, int fd) +looplist_open_dev(struct looplist *ll, int lnum) { - int ret; + struct stat st; + int used; + int fd; + + /* create a full device path */ + snprintf(ll->name, sizeof(ll->name), + ll->flag & LLFLG_SUBDIR ? + DEV_LOOP_PATH "/%d" : + DEV_PATH "/loop%d", + lnum); + + fd = open(ll->name, O_RDONLY); + if (fd == -1) { + if (errno == EACCES) + ll->ct_perm++; + return -1; + } + if (fstat(fd, &st) == -1) + goto error; + if (!S_ISBLK(st.st_mode) || major(st.st_rdev) != LOOPMAJOR) + goto error; + ll->ct_succ++; + + /* check if the device is wanted */ if (!(ll->flag & (LLFLG_USEDONLY | LLFLG_FREEONLY))) - return 1; - ret = is_loop_used(fd); + return fd; - if ((ll->flag & LLFLG_USEDONLY) && ret == 0) - return 0; - if ((ll->flag & LLFLG_FREEONLY) && ret == 1) - return 0; + used = is_loop_used(fd); - return 1; + if ((ll->flag & LLFLG_USEDONLY) && used) + return fd; + if ((ll->flag & LLFLG_FREEONLY) && !used) + return fd; +error: + close(fd); + return -1; +} + +/* returns from "loop" */ +static int +name2minor(int hasprefix, const char *name) +{ + int n; + char *end; + + if (hasprefix) { + if (strncmp(name, "loop", 4)) + return -1; + name += 4; + } + n = strtol(name, &end, 10); + if (end && end != name && *end == '\0' && n >= 0) + return n; + return -1; +} + +static int +cmpnum(const void *p1, const void *p2) +{ + return (* (int *) p1) > (* (int *) p2); +} + +/* + * The classic scandir() is more expensive and less portable. + * We needn't full loop device names -- minor numers (loop) + * are enough. + */ +static int +loop_scandir(const char *dirname, int **ary, int hasprefix) +{ + DIR *dir; + struct dirent *d; + int n, count = 0, arylen = 0; + + if (!dirname || !ary) + return -1; + dir = opendir(dirname); + if (!dir) + return -1; + + *ary = NULL; + + while((d = readdir(dir))) { + if (d->d_type != DT_BLK && d->d_type != DT_UNKNOWN) + continue; + n = name2minor(hasprefix, d->d_name); + if (n == -1 || n < NLOOPS_DEFAULT) + continue; + if (count + 1 > arylen) { + arylen += 1; + *ary = *ary ? realloc(*ary, arylen * sizeof(int)) : + malloc(arylen * sizeof(int)); + if (!*ary) + return -1; + } + (*ary)[count++] = n; + } + if (count) + qsort(*ary, count, sizeof(int), cmpnum); + + closedir(dir); + return count; } static int looplist_next(struct looplist *ll) { - int fd; - int ret; - char *dirname, *dev; + int fd, n; if (ll->flag & LLFLG_DONE) return -1; - /* A) try to use /sys/block/loopN devices (for losetup -a only) + /* A) Look for used loop devices in /proc/partitions ("losetup -a" only) */ - if (ll->flag & LLFLG_SYSFS) { - int mn; + if (ll->flag & LLFLG_PROCFS) { + char buf[BUFSIZ]; - 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) + if (!ll->proc) + ll->proc = fopen(_PATH_PROC_PARTITIONS, "r"); + + while (ll->proc && fgets(buf, sizeof(buf), ll->proc)) { + int m; + unsigned long long sz; + char name[128]; + + if (sscanf(buf, " %d %d %llu %128[^\n ]", + &m, &n, &sz, name) != 4) 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 (m != LOOPMAJOR) + continue; + fd = looplist_open_dev(ll, n); + if (fd != -1) + return fd; } - 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++; - } + fd = looplist_open_dev(ll, ll->ncur); + if (fd != -1) + return fd; } 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); + if (!ll->minors) { + ll->nminors = (ll->flag & LLFLG_SUBDIR) ? + loop_scandir(DEV_LOOP_PATH, &ll->minors, 0) : + loop_scandir(DEV_PATH, &ll->minors, 1); 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++; - } + for (++ll->ncur; ll->ncur < ll->nminors; ll->ncur++) { + fd = looplist_open_dev(ll, ll->minors[ll->ncur]); + if (fd != -1) + return fd; } + done: looplist_close(ll); return -1; @@ -429,8 +431,9 @@ show_used_loop_devices (void) { } looplist_close(&ll); - if (ll.ct_succ && ll.ct_perm) { - error(_("%s: no permission to look at /dev/loop#"), progname); + if (!ll.ct_succ && ll.ct_perm) { + error(_("%s: no permission to look at /dev/loop%s"), progname, + (ll.flag & LLFLG_SUBDIR) ? "/" : ""); return 1; } return 0; @@ -576,8 +579,9 @@ find_unused_loop_device (void) { if (devname) return devname; - if (ll.ct_succ && ll.ct_perm) - error(_("%s: no permission to look at /dev/loop#"), progname); + if (!ll.ct_succ && ll.ct_perm) + error(_("%s: no permission to look at /dev/loop%s"), progname, + (ll.flag & LLFLG_SUBDIR) ? "/" : ""); else if (ll.ct_succ) error(_("%s: could not find any free loop device"), progname); else -- 2.39.5