1use crate::checksum::{boot_sum, normal_sum_slice, read_i32_be_slice, read_u32_be_slice};
9use crate::constants::*;
10use crate::date::AmigaDate;
11use crate::error::{AffsError, Result};
12use crate::symlink::read_symlink_target_with_block_size;
13use crate::types::{EntryType, FsFlags, FsType, SectorDevice};
14
15pub const MAX_BLOCK_SIZE: usize = 8192;
17
18pub struct AffsReaderVar<'a, D: SectorDevice> {
24 device: &'a D,
25 fs_type: FsType,
27 fs_flags: FsFlags,
29 root_block: u32,
31 total_blocks: u32,
33 log_blocksize: u8,
35 block_size: usize,
37 hash_table_size: u32,
39 #[allow(dead_code)]
41 boot_sector: u32,
42 disk_name: [u8; MAX_NAME_LEN],
44 disk_name_len: u8,
46 creation_date: AmigaDate,
48 last_modified: AmigaDate,
50}
51
52struct ProbeResult {
54 fs_type: FsType,
55 fs_flags: FsFlags,
56 root_block: u32,
57 log_blocksize: u8,
58 block_size: usize,
59 hash_table_size: u32,
60 boot_sector: u32,
61 disk_name: [u8; MAX_NAME_LEN],
62 disk_name_len: u8,
63 creation_date: AmigaDate,
64 last_modified: AmigaDate,
65}
66
67impl<'a, D: SectorDevice> AffsReaderVar<'a, D> {
68 pub fn new(device: &'a D, total_sectors: u64) -> Result<Self> {
77 let result = Self::probe(device, total_sectors)?;
78
79 Ok(Self {
80 device,
81 fs_type: result.fs_type,
82 fs_flags: result.fs_flags,
83 root_block: result.root_block,
84 total_blocks: (total_sectors >> result.log_blocksize) as u32,
85 log_blocksize: result.log_blocksize,
86 block_size: result.block_size,
87 hash_table_size: result.hash_table_size,
88 boot_sector: result.boot_sector,
89 disk_name: result.disk_name,
90 disk_name_len: result.disk_name_len,
91 creation_date: result.creation_date,
92 last_modified: result.last_modified,
93 })
94 }
95
96 fn probe(device: &'a D, _total_sectors: u64) -> Result<ProbeResult> {
98 let mut buf = [0u8; MAX_BLOCK_SIZE];
100
101 for boot_sector in 0..=MAX_BOOT_BLOCK {
103 if Self::read_sectors(device, boot_sector as u64, &mut buf[..BOOT_BLOCK_SIZE]).is_err()
105 {
106 continue;
107 }
108
109 if &buf[0..3] != b"DOS" {
111 continue;
112 }
113
114 let flags = buf[3];
116 if (flags & DOSFS_FFS) == 0 {
117 continue; }
119
120 let fs_type = FsType::Ffs;
121 let fs_flags = FsFlags::from_dos_type(flags);
122
123 if buf[12] != 0 {
125 let checksum = read_u32_be_slice(&buf, 4);
126 let boot_buf: &[u8; BOOT_BLOCK_SIZE] = buf[..BOOT_BLOCK_SIZE].try_into().unwrap();
127 let calculated = boot_sum(boot_buf);
128 if checksum != calculated {
129 continue;
130 }
131 }
132
133 let root_block_num = read_u32_be_slice(&buf, 8);
134
135 for log_blocksize in 0..=MAX_LOG_BLOCK_SIZE {
137 let block_size = 512usize << log_blocksize;
138
139 let root_sector = (root_block_num as u64) << log_blocksize;
141 if Self::read_sectors(device, root_sector, &mut buf[..block_size]).is_err() {
142 continue;
143 }
144
145 let block_type = read_i32_be_slice(&buf, 0);
147 if block_type != T_HEADER {
148 continue;
149 }
150
151 let sec_type = read_i32_be_slice(&buf, block_size - 4);
153 if sec_type != ST_ROOT {
154 continue;
155 }
156
157 let hash_table_size = read_u32_be_slice(&buf, 12);
159 if hash_table_size == 0 {
160 continue;
161 }
162
163 let checksum = read_u32_be_slice(&buf, 20);
165 let calculated = normal_sum_slice(&buf[..block_size], 20);
166 if checksum != calculated {
167 continue;
168 }
169
170 let name_offset = block_size - FILE_LOCATION + 108; let name_len = buf[name_offset].min(MAX_NAME_LEN as u8);
173 let mut disk_name = [0u8; MAX_NAME_LEN];
174 disk_name[..name_len as usize]
175 .copy_from_slice(&buf[name_offset + 1..name_offset + 1 + name_len as usize]);
176
177 let date_offset = block_size - FILE_LOCATION + 0x1A4 - (BLOCK_SIZE - FILE_LOCATION);
181 let creation_date = AmigaDate::new(
182 read_i32_be_slice(&buf, date_offset),
183 read_i32_be_slice(&buf, date_offset + 4),
184 read_i32_be_slice(&buf, date_offset + 8),
185 );
186
187 let mod_offset = block_size - FILE_LOCATION + 0x1D8 - (BLOCK_SIZE - FILE_LOCATION);
188 let last_modified = AmigaDate::new(
189 read_i32_be_slice(&buf, mod_offset),
190 read_i32_be_slice(&buf, mod_offset + 4),
191 read_i32_be_slice(&buf, mod_offset + 8),
192 );
193
194 return Ok(ProbeResult {
195 fs_type,
196 fs_flags,
197 root_block: root_block_num,
198 log_blocksize,
199 block_size,
200 hash_table_size,
201 boot_sector,
202 disk_name,
203 disk_name_len: name_len,
204 creation_date,
205 last_modified,
206 });
207 }
208 }
209
210 Err(AffsError::InvalidDosType)
211 }
212
213 fn read_sectors(device: &D, start_sector: u64, buf: &mut [u8]) -> Result<()> {
215 let num_sectors = buf.len() / BLOCK_SIZE;
216 let mut sector_buf = [0u8; BLOCK_SIZE];
217
218 for i in 0..num_sectors {
219 device
220 .read_sector(start_sector + i as u64, &mut sector_buf)
221 .map_err(|()| AffsError::BlockReadError)?;
222 buf[i * BLOCK_SIZE..(i + 1) * BLOCK_SIZE].copy_from_slice(§or_buf);
223 }
224
225 Ok(())
226 }
227
228 fn read_block_into(&self, block: u32, buf: &mut [u8]) -> Result<()> {
230 let start_sector = (block as u64) << self.log_blocksize;
231 Self::read_sectors(self.device, start_sector, &mut buf[..self.block_size])
232 }
233
234 #[inline]
236 pub const fn fs_type(&self) -> FsType {
237 self.fs_type
238 }
239
240 #[inline]
242 pub const fn fs_flags(&self) -> FsFlags {
243 self.fs_flags
244 }
245
246 #[inline]
248 pub const fn root_block(&self) -> u32 {
249 self.root_block
250 }
251
252 #[inline]
254 pub const fn total_blocks(&self) -> u32 {
255 self.total_blocks
256 }
257
258 #[inline]
260 pub const fn block_size(&self) -> usize {
261 self.block_size
262 }
263
264 #[inline]
266 pub const fn log_blocksize(&self) -> u8 {
267 self.log_blocksize
268 }
269
270 #[inline]
272 pub fn disk_name(&self) -> &[u8] {
273 &self.disk_name[..self.disk_name_len as usize]
274 }
275
276 #[inline]
278 pub fn disk_name_str(&self) -> Option<&str> {
279 core::str::from_utf8(self.disk_name()).ok()
280 }
281
282 #[inline]
284 pub fn label(&self) -> &[u8] {
285 self.disk_name()
286 }
287
288 #[inline]
290 pub fn label_str(&self) -> Option<&str> {
291 self.disk_name_str()
292 }
293
294 #[inline]
296 pub const fn creation_date(&self) -> AmigaDate {
297 self.creation_date
298 }
299
300 #[inline]
302 pub const fn last_modified(&self) -> AmigaDate {
303 self.last_modified
304 }
305
306 #[inline]
311 pub fn mtime(&self) -> i64 {
312 self.last_modified.to_unix_timestamp()
313 }
314
315 #[inline]
317 pub const fn hash_table_size(&self) -> u32 {
318 self.hash_table_size
319 }
320
321 #[inline]
323 pub const fn is_intl(&self) -> bool {
324 self.fs_flags.intl || self.fs_flags.dircache
325 }
326
327 pub fn read_symlink(&self, block: u32, out: &mut [u8]) -> Result<usize> {
336 let mut buf = [0u8; MAX_BLOCK_SIZE];
337 self.read_block_into(block, &mut buf)?;
338
339 let sec_type = read_i32_be_slice(&buf, self.block_size - 4);
341 if sec_type != ST_LSOFT {
342 return Err(AffsError::NotASymlink);
343 }
344
345 Ok(read_symlink_target_with_block_size(
346 &buf[..self.block_size],
347 self.block_size,
348 out,
349 ))
350 }
351
352 pub fn read_root_dir(&self) -> Result<VarDirIter<'_, D>> {
354 let mut buf = [0u8; MAX_BLOCK_SIZE];
355 self.read_block_into(self.root_block, &mut buf)?;
356
357 let mut hash_table = [0u32; 256]; let ht_size = self.hash_table_size as usize;
360 for (i, slot) in hash_table.iter_mut().enumerate().take(ht_size.min(256)) {
361 *slot = read_u32_be_slice(&buf, SYMLINK_OFFSET + i * 4);
362 }
363
364 Ok(VarDirIter::new(
365 self.device,
366 hash_table,
367 ht_size,
368 self.is_intl(),
369 self.log_blocksize,
370 self.block_size,
371 ))
372 }
373
374 pub fn read_dir(&self, block: u32) -> Result<VarDirIter<'_, D>> {
376 if block == self.root_block {
377 return self.read_root_dir();
378 }
379
380 let mut buf = [0u8; MAX_BLOCK_SIZE];
381 self.read_block_into(block, &mut buf)?;
382
383 let block_type = read_i32_be_slice(&buf, 0);
385 if block_type != T_HEADER {
386 return Err(AffsError::InvalidBlockType);
387 }
388
389 let sec_type = read_i32_be_slice(&buf, self.block_size - 4);
391 if sec_type != ST_DIR && sec_type != ST_LDIR {
392 return Err(AffsError::NotADirectory);
393 }
394
395 let mut hash_table = [0u32; 256];
397 let ht_size = self.hash_table_size as usize;
398 for (i, slot) in hash_table.iter_mut().enumerate().take(ht_size.min(256)) {
399 *slot = read_u32_be_slice(&buf, SYMLINK_OFFSET + i * 4);
400 }
401
402 Ok(VarDirIter::new(
403 self.device,
404 hash_table,
405 ht_size,
406 self.is_intl(),
407 self.log_blocksize,
408 self.block_size,
409 ))
410 }
411}
412
413#[derive(Debug, Clone)]
415pub struct VarDirEntry {
416 pub name: [u8; MAX_NAME_LEN],
418 pub name_len: u8,
420 pub entry_type: EntryType,
422 pub block: u32,
424 pub parent: u32,
426 pub size: u32,
428 pub date: AmigaDate,
430}
431
432impl VarDirEntry {
433 #[inline]
435 pub fn name(&self) -> &[u8] {
436 &self.name[..self.name_len as usize]
437 }
438
439 #[inline]
441 pub fn name_str(&self) -> Option<&str> {
442 core::str::from_utf8(self.name()).ok()
443 }
444
445 #[inline]
447 pub const fn is_dir(&self) -> bool {
448 self.entry_type.is_dir()
449 }
450
451 #[inline]
453 pub const fn is_file(&self) -> bool {
454 self.entry_type.is_file()
455 }
456
457 #[inline]
459 pub const fn is_symlink(&self) -> bool {
460 matches!(self.entry_type, EntryType::SoftLink)
461 }
462}
463
464pub struct VarDirIter<'a, D: SectorDevice> {
466 device: &'a D,
467 hash_table: [u32; 256],
468 hash_table_size: usize,
469 hash_index: usize,
470 current_chain: u32,
471 #[allow(dead_code)]
472 intl: bool,
473 log_blocksize: u8,
474 block_size: usize,
475 buf: [u8; MAX_BLOCK_SIZE],
476}
477
478impl<'a, D: SectorDevice> VarDirIter<'a, D> {
479 fn new(
480 device: &'a D,
481 hash_table: [u32; 256],
482 hash_table_size: usize,
483 intl: bool,
484 log_blocksize: u8,
485 block_size: usize,
486 ) -> Self {
487 Self {
488 device,
489 hash_table,
490 hash_table_size,
491 hash_index: 0,
492 current_chain: 0,
493 intl,
494 log_blocksize,
495 block_size,
496 buf: [0u8; MAX_BLOCK_SIZE],
497 }
498 }
499
500 fn read_block_into(&mut self, block: u32) -> Result<()> {
501 let start_sector = (block as u64) << self.log_blocksize;
502 let num_sectors = 1usize << self.log_blocksize;
503 let mut sector_buf = [0u8; BLOCK_SIZE];
504
505 for i in 0..num_sectors {
506 self.device
507 .read_sector(start_sector + i as u64, &mut sector_buf)
508 .map_err(|()| AffsError::BlockReadError)?;
509 self.buf[i * BLOCK_SIZE..(i + 1) * BLOCK_SIZE].copy_from_slice(§or_buf);
510 }
511
512 Ok(())
513 }
514
515 fn parse_entry(&self, block: u32) -> Option<VarDirEntry> {
516 let buf = &self.buf[..self.block_size];
517
518 let sec_type = read_i32_be_slice(buf, self.block_size - 4);
520 let entry_type = EntryType::from_sec_type(sec_type)?;
521
522 let name_offset = self.block_size - FILE_LOCATION + 108;
524 let name_len = buf[name_offset].min(MAX_NAME_LEN as u8);
525 let mut name = [0u8; MAX_NAME_LEN];
526 name[..name_len as usize]
527 .copy_from_slice(&buf[name_offset + 1..name_offset + 1 + name_len as usize]);
528
529 let size_offset = self.block_size - FILE_LOCATION + 12;
532 let size = read_u32_be_slice(buf, size_offset);
533
534 let parent = read_u32_be_slice(buf, self.block_size - 12);
536
537 let date_offset = self.block_size - FILE_LOCATION + 0x1A4 - (BLOCK_SIZE - FILE_LOCATION);
539 let date = AmigaDate::new(
540 read_i32_be_slice(buf, date_offset),
541 read_i32_be_slice(buf, date_offset + 4),
542 read_i32_be_slice(buf, date_offset + 8),
543 );
544
545 Some(VarDirEntry {
546 name,
547 name_len,
548 entry_type,
549 block,
550 parent,
551 size,
552 date,
553 })
554 }
555}
556
557impl<D: SectorDevice> Iterator for VarDirIter<'_, D> {
558 type Item = Result<VarDirEntry>;
559
560 fn next(&mut self) -> Option<Self::Item> {
561 loop {
562 if self.current_chain != 0 {
564 if let Err(e) = self.read_block_into(self.current_chain) {
565 return Some(Err(e));
566 }
567
568 let block = self.current_chain;
569
570 self.current_chain = read_u32_be_slice(&self.buf, self.block_size - 16);
572
573 if let Some(entry) = self.parse_entry(block) {
574 return Some(Ok(entry));
575 }
576 continue;
577 }
578
579 while self.hash_index < self.hash_table_size {
581 let block = self.hash_table[self.hash_index];
582 self.hash_index += 1;
583
584 if block != 0 {
585 self.current_chain = block;
586 break;
587 }
588 }
589
590 if self.current_chain == 0 {
591 return None;
592 }
593 }
594 }
595}
596
597#[cfg(test)]
598mod tests {
599 use super::*;
600
601 struct DummySectorDevice;
602
603 impl SectorDevice for DummySectorDevice {
604 fn read_sector(&self, _sector: u64, _buf: &mut [u8; 512]) -> core::result::Result<(), ()> {
605 Err(())
606 }
607 }
608
609 #[test]
610 fn test_var_reader_error_on_bad_device() {
611 let device = DummySectorDevice;
612 let result = AffsReaderVar::new(&device, 1760);
613 assert!(result.is_err());
614 }
615
616 struct DummyGoodDevice;
619
620 impl DummyGoodDevice {
621 fn write_u32_be(buf: &mut [u8], offset: usize, val: u32) {
622 let bytes = val.to_be_bytes();
623 buf[offset..offset + 4].copy_from_slice(&bytes);
624 }
625
626 fn write_i32_be(buf: &mut [u8], offset: usize, val: i32) {
627 let bytes = val.to_be_bytes();
628 buf[offset..offset + 4].copy_from_slice(&bytes);
629 }
630 }
631
632 impl SectorDevice for DummyGoodDevice {
633 fn read_sector(&self, sector: u64, buf: &mut [u8; 512]) -> core::result::Result<(), ()> {
634 for b in buf.iter_mut() {
639 *b = 0;
640 }
641
642 match sector {
643 0 => {
644 let mut boot = [0u8; 1024];
646 boot.fill(0);
647 boot[0..3].copy_from_slice(b"DOS");
648 boot[3] = DOSFS_FFS; DummyGoodDevice::write_u32_be(&mut boot, 8, 2); buf.copy_from_slice(&boot[0..512]);
652 Ok(())
653 }
654 1 => {
655 let mut boot = [0u8; 1024];
657 boot.fill(0);
658 boot[0..3].copy_from_slice(b"DOS");
659 boot[3] = DOSFS_FFS;
660 DummyGoodDevice::write_u32_be(&mut boot, 8, 2);
661 buf.copy_from_slice(&boot[512..1024]);
662 Ok(())
663 }
664 2 => {
665 let mut rb = [0u8; 512];
667 rb.fill(0);
668 DummyGoodDevice::write_i32_be(&mut rb, 0, T_HEADER);
670 DummyGoodDevice::write_u32_be(&mut rb, 12, 4);
672 DummyGoodDevice::write_i32_be(&mut rb, 512 - 4, ST_ROOT);
675 DummyGoodDevice::write_u32_be(&mut rb, SYMLINK_OFFSET, 5);
677 let name_offset = 512 - FILE_LOCATION + 108;
679 rb[name_offset] = 4; rb[name_offset + 1..name_offset + 1 + 4].copy_from_slice(b"test");
681 let checksum = normal_sum_slice(&rb[..512], 20);
684 DummyGoodDevice::write_u32_be(&mut rb, 20, checksum);
685 buf.copy_from_slice(&rb);
686 Ok(())
687 }
688 5 => {
689 let mut eb = [0u8; 512];
691 eb.fill(0);
692 DummyGoodDevice::write_i32_be(&mut eb, 0, T_HEADER);
693 DummyGoodDevice::write_i32_be(&mut eb, 512 - 4, ST_FILE);
695 let name_offset = 512 - FILE_LOCATION + 108;
697 eb[name_offset] = 4;
698 eb[name_offset + 1..name_offset + 1 + 4].copy_from_slice(b"file");
699 let size_offset = 512 - FILE_LOCATION + 12;
701 DummyGoodDevice::write_u32_be(&mut eb, size_offset, 123);
702 DummyGoodDevice::write_u32_be(&mut eb, 512 - 12, 2);
704 buf.copy_from_slice(&eb);
705 Ok(())
706 }
707 _ => Err(()),
708 }
709 }
710 }
711
712 #[test]
713 fn test_var_probe_and_dir_iter() {
714 let device = DummyGoodDevice;
715 let reader = AffsReaderVar::new(&device, 100).expect("probe should succeed");
717 assert_eq!(reader.block_size(), 512);
718 assert_eq!(reader.root_block(), 2);
719 assert_eq!(reader.disk_name_str(), Some("test"));
720
721 let mut iter = reader.read_root_dir().expect("read_root_dir");
723 let first = iter.next().expect("entry").expect("ok entry");
724 assert_eq!(first.name_str(), Some("file"));
725 assert_eq!(first.size, 123);
726 assert_eq!(first.block, 5);
727 }
728}