Skip to content

Commit 188781c

Browse files
committed
Process review
1 parent f5f0f0f commit 188781c

File tree

7 files changed

+39
-32
lines changed

7 files changed

+39
-32
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# dissect.apfs
22

3-
A Dissect module implementing a parser for the APFS file system, a commonly used Apple filesystem. For more
3+
A Dissect module implementing a parser for the APFS file system, a commonly used Apple file system. For more
44
information, please see [the documentation](https://docs.dissect.tools/en/latest/projects/dissect.apfs/index.html).
55

66
## Requirements

dissect/apfs/c_apfs.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# Resources:
2+
# - https://developer.apple.com/support/downloads/Apple-File-System-Reference.pdf
3+
# - https://github.com/sgan81/apfs-fuse
4+
# - https://github.com/linux-apfs/linux-apfs-rw
15
from dissect.cstruct import cstruct
26

37
apfs_def = """

dissect/apfs/cursor.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ def last(self) -> bool:
8484
def next(self) -> bool:
8585
"""Move the cursor to the next item in the B-Tree."""
8686
if self._node.is_nonleaf:
87-
# Treat as if we were at the first key in a leaf node
87+
# Treat as if we were at the first key
8888
self.first()
8989
return self._node.nkeys != 0
9090

@@ -112,7 +112,9 @@ def next(self) -> bool:
112112
def prev(self) -> bool:
113113
"""Move the cursor to the previous item in the B-Tree."""
114114
if self._node.is_nonleaf:
115-
raise ValueError("Cannot call prev() on a non-leaf node")
115+
# Treat as if we were at the last key
116+
self.last()
117+
return self._node.nkeys != 0
116118

117119
if self._idx - 1 >= 0:
118120
self._idx -= 1
@@ -138,7 +140,7 @@ def prev(self) -> bool:
138140

139141
return True
140142

141-
def push(self) -> None:
143+
def push(self) -> Self:
142144
"""Push down to the child node at the current index."""
143145
child_node = self.btree._node_child(self._node, self._idx, self.omap, self.oid, self.xid)
144146

@@ -148,7 +150,7 @@ def push(self) -> None:
148150

149151
return self
150152

151-
def pop(self) -> None:
153+
def pop(self) -> Self:
152154
"""Pop back to the parent node."""
153155
if not self._stack:
154156
raise IndexError("Cannot pop from an empty stack")

dissect/apfs/objects/fs.py

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from collections.abc import Iterator
2626
from datetime import datetime
2727

28+
from dissect.apfs.objects.btree_node import BTreeNode
2829
from dissect.apfs.objects.keybag import VolumeKeybag
2930

3031

@@ -151,7 +152,7 @@ def is_onekey(self) -> bool:
151152
def formatted_by(self) -> tuple[str, datetime, int]:
152153
"""Information about the tool that formatted the filesystem."""
153154
return (
154-
self.object.apfs_formatted_by.id.decode().split("\x00", 1)[0],
155+
self.object.apfs_formatted_by.id.split(b"\x00", 1)[0].decode(),
155156
from_unix(self.object.apfs_formatted_by.timestamp),
156157
self.object.apfs_formatted_by.last_xid,
157158
)
@@ -166,7 +167,7 @@ def modified_by(self) -> list[tuple[str, datetime, int]]:
166167

167168
result.append(
168169
(
169-
entry.id.decode().split("\x00", 1)[0],
170+
entry.id.split(b"\x00", 1)[0].decode(),
170171
from_unix(entry.timestamp),
171172
entry.last_xid,
172173
)
@@ -224,7 +225,7 @@ def unlock(self, password: str, uuid: UUID | str | None = None) -> None:
224225
raise Error("No VEK found for this volume")
225226

226227
for kek in self.keybag.keks():
227-
if uuid is not None and str(kek.uuid) != uuid:
228+
if uuid is not None and str(kek.uuid) != str(uuid):
228229
continue
229230

230231
if not kek.verify():
@@ -245,7 +246,7 @@ def cursor(self) -> Cursor:
245246
"""Create a new cursor for the volume's root B-tree."""
246247
return Cursor(self.root_tree, self.omap, self.object.apfs_root_tree_oid if self.is_sealed else 0, self.xid)
247248

248-
def _cursor_state(self, oid: int) -> Any:
249+
def _cursor_state(self, oid: int) -> tuple[BTreeNode, int, list[tuple[BTreeNode, int]]]:
249250
"""Precompute the cursor state for a given object ID.
250251
251252
Args:
@@ -308,6 +309,7 @@ def inode(self, oid: int | str, sibling_id: int | None = None) -> INode:
308309
309310
Args:
310311
oid: The object ID of the inode to retrieve.
312+
sibling_id: The sibling ID of the inode to retrieve, if applicable.
311313
"""
312314
if isinstance(oid, str):
313315
if ":" not in oid:
@@ -321,12 +323,10 @@ def inodes(self) -> Iterator[INode]:
321323

322324
for key, value in cursor.walk():
323325
oid, type = parse_fs_object_key(key)
324-
if type != c_apfs.APFS_TYPE.INODE:
325-
continue
326-
327-
inode = self.inode(oid)
328-
inode._inode_raw = value
329-
yield inode
326+
if type == c_apfs.APFS_TYPE.INODE:
327+
inode = self.inode(oid)
328+
inode._inode_raw = value
329+
yield inode
330330

331331
def get(self, path: str | int | DirectoryEntry, node: INode | None = None) -> INode:
332332
"""Get an inode by its path, object ID, or directory entry.
@@ -406,6 +406,7 @@ class INode:
406406
Args:
407407
volume: Parent APFS volume.
408408
oid: The object ID of the inode.
409+
sibling_id: The sibling ID of the inode, if applicable.
409410
"""
410411

411412
def __init__(self, volume: FS, oid: int, sibling_id: int | None = None):
@@ -476,10 +477,7 @@ def parent(self) -> INode:
476477
def parents(self) -> Iterator[INode]:
477478
"""Iterate over the parent inodes of this inode, up to the root."""
478479
obj = self
479-
while True:
480-
if obj.parent is obj:
481-
break
482-
480+
while obj.parent is not obj:
483481
obj = obj.parent
484482
yield obj
485483

@@ -607,7 +605,7 @@ def siblings(self) -> list[INode]:
607605

608606
@cached_property
609607
def sibling_link(self) -> tuple[int, str] | None:
610-
"""The sibling link (``parent_id``, ``name`` tuple) of this inode, if available."""
608+
"""The sibling link (``parent_id``, ``name``) tuple of this inode, if available."""
611609
if self.sibling_id is not None:
612610
for _, key, value in self.volume._records(self.oid, c_apfs.APFS_TYPE.SIBLING_LINK):
613611
if c_apfs.j_sibling_key(key).sibling_id == self.sibling_id:
@@ -637,7 +635,7 @@ def names(self) -> list[str]:
637635
def path(self) -> str:
638636
"""The full path of this inode, if available."""
639637
parts = [self.name or f"<unlinked:{self.oid}>"]
640-
parts.extend(parent.name or f"<unlinked:{self.oid}>" for parent in self.parents)
638+
parts.extend(parent.name or f"<unlinked:{parent.oid}>" for parent in self.parents)
641639

642640
return "/" + "/".join(parts[::-1])
643641

@@ -689,7 +687,7 @@ def readlink(self) -> str:
689687

690688
return self.xattr[c_apfs.SYMLINK_EA_NAME].open().read().decode().rstrip("\x00")
691689

692-
def open(self) -> FileStream:
690+
def open(self) -> BufferedStream | DecmpfsStream | FileStream:
693691
"""Open a stream for reading the inode data."""
694692
if self.is_compressed():
695693
return DecmpfsStream(self)

dissect/apfs/objects/keybag.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ def verify(self) -> bool:
179179
)
180180

181181
def unwrap(self, password: str) -> bytes:
182-
"""Unwrap the KEK using the given key."""
182+
"""Unwrap the KEK using the given password."""
183183
blob = self["blob"]
184184
key = hashlib.pbkdf2_hmac(
185185
"sha256",

dissect/apfs/stream.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ def __init__(self, volume: FS, oid: int, size: int):
2929
self.volume = volume
3030
self.oid = oid
3131

32+
if self.volume.is_sealed:
33+
self._cursor = Cursor(self.volume.fext_tree)
34+
else:
35+
self._cursor = self.volume.cursor()
36+
3237
super().__init__(size, self.volume.container.block_size)
3338

3439
def _lookup(self, offset: int) -> tuple[int, int, int, int]:
@@ -38,23 +43,22 @@ def _lookup(self, offset: int) -> tuple[int, int, int, int]:
3843
return self._lookup_normal(offset)
3944

4045
def _lookup_normal(self, offset: int) -> tuple[int, int, int, int]:
41-
cursor = self.volume.cursor()
42-
cursor.search(
46+
self._cursor.reset().search(
4347
(
4448
(self.oid, c_apfs.APFS_TYPE.FILE_EXTENT.value),
4549
offset,
4650
),
4751
cmp=cmp_fs_extent,
4852
)
4953

50-
key = c_apfs.j_file_extent_key(cursor.key())
54+
key = c_apfs.j_file_extent_key(self._cursor.key())
5155
oid = key.hdr.obj_id_and_type & c_apfs.OBJ_ID_MASK
5256
type = (key.hdr.obj_id_and_type & c_apfs.OBJ_TYPE_MASK) >> c_apfs.OBJ_TYPE_SHIFT
5357

5458
if oid != self.oid or type != c_apfs.APFS_TYPE.FILE_EXTENT or key.logical_addr > offset:
5559
raise Error(f"Could not find file extent for {self.oid} at offset {offset}")
5660

57-
value = c_apfs.j_file_extent_val(cursor.value())
61+
value = c_apfs.j_file_extent_val(self._cursor.value())
5862

5963
return (
6064
key.logical_addr,
@@ -64,14 +68,13 @@ def _lookup_normal(self, offset: int) -> tuple[int, int, int, int]:
6468
)
6569

6670
def _lookup_sealed(self, offset: int) -> tuple[int, int, int, int]:
67-
cursor = Cursor(self.volume.fext_tree)
68-
cursor.search((self.oid, offset), cmp=cmp_fext)
71+
self._cursor.reset().search((self.oid, offset), cmp=cmp_fext)
6972

70-
key = c_apfs.fext_tree_key(cursor.key())
73+
key = c_apfs.fext_tree_key(self._cursor.key())
7174
if key.private_id != self.oid or key.logical_addr > offset:
7275
raise Error(f"Could not find file extent for {self.oid} at offset {offset}")
7376

74-
value = c_apfs.fext_tree_val(cursor.value())
77+
value = c_apfs.fext_tree_val(self._cursor.value())
7578

7679
return (
7780
key.logical_addr,

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "dissect.apfs"
7-
description = "A Dissect module implementing a parser for the APFS file system, a commonly used Apple filesystem"
7+
description = "A Dissect module implementing a parser for the APFS file system, a commonly used Apple file system"
88
readme = "README.md"
99
requires-python = ">=3.10.0"
1010
license = "AGPL-3.0-or-later"

0 commit comments

Comments
 (0)