def test_loopback_basic(tmpdir): index = meta.Index(os.curdir) info = index.get_module_info("Device", "org.osbuild.loopback") tree = os.path.join(tmpdir, "tree") os.makedirs(tree) devpath = os.path.join(tmpdir, "dev") os.makedirs(devpath) size = 1024 * 1024 filename = os.path.join(tree, "image.img") with open(filename, "wb") as f: f.truncate(size) sb = os.fstat(f.fileno()) testfile = os.path.join(tmpdir, "test.img") options = { "filename": "image.img", "start": 0, "size": size // 512 # size is in sectors / blocks } dev = devices.Device("loop", info, None, options) with host.ServiceManager() as mgr: devmgr = devices.DeviceManager(mgr, devpath, tree) reply = devmgr.open(dev) assert reply assert reply["path"] node = reply["path"] assert os.path.exists(os.path.join(devpath, node)) minor = reply["node"]["minor"] lo = loop.Loop(minor) li = lo.get_status() assert li.lo_offset == 0 assert li.lo_sizelimit == size assert li.lo_inode == sb.st_ino with pytest.raises(OSError): with open(testfile, "wb") as f: f.truncate(1) lo.set_fd(f.fileno()) lo.close() uid = f"device/{dev.name}" client = mgr.services[uid] client.call("close", None) lo = loop.Loop(minor) with open(filename, "r") as f: assert not lo.is_bound_to(f.fileno())
def _create_device(self, fd, dir_fd, offset=None, sizelimit=None): while True: # Getting an unbound loopback device and attaching a backing # file descriptor to it is racy, so we must use a retry loop lo = loop.Loop(self.ctl.get_unbound()) try: lo.set_fd(fd) except OSError as e: if e.errno == errno.EBUSY: continue raise e break lo.set_status(offset=offset, sizelimit=sizelimit, autoclear=True) lo.mknod(dir_fd) # Pin the Loop objects so they are only released when the LoopServer # is destroyed. self.devs.append(lo) return lo.devname
def loop_create_device(ctl, fd, offset=None, sizelimit=None): while True: lo = loop.Loop(ctl.get_unbound()) try: lo.set_fd(fd) except OSError as e: lo.close() if e.errno == errno.EBUSY: continue raise e try: lo.set_status(offset=offset, sizelimit=sizelimit, autoclear=True) except BlockingIOError: lo.clear_fd() lo.close() continue break try: yield lo finally: lo.close()
def test_lock(tempdir): path = os.path.join(tempdir, "test.img") ctl = loop.LoopControl() assert ctl lo, lo2, f = None, None, None try: f = open(path, "wb+") f.truncate(1024) f.flush() lo = ctl.loop_for_fd(f.fileno(), autoclear=True, lock=True) assert lo lo2 = loop.Loop(lo.minor) assert lo2 with pytest.raises(BlockingIOError): lo2.flock(fcntl.LOCK_EX | fcntl.LOCK_NB) lo.close() lo = None # after lo is closed, the lock should be release and # we should be able to obtain the lock lo2.flock(fcntl.LOCK_EX | fcntl.LOCK_NB) lo2.clear_fd() finally: if lo2: lo2.close() if lo: lo.close() if f: f.close() ctl.close()
def test_clear_fd_wait(tempdir): path = os.path.join(tempdir, "test.img") ctl = loop.LoopControl() assert ctl delay_time = 0.25 def close_loop(lo, barrier): barrier.wait() time.sleep(delay_time) print("closing loop") lo.close() lo, lo2, f = None, None, None try: f = open(path, "wb+") f.truncate(1024) f.flush() lo = ctl.loop_for_fd(f.fileno(), autoclear=False) assert lo # Increase reference count of the loop to > 1 thus # preventing the kernel from immediately closing the # device. Instead the kernel will set the autoclear # attribute and return lo2 = loop.Loop(lo.minor) assert lo2 # as long as the second loop is alive, the kernel can # not clear the fd and thus we will get a timeout with pytest.raises(TimeoutError): lo.clear_fd_wait(f.fileno(), 0.1, 0.01) # start a thread and sync with a barrier, then close # the loop device in the background thread while the # main thread is waiting in `clear_fd_wait`. We wait # four times the delay time of the thread to ensure # we don't get a timeout. barrier = threading.Barrier(2) thread = threading.Thread(target=close_loop, args=(lo2, barrier)) barrier.reset() thread.start() barrier.wait() lo.clear_fd_wait(f.fileno(), 4 * delay_time, delay_time / 10) # no timeout exception has occurred and thus the device # must not be be bound to the original file anymore assert not lo.is_bound_to(f.fileno()) finally: if lo2: lo2.close() if lo: with contextlib.suppress(OSError): lo.clear_fd() lo.close() if f: f.close() ctl.close()