image-info: remove base loop device before analysing partitions

Instead of keeping the loop device of the base image and then opening
each partition as a loop device, remove the original loop device of the
base image and then create a loop device for each partition from the
file itself using the partition offsets.

The open_image() function is renamed to convert_image() and now only
handles converting qcow2 files to raw files if necessary.
The loop_open() context is done in analyse_image() instead, so that the
base loop device can be closed without removing the converted image.

This fixes the following issue with LVM partitions:
When the same lvm partition UUID is on two devices (e.g., /dev/loop0p4
and /dev/loop1), the 'vgchange -ay' command fails with the following
error:

  Cannot activate LVs in VG rootvg while PVs appear on duplicate
  devices.

This happens when we open the LVM partition as a separate loop device,
which we do for all partitions that we want to inspect.

NB: It's possible to restrict the vgchange command to a specific device
with --devices, but this isn't available in older versions of lvm2 (it
was introduced in 2.03.11).
This commit is contained in:
Achilleas Koutsou 2022-09-02 13:51:46 +02:00 committed by Tom Gundersen
parent 81e11c7946
commit 6dafa36fc7

View file

@ -70,7 +70,7 @@ def loop_open(ctl, image, *, offset=None, size=None):
@contextlib.contextmanager
def open_image(ctl, image, fmt):
def convert_image(ctl, image, fmt):
with tempfile.TemporaryDirectory(dir="/var/tmp") as tmp:
if fmt["type"] != "raw":
target = os.path.join(tmp, "image.raw")
@ -93,10 +93,7 @@ def open_image(ctl, image, fmt):
else:
target = image
size = os.stat(target).st_size
with loop_open(ctl, target, offset=0, size=size) as dev:
yield target, dev
yield target
@contextlib.contextmanager
@ -2556,7 +2553,7 @@ def partition_is_lvm(part: Dict) -> bool:
return part["type"].upper() in ["E6D6D379-F507-44C2-A23C-238F2A3DF928", "8E"]
def append_partitions(report, device, loctl):
def append_partitions(report, image, loctl):
partitions = report["partitions"]
with contextlib.ExitStack() as cm:
@ -2564,7 +2561,7 @@ def append_partitions(report, device, loctl):
filesystems = {}
for part in partitions:
start, size = part["start"], part["size"]
dev = cm.enter_context(loop_open(loctl, device, offset=start, size=size))
dev = cm.enter_context(loop_open(loctl, image, offset=start, size=size))
read_partition(dev, part)
if partition_is_lvm(part):
lvm = cm.enter_context(discover_lvm(dev))
@ -2630,17 +2627,20 @@ def analyse_image(image):
imgfmt = read_image_format(image)
report = {"image-format": imgfmt}
with open_image(loctl, image, imgfmt) as (_, device):
report["bootloader"] = read_bootloader_type(device)
report.update(read_partition_table(device))
with convert_image(loctl, image, imgfmt) as target:
size = os.stat(target).st_size
with loop_open(loctl, target, offset=0, size=size) as device:
report["bootloader"] = read_bootloader_type(device)
report.update(read_partition_table(device))
if not report["partition-table"]:
# no partition table: mount device and treat it as a partition
with mount(device) as tree:
append_filesystem(report, tree)
return report
if report["partition-table"]:
append_partitions(report, device, loctl)
else:
with mount(device) as tree:
append_filesystem(report, tree)
return report
# close loop device and descend into partitions on image file
append_partitions(report, target, loctl)
return report
def append_directory(report, tree):