diff options
author | Karel Zak | 2011-12-20 13:07:54 +0100 |
---|---|---|
committer | Karel Zak | 2011-12-20 13:07:54 +0100 |
commit | bd6d0d039c311b4f31f1c2a1312ff414dfbd020a (patch) | |
tree | b362c3559c370e34b7b0463c7a088ecb326898e7 /sys-utils | |
parent | losetup: remove mount-specific headers (diff) | |
download | kernel-qcow2-util-linux-bd6d0d039c311b4f31f1c2a1312ff414dfbd020a.tar.gz kernel-qcow2-util-linux-bd6d0d039c311b4f31f1c2a1312ff414dfbd020a.tar.xz kernel-qcow2-util-linux-bd6d0d039c311b4f31f1c2a1312ff414dfbd020a.zip |
losetup: move to sys-utils directory
Now losetup(8) is independent on mount/ stuff at all.
Signed-off-by: Karel Zak <kzak@redhat.com>
Diffstat (limited to 'sys-utils')
-rw-r--r-- | sys-utils/.gitignore | 1 | ||||
-rw-r--r-- | sys-utils/Makefile.am | 17 | ||||
-rw-r--r-- | sys-utils/losetup.8 | 188 | ||||
-rw-r--r-- | sys-utils/losetup.c | 1131 |
4 files changed, 1337 insertions, 0 deletions
diff --git a/sys-utils/.gitignore b/sys-utils/.gitignore index 01a3df986..3426674b0 100644 --- a/sys-utils/.gitignore +++ b/sys-utils/.gitignore @@ -14,6 +14,7 @@ ipcs ldattach linux32.8 linux64.8 +losetup lscpu mips32.8 mips64.8 diff --git a/sys-utils/Makefile.am b/sys-utils/Makefile.am index 7285bcb7e..8f2f12ec4 100644 --- a/sys-utils/Makefile.am +++ b/sys-utils/Makefile.am @@ -18,6 +18,23 @@ dist_man_MANS += dmesg.1 ctrlaltdel.8 cytune.8 setarch.8 \ ldattach.8 tunelp.8 rtcwake.8 fsfreeze.8 fstrim.8 endif +if BUILD_LOSETUP +sbin_PROGRAMS += losetup +dist_man_MANS += losetup.8 + +losetup_SOURCES = losetup.c \ + $(top_srcdir)/lib/canonicalize.c \ + $(top_srcdir)/lib/strutils.c + +if HAVE_STATIC_LOSETUP +bin_PROGRAMS += losetup.static +losetup_static_SOURCES = $(losetup_SOURCES) +losetup_static_LDFLAGS = -all-static +endif + +endif # BUILD_LOSETUP + + if BUILD_PRLIMIT usrbin_exec_PROGRAMS += prlimit dist_man_MANS += prlimit.1 diff --git a/sys-utils/losetup.8 b/sys-utils/losetup.8 new file mode 100644 index 000000000..a6b45a0dd --- /dev/null +++ b/sys-utils/losetup.8 @@ -0,0 +1,188 @@ +.TH LOSETUP 8 "July 2003" "util-linux" "System Administration" +.SH NAME +losetup \- set up and control loop devices +.SH SYNOPSIS +.ad l +Get info: +.sp +.in +5 +.B losetup +.I loopdev +.sp +.B losetup -a +.sp +.B losetup -j +.I file +.RB [ \-o +.IR offset ] +.sp +.in -5 +Delete loop: +.sp +.in +5 +.B "losetup \-d" +.IR loopdev ... +.sp +.in -5 +Delete all used loop devices: +.sp +.in +5 +.B "losetup \-D" +.sp +.in -5 +Print name of first unused loop device: +.sp +.in +5 +.B "losetup \-f" +.sp +.in -5 +Setup loop device: +.sp +.in +5 +.B losetup +.RB [{ \-e | \-E } +.IR encryption ] +.RB [ \-o +.IR offset ] +.RB [ \-\-sizelimit +.IR size ] +.in +8 +.RB [ \-p +.IR pfd ] +.RB [ \-r ] +.RB { \-f [ \-\-show ]| \fIloopdev\fP } +.I file +.sp +.in -13 +Resize loop device: +.sp +.in +5 +.B "losetup \-c" +.I loopdev +.in -5 +.ad b +.SH DESCRIPTION +.B losetup +is used to associate loop devices with regular files or block devices, +to detach loop devices and to query the status of a loop device. If only the +\fIloopdev\fP argument is given, the status of the corresponding loop +device is shown. + +.SS "Encryption" +It is possible to specify transfer functions (for encryption/decryption +or other purposes) using one of the +.B \-E +and +.B \-e +options. +There are two mechanisms to specify the desired encryption: by number +and by name. If an encryption is specified by number then one +has to make sure that the Linux kernel knows about the encryption with that +number, probably by patching the kernel. Standard numbers that are +always present are 0 (no encryption) and 1 (XOR encryption). +When the cryptoloop module is loaded (or compiled in), it uses number 18. +This cryptoloop module will take the name of an arbitrary encryption type +and find the module that knows how to perform that encryption. +.SH OPTIONS +The \fIsize\fR and \fIoffset\fR arguments may be followed by binary (2^N) +suffixes KiB, MiB, GiB, TiB, PiB and EiB (the "iB" is optional, e.g. "K" has the +same meaning as "KiB") or decimal (10^N) suffixes KB, MB, GB, PB and EB. + +.IP "\fB\-a, \-\-all\fP" +show status of all loop devices +.IP "\fB\-c, \-\-set-capacity\fP \fIloopdev\fP +force loop driver to reread size of the file associated with the specified loop device +.IP "\fB\-d, \-\-detach\fP \fIloopdev\fP..." +detach the file or device associated with the specified loop device(s) +.IP "\fB\-e, \-E, \-\-encryption \fIencryption_type\fP" +enable data encryption with specified name or number +.IP "\fB\-f, \-\-find\fP" +find the first unused loop device. If a +.I file +argument is present, use this device. Otherwise, print its name +.IP "\fB\-h, \-\-help\fP" +print help +.IP "\fB\-j, \-\-associated \fIfile\fP" +show status of all loop devices associated with given +.I file +.IP "\fB\-o, \-\-offset \fIoffset\fP" +the data start is moved \fIoffset\fP bytes into the specified file or +device +.IP "\fB\-\-sizelimit \fIsize\fP" +the data end is set to no more than \fIsize\fP bytes after the data start +.IP "\fB\-p, \-\-pass-fd \fInum\fP" +read the passphrase from file descriptor with number +.I num +instead of from the terminal +.IP "\fB\-r, \-\-read-only\fP" +setup read-only loop device +.IP "\fB\-\-show\fP" +print device name if the +.I -f +option and a +.I file +argument are present. + +The short form of this option (\fB\-s\fP) is deprecated. This short form could +be in collision with Loop-AES implementation where the same option is used for +\fB\-\-sizelimit\fP. +.IP "\fB\-v, \-\-verbose\fP" +verbose mode + +.SH RETURN VALUE +.B losetup +returns 0 on success, nonzero on failure. When +.B losetup +displays the status of a loop device, it returns 1 if the device +is not configured and 2 if an error occurred which prevented +.B losetup +from determining the status of the device. + +.SH FILES +.nf +/dev/loop0, /dev/loop1, ... loop devices (major=7) +.fi +.SH EXAMPLE +If you are using the loadable module you must have the module loaded +first with the command +.IP +# modprobe loop +.LP +Maybe also encryption modules are needed. +.IP +# modprobe des +# modprobe cryptoloop +.LP +The following commands can be used as an example of using the loop device. +.nf +.IP +# dd if=/dev/zero of=/file bs=1k count=100 +# losetup -e des /dev/loop0 /file +Password: +Init (up to 16 hex digits): +# mkfs -t ext2 /dev/loop0 100 +# mount -t ext2 /dev/loop0 /mnt + ... +# umount /dev/loop0 +# losetup -d /dev/loop0 +.fi +.LP +If you are using the loadable module you may remove the module with +the command +.IP +# rmmod loop +.LP +.fi +.SH RESTRICTION +DES encryption is painfully slow. On the other hand, XOR is terribly weak. + +Cryptoloop is deprecated in favor of dm-crypt. For more details see +.BR cryptsetup (8). +.SH AVAILABILITY +The losetup command is part of the util-linux package and is available from +ftp://ftp.kernel.org/pub/linux/utils/util-linux/. +.\" .SH AUTHORS +.\" .nf +.\" Original version: Theodore Ts'o <tytso@athena.mit.edu> +.\" Original DES by: Eric Young <eay@psych.psy.uq.oz.au> +.\" .fi diff --git a/sys-utils/losetup.c b/sys-utils/losetup.c new file mode 100644 index 000000000..3e19fa6a1 --- /dev/null +++ b/sys-utils/losetup.c @@ -0,0 +1,1131 @@ +/* Originally from Ted's losetup.c */ +/* + * losetup.c - setup and control loop devices + */ +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <fcntl.h> +#include <errno.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/mman.h> +#include <sys/sysmacros.h> +#include <inttypes.h> +#include <dirent.h> +#include <getopt.h> +#include <stdarg.h> + +#include "strutils.h" +#include "nls.h" +#include "pathnames.h" +#include "loopdev.h" +#include "xalloc.h" +#include "canonicalize.h" + +static int verbose; + +static int is_associated(int dev, struct stat *file, unsigned long long offset, int isoff); + +#define LOOPMAJOR 7 +#define NLOOPS_DEFAULT 8 /* /dev/loop[0-7] */ + +struct looplist { + int flag; /* scanning options */ + FILE *proc; /* /proc/partitions */ + int ncur; /* current position */ + 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 */ +}; + +#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_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 */ + +#define SETLOOP_RDONLY (1<<0) /* Open loop read-only */ +#define SETLOOP_AUTOCLEAR (1<<1) /* Automatically detach loop on close (2.6.25?) */ + +static int del_loop (const char *device); + +/* TODO: move to lib/sysfs.c */ +static char *loopfile_from_sysfs(const char *device) +{ + FILE *f; + struct stat st; + char buf[PATH_MAX], *res = NULL; + + if (stat(device, &st) || !S_ISBLK(st.st_mode)) + return NULL; + + snprintf(buf, sizeof(buf), _PATH_SYS_DEVBLOCK "/%d:%d/loop/backing_file", + major(st.st_rdev), minor(st.st_rdev)); + + f = fopen(buf, "r"); + if (!f) + return NULL; + + if (fgets(buf, sizeof(buf), f)) { + size_t sz = strlen(buf); + if (sz) { + buf[sz - 1] = '\0'; + res = xstrdup(buf); + } + } + + fclose(f); + return res; +} + +char *loopdev_get_loopfile(const char *device) +{ + char *res = loopfile_from_sysfs(device); + + if (!res) { + struct loop_info64 lo64; + int fd; + + if ((fd = open(device, O_RDONLY)) < 0) + return NULL; + + if (ioctl(fd, LOOP_GET_STATUS64, &lo64) == 0) { + lo64.lo_file_name[LO_NAME_SIZE-2] = '*'; + lo64.lo_file_name[LO_NAME_SIZE-1] = 0; + res = xstrdup((char *) lo64.lo_file_name); + + } + close(fd); + } + return res; +} + +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_info64 li; + + errno = 0; + if (ioctl (fd, LOOP_GET_STATUS64, &li) < 0 && errno == ENXIO) + return 0; + return 1; +} + +static int +is_loopfd_autoclear(int fd) +{ + struct loop_info64 lo64; + + if (ioctl(fd, LOOP_GET_STATUS64, &lo64) == 0) { + if (lo64.lo_flags & LO_FLAGS_AUTOCLEAR) + return 1; + + } + return 0; +} + +int +is_loop_autoclear(const char *device) +{ + int fd, rc; + + if ((fd = open(device, O_RDONLY)) < 0) + return 0; + rc = is_loopfd_autoclear(fd); + + close(fd); + return rc; +} + +static int +looplist_open(struct looplist *ll, int flag) +{ + struct stat st; + + memset(ll, 0, sizeof(*ll)); + ll->flag = flag; + ll->ncur = -1; + + if (stat(_PATH_DEV, &st) == -1 || (!S_ISDIR(st.st_mode))) + return -1; /* /dev doesn't exist */ + + if (stat(_PATH_DEV_LOOP, &st) == 0 && S_ISDIR(st.st_mode)) + ll->flag |= LLFLG_SUBDIR; /* /dev/loop/ exists */ + + if ((ll->flag & LLFLG_USEDONLY) && + stat(_PATH_PROC_PARTITIONS, &st) == 0) + ll->flag |= LLFLG_PROCFS; /* try /proc/partitions */ + + ll->flag |= LLFLG_DFLT; /* required! */ + return 0; +} + +static void +looplist_close(struct looplist *ll) +{ + 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_open_dev(struct looplist *ll, int lnum) +{ + struct stat st; + int used; + int fd; + + /* create a full device path */ + snprintf(ll->name, sizeof(ll->name), + ll->flag & LLFLG_SUBDIR ? + _PATH_DEV_LOOP "/%d" : + _PATH_DEV "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 fd; + + used = is_loop_used(fd); + + if ((ll->flag & LLFLG_USEDONLY) && used) + return fd; + if ((ll->flag & LLFLG_FREEONLY) && !used) + return fd; +error: + close(fd); + return -1; +} + +/* returns <N> from "loop<N>" */ +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) - (*(int *) p1 < *(int *) p2); +} + +/* + * The classic scandir() is more expensive and less portable. + * We needn't full loop device names -- minor numbers (loop<N>) + * 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 && d->d_type != DT_LNK) + continue; + n = name2minor(hasprefix, d->d_name); + if (n == -1 || n < NLOOPS_DEFAULT) + continue; + if (count + 1 > arylen) { + int *tmp; + + arylen += 1; + + tmp = realloc(*ary, arylen * sizeof(int)); + if (!tmp) { + free(*ary); + return -1; + } + *ary = tmp; + } + (*ary)[count++] = n; + } + if (count) + qsort(*ary, count, sizeof(int), cmpnum); + + closedir(dir); + return count; +} + +static int +looplist_next(struct looplist *ll) +{ + int fd, n; + + if (ll->flag & LLFLG_DONE) + return -1; + + /* A) Look for used loop devices in /proc/partitions ("losetup -a" only) + */ + if (ll->flag & LLFLG_PROCFS) { + char buf[BUFSIZ]; + + 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; + if (m != LOOPMAJOR) + continue; + /* unfortunately, real minor numbers needn't to match + * loop<N> device name. We have to follow device name. + */ + n = name2minor(1, name); + fd = looplist_open_dev(ll, n); + if (fd != -1) + return fd; + } + 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++) { + fd = looplist_open_dev(ll, ll->ncur); + if (fd != -1) + return fd; + } + ll->flag &= ~LLFLG_DFLT; + } + + /* C) the worst possibility, scan all /dev or /dev/loop + */ + if (!ll->minors) { + ll->nminors = (ll->flag & LLFLG_SUBDIR) ? + loop_scandir(_PATH_DEV_LOOP, &ll->minors, 0) : + loop_scandir(_PATH_DEV, &ll->minors, 1); + ll->ncur = -1; + } + 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; +} + +/* Find loop device associated with given @filename. Used for unmounting loop + * device specified by associated backing file. + * + * returns: 1 no such device/error + * 2 more than one loop device associated with @filename + * 0 exactly one loop device associated with @filename + * (@loopdev points to string containing full device name) + */ +int +find_loopdev_by_backing_file(const char *filename, char **loopdev) +{ + struct looplist ll; + struct stat filestat; + int fd; + int devs_n = 0; /* number of loop devices found */ + char* devname = NULL; + + if (stat(filename, &filestat) == -1) { + perror(filename); + return 1; + } + + if (looplist_open(&ll, LLFLG_USEDONLY) == -1) { + warnx(_("/dev directory does not exist.")); + return 1; + } + + while((devs_n < 2) && (fd = looplist_next(&ll)) != -1) { + if (is_associated(fd, &filestat, 0, 0) == 1) { + if (!devname) + devname = xstrdup(ll.name); + devs_n++; + } + close(fd); + } + looplist_close(&ll); + + if (devs_n == 1) { + *loopdev = devname; + return 0; /* exactly one loopdev */ + } + free(devname); + return devs_n ? 2 : 1; /* more loopdevs or error */ +} + +static int +set_capacity(const char *device) +{ + int errsv; + int fd = open(device, O_RDONLY); + + if (fd == -1) + goto err; + + if (ioctl(fd, LOOP_SET_CAPACITY) != 0) + goto err; + + return 0; +err: + errsv = errno; + fprintf(stderr, _("loop: can't set capacity on device %s: %s\n"), + device, strerror (errsv)); + if (fd != -1) + close(fd); + return 2; +} + +static int +show_loop_fd(int fd, char *device) { + struct loop_info64 loopinfo64; + int errsv; + + if (ioctl(fd, LOOP_GET_STATUS64, &loopinfo64) == 0) { + + char *lofile = NULL; + + loopinfo64.lo_file_name[LO_NAME_SIZE-2] = '*'; + loopinfo64.lo_file_name[LO_NAME_SIZE-1] = 0; + loopinfo64.lo_crypt_name[LO_NAME_SIZE-1] = 0; + + /* ioctl has limited buffer for backing file name, since + * kernel 2.6.37 the filename is available in sysfs too + */ + if (strlen((char *) loopinfo64.lo_file_name) == LO_NAME_SIZE - 1) + lofile = loopfile_from_sysfs(device); + if (!lofile) + lofile = (char *) loopinfo64.lo_file_name; + + printf("%s: [%04" PRIx64 "]:%" PRIu64 " (%s)", + device, loopinfo64.lo_device, loopinfo64.lo_inode, + lofile); + + if (lofile != (char *) loopinfo64.lo_file_name) + free(lofile); + + if (loopinfo64.lo_offset) + printf(_(", offset %" PRIu64 ), loopinfo64.lo_offset); + + if (loopinfo64.lo_sizelimit) + printf(_(", sizelimit %" PRIu64 ), loopinfo64.lo_sizelimit); + + if (loopinfo64.lo_encrypt_type || + loopinfo64.lo_crypt_name[0]) { + char *e = (char *)loopinfo64.lo_crypt_name; + + if (*e == 0 && loopinfo64.lo_encrypt_type == 1) + e = "XOR"; + printf(_(", encryption %s (type %" PRIu32 ")"), + e, loopinfo64.lo_encrypt_type); + } + printf("\n"); + return 0; + } + + errsv = errno; + fprintf(stderr, _("loop: can't get info on device %s: %s\n"), + device, strerror (errsv)); + 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) { + struct looplist ll; + int fd; + + if (looplist_open(&ll, LLFLG_USEDONLY) == -1) { + warnx(_("/dev directory does not exist.")); + return 1; + } + + while((fd = looplist_next(&ll)) != -1) { + show_loop_fd(fd, ll.name); + close(fd); + } + looplist_close(&ll); + + if (!ll.ct_succ && ll.ct_perm) { + warnx(_("no permission to look at /dev/loop%s<N>"), + (ll.flag & LLFLG_SUBDIR) ? "/" : ""); + return 1; + } + return 0; +} + +/* list all associated loop devices */ +static int +show_associated_loop_devices(char *filename, unsigned long long offset, int isoff) +{ + struct looplist ll; + struct stat filestat; + int fd; + + if (stat(filename, &filestat) == -1) { + perror(filename); + return 1; + } + + if (looplist_open(&ll, LLFLG_USEDONLY) == -1) { + warnx(_("/dev directory does not exist.")); + return 1; + } + + while((fd = looplist_next(&ll)) != -1) { + if (is_associated(fd, &filestat, offset, isoff) == 1) + show_loop_fd(fd, ll.name); + close(fd); + } + looplist_close(&ll); + + return 0; +} + +/* check if the loopfile is already associated with the same given + * parameters. + * + * returns: 0 unused / error + * 1 loop device already used + */ +static int +is_associated(int dev, struct stat *file, unsigned long long offset, int isoff) +{ + struct loop_info64 linfo64; + int ret = 0; + + if (ioctl(dev, LOOP_GET_STATUS64, &linfo64) == 0) { + if (file->st_dev == linfo64.lo_device && + file->st_ino == linfo64.lo_inode && + (isoff == 0 || offset == linfo64.lo_offset)) + ret = 1; + + } + + return ret; +} + +/* check if the loop file is already used with the same given + * parameters. We check for device no, inode and offset. + * returns: associated devname or NULL + */ +char * +loopfile_used (const char *filename, unsigned long long offset) { + struct looplist ll; + char *devname = NULL; + struct stat filestat; + int fd; + + if (stat(filename, &filestat) == -1) { + perror(filename); + return NULL; + } + + if (looplist_open(&ll, LLFLG_USEDONLY) == -1) { + warnx(_("/dev directory does not exist.")); + return NULL; + } + + while((fd = looplist_next(&ll)) != -1) { + int res = is_associated(fd, &filestat, offset, 1); + close(fd); + if (res == 1) { + devname = xstrdup(ll.name); + break; + } + } + looplist_close(&ll); + + return devname; +} + +int +loopfile_used_with(char *devname, const char *filename, unsigned long long offset) +{ + struct stat statbuf; + int fd, ret; + + if (!is_loop_device(devname)) + return 0; + + if (stat(filename, &statbuf) == -1) + return 0; + + fd = open(devname, O_RDONLY); + if (fd == -1) + return 0; + + ret = is_associated(fd, &statbuf, offset, 1); + close(fd); + return ret; +} + +char * +find_unused_loop_device (void) { + struct looplist ll; + char *devname = NULL; + int fd; + + if (looplist_open(&ll, LLFLG_FREEONLY) == -1) { + warnx(_("/dev directory does not exist.")); + return NULL; + } + + if ((fd = looplist_next(&ll)) != -1) { + close(fd); + devname = xstrdup(ll.name); + } + looplist_close(&ll); + if (devname) + return devname; + + if (!ll.ct_succ && ll.ct_perm) + warnx(_("no permission to look at /dev/loop%s<N>"), + (ll.flag & LLFLG_SUBDIR) ? "/" : ""); + else if (ll.ct_succ) + warnx(_("could not find any free loop device")); + else + warnx(_( + "Could not find any loop device. Maybe this kernel " + "does not know\n" + " about the loop device? (If so, recompile or " + "`modprobe loop'.)")); + return NULL; +} + +/* + * A function to read the passphrase either from the terminal or from + * an open file descriptor. + */ +static char * +xgetpass(int pfd, const char *prompt) { + char *pass; + int buflen, i; + + if (pfd < 0) /* terminal */ + return getpass(prompt); + + pass = NULL; + buflen = 0; + for (i=0; ; i++) { + if (i >= buflen-1) { + /* we're running out of space in the buffer. + * Make it bigger: */ + char *tmppass = pass; + buflen += 128; + pass = realloc(tmppass, buflen); + if (pass == NULL) { + /* realloc failed. Stop reading. */ + warn(_("Out of memory while reading passphrase")); + pass = tmppass; /* the old buffer hasn't changed */ + break; + } + } + if (read(pfd, pass+i, 1) != 1 || + pass[i] == '\n' || pass[i] == 0) + break; + } + + if (pass == NULL) + return ""; + + pass[i] = 0; + return pass; +} + +static int +digits_only(const char *s) { + while (*s) + if (!isdigit(*s++)) + return 0; + return 1; +} + +/* + * return codes: + * 0 - success + * 1 - error + * 2 - error (EBUSY) + */ +int +set_loop(const char *device, const char *file, unsigned long long offset, + unsigned long long sizelimit, const char *encryption, int pfd, int *options) { + struct loop_info64 loopinfo64; + int fd, ffd, mode, i; + char *pass; + char *filename; + + if (verbose) { + char *xdev = loopfile_used(file, offset); + + if (xdev) { + printf(_("warning: %s is already associated with %s\n"), + file, xdev); + free(xdev); + } + } + + mode = (*options & SETLOOP_RDONLY) ? O_RDONLY : O_RDWR; + if ((ffd = open(file, mode)) < 0) { + if (!(*options & SETLOOP_RDONLY) && + (errno == EROFS || errno == EACCES)) + ffd = open(file, mode = O_RDONLY); + if (ffd < 0) { + perror(file); + return 1; + } + if (verbose) + printf(_("warning: %s: is write-protected, using read-only.\n"), + file); + *options |= SETLOOP_RDONLY; + } + if ((fd = open(device, mode)) < 0) { + perror (device); + close(ffd); + return 1; + } + memset(&loopinfo64, 0, sizeof(loopinfo64)); + + if (!(filename = canonicalize_path(file))) + filename = (char *) file; + xstrncpy((char *)loopinfo64.lo_file_name, filename, LO_NAME_SIZE); + + if (encryption && *encryption) { + if (digits_only(encryption)) { + loopinfo64.lo_encrypt_type = atoi(encryption); + } else { + loopinfo64.lo_encrypt_type = LO_CRYPT_CRYPTOAPI; + snprintf((char *)loopinfo64.lo_crypt_name, LO_NAME_SIZE, + "%s", encryption); + } + } + + loopinfo64.lo_offset = offset; + loopinfo64.lo_sizelimit = sizelimit; + +#ifdef MCL_FUTURE + /* + * Oh-oh, sensitive data coming up. Better lock into memory to prevent + * passwd etc being swapped out and left somewhere on disk. + */ + if (loopinfo64.lo_encrypt_type != LO_CRYPT_NONE) { + if(mlockall(MCL_CURRENT | MCL_FUTURE)) { + perror("memlock"); + fprintf(stderr, _("Couldn't lock into memory, exiting.\n")); + exit(1); + } + } +#endif + + switch (loopinfo64.lo_encrypt_type) { + case LO_CRYPT_NONE: + loopinfo64.lo_encrypt_key_size = 0; + break; + case LO_CRYPT_XOR: + pass = getpass(_("Password: ")); + goto gotpass; + default: + pass = xgetpass(pfd, _("Password: ")); + gotpass: + memset(loopinfo64.lo_encrypt_key, 0, LO_KEY_SIZE); + xstrncpy((char *)loopinfo64.lo_encrypt_key, pass, LO_KEY_SIZE); + memset(pass, 0, strlen(pass)); + loopinfo64.lo_encrypt_key_size = LO_KEY_SIZE; + } + + if (ioctl(fd, LOOP_SET_FD, ffd) < 0) { + int rc = 1; + + if (errno == EBUSY) { + if (verbose) + printf(_("ioctl LOOP_SET_FD failed: %m\n")); + rc = 2; + } else + perror("ioctl: LOOP_SET_FD"); + + close(fd); + close(ffd); + if (file != filename) + free(filename); + return rc; + } + close (ffd); + + if (*options & SETLOOP_AUTOCLEAR) + loopinfo64.lo_flags = LO_FLAGS_AUTOCLEAR; + + i = ioctl(fd, LOOP_SET_STATUS64, &loopinfo64); + if (i) + perror("ioctl: LOOP_SET_STATUS64"); + + if ((*options & SETLOOP_AUTOCLEAR) && !is_loopfd_autoclear(fd)) + /* kernel doesn't support loop auto-destruction */ + *options &= ~SETLOOP_AUTOCLEAR; + + memset(&loopinfo64, 0, sizeof(loopinfo64)); + + if (i) { + ioctl (fd, LOOP_CLR_FD, 0); + close (fd); + if (file != filename) + free(filename); + return 1; + } + + /* + * HACK: here we're leaking a file descriptor, + * but mount is a short-lived process anyway. + */ + if (!(*options & SETLOOP_AUTOCLEAR)) + close (fd); + + if (verbose) + printf(_("set_loop(%s,%s,%llu,%llu): success\n"), + device, filename, offset, sizelimit); + if (file != filename) + free(filename); + return 0; +} + +static int +delete_all_devices (void) +{ + struct looplist ll; + int fd; + int ok = 0; + + if (looplist_open(&ll, LLFLG_USEDONLY) == -1) { + warnx(_("/dev directory does not exist.")); + return 1; + } + + while((fd = looplist_next(&ll)) != -1) { + close(fd); + ok |= del_loop(ll.name); + } + looplist_close(&ll); + + if (!ll.ct_succ && ll.ct_perm) { + warnx(_("no permission to look at /dev/loop%s<N>"), + (ll.flag & LLFLG_SUBDIR) ? "/" : ""); + return 1; + } + return ok; +} + +static int +del_loop (const char *device) { + int fd, errsv; + + if ((fd = open (device, O_RDONLY)) < 0) { + errsv = errno; + goto error; + } + if (ioctl (fd, LOOP_CLR_FD, 0) < 0) { + errsv = errno; + goto error; + } + close (fd); + if (verbose) + printf(_("del_loop(%s): success\n"), device); + return 0; + +error: + fprintf(stderr, _("loop: can't delete device %s: %s\n"), + device, strerror(errsv)); + if (fd >= 0) + close(fd); + return 1; +} + +static void +usage(FILE *out) { + + fputs(_("\nUsage:\n"), out); + fprintf(out, + _(" %1$s loop_device give info\n" + " %1$s -a | --all list all used\n" + " %1$s -d | --detach <loopdev> [<loopdev> ...] delete\n" + " %1$s -D | --detach-all delete all used\n" + " %1$s -f | --find find unused\n" + " %1$s -c | --set-capacity <loopdev> resize\n" + " %1$s -j | --associated <file> [-o <num>] list all associated with <file>\n" + " %1$s [options] {-f|--find|loopdev} <file> setup\n"), + program_invocation_short_name); + + fputs(_("\nOptions:\n"), out); + fputs(_(" -e, --encryption <type> enable data encryption with specified <name/num>\n" + " -h, --help this help\n" + " -o, --offset <num> start at offset <num> into file\n" + " --sizelimit <num> loop limited to only <num> bytes of the file\n" + " -p, --pass-fd <num> read passphrase from file descriptor <num>\n" + " -r, --read-only setup read-only loop device\n" + " --show print device name (with -f <file>)\n" + " -v, --verbose verbose mode\n\n"), out); + + exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS); + } + +int +main(int argc, char **argv) { + char *p, *offset, *sizelimit, *encryption, *passfd, *device, *file, *assoc; + int delete, delete_all, find, c, all, capacity; + int res = 0; + int showdev = 0; + int ro = 0; + int pfd = -1; + uintmax_t off = 0, slimit = 0; + + static const struct option longopts[] = { + { "all", 0, 0, 'a' }, + { "set-capacity", 0, 0, 'c' }, + { "detach", 0, 0, 'd' }, + { "detach-all", 0, 0, 'D' }, + { "encryption", 1, 0, 'e' }, + { "find", 0, 0, 'f' }, + { "help", 0, 0, 'h' }, + { "associated", 1, 0, 'j' }, + { "offset", 1, 0, 'o' }, + { "sizelimit", 1, 0, 128 }, + { "pass-fd", 1, 0, 'p' }, + { "read-only", 0, 0, 'r' }, + { "show", 0, 0, 's' }, + { "verbose", 0, 0, 'v' }, + { NULL, 0, 0, 0 } + }; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + + capacity = delete = delete_all = find = all = 0; + assoc = offset = sizelimit = encryption = passfd = NULL; + + while ((c = getopt_long(argc, argv, "acdDe:E:fhj:o:p:rsv", + longopts, NULL)) != -1) { + switch (c) { + case 'a': + all = 1; + break; + case 'c': + capacity = 1; + break; + case 'r': + ro = 1; + break; + case 'd': + delete = 1; + break; + case 'D': + delete_all = 1; + break; + case 'E': + case 'e': + encryption = optarg; + break; + case 'f': + find = 1; + break; + case 'h': + usage(stdout); + break; + case 'j': + assoc = optarg; + break; + case 'o': + offset = optarg; + break; + case 'p': + passfd = optarg; + break; + case 's': + showdev = 1; + break; + case 'v': + verbose = 1; + break; + + case 128: /* --sizelimit */ + sizelimit = optarg; + break; + + default: + usage(stderr); + } + } + + if (argc == 1) { + usage(stderr); + } else if (delete) { + if (argc < optind+1 || encryption || offset || sizelimit || + capacity || find || all || showdev || assoc || ro) + usage(stderr); + } else if (delete_all) { + if (argc > optind || encryption || offset || sizelimit || + capacity || find || all || showdev || assoc || ro) + usage(stderr); + } else if (find) { + if (capacity || all || assoc || argc < optind || argc > optind+1) + usage(stderr); + } else if (all || delete_all) { + /* only -v is allowed */ + if ((argc == 3 && verbose == 0) || argc > 3) + usage(stderr); + } else if (assoc) { + if (capacity || encryption || showdev || passfd || ro) + usage(stderr); + } else if (capacity) { + if (argc != optind + 1 || encryption || offset || sizelimit || + showdev || ro) + usage(stderr); + } else { + if (argc < optind+1 || argc > optind+2) + usage(stderr); + } + + if (offset && strtosize(offset, &off)) { + warnx(_("invalid offset '%s' specified"), offset); + usage(stderr); + } + if (sizelimit && strtosize(sizelimit, &slimit)) { + warnx(_("invalid sizelimit '%s' specified"), sizelimit); + usage(stderr); + } + + if (all) + return show_used_loop_devices(); + else if (delete_all) + return delete_all_devices(); + else if (assoc) + return show_associated_loop_devices(assoc, off, offset ? 1 : 0); + else if (find) { + device = find_unused_loop_device(); + if (device == NULL) + return -1; + if (argc == optind) { + if (verbose) + printf(_("Loop device is %s\n"), device); + printf("%s\n", device); + return 0; + } + file = argv[optind]; + } else if (!delete) { + device = argv[optind]; + if (argc == optind+1) + file = NULL; + else + file = argv[optind+1]; + } + + if (delete) { + while (optind < argc) + res += del_loop(argv[optind++]); + } else if (capacity) { + res = set_capacity(device); + } else if (file == NULL) + res = show_loop(device); + else { + if (passfd && sscanf(passfd, "%d", &pfd) != 1) + usage(stderr); + do { + res = set_loop(device, file, off, slimit, encryption, pfd, &ro); + if (res == 2 && find) { + if (verbose) + printf(_("stolen loop=%s...trying again\n"), + device); + free(device); + if (!(device = find_unused_loop_device())) + return -1; + } + } while (find && res == 2); + + if (device) { + if (res == 2) + warnx(_("%s: device is busy"), device); + else if (res == 0) { + if (verbose) + printf(_("Loop device is %s\n"), device); + if (showdev && find) + printf("%s\n", device); + } + } + } + return res; +} + |