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