dissect.target 3.17.dev19__py3-none-any.whl → 3.17.dev20__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dissect/target/filesystem.py +192 -67
- dissect/target/loaders/vb.py +2 -2
- dissect/target/plugins/filesystem/walkfs.py +2 -2
- dissect/target/plugins/os/unix/esxi/_os.py +2 -2
- dissect/target/plugins/os/unix/linux/debian/vyos/_os.py +1 -1
- dissect/target/plugins/os/unix/linux/fortios/_os.py +3 -3
- dissect/target/tools/shell.py +2 -2
- {dissect.target-3.17.dev19.dist-info → dissect.target-3.17.dev20.dist-info}/METADATA +1 -1
- {dissect.target-3.17.dev19.dist-info → dissect.target-3.17.dev20.dist-info}/RECORD +14 -14
- {dissect.target-3.17.dev19.dist-info → dissect.target-3.17.dev20.dist-info}/COPYRIGHT +0 -0
- {dissect.target-3.17.dev19.dist-info → dissect.target-3.17.dev20.dist-info}/LICENSE +0 -0
- {dissect.target-3.17.dev19.dist-info → dissect.target-3.17.dev20.dist-info}/WHEEL +0 -0
- {dissect.target-3.17.dev19.dist-info → dissect.target-3.17.dev20.dist-info}/entry_points.txt +0 -0
- {dissect.target-3.17.dev19.dist-info → dissect.target-3.17.dev20.dist-info}/top_level.txt +0 -0
dissect/target/filesystem.py
CHANGED
@@ -525,21 +525,21 @@ class FilesystemEntry:
|
|
525
525
|
follow_symlinks: Whether to resolve the entry if it is a symbolic link.
|
526
526
|
|
527
527
|
Returns:
|
528
|
-
The resolved symbolic link if ``follow_symlinks`` is ``True`` and the
|
529
|
-
symbolic link or else the
|
528
|
+
The resolved symbolic link if ``follow_symlinks`` is ``True`` and the :class:`FilesystemEntry` is a
|
529
|
+
symbolic link or else the :class:`FilesystemEntry` itself.
|
530
530
|
"""
|
531
531
|
if follow_symlinks and self.is_symlink():
|
532
532
|
return self.readlink_ext()
|
533
533
|
return self
|
534
534
|
|
535
535
|
def get(self, path: str) -> FilesystemEntry:
|
536
|
-
"""Retrieve a FilesystemEntry relative to this entry.
|
536
|
+
"""Retrieve a :class:`FilesystemEntry` relative to this entry.
|
537
537
|
|
538
538
|
Args:
|
539
539
|
path: The path relative to this filesystem entry.
|
540
540
|
|
541
541
|
Returns:
|
542
|
-
A relative FilesystemEntry
|
542
|
+
A relative :class:`FilesystemEntry`.
|
543
543
|
"""
|
544
544
|
raise NotImplementedError()
|
545
545
|
|
@@ -560,10 +560,10 @@ class FilesystemEntry:
|
|
560
560
|
raise NotImplementedError()
|
561
561
|
|
562
562
|
def scandir(self) -> Iterator[FilesystemEntry]:
|
563
|
-
"""Iterate over the contents of a directory,
|
563
|
+
"""Iterate over the contents of a directory, yields :class:`FilesystemEntry`.
|
564
564
|
|
565
565
|
Returns:
|
566
|
-
An iterator of
|
566
|
+
An iterator of :class:`FilesystemEntry`.
|
567
567
|
"""
|
568
568
|
raise NotImplementedError()
|
569
569
|
|
@@ -576,10 +576,10 @@ class FilesystemEntry:
|
|
576
576
|
return list(self.iterdir())
|
577
577
|
|
578
578
|
def listdir_ext(self) -> list[FilesystemEntry]:
|
579
|
-
"""List the contents of a directory as FilesystemEntry
|
579
|
+
"""List the contents of a directory as a list of :class:`FilesystemEntry`.
|
580
580
|
|
581
581
|
Returns:
|
582
|
-
A list of FilesystemEntry
|
582
|
+
A list of :class:`FilesystemEntry`.
|
583
583
|
"""
|
584
584
|
return list(self.scandir())
|
585
585
|
|
@@ -614,7 +614,7 @@ class FilesystemEntry:
|
|
614
614
|
onerror: Optional[Callable] = None,
|
615
615
|
followlinks: bool = False,
|
616
616
|
) -> Iterator[FilesystemEntry]:
|
617
|
-
"""Walk a directory and show its contents as FilesystemEntry
|
617
|
+
"""Walk a directory and show its contents as :class:`FilesystemEntry`.
|
618
618
|
|
619
619
|
It walks across all the files inside the entry recursively.
|
620
620
|
|
@@ -629,11 +629,11 @@ class FilesystemEntry:
|
|
629
629
|
followlinks: ``True`` if we want to follow any symbolic link
|
630
630
|
|
631
631
|
Returns:
|
632
|
-
An iterator of
|
632
|
+
An iterator of :class:`FilesystemEntry`.
|
633
633
|
"""
|
634
634
|
yield from fsutil.walk_ext(self, topdown, onerror, followlinks)
|
635
635
|
|
636
|
-
def glob(self, pattern) -> Iterator[str]:
|
636
|
+
def glob(self, pattern: str) -> Iterator[str]:
|
637
637
|
"""Iterate over this directory part of ``patern``, returning entries matching ``pattern`` as strings.
|
638
638
|
|
639
639
|
Args:
|
@@ -645,21 +645,23 @@ class FilesystemEntry:
|
|
645
645
|
for entry in self.glob_ext(pattern):
|
646
646
|
yield entry.path
|
647
647
|
|
648
|
-
def glob_ext(self, pattern) -> Iterator[FilesystemEntry]:
|
649
|
-
"""Iterate over the directory part of ``pattern``, returning entries matching
|
648
|
+
def glob_ext(self, pattern: str) -> Iterator[FilesystemEntry]:
|
649
|
+
"""Iterate over the directory part of ``pattern``, returning entries matching
|
650
|
+
``pattern`` as :class:`FilesysmteEntry`.
|
650
651
|
|
651
652
|
Args:
|
652
653
|
pattern: The pattern to glob for.
|
653
654
|
|
654
655
|
Returns:
|
655
|
-
An iterator of FilesystemEntry
|
656
|
+
An iterator of :class:`FilesystemEntry` that match the pattern.
|
656
657
|
"""
|
657
658
|
yield from fsutil.glob_ext(self, pattern)
|
658
659
|
|
659
660
|
def exists(self, path: str) -> bool:
|
660
661
|
"""Determines whether a ``path``, relative to this entry, exists.
|
661
662
|
|
662
|
-
If the `path` is a symbolic link, it will attempt to resolve it to find
|
663
|
+
If the `path` is a symbolic link, it will attempt to resolve it to find
|
664
|
+
the :class:`FilesystemEntry` it points to.
|
663
665
|
|
664
666
|
Args:
|
665
667
|
path: The path relative to this entry.
|
@@ -737,7 +739,7 @@ class FilesystemEntry:
|
|
737
739
|
raise NotImplementedError()
|
738
740
|
|
739
741
|
def readlink_ext(self) -> FilesystemEntry:
|
740
|
-
"""Read the link where this entry points to, return the resulting path as FilesystemEntry
|
742
|
+
"""Read the link where this entry points to, return the resulting path as :class:`FilesystemEntry`.
|
741
743
|
|
742
744
|
If it is a symlink and returns the string that corresponds to that path.
|
743
745
|
This means it follows the path a link points to, it tries to do it recursively.
|
@@ -860,7 +862,7 @@ class VirtualDirectory(FilesystemEntry):
|
|
860
862
|
raise TypeError(f"lattr is not allowed on VirtualDirectory: {self.path}")
|
861
863
|
|
862
864
|
def add(self, name: str, entry: FilesystemEntry) -> None:
|
863
|
-
"""Add an entry to this VirtualDirectory
|
865
|
+
"""Add an entry to this :class:`VirtualDirectory`."""
|
864
866
|
if not self.fs.case_sensitive:
|
865
867
|
name = name.lower()
|
866
868
|
|
@@ -1214,7 +1216,7 @@ class VirtualFilesystem(Filesystem):
|
|
1214
1216
|
self.map_file_entry(vfspath, VirtualFile(self, file_path, fh))
|
1215
1217
|
|
1216
1218
|
def map_file_entry(self, vfspath: str, entry: FilesystemEntry) -> None:
|
1217
|
-
"""Map a FilesystemEntry into the VFS.
|
1219
|
+
"""Map a :class:`FilesystemEntry` into the VFS.
|
1218
1220
|
|
1219
1221
|
Any missing subdirectories up to, but not including, the last part of
|
1220
1222
|
``vfspath`` will be created.
|
@@ -1271,7 +1273,7 @@ class VirtualFilesystem(Filesystem):
|
|
1271
1273
|
return self.map_dir_from_tar(vfspath.lstrip("/"), tar_file, map_single_file=True)
|
1272
1274
|
|
1273
1275
|
def link(self, src: str, dst: str) -> None:
|
1274
|
-
"""Hard link a FilesystemEntry to another location.
|
1276
|
+
"""Hard link a :class:`FilesystemEntry` to another location.
|
1275
1277
|
|
1276
1278
|
Args:
|
1277
1279
|
src: The path to the target of the link.
|
@@ -1291,65 +1293,132 @@ class VirtualFilesystem(Filesystem):
|
|
1291
1293
|
self.map_file_entry(dst, VirtualSymlink(self, dst, src))
|
1292
1294
|
|
1293
1295
|
|
1294
|
-
class
|
1295
|
-
__type__ = "
|
1296
|
+
class LayerFilesystem(Filesystem):
|
1297
|
+
__type__ = "layer"
|
1296
1298
|
|
1297
|
-
def __init__(self,
|
1298
|
-
self.
|
1299
|
-
self.layers = []
|
1299
|
+
def __init__(self, **kwargs):
|
1300
|
+
self.layers: list[Filesystem] = []
|
1300
1301
|
self.mounts = {}
|
1301
1302
|
self._alt_separator = "/"
|
1302
1303
|
self._case_sensitive = True
|
1303
|
-
self._root_entry =
|
1304
|
-
self.root = self.
|
1305
|
-
super().__init__(None)
|
1304
|
+
self._root_entry = LayerFilesystemEntry(self, "/", [])
|
1305
|
+
self.root = self.append_layer()
|
1306
|
+
super().__init__(None, **kwargs)
|
1307
|
+
|
1308
|
+
def __getattr__(self, attr: str) -> Any:
|
1309
|
+
"""Provide "magic" access to filesystem specific attributes from any of the layers.
|
1310
|
+
|
1311
|
+
For example, on a :class:`LayerFilesystem` ``fs``, you can do ``fs.ntfs`` to access the
|
1312
|
+
internal NTFS object if it has an NTFS layer.
|
1313
|
+
"""
|
1314
|
+
for fs in self.layers:
|
1315
|
+
try:
|
1316
|
+
return getattr(fs, attr)
|
1317
|
+
except AttributeError:
|
1318
|
+
continue
|
1319
|
+
else:
|
1320
|
+
return object.__getattribute__(self, attr)
|
1306
1321
|
|
1307
1322
|
@staticmethod
|
1308
1323
|
def detect(fh: BinaryIO) -> bool:
|
1309
|
-
raise TypeError("Detect is not allowed on
|
1324
|
+
raise TypeError("Detect is not allowed on LayerFilesystem class")
|
1310
1325
|
|
1311
|
-
def mount(self, path: str, fs: Filesystem) -> None:
|
1326
|
+
def mount(self, path: str, fs: Filesystem, ignore_existing: bool = True) -> None:
|
1312
1327
|
"""Mount a filesystem at a given path.
|
1313
1328
|
|
1314
1329
|
If there's an overlap with an existing mount, creates a new layer.
|
1330
|
+
|
1331
|
+
Args:
|
1332
|
+
path: The path to mount the filesystem at.
|
1333
|
+
fs: The filesystem to mount.
|
1334
|
+
ignore_existing: Whether to ignore existing mounts and create a new layer. Defaults to ``True``.
|
1315
1335
|
"""
|
1316
1336
|
root = self.root
|
1317
1337
|
for mount in self.mounts.keys():
|
1318
|
-
if path == mount:
|
1338
|
+
if ignore_existing and path == mount:
|
1319
1339
|
continue
|
1320
1340
|
|
1321
1341
|
if path.startswith(mount):
|
1322
|
-
root = self.
|
1342
|
+
root = self.append_layer()
|
1323
1343
|
break
|
1324
1344
|
|
1325
1345
|
root.map_fs(path, fs)
|
1326
1346
|
self.mounts[path] = fs
|
1327
1347
|
|
1328
1348
|
def link(self, dst: str, src: str) -> None:
|
1329
|
-
"""Hard link a
|
1330
|
-
dst = fsutil.normalize(dst, alt_separator=self.alt_separator)
|
1349
|
+
"""Hard link a :class:`FilesystemEntry` to another location."""
|
1331
1350
|
self.root.map_file_entry(dst, self.get(src))
|
1332
1351
|
|
1333
1352
|
def symlink(self, dst: str, src: str) -> None:
|
1334
1353
|
"""Create a symlink to another location."""
|
1335
1354
|
self.root.symlink(dst, src)
|
1336
1355
|
|
1337
|
-
def
|
1356
|
+
def append_layer(self, **kwargs) -> VirtualFilesystem:
|
1357
|
+
"""Append a new layer."""
|
1358
|
+
layer = VirtualFilesystem(case_sensitive=self.case_sensitive, alt_separator=self.alt_separator, **kwargs)
|
1359
|
+
self.append_fs_layer(layer)
|
1360
|
+
return layer
|
1361
|
+
|
1362
|
+
add_layer = append_layer
|
1363
|
+
|
1364
|
+
def prepend_layer(self, **kwargs) -> VirtualFilesystem:
|
1365
|
+
"""Prepend a new layer."""
|
1338
1366
|
layer = VirtualFilesystem(case_sensitive=self.case_sensitive, alt_separator=self.alt_separator, **kwargs)
|
1339
|
-
self.
|
1340
|
-
self._root_entry.entries.append(layer.root)
|
1367
|
+
self.prepend_fs_layer(layer)
|
1341
1368
|
return layer
|
1342
1369
|
|
1370
|
+
def append_fs_layer(self, fs: Filesystem) -> None:
|
1371
|
+
"""Append a filesystem as a layer.
|
1372
|
+
|
1373
|
+
Args:
|
1374
|
+
fs: The filesystem to append.
|
1375
|
+
"""
|
1376
|
+
# Counterintuitively, we prepend the filesystem to the list of layers
|
1377
|
+
# We could reverse the list of layers upon iteration, but that is a hot path
|
1378
|
+
self.layers.insert(0, fs)
|
1379
|
+
self._root_entry.entries.insert(0, fs.get("/"))
|
1380
|
+
|
1381
|
+
def prepend_fs_layer(self, fs: Filesystem) -> None:
|
1382
|
+
"""Prepend a filesystem as a layer.
|
1383
|
+
|
1384
|
+
Args:
|
1385
|
+
fs: The filesystem to prepend.
|
1386
|
+
"""
|
1387
|
+
# Counterintuitively, we append the filesystem to the list of layers
|
1388
|
+
# We could reverse the list of layers upon iteration, but that is a hot path
|
1389
|
+
self.layers.append(fs)
|
1390
|
+
self._root_entry.entries.append(fs.get("/"))
|
1391
|
+
|
1392
|
+
def remove_fs_layer(self, fs: Filesystem) -> None:
|
1393
|
+
"""Remove a filesystem layer.
|
1394
|
+
|
1395
|
+
Args:
|
1396
|
+
fs: The filesystem to remove.
|
1397
|
+
"""
|
1398
|
+
self.remove_layer(self.layers.index(fs))
|
1399
|
+
|
1400
|
+
def remove_layer(self, idx: int) -> None:
|
1401
|
+
"""Remove a layer by index.
|
1402
|
+
|
1403
|
+
Args:
|
1404
|
+
idx: The index of the layer to remove.
|
1405
|
+
"""
|
1406
|
+
del self.layers[idx]
|
1407
|
+
del self._root_entry.entries[idx]
|
1408
|
+
|
1343
1409
|
@property
|
1344
1410
|
def case_sensitive(self) -> bool:
|
1411
|
+
"""Whether the filesystem is case sensitive."""
|
1345
1412
|
return self._case_sensitive
|
1346
1413
|
|
1347
1414
|
@property
|
1348
1415
|
def alt_separator(self) -> str:
|
1416
|
+
"""The alternative separator of the filesystem."""
|
1349
1417
|
return self._alt_separator
|
1350
1418
|
|
1351
1419
|
@case_sensitive.setter
|
1352
1420
|
def case_sensitive(self, value: bool) -> None:
|
1421
|
+
"""Set the case sensitivity of the filesystem (and all layers)."""
|
1353
1422
|
self._case_sensitive = value
|
1354
1423
|
self.root.case_sensitive = value
|
1355
1424
|
for layer in self.layers:
|
@@ -1357,14 +1426,14 @@ class RootFilesystem(Filesystem):
|
|
1357
1426
|
|
1358
1427
|
@alt_separator.setter
|
1359
1428
|
def alt_separator(self, value: str) -> None:
|
1429
|
+
"""Set the alternative separator of the filesystem (and all layers)."""
|
1360
1430
|
self._alt_separator = value
|
1361
1431
|
self.root.alt_separator = value
|
1362
1432
|
for layer in self.layers:
|
1363
1433
|
layer.alt_separator = value
|
1364
1434
|
|
1365
|
-
def get(self, path: str, relentry:
|
1366
|
-
|
1367
|
-
|
1435
|
+
def get(self, path: str, relentry: Optional[LayerFilesystemEntry] = None) -> LayerFilesystemEntry:
|
1436
|
+
"""Get a :class:`FilesystemEntry` from the filesystem."""
|
1368
1437
|
entry = relentry or self._root_entry
|
1369
1438
|
path = fsutil.normalize(path, alt_separator=self.alt_separator).strip("/")
|
1370
1439
|
full_path = fsutil.join(entry.path, path, alt_separator=self.alt_separator)
|
@@ -1388,9 +1457,10 @@ class RootFilesystem(Filesystem):
|
|
1388
1457
|
raise NotASymlinkError(full_path)
|
1389
1458
|
raise FileNotFoundError(full_path)
|
1390
1459
|
|
1391
|
-
return
|
1460
|
+
return LayerFilesystemEntry(self, full_path, entries)
|
1392
1461
|
|
1393
1462
|
def _get_from_entry(self, path: str, entry: FilesystemEntry) -> FilesystemEntry:
|
1463
|
+
"""Get a :class:`FilesystemEntry` relative to a specific entry."""
|
1394
1464
|
parts = path.split("/")
|
1395
1465
|
|
1396
1466
|
for part in parts:
|
@@ -1405,11 +1475,11 @@ class RootFilesystem(Filesystem):
|
|
1405
1475
|
class EntryList(list):
|
1406
1476
|
"""Wrapper list for filesystem entries.
|
1407
1477
|
|
1408
|
-
|
1409
|
-
|
1478
|
+
Exposes a ``__getattr__`` on a list of items. Useful to access internal objects from filesystem implementations.
|
1479
|
+
For example, access the underlying NTFS object from a list of virtual and NTFS entries.
|
1410
1480
|
"""
|
1411
1481
|
|
1412
|
-
def __init__(self, value:
|
1482
|
+
def __init__(self, value: FilesystemEntry | list[FilesystemEntry]):
|
1413
1483
|
if not isinstance(value, list):
|
1414
1484
|
value = [value]
|
1415
1485
|
super().__init__(value)
|
@@ -1424,20 +1494,12 @@ class EntryList(list):
|
|
1424
1494
|
return object.__getattribute__(self, attr)
|
1425
1495
|
|
1426
1496
|
|
1427
|
-
class
|
1497
|
+
class LayerFilesystemEntry(FilesystemEntry):
|
1428
1498
|
def __init__(self, fs: Filesystem, path: str, entry: FilesystemEntry):
|
1429
1499
|
super().__init__(fs, path, EntryList(entry))
|
1430
|
-
self.entries = self.entry
|
1500
|
+
self.entries: EntryList = self.entry
|
1431
1501
|
self._link = None
|
1432
1502
|
|
1433
|
-
def __getattr__(self, attr):
|
1434
|
-
for entry in self.entries:
|
1435
|
-
try:
|
1436
|
-
return getattr(entry, attr)
|
1437
|
-
except AttributeError:
|
1438
|
-
continue
|
1439
|
-
return object.__getattribute__(self, attr)
|
1440
|
-
|
1441
1503
|
def _exec(self, func: str, *args, **kwargs) -> Any:
|
1442
1504
|
"""Helper method to execute a method over all contained entries."""
|
1443
1505
|
exc = []
|
@@ -1451,18 +1513,16 @@ class RootFilesystemEntry(FilesystemEntry):
|
|
1451
1513
|
exceptions = ",".join(exc)
|
1452
1514
|
else:
|
1453
1515
|
exceptions = "No entries"
|
1516
|
+
|
1454
1517
|
raise FilesystemError(f"Can't resolve {func} for {self}: {exceptions}")
|
1455
1518
|
|
1456
1519
|
def get(self, path: str) -> FilesystemEntry:
|
1457
|
-
self.fs.target.log.debug("%r::get(%r)", self, path)
|
1458
1520
|
return self.fs.get(path, self._resolve())
|
1459
1521
|
|
1460
1522
|
def open(self) -> BinaryIO:
|
1461
|
-
self.fs.target.log.debug("%r::open()", self)
|
1462
1523
|
return self._resolve()._exec("open")
|
1463
1524
|
|
1464
1525
|
def iterdir(self) -> Iterator[str]:
|
1465
|
-
self.fs.target.log.debug("%r::iterdir()", self)
|
1466
1526
|
yielded = {".", ".."}
|
1467
1527
|
selfentry = self._resolve()
|
1468
1528
|
for fsentry in selfentry.entries:
|
@@ -1474,8 +1534,7 @@ class RootFilesystemEntry(FilesystemEntry):
|
|
1474
1534
|
yield entry_name
|
1475
1535
|
yielded.add(name)
|
1476
1536
|
|
1477
|
-
def scandir(self) -> Iterator[
|
1478
|
-
self.fs.target.log.debug("%r::scandir()", self)
|
1537
|
+
def scandir(self) -> Iterator[LayerFilesystemEntry]:
|
1479
1538
|
# Every entry is actually a list of entries from the different
|
1480
1539
|
# overlaying FSes, of which each may implement a different function
|
1481
1540
|
# like .stat() or .open()
|
@@ -1495,49 +1554,115 @@ class RootFilesystemEntry(FilesystemEntry):
|
|
1495
1554
|
# overlaying FSes may have different casing of the name.
|
1496
1555
|
entry_name = entries[0].name
|
1497
1556
|
path = fsutil.join(selfentry.path, entry_name, alt_separator=selfentry.fs.alt_separator)
|
1498
|
-
yield
|
1557
|
+
yield LayerFilesystemEntry(selfentry.fs, path, entries)
|
1499
1558
|
|
1500
1559
|
def is_file(self, follow_symlinks: bool = True) -> bool:
|
1501
|
-
self.fs.target.log.debug("%r::is_file()", self)
|
1502
1560
|
try:
|
1503
1561
|
return self._resolve(follow_symlinks=follow_symlinks)._exec("is_file", follow_symlinks=follow_symlinks)
|
1504
1562
|
except FileNotFoundError:
|
1505
1563
|
return False
|
1506
1564
|
|
1507
1565
|
def is_dir(self, follow_symlinks: bool = True) -> bool:
|
1508
|
-
self.fs.target.log.debug("%r::is_dir()", self)
|
1509
1566
|
try:
|
1510
1567
|
return self._resolve(follow_symlinks=follow_symlinks)._exec("is_dir", follow_symlinks=follow_symlinks)
|
1511
1568
|
except FileNotFoundError:
|
1512
1569
|
return False
|
1513
1570
|
|
1514
1571
|
def is_symlink(self) -> bool:
|
1515
|
-
self.fs.target.log.debug("%r::is_symlink()", self)
|
1516
1572
|
return self._exec("is_symlink")
|
1517
1573
|
|
1518
1574
|
def readlink(self) -> str:
|
1519
|
-
self.fs.target.log.debug("%r::readlink()", self)
|
1520
1575
|
if not self.is_symlink():
|
1521
1576
|
raise NotASymlinkError(f"Not a link: {self}")
|
1522
1577
|
return self._exec("readlink")
|
1523
1578
|
|
1524
1579
|
def stat(self, follow_symlinks: bool = True) -> fsutil.stat_result:
|
1525
|
-
self.fs.target.log.debug("%r::stat()", self)
|
1526
1580
|
return self._resolve(follow_symlinks=follow_symlinks)._exec("stat", follow_symlinks=follow_symlinks)
|
1527
1581
|
|
1528
1582
|
def lstat(self) -> fsutil.stat_result:
|
1529
|
-
self.fs.target.log.debug("%r::lstat()", self)
|
1530
1583
|
return self._exec("lstat")
|
1531
1584
|
|
1532
1585
|
def attr(self) -> Any:
|
1533
|
-
self.fs.target.log.debug("%r::attr()", self)
|
1534
1586
|
return self._resolve()._exec("attr")
|
1535
1587
|
|
1536
1588
|
def lattr(self) -> Any:
|
1537
|
-
self.fs.target.log.debug("%r::lattr()", self)
|
1538
1589
|
return self._exec("lattr")
|
1539
1590
|
|
1540
1591
|
|
1592
|
+
class RootFilesystem(LayerFilesystem):
|
1593
|
+
__type__ = "root"
|
1594
|
+
|
1595
|
+
def __init__(self, target: Target):
|
1596
|
+
self.target = target
|
1597
|
+
super().__init__()
|
1598
|
+
|
1599
|
+
@staticmethod
|
1600
|
+
def detect(fh: BinaryIO) -> bool:
|
1601
|
+
raise TypeError("Detect is not allowed on RootFilesystem class")
|
1602
|
+
|
1603
|
+
def get(self, path: str, relentry: Optional[LayerFilesystemEntry] = None) -> RootFilesystemEntry:
|
1604
|
+
self.target.log.debug("%r::get(%r)", self, path)
|
1605
|
+
entry = super().get(path, relentry)
|
1606
|
+
entry.__class__ = RootFilesystemEntry
|
1607
|
+
return entry
|
1608
|
+
|
1609
|
+
|
1610
|
+
class RootFilesystemEntry(LayerFilesystemEntry):
|
1611
|
+
fs: RootFilesystem
|
1612
|
+
|
1613
|
+
def get(self, path: str) -> RootFilesystemEntry:
|
1614
|
+
self.fs.target.log.debug("%r::get(%r)", self, path)
|
1615
|
+
entry = super().get(path)
|
1616
|
+
entry.__class__ = RootFilesystemEntry
|
1617
|
+
return entry
|
1618
|
+
|
1619
|
+
def open(self) -> BinaryIO:
|
1620
|
+
self.fs.target.log.debug("%r::open()", self)
|
1621
|
+
return super().open()
|
1622
|
+
|
1623
|
+
def iterdir(self) -> Iterator[str]:
|
1624
|
+
self.fs.target.log.debug("%r::iterdir()", self)
|
1625
|
+
yield from super().iterdir()
|
1626
|
+
|
1627
|
+
def scandir(self) -> Iterator[RootFilesystemEntry]:
|
1628
|
+
self.fs.target.log.debug("%r::scandir()", self)
|
1629
|
+
for entry in super().scandir():
|
1630
|
+
entry.__class__ = RootFilesystemEntry
|
1631
|
+
yield entry
|
1632
|
+
|
1633
|
+
def is_file(self, follow_symlinks: bool = True) -> bool:
|
1634
|
+
self.fs.target.log.debug("%r::is_file()", self)
|
1635
|
+
return super().is_file(follow_symlinks=follow_symlinks)
|
1636
|
+
|
1637
|
+
def is_dir(self, follow_symlinks: bool = True) -> bool:
|
1638
|
+
self.fs.target.log.debug("%r::is_dir()", self)
|
1639
|
+
return super().is_dir(follow_symlinks=follow_symlinks)
|
1640
|
+
|
1641
|
+
def is_symlink(self) -> bool:
|
1642
|
+
self.fs.target.log.debug("%r::is_symlink()", self)
|
1643
|
+
return super().is_symlink()
|
1644
|
+
|
1645
|
+
def readlink(self) -> str:
|
1646
|
+
self.fs.target.log.debug("%r::readlink()", self)
|
1647
|
+
return super().readlink()
|
1648
|
+
|
1649
|
+
def stat(self, follow_symlinks: bool = True) -> fsutil.stat_result:
|
1650
|
+
self.fs.target.log.debug("%r::stat()", self)
|
1651
|
+
return super().stat(follow_symlinks=follow_symlinks)
|
1652
|
+
|
1653
|
+
def lstat(self) -> fsutil.stat_result:
|
1654
|
+
self.fs.target.log.debug("%r::lstat()", self)
|
1655
|
+
return super().lstat()
|
1656
|
+
|
1657
|
+
def attr(self) -> Any:
|
1658
|
+
self.fs.target.log.debug("%r::attr()", self)
|
1659
|
+
return super().attr()
|
1660
|
+
|
1661
|
+
def lattr(self) -> Any:
|
1662
|
+
self.fs.target.log.debug("%r::lattr()", self)
|
1663
|
+
return super().lattr()
|
1664
|
+
|
1665
|
+
|
1541
1666
|
def register(module: str, class_name: str, internal: bool = True) -> None:
|
1542
1667
|
"""Register a filesystem implementation to use when opening a filesystem.
|
1543
1668
|
|
dissect/target/loaders/vb.py
CHANGED
@@ -14,8 +14,8 @@ class VBLoader(Loader):
|
|
14
14
|
return (mft_exists or c_drive_exists) and config_exists
|
15
15
|
|
16
16
|
def map(self, target):
|
17
|
-
|
18
|
-
|
17
|
+
remap_overlay = target.fs.append_layer()
|
18
|
+
ntfs_overlay = target.fs.append_layer()
|
19
19
|
dfs = DirectoryFilesystem(self.path, case_sensitive=False)
|
20
20
|
target.filesystems.add(dfs)
|
21
21
|
|
@@ -3,7 +3,7 @@ from typing import Iterable
|
|
3
3
|
from dissect.util.ts import from_unix
|
4
4
|
|
5
5
|
from dissect.target.exceptions import FileNotFoundError, UnsupportedPluginError
|
6
|
-
from dissect.target.filesystem import
|
6
|
+
from dissect.target.filesystem import LayerFilesystemEntry
|
7
7
|
from dissect.target.helpers.fsutil import TargetPath
|
8
8
|
from dissect.target.helpers.record import TargetRecordDescriptor
|
9
9
|
from dissect.target.plugin import Plugin, export
|
@@ -50,7 +50,7 @@ def generate_record(target: Target, path: TargetPath) -> FilesystemRecord:
|
|
50
50
|
stat = path.lstat()
|
51
51
|
btime = from_unix(stat.st_birthtime) if stat.st_birthtime else None
|
52
52
|
entry = path.get()
|
53
|
-
if isinstance(entry,
|
53
|
+
if isinstance(entry, LayerFilesystemEntry):
|
54
54
|
fs_types = [sub_entry.fs.__type__ for sub_entry in entry.entries]
|
55
55
|
else:
|
56
56
|
fs_types = [entry.fs.__type__]
|
@@ -97,7 +97,7 @@ class ESXiPlugin(UnixPlugin):
|
|
97
97
|
|
98
98
|
# Create a root layer for the "local state" filesystem
|
99
99
|
# This stores persistent configuration data
|
100
|
-
local_layer = target.fs.
|
100
|
+
local_layer = target.fs.append_layer()
|
101
101
|
|
102
102
|
# Mount all the visor tars in individual filesystem layers
|
103
103
|
_mount_modules(target, sysvol, cfg)
|
@@ -209,7 +209,7 @@ def _mount_modules(target: Target, sysvol: Filesystem, cfg: dict[str, str]):
|
|
209
209
|
tfs = tar.TarFilesystem(cfile, tarinfo=vmtar.VisorTarInfo)
|
210
210
|
|
211
211
|
if tfs:
|
212
|
-
target.fs.
|
212
|
+
target.fs.append_layer().mount("/", tfs)
|
213
213
|
|
214
214
|
|
215
215
|
def _mount_local(target: Target, local_layer: VirtualFilesystem):
|
@@ -24,7 +24,7 @@ class VyosPlugin(LinuxPlugin):
|
|
24
24
|
self._version, rootpath = latest
|
25
25
|
|
26
26
|
# VyOS does some additional magic with base system files
|
27
|
-
layer = target.fs.
|
27
|
+
layer = target.fs.append_layer()
|
28
28
|
layer.map_file_entry("/", target.fs.root.get(f"/boot/{self._version}/{rootpath}"))
|
29
29
|
super().__init__(target)
|
30
30
|
|
@@ -113,7 +113,7 @@ class FortiOSPlugin(LinuxPlugin):
|
|
113
113
|
|
114
114
|
# FortiGate
|
115
115
|
if (datafs_tar := sysvol.path("/datafs.tar.gz")).exists():
|
116
|
-
target.fs.
|
116
|
+
target.fs.append_layer().mount("/data", TarFilesystem(datafs_tar.open("rb")))
|
117
117
|
|
118
118
|
# Additional FortiGate or FortiManager tars with corrupt XZ streams
|
119
119
|
target.log.warning("Attempting to load XZ files, this can take a while.")
|
@@ -127,11 +127,11 @@ class FortiOSPlugin(LinuxPlugin):
|
|
127
127
|
):
|
128
128
|
if (tar := target.fs.path(path)).exists() or (tar := sysvol.path(path)).exists():
|
129
129
|
fh = xz.repair_checksum(tar.open("rb"))
|
130
|
-
target.fs.
|
130
|
+
target.fs.append_layer().mount("/", TarFilesystem(fh))
|
131
131
|
|
132
132
|
# FortiAnalyzer and FortiManager
|
133
133
|
if (rootfs_ext_tar := sysvol.path("rootfs-ext.tar.xz")).exists():
|
134
|
-
target.fs.
|
134
|
+
target.fs.append_layer().mount("/", TarFilesystem(rootfs_ext_tar.open("rb")))
|
135
135
|
|
136
136
|
# Filesystem mounts can be discovered in the FortiCare debug report
|
137
137
|
# or using ``fnsysctl ls`` and ``fnsysctl df`` in the cli.
|
dissect/target/tools/shell.py
CHANGED
@@ -31,7 +31,7 @@ from dissect.target.exceptions import (
|
|
31
31
|
RegistryValueNotFoundError,
|
32
32
|
TargetError,
|
33
33
|
)
|
34
|
-
from dissect.target.filesystem import FilesystemEntry,
|
34
|
+
from dissect.target.filesystem import FilesystemEntry, LayerFilesystemEntry
|
35
35
|
from dissect.target.helpers import cyber, fsutil, regutil
|
36
36
|
from dissect.target.plugin import arg
|
37
37
|
from dissect.target.target import Target
|
@@ -469,7 +469,7 @@ class TargetCli(TargetCmd):
|
|
469
469
|
# If we happen to scan an NTFS filesystem see if any of the
|
470
470
|
# entries has an alternative data stream and also list them.
|
471
471
|
entry = file_.get()
|
472
|
-
if isinstance(entry,
|
472
|
+
if isinstance(entry, LayerFilesystemEntry):
|
473
473
|
if entry.entries.fs.__type__ == "ntfs":
|
474
474
|
attrs = entry.lattr()
|
475
475
|
for data_stream in attrs.DATA:
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: dissect.target
|
3
|
-
Version: 3.17.
|
3
|
+
Version: 3.17.dev20
|
4
4
|
Summary: This module ties all other Dissect modules together, it provides a programming API and command line tools which allow easy access to various data sources inside disk images or file collections (a.k.a. targets)
|
5
5
|
Author-email: Dissect Team <dissect@fox-it.com>
|
6
6
|
License: Affero General Public License v3
|
@@ -1,7 +1,7 @@
|
|
1
1
|
dissect/target/__init__.py,sha256=Oc7ounTgq2hE4nR6YcNabetc7SQA40ldSa35VEdZcQU,63
|
2
2
|
dissect/target/container.py,sha256=0YcwcGmfJjhPXUB6DEcjWEoSuAtTDxMDpoTviMrLsxM,9353
|
3
3
|
dissect/target/exceptions.py,sha256=VVW_Rq_vQinapz-2mbJ3UkxBEZpb2pE_7JlhMukdtrY,2877
|
4
|
-
dissect/target/filesystem.py,sha256=
|
4
|
+
dissect/target/filesystem.py,sha256=1i-lToeTX-HgQXQOYxPXH-90M_eq43W4FFzNDRdpgpk,60094
|
5
5
|
dissect/target/loader.py,sha256=_mrMOzKdpb7nlZJpLENOLuU4Ty92PzJem9GFDuo0PK4,7298
|
6
6
|
dissect/target/plugin.py,sha256=HAN8maaDt-Rlqt8Rr1IW7gXQpzNQZjCVz-i4aSPphSw,48677
|
7
7
|
dissect/target/report.py,sha256=06uiP4MbNI8cWMVrC1SasNS-Yg6ptjVjckwj8Yhe0Js,7958
|
@@ -102,7 +102,7 @@ dissect/target/loaders/tar.py,sha256=ah5t9tzplJp-fJrvwTTtfqdjXTpZLQIikWglnaTKQHs
|
|
102
102
|
dissect/target/loaders/target.py,sha256=MU_HUtg58YdhdZu6ga1sYG7fK61Dn7N0TBkWXDCWwyc,798
|
103
103
|
dissect/target/loaders/targetd.py,sha256=sfbn2_j3il2G-rPywAoNT5YPtD5KmKkmBv1zrPDRs6I,8250
|
104
104
|
dissect/target/loaders/utm.py,sha256=e5x5ZI3HeL0STh4S-CaQb68Rnug4SVZR9zlmHaGFj0M,978
|
105
|
-
dissect/target/loaders/vb.py,sha256=
|
105
|
+
dissect/target/loaders/vb.py,sha256=CdimOMeoJEDq8xYDgtldGSiwhR-dY5uxac1L0sYwAEU,2078
|
106
106
|
dissect/target/loaders/vbox.py,sha256=8JD7D8iAY9JRvTHsrosp5ZMsZezuLhZ10Zt8sEL7KBI,732
|
107
107
|
dissect/target/loaders/velociraptor.py,sha256=FNxZgs_ehmgGO_Giw5oNl7cVOWNqI2nEiPWT4GjF2e0,4955
|
108
108
|
dissect/target/loaders/vma.py,sha256=AAY5-s-nz6wgvmcFkptJD7nNXhpkdf6SqEKVOrJaIKs,644
|
@@ -160,7 +160,7 @@ dissect/target/plugins/filesystem/acquire_handles.py,sha256=-pX_akH5GrYe0HofXOa2
|
|
160
160
|
dissect/target/plugins/filesystem/acquire_hash.py,sha256=OVxI19-Bl1tdqCiFMscFMLmyoiBOsuAjL-Q8aQpEwl0,1441
|
161
161
|
dissect/target/plugins/filesystem/icat.py,sha256=bOMi04IlljnKwxTWTZJKtK7RxKnabFu3WcXyUwzkE-4,4090
|
162
162
|
dissect/target/plugins/filesystem/resolver.py,sha256=HfyASUFV4F9uD-yFXilFpPTORAsRDvdmTvuYHgOaOWg,4776
|
163
|
-
dissect/target/plugins/filesystem/walkfs.py,sha256=
|
163
|
+
dissect/target/plugins/filesystem/walkfs.py,sha256=e8HEZcV5Wiua26FGWL3xgiQ_PIhcNvGI5KCdsAx2Nmo,2298
|
164
164
|
dissect/target/plugins/filesystem/yara.py,sha256=q_pbrQArNaWP4ILRzK7VQhukIw16LhUvntoviHmZ38Q,2241
|
165
165
|
dissect/target/plugins/filesystem/ntfs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
166
166
|
dissect/target/plugins/filesystem/ntfs/mft.py,sha256=Za-fsTcKlAlhm9ugJlMdwsJVf2Osrh4PrEGSFuv-Eeo,9564
|
@@ -205,7 +205,7 @@ dissect/target/plugins/os/unix/bsd/osx/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JC
|
|
205
205
|
dissect/target/plugins/os/unix/bsd/osx/_os.py,sha256=KvP7YJ7apVwoIop7MR-8q5QbVGoB6MdR42l6ssEe6es,4081
|
206
206
|
dissect/target/plugins/os/unix/bsd/osx/user.py,sha256=qopB0s3n7e6Q7NjWzn8Z-dKtDtU7e6In4Vm7hIvvedo,2322
|
207
207
|
dissect/target/plugins/os/unix/esxi/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
208
|
-
dissect/target/plugins/os/unix/esxi/_os.py,sha256=
|
208
|
+
dissect/target/plugins/os/unix/esxi/_os.py,sha256=8kFFK9986zN8hXmDUWwdQHtbV33nWKerRuisg_xbsoQ,17504
|
209
209
|
dissect/target/plugins/os/unix/linux/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
210
210
|
dissect/target/plugins/os/unix/linux/_os.py,sha256=YJYwuq_iAinOrPqTE49Q4DLYMWBeRCly1uTbDvPhp6Q,2796
|
211
211
|
dissect/target/plugins/os/unix/linux/cmdline.py,sha256=XIvaTL42DzeQGhqHN_RTMI5g8hbI2_wjzb7KZ0kPOM0,1591
|
@@ -224,10 +224,10 @@ dissect/target/plugins/os/unix/linux/debian/_os.py,sha256=GI19ZqcyfZ1mUYg2NCx93H
|
|
224
224
|
dissect/target/plugins/os/unix/linux/debian/apt.py,sha256=dkTfLrS-MS8wfrXILFLHDoLqBkM_w16KTRQ7ysiZkZY,4316
|
225
225
|
dissect/target/plugins/os/unix/linux/debian/dpkg.py,sha256=DPBLQiHAF7ZS8IorRsGAiBj4HhvwuJbmkMEHOuTZisw,5735
|
226
226
|
dissect/target/plugins/os/unix/linux/debian/vyos/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
227
|
-
dissect/target/plugins/os/unix/linux/debian/vyos/_os.py,sha256=
|
227
|
+
dissect/target/plugins/os/unix/linux/debian/vyos/_os.py,sha256=TPjcfv1n68RCe3Er4aCVQwQDCZwJT-NLvje3kPjDfhk,1744
|
228
228
|
dissect/target/plugins/os/unix/linux/fortios/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
229
229
|
dissect/target/plugins/os/unix/linux/fortios/_keys.py,sha256=jDDHObfsUn9BGoIir9p4J_-rg9rI1rgoOfnL3R3lg4o,123358
|
230
|
-
dissect/target/plugins/os/unix/linux/fortios/_os.py,sha256=
|
230
|
+
dissect/target/plugins/os/unix/linux/fortios/_os.py,sha256=lyURdGaIsPmEhRZ6P5DQtwOggOLe9TZuY1cTirLSukA,19444
|
231
231
|
dissect/target/plugins/os/unix/linux/fortios/generic.py,sha256=tT4-lE0Z_DeDIN3zHrQbE8JB3cRJop1_TiEst-Au0bs,1230
|
232
232
|
dissect/target/plugins/os/unix/linux/fortios/locale.py,sha256=VDdk60sqe2JTfftssO05C667-_BpI3kcqKOTVzO3ueU,5209
|
233
233
|
dissect/target/plugins/os/unix/linux/redhat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -322,7 +322,7 @@ dissect/target/tools/logging.py,sha256=5ZnumtMWLyslxfrUGZ4ntRyf3obOOhmn8SBjKfdLc
|
|
322
322
|
dissect/target/tools/mount.py,sha256=L_0tSmiBdW4aSaF0vXjB0bAkTC0kmT2N1hrbW6s5Jow,3254
|
323
323
|
dissect/target/tools/query.py,sha256=6zz9SXS6YnHj7eguORS8Je7N4iM0i1PZDIQ-gyJ1nPY,15593
|
324
324
|
dissect/target/tools/reg.py,sha256=FDsiBBDxjWVUBTRj8xn82vZe-J_d9piM-TKS3PHZCcM,3193
|
325
|
-
dissect/target/tools/shell.py,sha256=
|
325
|
+
dissect/target/tools/shell.py,sha256=4v6Z06YJDjKv6e6SRvWNjQ2n_KHo_CjL4P0w1_gY_ro,44827
|
326
326
|
dissect/target/tools/utils.py,sha256=sQizexY3ui5vmWw4KOBLg5ecK3TPFjD-uxDqRn56ZTY,11304
|
327
327
|
dissect/target/tools/dump/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
328
328
|
dissect/target/tools/dump/run.py,sha256=aD84peRS4zHqC78fH7Vd4ni3m1ZmVP70LyMwBRvoDGY,9463
|
@@ -336,10 +336,10 @@ dissect/target/volumes/luks.py,sha256=OmCMsw6rCUXG1_plnLVLTpsvE1n_6WtoRUGQbpmu1z
|
|
336
336
|
dissect/target/volumes/lvm.py,sha256=wwQVR9I3G9YzmY6UxFsH2Y4MXGBcKL9aayWGCDTiWMU,2269
|
337
337
|
dissect/target/volumes/md.py,sha256=j1K1iKmspl0C_OJFc7-Q1BMWN2OCC5EVANIgVlJ_fIE,1673
|
338
338
|
dissect/target/volumes/vmfs.py,sha256=-LoUbn9WNwTtLi_4K34uV_-wDw2W5hgaqxZNj4UmqAQ,1730
|
339
|
-
dissect.target-3.17.
|
340
|
-
dissect.target-3.17.
|
341
|
-
dissect.target-3.17.
|
342
|
-
dissect.target-3.17.
|
343
|
-
dissect.target-3.17.
|
344
|
-
dissect.target-3.17.
|
345
|
-
dissect.target-3.17.
|
339
|
+
dissect.target-3.17.dev20.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
|
340
|
+
dissect.target-3.17.dev20.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
|
341
|
+
dissect.target-3.17.dev20.dist-info/METADATA,sha256=w19ITKpjeWu52HPdEU4SD_axvpgGpBr8_nsETBOLeEw,11300
|
342
|
+
dissect.target-3.17.dev20.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
343
|
+
dissect.target-3.17.dev20.dist-info/entry_points.txt,sha256=tvFPa-Ap-gakjaPwRc6Fl6mxHzxEZ_arAVU-IUYeo_s,447
|
344
|
+
dissect.target-3.17.dev20.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
|
345
|
+
dissect.target-3.17.dev20.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
{dissect.target-3.17.dev19.dist-info → dissect.target-3.17.dev20.dist-info}/entry_points.txt
RENAMED
File without changes
|
File without changes
|