From cbdb5025a3127d20a1e18a14bbcb0cc17be84710 Mon Sep 17 00:00:00 2001 From: Manuel Bentele Date: Wed, 2 Dec 2020 07:07:44 +0100 Subject: Setup xloop device with XLOOP_CONFIGURE ioctl call This feature allows the userspace tool xlosetup to completely setup a loop device with a single ioctl call, removing the in-between state where the device can be partially configured, eg. the loop device has a backing file associated with it, but is reading from the wrong offset. Besides removing the intermediate state, another big benefit of this ioctl is that XLOOP_SET_STATUS can be slow. The main reason for this slowness is that XLOOP_SET_STATUS(64) calls blk_mq_freeze_queue() to freeze the associated queue. This requires waiting for RCU. --- src/kernel/xloop_main.c | 10 ++-- src/utils/include/loopdev.h | 15 ++++- src/utils/lib/loopdev.c | 133 ++++++++++++++++++++++++++--------------- src/utils/sys-utils/xlosetup.8 | 7 +++ src/utils/sys-utils/xlosetup.c | 10 +++- 5 files changed, 118 insertions(+), 57 deletions(-) (limited to 'src') diff --git a/src/kernel/xloop_main.c b/src/kernel/xloop_main.c index de1fc3d..441282a 100644 --- a/src/kernel/xloop_main.c +++ b/src/kernel/xloop_main.c @@ -877,11 +877,6 @@ static int xloop_configure(struct xloop_device *xlo, fmode_t mode, if (error) goto out_unlock; - error = xloop_file_fmt_init(xlo->xlo_fmt, - config->info.xlo_file_fmt_type); - if (error) - goto out_unlock; - set_device_ro(bdev, (xlo->xlo_flags & XLO_FLAGS_READ_ONLY) != 0); xlo->use_dio = xlo->xlo_flags & XLO_FLAGS_DIRECT_IO; @@ -890,6 +885,11 @@ static int xloop_configure(struct xloop_device *xlo, fmode_t mode, xlo->old_gfp_mask = mapping_gfp_mask(mapping); mapping_set_gfp_mask(mapping, xlo->old_gfp_mask & ~(__GFP_IO|__GFP_FS)); + error = xloop_file_fmt_init(xlo->xlo_fmt, + config->info.xlo_file_fmt_type); + if (error) + goto out_unlock; + if (!(xlo->xlo_flags & XLO_FLAGS_READ_ONLY) && file->f_op->fsync) blk_queue_write_cache(xlo->xlo_queue, true, false); diff --git a/src/utils/include/loopdev.h b/src/utils/include/loopdev.h index 0221e6b..9fa0688 100644 --- a/src/utils/include/loopdev.h +++ b/src/utils/include/loopdev.h @@ -75,6 +75,19 @@ struct loop_info64 { uint32_t lo_file_fmt_type; }; +#ifndef LOOP_CONFIGURE +/* + * Since Linux v5.8-rc1 (commit 3448914e8cc550ba792d4ccc74471d1ca4293aae) + */ +# define LOOP_CONFIGURE 0x4C0A +struct loop_config { + uint32_t fd; + uint32_t block_size; + struct loop_info64 info; + uint64_t __reserved[8]; +}; +#endif + #define LOOPDEV_MAJOR XLOOP_MAJOR /* loop major number */ #define LOOPDEV_DEFAULT_NNODES CONFIG_BLK_DEV_XLOOP_MIN_COUNT /* default number of loop devices */ @@ -114,7 +127,7 @@ struct loopdev_cxt { unsigned int control_ok:1; /* /dev/loop-control success */ struct path_cxt *sysfs; /* pointer to /sys/dev/block// */ - struct loop_info64 info; /* for GET/SET ioctl */ + struct loop_config config; /* for GET/SET ioctl */ struct loopdev_iter iter; /* scans /sys or /dev for used/free devices */ }; diff --git a/src/utils/lib/loopdev.c b/src/utils/lib/loopdev.c index be4e486..194daf9 100644 --- a/src/utils/lib/loopdev.c +++ b/src/utils/lib/loopdev.c @@ -100,7 +100,7 @@ int loopcxt_set_device(struct loopdev_cxt *lc, const char *device) lc->has_info = 0; lc->info_failed = 0; *lc->device = '\0'; - memset(&lc->info, 0, sizeof(lc->info)); + memset(&lc->config, 0, sizeof(lc->config)); /* set new */ if (device) { @@ -659,17 +659,17 @@ struct loop_info64 *loopcxt_get_info(struct loopdev_cxt *lc) } errno = 0; if (lc->has_info) - return &lc->info; + return &lc->config.info; fd = loopcxt_get_fd(lc); if (fd < 0) return NULL; - if (ioctl(fd, LOOP_GET_STATUS64, &lc->info) == 0) { + if (ioctl(fd, LOOP_GET_STATUS64, &lc->config.info) == 0) { lc->has_info = 1; lc->info_failed = 0; DBG(CXT, ul_debugobj(lc, "reading loop_info64 OK")); - return &lc->info; + return &lc->config.info; } lc->info_failed = 1; @@ -1156,7 +1156,7 @@ int loopcxt_set_offset(struct loopdev_cxt *lc, uint64_t offset) { if (!lc) return -EINVAL; - lc->info.lo_offset = offset; + lc->config.info.lo_offset = offset; DBG(CXT, ul_debugobj(lc, "set offset=%jd", offset)); return 0; @@ -1169,7 +1169,7 @@ int loopcxt_set_sizelimit(struct loopdev_cxt *lc, uint64_t sizelimit) { if (!lc) return -EINVAL; - lc->info.lo_sizelimit = sizelimit; + lc->config.info.lo_sizelimit = sizelimit; DBG(CXT, ul_debugobj(lc, "set sizelimit=%jd", sizelimit)); return 0; @@ -1202,7 +1202,7 @@ int loopcxt_set_blocksize(struct loopdev_cxt *lc, uint64_t blocksize) int loopcxt_set_file_fmt_type(struct loopdev_cxt *lc, uint32_t file_fmt_type) { if (!lc) return -EINVAL; - lc->info.lo_file_fmt_type = file_fmt_type; + lc->config.info.lo_file_fmt_type = file_fmt_type; DBG(CXT, ul_debugobj(lc, "set file_fmt_type=%u", (unsigned) file_fmt_type)); return 0; @@ -1220,7 +1220,7 @@ int loopcxt_set_flags(struct loopdev_cxt *lc, uint32_t flags) { if (!lc) return -EINVAL; - lc->info.lo_flags = flags; + lc->config.info.lo_flags = flags; DBG(CXT, ul_debugobj(lc, "set flags=%u", (unsigned) flags)); return 0; @@ -1243,9 +1243,9 @@ int loopcxt_set_backing_file(struct loopdev_cxt *lc, const char *filename) if (!lc->filename) return -errno; - xstrncpy((char *)lc->info.lo_file_name, lc->filename, LO_NAME_SIZE); + xstrncpy((char *)lc->config.info.lo_file_name, lc->filename, LO_NAME_SIZE); - DBG(CXT, ul_debugobj(lc, "set backing file=%s", lc->info.lo_file_name)); + DBG(CXT, ul_debugobj(lc, "set backing file=%s", lc->config.info.lo_file_name)); return 0; } @@ -1268,7 +1268,7 @@ static int loopcxt_check_size(struct loopdev_cxt *lc, int file_fd) int dev_fd; struct stat st; - if (!lc->info.lo_offset && !lc->info.lo_sizelimit) + if (!lc->config.info.lo_offset && !lc->config.info.lo_sizelimit) return 0; if (fstat(file_fd, &st)) { @@ -1284,16 +1284,16 @@ static int loopcxt_check_size(struct loopdev_cxt *lc, int file_fd) } else expected_size = st.st_size; - if (expected_size == 0 || expected_size <= lc->info.lo_offset) { + if (expected_size == 0 || expected_size <= lc->config.info.lo_offset) { DBG(CXT, ul_debugobj(lc, "failed to determine expected size")); return 0; /* ignore this error */ } - if (lc->info.lo_offset > 0) - expected_size -= lc->info.lo_offset; + if (lc->config.info.lo_offset > 0) + expected_size -= lc->config.info.lo_offset; - if (lc->info.lo_sizelimit > 0 && lc->info.lo_sizelimit < expected_size) - expected_size = lc->info.lo_sizelimit; + if (lc->config.info.lo_sizelimit > 0 && lc->config.info.lo_sizelimit < expected_size) + expected_size = lc->config.info.lo_sizelimit; dev_fd = loopcxt_get_fd(lc); if (dev_fd < 0) { @@ -1361,6 +1361,7 @@ int loopcxt_setup_device(struct loopdev_cxt *lc) { int file_fd, dev_fd, mode = O_RDWR, rc = -1, cnt = 0, err, again; int errsv = 0; + int fallback = 0; if (!lc || !*lc->device || !lc->filename) return -EINVAL; @@ -1370,7 +1371,7 @@ int loopcxt_setup_device(struct loopdev_cxt *lc) /* * Open backing file and device */ - if (lc->info.lo_flags & LO_FLAGS_READ_ONLY) + if (lc->config.info.lo_flags & LO_FLAGS_READ_ONLY) mode = O_RDONLY; if ((file_fd = open(lc->filename, mode | O_CLOEXEC)) < 0) { @@ -1393,10 +1394,10 @@ int loopcxt_setup_device(struct loopdev_cxt *lc) if (mode == O_RDONLY) { lc->flags |= LOOPDEV_FL_RDONLY; /* open() mode */ - lc->info.lo_flags |= LO_FLAGS_READ_ONLY; /* kernel loopdev mode */ + lc->config.info.lo_flags |= LO_FLAGS_READ_ONLY; /* kernel loopdev mode */ } else { lc->flags |= LOOPDEV_FL_RDWR; /* open() mode */ - lc->info.lo_flags &= ~LO_FLAGS_READ_ONLY; + lc->config.info.lo_flags &= ~LO_FLAGS_READ_ONLY; lc->flags &= ~LOOPDEV_FL_RDONLY; } @@ -1407,8 +1408,8 @@ int loopcxt_setup_device(struct loopdev_cxt *lc) break; if (errno != EACCES && errno != ENOENT) break; - /* We have permissions to open /dev/xloop-control, but open - * /dev/xloopN failed with EACCES, it's probably because udevd + /* We have permissions to open /dev/loop-control, but open + * /dev/loopN failed with EACCES, it's probably because udevd * does not applied chown yet. Let's wait a moment. */ xusleep(25000); } while (cnt++ < 16); @@ -1421,44 +1422,70 @@ int loopcxt_setup_device(struct loopdev_cxt *lc) DBG(SETUP, ul_debugobj(lc, "device open: OK")); /* - * Set FD + * Atomic way to configure all by one ioctl call + * -- since Linux v5.8-rc1, commit 3448914e8cc550ba792d4ccc74471d1ca4293aae */ - if (ioctl(dev_fd, LOOP_SET_FD, file_fd) < 0) { + lc->config.fd = file_fd; + if (ioctl(dev_fd, LOOP_CONFIGURE, &lc->config) < 0) { rc = -errno; errsv = errno; - DBG(SETUP, ul_debugobj(lc, "LOOP_SET_FD failed: %m")); - goto err; + if (errno != EINVAL) { + DBG(SETUP, ul_debugobj(lc, "LOOP_CONFIGURE failed: %m")); + goto err; + } + fallback = 1; + } else { + if (lc->blocksize > 0 + && (rc = loopcxt_ioctl_blocksize(lc, lc->blocksize)) < 0) { + errsv = -rc; + goto err; + } + DBG(SETUP, ul_debugobj(lc, "LOOP_CONFIGURE: OK")); } - DBG(SETUP, ul_debugobj(lc, "LOOP_SET_FD: OK")); + /* + * Old deprecated way; first assign backing file FD and then in the + * second step set loop device properties. + */ + if (fallback) { + if (ioctl(dev_fd, LOOP_SET_FD, file_fd) < 0) { + rc = -errno; + errsv = errno; + DBG(SETUP, ul_debugobj(lc, "LOOP_SET_FD failed: %m")); + goto err; + } - if (lc->blocksize > 0 - && (rc = loopcxt_ioctl_blocksize(lc, lc->blocksize)) < 0) { - errsv = -rc; - goto err; - } + DBG(SETUP, ul_debugobj(lc, "LOOP_SET_FD: OK")); - do { - err = ioctl(dev_fd, LOOP_SET_STATUS64, &lc->info); - again = err && errno == EAGAIN; - if (again) - xusleep(250000); - } while (again); - if (err) { - rc = -errno; - errsv = errno; - DBG(SETUP, ul_debugobj(lc, "LOOP_SET_STATUS64 failed: %m")); - goto err; - } + if (lc->blocksize > 0 + && (rc = loopcxt_ioctl_blocksize(lc, lc->blocksize)) < 0) { + errsv = -rc; + goto err; + } - DBG(SETUP, ul_debugobj(lc, "LOOP_SET_STATUS64: OK")); + do { + err = ioctl(dev_fd, LOOP_SET_STATUS64, &lc->config.info); + again = err && errno == EAGAIN; + if (again) + xusleep(250000); + } while (again); + + if (err) { + rc = -errno; + errsv = errno; + DBG(SETUP, ul_debugobj(lc, "LOOP_SET_STATUS64 failed: %m")); + goto err; + } + + DBG(SETUP, ul_debugobj(lc, "LOOP_SET_STATUS64: OK")); + } if ((rc = loopcxt_check_size(lc, file_fd))) goto err; close(file_fd); - memset(&lc->info, 0, sizeof(lc->info)); + memset(&lc->config, 0, sizeof(lc->config)); lc->has_info = 0; lc->info_failed = 0; @@ -1501,7 +1528,7 @@ int loopcxt_ioctl_status(struct loopdev_cxt *lc) DBG(SETUP, ul_debugobj(lc, "device open: OK")); do { - err = ioctl(dev_fd, LOOP_SET_STATUS64, &lc->info); + err = ioctl(dev_fd, LOOP_SET_STATUS64, &lc->config.info); again = err && errno == EAGAIN; if (again) xusleep(250000); @@ -1654,7 +1681,7 @@ int loopcxt_find_unused(struct loopdev_cxt *lc) } if (rc < 0) { - DBG(CXT, ul_debugobj(lc, "using loop scan")); + DBG(CXT, ul_debugobj(lc, "using xloop scan")); rc = loopcxt_init_iterator(lc, LOOPITER_FL_FREE); if (rc) return rc; @@ -1705,6 +1732,17 @@ char *loopdev_get_backing_file(const char *device) return res; } +int loopdev_has_backing_file(const char *device) +{ + char *tmp = loopdev_get_backing_file(device); + + if (tmp) { + free(tmp); + return 1; + } + return 0; +} + /* * Returns: TRUE/FALSE */ @@ -1911,4 +1949,3 @@ int loopdev_count_by_backing_file(const char *filename, char **loopdev) } return count; } - diff --git a/src/utils/sys-utils/xlosetup.8 b/src/utils/sys-utils/xlosetup.8 index 4e063e6..c948899 100644 --- a/src/utils/sys-utils/xlosetup.8 +++ b/src/utils/sys-utils/xlosetup.8 @@ -68,6 +68,13 @@ It's possible to create more independent xloop devices for the same backing file. .B This setup may be dangerous, can cause data loss, corruption and overwrites. Use \fB\-\-nooverlap\fR with \fB\-\-find\fR during setup to avoid this problem. +.sp +The xloop device setup is not an atomic operation when used with \fB\-\-find\fP, and +.B xlosetup +does not protect this operation by any lock. The number of attempts is +internally restricted to a maximum of 64. It is recommended to use for example +.BR flock (1) +to avoid a collision in heavily parallel use cases. .SH OPTIONS The \fIsize\fR and \fIoffset\fR diff --git a/src/utils/sys-utils/xlosetup.c b/src/utils/sys-utils/xlosetup.c index 60fa0ba..cbc2e69 100644 --- a/src/utils/sys-utils/xlosetup.c +++ b/src/utils/sys-utils/xlosetup.c @@ -469,7 +469,7 @@ static int create_loop(struct loopdev_cxt *lc, uint64_t blocksize, uint32_t file_fmt_type) { int hasdev = loopcxt_has_device(lc); - int rc = 0; + int rc = 0, ntries = 0; /* xlosetup --find --noverlap file.img */ if (!hasdev && nooverlap) { @@ -501,7 +501,7 @@ static int create_loop(struct loopdev_cxt *lc, errx(EXIT_FAILURE, _("%s: overlapping encrypted xloop device exists"), file); } - lc->info.lo_flags &= ~LO_FLAGS_AUTOCLEAR; + lc->config.info.lo_flags &= ~LO_FLAGS_AUTOCLEAR; if (loopcxt_ioctl_status(lc)) { loopcxt_deinit(lc); errx(EXIT_FAILURE, _("%s: failed to re-use xloop device"), file); @@ -571,8 +571,12 @@ static int create_loop(struct loopdev_cxt *lc, rc = loopcxt_setup_device(lc); if (rc == 0) break; /* success */ - if (errno == EBUSY && !hasdev) + + if (errno == EBUSY && !hasdev && ntries < 64) { + xusleep(200000); + ntries++; continue; + } /* errors */ errpre = hasdev && loopcxt_get_fd(lc) < 0 ? -- cgit v1.2.3-55-g7522