diff options
Diffstat (limited to 'lib/loopdev.c')
-rw-r--r-- | lib/loopdev.c | 86 |
1 files changed, 83 insertions, 3 deletions
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); |