Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix support for long non-residential symlinks #35

Merged
merged 5 commits into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 18 additions & 15 deletions dissect/xfs/xfs.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import stat
from datetime import datetime
from functools import lru_cache
from typing import BinaryIO, Iterator, Optional, Union
from typing import BinaryIO, Iterator
from uuid import UUID

from dissect.util import ts
Expand Down Expand Up @@ -55,7 +55,7 @@ def __init__(self, fh: BinaryIO):

self.root = self.get_inode(self.sb.sb_rootino)

def get(self, path: Union[int, str], node: Optional[INode] = None) -> INode:
def get(self, path: int | str, node: INode | None = None) -> INode:
if isinstance(path, int):
return self.get_inode(path)

Expand Down Expand Up @@ -102,14 +102,14 @@ def walk_extents(self, block: int) -> Iterator[tuple[int, int, int, int]]:
for record in self.walk_large_tree(block, 16, (c_xfs.XFS_BMAP_MAGIC, c_xfs.XFS_BMAP_CRC_MAGIC)):
yield parse_fsblock(record)

def walk_large_tree(self, block: int, leaf_size: int, magic: Optional[list[int]] = None) -> Iterator[bytes]:
def walk_large_tree(self, block: int, leaf_size: int, magic: list[int] | None = None) -> Iterator[bytes]:
self.fh.seek(block * self.block_size)
root = self._lblock_s(self.fh)

yield from self._walk_large_tree(root, leaf_size, magic)

def walk_small_tree(
self, block: int, agnum: int, leaf_size: int, magic: Optional[list[int]] = None
self, block: int, agnum: int, leaf_size: int, magic: list[int] | None = None
) -> Iterator[bytes]:
block = agnum * self.sb.sb_agblocks + block
self.fh.seek(block * self.block_size)
Expand All @@ -122,7 +122,7 @@ def _walk_small_tree(
node: c_xfs.xfs_btree_sblock | c_xfs.xfs_btree_sblock_crc,
leaf_size: int,
agnum: int,
magic: Optional[list[int]] = None,
magic: list[int] | None = None,
) -> Iterator[bytes]:
fh = self.fh
if magic and node.bb_magic not in magic:
Expand All @@ -148,7 +148,7 @@ def _walk_large_tree(
self,
node: c_xfs.xfs_btree_lblock | c_xfs.xfs_btree_lblock_crc,
leaf_size: int,
magic: Optional[list[int]] = None,
magic: list[int] | None = None,
) -> Iterator[bytes]:
fh = self.fh
if magic and node.bb_magic not in magic:
Expand Down Expand Up @@ -210,9 +210,9 @@ def __init__(self, xfs: XFS, fh: BinaryIO, num: int):
def get_inode(
self,
inum: int,
filename: Optional[str] = None,
filetype: Optional[int] = None,
parent: Optional[INode] = None,
filename: str | None = None,
filetype: int | None = None,
parent: INode | None = None,
lazy: bool = False,
) -> INode:
inode = INode(self, inum, filename, filetype, parent=parent)
Expand All @@ -230,7 +230,7 @@ def walk_extents(self, fsb: int) -> Iterator[tuple[int, int, int, int]]:
def walk_agi(self) -> Iterator[c_xfs.xfs_inobt_rec]:
yield from self.xfs.walk_agi(self.agi.agi_root, self.num)

def walk_tree(self, fsb: int, magic: Optional[list[int]] = None, small: bool = False):
def walk_tree(self, fsb: int, magic: list[int] | None = None, small: bool = False):
agnum, blknum = fsb_to_bb(fsb, self.sb.sb_agblklog)
block = agnum * self.xfs.sb.sb_agblocks + blknum

Expand All @@ -245,9 +245,9 @@ def __init__(
self,
ag: AllocationGroup,
inum: int,
filename: Optional[str] = None,
filetype: Optional[int] = None,
parent: Optional[INode] = None,
filename: str | None = None,
filetype: int | None = None,
parent: INode | None = None,
):
self.ag = ag
self.xfs = ag.xfs
Expand Down Expand Up @@ -325,7 +325,10 @@ def link(self) -> str:

if not self._link:
if self.inode.di_format != c_xfs.xfs_dinode_fmt.XFS_DINODE_FMT_LOCAL and self.xfs.version == 5:
fh = self.open()
# We do not use open because for non-resident symlinks self.size does not include the symlink header
runs = self.dataruns()
symlink_size = c_xfs.xfs_dsymlink_hdr.size + self.size
fh = RunlistStream(self.xfs.fh, runs, symlink_size, self.xfs.block_size)

header = c_xfs.xfs_dsymlink_hdr(fh)
if header.sl_magic != c_xfs.XFS_SYMLINK_MAGIC:
Expand Down Expand Up @@ -514,7 +517,7 @@ def attrfork(self) -> BinaryIO:

return RangeStream(self._buf, offset, size)

def dataruns(self) -> list[tuple[Optional[int], int]]:
def dataruns(self) -> list[tuple[int | None, int]]:
if not self._runlist:
runs = []
run_offset = 0
Expand Down
1 change: 1 addition & 0 deletions tests/data/.gitattributes
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While it's maybe a bit nicer to have this file in tests/data, it's more consistent with other dissect projects to have it in the project root instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apparently there already was a .gitattributes in the project root, so it is also more consistent with this project :)
Fixed.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
xfs_symlink_long.bin.gz filter=lfs diff=lfs merge=lfs -text
3 changes: 3 additions & 0 deletions tests/data/xfs_symlink_long.bin.gz
Git LFS file not shown
5 changes: 4 additions & 1 deletion tests/test_xfs.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ def test_xfs_bigtime(xfs_bigtime_bin):
("tests/data/xfs_symlink_test1.bin.gz"),
("tests/data/xfs_symlink_test2.bin.gz"),
("tests/data/xfs_symlink_test3.bin.gz"),
("tests/data/xfs_symlink_long.bin.gz"),
],
)
def test_symlinks(image_file):
Expand All @@ -85,4 +86,6 @@ def resolve(node):
return node

with gzip.open(image_file, "rb") as disk:
assert resolve(XFS(disk).get(path)).open().read() == expect
link_inode = resolve(XFS(disk).get(path))
assert link_inode.nblocks == 1
assert link_inode.open().read() == expect