losetup: avoid infinite busy loop

issue report:
 if i run the heavy duty test from #16859 a couple of times I can get
 the loopback layer in the kernel into a state where there's a loopback
 block device allocated, that you can open, but where both LOOP_CLR_FD
 and _SET_FD fail with EBUSY. and /dev/loop-control still returns it as
 the next free one...  weird state util-linux losetup when called to
 allocate a new device then freezes

This commit:
* restrict number of attempts to 16
* use 200000ms sleep between attempts
* add note about non-atomic loop device setup to the man page

Reported-by: Lennart Poettering <lennart@poettering.net>
Signed-off-by: Karel Zak <kzak@redhat.com>
This commit is contained in:
Karel Zak 2020-10-13 10:31:42 +02:00
parent a83bea2d3d
commit 3ff6fb802d
2 changed files with 18 additions and 2 deletions

View File

@ -68,6 +68,13 @@ It's possible to create more independent loop 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 loop device setup is not an atomic operation when used with \fB\-\-find\fP, and
.B losetup
does not protect this operation by any lock. The number of attempts is
internally restricted to a maximum of 16. 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
@ -177,6 +184,11 @@ returns 0 on success, nonzero on failure. When
displays the status of a loop device, it returns 1 if the device
is not configured and 2 if an error occurred which prevented
determining the status of the device.
.SH NOTES
Since version 2.37
.B losetup
uses LOOP_CONFIGURE ioctl to setup a new loop device by one ioctl call. The
old versions use LOOP_SET_FD and LOOP_SET_STATUS64 ioctls to do the same.
.SH ENVIRONMENT
.IP LOOPDEV_DEBUG=all

View File

@ -476,7 +476,7 @@ static int create_loop(struct loopdev_cxt *lc,
uint64_t blocksize)
{
int hasdev = loopcxt_has_device(lc);
int rc = 0;
int rc = 0, ntries = 0;
/* losetup --find --noverlap file.img */
if (!hasdev && nooverlap) {
@ -572,8 +572,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 < 16) {
xusleep(200000);
ntries++;
continue;
}
/* errors */
errpre = hasdev && loopcxt_get_fd(lc) < 0 ?