From a2d9c538f45fdfd674cb396362daacd850d83af4 Mon Sep 17 00:00:00 2001 From: Schamper <1254028+Schamper@users.noreply.github.com> Date: Tue, 22 Aug 2023 23:32:25 +0200 Subject: [PATCH 1/5] Add support for dissect.btrfs --- dissect/target/filesystem.py | 96 ++++++++++++-- dissect/target/filesystems/btrfs.py | 180 ++++++++++++++++++++++++++ dissect/target/helpers/utils.py | 17 ++- dissect/target/plugins/os/unix/_os.py | 45 ++++--- dissect/target/target.py | 54 ++++---- dissect/target/tools/mount.py | 14 +- tests/plugins/os/unix/test__os.py | 12 +- tests/test_target.py | 1 + 8 files changed, 347 insertions(+), 72 deletions(-) create mode 100644 dissect/target/filesystems/btrfs.py diff --git a/dissect/target/filesystem.py b/dissect/target/filesystem.py index 4a1b2e6ba..592b9db3a 100644 --- a/dissect/target/filesystem.py +++ b/dissect/target/filesystem.py @@ -41,11 +41,13 @@ class Filesystem: """Base class for filesystems.""" __fstype__: str = None - """Defines the type of filesystem it is.""" + """Defines the type of filesystem this is.""" + __multi_volume__: bool = False + """Whether this filesystem supports multiple volumes (disks).""" def __init__( self, - volume: Optional[BinaryIO] = None, + volume: Optional[Union[BinaryIO, list[BinaryIO]]] = None, alt_separator: str = "", case_sensitive: bool = True, ) -> None: @@ -53,7 +55,7 @@ def __init__( Args: volume: A volume or other file-like object associated with the filesystem. - case_sensitive: Defines if the paths in the Filesystem are case sensitive or not. + case_sensitive: Defines if the paths in the filesystem are case sensitive or not. alt_separator: The alternative separator used to distingish between directories in a path. Raises: @@ -69,7 +71,7 @@ def __repr__(self) -> str: return f"<{self.__class__.__name__}>" def path(self, *args) -> fsutil.TargetPath: - """Get a specific path from the filesystem.""" + """Instantiate a new path-like object on this filesystem.""" return fsutil.TargetPath(self, *args) @classmethod @@ -112,6 +114,52 @@ def _detect(fh: BinaryIO) -> bool: """ raise NotImplementedError() + @classmethod + def detect_id(cls, fh: BinaryIO) -> Optional[bytes]: + """Return a filesystem set identifier. + + Only used in filesystems that support multiple volumes (disks) to find all volumes + belonging to a single filesystem. + + Args: + fh: A file-like object, usually a disk or partition. + """ + if not cls.__multi_volume__: + return None + + offset = fh.tell() + try: + fh.seek(0) + return cls._detect_id(fh) + except NotImplementedError: + raise + except Exception as e: + log.warning("Failed to detect ID on %s filesystem", cls.__fstype__) + log.debug("", exc_info=e) + finally: + fh.seek(offset) + + return None + + @staticmethod + def _detect_id(fh: BinaryIO) -> Optional[bytes]: + """Return a filesystem set identifier. + + This method should be implemented by subclasses of filesystems that support multiple volumes (disks). + The position of ``fh`` is guaranteed to be ``0``. + + Args: + fh: A file-like object, usually a disk or partition. + + Returns: + An identifier that can be used to combine the given ``fh`` with others beloning to the same set. + """ + raise NotImplementedError() + + def iter_subfs(self) -> Iterator[Filesystem]: + """Yield possible sub-filesystems.""" + yield from () + def get(self, path: str) -> FilesystemEntry: """Retrieve a :class:`FilesystemEntry` from the filesystem. @@ -1448,6 +1496,18 @@ def register(module: str, class_name: str, internal: bool = True) -> None: FILESYSTEMS.append(getattr(import_lazy(module), class_name)) +def is_multi_volume_filesystem(fh: BinaryIO) -> bool: + for filesystem in FILESYSTEMS: + try: + if filesystem.__multi_volume__ and filesystem.detect(fh): + return True + except ImportError as e: + log.info("Failed to import %s", filesystem) + log.debug("", exc_info=e) + + return False + + def open(fh: BinaryIO, *args, **kwargs) -> Filesystem: offset = fh.tell() fh.seek(0) @@ -1456,10 +1516,7 @@ def open(fh: BinaryIO, *args, **kwargs) -> Filesystem: for filesystem in FILESYSTEMS: try: if filesystem.detect(fh): - instance = filesystem(fh, *args, **kwargs) - instance.volume = fh - - return instance + return filesystem(fh, *args, **kwargs) except ImportError as e: log.info("Failed to import %s", filesystem) log.debug("", exc_info=e) @@ -1469,12 +1526,35 @@ def open(fh: BinaryIO, *args, **kwargs) -> Filesystem: raise FilesystemError(f"Failed to open filesystem for {fh}") +def open_multi_volume(fhs: list[BinaryIO], *args, **kwargs) -> Filesystem: + for filesystem in FILESYSTEMS: + try: + if not filesystem.__multi_volume__: + continue + + volumes = defaultdict(list) + for fh in fhs: + if not filesystem.detect(fh): + continue + + identifier = filesystem.detect_id(fh) + volumes[identifier].append(fh) + + for vols in volumes.values(): + yield filesystem(vols, *args, **kwargs) + + except ImportError as e: + log.info("Failed to import %s", filesystem) + log.debug("", exc_info=e) + + register("ntfs", "NtfsFilesystem") register("extfs", "ExtFilesystem") register("xfs", "XfsFilesystem") register("fat", "FatFilesystem") register("ffs", "FfsFilesystem") register("vmfs", "VmfsFilesystem") +register("btrfs", "BtrfsFilesystem") register("exfat", "ExfatFilesystem") register("squashfs", "SquashFSFilesystem") register("zip", "ZipFilesystem") diff --git a/dissect/target/filesystems/btrfs.py b/dissect/target/filesystems/btrfs.py new file mode 100644 index 000000000..d63d9b0ff --- /dev/null +++ b/dissect/target/filesystems/btrfs.py @@ -0,0 +1,180 @@ +from __future__ import annotations + +from typing import BinaryIO, Iterator, Optional, Union + +import dissect.btrfs as btrfs +from dissect.btrfs.c_btrfs import c_btrfs + +from dissect.target.exceptions import ( + FileNotFoundError, + FilesystemError, + IsADirectoryError, + NotADirectoryError, + NotASymlinkError, +) +from dissect.target.filesystem import Filesystem, FilesystemEntry +from dissect.target.helpers import fsutil + + +class BtrfsFilesystem(Filesystem): + __fstype__ = "btrfs" + __multi_volume__ = True + + def __init__(self, fh: Union[BinaryIO, list[BinaryIO]], *args, **kwargs): + super().__init__(fh, *args, **kwargs) + self.btrfs = btrfs.Btrfs(fh) + self.subfs = self.open_subvolume() + self.subvolume = self.subfs.subvolume + + @staticmethod + def _detect(fh: BinaryIO) -> bool: + fh.seek(c_btrfs.BTRFS_SUPER_INFO_OFFSET) + block = fh.read(4096) + magic = int.from_bytes(block[64:72], "little") + + return magic == c_btrfs.BTRFS_MAGIC + + @staticmethod + def _detect_id(fh: BinaryIO) -> Optional[bytes]: + # First field is csum, followed by fsid + fh.seek(c_btrfs.BTRFS_SUPER_INFO_OFFSET + c_btrfs.BTRFS_CSUM_SIZE) + return fh.read(c_btrfs.BTRFS_FSID_SIZE) + + def iter_subfs(self) -> Iterator[BtrfsSubvolumeFilesystem]: + for subvol in self.btrfs.subvolumes(): + if subvol.objectid == self.subfs.subvolume.objectid: + # Skip the default volume as it's already opened by the main filesystem + continue + yield self.open_subvolume(subvolid=subvol.objectid) + + def open_subvolume(self, subvol: Optional[str] = None, subvolid: Optional[int] = None) -> BtrfsSubvolumeFilesystem: + return BtrfsSubvolumeFilesystem(self, subvol, subvolid) + + def get(self, path: str) -> FilesystemEntry: + return self.subfs.get(path) + + +class BtrfsSubvolumeFilesystem(Filesystem): + __fstype__ = "btrfs" + + def __init__(self, fs: BtrfsFilesystem, subvol: Optional[str] = None, subvolid: Optional[int] = None): + super().__init__(fs.volume, alt_separator=fs.alt_separator, case_sensitive=fs.case_sensitive) + if subvol is not None and subvolid is not None: + raise ValueError("Only one of subvol or subvolid is allowed") + + self.fs = fs + self.btrfs = fs.btrfs + if subvol: + self.subvolume = self.btrfs.find_subvolume(subvol) + elif subvolid: + self.subvolume = self.btrfs.open_subvolume(subvolid) + else: + self.subvolume = self.btrfs.default_subvolume + + def get(self, path: str) -> FilesystemEntry: + return BtrfsFilesystemEntry(self, path, self._get_node(path)) + + def _get_node(self, path: str, node: Optional[btrfs.INode] = None) -> btrfs.INode: + try: + return self.subvolume.get(path, node) + except btrfs.FileNotFoundError as e: + raise FileNotFoundError(path, cause=e) + except btrfs.NotADirectoryError as e: + raise NotADirectoryError(path, cause=e) + except btrfs.NotASymlinkError as e: + raise NotASymlinkError(path, cause=e) + except btrfs.Error as e: + raise FileNotFoundError(path, cause=e) + + +class BtrfsFilesystemEntry(FilesystemEntry): + fs: BtrfsFilesystem + entry: btrfs.INode + + def get(self, path: str) -> FilesystemEntry: + entry_path = fsutil.join(self.path, path, alt_separator=self.fs.alt_separator) + entry = self.fs._get_node(path, self.entry) + return BtrfsFilesystemEntry(self.fs, entry_path, entry) + + def open(self) -> BinaryIO: + if self.is_dir(): + raise IsADirectoryError(self.path) + return self._resolve().entry.open() + + def _iterdir(self) -> Iterator[btrfs.INode]: + if not self.is_dir(): + raise NotADirectoryError(self.path) + + if self.is_symlink(): + for entry in self.readlink_ext().iterdir(): + yield entry + else: + for name, entry in self.entry.iterdir(): + if name in (".", ".."): + continue + + yield name, entry + + def iterdir(self) -> Iterator[str]: + for name, _ in self._iterdir(): + yield name + + def scandir(self) -> Iterator[FilesystemEntry]: + for name, entry in self._iterdir(): + entry_path = fsutil.join(self.path, name, alt_separator=self.fs.alt_separator) + yield BtrfsFilesystemEntry(self.fs, entry_path, entry) + + def is_dir(self, follow_symlinks: bool = True) -> bool: + try: + return self._resolve(follow_symlinks=follow_symlinks).entry.is_dir() + except FilesystemError: + return False + + def is_file(self, follow_symlinks: bool = True) -> bool: + try: + return self._resolve(follow_symlinks=follow_symlinks).entry.is_file() + except FilesystemError: + return False + + def is_symlink(self) -> bool: + return self.entry.is_symlink() + + def readlink(self) -> str: + if not self.is_symlink(): + raise NotASymlinkError() + + return self.entry.link + + def stat(self, follow_symlinks: bool = True) -> fsutil.stat_result: + return self._resolve(follow_symlinks=follow_symlinks).lstat() + + def lstat(self) -> fsutil.stat_result: + entry = self.entry + node = self.entry.inode + + # mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime + st_info = st_info = fsutil.stat_result( + [ + entry.mode, + entry.inum, + 0, + node.nlink, + entry.uid, + entry.gid, + entry.size, + # timestamp() returns a float which will fill both the integer and float fields + entry.atime.timestamp(), + entry.mtime.timestamp(), + entry.ctime.timestamp(), + ] + ) + + # Set the nanosecond resolution separately + st_info.st_atime_ns = entry.atime_ns + st_info.st_mtime_ns = entry.mtime_ns + st_info.st_ctime_ns = entry.ctime_ns + + # Btrfs has a birth time, called otime + st_info.st_birthtime = entry.otime.timestamp() + + return st_info diff --git a/dissect/target/helpers/utils.py b/dissect/target/helpers/utils.py index 7c3922526..181a80408 100644 --- a/dissect/target/helpers/utils.py +++ b/dissect/target/helpers/utils.py @@ -4,7 +4,7 @@ from datetime import datetime, timezone, tzinfo from enum import Enum from pathlib import Path -from typing import BinaryIO, Iterator, Union +from typing import BinaryIO, Callable, Iterator, Optional, Union from dissect.util.ts import from_unix @@ -17,7 +17,7 @@ class StrEnum(str, Enum): """Sortable and serializible string-based enum""" -def list_to_frozen_set(function): +def list_to_frozen_set(function: Callable) -> Callable: def wrapper(*args): args = [frozenset(x) if isinstance(x, list) else x for x in args] return function(*args) @@ -25,7 +25,7 @@ def wrapper(*args): return wrapper -def parse_path_uri(path): +def parse_path_uri(path: Path) -> tuple[Optional[str], Optional[str], Optional[str]]: if path is None: return None, None, None parsed_path = urllib.parse.urlparse(str(path)) @@ -33,6 +33,17 @@ def parse_path_uri(path): return parsed_path.scheme, parsed_path.path, parsed_query +def parse_options_string(options: str) -> dict[str, Union[str, bool]]: + result = {} + for opt in options.split(","): + if "=" in opt: + key, _, value = opt.partition("=") + result[key] = value + else: + result[opt] = True + return result + + SLUG_RE = re.compile(r"[/\\ ]") diff --git a/dissect/target/plugins/os/unix/_os.py b/dissect/target/plugins/os/unix/_os.py index f9e4edd85..6040dcb54 100644 --- a/dissect/target/plugins/os/unix/_os.py +++ b/dissect/target/plugins/os/unix/_os.py @@ -11,6 +11,7 @@ from dissect.target.filesystem import Filesystem from dissect.target.helpers.fsutil import TargetPath from dissect.target.helpers.record import UnixUserRecord +from dissect.target.helpers.utils import parse_options_string from dissect.target.plugin import OperatingSystem, OSPlugin, arg, export from dissect.target.target import Target @@ -177,33 +178,41 @@ def _parse_hosts_string(self, paths: Optional[list[str]] = None) -> dict[str, st def _add_mounts(self) -> None: fstab = self.target.fs.path("/etc/fstab") - volumes_to_mount = [v for v in self.target.volumes if v.fs] - - for dev_id, volume_name, _, mount_point in parse_fstab(fstab, self.target.log): - for volume in volumes_to_mount: + for dev_id, volume_name, mount_point, _, options in parse_fstab(fstab, self.target.log): + opts = parse_options_string(options) + subvol = opts.get("subvol", None) + subvolid = opts.get("subvolid", None) + for fs in self.target.filesystems: fs_id = None + fs_subvol = None + fs_subvolid = None + fs_volume_name = fs.volume.name if fs.volume and not isinstance(fs.volume, list) else None last_mount = None if dev_id: - if volume.fs.__fstype__ == "xfs": - fs_id = volume.fs.xfs.uuid - elif volume.fs.__fstype__ == "ext": - fs_id = volume.fs.extfs.uuid - last_mount = volume.fs.extfs.last_mount - elif volume.fs.__fstype__ == "fat": - fs_id = volume.fs.fatfs.volume_id + if fs.__fstype__ == "xfs": + fs_id = fs.xfs.uuid + elif fs.__fstype__ == "ext": + fs_id = fs.extfs.uuid + last_mount = fs.extfs.last_mount + elif fs.__fstype__ == "btrfs": + fs_id = fs.btrfs.uuid + fs_subvol = fs.subvolume.path + fs_subvolid = fs.subvolume.objectid + elif fs.__fstype__ == "fat": + fs_id = fs.fatfs.volume_id # This normalizes fs_id to comply with libblkid generated UUIDs # This is needed because FAT filesystems don't have a real UUID, # but instead a volume_id which is not case-sensitive fs_id = fs_id[:4].upper() + "-" + fs_id[4:].upper() if ( - (fs_id and (fs_id == dev_id)) - or (volume.name and (volume.name == volume_name)) + (fs_id and (fs_id == dev_id and (subvol == fs_subvol or subvolid == fs_subvolid))) + or (fs_volume_name and (fs_volume_name == volume_name)) or (last_mount and (last_mount == mount_point)) ): - self.target.log.debug("Mounting %s at %s", volume, mount_point) - self.target.fs.mount(mount_point, volume.fs) + self.target.log.debug("Mounting %s (%s) at %s", fs, fs.volume, mount_point) + self.target.fs.mount(mount_point, fs) def _parse_os_release(self, glob: Optional[str] = None) -> dict[str, str]: """Parse files containing Unix version information. @@ -283,7 +292,7 @@ def _get_architecture(self, os: str = "unix") -> Optional[str]: def parse_fstab( fstab: TargetPath, log: logging.Logger = log, -) -> Iterator[tuple[Union[uuid.UUID, str], str, str, str]]: +) -> Iterator[tuple[Union[uuid.UUID, str], str, str, str, str]]: """Parse fstab file and return a generator that streams the details of entries, with unsupported FS types and block devices filtered away. """ @@ -310,7 +319,7 @@ def parse_fstab( if len(entry_parts) != 6: continue - dev, mount_point, fs_type, _, _, _ = entry_parts + dev, mount_point, fs_type, options, _, _ = entry_parts if fs_type in SKIP_FS_TYPES: log.warning("Skipped FS type: %s, %s, %s", fs_type, dev, mount_point) @@ -341,4 +350,4 @@ def parse_fstab( except ValueError: pass - yield dev_id, volume_name, fs_type, mount_point + yield dev_id, volume_name, mount_point, fs_type, options diff --git a/dissect/target/target.py b/dissect/target/target.py index 269148b01..b9727c7b7 100644 --- a/dissect/target/target.py +++ b/dissect/target/target.py @@ -155,6 +155,7 @@ def apply(self) -> None: """Resolve all disks, volumes and filesystems and load an operating system on the current ``Target``.""" self.disks.apply() self.volumes.apply() + self.filesystems.apply() self._init_os() self._applied = True @@ -712,18 +713,11 @@ def apply(self) -> None: class VolumeCollection(Collection[volume.Volume]): - def open(self, vol: volume.Volume) -> None: - try: - if not hasattr(vol, "fs") or vol.fs is None: - vol.fs = filesystem.open(vol) - self.target.log.debug("Opened filesystem: %s on %s", vol.fs, vol) - self.target.filesystems.add(vol.fs) - except FilesystemError as e: - self.target.log.warning("Can't identify filesystem: %s", vol) - self.target.log.debug("", exc_info=e) - def apply(self) -> None: todo = self.entries + fs_volumes = [] + lvm_volumes = [] + encrypted_volumes = [] while todo: new_volumes = [] @@ -742,17 +736,17 @@ def apply(self) -> None: vs = volume.open(vol) except Exception: # If opening a volume system fails, there's likely none, so open as a filesystem instead - self.open(vol) + fs_volumes.append(vol) continue if not len(vs.volumes): - self.open(vol) + fs_volumes.append(vol) continue for new_vol in vs.volumes: if new_vol.offset == 0: self.target.log.info("Found volume with offset 0, opening as raw volume instead") - self.open(new_vol) + fs_volumes.append(new_vol) continue new_volumes.append(new_vol) @@ -773,20 +767,30 @@ def apply(self) -> None: todo = new_volumes - # ASDF - getting the correct starting system volume - start_fs = None - start_vol = None - for idx, vol in enumerate(self.entries): - if start_fs is None and (vol.name is None): - start_fs = idx + mv_fs_volumes = [] + for vol in fs_volumes: + try: + if getattr(vol, "fs", None) is None: + if filesystem.is_multi_volume_filesystem(vol): + mv_fs_volumes.append(vol) + else: + vol.fs = filesystem.open(vol) + self.target.log.debug("Opened filesystem: %s on %s", vol.fs, vol) - if start_vol is None and start_fs is not None and (vol.name is not None and vol.fs is None): - start_vol = idx + if getattr(vol, "fs", None) is not None: + self.target.filesystems.add(vol.fs) + except FilesystemError as e: + self.target.log.warning("Can't identify filesystem: %s", vol) + self.target.log.debug("", exc_info=e) - if start_fs is not None and start_vol is not None and (vol.name is not None and vol.fs is None): - rel_vol = idx - start_vol - vol.fs = self.entries[start_fs + rel_vol].fs + for fs in filesystem.open_multi_volume(mv_fs_volumes): + self.target.filesystems.add(fs) + for vol in fs.volume: + vol.fs = fs class FilesystemCollection(Collection[filesystem.Filesystem]): - pass + def apply(self) -> None: + for fs in self.entries: + for subfs in fs.iter_subfs(): + self.add(subfs) diff --git a/dissect/target/tools/mount.py b/dissect/target/tools/mount.py index ff177238f..4b0aaa041 100644 --- a/dissect/target/tools/mount.py +++ b/dissect/target/tools/mount.py @@ -3,6 +3,7 @@ from typing import Union from dissect.target import Target, filesystem +from dissect.target.helpers.utils import parse_options_string from dissect.target.tools.utils import ( catch_sigpipe, configure_generic_arguments, @@ -70,7 +71,7 @@ def main(): vfs.mount(fname, fs) # This is kinda silly because fusepy will convert this back into string arguments - options = _parse_options(args.options) if args.options else {} + options = parse_options_string(args.options) if args.options else {} options["nothreads"] = True options["allow_other"] = True @@ -83,17 +84,6 @@ def main(): parser.exit("FUSE error") -def _parse_options(options: str) -> dict[str, Union[str, bool]]: - result = {} - for opt in options.split(","): - if "=" in opt: - key, _, value = opt.partition("=") - result[key] = value - else: - result[opt] = True - return result - - def _format_options(options: dict[str, Union[str, bool]]) -> str: return ",".join([key if value is True else f"{key}={value}" for key, value in options.items()]) diff --git a/tests/plugins/os/unix/test__os.py b/tests/plugins/os/unix/test__os.py index 33568c60a..432f5224e 100644 --- a/tests/plugins/os/unix/test__os.py +++ b/tests/plugins/os/unix/test__os.py @@ -58,10 +58,10 @@ def test_parse_fstab(): # = 6 expected results assert set(records) == { - (UUID("5d1f1508-069b-4274-9bfa-ae2bf7ffb5e0"), None, "ext4", "/home"), - (UUID("28a25297-9825-4f87-ac41-f9c20cd5db4f"), None, "ext4", "/boot"), - (UUID("af0b9707-0945-499a-a37d-4da23d8dd245"), None, "auto", "/moredata"), - ("F631-BECA", None, "vfat", "/boot/efi"), - (None, "vg--main-lv--var", "auto", "/var"), - (None, "vg--main-lv--data", "auto", "/data"), + (UUID("5d1f1508-069b-4274-9bfa-ae2bf7ffb5e0"), None, "/home", "ext4", "defaults"), + (UUID("28a25297-9825-4f87-ac41-f9c20cd5db4f"), None, "/boot", "ext4", "defaults"), + (UUID("af0b9707-0945-499a-a37d-4da23d8dd245"), None, "/moredata", "auto", "default"), + ("F631-BECA", None, "/boot/efi", "vfat", "defaults,discard,umask=0077"), + (None, "vg--main-lv--var", "/var", "auto", "default"), + (None, "vg--main-lv--data", "/data", "auto", "default"), } diff --git a/tests/test_target.py b/tests/test_target.py index 5c62d7d55..1d0454424 100644 --- a/tests/test_target.py +++ b/tests/test_target.py @@ -171,6 +171,7 @@ def mocked_win_volumes_fs(): mock_good_volume.drive_letter = "W" mock_good_fs = Mock(name="good-fs") + mock_good_fs.iter_subfs.return_value = [] def mock_filesystem_open(volume): if volume == mock_good_volume: From ad13fb011ef7cd70ff54ceab6434c68c640f4794 Mon Sep 17 00:00:00 2001 From: Schamper <1254028+Schamper@users.noreply.github.com> Date: Mon, 20 Nov 2023 11:34:26 +0100 Subject: [PATCH 2/5] Add dissect.btrfs to pyproject.toml --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 1b846aa18..30697d0cf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,6 +47,7 @@ repository = "https://github.com/fox-it/dissect.target" [project.optional-dependencies] full = [ "asn1crypto", + "dissect.btrfs>=1.0.dev,<2.0.dev", "dissect.cim>=3.0.dev,<4.0.dev", "dissect.clfs>=1.0.dev,<2.0.dev", "dissect.esedb>=3.0.dev,<4.0.dev", From 2d52694b0f02e209466342a327994ffa5834aea2 Mon Sep 17 00:00:00 2001 From: Schamper <1254028+Schamper@users.noreply.github.com> Date: Mon, 20 Nov 2023 12:56:02 +0100 Subject: [PATCH 3/5] Fix incorrect merge --- dissect/target/plugins/os/unix/_os.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dissect/target/plugins/os/unix/_os.py b/dissect/target/plugins/os/unix/_os.py index 35fc4282b..b339743ae 100644 --- a/dissect/target/plugins/os/unix/_os.py +++ b/dissect/target/plugins/os/unix/_os.py @@ -190,17 +190,17 @@ def _add_mounts(self) -> None: last_mount = None if dev_id: - if volume.fs.__type__ == "xfs": + if fs.__type__ == "xfs": fs_id = fs.xfs.uuid - elif volume.fs.__type__ == "ext": + elif fs.__type__ == "ext": fs_id = fs.extfs.uuid last_mount = fs.extfs.last_mount elif fs.__type__ == "btrfs": fs_id = fs.btrfs.uuid fs_subvol = fs.subvolume.path fs_subvolid = fs.subvolume.objectid - elif volume.fs.__type__ == "fat": - fs_id = volume.fs.fatfs.volume_id + elif fs.__type__ == "fat": + fs_id = fs.fatfs.volume_id # This normalizes fs_id to comply with libblkid generated UUIDs # This is needed because FAT filesystems don't have a real UUID, # but instead a volume_id which is not case-sensitive From b1b1fa0cbf97fa4282fecdf9c3d605715f03967c Mon Sep 17 00:00:00 2001 From: Schamper <1254028+Schamper@users.noreply.github.com> Date: Mon, 20 Nov 2023 13:22:37 +0100 Subject: [PATCH 4/5] Revert wrong merge --- dissect/target/target.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/dissect/target/target.py b/dissect/target/target.py index e32a5581f..cdc142cc2 100644 --- a/dissect/target/target.py +++ b/dissect/target/target.py @@ -760,12 +760,8 @@ def apply(self) -> None: fs_volumes.append(vol) continue - for new_vol in vs.volumes: - if new_vol.offset == 0: - self.target.log.info("Found volume with offset 0, opening as raw volume instead") - fs_volumes.append(new_vol) - continue - new_volumes.append(new_vol) + self.entries.extend(vs.volumes) + new_volumes.extend(vs.volumes) self.target.log.debug("LVM volumes found: %s", lvm_volumes) self.target.log.debug("Encrypted volumes found: %s", encrypted_volumes) From 190caa6e40f994f353acdeb7a98f9a52aea96a70 Mon Sep 17 00:00:00 2001 From: Schamper <1254028+Schamper@users.noreply.github.com> Date: Mon, 20 Nov 2023 13:35:28 +0100 Subject: [PATCH 5/5] Fix wrong merge again --- dissect/target/target.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dissect/target/target.py b/dissect/target/target.py index cdc142cc2..e18a0fe12 100644 --- a/dissect/target/target.py +++ b/dissect/target/target.py @@ -745,7 +745,7 @@ def apply(self) -> None: if vol.offset == 0 and vol.vs and vol.vs.__type__ == "disk": # We are going to re-open a volume system on itself, bail out self.target.log.info("Found volume with offset 0, opening as raw volume instead") - self.open(vol) + fs_volumes.append(vol) continue try: