dissect.apfs 1.1__tar.gz → 1.1.dev1__tar.gz

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.
Files changed (62) hide show
  1. {dissect_apfs-1.1/dissect.apfs.egg-info → dissect_apfs-1.1.dev1}/PKG-INFO +1 -1
  2. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/dissect/apfs/apfs.py +2 -28
  3. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/dissect/apfs/objects/fs.py +9 -9
  4. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/dissect/apfs/objects/nx_superblock.py +4 -36
  5. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/dissect/apfs/objects/omap.py +0 -9
  6. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1/dissect.apfs.egg-info}/PKG-INFO +1 -1
  7. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/dissect.apfs.egg-info/SOURCES.txt +0 -1
  8. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/pyproject.toml +1 -0
  9. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/tests/test_apfs.py +1 -23
  10. dissect_apfs-1.1/tests/_data/corrupt.bin.gz +0 -0
  11. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/COPYRIGHT +0 -0
  12. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/LICENSE +0 -0
  13. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/MANIFEST.in +0 -0
  14. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/README.md +0 -0
  15. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/dissect/apfs/__init__.py +0 -0
  16. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/dissect/apfs/c_apfs.py +0 -0
  17. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/dissect/apfs/c_apfs.pyi +0 -0
  18. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/dissect/apfs/cursor.py +0 -0
  19. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/dissect/apfs/exception.py +0 -0
  20. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/dissect/apfs/objects/__init__.py +0 -0
  21. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/dissect/apfs/objects/base.py +0 -0
  22. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/dissect/apfs/objects/btree.py +0 -0
  23. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/dissect/apfs/objects/btree_node.py +0 -0
  24. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/dissect/apfs/objects/checkpoint_map.py +0 -0
  25. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/dissect/apfs/objects/efi_jumpstart.py +0 -0
  26. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/dissect/apfs/objects/er_recovery_block.py +0 -0
  27. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/dissect/apfs/objects/er_state.py +0 -0
  28. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/dissect/apfs/objects/gbitmap.py +0 -0
  29. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/dissect/apfs/objects/gbitmap_block.py +0 -0
  30. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/dissect/apfs/objects/integrity_meta.py +0 -0
  31. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/dissect/apfs/objects/keybag.py +0 -0
  32. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/dissect/apfs/objects/nx_fusion_wbc.py +0 -0
  33. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/dissect/apfs/objects/nx_fusion_wbc_list.py +0 -0
  34. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/dissect/apfs/objects/nx_reap_list.py +0 -0
  35. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/dissect/apfs/objects/nx_reaper.py +0 -0
  36. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/dissect/apfs/objects/snap_meta_ext.py +0 -0
  37. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/dissect/apfs/objects/spaceman.py +0 -0
  38. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/dissect/apfs/objects/spaceman_bitmap.py +0 -0
  39. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/dissect/apfs/objects/spaceman_cab.py +0 -0
  40. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/dissect/apfs/objects/spaceman_cib.py +0 -0
  41. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/dissect/apfs/stream.py +0 -0
  42. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/dissect/apfs/util.py +0 -0
  43. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/dissect.apfs.egg-info/dependency_links.txt +0 -0
  44. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/dissect.apfs.egg-info/requires.txt +0 -0
  45. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/dissect.apfs.egg-info/top_level.txt +0 -0
  46. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/setup.cfg +0 -0
  47. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/tests/__init__.py +0 -0
  48. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/tests/_data/case_insensitive.bin.gz +0 -0
  49. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/tests/_data/case_insensitive_beta.bin.gz +0 -0
  50. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/tests/_data/case_sensitive.bin.gz +0 -0
  51. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/tests/_data/case_sensitive_beta.bin.gz +0 -0
  52. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/tests/_data/encrypted.bin.gz +0 -0
  53. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/tests/_data/jhfs_converted.bin.gz +0 -0
  54. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/tests/_data/jhfs_encrypted.bin.gz +0 -0
  55. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/tests/_data/snapshot.bin.gz +0 -0
  56. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/tests/_docs/Makefile +0 -0
  57. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/tests/_docs/__init__.py +0 -0
  58. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/tests/_docs/conf.py +0 -0
  59. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/tests/_docs/index.rst +0 -0
  60. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/tests/conftest.py +0 -0
  61. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/tests/test_exception.py +0 -0
  62. {dissect_apfs-1.1 → dissect_apfs-1.1.dev1}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dissect.apfs
3
- Version: 1.1
3
+ Version: 1.1.dev1
4
4
  Summary: A Dissect module implementing a parser for the APFS file system, a commonly used Apple file system
5
5
  Author-email: Dissect Team <dissect@fox-it.com>
6
6
  License-Expression: AGPL-3.0-or-later
@@ -1,7 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- import logging
4
- import os
5
3
  from typing import TYPE_CHECKING, BinaryIO
6
4
 
7
5
  from dissect.apfs.c_apfs import c_apfs
@@ -13,9 +11,6 @@ if TYPE_CHECKING:
13
11
  from dissect.apfs.objects.fs import FS
14
12
  from dissect.apfs.objects.keybag import ContainerKeybag
15
13
 
16
- log = logging.getLogger(__name__)
17
- log.setLevel(os.getenv("DISSECT_LOG_APFS", "CRITICAL"))
18
-
19
14
 
20
15
  class APFS:
21
16
  """Container class for APFS operations.
@@ -29,29 +24,8 @@ class APFS:
29
24
  self.fh.seek(0)
30
25
 
31
26
  self.sb = NxSuperblock.from_block(self, 0, self.fh.read(c_apfs.NX_DEFAULT_BLOCK_SIZE))
32
- self.sb.check()
33
-
34
- self.sbs = sorted(
35
- [obj for obj in self.sb.checkpoint_objects if isinstance(obj, NxSuperblock)],
36
- key=lambda obj: obj.xid,
37
- reverse=True,
38
- )
39
-
40
- # TODO: Do more accurate checkpoint traversal
41
- for sb in self.sbs:
42
- try:
43
- sb.check()
44
- sb.compare(self.sb)
45
- except Exception as e:
46
- log.debug("Skipping superblock xid=%d: %s", sb.xid, e)
47
- continue
48
-
49
- if not sb.omap.is_valid():
50
- log.debug("Skipping superblock xid=%d: invalid OMAP", sb.xid)
51
- continue
52
-
53
- self.sb = sb
54
- break
27
+ self.sbs = [self.sb] + [obj for obj in self.sb.checkpoint_objects if isinstance(obj, NxSuperblock)]
28
+ self.sb = sorted(self.sbs, key=lambda obj: obj.xid)[-1]
55
29
 
56
30
  @property
57
31
  def block_size(self) -> int:
@@ -792,27 +792,27 @@ class DirectoryEntry:
792
792
  @cached_property
793
793
  def type(self) -> int:
794
794
  """The file type of this directory entry."""
795
- return (self.value.flags & c_apfs.DREC_TYPE_MASK) << 12
795
+ return self.value.flags & c_apfs.DREC_TYPE_MASK << 12
796
796
 
797
797
  def is_dir(self) -> bool:
798
798
  """Return whether this directory entry is a directory."""
799
- return stat.S_ISDIR(self.type)
799
+ return stat.S_ISDIR(self.type << 12)
800
800
 
801
801
  def is_file(self) -> bool:
802
802
  """Return whether this directory entry is a regular file."""
803
- return stat.S_ISREG(self.type)
803
+ return stat.S_ISREG(self.type << 12)
804
804
 
805
805
  def is_symlink(self) -> bool:
806
806
  """Return whether this directory entry is a symbolic link."""
807
- return stat.S_ISLNK(self.type)
807
+ return stat.S_ISLNK(self.type << 12)
808
808
 
809
809
  def is_block_device(self) -> bool:
810
810
  """Return whether this directory entry is a block device."""
811
- return stat.S_ISBLK(self.type)
811
+ return stat.S_ISBLK(self.type << 12)
812
812
 
813
813
  def is_character_device(self) -> bool:
814
814
  """Return whether this directory entry is a character device."""
815
- return stat.S_ISCHR(self.type)
815
+ return stat.S_ISCHR(self.type << 12)
816
816
 
817
817
  def is_device(self) -> bool:
818
818
  """Return whether this directory entry is a device (block or character)."""
@@ -820,15 +820,15 @@ class DirectoryEntry:
820
820
 
821
821
  def is_fifo(self) -> bool:
822
822
  """Return whether this directory entry is a FIFO."""
823
- return stat.S_ISFIFO(self.type)
823
+ return stat.S_ISFIFO(self.type << 12)
824
824
 
825
825
  def is_socket(self) -> bool:
826
826
  """Return whether this directory entry is a socket."""
827
- return stat.S_ISSOCK(self.type)
827
+ return stat.S_ISSOCK(self.type << 12)
828
828
 
829
829
  def is_whiteout(self) -> bool:
830
830
  """Return whether this directory entry is a whiteout."""
831
- return stat.S_ISWHT(self.type)
831
+ return stat.S_ISWHT(self.type << 12)
832
832
 
833
833
 
834
834
  class XAttr:
@@ -29,16 +29,8 @@ class NxSuperblock(Object):
29
29
  __struct__ = c_apfs.nx_superblock
30
30
  object: c_apfs.nx_superblock
31
31
 
32
- def check(self) -> None:
33
- """Check the validity of the superblock."""
34
- if not self.is_valid():
35
- raise Error("Invalid nx_superblock checksum")
36
-
37
- if self.type != c_apfs.OBJECT_TYPE.NX_SUPERBLOCK:
38
- raise Error("Invalid nx_superblock type")
39
-
40
- if not self.is_ephemeral:
41
- raise Error("Invalid nx_superblock storage type")
32
+ def __init__(self, *args, **kwargs):
33
+ super().__init__(*args, **kwargs)
42
34
 
43
35
  if self.object.nx_magic.to_bytes(4, "big") != c_apfs.NX_MAGIC:
44
36
  raise Error(
@@ -46,24 +38,6 @@ class NxSuperblock(Object):
46
38
  f"(expected {c_apfs.NX_MAGIC!r}, got {self.object.nx_magic.to_bytes(4, 'big')!r})"
47
39
  )
48
40
 
49
- def compare(self, other: NxSuperblock) -> None:
50
- """Compare this superblock to another superblock."""
51
- if self.header.o_xid < other.header.o_xid:
52
- raise Error("Lower xid than other superblock")
53
-
54
- for attr in (
55
- "nx_uuid",
56
- "nx_fusion_uuid",
57
- "nx_block_size",
58
- "nx_block_count",
59
- "nx_xp_desc_blocks",
60
- "nx_xp_data_blocks",
61
- "nx_xp_desc_base",
62
- "nx_xp_data_base",
63
- ):
64
- if getattr(self.object, attr) != getattr(other.object, attr):
65
- raise Error(f"Mismatch on {attr}")
66
-
67
41
  @cached_property
68
42
  def block_size(self) -> int:
69
43
  """The block size of the container."""
@@ -92,20 +66,14 @@ class NxSuperblock(Object):
92
66
  @cached_property
93
67
  def checkpoint_objects(self) -> list[CheckpointMap | NxSuperblock]:
94
68
  """All checkpoint objects in the container."""
95
- # TODO: Rework this a bit to be more accurate
96
- return list(
97
- _read_checkpoint_objects(self.container, self.object.nx_xp_desc_base, self.object.nx_xp_desc_blocks)
98
- )
69
+ return list(_read_checkpoint_objects(self.container, self.object.nx_xp_desc_base, self.object.nx_xp_desc_len))
99
70
 
100
71
  @cached_property
101
72
  def ephemeral_objects(self) -> dict[int, Object]:
102
73
  """All ephemeral objects in the container."""
103
- # TODO: I don't think this is correct
104
74
  return {
105
75
  obj.oid: obj
106
- for obj in _read_checkpoint_objects(
107
- self.container, self.object.nx_xp_data_base, self.object.nx_xp_data_blocks
108
- )
76
+ for obj in _read_checkpoint_objects(self.container, self.object.nx_xp_data_base, self.object.nx_xp_data_len)
109
77
  }
110
78
 
111
79
  @cached_property
@@ -20,15 +20,6 @@ class ObjectMap(Object):
20
20
 
21
21
  self.lookup = lru_cache(128)(self.lookup)
22
22
 
23
- def is_valid(self) -> bool:
24
- return (
25
- super().is_valid()
26
- and self.type == c_apfs.OBJECT_TYPE.OMAP
27
- and self.subtype == 0
28
- and self.is_physical
29
- and self.oid == self.address
30
- )
31
-
32
23
  @cached_property
33
24
  def btree(self) -> BTree:
34
25
  """The B-tree of the object map."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dissect.apfs
3
- Version: 1.1
3
+ Version: 1.1.dev1
4
4
  Summary: A Dissect module implementing a parser for the APFS file system, a commonly used Apple file system
5
5
  Author-email: Dissect Team <dissect@fox-it.com>
6
6
  License-Expression: AGPL-3.0-or-later
@@ -49,7 +49,6 @@ tests/_data/case_insensitive.bin.gz
49
49
  tests/_data/case_insensitive_beta.bin.gz
50
50
  tests/_data/case_sensitive.bin.gz
51
51
  tests/_data/case_sensitive_beta.bin.gz
52
- tests/_data/corrupt.bin.gz
53
52
  tests/_data/encrypted.bin.gz
54
53
  tests/_data/jhfs_converted.bin.gz
55
54
  tests/_data/jhfs_encrypted.bin.gz
@@ -122,3 +122,4 @@ known-third-party = ["dissect"]
122
122
  include = ["dissect.*"]
123
123
 
124
124
  [tool.setuptools_scm]
125
+ local_scheme = "no-local-version"
@@ -6,7 +6,7 @@ from typing import TYPE_CHECKING
6
6
 
7
7
  import pytest
8
8
 
9
- from dissect.apfs.apfs import APFS, log
9
+ from dissect.apfs.apfs import APFS
10
10
  from dissect.apfs.c_apfs import c_apfs
11
11
  from tests.conftest import absolute_path
12
12
 
@@ -36,11 +36,6 @@ def _assert_apfs_content(volume: FS, beta: bool) -> None:
36
36
  ]
37
37
  )
38
38
 
39
- # Test direntry parsing
40
- assert node.listdir()["dir"].is_dir()
41
- assert node.listdir()["hardlink"].is_file()
42
- assert node.listdir()["symlink-dir"].is_symlink()
43
-
44
39
  # Empty file
45
40
  node = volume.get("empty")
46
41
  assert node.name == "empty"
@@ -245,9 +240,6 @@ def _assert_apfs_content(volume: FS, beta: bool) -> None:
245
240
 
246
241
  if ".HFS+ Private Directory Data\r" not in volume.get("/").listdir() and not beta:
247
242
  # Special files
248
- dirents = volume.get("dir").listdir()
249
- assert dirents["blockdev"].is_block_device()
250
-
251
243
  node = volume.get("dir/blockdev")
252
244
  assert node.name == "blockdev"
253
245
  assert node.is_block_device()
@@ -271,8 +263,6 @@ def _assert_apfs_content(volume: FS, beta: bool) -> None:
271
263
  "chardev-svr4",
272
264
  "chardev-ultrix",
273
265
  ]:
274
- assert dirents[name].is_character_device()
275
-
276
266
  node = volume.get(f"dir/{name}")
277
267
  assert node.name == name
278
268
  assert node.is_character_device()
@@ -367,15 +357,3 @@ def test_snapshots() -> None:
367
357
  for i, snapshot in enumerate(volume.snapshots):
368
358
  assert snapshot.name == f"Snapshot {i}"
369
359
  assert snapshot.open().get("file").open().read() == f"Snapshot {i}\n".encode()
370
-
371
-
372
- def test_corrupt_checkpoints(caplog: pytest.LogCaptureFixture) -> None:
373
- """Test APFS volumes with corrupt checkpoints."""
374
- with gzip.open(absolute_path("_data/corrupt.bin.gz"), "rb") as fh, caplog.at_level("DEBUG", log.name):
375
- container = APFS(fh)
376
-
377
- assert container.sb.xid == 302
378
- assert len(container.volumes) == 1
379
-
380
- assert caplog.messages[0] == "Skipping superblock xid=304: invalid OMAP"
381
- assert caplog.messages[1] == "Skipping superblock xid=303: Invalid nx_superblock checksum"
Binary file
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes