loop: add clear_fd_wait method

Add a helper method that clears the fd for a given loop device but
also ensures that the loop device is not bound to the supplied fd
anymore. Check the function documentation for more information.
Add a corresponding test.
This commit is contained in:
Christian Kellner 2021-08-08 13:32:25 +00:00 committed by Tom Gundersen
parent a367a0df1d
commit d8e48c0511
2 changed files with 127 additions and 0 deletions

View file

@ -4,6 +4,7 @@ import errno
import fcntl
import os
import stat
import time
__all__ = [
@ -160,6 +161,61 @@ class Loop:
fcntl.ioctl(self.fd, self.LOOP_CLR_FD)
def clear_fd_wait(self, fd: int, timeout: float, wait: float = 0.1) -> None:
"""Wait until the file descriptor is cleared
When clearing the file descriptor of the loopback device the
kernel will check if the loop device has a reference count
greater then one(!), i.e. if another fd besied the one trying
to clear the loopback device is open. If so it will only set
the `LO_FLAGS_AUTOCLEAR` flag and wait until the the device
is released. This means we cannot be sure the loopback device
is actually cleared.
To alleviated this situation we wait until the the loop is not
bound anymore or not bound to `fd` anymore (in case someone
else bound it between checks).
Raises a `TimeoutError` if the file descriptor when `timeout`
is reached.
Parameters
----------
fd : int
the file descriptor to wait for
timeout : float
the maximum time to wait in seconds
wait : float
the time to wait between each check in seconds
"""
file_info = os.fstat(fd)
endtime = time.monotonic() + timeout
# wait until the loop device is unbound, which means calling
# `get_status` will fail with `ENXIO` or if someone raced us
# and bound the loop device again, it is not backed by "our"
# file descriptor specified via `fd` anymore
while True:
try:
self.clear_fd()
loop_info = self.get_status()
except OSError as err:
# check if the loop is still bound
if err.errno == errno.ENXIO:
return
# check if it is backed by the fd
if not loop_info.is_bound_to(file_info):
return
if time.monotonic() > endtime:
raise TimeoutError("waiting for loop device timed out")
time.sleep(wait)
def change_fd(self, fd):
"""Replace the bound filedescriptor