From b3e201d123a89d9981df1713a97858660b7b4b50 Mon Sep 17 00:00:00 2001 From: JSCU-CNI <121175071+JSCU-CNI@users.noreply.github.com> Date: Thu, 8 Feb 2024 15:39:17 +0100 Subject: [PATCH 1/7] add basic cpio loader --- .gitattributes | 2 ++ dissect/target/helpers/fsutil.py | 26 ++++++++++---- dissect/target/loader.py | 1 + dissect/target/loaders/cpio.py | 32 +++++++++++++++++ .../loaders/cpio/initrd.img-6.1.0-15-amd64 | 3 ++ .../loaders/cpio/initrd.img-6.1.0-17-amd64 | 3 ++ tests/loaders/test_cpio.py | 36 +++++++++++++++++++ 7 files changed, 97 insertions(+), 6 deletions(-) create mode 100644 dissect/target/loaders/cpio.py create mode 100644 tests/_data/loaders/cpio/initrd.img-6.1.0-15-amd64 create mode 100644 tests/_data/loaders/cpio/initrd.img-6.1.0-17-amd64 create mode 100644 tests/loaders/test_cpio.py diff --git a/.gitattributes b/.gitattributes index 7e4edd4db..83f767082 100644 --- a/.gitattributes +++ b/.gitattributes @@ -13,3 +13,5 @@ tests/_data/volumes/bde/enc-volume.bin filter=lfs diff=lfs merge=lfs -text tests/_data/volumes/md/md-nested.bin.gz filter=lfs diff=lfs merge=lfs -text tests/_data/loaders/tar/test-anon-filesystems.tar filter=lfs diff=lfs merge=lfs -text tests/_data/plugins/apps/browser/firefox/cookies.sqlite filter=lfs diff=lfs merge=lfs -text +tests/_data/loaders/cpio/initrd.img-6.1.0-15-amd64 filter=lfs diff=lfs merge=lfs -text +tests/_data/loaders/cpio/initrd.img-6.1.0-17-amd64 filter=lfs diff=lfs merge=lfs -text diff --git a/dissect/target/helpers/fsutil.py b/dissect/target/helpers/fsutil.py index 7866eead1..2b7de8516 100644 --- a/dissect/target/helpers/fsutil.py +++ b/dissect/target/helpers/fsutil.py @@ -16,9 +16,16 @@ try: import bz2 - HAVE_BZ2 = True + HAS_BZ2 = True except ImportError: - HAVE_BZ2 = False + HAS_BZ2 = False + +try: + import zstandard + + HAS_ZSTD = True +except ImportError: + HAS_ZSTD = False import dissect.target.filesystem as filesystem from dissect.target.exceptions import FileNotFoundError, SymlinkRecursionError @@ -451,7 +458,7 @@ def open_decompress( errors: Optional[str] = "backslashreplace", newline: Optional[str] = None, ) -> Union[BinaryIO, TextIO]: - """Open and decompress a file. Handles gz and bz2 files. Uncompressed files are opened as-is. + """Open and decompress a file. Handles gz, bz2 and zstd files. Uncompressed files are opened as-is. Args: path: The path to the file to open and decompress. It is assumed this path exists. @@ -469,7 +476,7 @@ def open_decompress( for line in open_decompress(Path("/dir/file.gz"), "rt"): print(line) """ - file = path.open() + file = path.open("rb") magic = file.read(4) file.seek(0) @@ -480,9 +487,16 @@ def open_decompress( if magic[:2] == b"\x1f\x8b": return gzip.open(file, mode, encoding=encoding, errors=errors, newline=newline) - # In a valid bz2 header the 4th byte is in the range b'1' ... b'9'. - elif HAVE_BZ2 and magic[:3] == b"BZh" and 0x31 <= magic[3] <= 0x39: + + elif HAS_BZ2 and magic[:3] == b"BZh" and 0x31 <= magic[3] <= 0x39: + # In a valid bz2 header the 4th byte is in the range b'1' ... b'9'. return bz2.open(file, mode, encoding=encoding, errors=errors, newline=newline) + + elif HAS_ZSTD and magic[:4] in [b"\xfd\x2f\xb5\x28", b"\x28\xb5\x2f\xfd"]: + # stream_reader is not seekable, so we have to resort to the less + # efficient decompressor which returns bytes. + return io.BytesIO(zstandard.decompress(path.open("rb").read())) + else: file.close() return path.open(mode, encoding=encoding, errors=errors, newline=newline) diff --git a/dissect/target/loader.py b/dissect/target/loader.py index de1c04df0..328e548ee 100644 --- a/dissect/target/loader.py +++ b/dissect/target/loader.py @@ -203,4 +203,5 @@ def open(item: Union[str, Path], *args, **kwargs) -> Loader: register("smb", "SmbLoader") register("cb", "CbLoader") register("cyber", "CyberLoader") +register("cpio", "CpioLoader") register("multiraw", "MultiRawLoader") # Should be last diff --git a/dissect/target/loaders/cpio.py b/dissect/target/loaders/cpio.py new file mode 100644 index 000000000..5cde3b71a --- /dev/null +++ b/dissect/target/loaders/cpio.py @@ -0,0 +1,32 @@ +from pathlib import Path + +from dissect.util import cpio + +from dissect.target import Target +from dissect.target.filesystems.tar import TarFilesystem +from dissect.target.helpers.fsutil import open_decompress +from dissect.target.loader import Loader + + +class CpioLoader(Loader): + """Load (compressed) CPIO files.""" + + def __init__(self, path: Path, **kwargs): + super().__init__(path) + + fh = open_decompress(path, "rb") + self.cpio = TarFilesystem(fh, tarinfo=cpio.CpioInfo) + + @staticmethod + def detect(path: Path) -> bool: + magic = path.open("rb").read(6) + + # gzip, bz2 or zstd + if magic[:2] == b"\x1f\x8b" or magic[:4] in [b"\xfd\x2f\xb5\x28", b"\x28\xb5\x2f\xfd"]: + magic = open_decompress(path, "rb").read(6) + + return magic in [b"070701", b"070707", b"070702"] or magic[:2] in [b"\x71\xc7", b"\xc7\x71"] + + def map(self, target: Target) -> None: + target.filesystems.add(self.cpio) + target.fs.mount("/", self.cpio) diff --git a/tests/_data/loaders/cpio/initrd.img-6.1.0-15-amd64 b/tests/_data/loaders/cpio/initrd.img-6.1.0-15-amd64 new file mode 100644 index 000000000..5d150b16e --- /dev/null +++ b/tests/_data/loaders/cpio/initrd.img-6.1.0-15-amd64 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a12f63a0c0783d9f647e46d058cfd1cd973ae0fc47332741d5ada70c91838426 +size 30818636 diff --git a/tests/_data/loaders/cpio/initrd.img-6.1.0-17-amd64 b/tests/_data/loaders/cpio/initrd.img-6.1.0-17-amd64 new file mode 100644 index 000000000..fb46ce16b --- /dev/null +++ b/tests/_data/loaders/cpio/initrd.img-6.1.0-17-amd64 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:da629b5cf297525debef2388df1259f6c272f07a42c22162070e601dcfbe79a4 +size 38204386 diff --git a/tests/loaders/test_cpio.py b/tests/loaders/test_cpio.py new file mode 100644 index 000000000..07eddf135 --- /dev/null +++ b/tests/loaders/test_cpio.py @@ -0,0 +1,36 @@ +from pathlib import Path + +from dissect.target import Target +from dissect.target.loaders.cpio import CpioLoader +from tests._utils import absolute_path + + +def test_cpio_uncompressed(target_default: Target) -> None: + cpio_path = Path(absolute_path("_data/loaders/cpio/initrd.img-6.1.0-17-amd64")) + + loader = CpioLoader(cpio_path) + loader.map(target_default) + assert len(target_default.fs.mounts) == 1 + + assert [f.name for f in target_default.fs.path("/").iterdir()] == ["kernel"] + + +def test_cpio_compressed_zstd(target_default: Target) -> None: + cpio_path = Path(absolute_path("_data/loaders/cpio/initrd.img-6.1.0-15-amd64")) + + loader = CpioLoader(cpio_path) + loader.map(target_default) + assert len(target_default.fs.mounts) == 1 + + assert [f.name for f in target_default.fs.path("/").iterdir()] == [ + "bin", + "conf", + "etc", + "init", + "lib", + "lib64", + "run", + "sbin", + "scripts", + "usr", + ] From 06cd353d54888d580b9c6ca0b34b31e226c16341 Mon Sep 17 00:00:00 2001 From: JSCU-CNI <121175071+JSCU-CNI@users.noreply.github.com> Date: Tue, 13 Feb 2024 13:13:59 +0100 Subject: [PATCH 2/7] implement review comments --- dissect/target/helpers/fsutil.py | 12 ++++++------ dissect/target/loaders/cpio.py | 16 ++++++---------- tests/loaders/test_cpio.py | 14 ++++++++++++-- 3 files changed, 24 insertions(+), 18 deletions(-) diff --git a/dissect/target/helpers/fsutil.py b/dissect/target/helpers/fsutil.py index 2b7de8516..f23ddc446 100644 --- a/dissect/target/helpers/fsutil.py +++ b/dissect/target/helpers/fsutil.py @@ -16,16 +16,16 @@ try: import bz2 - HAS_BZ2 = True + HAVE_BZ2 = True except ImportError: - HAS_BZ2 = False + HAVE_BZ2 = False try: import zstandard - HAS_ZSTD = True + HAVE_ZSTD = True except ImportError: - HAS_ZSTD = False + HAVE_ZSTD = False import dissect.target.filesystem as filesystem from dissect.target.exceptions import FileNotFoundError, SymlinkRecursionError @@ -488,11 +488,11 @@ def open_decompress( if magic[:2] == b"\x1f\x8b": return gzip.open(file, mode, encoding=encoding, errors=errors, newline=newline) - elif HAS_BZ2 and magic[:3] == b"BZh" and 0x31 <= magic[3] <= 0x39: + elif HAVE_BZ2 and magic[:3] == b"BZh" and 0x31 <= magic[3] <= 0x39: # In a valid bz2 header the 4th byte is in the range b'1' ... b'9'. return bz2.open(file, mode, encoding=encoding, errors=errors, newline=newline) - elif HAS_ZSTD and magic[:4] in [b"\xfd\x2f\xb5\x28", b"\x28\xb5\x2f\xfd"]: + elif HAVE_ZSTD and magic[:4] in [b"\xfd\x2f\xb5\x28", b"\x28\xb5\x2f\xfd"]: # stream_reader is not seekable, so we have to resort to the less # efficient decompressor which returns bytes. return io.BytesIO(zstandard.decompress(path.open("rb").read())) diff --git a/dissect/target/loaders/cpio.py b/dissect/target/loaders/cpio.py index 5cde3b71a..6fd4e1a38 100644 --- a/dissect/target/loaders/cpio.py +++ b/dissect/target/loaders/cpio.py @@ -11,22 +11,18 @@ class CpioLoader(Loader): """Load (compressed) CPIO files.""" - def __init__(self, path: Path, **kwargs): - super().__init__(path) - - fh = open_decompress(path, "rb") - self.cpio = TarFilesystem(fh, tarinfo=cpio.CpioInfo) - @staticmethod def detect(path: Path) -> bool: - magic = path.open("rb").read(6) + with path.open("rb") as fh: + magic = fh.read(6) # gzip, bz2 or zstd if magic[:2] == b"\x1f\x8b" or magic[:4] in [b"\xfd\x2f\xb5\x28", b"\x28\xb5\x2f\xfd"]: - magic = open_decompress(path, "rb").read(6) + with open_decompress(path, "rb") as fh: + magic = fh.read(6) return magic in [b"070701", b"070707", b"070702"] or magic[:2] in [b"\x71\xc7", b"\xc7\x71"] def map(self, target: Target) -> None: - target.filesystems.add(self.cpio) - target.fs.mount("/", self.cpio) + tfs = TarFilesystem(open_decompress(self.path, "rb"), tarinfo=cpio.CpioInfo) + target.filesystems.add(tfs) diff --git a/tests/loaders/test_cpio.py b/tests/loaders/test_cpio.py index 07eddf135..23159a911 100644 --- a/tests/loaders/test_cpio.py +++ b/tests/loaders/test_cpio.py @@ -7,9 +7,14 @@ def test_cpio_uncompressed(target_default: Target) -> None: cpio_path = Path(absolute_path("_data/loaders/cpio/initrd.img-6.1.0-17-amd64")) - loader = CpioLoader(cpio_path) + + assert loader.detect(cpio_path) + loader.map(target_default) + target_default.apply() + + assert len(target_default.filesystems) == 1 assert len(target_default.fs.mounts) == 1 assert [f.name for f in target_default.fs.path("/").iterdir()] == ["kernel"] @@ -17,9 +22,14 @@ def test_cpio_uncompressed(target_default: Target) -> None: def test_cpio_compressed_zstd(target_default: Target) -> None: cpio_path = Path(absolute_path("_data/loaders/cpio/initrd.img-6.1.0-15-amd64")) - loader = CpioLoader(cpio_path) + + assert loader.detect(cpio_path) + loader.map(target_default) + target_default.apply() + + assert len(target_default.filesystems) == 1 assert len(target_default.fs.mounts) == 1 assert [f.name for f in target_default.fs.path("/").iterdir()] == [ From f87ad0e9a246b9d323a8b2acb02528834308451b Mon Sep 17 00:00:00 2001 From: Schamper <1254028+Schamper@users.noreply.github.com> Date: Wed, 14 Feb 2024 21:27:12 +0100 Subject: [PATCH 3/7] Convert to filesystem --- dissect/target/filesystem.py | 2 ++ dissect/target/filesystems/cpio.py | 16 +++++++++++ dissect/target/helpers/fsutil.py | 23 ++++++++++++--- dissect/target/loader.py | 1 - dissect/target/loaders/cpio.py | 28 ------------------ tests/filesystems/test_cpio.py | 35 +++++++++++++++++++++++ tests/loaders/test_cpio.py | 46 ------------------------------ 7 files changed, 72 insertions(+), 79 deletions(-) create mode 100644 dissect/target/filesystems/cpio.py delete mode 100644 dissect/target/loaders/cpio.py create mode 100644 tests/filesystems/test_cpio.py delete mode 100644 tests/loaders/test_cpio.py diff --git a/dissect/target/filesystem.py b/dissect/target/filesystem.py index 8d26a8c5c..3d409ad1f 100644 --- a/dissect/target/filesystem.py +++ b/dissect/target/filesystem.py @@ -1588,5 +1588,7 @@ def open_multi_volume(fhs: list[BinaryIO], *args, **kwargs) -> Filesystem: register("exfat", "ExfatFilesystem") register("squashfs", "SquashFSFilesystem") register("zip", "ZipFilesystem") +register("tar", "TarFilesystem") +register("cpio", "CpioFilesystem") register("ad1", "AD1Filesystem") register("jffs", "JFFSFilesystem") diff --git a/dissect/target/filesystems/cpio.py b/dissect/target/filesystems/cpio.py new file mode 100644 index 000000000..034a0ceea --- /dev/null +++ b/dissect/target/filesystems/cpio.py @@ -0,0 +1,16 @@ +from typing import BinaryIO + +from dissect.util import cpio + +from dissect.target.filesystems.tar import TarFilesystem +from dissect.target.helpers.fsutil import open_decompress + + +class CpioFilesystem(TarFilesystem): + def __init__(self, fh: BinaryIO, base: str | None = None, *args, **kwargs): + super().__init__(open_decompress(fileobj=fh), base, tarinfo=cpio.CpioInfo, *args, **kwargs) + + @staticmethod + def _detect(fh: BinaryIO) -> bool: + """Detect a cpio file on a given file-like object.""" + return cpio.detect_header(open_decompress(fileobj=fh)) != cpio.FORMAT_CPIO_UNKNOWN diff --git a/dissect/target/helpers/fsutil.py b/dissect/target/helpers/fsutil.py index f23ddc446..32104c7dd 100644 --- a/dissect/target/helpers/fsutil.py +++ b/dissect/target/helpers/fsutil.py @@ -452,7 +452,8 @@ def resolve_link( def open_decompress( - path: TargetPath, + path: Optional[TargetPath] = None, + fileobj: Optional[BinaryIO] = None, mode: str = "rb", encoding: Optional[str] = "UTF-8", errors: Optional[str] = "backslashreplace", @@ -462,6 +463,7 @@ def open_decompress( Args: path: The path to the file to open and decompress. It is assumed this path exists. + fileobj: The file-like object to open and decompress. This is mutually exclusive with path. mode: The mode in which to open the file. encoding: The decoding for text streams. By default UTF-8 encoding is used. errors: The error handling for text streams. By default we're more lenient and use ``backslashreplace``. @@ -476,7 +478,17 @@ def open_decompress( for line in open_decompress(Path("/dir/file.gz"), "rt"): print(line) """ - file = path.open("rb") + if path and fileobj: + raise ValueError("path and fileobj are mutually exclusive") + + if not path and not fileobj: + raise ValueError("path or fileobj is required") + + if path: + file = path.open("rb") + else: + file = fileobj + magic = file.read(4) file.seek(0) @@ -495,12 +507,15 @@ def open_decompress( elif HAVE_ZSTD and magic[:4] in [b"\xfd\x2f\xb5\x28", b"\x28\xb5\x2f\xfd"]: # stream_reader is not seekable, so we have to resort to the less # efficient decompressor which returns bytes. - return io.BytesIO(zstandard.decompress(path.open("rb").read())) + return io.BytesIO(zstandard.decompress(file.read())) - else: + elif path: file.close() return path.open(mode, encoding=encoding, errors=errors, newline=newline) + else: + return file + def reverse_readlines(fh: TextIO, chunk_size: int = 1024 * 1024 * 8) -> Iterator[str]: """Like iterating over a ``TextIO`` file-like object, but starting from the end of the file. diff --git a/dissect/target/loader.py b/dissect/target/loader.py index 328e548ee..de1c04df0 100644 --- a/dissect/target/loader.py +++ b/dissect/target/loader.py @@ -203,5 +203,4 @@ def open(item: Union[str, Path], *args, **kwargs) -> Loader: register("smb", "SmbLoader") register("cb", "CbLoader") register("cyber", "CyberLoader") -register("cpio", "CpioLoader") register("multiraw", "MultiRawLoader") # Should be last diff --git a/dissect/target/loaders/cpio.py b/dissect/target/loaders/cpio.py deleted file mode 100644 index 6fd4e1a38..000000000 --- a/dissect/target/loaders/cpio.py +++ /dev/null @@ -1,28 +0,0 @@ -from pathlib import Path - -from dissect.util import cpio - -from dissect.target import Target -from dissect.target.filesystems.tar import TarFilesystem -from dissect.target.helpers.fsutil import open_decompress -from dissect.target.loader import Loader - - -class CpioLoader(Loader): - """Load (compressed) CPIO files.""" - - @staticmethod - def detect(path: Path) -> bool: - with path.open("rb") as fh: - magic = fh.read(6) - - # gzip, bz2 or zstd - if magic[:2] == b"\x1f\x8b" or magic[:4] in [b"\xfd\x2f\xb5\x28", b"\x28\xb5\x2f\xfd"]: - with open_decompress(path, "rb") as fh: - magic = fh.read(6) - - return magic in [b"070701", b"070707", b"070702"] or magic[:2] in [b"\x71\xc7", b"\xc7\x71"] - - def map(self, target: Target) -> None: - tfs = TarFilesystem(open_decompress(self.path, "rb"), tarinfo=cpio.CpioInfo) - target.filesystems.add(tfs) diff --git a/tests/filesystems/test_cpio.py b/tests/filesystems/test_cpio.py new file mode 100644 index 000000000..0944d50bb --- /dev/null +++ b/tests/filesystems/test_cpio.py @@ -0,0 +1,35 @@ +from pathlib import Path + +from dissect.target.filesystems.cpio import CpioFilesystem +from tests._utils import absolute_path + + +def test_cpio_uncompressed() -> None: + cpio_path = Path(absolute_path("_data/loaders/cpio/initrd.img-6.1.0-17-amd64")) + + with cpio_path.open("rb") as fh: + assert CpioFilesystem.detect(fh) + + fs = CpioFilesystem(fh) + assert [f.name for f in fs.path("/").iterdir()] == ["kernel"] + + +def test_cpio_compressed_zstd() -> None: + cpio_path = Path(absolute_path("_data/loaders/cpio/initrd.img-6.1.0-15-amd64")) + + with cpio_path.open("rb") as fh: + assert CpioFilesystem.detect(fh) + + fs = CpioFilesystem(fh) + assert [f.name for f in fs.path("/").iterdir()] == [ + "bin", + "conf", + "etc", + "init", + "lib", + "lib64", + "run", + "sbin", + "scripts", + "usr", + ] diff --git a/tests/loaders/test_cpio.py b/tests/loaders/test_cpio.py deleted file mode 100644 index 23159a911..000000000 --- a/tests/loaders/test_cpio.py +++ /dev/null @@ -1,46 +0,0 @@ -from pathlib import Path - -from dissect.target import Target -from dissect.target.loaders.cpio import CpioLoader -from tests._utils import absolute_path - - -def test_cpio_uncompressed(target_default: Target) -> None: - cpio_path = Path(absolute_path("_data/loaders/cpio/initrd.img-6.1.0-17-amd64")) - loader = CpioLoader(cpio_path) - - assert loader.detect(cpio_path) - - loader.map(target_default) - target_default.apply() - - assert len(target_default.filesystems) == 1 - assert len(target_default.fs.mounts) == 1 - - assert [f.name for f in target_default.fs.path("/").iterdir()] == ["kernel"] - - -def test_cpio_compressed_zstd(target_default: Target) -> None: - cpio_path = Path(absolute_path("_data/loaders/cpio/initrd.img-6.1.0-15-amd64")) - loader = CpioLoader(cpio_path) - - assert loader.detect(cpio_path) - - loader.map(target_default) - target_default.apply() - - assert len(target_default.filesystems) == 1 - assert len(target_default.fs.mounts) == 1 - - assert [f.name for f in target_default.fs.path("/").iterdir()] == [ - "bin", - "conf", - "etc", - "init", - "lib", - "lib64", - "run", - "sbin", - "scripts", - "usr", - ] From 6c06e18b532474daf9a16a213bb2b85387c75fbc Mon Sep 17 00:00:00 2001 From: Schamper <1254028+Schamper@users.noreply.github.com> Date: Wed, 14 Feb 2024 21:29:40 +0100 Subject: [PATCH 4/7] Fix type hint --- dissect/target/filesystems/cpio.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dissect/target/filesystems/cpio.py b/dissect/target/filesystems/cpio.py index 034a0ceea..2f97e33ac 100644 --- a/dissect/target/filesystems/cpio.py +++ b/dissect/target/filesystems/cpio.py @@ -1,4 +1,4 @@ -from typing import BinaryIO +from typing import BinaryIO, Optional from dissect.util import cpio @@ -7,7 +7,7 @@ class CpioFilesystem(TarFilesystem): - def __init__(self, fh: BinaryIO, base: str | None = None, *args, **kwargs): + def __init__(self, fh: BinaryIO, base: Optional[str] = None, *args, **kwargs): super().__init__(open_decompress(fileobj=fh), base, tarinfo=cpio.CpioInfo, *args, **kwargs) @staticmethod From 23659f9e01c7892d2fe4ae81c56a1d705bea2aa0 Mon Sep 17 00:00:00 2001 From: Schamper <1254028+Schamper@users.noreply.github.com> Date: Wed, 14 Feb 2024 21:48:00 +0100 Subject: [PATCH 5/7] Fix open_decompress arguments --- dissect/target/helpers/fsutil.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/dissect/target/helpers/fsutil.py b/dissect/target/helpers/fsutil.py index 32104c7dd..a6083f8af 100644 --- a/dissect/target/helpers/fsutil.py +++ b/dissect/target/helpers/fsutil.py @@ -453,18 +453,21 @@ def resolve_link( def open_decompress( path: Optional[TargetPath] = None, - fileobj: Optional[BinaryIO] = None, mode: str = "rb", + *, + fileobj: Optional[BinaryIO] = None, encoding: Optional[str] = "UTF-8", errors: Optional[str] = "backslashreplace", newline: Optional[str] = None, ) -> Union[BinaryIO, TextIO]: """Open and decompress a file. Handles gz, bz2 and zstd files. Uncompressed files are opened as-is. + When passing in an already opened ``fileobj``, the mode, encoding, errors and newline arguments are ignored. + Args: path: The path to the file to open and decompress. It is assumed this path exists. - fileobj: The file-like object to open and decompress. This is mutually exclusive with path. mode: The mode in which to open the file. + fileobj: The file-like object to open and decompress. This is mutually exclusive with path. encoding: The decoding for text streams. By default UTF-8 encoding is used. errors: The error handling for text streams. By default we're more lenient and use ``backslashreplace``. newline: How newlines are handled for text streams. From 81f0aa102684f10856f16c86a4ebbdbe89e24548 Mon Sep 17 00:00:00 2001 From: Schamper <1254028+Schamper@users.noreply.github.com> Date: Wed, 14 Feb 2024 22:05:28 +0100 Subject: [PATCH 6/7] Correct __type__ --- dissect/target/filesystems/cpio.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dissect/target/filesystems/cpio.py b/dissect/target/filesystems/cpio.py index 2f97e33ac..5e8d2aee2 100644 --- a/dissect/target/filesystems/cpio.py +++ b/dissect/target/filesystems/cpio.py @@ -7,6 +7,8 @@ class CpioFilesystem(TarFilesystem): + __type__ = "cpio" + def __init__(self, fh: BinaryIO, base: Optional[str] = None, *args, **kwargs): super().__init__(open_decompress(fileobj=fh), base, tarinfo=cpio.CpioInfo, *args, **kwargs) From 46d236a71955f66956c0d6890747e575c1133675 Mon Sep 17 00:00:00 2001 From: Erik Schamper <1254028+Schamper@users.noreply.github.com> Date: Mon, 19 Feb 2024 12:32:36 +0100 Subject: [PATCH 7/7] Apply suggestions from code review Co-authored-by: Stefan de Reuver <9864602+Horofic@users.noreply.github.com> --- dissect/target/helpers/fsutil.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/dissect/target/helpers/fsutil.py b/dissect/target/helpers/fsutil.py index a6083f8af..cab58d1cc 100644 --- a/dissect/target/helpers/fsutil.py +++ b/dissect/target/helpers/fsutil.py @@ -503,21 +503,20 @@ def open_decompress( if magic[:2] == b"\x1f\x8b": return gzip.open(file, mode, encoding=encoding, errors=errors, newline=newline) - elif HAVE_BZ2 and magic[:3] == b"BZh" and 0x31 <= magic[3] <= 0x39: + if HAVE_BZ2 and magic[:3] == b"BZh" and 0x31 <= magic[3] <= 0x39: # In a valid bz2 header the 4th byte is in the range b'1' ... b'9'. return bz2.open(file, mode, encoding=encoding, errors=errors, newline=newline) - elif HAVE_ZSTD and magic[:4] in [b"\xfd\x2f\xb5\x28", b"\x28\xb5\x2f\xfd"]: + if HAVE_ZSTD and magic[:4] in [b"\xfd\x2f\xb5\x28", b"\x28\xb5\x2f\xfd"]: # stream_reader is not seekable, so we have to resort to the less # efficient decompressor which returns bytes. return io.BytesIO(zstandard.decompress(file.read())) - elif path: + if path: file.close() return path.open(mode, encoding=encoding, errors=errors, newline=newline) - else: - return file + return file def reverse_readlines(fh: TextIO, chunk_size: int = 1024 * 1024 * 8) -> Iterator[str]: