6
6
import stat
7
7
from datetime import datetime
8
8
from functools import lru_cache
9
- from typing import BinaryIO , Iterator , Optional , Union
9
+ from typing import BinaryIO , Iterator
10
10
from uuid import UUID
11
11
12
12
from dissect .util import ts
@@ -55,7 +55,7 @@ def __init__(self, fh: BinaryIO):
55
55
56
56
self .root = self .get_inode (self .sb .sb_rootino )
57
57
58
- def get (self , path : Union [ int , str ] , node : Optional [ INode ] = None ) -> INode :
58
+ def get (self , path : int | str , node : INode | None = None ) -> INode :
59
59
if isinstance (path , int ):
60
60
return self .get_inode (path )
61
61
@@ -102,14 +102,14 @@ def walk_extents(self, block: int) -> Iterator[tuple[int, int, int, int]]:
102
102
for record in self .walk_large_tree (block , 16 , (c_xfs .XFS_BMAP_MAGIC , c_xfs .XFS_BMAP_CRC_MAGIC )):
103
103
yield parse_fsblock (record )
104
104
105
- def walk_large_tree (self , block : int , leaf_size : int , magic : Optional [ list [int ]] = None ) -> Iterator [bytes ]:
105
+ def walk_large_tree (self , block : int , leaf_size : int , magic : list [int ] | None = None ) -> Iterator [bytes ]:
106
106
self .fh .seek (block * self .block_size )
107
107
root = self ._lblock_s (self .fh )
108
108
109
109
yield from self ._walk_large_tree (root , leaf_size , magic )
110
110
111
111
def walk_small_tree (
112
- self , block : int , agnum : int , leaf_size : int , magic : Optional [ list [int ]] = None
112
+ self , block : int , agnum : int , leaf_size : int , magic : list [int ] | None = None
113
113
) -> Iterator [bytes ]:
114
114
block = agnum * self .sb .sb_agblocks + block
115
115
self .fh .seek (block * self .block_size )
@@ -122,7 +122,7 @@ def _walk_small_tree(
122
122
node : c_xfs .xfs_btree_sblock | c_xfs .xfs_btree_sblock_crc ,
123
123
leaf_size : int ,
124
124
agnum : int ,
125
- magic : Optional [ list [int ]] = None ,
125
+ magic : list [int ] | None = None ,
126
126
) -> Iterator [bytes ]:
127
127
fh = self .fh
128
128
if magic and node .bb_magic not in magic :
@@ -148,7 +148,7 @@ def _walk_large_tree(
148
148
self ,
149
149
node : c_xfs .xfs_btree_lblock | c_xfs .xfs_btree_lblock_crc ,
150
150
leaf_size : int ,
151
- magic : Optional [ list [int ]] = None ,
151
+ magic : list [int ] | None = None ,
152
152
) -> Iterator [bytes ]:
153
153
fh = self .fh
154
154
if magic and node .bb_magic not in magic :
@@ -210,9 +210,9 @@ def __init__(self, xfs: XFS, fh: BinaryIO, num: int):
210
210
def get_inode (
211
211
self ,
212
212
inum : int ,
213
- filename : Optional [ str ] = None ,
214
- filetype : Optional [ int ] = None ,
215
- parent : Optional [ INode ] = None ,
213
+ filename : str | None = None ,
214
+ filetype : int | None = None ,
215
+ parent : INode | None = None ,
216
216
lazy : bool = False ,
217
217
) -> INode :
218
218
inode = INode (self , inum , filename , filetype , parent = parent )
@@ -230,7 +230,7 @@ def walk_extents(self, fsb: int) -> Iterator[tuple[int, int, int, int]]:
230
230
def walk_agi (self ) -> Iterator [c_xfs .xfs_inobt_rec ]:
231
231
yield from self .xfs .walk_agi (self .agi .agi_root , self .num )
232
232
233
- def walk_tree (self , fsb : int , magic : Optional [ list [int ]] = None , small : bool = False ):
233
+ def walk_tree (self , fsb : int , magic : list [int ] | None = None , small : bool = False ) -> Iterator [ bytes ] :
234
234
agnum , blknum = fsb_to_bb (fsb , self .sb .sb_agblklog )
235
235
block = agnum * self .xfs .sb .sb_agblocks + blknum
236
236
@@ -245,10 +245,10 @@ def __init__(
245
245
self ,
246
246
ag : AllocationGroup ,
247
247
inum : int ,
248
- filename : Optional [ str ] = None ,
249
- filetype : Optional [ int ] = None ,
250
- parent : Optional [ INode ] = None ,
251
- ):
248
+ filename : str | None = None ,
249
+ filetype : int | None = None ,
250
+ parent : INode | None = None ,
251
+ ) -> None :
252
252
self .ag = ag
253
253
self .xfs = ag .xfs
254
254
self .inum = inum + (ag .num << ag ._inum_bits )
@@ -325,7 +325,17 @@ def link(self) -> str:
325
325
326
326
if not self ._link :
327
327
if self .inode .di_format != c_xfs .xfs_dinode_fmt .XFS_DINODE_FMT_LOCAL and self .xfs .version == 5 :
328
- fh = self .open ()
328
+ # Almost always, symlinks (max size of 1024) fit within a block. If the block size if 512, we might
329
+ # need three blocks. These three blocks could theoretially be distributed over multiple extents.
330
+ # Linux kernel handles this by using sl_offset to piece the symlink back together.
331
+ # As this edge case of an edge case is very unlikely, it is unsupported until we observe it.
332
+ # Ticket: https://github.com/fox-it/dissect.xfs/issues/36
333
+ if len (self .dataruns ()) > 1 :
334
+ raise NotImplementedError (f"{ self !r} has a symlink distributed over multiple extents" )
335
+
336
+ # We do not use open because for non-resident symlinks self.size does not include the symlink header
337
+ symlink_size = len (c_xfs .xfs_dsymlink_hdr ) + self .size
338
+ fh = RunlistStream (self .xfs .fh , self .dataruns (), symlink_size , self .xfs .block_size )
329
339
330
340
header = c_xfs .xfs_dsymlink_hdr (fh )
331
341
if header .sl_magic != c_xfs .XFS_SYMLINK_MAGIC :
@@ -514,7 +524,7 @@ def attrfork(self) -> BinaryIO:
514
524
515
525
return RangeStream (self ._buf , offset , size )
516
526
517
- def dataruns (self ) -> list [tuple [Optional [ int ] , int ]]:
527
+ def dataruns (self ) -> list [tuple [int | None , int ]]:
518
528
if not self ._runlist :
519
529
runs = []
520
530
run_offset = 0
0 commit comments