Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/unblob/handlers/filesystem/ufs.py: 43%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

180 statements  

1import io 

2import stat 

3import struct 

4from collections.abc import Iterator 

5from pathlib import Path 

6 

7from unblob.file_utils import ( 

8 Endian, 

9 FileSystem, 

10 InvalidInputFormat, 

11 StructParser, 

12) 

13from unblob.models import ( 

14 Extractor, 

15 ExtractResult, 

16 File, 

17 HandlerDoc, 

18 HandlerType, 

19 HexString, 

20 Reference, 

21 StructHandler, 

22 ValidChunk, 

23) 

24 

25UFS_C_DEFINITION = """ 

26 #define UFS_MAXMNTLEN 512 

27 #define UFS2_MAXMNTLEN 468 

28 #define UFS2_MAXVOLLEN 32 

29 #define UFS_MAXCSBUFS 31 

30 #define UFS2_NOCSPTRS 28 

31 

32 struct ufs_csum { 

33 uint32 cs_ndir; 

34 uint32 cs_nbfree; 

35 uint32 cs_nifree; 

36 uint32 cs_nffree; 

37 }; 

38 

39 struct ufs2_csum_total { 

40 uint64 cs_ndir; 

41 uint64 cs_nbfree; 

42 uint64 cs_nifree; 

43 uint64 cs_nffree; 

44 uint64 cs_numclusters; 

45 uint64 cs_spare[3]; 

46 }; 

47 

48 struct ufs_timeval { 

49 uint32 tv_sec; 

50 uint32 tv_usec; 

51 }; 

52 

53 struct ufs_super_block { 

54 union { 

55 struct { 

56 uint32 fs_link; /* UNUSED */ 

57 } fs_42; 

58 struct { 

59 uint32 fs_state; /* file system state flag */ 

60 } fs_sun; 

61 } fs_u0; 

62 

63 uint32 fs_rlink; /* UNUSED */ 

64 uint32 fs_sblkno; /* addr of super-block in filesys */ 

65 uint32 fs_cblkno; /* offset of cyl-block in filesys */ 

66 uint32 fs_iblkno; /* offset of inode-blocks in filesys */ 

67 uint32 fs_dblkno; /* offset of first data after cg */ 

68 uint32 fs_cgoffset; /* cylinder group offset in cylinder */ 

69 uint32 fs_cgmask; /* used to calc mod fs_ntrak */ 

70 uint32 fs_time; /* last time written */ 

71 uint32 fs_size; /* number of blocks in fs */ 

72 uint32 fs_dsize; /* number of data blocks in fs */ 

73 uint32 fs_ncg; /* number of cylinder groups */ 

74 uint32 fs_bsize; /* size of basic blocks in fs */ 

75 uint32 fs_fsize; /* size of frag blocks in fs */ 

76 uint32 fs_frag; /* number of frags in a block in fs */ 

77 /* configuration parameters */ 

78 uint32 fs_minfree; /* minimum percentage of free blocks */ 

79 uint32 fs_rotdelay; /* num of ms for optimal next block */ 

80 uint32 fs_rps; /* disk revolutions per second */ 

81 /* fields computed from the others */ 

82 uint32 fs_bmask; /* blkoff calc of blk offsets */ 

83 uint32 fs_fmask; /* fragoff calc of frag offsets */ 

84 uint32 fs_bshift; /* lblkno calc of logical blkno */ 

85 uint32 fs_fshift; /* numfrags calc number of frags */ 

86 /* configuration parameters */ 

87 uint32 fs_maxcontig; /* max number of contiguous blks */ 

88 uint32 fs_maxbpg; /* max number of blks per cyl group */ 

89 /* fields computed from the others */ 

90 uint32 fs_fragshift; /* block to frag shift */ 

91 uint32 fs_fsbtodb; /* fsbtodb and dbtofsb shift constant */ 

92 uint32 fs_sbsize; /* actual size of super block */ 

93 uint32 fs_csmask; /* csum block offset */ 

94 uint32 fs_csshift; /* csum block number */ 

95 uint32 fs_nindir; /* value of NINDIR */ 

96 uint32 fs_inopb; /* value of INOPB */ 

97 uint32 fs_nspf; /* value of NSPF */ 

98 /* optimization preference */ 

99 uint32 fs_optim; 

100 

101 /* fields derived from the hardware */ 

102 union { 

103 struct { 

104 uint32 fs_npsect; /* # sectors/track including spares */ 

105 } fs_sun; 

106 struct { 

107 uint32 fs_state; /* file system state time stamp */ 

108 } fs_sunx86; 

109 } fs_u1; 

110 

111 uint32 fs_interleave; /* hardware sector interleave */ 

112 uint32 fs_trackskew; /* sector 0 skew, per track */ 

113 uint32 fs_id[2]; /* file system id */ 

114 /* sizes determined by number of cylinder groups and their sizes */ 

115 uint32 fs_csaddr; /* blk addr of cyl grp summary area */ 

116 uint32 fs_cssize; /* size of cyl grp summary area */ 

117 uint32 fs_cgsize; /* cylinder group size */ 

118 /* fields derived from the hardware */ 

119 uint32 fs_ntrak; /* tracks per cylinder */ 

120 uint32 fs_nsect; /* sectors per track */ 

121 uint32 fs_spc; /* sectors per cylinder */ 

122 /* this comes from the disk driver partitioning */ 

123 uint32 fs_ncyl; /* cylinders in file system */ 

124 /* fields computed from the others */ 

125 uint32 fs_cpg; /* cylinders per group */ 

126 uint32 fs_inodes_per_group; /* inodes per cylinder group */ 

127 uint32 fs_frags_per_group; /* blocks per group * fs_frag */ 

128 

129 /* this data must be re-computed after crashes */ 

130 struct ufs_csum fs_cstotal; /* cylinder summary information */ 

131 

132 /* fields cleared at mount time */ 

133 int8 fs_fmod; /* super block modified flag */ 

134 int8 fs_clean; /* file system is clean flag */ 

135 int8 fs_ronly; /* mounted read-only flag */ 

136 int8 fs_flags; 

137 

138 union { 

139 struct { 

140 int8 fs_fsmnt[UFS_MAXMNTLEN]; /* name mounted on */ 

141 uint32 fs_cgrotor; /* last cg searched */ 

142 uint32 fs_csp[UFS_MAXCSBUFS]; /* list of fs_cs info buffers */ 

143 uint32 fs_maxcluster; 

144 uint32 fs_cpc; /* cyl per cycle in postbl */ 

145 uint16 fs_opostbl[16][8]; /* old rotation block list head */ 

146 } fs_u1; 

147 struct { 

148 int8 fs_fsmnt[UFS2_MAXMNTLEN]; /* name mounted on */ 

149 uint8 fs_volname[UFS2_MAXVOLLEN]; /* volume name */ 

150 uint64 fs_swuid; /* system-wide uid */ 

151 uint32 fs_pad; /* due to alignment of fs_swuid */ 

152 uint32 fs_cgrotor; /* last cg searched */ 

153 uint32 fs_ocsp[UFS2_NOCSPTRS]; /* list of fs_cs info buffers */ 

154 uint32 fs_contigdirs; /* # of contiguously allocated dirs */ 

155 uint32 fs_csp; /* cg summary info buffer */ 

156 uint32 fs_maxcluster; 

157 uint32 fs_active; /* used by snapshots to track fs */ 

158 uint32 fs_old_cpc; /* cyl per cycle in postbl */ 

159 uint32 fs_maxbsize; /* maximum blocking factor permitted */ 

160 uint64 fs_sparecon64[17]; /* old rotation block list head */ 

161 uint64 fs_sblockloc; /* byte offset of standard superblock */ 

162 struct ufs2_csum_total fs_cstotal; /* cylinder summary information */ 

163 struct ufs_timeval fs_time; /* last time written */ 

164 uint64 fs_size_64; /* number of blocks in fs */ 

165 uint64 fs_dsize; /* number of data blocks in fs */ 

166 uint64 fs_csaddr; /* blk addr of cyl grp summary area */ 

167 uint64 fs_pendingblocks; /* blocks in process of being freed */ 

168 uint32 fs_pendinginodes; /* inodes in process of being freed */ 

169 } fs_u2; 

170 } fs_u11; 

171 

172 union { 

173 struct { 

174 uint32 fs_sparecon[53]; /* reserved for future constants */ 

175 uint32 fs_reclaim; 

176 uint32 fs_sparecon2[1]; 

177 uint32 fs_state; /* file system state time stamp */ 

178 uint32 fs_qbmask[2]; /* ~usb_bmask */ 

179 uint32 fs_qfmask[2]; /* ~usb_fmask */ 

180 } fs_sun; 

181 struct { 

182 uint32 fs_sparecon[53]; /* reserved for future constants */ 

183 uint32 fs_reclaim; 

184 uint32 fs_sparecon2[1]; 

185 uint32 fs_npsect; /* # sectors/track including spares */ 

186 uint32 fs_qbmask[2]; /* ~usb_bmask */ 

187 uint32 fs_qfmask[2]; /* ~usb_fmask */ 

188 } fs_sunx86; 

189 struct { 

190 uint32 fs_sparecon[50]; /* reserved for future constants */ 

191 uint32 fs_contigsumsize; /* size of cluster summary array */ 

192 uint32 fs_maxsymlinklen; /* max length of an internal symlink */ 

193 uint32 fs_inodefmt; /* format of on-disk inodes */ 

194 uint32 fs_maxfilesize[2]; /* max representable file size */ 

195 uint32 fs_qbmask[2]; /* ~usb_bmask */ 

196 uint32 fs_qfmask[2]; /* ~usb_fmask */ 

197 uint32 fs_state; /* file system state time stamp */ 

198 } fs_44; 

199 } fs_u2_arch; 

200 

201 uint32 fs_postblformat; /* format of positional layout tables */ 

202 uint32 fs_nrpos; /* number of rotational positions */ 

203 uint32 fs_postbloff; /* rotation block list head */ 

204 uint32 fs_rotbloff; /* blocks for each rotation */ 

205 uint32 fs_magic; /* magic number */ 

206 uint8 fs_space[1]; /* list of blocks for each rotation */ 

207} ufs_superblock_t; 

208 

209 #define UFS_NDADDR 12 

210 #define UFS_NINDIR 3 

211 

212 /* UFS1 FreeBSD & Solaris */ 

213 struct ufs1_inode { 

214 uint16 mode; /* 0x0 */ 

215 uint16 nlink; /* 0x2 */ 

216 union { 

217 struct { 

218 uint16 suid; /* 0x4 */ 

219 uint16 sgid; /* 0x6 */ 

220 } oldids; 

221 uint32 inumber; /* 0x4 lsf: inode number */ 

222 uint32 author; /* 0x4 GNU HURD: author */ 

223 } u1; 

224 uint64 size; /* 0x8 */ 

225 struct ufs_timeval atime; /* 0x10 access */ 

226 struct ufs_timeval mtime; /* 0x18 modification */ 

227 struct ufs_timeval ctime; /* 0x20 creation */ 

228 union { 

229 struct { 

230 uint32 direct_blocks[UFS_NDADDR];/* 0x28 data blocks */ 

231 uint32 indirect_blocks[UFS_NINDIR];/* 0x58 indirect blocks */ 

232 } addr; 

233 uint8 symlink[4*(UFS_NDADDR+UFS_NINDIR)];/* 0x28 fast symlink */ 

234 } u2; 

235 uint32 flags; /* 0x64 immutable, append-only... */ 

236 uint32 blocks; /* 0x68 blocks in use */ 

237 uint32 gen; /* 0x6c like ext2 i_version, for NFS support */ 

238 union { 

239 struct { 

240 uint32 shadow; /* 0x70 shadow inode with security data */ 

241 uint32 uid; /* 0x74 long EFT version of uid */ 

242 uint32 gid; /* 0x78 long EFT version of gid */ 

243 uint32 oeftflag; /* 0x7c reserved */ 

244 } sun; 

245 struct { 

246 uint32 uid; /* 0x70 File owner */ 

247 uint32 gid; /* 0x74 File group */ 

248 uint32 spare[2]; /* 0x78 reserved */ 

249 } bsd44; 

250 struct { 

251 uint32 uid; /* 0x70 */ 

252 uint32 gid; /* 0x74 */ 

253 uint16 modeh; /* 0x78 mode high bits */ 

254 uint16 spare; /* 0x7A unused */ 

255 uint32 trans; /* 0x7c filesystem translator */ 

256 } hurd; 

257 } u3; 

258 } ufs1_inode_t; 

259 

260 #define UFS_NXADDR 2 

261 

262 /* ---- UFS2 on-disk inode (256 bytes) ---- */ 

263 struct ufs2_inode { 

264 uint16 mode; /* 0: IFMT, permissions; see below. */ 

265 uint16 nlink; /* 2: File link count. */ 

266 uint32 uid; /* 4: File owner. */ 

267 uint32 gid; /* 8: File group. */ 

268 uint32 blksize; /* 12: Inode blocksize. */ 

269 uint64 size; /* 16: File byte count. */ 

270 uint64 blocks; /* 24: Bytes actually held. */ 

271 uint64 atime; /* 32: Last access time. */ 

272 uint64 mtime; /* 40: Last modified time. */ 

273 uint64 ctime; /* 48: Last inode change time. */ 

274 uint64 birthtime; /* 56: Inode creation time. */ 

275 uint32 mtimensec; /* 64: Last modified time. */ 

276 uint32 atimensec; /* 68: Last access time. */ 

277 uint32 ctimensec; /* 72: Last inode change time. */ 

278 uint32 birthnsec; /* 76: Inode creation time. */ 

279 uint32 gen; /* 80: Generation number. */ 

280 uint32 kernflags; /* 84: Kernel flags. */ 

281 uint32 flags; /* 88: Status flags (chflags). */ 

282 uint32 extsize; /* 92: External attributes block. */ 

283 uint64 extb[UFS_NXADDR];/* 96: External attributes block. */ 

284 union { 

285 struct { 

286 uint64 direct_blocks[UFS_NDADDR]; /* 112: Direct disk blocks. */ 

287 uint64 indirect_blocks[UFS_NINDIR];/* 208: Indirect disk blocks.*/ 

288 } addr; 

289 uint8 symlink[2*4*(UFS_NDADDR+UFS_NINDIR)];/* 0x28 fast symlink */ 

290 } u2; 

291 uint64 spare[3]; /* 232: Reserved; currently unused */ 

292 } ufs2_inode_t; 

293 

294 /* New OpenBSD & FreeBSD */ 

295 

296 struct ufs_dirent { 

297 uint32 d_ino; /* inode number of this entry */ 

298 uint16 d_reclen; /* length of this entry */ 

299 uint8 d_type; /* file type */ 

300 uint8 d_namlen; 

301 char d_name[d_namlen]; 

302 }; 

303 

304 /* Solaris & old xBSD (no type field) */ 

305 struct old_ufs_dirent { 

306 uint32 d_ino; 

307 uint16 d_reclen; 

308 uint16 d_namlen; 

309 char d_name[d_namlen]; 

310 } 

311 

312""" 

313 

314MAGIC_OFFSET = 0x55C # relative to SB_OFFSET 

315UFS_ROOT_INO = 2 

316DELETED_INO = 0 

317MAX_BLOCK_SIZE = 65536 # FreeBSD MAXBSIZE (sys/sys/param.h) 

318 

319 

320class UFSParser: 

321 INODE_STRUCT: str 

322 INODE_SIZE: int 

323 PTR_SIZE: int 

324 DIRENT_STRUCT: str = "ufs_dirent" 

325 

326 def __init__(self, file: File, sb_offset: int): 

327 self.file = file 

328 self._struct_parser = StructParser(UFS_C_DEFINITION) 

329 self.file.seek(sb_offset, io.SEEK_SET) 

330 self.super_block = self._struct_parser.parse( 

331 "ufs_superblock_t", self.file, Endian.LITTLE 

332 ) 

333 

334 def walk_extract(self, fs: FileSystem, ino_num: int, path: Path): # noqa: C901 

335 inode = self.read_inode(ino_num) 

336 file_type = stat.S_IFMT(inode.mode) 

337 

338 match file_type: 

339 case stat.S_IFDIR: 

340 fs.mkdir(path, exist_ok=True) 

341 for child_ino, name in self.parse_dentries(inode): 

342 if name in (".", ".."): 

