1use crate::dir::{read_block, read_by_path};
30use crate::error::UfsError;
31use crate::inode::{read_inode, Inode, UFS_NDADDR};
32use crate::superblock::{Superblock, UfsVersion};
33
34pub fn read_file(partition: &[u8], sb: &Superblock, ino: u64) -> Result<Vec<u8>, UfsError> {
48 let inode = read_inode(partition, sb, ino)?;
49 read_inode_file(partition, sb, &inode)
50}
51
52pub fn read_inode_file(
60 partition: &[u8],
61 sb: &Superblock,
62 inode: &Inode,
63) -> Result<Vec<u8>, UfsError> {
64 if sb.bsize <= 0 {
65 return Err(UfsError::ImpossibleGeometry {
66 field: "fs_bsize",
67 value: sb.bsize as u64,
68 limit: i64::MAX as u64,
69 });
70 }
71 if sb.fsize <= 0 {
72 return Err(UfsError::ImpossibleGeometry {
73 field: "fs_fsize",
74 value: sb.fsize as u64,
75 limit: i64::MAX as u64,
76 });
77 }
78 let size = inode.size;
79 let part_len = partition.len() as u64;
82 if size > part_len {
83 return Err(UfsError::ImpossibleGeometry {
84 field: "di_size",
85 value: size,
86 limit: part_len,
87 });
88 }
89 let bsize = sb.bsize as u64;
90 let size_usize = usize::try_from(size).unwrap_or(usize::MAX);
91 let mut out = vec![0u8; size_usize];
92
93 if size == 0 {
94 return Ok(out);
95 }
96
97 let n_blocks = size.div_ceil(bsize);
99 let nindir = if sb.nindir > 0 { sb.nindir as u64 } else { 0 };
100
101 let mut remaining = size;
102 let mut written = 0usize;
103 let mut bi: u64 = 0;
104 while bi < n_blocks {
105 let this_len = usize::try_from(remaining.min(bsize)).unwrap_or(usize::MAX);
106 let addr = resolve_block(partition, sb, inode, bi, nindir);
107 if addr != 0 {
108 let block = read_block(partition, sb, addr, this_len)?;
109 let take = block.len().min(this_len);
112 if let Some(dst) = out.get_mut(written..written + take) {
113 dst.copy_from_slice(&block[..take]);
114 }
115 }
116 written = written.saturating_add(this_len);
118 remaining = remaining.saturating_sub(bsize);
119 bi += 1;
120 }
121
122 Ok(out)
123}
124
125fn resolve_block(partition: &[u8], sb: &Superblock, inode: &Inode, bi: u64, nindir: u64) -> u64 {
133 let ndaddr = UFS_NDADDR as u64;
134 if bi < ndaddr {
135 return inode.direct[bi as usize];
136 }
137 if nindir == 0 {
138 return 0; }
140 let mut i = bi - ndaddr;
141
142 if i < nindir {
144 return indirect_ptr(partition, sb, inode.indirect[0], i);
145 }
146 i -= nindir;
147
148 let nindir2 = nindir.saturating_mul(nindir);
150 if i < nindir2 {
151 let sib = indirect_ptr(partition, sb, inode.indirect[1], i / nindir);
152 return indirect_ptr(partition, sb, sib, i % nindir);
153 }
154 i -= nindir2;
155
156 let nindir3 = nindir2.saturating_mul(nindir);
162 if i >= nindir3 {
163 return 0; }
165 let dib = indirect_ptr(partition, sb, inode.indirect[2], i / nindir2);
166 let rem = i % nindir2;
167 let sib = indirect_ptr(partition, sb, dib, rem / nindir);
168 indirect_ptr(partition, sb, sib, rem % nindir)
169}
170
171fn indirect_ptr(partition: &[u8], sb: &Superblock, ind_addr: u64, idx: u64) -> u64 {
175 if ind_addr == 0 {
176 return 0;
177 }
178 let bsize = if sb.bsize > 0 { sb.bsize as usize } else { 0 };
179 let Ok(block) = read_block(partition, sb, ind_addr, bsize) else {
180 return 0; };
182 let ptr_size = match sb.version {
183 UfsVersion::Ufs2 => 8usize,
184 UfsVersion::Ufs1 => 4usize,
185 };
186 let off = usize::try_from(idx.saturating_mul(ptr_size as u64)).unwrap_or(usize::MAX);
187 match sb.version {
188 UfsVersion::Ufs2 => sb.endian.u64(block, off),
189 UfsVersion::Ufs1 => u64::from(sb.endian.u32(block, off)),
190 }
191}
192
193pub fn read_symlink_target(
205 partition: &[u8],
206 sb: &Superblock,
207 inode: &Inode,
208) -> Result<Vec<u8>, UfsError> {
209 if let Some(inline) = inode.symlink_target() {
210 return Ok(inline.to_vec());
211 }
212 read_inode_file(partition, sb, inode)
214}
215
216pub fn read_path_content(
224 partition: &[u8],
225 sb: &Superblock,
226 path: &str,
227) -> Result<Option<Vec<u8>>, UfsError> {
228 let Some((_ino, inode)) = read_by_path(partition, sb, path)? else {
229 return Ok(None);
230 };
231 Ok(Some(read_inode_file(partition, sb, &inode)?))
232}
233
234#[cfg(test)]
235#[allow(clippy::unreadable_literal)]
236mod tests {
237 use super::*;
238 use crate::superblock::FS_UFS2_MAGIC;
239
240 fn tiny_sb(bsize: i32, fsize: i32, nindir: i32) -> Superblock {
244 let mut d = vec![0u8; 1376];
245 let wr32 = |d: &mut [u8], off: usize, v: i32| {
246 d[off..off + 4].copy_from_slice(&v.to_le_bytes());
247 };
248 let wr64 = |d: &mut [u8], off: usize, v: i64| {
249 d[off..off + 8].copy_from_slice(&v.to_le_bytes());
250 };
251 wr32(&mut d, 8, 1); wr32(&mut d, 12, 2); wr32(&mut d, 16, 4); wr32(&mut d, 20, 8); wr32(&mut d, 44, 1); wr32(&mut d, 48, bsize); wr32(&mut d, 52, fsize); wr32(&mut d, 56, 1); wr32(&mut d, 116, nindir); wr32(&mut d, 120, bsize / 256); wr32(&mut d, 184, 128); wr32(&mut d, 188, 4096); wr32(&mut d, 1320, 120); wr64(&mut d, 1080, 65536); d[1372..1376].copy_from_slice(&FS_UFS2_MAGIC.to_le_bytes());
266 Superblock::parse(&d).unwrap()
267 }
268
269 fn inode_with(size: u64, direct: &[u64], ib: [u64; 3]) -> Inode {
271 let mut d = vec![0u8; 256];
273 d[0..2].copy_from_slice(&0o100644u16.to_le_bytes()); d[2..4].copy_from_slice(&1u16.to_le_bytes()); d[16..24].copy_from_slice(&size.to_le_bytes()); for (i, &a) in direct.iter().enumerate() {
277 d[112 + i * 8..112 + i * 8 + 8].copy_from_slice(&a.to_le_bytes());
278 }
279 for (i, &a) in ib.iter().enumerate() {
280 d[208 + i * 8..208 + i * 8 + 8].copy_from_slice(&a.to_le_bytes());
281 }
282 Inode::parse(&d, UfsVersion::Ufs2, crate::Endian::Little).unwrap()
283 }
284
285 #[test]
286 fn read_file_direct_only_single_block() {
287 let sb = tiny_sb(512, 512, 64);
288 let mut part = vec![0u8; 512 * 32];
290 let payload: Vec<u8> = (0..100u16).map(|i| (i & 0xff) as u8).collect();
291 let frag = 10usize;
292 part[frag * 512..frag * 512 + payload.len()].copy_from_slice(&payload);
293 let inode = inode_with(payload.len() as u64, &[frag as u64], [0, 0, 0]);
294 let got = read_inode_file(&part, &sb, &inode).unwrap();
295 assert_eq!(got, payload);
296 }
297
298 #[test]
299 fn read_file_zero_size_is_empty() {
300 let sb = tiny_sb(512, 512, 64);
301 let part = vec![0u8; 512 * 4];
302 let inode = inode_with(0, &[0; 12], [0, 0, 0]);
303 assert!(read_inode_file(&part, &sb, &inode).unwrap().is_empty());
304 }
305
306 #[test]
307 fn read_file_single_indirect_block() {
308 let sb = tiny_sb(512, 512, 64);
309 let mut part = vec![0u8; 512 * 64];
311 let mut direct = [0u64; 12];
312 for (i, slot) in direct.iter_mut().enumerate() {
314 let f = 20 + i as u64;
315 *slot = f;
316 part[f as usize * 512] = (i + 1) as u8; }
318 let sib = 40usize;
320 let data13 = 41u64;
321 part[sib * 512..sib * 512 + 8].copy_from_slice(&data13.to_le_bytes());
322 part[data13 as usize * 512] = 0xAB;
323 let size = 13 * 512u64;
324 let inode = inode_with(size, &direct, [sib as u64, 0, 0]);
325 let got = read_inode_file(&part, &sb, &inode).unwrap();
326 assert_eq!(got.len() as u64, size);
327 assert_eq!(got[0], 1, "first direct block marker");
328 assert_eq!(got[12 * 512], 0xAB, "block 12 came via single-indirect");
329 }
330
331 #[test]
332 fn read_file_double_and_triple_indirect_with_nindir2() {
333 let sb = tiny_sb(512, 512, 2);
337 let bsize = 512usize;
338 let mut part = vec![0u8; bsize * 128];
339 let put_ptr = |p: &mut [u8], frag: usize, idx: usize, target: u64| {
340 let off = frag * bsize + idx * 8;
341 p[off..off + 8].copy_from_slice(&target.to_le_bytes());
342 };
343 let mark = |p: &mut [u8], frag: usize, m: u8| p[frag * bsize] = m;
345
346 let mut direct = [0u64; 12];
348 for (i, slot) in direct.iter_mut().enumerate() {
349 let f = 20 + i;
350 *slot = f as u64;
351 mark(&mut part, f, i as u8 + 1);
352 }
353 put_ptr(&mut part, 40, 0, 50);
355 put_ptr(&mut part, 40, 1, 51);
356 mark(&mut part, 50, 100);
357 mark(&mut part, 51, 101);
358 put_ptr(&mut part, 41, 0, 42);
360 put_ptr(&mut part, 41, 1, 43);
361 put_ptr(&mut part, 42, 0, 52); put_ptr(&mut part, 42, 1, 53); put_ptr(&mut part, 43, 0, 54); put_ptr(&mut part, 43, 1, 55); for (blk, f) in [(14, 52), (15, 53), (16, 54), (17, 55)] {
366 mark(&mut part, f, blk as u8);
367 }
368 put_ptr(&mut part, 44, 0, 45);
370 put_ptr(&mut part, 45, 0, 46);
371 put_ptr(&mut part, 46, 0, 56); mark(&mut part, 56, 200);
373
374 let n_blocks = 19u64; let size = n_blocks * bsize as u64;
376 let inode = inode_with(size, &direct, [40, 41, 44]);
377 let got = read_inode_file(&part, &sb, &inode).unwrap();
378 assert_eq!(got.len() as u64, size);
379 assert_eq!(got[0], 1, "direct block 0");
380 assert_eq!(got[12 * bsize], 100, "block 12 via single-indirect");
381 assert_eq!(got[14 * bsize], 14, "block 14 via double-indirect");
382 assert_eq!(got[17 * bsize], 17, "block 17 via double-indirect");
383 assert_eq!(got[18 * bsize], 200, "block 18 via triple-indirect");
384 }
385
386 #[test]
387 fn read_file_ufs1_single_indirect_uses_4byte_pointers() {
388 let mut d = vec![0u8; 1376];
390 let wr32 = |d: &mut [u8], off: usize, v: i32| {
391 d[off..off + 4].copy_from_slice(&v.to_le_bytes());
392 };
393 wr32(&mut d, 8, 1);
394 wr32(&mut d, 12, 2);
395 wr32(&mut d, 16, 4);
396 wr32(&mut d, 20, 8);
397 wr32(&mut d, 44, 1);
398 wr32(&mut d, 48, 512); wr32(&mut d, 52, 512); wr32(&mut d, 56, 1); wr32(&mut d, 116, 128); wr32(&mut d, 120, 4); wr32(&mut d, 184, 128);
404 wr32(&mut d, 188, 4096);
405 wr32(&mut d, 1320, 60);
406 wr32(&mut d, 36, 65536); d[1372..1376].copy_from_slice(&crate::superblock::FS_UFS1_MAGIC.to_le_bytes());
408 let sb = Superblock::parse(&d).unwrap();
409 assert_eq!(sb.version, UfsVersion::Ufs1);
410
411 let bsize = 512usize;
412 let mut part = vec![0u8; bsize * 64];
413 let mut dn = vec![0u8; 128];
416 dn[0..2].copy_from_slice(&0o100644u16.to_le_bytes());
417 dn[8..16].copy_from_slice(&(13u64 * bsize as u64).to_le_bytes());
418 for i in 0..12u32 {
419 let f = 20 + i;
420 dn[40 + i as usize * 4..40 + i as usize * 4 + 4].copy_from_slice(&f.to_le_bytes());
421 part[f as usize * bsize] = i as u8 + 1;
422 }
423 let sib = 40u32;
424 dn[88..92].copy_from_slice(&sib.to_le_bytes());
425 part[sib as usize * bsize..sib as usize * bsize + 4].copy_from_slice(&41u32.to_le_bytes());
427 part[41 * bsize] = 0xCD;
428 let inode = Inode::parse(&dn, UfsVersion::Ufs1, crate::Endian::Little).unwrap();
429 let got = read_inode_file(&part, &sb, &inode).unwrap();
430 assert_eq!(got.len(), 13 * bsize);
431 assert_eq!(
432 got[12 * bsize],
433 0xCD,
434 "UFS1 4-byte indirect pointer resolved"
435 );
436 }
437
438 #[test]
439 fn read_file_by_inode_number_locates_then_reads() {
440 let (part, sb) = minimal_fs();
443 let got = read_file(&part, &sb, 4).unwrap();
444 assert_eq!(got, b"twenty-byte-content!");
445 }
446
447 #[test]
448 fn read_file_hole_zero_fills() {
449 let sb = tiny_sb(512, 512, 64);
450 let mut part = vec![0u8; 512 * 16];
451 let f1 = 9u64;
453 part[f1 as usize * 512] = 0x77;
454 let inode = inode_with(2 * 512, &[0, f1], [0, 0, 0]);
455 let got = read_inode_file(&part, &sb, &inode).unwrap();
456 assert!(got[..512].iter().all(|&b| b == 0), "hole zero-filled");
457 assert_eq!(got[512], 0x77);
458 }
459
460 #[test]
461 fn read_file_rejects_allocation_bomb() {
462 let sb = tiny_sb(512, 512, 64);
463 let part = vec![0u8; 512 * 4];
464 let inode = inode_with(u64::MAX, &[0; 12], [0, 0, 0]);
465 let err = read_inode_file(&part, &sb, &inode).unwrap_err();
466 assert!(matches!(
467 err,
468 UfsError::ImpossibleGeometry {
469 field: "di_size",
470 ..
471 }
472 ));
473 }
474
475 #[test]
476 fn read_file_rejects_zero_bsize() {
477 let mut sb = tiny_sb(512, 512, 64);
478 sb.bsize = 0;
479 let part = vec![0u8; 512 * 4];
480 let inode = inode_with(100, &[1], [0, 0, 0]);
481 let err = read_inode_file(&part, &sb, &inode).unwrap_err();
482 assert!(matches!(
483 err,
484 UfsError::ImpossibleGeometry {
485 field: "fs_bsize",
486 ..
487 }
488 ));
489 }
490
491 #[test]
492 fn read_file_rejects_zero_fsize() {
493 let mut sb = tiny_sb(512, 512, 64);
494 sb.fsize = 0;
495 let part = vec![0u8; 512 * 4];
496 let inode = inode_with(100, &[1], [0, 0, 0]);
497 let err = read_inode_file(&part, &sb, &inode).unwrap_err();
498 assert!(matches!(
499 err,
500 UfsError::ImpossibleGeometry {
501 field: "fs_fsize",
502 ..
503 }
504 ));
505 }
506
507 #[test]
508 fn read_file_truncated_partition_no_panic() {
509 let sb = tiny_sb(512, 512, 64);
510 let part = vec![0u8; 512 * 3];
513 let inode = inode_with(2 * 512, &[2, 100], [0, 0, 0]); let got = read_inode_file(&part, &sb, &inode).unwrap();
515 assert_eq!(got.len(), 2 * 512);
516 }
517
518 #[test]
519 fn resolve_block_zero_nindir_collapses_to_direct_only() {
520 let sb = tiny_sb(512, 512, 0); let inode = inode_with(20 * 512, &[5; 12], [40, 0, 0]);
522 assert_eq!(resolve_block(&[], &sb, &inode, 12, 0), 0);
524 assert_eq!(resolve_block(&[], &sb, &inode, 0, 0), 5);
526 }
527
528 #[test]
529 fn indirect_ptr_hole_addr_is_zero() {
530 let sb = tiny_sb(512, 512, 64);
531 assert_eq!(indirect_ptr(&[0u8; 512], &sb, 0, 0), 0);
532 }
533
534 #[test]
535 fn read_symlink_target_fast_inline() {
536 let sb = tiny_sb(512, 512, 64);
537 let mut d = vec![0u8; 256];
539 d[0..2].copy_from_slice(&0o120755u16.to_le_bytes());
540 d[16..24].copy_from_slice(&5u64.to_le_bytes());
541 d[112..117].copy_from_slice(b"a/b/c");
542 let inode = Inode::parse(&d, UfsVersion::Ufs2, crate::Endian::Little).unwrap();
543 let target = read_symlink_target(&[], &sb, &inode).unwrap();
544 assert_eq!(target, b"a/b/c");
545 }
546
547 #[test]
548 fn read_symlink_target_slow_reads_data_block() {
549 let sb = tiny_sb(512, 512, 64);
550 let target = b"x".repeat(130);
552 let mut part = vec![0u8; 512 * 16];
553 let frag = 7u64;
554 part[frag as usize * 512..frag as usize * 512 + 130].copy_from_slice(&target);
555 let mut d = vec![0u8; 256];
558 d[0..2].copy_from_slice(&0o120755u16.to_le_bytes());
559 d[16..24].copy_from_slice(&130u64.to_le_bytes());
560 d[112..120].copy_from_slice(&frag.to_le_bytes());
561 let inode = Inode::parse(&d, UfsVersion::Ufs2, crate::Endian::Little).unwrap();
562 assert!(
563 inode.symlink_target().is_none(),
564 "slow symlink is not inline"
565 );
566 let got = read_symlink_target(&part, &sb, &inode).unwrap();
567 assert_eq!(got, target);
568 }
569
570 fn minimal_fs() -> (Vec<u8>, Superblock) {
574 let sb = tiny_sb(512, 512, 64);
575 let fsize = 512usize;
576 let iblkno = 4usize;
577 let fpg = 4096usize;
578 let ipg = 128usize;
579 let isz = 256usize;
580 let ino_byte = |ino: usize| (ino / ipg * fpg + iblkno) * fsize + (ino % ipg) * isz;
581
582 let root_frag = 300u64;
583 let file_frag = 301u64;
584 let max = [ino_byte(5), (file_frag as usize + 1) * fsize]
585 .into_iter()
586 .max()
587 .unwrap();
588 let mut part = vec![0u8; max + 16];
589
590 let mut rdi = vec![0u8; isz];
592 rdi[0..2].copy_from_slice(&0o040755u16.to_le_bytes());
593 rdi[16..24].copy_from_slice(&512u64.to_le_bytes());
594 rdi[112..120].copy_from_slice(&root_frag.to_le_bytes());
595 part[ino_byte(2)..ino_byte(2) + isz].copy_from_slice(&rdi);
596
597 let payload = b"twenty-byte-content!";
599 let mut fi = vec![0u8; isz];
600 fi[0..2].copy_from_slice(&0o100644u16.to_le_bytes());
601 fi[16..24].copy_from_slice(&(payload.len() as u64).to_le_bytes());
602 fi[112..120].copy_from_slice(&file_frag.to_le_bytes());
603 part[ino_byte(4)..ino_byte(4) + isz].copy_from_slice(&fi);
604 let fb = file_frag as usize * fsize;
605 part[fb..fb + payload.len()].copy_from_slice(payload);
606
607 let mut rb = Vec::new();
609 let direct = |ino: u32, reclen: u16, name: &[u8]| -> Vec<u8> {
610 let mut e = vec![0u8; reclen as usize];
611 e[0..4].copy_from_slice(&ino.to_le_bytes());
612 e[4..6].copy_from_slice(&reclen.to_le_bytes());
613 e[6] = 8; e[7] = name.len() as u8;
615 e[8..8 + name.len()].copy_from_slice(name);
616 e
617 };
618 rb.extend(direct(2, 12, b"."));
619 rb.extend(direct(2, 12, b".."));
620 rb.extend(direct(4, 512 - 24, b"f"));
621 let rbo = root_frag as usize * fsize;
622 part[rbo..rbo + rb.len()].copy_from_slice(&rb);
623
624 (part, sb)
625 }
626
627 #[test]
628 fn read_path_content_reads_a_file_and_missing_is_none() {
629 let (part, sb) = minimal_fs();
630 let got = read_path_content(&part, &sb, "/f").unwrap().expect("found");
631 assert_eq!(got, b"twenty-byte-content!");
632 assert!(read_path_content(&part, &sb, "/does-not-exist")
633 .unwrap()
634 .is_none());
635 }
636}