diff options
author | Jeff Mahoney | 2013-04-09 14:32:50 +0200 |
---|---|---|
committer | Karel Zak | 2013-04-09 14:32:50 +0200 |
commit | 293714c0d157ae04d08bb587e800c70f05cc4a29 (patch) | |
tree | e4f7e30892335c340ea5160843074d9176cca6b0 /lib | |
parent | libblkid: export blkid_init_debug() (diff) | |
download | kernel-qcow2-util-linux-293714c0d157ae04d08bb587e800c70f05cc4a29.tar.gz kernel-qcow2-util-linux-293714c0d157ae04d08bb587e800c70f05cc4a29.tar.xz kernel-qcow2-util-linux-293714c0d157ae04d08bb587e800c70f05cc4a29.zip |
loopdev: sync capacity after setting it
I recently tried to mount an hfsplus file system from an image file with
a partition table by using the loop offset and sizelimit options to specify
the location of the file system.
hfsplus stores some metadata at a set offset from the end of the partition,
so it's sensitive to the device size reported by the kernel.
It worked with this:
But failed with this:
/dev/loop0: [0089]:2 (<imagefile>), offset 32768, sizelimit 102400000
/dev/loop1: [0089]:2 (<imagefile>), offset 32768, sizelimit 102400000
/proc/partitions shows the correct number of blocks to match the sizelimit.
But if I set a breakpoint in mount before the mount syscall, I could see:
102400000
102432768
The kernel loop driver will set the gendisk capacity of the device at
LOOP_SET_STATUS64 but won't sync it to the block device until one of two
conditions are met: All open file descriptors referring to the device are
closed (and it will sync when re-opened) or if the LOOP_SET_CAPACITY ioctl
is called to sync it. Since mount opens the device and passes it directly
to the mount syscall after LOOP_SET_STATUS64 without closing and reopening
it, the sizelimit argument is effectively ignroed. The capacity needs to
be synced immediately for it to work as expected.
This patch adds the LOOP_SET_CAPACITY call to loopctx_setup_device since
the device isn't yet released to the user, so it's safe to sync the capacity
immediately.
[kzak@redhat.com: - port to the current git HEAD,
- use uint64_t]
Signed-off-by: Jeff Mahoney <jeffm@suse.com>
Signed-off-by: Karel Zak <kzak@redhat.com>
Diffstat (limited to 'lib')
-rw-r--r-- | lib/Makemodule.am | 2 | ||||
-rw-r--r-- | lib/blkdev.c | 3 | ||||
-rw-r--r-- | lib/loopdev.c | 86 |
3 files changed, 85 insertions, 6 deletions
diff --git a/lib/Makemodule.am b/lib/Makemodule.am index a118958c5..afc2156c7 100644 --- a/lib/Makemodule.am +++ b/lib/Makemodule.am @@ -70,7 +70,7 @@ test_ttyutils_CFLAGS = -DTEST_PROGRAM test_ttyutils_LDADD = libcommon.la test_blkdev_SOURCES = lib/blkdev.c -test_blkdev_CFLAGS = -DTEST_PROGRAM +test_blkdev_CFLAGS = -DTEST_PROGRAM_BLKDEV test_blkdev_LDADD = libcommon.la test_ismounted_SOURCES = lib/ismounted.c diff --git a/lib/blkdev.c b/lib/blkdev.c index 09c1a2ff3..f8182c0b3 100644 --- a/lib/blkdev.c +++ b/lib/blkdev.c @@ -28,7 +28,6 @@ #include "blkdev.h" #include "c.h" #include "linux_version.h" -#include "xalloc.h" static long blkdev_valid_offset (int fd, off_t offset) { @@ -336,7 +335,7 @@ const char *blkdev_scsi_type_to_name(int type) return NULL; } -#ifdef TEST_PROGRAM +#ifdef TEST_PROGRAM_BLKDEV #include <stdio.h> #include <stdlib.h> #include <fcntl.h> diff --git a/lib/loopdev.c b/lib/loopdev.c index 2a696ab4d..c35e306f9 100644 --- a/lib/loopdev.c +++ b/lib/loopdev.c @@ -41,6 +41,7 @@ #include "loopdev.h" #include "canonicalize.h" #include "at.h" +#include "blkdev.h" #define CONFIG_LOOPDEV_DEBUG @@ -1072,6 +1073,64 @@ int loopcxt_set_backing_file(struct loopdev_cxt *lc, const char *filename) } /* + * In kernels prior to v3.9, if the offset or sizelimit options + * are used, the block device's size won't be synced automatically. + * blockdev --getsize64 and filesystems will use the backing + * file size until the block device has been re-opened or the + * LOOP_SET_CAPACITY ioctl is called to sync the sizes. + * + * Since mount -oloop uses the LO_FLAGS_AUTOCLEAR option and passes + * the open file descriptor to the mount system call, we need to use + * the ioctl. Calling losetup directly doesn't have this problem since + * it closes the device when it exits and whatever consumes the device + * next will re-open it, causing the resync. + */ +static int loopcxt_check_size(struct loopdev_cxt *lc, int file_fd) +{ + uint64_t size, expected_size; + int dev_fd; + struct stat st; + + if (!lc->info.lo_offset && !lc->info.lo_sizelimit) + return 0; + + if (fstat(file_fd, &st)) + return -errno; + + expected_size = st.st_size; + + if (lc->info.lo_offset > 0) + expected_size -= lc->info.lo_offset; + + if (lc->info.lo_sizelimit > 0 && lc->info.lo_sizelimit < expected_size) + expected_size = lc->info.lo_sizelimit; + + dev_fd = loopcxt_get_fd(lc); + if (dev_fd < 0) + return -errno; + + if (blkdev_get_size(dev_fd, (unsigned long long *) &size)) + return -errno; + + if (expected_size != size) { + if (loopcxt_set_capacity(lc)) { + /* ioctl not available */ + if (errno == ENOTTY || errno == EINVAL) + errno = ERANGE; + return -errno; + } + + if (blkdev_get_size(dev_fd, (unsigned long long *) &size)) + return -errno; + + if (expected_size != size) + return -ERANGE; + } + + return 0; +} + +/* * @cl: context * * Associate the current device (see loopcxt_{set,get}_device()) with @@ -1150,9 +1209,6 @@ int loopcxt_setup_device(struct loopdev_cxt *lc) DBG(lc, loopdev_debug("setup: LOOP_SET_FD: OK")); - close(file_fd); - file_fd = -1; - if (ioctl(dev_fd, LOOP_SET_STATUS64, &lc->info)) { DBG(lc, loopdev_debug("LOOP_SET_STATUS64 failed: %m")); goto err; @@ -1160,6 +1216,12 @@ int loopcxt_setup_device(struct loopdev_cxt *lc) DBG(lc, loopdev_debug("setup: LOOP_SET_STATUS64: OK")); + if ((rc = loopcxt_check_size(lc, file_fd))) + goto err; + + close(file_fd); + file_fd = -1; + memset(&lc->info, 0, sizeof(lc->info)); lc->has_info = 0; lc->info_failed = 0; @@ -1176,6 +1238,24 @@ err: return rc; } +int loopcxt_set_capacity(struct loopdev_cxt *lc) +{ + int fd = loopcxt_get_fd(lc); + + if (fd < 0) + return -EINVAL; + + /* Kernels prior to v2.6.30 don't support this ioctl */ + if (ioctl(fd, LOOP_SET_CAPACITY, 0) < 0) { + int rc = -errno; + DBG(lc, loopdev_debug("LOOP_SET_CAPACITY failed: %m")); + return rc; + } + + DBG(lc, loopdev_debug("capacity set")); + return 0; +} + int loopcxt_delete_device(struct loopdev_cxt *lc) { int fd = loopcxt_get_fd(lc); |