343 continue 

344 self.walk_extract(fs, child_ino, path / name) 

345 case stat.S_IFREG: 

346 fs.write_chunks(path, self.read_file_content(inode)) 

347 case stat.S_IFLNK: 

348 fs.create_symlink(src=Path(self.read_symlink(inode)), dst=path) 

349 case stat.S_IFIFO: 

350 fs.mkfifo(path) 

351 case stat.S_IFSOCK: 

352 fs.mknod(path, mode=inode.mode) 

353 case stat.S_IFCHR | stat.S_IFBLK: 

354 fs.mknod(path, mode=inode.mode, device=self.get_direct_blocks(inode)[0]) 

355 

356 def parse_dentries(self, inode) -> Iterator[tuple[int, str]]: 

357 for chunk in self.read_file_content(inode): 

358 offset = 0 

359 while offset < len(chunk): 

360 entry = self._struct_parser.parse( 

361 self.DIRENT_STRUCT, chunk[offset:], Endian.LITTLE 

362 ) 

363 # d_reclen == 0 means end of valid entries in this block 

364 if entry.d_reclen == 0: 

365 break 

366 offset += entry.d_reclen 

367 if entry.d_ino == DELETED_INO: 

368 continue 

369 yield ( 

370 entry.d_ino, 

371 entry.d_name.decode("utf-8", errors="replace"), 

372 ) 

