loop-util: handle EAGAIN on LOOP_SET_STATUS64

Since
5db470e229 (i.e. kernel 5.0)
changing the .lo_offset field via LOOP_SET_STATUS64 might result in
EAGAIN. Let's handle that.

Fixes: #16858
This commit is contained in:
Lennart Poettering 2020-08-26 22:42:26 +02:00
parent e1da60e430
commit 738f29cb53
1 changed files with 36 additions and 4 deletions

View File

@ -34,6 +34,7 @@ static void cleanup_clear_loop_close(int *fd) {
}
static int loop_configure(int fd, const struct loop_config *c) {
_cleanup_close_ int lock_fd = -1;
int r;
assert(fd >= 0);
@ -84,15 +85,46 @@ static int loop_configure(int fd, const struct loop_config *c) {
return 0;
/* Otherwise, undo the attachment and use the old APIs */
(void) ioctl(fd, LOOP_CLR_FD);
if (ioctl(fd, LOOP_CLR_FD) < 0)
return -errno;
}
/* Since kernel commit 5db470e229e22b7eda6e23b5566e532c96fb5bc3 (kernel v5.0) the LOOP_SET_STATUS64
* ioctl can return EAGAIN in case we change the lo_offset field, if someone else is accessing the
* block device while we try to reconfigure it. This is a pretty common case, since udev might
* instantly start probing the device as soon as we attach an fd to it. Hence handle it in two ways:
* first, let's take the BSD lock that that ensures that udev will not step in between the point in
* time where we attach the fd and where we reconfigure the device. Secondly, let's wait 50ms on
* EAGAIN and retry. The former should be an efficient mechanism to avoid we have to wait 50ms
* needlessly if we are just racing against udev. The latter is protection against all other cases,
* i.e. peers that do not take the BSD lock.
*
* We take the BSD lock on a second, separately opened fd for the device. udev after all watches for
* close() events (specifically IN_CLOSE_WRITE) on block devices to reprobe them, hence by having a
* separate fd we will later close() we can ensure we trigger udev after everything is done. If we'd
* lock our own fd instead and keep it open for a long time udev would possibly never run on it
* again, even though the fd is unlocked, simply because we never close() it. It also has the nice
* benefit we can use the _cleanup_close_ logic to automatically release the lock, after we are
* done. */
lock_fd = fd_reopen(fd, O_RDWR|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
if (lock_fd < 0)
return lock_fd;
if (flock(lock_fd, LOCK_EX) < 0)
return -errno;
if (ioctl(fd, LOOP_SET_FD, c->fd) < 0)
return -errno;
if (ioctl(fd, LOOP_SET_STATUS64, &c->info) < 0) {
r = -errno;
goto fail;
for (unsigned n_attempts = 0;;) {
if (ioctl(fd, LOOP_SET_STATUS64, &c->info) >= 0)
break;
if (errno != EAGAIN || ++n_attempts >= 64) {
r = log_debug_errno(errno, "Failed to configure loopback device: %m");
goto fail;
}
(void) usleep(50 * USEC_PER_MSEC);
}
return 0;