devices/loopback: add read-only option
It's sometimes useful to set up a loop device for an already formatted disk/filesystem image to derive new artifacts from it. In that case, we want to make sure it's impossible to modify its contents in any way in that process, both for our own purposes and for other stages operating on it. Notably, mounting some filesystems read-only still seem to touch the disk (like XFS).
This commit is contained in:
parent
478fee2876
commit
3c3be92016
2 changed files with 25 additions and 7 deletions
|
|
@ -58,6 +58,10 @@ SCHEMA = """
|
||||||
"lock": {
|
"lock": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Lock the device after opening it"
|
"description": "Lock the device after opening it"
|
||||||
|
},
|
||||||
|
"read-only": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Set up the device as read-only"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
@ -77,7 +81,7 @@ class LoopbackService(devices.DeviceService):
|
||||||
lock = UdevInhibitor.for_device(lo.LOOP_MAJOR, lo.minor)
|
lock = UdevInhibitor.for_device(lo.LOOP_MAJOR, lo.minor)
|
||||||
lo.on_close = lambda _l: lock.release()
|
lo.on_close = lambda _l: lock.release()
|
||||||
|
|
||||||
def make_loop(self, fd: int, offset, sizelimit, lock, partscan):
|
def make_loop(self, fd: int, offset, sizelimit, lock, partscan, read_only):
|
||||||
if not sizelimit:
|
if not sizelimit:
|
||||||
sizelimit = os.fstat(fd).st_size - offset
|
sizelimit = os.fstat(fd).st_size - offset
|
||||||
else:
|
else:
|
||||||
|
|
@ -89,6 +93,7 @@ class LoopbackService(devices.DeviceService):
|
||||||
sizelimit=sizelimit,
|
sizelimit=sizelimit,
|
||||||
blocksize=self.sector_size,
|
blocksize=self.sector_size,
|
||||||
partscan=partscan,
|
partscan=partscan,
|
||||||
|
read_only=read_only,
|
||||||
autoclear=True)
|
autoclear=True)
|
||||||
|
|
||||||
return lo
|
return lo
|
||||||
|
|
@ -100,12 +105,13 @@ class LoopbackService(devices.DeviceService):
|
||||||
size = options.get("size")
|
size = options.get("size")
|
||||||
lock = options.get("lock", False)
|
lock = options.get("lock", False)
|
||||||
partscan = options.get("partscan", False)
|
partscan = options.get("partscan", False)
|
||||||
|
read_only = options.get("read-only", False)
|
||||||
|
|
||||||
path = os.path.join(tree, filename.lstrip("/"))
|
path = os.path.join(tree, filename.lstrip("/"))
|
||||||
|
|
||||||
self.fd = os.open(path, os.O_RDWR | os.O_CLOEXEC)
|
self.fd = os.open(path, os.O_RDWR | os.O_CLOEXEC)
|
||||||
try:
|
try:
|
||||||
self.lo = self.make_loop(self.fd, start, size, lock, partscan)
|
self.lo = self.make_loop(self.fd, start, size, lock, partscan, read_only)
|
||||||
except Exception as error: # pylint: disable: broad-except
|
except Exception as error: # pylint: disable: broad-except
|
||||||
self.close()
|
self.close()
|
||||||
raise error from None
|
raise error from None
|
||||||
|
|
|
||||||
|
|
@ -317,7 +317,7 @@ class Loop:
|
||||||
# it is bound, check if it is bound by `fd`
|
# it is bound, check if it is bound by `fd`
|
||||||
return loop_info.is_bound_to(file_info)
|
return loop_info.is_bound_to(file_info)
|
||||||
|
|
||||||
def _config_info(self, info, offset, sizelimit, autoclear, partscan):
|
def _config_info(self, info, offset, sizelimit, autoclear, partscan, read_only):
|
||||||
# pylint: disable=attribute-defined-outside-init
|
# pylint: disable=attribute-defined-outside-init
|
||||||
if offset:
|
if offset:
|
||||||
info.lo_offset = offset
|
info.lo_offset = offset
|
||||||
|
|
@ -333,9 +333,14 @@ class Loop:
|
||||||
info.lo_flags |= self.LO_FLAGS_PARTSCAN
|
info.lo_flags |= self.LO_FLAGS_PARTSCAN
|
||||||
else:
|
else:
|
||||||
info.lo_flags &= ~self.LO_FLAGS_PARTSCAN
|
info.lo_flags &= ~self.LO_FLAGS_PARTSCAN
|
||||||
|
if read_only is not None:
|
||||||
|
if read_only:
|
||||||
|
info.lo_flags |= self.LO_FLAGS_READ_ONLY
|
||||||
|
else:
|
||||||
|
info.lo_flags &= ~self.LO_FLAGS_READ_ONLY
|
||||||
return info
|
return info
|
||||||
|
|
||||||
def set_status(self, offset=None, sizelimit=None, autoclear=None, partscan=None):
|
def set_status(self, offset=None, sizelimit=None, autoclear=None, partscan=None, read_only=None):
|
||||||
"""Set properties of the loopback device
|
"""Set properties of the loopback device
|
||||||
|
|
||||||
The loopback device must be bound, and the properties will be
|
The loopback device must be bound, and the properties will be
|
||||||
|
|
@ -371,12 +376,16 @@ class Loop:
|
||||||
partscan : bool, optional
|
partscan : bool, optional
|
||||||
Whether or not to enable partition scanning, or None to leave
|
Whether or not to enable partition scanning, or None to leave
|
||||||
unchanged (default is None)
|
unchanged (default is None)
|
||||||
|
read_only : bool, optional
|
||||||
|
Whether or not to setup the loopback device as read-only (default
|
||||||
|
is None).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
info = self._config_info(self.get_status(), offset, sizelimit, autoclear, partscan)
|
info = self._config_info(self.get_status(), offset, sizelimit, autoclear, partscan, read_only)
|
||||||
fcntl.ioctl(self.fd, self.LOOP_SET_STATUS64, info)
|
fcntl.ioctl(self.fd, self.LOOP_SET_STATUS64, info)
|
||||||
|
|
||||||
def configure(self, fd: int, offset=None, sizelimit=None, blocksize=0, autoclear=None, partscan=None):
|
def configure(self, fd: int, offset=None, sizelimit=None, blocksize=0, autoclear=None, partscan=None,
|
||||||
|
read_only=None):
|
||||||
"""
|
"""
|
||||||
Configure the loopback device
|
Configure the loopback device
|
||||||
Bind and configure in a single operation a file descriptor to the
|
Bind and configure in a single operation a file descriptor to the
|
||||||
|
|
@ -426,12 +435,15 @@ class Loop:
|
||||||
partscan : bool, optional
|
partscan : bool, optional
|
||||||
Whether or not to enable partition scanning, or None to leave
|
Whether or not to enable partition scanning, or None to leave
|
||||||
unchanged (default is None)
|
unchanged (default is None)
|
||||||
|
read_only : bool, optional
|
||||||
|
Whether or not to setup the loopback device as read-only (default
|
||||||
|
is None).
|
||||||
"""
|
"""
|
||||||
# pylint: disable=attribute-defined-outside-init
|
# pylint: disable=attribute-defined-outside-init
|
||||||
config = LoopConfig()
|
config = LoopConfig()
|
||||||
config.fd = fd
|
config.fd = fd
|
||||||
config.block_size = int(blocksize)
|
config.block_size = int(blocksize)
|
||||||
config.info = self._config_info(LoopInfo(), offset, sizelimit, autoclear, partscan)
|
config.info = self._config_info(LoopInfo(), offset, sizelimit, autoclear, partscan, read_only)
|
||||||
try:
|
try:
|
||||||
fcntl.ioctl(self.fd, self.LOOP_CONFIGURE, config)
|
fcntl.ioctl(self.fd, self.LOOP_CONFIGURE, config)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue