dissect.target 3.13.dev26__py3-none-any.whl → 3.14__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- dissect/target/container.py +9 -1
- dissect/target/containers/asdf.py +2 -0
- dissect/target/containers/ewf.py +2 -0
- dissect/target/containers/hdd.py +2 -0
- dissect/target/containers/hds.py +2 -0
- dissect/target/containers/qcow2.py +2 -0
- dissect/target/containers/raw.py +2 -0
- dissect/target/containers/split.py +2 -0
- dissect/target/containers/vdi.py +2 -0
- dissect/target/containers/vhd.py +2 -0
- dissect/target/containers/vhdx.py +2 -0
- dissect/target/containers/vmdk.py +2 -0
- dissect/target/filesystem.py +108 -15
- dissect/target/filesystems/ad1.py +1 -1
- dissect/target/filesystems/btrfs.py +180 -0
- dissect/target/filesystems/cb.py +4 -4
- dissect/target/filesystems/config.py +161 -31
- dissect/target/filesystems/dir.py +1 -1
- dissect/target/filesystems/exfat.py +1 -1
- dissect/target/filesystems/extfs.py +5 -1
- dissect/target/filesystems/fat.py +1 -1
- dissect/target/filesystems/ffs.py +1 -1
- dissect/target/filesystems/itunes.py +1 -1
- dissect/target/filesystems/ntfs.py +1 -1
- dissect/target/filesystems/smb.py +1 -1
- dissect/target/filesystems/squashfs.py +1 -1
- dissect/target/filesystems/tar.py +1 -1
- dissect/target/filesystems/vmfs.py +1 -1
- dissect/target/filesystems/xfs.py +1 -1
- dissect/target/filesystems/zip.py +1 -1
- dissect/target/helpers/cache.py +2 -2
- dissect/target/helpers/configutil.py +283 -83
- dissect/target/helpers/fsutil.py +9 -6
- dissect/target/helpers/hashutil.py +20 -19
- dissect/target/helpers/utils.py +14 -3
- dissect/target/loaders/ad1.py +1 -1
- dissect/target/loaders/asdf.py +1 -1
- dissect/target/loaders/log.py +2 -2
- dissect/target/loaders/smb.py +23 -13
- dissect/target/loaders/targetd.py +12 -2
- dissect/target/loaders/vma.py +1 -1
- dissect/target/loaders/xva.py +1 -1
- dissect/target/plugin.py +14 -2
- dissect/target/plugins/apps/av/sophos.py +1 -2
- dissect/target/plugins/apps/av/symantec.py +3 -4
- dissect/target/plugins/apps/av/trendmicro.py +2 -3
- dissect/target/plugins/{browsers → apps/browser}/chrome.py +6 -3
- dissect/target/plugins/{browsers → apps/browser}/chromium.py +18 -13
- dissect/target/plugins/{browsers → apps/browser}/edge.py +6 -3
- dissect/target/plugins/{browsers → apps/browser}/firefox.py +3 -7
- dissect/target/plugins/{browsers → apps/browser}/iexplore.py +14 -4
- dissect/target/plugins/apps/remoteaccess/teamviewer.py +55 -27
- dissect/target/plugins/apps/ssh/opensshd.py +31 -30
- dissect/target/plugins/apps/{webservers → webserver}/apache.py +1 -1
- dissect/target/plugins/apps/{webservers → webserver}/caddy.py +1 -1
- dissect/target/plugins/apps/{webservers → webserver}/iis.py +1 -1
- dissect/target/plugins/apps/{webservers → webserver}/nginx.py +1 -1
- dissect/target/plugins/child/hyperv.py +1 -2
- dissect/target/plugins/child/vmware_workstation.py +1 -3
- dissect/target/plugins/filesystem/acquire_handles.py +2 -0
- dissect/target/plugins/filesystem/acquire_hash.py +1 -7
- dissect/target/plugins/filesystem/icat.py +5 -5
- dissect/target/plugins/filesystem/ntfs/mft.py +2 -2
- dissect/target/plugins/filesystem/ntfs/mft_timeline.py +2 -2
- dissect/target/plugins/filesystem/ntfs/usnjrnl.py +2 -3
- dissect/target/plugins/filesystem/resolver.py +1 -1
- dissect/target/plugins/filesystem/unix/capability.py +77 -66
- dissect/target/plugins/filesystem/walkfs.py +25 -19
- dissect/target/plugins/filesystem/yara.py +20 -19
- dissect/target/plugins/general/config.py +28 -11
- dissect/target/plugins/os/unix/_os.py +28 -21
- dissect/target/plugins/os/unix/bsd/osx/user.py +1 -3
- dissect/target/plugins/os/unix/cronjobs.py +4 -16
- dissect/target/plugins/os/unix/{linux/esxi → esxi}/_os.py +5 -6
- dissect/target/plugins/os/unix/generic.py +5 -1
- dissect/target/plugins/os/unix/history.py +2 -1
- dissect/target/plugins/os/unix/linux/_os.py +12 -5
- dissect/target/plugins/os/unix/linux/services.py +112 -0
- dissect/target/plugins/os/unix/linux/suse/zypper.py +4 -4
- dissect/target/plugins/os/unix/locale.py +3 -1
- dissect/target/plugins/os/unix/log/journal.py +7 -6
- dissect/target/plugins/os/unix/packagemanager.py +3 -3
- dissect/target/plugins/os/unix/shadow.py +1 -1
- dissect/target/plugins/os/windows/_os.py +2 -1
- dissect/target/plugins/os/windows/amcache.py +9 -10
- dissect/target/plugins/os/windows/catroot.py +2 -2
- dissect/target/plugins/os/windows/cim.py +5 -4
- dissect/target/plugins/os/windows/datetime.py +4 -1
- dissect/target/plugins/os/windows/defender.py +3 -3
- dissect/target/plugins/os/windows/generic.py +10 -11
- dissect/target/plugins/os/windows/lnk.py +6 -6
- dissect/target/plugins/os/windows/log/amcache.py +3 -5
- dissect/target/plugins/os/windows/log/pfro.py +1 -3
- dissect/target/plugins/os/windows/prefetch.py +5 -6
- dissect/target/plugins/os/windows/recyclebin.py +3 -4
- dissect/target/plugins/os/windows/regf/7zip.py +2 -4
- dissect/target/plugins/os/windows/regf/bam.py +1 -2
- dissect/target/plugins/os/windows/regf/cit.py +4 -5
- dissect/target/plugins/os/windows/regf/mru.py +6 -2
- dissect/target/plugins/os/windows/regf/muicache.py +1 -3
- dissect/target/plugins/os/windows/regf/recentfilecache.py +1 -2
- dissect/target/plugins/os/windows/regf/shimcache.py +1 -2
- dissect/target/plugins/os/windows/regf/trusteddocs.py +1 -1
- dissect/target/plugins/os/windows/regf/userassist.py +1 -2
- dissect/target/plugins/os/windows/services.py +2 -4
- dissect/target/plugins/os/windows/sru.py +4 -4
- dissect/target/plugins/os/windows/startupinfo.py +5 -6
- dissect/target/plugins/os/windows/syscache.py +2 -3
- dissect/target/target.py +65 -32
- dissect/target/tools/info.py +2 -1
- dissect/target/tools/mount.py +2 -12
- dissect/target/tools/shell.py +3 -2
- dissect/target/volume.py +10 -9
- dissect/target/volumes/bde.py +1 -1
- dissect/target/volumes/ddf.py +2 -0
- dissect/target/volumes/disk.py +2 -0
- dissect/target/volumes/luks.py +1 -1
- dissect/target/volumes/lvm.py +2 -0
- dissect/target/volumes/md.py +2 -0
- dissect/target/volumes/vmfs.py +2 -0
- {dissect.target-3.13.dev26.dist-info → dissect.target-3.14.dist-info}/METADATA +2 -1
- {dissect.target-3.13.dev26.dist-info → dissect.target-3.14.dist-info}/RECORD +137 -136
- {dissect.target-3.13.dev26.dist-info → dissect.target-3.14.dist-info}/WHEEL +1 -1
- dissect/target/plugins/os/unix/services.py +0 -151
- /dissect/target/plugins/apps/{containers → browser}/__init__.py +0 -0
- /dissect/target/plugins/{browsers → apps/browser}/browser.py +0 -0
- /dissect/target/plugins/apps/{vpns → container}/__init__.py +0 -0
- /dissect/target/plugins/apps/{containers → container}/docker.py +0 -0
- /dissect/target/plugins/apps/{webservers → vpn}/__init__.py +0 -0
- /dissect/target/plugins/apps/{vpns → vpn}/openvpn.py +0 -0
- /dissect/target/plugins/apps/{vpns → vpn}/wireguard.py +0 -0
- /dissect/target/plugins/{browsers → apps/webserver}/__init__.py +0 -0
- /dissect/target/plugins/apps/{webservers/webservers.py → webserver/webserver.py} +0 -0
- /dissect/target/plugins/os/unix/{linux/esxi → esxi}/__init__.py +0 -0
- {dissect.target-3.13.dev26.dist-info → dissect.target-3.14.dist-info}/COPYRIGHT +0 -0
- {dissect.target-3.13.dev26.dist-info → dissect.target-3.14.dist-info}/LICENSE +0 -0
- {dissect.target-3.13.dev26.dist-info → dissect.target-3.14.dist-info}/entry_points.txt +0 -0
- {dissect.target-3.13.dev26.dist-info → dissect.target-3.14.dist-info}/top_level.txt +0 -0
dissect/target/container.py
CHANGED
@@ -34,6 +34,11 @@ class Container(io.IOBase):
|
|
34
34
|
vs: An optional shorthand to set the underlying volume system, usually set later.
|
35
35
|
"""
|
36
36
|
|
37
|
+
# Due to lazy importing we generally can't use isinstance(), so we add a short identifying string to each class
|
38
|
+
# This has the added benefit of having a readily available "pretty name" for each implementation
|
39
|
+
__type__: str = None
|
40
|
+
"""A short string identifying the type of container."""
|
41
|
+
|
37
42
|
def __init__(self, fh: Union[BinaryIO, Path], size: int, vs: VolumeSystem = None):
|
38
43
|
self.fh = fh
|
39
44
|
self.size = size
|
@@ -41,7 +46,10 @@ class Container(io.IOBase):
|
|
41
46
|
# Shorthand access to vs
|
42
47
|
self.vs = vs
|
43
48
|
|
44
|
-
|
49
|
+
if self.__type__ is None:
|
50
|
+
raise NotImplementedError(f"{self.__class__.__name__} must define __type__")
|
51
|
+
|
52
|
+
def __repr__(self) -> str:
|
45
53
|
return f"<{self.__class__.__name__} size={self.size} vs={self.vs}>"
|
46
54
|
|
47
55
|
@classmethod
|
dissect/target/containers/ewf.py
CHANGED
@@ -12,6 +12,8 @@ from dissect.target.container import Container
|
|
12
12
|
class EwfContainer(Container):
|
13
13
|
"""Expert Witness Disk Image Format"""
|
14
14
|
|
15
|
+
__type__ = "ewf"
|
16
|
+
|
15
17
|
def __init__(self, fh: Union[list, BinaryIO, Path], *args, **kwargs):
|
16
18
|
fhs = [fh] if not isinstance(fh, list) else fh
|
17
19
|
if hasattr(fhs[0], "read"):
|
dissect/target/containers/hdd.py
CHANGED
dissect/target/containers/hds.py
CHANGED
dissect/target/containers/raw.py
CHANGED
dissect/target/containers/vdi.py
CHANGED
dissect/target/containers/vhd.py
CHANGED
@@ -10,6 +10,8 @@ from dissect.target.container import Container
|
|
10
10
|
class VmdkContainer(Container):
|
11
11
|
"""VMWare hard disks"""
|
12
12
|
|
13
|
+
__type__ = "vmdk"
|
14
|
+
|
13
15
|
def __init__(self, fh: Union[BinaryIO, Path], *args, **kwargs):
|
14
16
|
self.vmdk = vmdk.VMDK(fh)
|
15
17
|
super().__init__(fh, self.vmdk.size, *args, **kwargs)
|
dissect/target/filesystem.py
CHANGED
@@ -5,6 +5,7 @@ import io
|
|
5
5
|
import logging
|
6
6
|
import os
|
7
7
|
import stat
|
8
|
+
import warnings
|
8
9
|
from collections import defaultdict
|
9
10
|
from typing import (
|
10
11
|
TYPE_CHECKING,
|
@@ -40,12 +41,16 @@ log = logging.getLogger(__name__)
|
|
40
41
|
class Filesystem:
|
41
42
|
"""Base class for filesystems."""
|
42
43
|
|
43
|
-
|
44
|
-
|
44
|
+
# Due to lazy importing we generally can't use isinstance(), so we add a short identifying string to each class
|
45
|
+
# This has the added benefit of having a readily available "pretty name" for each implementation
|
46
|
+
__type__: str = None
|
47
|
+
"""A short string identifying the type of filesystem."""
|
48
|
+
__multi_volume__: bool = False
|
49
|
+
"""Whether this filesystem supports multiple volumes (disks)."""
|
45
50
|
|
46
51
|
def __init__(
|
47
52
|
self,
|
48
|
-
volume: Optional[BinaryIO] = None,
|
53
|
+
volume: Optional[Union[BinaryIO, list[BinaryIO]]] = None,
|
49
54
|
alt_separator: str = "",
|
50
55
|
case_sensitive: bool = True,
|
51
56
|
) -> None:
|
@@ -53,23 +58,33 @@ class Filesystem:
|
|
53
58
|
|
54
59
|
Args:
|
55
60
|
volume: A volume or other file-like object associated with the filesystem.
|
56
|
-
case_sensitive: Defines if the paths in the
|
61
|
+
case_sensitive: Defines if the paths in the filesystem are case sensitive or not.
|
57
62
|
alt_separator: The alternative separator used to distingish between directories in a path.
|
58
63
|
|
59
64
|
Raises:
|
60
|
-
NotImplementedError: When the internal ``
|
65
|
+
NotImplementedError: When the internal ``__type__`` of the class is not defined.
|
61
66
|
"""
|
62
67
|
self.volume = volume
|
63
68
|
self.case_sensitive = case_sensitive
|
64
69
|
self.alt_separator = alt_separator
|
65
|
-
|
66
|
-
|
70
|
+
|
71
|
+
if self.__type__ is None:
|
72
|
+
raise NotImplementedError(f"{self.__class__.__name__} must define __type__")
|
67
73
|
|
68
74
|
def __repr__(self) -> str:
|
69
75
|
return f"<{self.__class__.__name__}>"
|
70
76
|
|
77
|
+
@classmethod
|
78
|
+
@property
|
79
|
+
def __fstype__(cls) -> str:
|
80
|
+
warnings.warn(
|
81
|
+
"The __fstype__ attribute is deprecated and will be removed in dissect.target 3.15. Use __type__ instead",
|
82
|
+
category=DeprecationWarning,
|
83
|
+
)
|
84
|
+
return cls.__type__
|
85
|
+
|
71
86
|
def path(self, *args) -> fsutil.TargetPath:
|
72
|
-
"""
|
87
|
+
"""Instantiate a new path-like object on this filesystem."""
|
73
88
|
return fsutil.TargetPath(self, *args)
|
74
89
|
|
75
90
|
@classmethod
|
@@ -91,7 +106,7 @@ class Filesystem:
|
|
91
106
|
except NotImplementedError:
|
92
107
|
raise
|
93
108
|
except Exception as e:
|
94
|
-
log.warning("Failed to detect %s filesystem", cls.
|
109
|
+
log.warning("Failed to detect %s filesystem", cls.__type__)
|
95
110
|
log.debug("", exc_info=e)
|
96
111
|
finally:
|
97
112
|
fh.seek(offset)
|
@@ -112,6 +127,52 @@ class Filesystem:
|
|
112
127
|
"""
|
113
128
|
raise NotImplementedError()
|
114
129
|
|
130
|
+
@classmethod
|
131
|
+
def detect_id(cls, fh: BinaryIO) -> Optional[bytes]:
|
132
|
+
"""Return a filesystem set identifier.
|
133
|
+
|
134
|
+
Only used in filesystems that support multiple volumes (disks) to find all volumes
|
135
|
+
belonging to a single filesystem.
|
136
|
+
|
137
|
+
Args:
|
138
|
+
fh: A file-like object, usually a disk or partition.
|
139
|
+
"""
|
140
|
+
if not cls.__multi_volume__:
|
141
|
+
return None
|
142
|
+
|
143
|
+
offset = fh.tell()
|
144
|
+
try:
|
145
|
+
fh.seek(0)
|
146
|
+
return cls._detect_id(fh)
|
147
|
+
except NotImplementedError:
|
148
|
+
raise
|
149
|
+
except Exception as e:
|
150
|
+
log.warning("Failed to detect ID on %s filesystem", cls.__type__)
|
151
|
+
log.debug("", exc_info=e)
|
152
|
+
finally:
|
153
|
+
fh.seek(offset)
|
154
|
+
|
155
|
+
return None
|
156
|
+
|
157
|
+
@staticmethod
|
158
|
+
def _detect_id(fh: BinaryIO) -> Optional[bytes]:
|
159
|
+
"""Return a filesystem set identifier.
|
160
|
+
|
161
|
+
This method should be implemented by subclasses of filesystems that support multiple volumes (disks).
|
162
|
+
The position of ``fh`` is guaranteed to be ``0``.
|
163
|
+
|
164
|
+
Args:
|
165
|
+
fh: A file-like object, usually a disk or partition.
|
166
|
+
|
167
|
+
Returns:
|
168
|
+
An identifier that can be used to combine the given ``fh`` with others beloning to the same set.
|
169
|
+
"""
|
170
|
+
raise NotImplementedError()
|
171
|
+
|
172
|
+
def iter_subfs(self) -> Iterator[Filesystem]:
|
173
|
+
"""Yield possible sub-filesystems."""
|
174
|
+
yield from ()
|
175
|
+
|
115
176
|
def get(self, path: str) -> FilesystemEntry:
|
116
177
|
"""Retrieve a :class:`FilesystemEntry` from the filesystem.
|
117
178
|
|
@@ -1028,7 +1089,7 @@ class VirtualSymlink(FilesystemEntry):
|
|
1028
1089
|
|
1029
1090
|
|
1030
1091
|
class VirtualFilesystem(Filesystem):
|
1031
|
-
|
1092
|
+
__type__ = "virtual"
|
1032
1093
|
|
1033
1094
|
def __init__(self, **kwargs):
|
1034
1095
|
super().__init__(None, **kwargs)
|
@@ -1184,7 +1245,7 @@ class VirtualFilesystem(Filesystem):
|
|
1184
1245
|
|
1185
1246
|
|
1186
1247
|
class RootFilesystem(Filesystem):
|
1187
|
-
|
1248
|
+
__type__ = "root"
|
1188
1249
|
|
1189
1250
|
def __init__(self, target: Target):
|
1190
1251
|
self.target = target
|
@@ -1448,6 +1509,18 @@ def register(module: str, class_name: str, internal: bool = True) -> None:
|
|
1448
1509
|
FILESYSTEMS.append(getattr(import_lazy(module), class_name))
|
1449
1510
|
|
1450
1511
|
|
1512
|
+
def is_multi_volume_filesystem(fh: BinaryIO) -> bool:
|
1513
|
+
for filesystem in FILESYSTEMS:
|
1514
|
+
try:
|
1515
|
+
if filesystem.__multi_volume__ and filesystem.detect(fh):
|
1516
|
+
return True
|
1517
|
+
except ImportError as e:
|
1518
|
+
log.info("Failed to import %s", filesystem)
|
1519
|
+
log.debug("", exc_info=e)
|
1520
|
+
|
1521
|
+
return False
|
1522
|
+
|
1523
|
+
|
1451
1524
|
def open(fh: BinaryIO, *args, **kwargs) -> Filesystem:
|
1452
1525
|
offset = fh.tell()
|
1453
1526
|
fh.seek(0)
|
@@ -1456,10 +1529,7 @@ def open(fh: BinaryIO, *args, **kwargs) -> Filesystem:
|
|
1456
1529
|
for filesystem in FILESYSTEMS:
|
1457
1530
|
try:
|
1458
1531
|
if filesystem.detect(fh):
|
1459
|
-
|
1460
|
-
instance.volume = fh
|
1461
|
-
|
1462
|
-
return instance
|
1532
|
+
return filesystem(fh, *args, **kwargs)
|
1463
1533
|
except ImportError as e:
|
1464
1534
|
log.info("Failed to import %s", filesystem)
|
1465
1535
|
log.debug("", exc_info=e)
|
@@ -1469,12 +1539,35 @@ def open(fh: BinaryIO, *args, **kwargs) -> Filesystem:
|
|
1469
1539
|
raise FilesystemError(f"Failed to open filesystem for {fh}")
|
1470
1540
|
|
1471
1541
|
|
1542
|
+
def open_multi_volume(fhs: list[BinaryIO], *args, **kwargs) -> Filesystem:
|
1543
|
+
for filesystem in FILESYSTEMS:
|
1544
|
+
try:
|
1545
|
+
if not filesystem.__multi_volume__:
|
1546
|
+
continue
|
1547
|
+
|
1548
|
+
volumes = defaultdict(list)
|
1549
|
+
for fh in fhs:
|
1550
|
+
if not filesystem.detect(fh):
|
1551
|
+
continue
|
1552
|
+
|
1553
|
+
identifier = filesystem.detect_id(fh)
|
1554
|
+
volumes[identifier].append(fh)
|
1555
|
+
|
1556
|
+
for vols in volumes.values():
|
1557
|
+
yield filesystem(vols, *args, **kwargs)
|
1558
|
+
|
1559
|
+
except ImportError as e:
|
1560
|
+
log.info("Failed to import %s", filesystem)
|
1561
|
+
log.debug("", exc_info=e)
|
1562
|
+
|
1563
|
+
|
1472
1564
|
register("ntfs", "NtfsFilesystem")
|
1473
1565
|
register("extfs", "ExtFilesystem")
|
1474
1566
|
register("xfs", "XfsFilesystem")
|
1475
1567
|
register("fat", "FatFilesystem")
|
1476
1568
|
register("ffs", "FfsFilesystem")
|
1477
1569
|
register("vmfs", "VmfsFilesystem")
|
1570
|
+
register("btrfs", "BtrfsFilesystem")
|
1478
1571
|
register("exfat", "ExfatFilesystem")
|
1479
1572
|
register("squashfs", "SquashFSFilesystem")
|
1480
1573
|
register("zip", "ZipFilesystem")
|
@@ -0,0 +1,180 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from typing import BinaryIO, Iterator, Optional, Union
|
4
|
+
|
5
|
+
import dissect.btrfs as btrfs
|
6
|
+
from dissect.btrfs.c_btrfs import c_btrfs
|
7
|
+
|
8
|
+
from dissect.target.exceptions import (
|
9
|
+
FileNotFoundError,
|
10
|
+
FilesystemError,
|
11
|
+
IsADirectoryError,
|
12
|
+
NotADirectoryError,
|
13
|
+
NotASymlinkError,
|
14
|
+
)
|
15
|
+
from dissect.target.filesystem import Filesystem, FilesystemEntry
|
16
|
+
from dissect.target.helpers import fsutil
|
17
|
+
|
18
|
+
|
19
|
+
class BtrfsFilesystem(Filesystem):
|
20
|
+
__type__ = "btrfs"
|
21
|
+
__multi_volume__ = True
|
22
|
+
|
23
|
+
def __init__(self, fh: Union[BinaryIO, list[BinaryIO]], *args, **kwargs):
|
24
|
+
super().__init__(fh, *args, **kwargs)
|
25
|
+
self.btrfs = btrfs.Btrfs(fh)
|
26
|
+
self.subfs = self.open_subvolume()
|
27
|
+
self.subvolume = self.subfs.subvolume
|
28
|
+
|
29
|
+
@staticmethod
|
30
|
+
def _detect(fh: BinaryIO) -> bool:
|
31
|
+
fh.seek(c_btrfs.BTRFS_SUPER_INFO_OFFSET)
|
32
|
+
block = fh.read(4096)
|
33
|
+
magic = int.from_bytes(block[64:72], "little")
|
34
|
+
|
35
|
+
return magic == c_btrfs.BTRFS_MAGIC
|
36
|
+
|
37
|
+
@staticmethod
|
38
|
+
def _detect_id(fh: BinaryIO) -> Optional[bytes]:
|
39
|
+
# First field is csum, followed by fsid
|
40
|
+
fh.seek(c_btrfs.BTRFS_SUPER_INFO_OFFSET + c_btrfs.BTRFS_CSUM_SIZE)
|
41
|
+
return fh.read(c_btrfs.BTRFS_FSID_SIZE)
|
42
|
+
|
43
|
+
def iter_subfs(self) -> Iterator[BtrfsSubvolumeFilesystem]:
|
44
|
+
for subvol in self.btrfs.subvolumes():
|
45
|
+
if subvol.objectid == self.subfs.subvolume.objectid:
|
46
|
+
# Skip the default volume as it's already opened by the main filesystem
|
47
|
+
continue
|
48
|
+
yield self.open_subvolume(subvolid=subvol.objectid)
|
49
|
+
|
50
|
+
def open_subvolume(self, subvol: Optional[str] = None, subvolid: Optional[int] = None) -> BtrfsSubvolumeFilesystem:
|
51
|
+
return BtrfsSubvolumeFilesystem(self, subvol, subvolid)
|
52
|
+
|
53
|
+
def get(self, path: str) -> FilesystemEntry:
|
54
|
+
return self.subfs.get(path)
|
55
|
+
|
56
|
+
|
57
|
+
class BtrfsSubvolumeFilesystem(Filesystem):
|
58
|
+
__type__ = "btrfs"
|
59
|
+
|
60
|
+
def __init__(self, fs: BtrfsFilesystem, subvol: Optional[str] = None, subvolid: Optional[int] = None):
|
61
|
+
super().__init__(fs.volume, alt_separator=fs.alt_separator, case_sensitive=fs.case_sensitive)
|
62
|
+
if subvol is not None and subvolid is not None:
|
63
|
+
raise ValueError("Only one of subvol or subvolid is allowed")
|
64
|
+
|
65
|
+
self.fs = fs
|
66
|
+
self.btrfs = fs.btrfs
|
67
|
+
if subvol:
|
68
|
+
self.subvolume = self.btrfs.find_subvolume(subvol)
|
69
|
+
elif subvolid:
|
70
|
+
self.subvolume = self.btrfs.open_subvolume(subvolid)
|
71
|
+
else:
|
72
|
+
self.subvolume = self.btrfs.default_subvolume
|
73
|
+
|
74
|
+
def get(self, path: str) -> FilesystemEntry:
|
75
|
+
return BtrfsFilesystemEntry(self, path, self._get_node(path))
|
76
|
+
|
77
|
+
def _get_node(self, path: str, node: Optional[btrfs.INode] = None) -> btrfs.INode:
|
78
|
+
try:
|
79
|
+
return self.subvolume.get(path, node)
|
80
|
+
except btrfs.FileNotFoundError as e:
|
81
|
+
raise FileNotFoundError(path, cause=e)
|
82
|
+
except btrfs.NotADirectoryError as e:
|
83
|
+
raise NotADirectoryError(path, cause=e)
|
84
|
+
except btrfs.NotASymlinkError as e:
|
85
|
+
raise NotASymlinkError(path, cause=e)
|
86
|
+
except btrfs.Error as e:
|
87
|
+
raise FileNotFoundError(path, cause=e)
|
88
|
+
|
89
|
+
|
90
|
+
class BtrfsFilesystemEntry(FilesystemEntry):
|
91
|
+
fs: BtrfsFilesystem
|
92
|
+
entry: btrfs.INode
|
93
|
+
|
94
|
+
def get(self, path: str) -> FilesystemEntry:
|
95
|
+
entry_path = fsutil.join(self.path, path, alt_separator=self.fs.alt_separator)
|
96
|
+
entry = self.fs._get_node(path, self.entry)
|
97
|
+
return BtrfsFilesystemEntry(self.fs, entry_path, entry)
|
98
|
+
|
99
|
+
def open(self) -> BinaryIO:
|
100
|
+
if self.is_dir():
|
101
|
+
raise IsADirectoryError(self.path)
|
102
|
+
return self._resolve().entry.open()
|
103
|
+
|
104
|
+
def _iterdir(self) -> Iterator[btrfs.INode]:
|
105
|
+
if not self.is_dir():
|
106
|
+
raise NotADirectoryError(self.path)
|
107
|
+
|
108
|
+
if self.is_symlink():
|
109
|
+
for entry in self.readlink_ext().iterdir():
|
110
|
+
yield entry
|
111
|
+
else:
|
112
|
+
for name, entry in self.entry.iterdir():
|
113
|
+
if name in (".", ".."):
|
114
|
+
continue
|
115
|
+
|
116
|
+
yield name, entry
|
117
|
+
|
118
|
+
def iterdir(self) -> Iterator[str]:
|
119
|
+
for name, _ in self._iterdir():
|
120
|
+
yield name
|
121
|
+
|
122
|
+
def scandir(self) -> Iterator[FilesystemEntry]:
|
123
|
+
for name, entry in self._iterdir():
|
124
|
+
entry_path = fsutil.join(self.path, name, alt_separator=self.fs.alt_separator)
|
125
|
+
yield BtrfsFilesystemEntry(self.fs, entry_path, entry)
|
126
|
+
|
127
|
+
def is_dir(self, follow_symlinks: bool = True) -> bool:
|
128
|
+
try:
|
129
|
+
return self._resolve(follow_symlinks=follow_symlinks).entry.is_dir()
|
130
|
+
except FilesystemError:
|
131
|
+
return False
|
132
|
+
|
133
|
+
def is_file(self, follow_symlinks: bool = True) -> bool:
|
134
|
+
try:
|
135
|
+
return self._resolve(follow_symlinks=follow_symlinks).entry.is_file()
|
136
|
+
except FilesystemError:
|
137
|
+
return False
|
138
|
+
|
139
|
+
def is_symlink(self) -> bool:
|
140
|
+
return self.entry.is_symlink()
|
141
|
+
|
142
|
+
def readlink(self) -> str:
|
143
|
+
if not self.is_symlink():
|
144
|
+
raise NotASymlinkError()
|
145
|
+
|
146
|
+
return self.entry.link
|
147
|
+
|
148
|
+
def stat(self, follow_symlinks: bool = True) -> fsutil.stat_result:
|
149
|
+
return self._resolve(follow_symlinks=follow_symlinks).lstat()
|
150
|
+
|
151
|
+
def lstat(self) -> fsutil.stat_result:
|
152
|
+
entry = self.entry
|
153
|
+
node = self.entry.inode
|
154
|
+
|
155
|
+
# mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime
|
156
|
+
st_info = st_info = fsutil.stat_result(
|
157
|
+
[
|
158
|
+
entry.mode,
|
159
|
+
entry.inum,
|
160
|
+
0,
|
161
|
+
node.nlink,
|
162
|
+
entry.uid,
|
163
|
+
entry.gid,
|
164
|
+
entry.size,
|
165
|
+
# timestamp() returns a float which will fill both the integer and float fields
|
166
|
+
entry.atime.timestamp(),
|
167
|
+
entry.mtime.timestamp(),
|
168
|
+
entry.ctime.timestamp(),
|
169
|
+
]
|
170
|
+
)
|
171
|
+
|
172
|
+
# Set the nanosecond resolution separately
|
173
|
+
st_info.st_atime_ns = entry.atime_ns
|
174
|
+
st_info.st_mtime_ns = entry.mtime_ns
|
175
|
+
st_info.st_ctime_ns = entry.ctime_ns
|
176
|
+
|
177
|
+
# Btrfs has a birth time, called otime
|
178
|
+
st_info.st_birthtime = entry.otime.timestamp()
|
179
|
+
|
180
|
+
return st_info
|
dissect/target/filesystems/cb.py
CHANGED
@@ -23,7 +23,7 @@ class OS(IntEnum):
|
|
23
23
|
|
24
24
|
|
25
25
|
class CbFilesystem(Filesystem):
|
26
|
-
|
26
|
+
__type__ = "cb"
|
27
27
|
|
28
28
|
def __init__(self, session: LiveResponseSession, prefix: str, *args, **kwargs):
|
29
29
|
self.session = session
|
@@ -97,14 +97,14 @@ class CbFilesystemEntry(FilesystemEntry):
|
|
97
97
|
if not self.is_dir():
|
98
98
|
raise NotADirectoryError(f"'{self.path}' is not a directory")
|
99
99
|
|
100
|
-
|
101
|
-
for entry in self.fs.session.list_directory(self.cbpath +
|
100
|
+
separator = self.fs.alt_separator or "/"
|
101
|
+
for entry in self.fs.session.list_directory(self.cbpath + separator):
|
102
102
|
filename = entry["filename"]
|
103
103
|
if filename in (".", ".."):
|
104
104
|
continue
|
105
105
|
|
106
106
|
path = fsutil.join(self.path, filename, alt_separator=self.fs.alt_separator)
|
107
|
-
cbpath =
|
107
|
+
cbpath = separator.join([self.cbpath, filename])
|
108
108
|
yield CbFilesystemEntry(self.fs, path, entry, cbpath)
|
109
109
|
|
110
110
|
def is_dir(self, follow_symlinks: bool = True) -> bool:
|