summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorManuel Bentele2020-12-02 07:07:44 +0100
committerManuel Bentele2020-12-02 07:07:44 +0100
commitcbdb5025a3127d20a1e18a14bbcb0cc17be84710 (patch)
tree6df46d1e538691cee1b34fba55d6466253d198e2 /src
parentAdd Linux kernel module support for 32-bit architectures (eg. ARM) (diff)
downloadxloop-cbdb5025a3127d20a1e18a14bbcb0cc17be84710.tar.gz
xloop-cbdb5025a3127d20a1e18a14bbcb0cc17be84710.tar.xz
xloop-cbdb5025a3127d20a1e18a14bbcb0cc17be84710.zip
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.
Diffstat (limited to 'src')
-rw-r--r--src/kernel/xloop_main.c10
-rw-r--r--src/utils/include/loopdev.h15
-rw-r--r--src/utils/lib/loopdev.c133
-rw-r--r--src/utils/sys-utils/xlosetup.87
-rw-r--r--src/utils/sys-utils/xlosetup.c10
5 files changed, 118 insertions, 57 deletions
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/<maj:min>/ */
- 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 ?