tools/image-info: use osbuild's internal to mount
On the newest versions of rhel 92, 88, a change in behavior makes the
previous version of image-info failing to mount loopback devices. We've
tracked down this error to be a race condition on udev, yet without
understanding what changed for now.
Osbuild had for some time already a cleaner way to mount partitions.
osbuild has some machinery to opt out of block device handling in udev
48a4419705/devices/org.osbuild.loopback (L69)
Using this fixes the issue at hand.
This changes the way we need to mount all the partitions, including the
LVM ones. This new mechanism might also pave the way to include lusks fs.
This commit is contained in:
parent
aaddca445d
commit
31e3729236
1 changed files with 102 additions and 64 deletions
166
tools/image-info
166
tools/image-info
|
|
@ -3,7 +3,6 @@
|
|||
import argparse
|
||||
import configparser
|
||||
import contextlib
|
||||
import errno
|
||||
import functools
|
||||
import glob
|
||||
import mimetypes
|
||||
|
|
@ -21,9 +20,12 @@ import xml.etree.ElementTree
|
|||
import yaml
|
||||
|
||||
from collections import OrderedDict
|
||||
from typing import Dict, Any, Generator
|
||||
from typing import Dict, Any
|
||||
|
||||
from osbuild import loop
|
||||
from osbuild import devices, host, mounts, meta, monitor
|
||||
|
||||
index = meta.Index("/usr/lib/osbuild/")
|
||||
SECTOR_SIZE = 512
|
||||
|
||||
|
||||
def run_ostree(*args, _input=None, _check=True, **kwargs):
|
||||
|
|
@ -37,37 +39,24 @@ def run_ostree(*args, _input=None, _check=True, **kwargs):
|
|||
return res
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
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()
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def loop_open(ctl, image, *, offset=None, size=None):
|
||||
with open(image, "rb") as f:
|
||||
fd = f.fileno()
|
||||
with loop_create_device(ctl, fd, offset=offset, sizelimit=size) as lo:
|
||||
yield os.path.join("/dev", lo.devname)
|
||||
|
||||
def loop_open(devmgr:devices.DeviceManager, name:str, image, size, offset=0):
|
||||
"""
|
||||
Uses a DeviceManager to open the `name` at `offset`
|
||||
Retuns a Device object and the path onto wich the image was loopback mounted
|
||||
"""
|
||||
info = index.get_module_info("Device", "org.osbuild.loopback")
|
||||
fname = os.path.basename(image)
|
||||
options = {
|
||||
"filename": fname,
|
||||
"start": offset // SECTOR_SIZE,
|
||||
"size": size // SECTOR_SIZE
|
||||
}
|
||||
dev = devices.Device(name, info, None, options)
|
||||
reply = devmgr.open(dev)
|
||||
return {
|
||||
"Device": dev,
|
||||
"path": os.path.join("/dev", reply["path"])
|
||||
}
|
||||
|
||||
@contextlib.contextmanager
|
||||
def convert_image(image, fmt):
|
||||
|
|
@ -2488,18 +2477,12 @@ def ensure_device_file(path: str, major: int, minor: int):
|
|||
os.mknod(path, 0o600 | stat.S_IFBLK, os.makedev(major, minor))
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def discover_lvm(dev) -> Generator[Dict[str, Any], None, None]:
|
||||
def discover_lvm(dev:str, parent:devices.Device, devmgr:devices.DeviceManager):
|
||||
# find the volume group name for the device file
|
||||
vg_name = volume_group_for_device(dev)
|
||||
|
||||
# activate it
|
||||
r = subprocess.run(["vgchange", "-ay", vg_name],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.PIPE,
|
||||
check=False)
|
||||
if r.returncode != 0:
|
||||
raise RuntimeError(r.stderr.strip())
|
||||
# activating LVM is done OSBuild side.
|
||||
# However we still have to get OSBuild the name of the VG to open
|
||||
|
||||
try:
|
||||
# Find all logical volumes in the volume group
|
||||
|
|
@ -2523,10 +2506,30 @@ def discover_lvm(dev) -> Generator[Dict[str, Any], None, None]:
|
|||
parsed = list(map(lambda l: l.split(";"), data.split("\n")))
|
||||
volumes = OrderedDict()
|
||||
|
||||
# devices_map stores for each device path onto the system the corresponding
|
||||
# OSBuild's Device object
|
||||
devices_map = {}
|
||||
|
||||
for vol in parsed:
|
||||
vol = list(map(lambda v: v.strip(), vol))
|
||||
assert len(vol) == 4
|
||||
name, voldev, major, minor = vol
|
||||
name, _, _, _ = vol
|
||||
|
||||
options = {
|
||||
"volume": name,
|
||||
}
|
||||
|
||||
# Create an OSBuild device object for the LVM partition
|
||||
device = devices.Device(
|
||||
name,
|
||||
index.get_module_info("Device", "org.osbuild.lvm2.lv"),
|
||||
parent,
|
||||
options)
|
||||
reply = devmgr.open(device)
|
||||
voldev = reply["path"] # get the path where is mounted the device
|
||||
minor = reply["node"]["minor"]
|
||||
major = reply["node"]["major"]
|
||||
|
||||
info = {
|
||||
"device": voldev
|
||||
}
|
||||
|
|
@ -2536,37 +2539,51 @@ def discover_lvm(dev) -> Generator[Dict[str, Any], None, None]:
|
|||
volumes[name] = info
|
||||
if name.startswith("root"):
|
||||
volumes.move_to_end(name, last=False)
|
||||
yield {
|
||||
|
||||
# associate the device path with the Device object, we will need it to
|
||||
# mount later on.
|
||||
devices_map[voldev] = device
|
||||
# get back both the device map and the result that'll go in the JSON report
|
||||
return devices_map, {
|
||||
"lvm": True,
|
||||
"lvm.vg": vg_name,
|
||||
"lvm.volumes": volumes
|
||||
}
|
||||
|
||||
finally:
|
||||
r = subprocess.run(["vgchange", "-an", vg_name],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.PIPE,
|
||||
check=False)
|
||||
if r.returncode != 0:
|
||||
raise RuntimeError(r.stderr.strip())
|
||||
pass
|
||||
|
||||
|
||||
def partition_is_lvm(part: Dict) -> bool:
|
||||
return part["type"].upper() in ["E6D6D379-F507-44C2-A23C-238F2A3DF928", "8E"]
|
||||
|
||||
|
||||
def append_partitions(report, image, loctl):
|
||||
def append_partitions(report, image):
|
||||
partitions = report["partitions"]
|
||||
with (tempfile.TemporaryDirectory() as mountpoint,
|
||||
host.ServiceManager(monitor=monitor.NullMonitor(1)) as mgr):
|
||||
|
||||
with contextlib.ExitStack() as cm:
|
||||
# open each partition as a loop device
|
||||
devmgr = devices.DeviceManager(mgr, "/dev", os.path.dirname(image))
|
||||
|
||||
# Device map associate a path onto where the device is mounted with its
|
||||
# corresponding Device object. Mount will require both the path and the
|
||||
# Device object in order to do its job.
|
||||
devices_map = {}
|
||||
filesystems = {}
|
||||
for part in partitions:
|
||||
start, size = part["start"], part["size"]
|
||||
dev = cm.enter_context(loop_open(loctl, image, offset=start, size=size))
|
||||
ret = loop_open(
|
||||
devmgr,
|
||||
part["partuuid"],
|
||||
image,
|
||||
size,
|
||||
offset=start)
|
||||
dev = ret["path"]
|
||||
devices_map[dev] = ret["Device"]
|
||||
read_partition(dev, part)
|
||||
if partition_is_lvm(part):
|
||||
lvm = cm.enter_context(discover_lvm(dev))
|
||||
dmap, lvm = discover_lvm(dev, ret["Device"], devmgr)
|
||||
devices_map.update(dmap)
|
||||
for vol in lvm["lvm.volumes"].values():
|
||||
if vol["fstype"]:
|
||||
mntopts = []
|
||||
|
|
@ -2600,6 +2617,7 @@ def append_partitions(report, image, loctl):
|
|||
|
||||
# mount all partitions to ther respective mount points
|
||||
root_tree = ""
|
||||
mmgr = mounts.MountManager(devmgr, mountpoint)
|
||||
for n, fstab_entry in enumerate(fstab):
|
||||
part_uuid = fstab_entry[0].split("=")[1].upper()
|
||||
part_device = filesystems[part_uuid]["device"]
|
||||
|
|
@ -2608,14 +2626,31 @@ def append_partitions(report, image, loctl):
|
|||
part_options = fstab_entry[3].split(",")
|
||||
part_options += filesystems[part_uuid].get("mntops", [])
|
||||
|
||||
if "ext4" in part_fstype:
|
||||
info = index.get_module_info("Mount", "org.osbuild.ext4")
|
||||
elif "vfat" in part_fstype:
|
||||
info = index.get_module_info("Mount", "org.osbuild.fat")
|
||||
elif "btrfs" in part_fstype:
|
||||
info = index.get_module_info("Mount", "org.osbuild.btrfs")
|
||||
elif "xfs" in part_fstype:
|
||||
info = index.get_module_info("Mount", "org.osbuild.xfs")
|
||||
else:
|
||||
raise RuntimeError("Unknown file system")
|
||||
options = { "readonly":True }
|
||||
|
||||
|
||||
# the first mount point should be root
|
||||
if n == 0:
|
||||
if part_mountpoint != "/":
|
||||
raise RuntimeError("The first mountpoint in sorted fstab entries is not '/'")
|
||||
root_tree = cm.enter_context(mount(part_device, part_options))
|
||||
continue
|
||||
root_tree = mountpoint
|
||||
|
||||
cm.enter_context(mount_at(part_device, f"{root_tree}{part_mountpoint}", options=part_options, extra=["-t", part_fstype]))
|
||||
mmgr.mount(mounts.Mount(
|
||||
part_device,
|
||||
info,
|
||||
devices_map[part_device], # retrieves the associated Device Object
|
||||
part_mountpoint,
|
||||
options))
|
||||
|
||||
if not root_tree:
|
||||
raise RuntimeError("The root filesystem tree is not mounted")
|
||||
|
|
@ -2624,14 +2659,18 @@ def append_partitions(report, image, loctl):
|
|||
|
||||
|
||||
def analyse_image(image) -> Dict[str, Any]:
|
||||
loctl = loop.LoopControl()
|
||||
|
||||
imgfmt = read_image_format(image)
|
||||
report: Dict[str, Any] = {"image-format": imgfmt}
|
||||
|
||||
with convert_image(image, imgfmt) as target:
|
||||
size = os.stat(target).st_size
|
||||
with loop_open(loctl, target, offset=0, size=size) as device:
|
||||
with host.ServiceManager(monitor=monitor.NullMonitor(1)) as mgr:
|
||||
device = loop_open(
|
||||
devices.DeviceManager(mgr, "/dev", os.path.dirname(target)),
|
||||
os.path.basename(target),
|
||||
target,
|
||||
size,
|
||||
offset=0)["path"]
|
||||
report["bootloader"] = read_bootloader_type(device)
|
||||
report.update(read_partition_table(device))
|
||||
if not report["partition-table"]:
|
||||
|
|
@ -2641,7 +2680,7 @@ def analyse_image(image) -> Dict[str, Any]:
|
|||
return report
|
||||
|
||||
# close loop device and descend into partitions on image file
|
||||
append_partitions(report, target, loctl)
|
||||
append_partitions(report, target)
|
||||
return report
|
||||
|
||||
|
||||
|
|
@ -2784,4 +2823,3 @@ def main():
|
|||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue