1use crate::bytes::{u8_at, Endian};
10use crate::error::UfsError;
11
12pub const FS_UFS1_MAGIC: u32 = 0x0001_1954;
14
15pub const FS_UFS2_MAGIC: u32 = 0x1954_0119;
17
18pub const SBLOCK_UFS1: usize = 8192;
21
22pub const SBLOCK_UFS2: usize = 65536;
25
26pub const UFS_ROOTINO: u64 = 2;
28
29const FS_MAGIC_OFF: usize = 1372;
31
32const SB_MIN_LEN: usize = 1376;
35
36const OFF_SBLKNO: usize = 8;
38const OFF_CBLKNO: usize = 12;
39const OFF_IBLKNO: usize = 16;
40const OFF_DBLKNO: usize = 20;
41const OFF_OLD_TIME: usize = 32;
42const OFF_OLD_SIZE: usize = 36;
43const OFF_OLD_DSIZE: usize = 40;
44const OFF_NCG: usize = 44;
45const OFF_BSIZE: usize = 48;
46const OFF_FSIZE: usize = 52;
47const OFF_FRAG: usize = 56;
48const OFF_BSHIFT: usize = 80;
49const OFF_FSHIFT: usize = 84;
50const OFF_FRAGSHIFT: usize = 96;
51const OFF_FSBTODB: usize = 100;
52const OFF_SBSIZE: usize = 104;
53const OFF_NINDIR: usize = 116;
54const OFF_INOPB: usize = 120;
55const OFF_IPG: usize = 184;
56const OFF_FPG: usize = 188;
57const OFF_SIZE: usize = 1080;
58const OFF_DSIZE: usize = 1088;
59const OFF_CSADDR: usize = 1096;
60const OFF_SBLOCKLOC: usize = 1000;
61const OFF_MAXSYMLINKLEN: usize = 1320;
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub enum UfsVersion {
66 Ufs1,
69 Ufs2,
72}
73
74#[derive(Debug, Clone, PartialEq, Eq)]
80#[non_exhaustive]
81pub struct Superblock {
82 pub version: UfsVersion,
84 pub endian: Endian,
86 pub sblkno: i32,
88 pub cblkno: i32,
90 pub iblkno: i32,
92 pub dblkno: i32,
94 pub ncg: u32,
96 pub bsize: i32,
98 pub fsize: i32,
100 pub frag: i32,
102 pub bshift: i32,
104 pub fshift: i32,
106 pub fragshift: i32,
108 pub fsbtodb: i32,
110 pub sbsize: i32,
112 pub nindir: i32,
114 pub inopb: u32,
116 pub ipg: i32,
118 pub fpg: i32,
120 pub size: i64,
123 pub dsize: i64,
125 pub csaddr: i64,
127 pub sblockloc: i64,
130 pub maxsymlinklen: i32,
132}
133
134impl Superblock {
135 pub fn parse(data: &[u8]) -> Result<Self, UfsError> {
150 if data.len() < SB_MIN_LEN {
155 return Err(UfsError::Truncated {
156 structure: "superblock",
157 need: SB_MIN_LEN,
158 have: data.len(),
159 });
160 }
161
162 let bytes = [
167 u8_at(data, FS_MAGIC_OFF),
168 u8_at(data, FS_MAGIC_OFF + 1),
169 u8_at(data, FS_MAGIC_OFF + 2),
170 u8_at(data, FS_MAGIC_OFF + 3),
171 ];
172 let le = u32::from_le_bytes(bytes);
173 let be = u32::from_be_bytes(bytes);
174 let (version, endian) = match (le, be) {
175 (FS_UFS1_MAGIC, _) => (UfsVersion::Ufs1, Endian::Little),
176 (FS_UFS2_MAGIC, _) => (UfsVersion::Ufs2, Endian::Little),
177 (_, FS_UFS1_MAGIC) => (UfsVersion::Ufs1, Endian::Big),
178 (_, FS_UFS2_MAGIC) => (UfsVersion::Ufs2, Endian::Big),
179 _ => {
180 return Err(UfsError::BadMagic {
181 offset: FS_MAGIC_OFF,
182 bytes,
183 le,
184 be,
185 })
186 }
187 };
188
189 let (size, dsize) = match version {
192 UfsVersion::Ufs1 => (
193 i64::from(endian.i32(data, OFF_OLD_SIZE)),
194 i64::from(endian.i32(data, OFF_OLD_DSIZE)),
195 ),
196 UfsVersion::Ufs2 => (endian.i64(data, OFF_SIZE), endian.i64(data, OFF_DSIZE)),
197 };
198 let sblockloc = match version {
199 UfsVersion::Ufs1 => 0,
200 UfsVersion::Ufs2 => endian.i64(data, OFF_SBLOCKLOC),
201 };
202 let _ = (OFF_OLD_TIME, OFF_CSADDR);
205
206 let sb = Self {
207 version,
208 endian,
209 sblkno: endian.i32(data, OFF_SBLKNO),
210 cblkno: endian.i32(data, OFF_CBLKNO),
211 iblkno: endian.i32(data, OFF_IBLKNO),
212 dblkno: endian.i32(data, OFF_DBLKNO),
213 ncg: endian.u32(data, OFF_NCG),
214 bsize: endian.i32(data, OFF_BSIZE),
215 fsize: endian.i32(data, OFF_FSIZE),
216 frag: endian.i32(data, OFF_FRAG),
217 bshift: endian.i32(data, OFF_BSHIFT),
218 fshift: endian.i32(data, OFF_FSHIFT),
219 fragshift: endian.i32(data, OFF_FRAGSHIFT),
220 fsbtodb: endian.i32(data, OFF_FSBTODB),
221 sbsize: endian.i32(data, OFF_SBSIZE),
222 nindir: endian.i32(data, OFF_NINDIR),
223 inopb: endian.u32(data, OFF_INOPB),
224 ipg: endian.i32(data, OFF_IPG),
225 fpg: endian.i32(data, OFF_FPG),
226 size,
227 dsize,
228 csaddr: endian.i64(data, OFF_CSADDR),
229 sblockloc,
230 maxsymlinklen: endian.i32(data, OFF_MAXSYMLINKLEN),
231 };
232
233 sb.validate_geometry()?;
234 Ok(sb)
235 }
236
237 fn validate_geometry(&self) -> Result<(), UfsError> {
240 if self.bsize <= 0 || self.bsize > 65536 {
242 return Err(UfsError::ImpossibleGeometry {
243 field: "fs_bsize",
244 value: self.bsize as u64,
245 limit: 65536,
246 });
247 }
248 if self.fsize <= 0 || self.fsize > self.bsize {
249 return Err(UfsError::ImpossibleGeometry {
250 field: "fs_fsize",
251 value: self.fsize as u64,
252 limit: self.bsize as u64,
253 });
254 }
255 const MAX_NCG: u64 = 1 << 24;
258 if u64::from(self.ncg) > MAX_NCG {
259 return Err(UfsError::ImpossibleGeometry {
260 field: "fs_ncg",
261 value: u64::from(self.ncg),
262 limit: MAX_NCG,
263 });
264 }
265 Ok(())
266 }
267
268 #[must_use]
270 pub fn inode_size(&self) -> u32 {
271 match self.version {
272 UfsVersion::Ufs1 => 128,
273 UfsVersion::Ufs2 => 256,
274 }
275 }
276
277 #[must_use]
280 pub fn primary_offset(&self) -> usize {
281 match self.version {
282 UfsVersion::Ufs1 => SBLOCK_UFS1,
283 UfsVersion::Ufs2 => SBLOCK_UFS2,
284 }
285 }
286}
287
288#[cfg(test)]
289mod tests {
290 use super::*;
291
292 fn synthetic(magic: u32, endian: Endian, ufs2: bool) -> Vec<u8> {
296 let mut d = vec![0u8; SB_MIN_LEN];
297 let wr32 = |d: &mut [u8], off: usize, v: i32| {
298 let b = match endian {
299 Endian::Little => v.to_le_bytes(),
300 Endian::Big => v.to_be_bytes(),
301 };
302 d[off..off + 4].copy_from_slice(&b);
303 };
304 let wr64 = |d: &mut [u8], off: usize, v: i64| {
305 let b = match endian {
306 Endian::Little => v.to_le_bytes(),
307 Endian::Big => v.to_be_bytes(),
308 };
309 d[off..off + 8].copy_from_slice(&b);
310 };
311 let mb = match endian {
314 Endian::Little => magic.to_le_bytes(),
315 Endian::Big => magic.to_be_bytes(),
316 };
317 d[FS_MAGIC_OFF..FS_MAGIC_OFF + 4].copy_from_slice(&mb);
318 wr32(&mut d, OFF_SBLKNO, 24);
319 wr32(&mut d, OFF_CBLKNO, 32);
320 wr32(&mut d, OFF_IBLKNO, 40);
321 wr32(&mut d, OFF_DBLKNO, 48);
322 wr32(&mut d, OFF_NCG, 4);
323 wr32(&mut d, OFF_BSIZE, 32768);
324 wr32(&mut d, OFF_FSIZE, 4096);
325 wr32(&mut d, OFF_FRAG, 8);
326 wr32(&mut d, OFF_BSHIFT, 15);
327 wr32(&mut d, OFF_FSHIFT, 12);
328 wr32(&mut d, OFF_INOPB, 128);
329 wr32(&mut d, OFF_IPG, 128);
330 wr32(&mut d, OFF_FPG, 256);
331 wr32(&mut d, OFF_MAXSYMLINKLEN, 120);
332 if ufs2 {
333 wr64(&mut d, OFF_SIZE, 1022);
334 wr64(&mut d, OFF_DSIZE, 901);
335 wr64(&mut d, OFF_SBLOCKLOC, SBLOCK_UFS2 as i64);
336 } else {
337 wr32(&mut d, OFF_OLD_SIZE, 1000);
338 wr32(&mut d, OFF_OLD_DSIZE, 900);
339 }
340 d
341 }
342
343 #[test]
344 fn detects_ufs2_little_endian() {
345 let d = synthetic(FS_UFS2_MAGIC, Endian::Little, true);
346 let sb = Superblock::parse(&d).unwrap();
347 assert_eq!(sb.version, UfsVersion::Ufs2);
348 assert_eq!(sb.endian, Endian::Little);
349 assert_eq!(sb.bsize, 32768);
350 assert_eq!(sb.ncg, 4);
351 assert_eq!(sb.size, 1022);
352 assert_eq!(sb.sblockloc, SBLOCK_UFS2 as i64);
353 assert_eq!(sb.inode_size(), 256);
354 assert_eq!(sb.primary_offset(), SBLOCK_UFS2);
355 }
356
357 #[test]
358 fn detects_ufs2_big_endian() {
359 let d = synthetic(FS_UFS2_MAGIC, Endian::Big, true);
360 let sb = Superblock::parse(&d).unwrap();
361 assert_eq!(sb.version, UfsVersion::Ufs2);
362 assert_eq!(sb.endian, Endian::Big);
363 assert_eq!(sb.bsize, 32768);
364 assert_eq!(sb.fpg, 256);
365 }
366
367 #[test]
368 fn detects_ufs1_and_uses_old_size_fields() {
369 let d = synthetic(FS_UFS1_MAGIC, Endian::Little, false);
370 let sb = Superblock::parse(&d).unwrap();
371 assert_eq!(sb.version, UfsVersion::Ufs1);
372 assert_eq!(sb.size, 1000, "UFS1 uses fs_old_size@36");
373 assert_eq!(sb.dsize, 900, "UFS1 uses fs_old_dsize@40");
374 assert_eq!(sb.sblockloc, 0, "UFS1 has no fs_sblockloc");
375 assert_eq!(sb.inode_size(), 128);
376 assert_eq!(sb.primary_offset(), SBLOCK_UFS1);
377 }
378
379 #[test]
380 fn detects_ufs1_big_endian() {
381 let d = synthetic(FS_UFS1_MAGIC, Endian::Big, false);
382 let sb = Superblock::parse(&d).unwrap();
383 assert_eq!(sb.version, UfsVersion::Ufs1);
384 assert_eq!(sb.endian, Endian::Big);
385 assert_eq!(sb.size, 1000, "UFS1 old_size decoded big-endian");
386 assert_eq!(sb.inode_size(), 128);
387 }
388
389 #[test]
390 fn bad_magic_fails_loud_with_bytes() {
391 let mut d = vec![0u8; SB_MIN_LEN];
392 d[FS_MAGIC_OFF..FS_MAGIC_OFF + 4].copy_from_slice(&0xdead_beef_u32.to_le_bytes());
393 let err = Superblock::parse(&d).unwrap_err();
394 assert!(
395 matches!(
396 &err,
397 UfsError::BadMagic { offset, bytes, le, .. }
398 if *offset == FS_MAGIC_OFF
399 && *le == 0xdead_beef
400 && *bytes == 0xdead_beef_u32.to_le_bytes()
401 ),
402 "expected BadMagic with offending bytes, got {err:?}"
403 );
404 }
405
406 #[test]
407 fn truncated_buffer_fails_loud_not_panic() {
408 let d = vec![0u8; SB_MIN_LEN - 1];
412 let err = Superblock::parse(&d).unwrap_err();
413 assert!(
414 matches!(
415 &err,
416 UfsError::Truncated { structure, need, .. }
417 if *structure == "superblock" && *need == SB_MIN_LEN
418 ),
419 "expected Truncated superblock, got {err:?}"
420 );
421 }
422
423 #[test]
424 fn empty_buffer_reports_truncated_not_panic() {
425 assert!(matches!(
426 Superblock::parse(&[]),
427 Err(UfsError::Truncated { .. })
428 ));
429 }
430
431 #[test]
432 fn impossible_block_size_rejected() {
433 let mut d = synthetic(FS_UFS2_MAGIC, Endian::Little, true);
434 d[OFF_BSIZE..OFF_BSIZE + 4].copy_from_slice(&(1_048_576_i32).to_le_bytes());
436 assert!(matches!(
437 Superblock::parse(&d),
438 Err(UfsError::ImpossibleGeometry {
439 field: "fs_bsize",
440 ..
441 })
442 ));
443 }
444
445 #[test]
446 fn impossible_fragment_size_rejected() {
447 let mut d = synthetic(FS_UFS2_MAGIC, Endian::Little, true);
448 d[OFF_FSIZE..OFF_FSIZE + 4].copy_from_slice(&(65536_i32).to_le_bytes());
450 assert!(matches!(
451 Superblock::parse(&d),
452 Err(UfsError::ImpossibleGeometry {
453 field: "fs_fsize",
454 ..
455 })
456 ));
457 }
458
459 #[test]
460 fn absurd_cg_count_rejected() {
461 let mut d = synthetic(FS_UFS2_MAGIC, Endian::Little, true);
462 d[OFF_NCG..OFF_NCG + 4].copy_from_slice(&0x7fff_ffff_u32.to_le_bytes());
463 assert!(matches!(
464 Superblock::parse(&d),
465 Err(UfsError::ImpossibleGeometry {
466 field: "fs_ncg",
467 ..
468 })
469 ));
470 }
471}