373 

374 def read_file_content(self, inode) -> Iterator[bytes]: 

375 remaining = inode.size 

376 for chunk in self.read_direct_blocks(inode): 

377 to_read = min(len(chunk), remaining) 

378 yield chunk[:to_read] 

379 remaining -= to_read 

380 if remaining <= 0: 

381 return 

382 for chunk in self.read_indirect_blocks(inode, remaining): 

383 yield chunk 

384 

385 def read_direct_blocks(self, inode) -> Iterator[bytes]: 

386 for fragment_index in self.get_direct_blocks(inode): 

387 if fragment_index == 0: 

388 # Sparse file: unallocated block reads as zeroes 

389 yield b"\x00" * self.super_block.fs_bsize 

390 else: 

391 self.file.seek(self.frag_to_offset(fragment_index), io.SEEK_SET) 

392 yield self.file.read(self.super_block.fs_bsize) 

393 

394 def read_indirect_blocks(self, inode, remaining: int) -> Iterator[bytes]: # noqa: C901 

395 indirect_blocks = self.get_indirect_blocks(inode) 

396 for level, fragment_index in enumerate(indirect_blocks, start=1): 

397 if fragment_index == 0 or remaining <= 0: 

398 break 

399 # levels: single=1, double=2, triple=3 

400 indexes = [fragment_index] 

401 for _ in range(level): 

402 next_indexes = [] 

403 for idx in indexes: 

404 if idx != 0: 

405 next_indexes.extend(self.read_block_pointers(idx)) 

406 indexes = next_indexes 

407 for data_index in indexes: 

408 if remaining <= 0: 

409 return 

410 to_read = min(self.super_block.fs_bsize, remaining) 

411 if data_index == 0: 

412 # Sparse file: unallocated block reads as zeroes 

413 yield b"\x00" * to_read 

414 else: 

415 self.file.seek(self.frag_to_offset(data_index), io.SEEK_SET) 

416 yield self.file.read(to_read) 

417 remaining -= to_read 

418 

419 def read_block_pointers(self, fragment_index: int) -> list[int]: 

420 self.file.seek(self.frag_to_offset(fragment_index), io.SEEK_SET) 

421 data = self.file.read(self.super_block.fs_bsize) 

422 count = self.super_block.fs_bsize // self.PTR_SIZE 

423 fmt = f"<{count}I" if self.PTR_SIZE == 4 else f"<{count}Q" 

424 return list(struct.unpack(fmt, data)) 

425 

426 def read_symlink(self, inode) -> str: 

427 if inode.blocks == 0: 

428 return bytes(inode.u2.symlink[: inode.size]).decode( 

429 "utf-8", errors="replace" 

430 ) 

431 chunk = next(self.read_file_content(inode)) 

432 return chunk[: inode.size].decode("utf-8", errors="replace") 

433 

434 def read_inode(self, ino_number: int): 

435 cylinder_group = ino_number // self.super_block.fs_inodes_per_group 

436 index = ino_number % self.super_block.fs_inodes_per_group 

437 offset = ( 

438 self.frag_to_offset( 

439 self.cylinder_group_start(cylinder_group) + self.super_block.fs_iblkno 

440 ) 

441 + index * self.INODE_SIZE 

442 ) 

443 self.file.seek(offset, io.SEEK_SET) 

444 return self._struct_parser.parse(self.INODE_STRUCT, self.file, Endian.LITTLE) 

445 

446 def frag_to_offset(self, fragment_index: int) -> int: 

447 """Convert a fragment index to a byte offset.""" 

448 return fragment_index * self.super_block.fs_fsize 

449 

450 def cylinder_group_start(self, cylinder_group: int) -> int: 

451 return cylinder_group * self.super_block.fs_frags_per_group 

452 

453 def get_direct_blocks(self, inode) -> list[int]: 

454 return inode.u2.addr.direct_blocks 

455 

456 def get_indirect_blocks(self, inode) -> list[int]: 

457 return inode.u2.addr.indirect_blocks 

458 

459 

460class UFS1Parser(UFSParser): 

461 INODE_STRUCT = "ufs1_inode_t" 

462 INODE_SIZE = 128 

463 PTR_SIZE = 4 

464 

465 def cylinder_group_start(self, cylinder_group: int) -> int: 

466 # Old UFS1 rotates cylinder group layout to minimize seek time on spinning disks 

467 return ( 

468 cylinder_group * self.super_block.fs_frags_per_group 

469 + self.super_block.fs_cgoffset 

470 * (cylinder_group & ~self.super_block.fs_cgmask) 

471 ) 

472 

473 

474class UFS2Parser(UFSParser): 

475 INODE_STRUCT = "ufs2_inode_t" 

476 INODE_SIZE = 256 

477 PTR_SIZE = 8 

478 

479 

480class SolarisUFS1Parser(UFS1Parser): 

481 DIRENT_STRUCT = "old_ufs_dirent" 

482 

483 

484class UFSExtractor(Extractor): 

485 def __init__(self, parser: type[UFSParser], sb_offset: int): 

486 self.parser = parser 

487 self.sb_offset = sb_offset 

488 

489 def extract(self, inpath: Path, outdir: Path): 

490 fs = FileSystem(outdir) 

491 with File.from_path(inpath) as f: 

492 parser = self.parser(f, self.sb_offset) 

493 parser.walk_extract(fs, UFS_ROOT_INO, Path("/")) 

494 return ExtractResult(reports=fs.problems) 

495 

496 

497class _UFSBaseHandler(StructHandler): 

498 HEADER_STRUCT = "ufs_superblock_t" 

499 C_DEFINITIONS = UFS_C_DEFINITION 

500 EXTRACTOR = None 

501 SB_OFFSET = 0 

502 

503 def get_block_size(self, header) -> int: 

504 raise NotImplementedError("Subclasses must implement this function.") 

505 

506 def is_valid_header(self, header) -> bool: 

507 return ( 

508 header.fs_fsize > 0 

509 and header.fs_bsize > 0 

510 and header.fs_bsize <= MAX_BLOCK_SIZE 

511 and header.fs_frag == (header.fs_bsize // header.fs_fsize) 

512 and self.get_block_size(header) > 0 

513 and header.fs_ncg > 0 

514 ) 

515 

516 def calculate_chunk(self, file: File, start_offset: int) -> ValidChunk | None: 

517 file.seek( 

518 start_offset + self.SB_OFFSET, io.SEEK_SET 

519 ) # Skip the boot sector to reach the start of UFS superblock 

520 header = self.parse_header(file) 

521 if not self.is_valid_header(header): 

522 raise InvalidInputFormat("Invalid UFS Header") 

523 

524 end_offset = start_offset + (self.get_block_size(header) * header.fs_fsize) 

525 return ValidChunk(start_offset=start_offset, end_offset=end_offset) 

526 

527 

528class UFS1Handler(_UFSBaseHandler): 

529 NAME = "ufs1" 

530 PATTERNS = [ 

531 HexString("( 01 | 02 ) 00 00 00 [8] 54 19 01 00") 

532 ] # fs_nrpos + UFS1 fs_magic + null fs_space 

533 SB_OFFSET = 0x2000 

534 EXTRACTOR = UFSExtractor(UFS1Parser, SB_OFFSET) 

535 PATTERN_MATCH_OFFSET = -SB_OFFSET - (MAGIC_OFFSET - 12) 

536 DOC = HandlerDoc( 

537 name="ufs1", 

538 description="UFS1 (Unix File System 1) is the original UFS implementation supported by Unix-like operating systems such as FreeBSD and Solaris. It utilizes a hierarchical tree structure and inodes to manage file metadata and data block addresses, with 32-bit block addressing limiting partition sizes to 1TB.", 

539 handler_type=HandlerType.FILESYSTEM, 

540 vendor=None, 

541 references=[ 

542 Reference( 

543 title="Unix File System Wikipedia", 

544 url="https://en.wikipedia.org/wiki/Unix_File_System", 

545 ) 

546 ], 

547 limitations=[], 

548 ) 

549 

550 def get_block_size(self, header) -> int: 

551 return header.fs_size 

552 

553 

554class UFS2Handler(_UFSBaseHandler): 

555 NAME = "ufs2" 

556 PATTERNS = [HexString("19 01 54 19 00")] # UFS2 fs_magic + null fs_space 

557 SB_OFFSET = 0x10000 

558 PATTERN_MATCH_OFFSET = -SB_OFFSET - MAGIC_OFFSET 

559 EXTRACTOR = UFSExtractor(UFS2Parser, SB_OFFSET) 

560 DOC = HandlerDoc( 

561 name="ufs2", 

562 description="UFS2 (Unix File System 2) is an extended version of UFS1 supported by Unix-like operating systems such as FreeBSD. It introduces 64-bit block addressing, extended file attributes, and improved performance over UFS1, while retaining the hierarchical tree structure and inodes for file metadata and data block management.", 

563 handler_type=HandlerType.FILESYSTEM, 

564 vendor=None, 

565 references=[ 

566 Reference( 

567 title="Unix File System Wikipedia", 

568 url="https://en.wikipedia.org/wiki/Unix_File_System", 

569 ) 

570 ], 

571 limitations=[], 

572 ) 

573 

574 def get_block_size(self, header) -> int: 

575 return header.fs_u11.fs_u2.fs_size_64 

576 

577 

578class SolarisHandler(_UFSBaseHandler): 

579 NAME = "solaris_ufs1" 

580 PATTERNS = [HexString("08 00 00 00 [8] 54 19 01 00")] 

581 SB_OFFSET = 0x2000 

582 EXTRACTOR = UFSExtractor(SolarisUFS1Parser, SB_OFFSET) 

583 PATTERN_MATCH_OFFSET = -SB_OFFSET - (MAGIC_OFFSET - 12) 

584 DOC = HandlerDoc( 

585 name="solaris_ufs1", 

586 description="Solaris UFS1 is the variant of UFS1 used by Oracle Solaris and illumos-based systems such as OpenIndiana and OmniOS. It shares the same overall design as FreeBSD UFS1 but keeps an older on-disk convention inherited from early Unix, making it incompatible with FreeBSD's variant despite the shared magic number.", 

587 handler_type=HandlerType.FILESYSTEM, 

588 vendor=None, 

589 references=[ 

590 Reference( 

591 title="Unix File System Wikipedia", 

592 url="https://en.wikipedia.org/wiki/Unix_File_System", 

593 ), 

594 Reference( 

595 title="Oracle Solaris", 

596 url="https://en.wikipedia.org/wiki/Oracle_Solaris", 

597 ), 

598 ], 

599 limitations=[], 

600 ) 

601 

602 def get_block_size(self, header) -> int: 

603 return header.fs_size