1import io
2
3from unblob.file_utils import (
4 InvalidInputFormat,
5)
6from unblob.models import (
7 File,
8 HandlerDoc,
9 HandlerType,
10 HexString,
11 Reference,
12 StructHandler,
13 ValidChunk,
14)
15
16UFS_C_DEFINITION = """
17 #define UFS_MAXMNTLEN 512
18 #define UFS2_MAXMNTLEN 468
19 #define UFS2_MAXVOLLEN 32
20 #define UFS_MAXCSBUFS 31
21 #define UFS2_NOCSPTRS 28
22
23 struct ufs_csum {
24 uint32 cs_ndir;
25 uint32 cs_nbfree;
26 uint32 cs_nifree;
27 uint32 cs_nffree;
28 };
29
30 struct ufs2_csum_total {
31 uint64 cs_ndir;
32 uint64 cs_nbfree;
33 uint64 cs_nifree;
34 uint64 cs_nffree;
35 uint64 cs_numclusters;
36 uint64 cs_spare[3];
37 };
38
39 struct ufs_timeval {
40 uint32 tv_sec;
41 uint32 tv_usec;
42 };
43
44 struct ufs_super_block {
45 union {
46 struct {
47 uint32 fs_link;
48 } fs_42;
49 struct {
50 uint32 fs_state;
51 } fs_sun;
52 } fs_u0;
53
54 uint32 fs_rlink;
55 uint32 fs_sblkno;
56 uint32 fs_cblkno;
57 uint32 fs_iblkno;
58 uint32 fs_dblkno;
59 uint32 fs_cgoffset;
60 uint32 fs_cgmask;
61 uint32 fs_time;
62 uint32 fs_size;
63 uint32 fs_dsize;
64 uint32 fs_ncg;
65 uint32 fs_bsize;
66 uint32 fs_fsize;
67 uint32 fs_frag;
68 uint32 fs_minfree;
69 uint32 fs_rotdelay;
70 uint32 fs_rps;
71 uint32 fs_bmask;
72 uint32 fs_fmask;
73 uint32 fs_bshift;
74 uint32 fs_fshift;
75 uint32 fs_maxcontig;
76 uint32 fs_maxbpg;
77 uint32 fs_fragshift;
78 uint32 fs_fsbtodb;
79 uint32 fs_sbsize;
80 uint32 fs_csmask;
81 uint32 fs_csshift;
82 uint32 fs_nindir;
83 uint32 fs_inopb;
84 uint32 fs_nspf;
85 uint32 fs_optim;
86
87 union {
88 struct {
89 uint32 fs_npsect;
90 } fs_sun;
91 struct {
92 uint32 fs_state;
93 } fs_sunx86;
94 } fs_u1;
95
96 uint32 fs_interleave;
97 uint32 fs_trackskew;
98 uint32 fs_id[2];
99 uint32 fs_csaddr;
100 uint32 fs_cssize;
101 uint32 fs_cgsize;
102 uint32 fs_ntrak;
103 uint32 fs_nsect;
104 uint32 fs_spc;
105 uint32 fs_ncyl;
106 uint32 fs_cpg;
107 uint32 fs_ipg;
108 uint32 fs_fpg;
109
110 struct ufs_csum fs_cstotal;
111
112 int8 fs_fmod;
113 int8 fs_clean;
114 int8 fs_ronly;
115 int8 fs_flags;
116
117 union {
118 struct {
119 int8 fs_fsmnt[UFS_MAXMNTLEN];
120 uint32 fs_cgrotor;
121 uint32 fs_csp[UFS_MAXCSBUFS];
122 uint32 fs_maxcluster;
123 uint32 fs_cpc;
124 uint16 fs_opostbl[16][8];
125 } fs_u1;
126 struct {
127 int8 fs_fsmnt[UFS2_MAXMNTLEN];
128 uint8 fs_volname[UFS2_MAXVOLLEN];
129 uint64 fs_swuid;
130 uint32 fs_pad;
131 uint32 fs_cgrotor;
132 uint32 fs_ocsp[UFS2_NOCSPTRS];
133 uint32 fs_contigdirs;
134 uint32 fs_csp;
135 uint32 fs_maxcluster;
136 uint32 fs_active;
137 uint32 fs_old_cpc;
138 uint32 fs_maxbsize;
139 uint64 fs_sparecon64[17];
140 uint64 fs_sblockloc;
141 struct ufs2_csum_total fs_cstotal;
142 struct ufs_timeval fs_time;
143 uint64 fs_size_64;
144 uint64 fs_dsize;
145 uint64 fs_csaddr;
146 uint64 fs_pendingblocks;
147 uint32 fs_pendinginodes;
148 } fs_u2;
149 } fs_u11;
150
151 union {
152 struct {
153 uint32 fs_sparecon[53];
154 uint32 fs_reclaim;
155 uint32 fs_sparecon2[1];
156 uint32 fs_state;
157 uint32 fs_qbmask[2];
158 uint32 fs_qfmask[2];
159 } fs_sun;
160 struct {
161 uint32 fs_sparecon[53];
162 uint32 fs_reclaim;
163 uint32 fs_sparecon2[1];
164 uint32 fs_npsect;
165 uint32 fs_qbmask[2];
166 uint32 fs_qfmask[2];
167 } fs_sunx86;
168 struct {
169 uint32 fs_sparecon[50];
170 uint32 fs_contigsumsize;
171 uint32 fs_maxsymlinklen;
172 uint32 fs_inodefmt;
173 uint32 fs_maxfilesize[2];
174 uint32 fs_qbmask[2];
175 uint32 fs_qfmask[2];
176 uint32 fs_state;
177 } fs_44;
178 } fs_u2_arch;
179
180 uint32 fs_postblformat;
181 uint32 fs_nrpos;
182 uint32 fs_postbloff;
183 uint32 fs_rotbloff;
184 uint32 fs_magic;
185 uint8
186 fs_space[1];
187} ufs_superblock_t;
188"""
189
190MAGIC_OFFSET = 0x55C # relative to SB_OFFSET
191
192
193class _UFSHandler(StructHandler):
194 HEADER_STRUCT = "ufs_superblock_t"
195 C_DEFINITIONS = UFS_C_DEFINITION
196 EXTRACTOR = None
197 SB_OFFSET = 0
198
199 def get_block_size(self, header) -> int:
200 raise NotImplementedError("Subclasses must implement this function.")
201
202 def is_valid_header(self, header) -> bool:
203 return (
204 header.fs_frag == (header.fs_bsize // header.fs_fsize)
205 and self.get_block_size(header) > 0
206 and header.fs_ncg > 0
207 )
208
209 def calculate_chunk(self, file: File, start_offset: int) -> ValidChunk | None:
210 file.seek(
211 start_offset + self.SB_OFFSET, io.SEEK_SET
212 ) # Skip the boot sector to reach the start of UFS superblock
213 header = self.parse_header(file)
214 if not self.is_valid_header(header):
215 raise InvalidInputFormat("Invalid UFS Header")
216
217 end_offset = start_offset + (self.get_block_size(header) * header.fs_fsize)
218 return ValidChunk(start_offset=start_offset, end_offset=end_offset)
219
220
221class UFS1Handler(_UFSHandler):
222 NAME = "ufs1"
223 PATTERNS = [HexString("54 19 01")] # UFS1 little endian
224 SB_OFFSET = 0x2000
225 PATTERN_MATCH_OFFSET = -SB_OFFSET - MAGIC_OFFSET
226 DOC = HandlerDoc(
227 name="ufs1",
228 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.",
229 handler_type=HandlerType.FILESYSTEM,
230 vendor=None,
231 references=[
232 Reference(
233 title="Unix File System Wikipedia",
234 url="https://en.wikipedia.org/wiki/Unix_File_System",
235 )
236 ],
237 limitations=[
238 "File extraction is not yet supported, only carving of the filesystem is currently available."
239 ],
240 )
241
242 def get_block_size(self, header) -> int:
243 return header.fs_size
244
245
246class UFS2Handler(_UFSHandler):
247 NAME = "ufs2"
248 PATTERNS = [HexString("19 01 54 19")]
249 SB_OFFSET = 0x10000
250 PATTERN_MATCH_OFFSET = -SB_OFFSET - MAGIC_OFFSET
251 DOC = HandlerDoc(
252 name="ufs2",
253 description="UFS2 (Unix File System 2) is an extended version of UFS1 supported by Unix-like operating systems such as FreeBSD and Solaris. 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.",
254 handler_type=HandlerType.FILESYSTEM,
255 vendor=None,
256 references=[
257 Reference(
258 title="Unix File System Wikipedia",
259 url="https://en.wikipedia.org/wiki/Unix_File_System",
260 )
261 ],
262 limitations=[
263 "File extraction is not yet supported, only carving of the filesystem is currently available."
264 ],
265 )
266
267 def get_block_size(self, header) -> int:
268 return header.fs_u11.fs_u2.fs_size_64