From 3c953ae49d0be936b415e6dfb3d9b18fb225e39c Mon Sep 17 00:00:00 2001 From: Christian Kellner Date: Tue, 3 Aug 2021 12:14:02 +0000 Subject: [PATCH] loop: helper to get a loop and bind it Add a new helper, `loop_for_fd` that will get (or create) an unbounded loop device, bind it to an fd and then set its status. Since this is racy and can fail the method does these steps in a retry-loop. --- osbuild/loop.py | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/osbuild/loop.py b/osbuild/loop.py index d6218839..eb89b7a0 100644 --- a/osbuild/loop.py +++ b/osbuild/loop.py @@ -1,5 +1,6 @@ import contextlib import ctypes +import errno import fcntl import os import stat @@ -383,3 +384,43 @@ class LoopControl: self._check_open() return fcntl.ioctl(self.fd, self.LOOP_CTL_GET_FREE) + + def loop_for_fd(self, fd: int, **kwargs): + """ + Get or create an unbound loopback device and bind it to an fd + + Getting an unbound loopback device, attaching a backing file + descriptor and setting the loop device status is racy so this + method will retry until it succeeds or it fails to get an + unbound loop device. + + All given keyword arguments are forwarded to `Loop.set_status`. + """ + + self._check_open() + + if fd < 0: + raise ValueError(f"Invalid file descriptor '{fd}'") + + while True: + lo = Loop(self.get_unbound()) + + try: + lo.set_fd(fd) + except OSError as e: + lo.close() + if e.errno == errno.EBUSY: + continue + raise e + + # `set_status` returns EBUSY when the pages from the + # previously bound file have not been fully cleared yet. + try: + lo.set_status(**kwargs) + except BlockingIOError: + lo.clear_fd() + lo.close() + continue + break + + return lo