osbuild: ensure loop.Loop() has the required device node

When loop.Loop() is called and a new loop device must be allocated
there is no gurantee that the correct device node is available on
the system. In containers /dev is often just a tmpfs with static
device nodes. So when /dev/loopN is not available when the
container is created the device node will be missing even if
`get_unbound()` create a new loop device for us.

This commit ensures that the device node is available. It creates
it unconditionally and ignores any EEXIST errors to ensure there
is no TOCTOU issue.

Note that the test could have passed a `Loop(dir_fd=open(tmpdir))`
instead of creating/patching loop.DEV_PATH but it seems slightly
nicer to test the flow without a custom dir_path as this is what
the real code that creates a loop device is also using.
This commit is contained in:
Michael Vogt 2023-11-23 15:23:15 +01:00 committed by Achilleas Koutsou
parent 9cd8fc979b
commit 158acaac78
2 changed files with 26 additions and 4 deletions

View file

@ -15,6 +15,9 @@ __all__ = [
"UnexpectedDevice"
]
# can be mocked in tests
DEV_PATH = "/dev"
class UnexpectedDevice(Exception):
def __init__(self, expected_minor, rdev, mode):
@ -124,8 +127,14 @@ class Loop:
with contextlib.ExitStack() as stack:
if not dir_fd:
dir_fd = os.open("/dev", os.O_DIRECTORY)
dir_fd = os.open(DEV_PATH, os.O_DIRECTORY)
stack.callback(lambda: os.close(dir_fd))
# ensure the device node is availale, in containers it may
# not get dynamically created
try:
self.mknod(dir_fd)
except FileExistsError:
pass
self.fd = os.open(self.devname, os.O_RDWR, dir_fd=dir_fd)
info = os.stat(self.fd)
@ -534,7 +543,7 @@ class LoopControl:
with contextlib.ExitStack() as stack:
if not dir_fd:
dir_fd = os.open("/dev", os.O_DIRECTORY)
dir_fd = os.open(DEV_PATH, os.O_DIRECTORY)
stack.callback(lambda: os.close(dir_fd))
self.fd = os.open("loop-control", os.O_RDWR, dir_fd=dir_fd)