From 38e89c1bf7a3e6279d8471afe53dbc719926b918 Mon Sep 17 00:00:00 2001 From: Andreia Ocanoaia Date: Wed, 4 Jun 2025 17:01:49 +0300 Subject: [PATCH 1/6] Improve QCow2 to support automatic backing file resolution - Allow the QCow2 constructor to accept a Path object for fh - If a backing file is detected, automatically attempt to open it relative to fh path's. Note: Providing a Qcow2 image with a backing file will work only if the fh is passed as a Path obj. Signed-off-by: Andreia Ocanoaia --- dissect/hypervisor/disk/qcow2.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/dissect/hypervisor/disk/qcow2.py b/dissect/hypervisor/disk/qcow2.py index ce1ef91..f87597f 100644 --- a/dissect/hypervisor/disk/qcow2.py +++ b/dissect/hypervisor/disk/qcow2.py @@ -7,6 +7,7 @@ import zlib from functools import cached_property, lru_cache from io import BytesIO +from pathlib import Path from typing import TYPE_CHECKING, BinaryIO from dissect.util.stream import AlignedStream @@ -50,7 +51,10 @@ class QCow2(AlignedStream): in all null bytes being read. """ - def __init__(self, fh: BinaryIO, data_file: BinaryIO | None = None, backing_file: BinaryIO | int | None = None): + def __init__(self, fh: BinaryIO | Path, data_file: BinaryIO | None = None, backing_file: BinaryIO | int | None = None): + f = fh + if not hasattr(fh, "read"): + fh = f.open("rb") self.fh = fh self.header = c_qcow2.QCowHeader(fh) @@ -116,7 +120,12 @@ def __init__(self, fh: BinaryIO, data_file: BinaryIO | None = None, backing_file self.image_backing_file = self.auto_backing_file.upper() if backing_file is None: - raise Error(f"backing-file required but not provided (auto_backing_file = {self.auto_backing_file})") + if hasattr(f, 'read'): + raise Error(f"backing-file required but not provided (auto_backing_file = {self.auto_backing_file})") + candidate_path = Path(f).parent / self.auto_backing_file + if not candidate_path.exists(): + raise Error(f"backing-file '{candidate_path}' not found (auto_backing_file = '{self.auto_backing_file}')") + backing_file = candidate_path.open("rb") if backing_file != ALLOW_NO_BACKING_FILE: self.backing_file = backing_file From a824b0c3bbeaf0666b6d84e70b8362625cb4eab0 Mon Sep 17 00:00:00 2001 From: Andreia Ocanoaia Date: Mon, 4 Aug 2025 11:14:34 +0300 Subject: [PATCH 2/6] Update dissect/hypervisor/disk/qcow2.py Co-authored-by: Erik Schamper <1254028+Schamper@users.noreply.github.com> --- dissect/hypervisor/disk/qcow2.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/dissect/hypervisor/disk/qcow2.py b/dissect/hypervisor/disk/qcow2.py index f87597f..228878a 100644 --- a/dissect/hypervisor/disk/qcow2.py +++ b/dissect/hypervisor/disk/qcow2.py @@ -52,10 +52,7 @@ class QCow2(AlignedStream): """ def __init__(self, fh: BinaryIO | Path, data_file: BinaryIO | None = None, backing_file: BinaryIO | int | None = None): - f = fh - if not hasattr(fh, "read"): - fh = f.open("rb") - self.fh = fh + self.fh = fh.open("rb") if isinstance(fh, Path) else fh self.header = c_qcow2.QCowHeader(fh) if self.header.magic != QCOW2_MAGIC: From 18b5a95ab05c65d7ad9b1434069726373f148c37 Mon Sep 17 00:00:00 2001 From: Andreia Ocanoaia Date: Mon, 4 Aug 2025 11:14:45 +0300 Subject: [PATCH 3/6] Update dissect/hypervisor/disk/qcow2.py Co-authored-by: Erik Schamper <1254028+Schamper@users.noreply.github.com> --- dissect/hypervisor/disk/qcow2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dissect/hypervisor/disk/qcow2.py b/dissect/hypervisor/disk/qcow2.py index 228878a..1f20a19 100644 --- a/dissect/hypervisor/disk/qcow2.py +++ b/dissect/hypervisor/disk/qcow2.py @@ -117,7 +117,7 @@ def __init__(self, fh: BinaryIO | Path, data_file: BinaryIO | None = None, back self.image_backing_file = self.auto_backing_file.upper() if backing_file is None: - if hasattr(f, 'read'): + if not isinstance(fh, Path): raise Error(f"backing-file required but not provided (auto_backing_file = {self.auto_backing_file})") candidate_path = Path(f).parent / self.auto_backing_file if not candidate_path.exists(): From 607550d2efff8c3f60af683520366c58d89e4555 Mon Sep 17 00:00:00 2001 From: Andreia Ocanoaia Date: Mon, 4 Aug 2025 11:14:55 +0300 Subject: [PATCH 4/6] Update dissect/hypervisor/disk/qcow2.py Co-authored-by: Erik Schamper <1254028+Schamper@users.noreply.github.com> --- dissect/hypervisor/disk/qcow2.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dissect/hypervisor/disk/qcow2.py b/dissect/hypervisor/disk/qcow2.py index 1f20a19..247ebd7 100644 --- a/dissect/hypervisor/disk/qcow2.py +++ b/dissect/hypervisor/disk/qcow2.py @@ -119,8 +119,7 @@ def __init__(self, fh: BinaryIO | Path, data_file: BinaryIO | None = None, back if backing_file is None: if not isinstance(fh, Path): raise Error(f"backing-file required but not provided (auto_backing_file = {self.auto_backing_file})") - candidate_path = Path(f).parent / self.auto_backing_file - if not candidate_path.exists(): + if not (candidate_path := fh.parent.joinpath(self.auto_backing_file)).exists(): raise Error(f"backing-file '{candidate_path}' not found (auto_backing_file = '{self.auto_backing_file}')") backing_file = candidate_path.open("rb") From 449a73dfed2977f6b1c291f897afcd8ca57125ce Mon Sep 17 00:00:00 2001 From: Andreia Ocanoaia Date: Mon, 4 Aug 2025 11:15:03 +0300 Subject: [PATCH 5/6] Update dissect/hypervisor/disk/qcow2.py Co-authored-by: Erik Schamper <1254028+Schamper@users.noreply.github.com> --- dissect/hypervisor/disk/qcow2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dissect/hypervisor/disk/qcow2.py b/dissect/hypervisor/disk/qcow2.py index 247ebd7..e42da3f 100644 --- a/dissect/hypervisor/disk/qcow2.py +++ b/dissect/hypervisor/disk/qcow2.py @@ -51,7 +51,7 @@ class QCow2(AlignedStream): in all null bytes being read. """ - def __init__(self, fh: BinaryIO | Path, data_file: BinaryIO | None = None, backing_file: BinaryIO | int | None = None): + def __init__(self, fh: BinaryIO | Path, data_file: BinaryIO | None = None, backing_file: BinaryIO | int | None = None): self.fh = fh.open("rb") if isinstance(fh, Path) else fh self.header = c_qcow2.QCowHeader(fh) From 3dd5e76bb932f5f49ecbbb87c65a474f6b619406 Mon Sep 17 00:00:00 2001 From: Andreia Ocanoaia Date: Wed, 6 Aug 2025 13:37:06 +0300 Subject: [PATCH 6/6] Lint dissect/hypervisor/disk/qcow.py Signed-off-by: Andreia Ocanoaia --- dissect/hypervisor/disk/qcow2.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/dissect/hypervisor/disk/qcow2.py b/dissect/hypervisor/disk/qcow2.py index e42da3f..3fceed5 100644 --- a/dissect/hypervisor/disk/qcow2.py +++ b/dissect/hypervisor/disk/qcow2.py @@ -51,7 +51,9 @@ class QCow2(AlignedStream): in all null bytes being read. """ - def __init__(self, fh: BinaryIO | Path, data_file: BinaryIO | None = None, backing_file: BinaryIO | int | None = None): + def __init__( + self, fh: BinaryIO | Path, data_file: BinaryIO | None = None, backing_file: BinaryIO | int | None = None + ): self.fh = fh.open("rb") if isinstance(fh, Path) else fh self.header = c_qcow2.QCowHeader(fh) @@ -118,9 +120,13 @@ def __init__(self, fh: BinaryIO | Path, data_file: BinaryIO | None = None, backi if backing_file is None: if not isinstance(fh, Path): - raise Error(f"backing-file required but not provided (auto_backing_file = {self.auto_backing_file})") + raise Error( + f"backing-file required but not provided (auto_backing_file = {self.auto_backing_file})" + ) if not (candidate_path := fh.parent.joinpath(self.auto_backing_file)).exists(): - raise Error(f"backing-file '{candidate_path}' not found (auto_backing_file = '{self.auto_backing_file}')") + raise Error( + f"backing-file '{candidate_path}' not found (auto_backing_file = '{self.auto_backing_file}')" + ) backing_file = candidate_path.open("rb") if backing_file != ALLOW_NO_BACKING_FILE: