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 disk_name: [u8; MAX_NAME_LEN],
41 disk_name_len: u8,
43 creation_date: AmigaDate,
45 last_modified: AmigaDate,
47}
48
49struct ProbeResult {
51 fs_type: FsType,
52 fs_flags: FsFlags,
53 root_block: u32,
54 log_blocksize: u8,
55 block_size: usize,
56 hash_table_size: u32,
57 disk_name: [u8; MAX_NAME_LEN],
58 disk_name_len: u8,
59 creation_date: AmigaDate,
60 last_modified: AmigaDate,
61}
62
63impl<'a, D: SectorDevice> AffsReaderVar<'a, D> {
64 pub fn new(device: &'a D, total_sectors: u64) -> Result<Self> {
73 let result = Self::probe(device, total_sectors)?;
74
75 Ok(Self {
76 device,
77 fs_type: result.fs_type,
78 fs_flags: result.fs_flags,
79 root_block: result.root_block,
80 total_blocks: (total_sectors >> result.log_blocksize) as u32,
81 log_blocksize: result.log_blocksize,
82 block_size: result.block_size,
83 hash_table_size: result.hash_table_size,
84 disk_name: result.disk_name,
85 disk_name_len: result.disk_name_len,
86 creation_date: result.creation_date,
87 last_modified: result.last_modified,
88 })
89 }
90
91 fn probe(device: &'a D, _total_sectors: u64) -> Result<ProbeResult> {
93 let mut buf = [0u8; MAX_BLOCK_SIZE];
95
96 for boot_sector in 0..=MAX_BOOT_BLOCK {
98 if Self::read_sectors(device, boot_sector as u64, &mut buf[..BOOT_BLOCK_SIZE]).is_err()
100 {
101 continue;
102 }
103
104 if &buf[0..3] != b"DOS" {
106 continue;
107 }
108
109 let flags = buf[3];
111 if (flags & DOSFS_FFS) == 0 {
112 continue; }
114
115 let fs_type = FsType::Ffs;
116 let fs_flags = FsFlags::from_dos_type(flags);
117
118 if buf[12] != 0 {
120 let checksum = read_u32_be_slice(&buf, 4);
121 let boot_buf: &[u8; BOOT_BLOCK_SIZE] = buf[..BOOT_BLOCK_SIZE].try_into().unwrap();
122 let calculated = boot_sum(boot_buf);
123 if checksum != calculated {
124 continue;
125 }
126 }
127
128 let root_block_num = read_u32_be_slice(&buf, 8);
129
130 for log_blocksize in 0..=MAX_LOG_BLOCK_SIZE {
132 let block_size = 512usize << log_blocksize;
133
134 let root_sector = (root_block_num as u64) << log_blocksize;
136 if Self::read_sectors(device, root_sector, &mut buf[..block_size]).is_err() {
137 continue;
138 }
139
140 let block_type = read_i32_be_slice(&buf, 0);
142 if block_type != T_HEADER {
143 continue;
144 }
145
146 let sec_type = read_i32_be_slice(&buf, block_size - 4);
148 if sec_type != ST_ROOT {
149 continue;
150 }
151
152 let hash_table_size = read_u32_be_slice(&buf, 12);
154 if hash_table_size == 0 {
155 continue;
156 }
157
158 let checksum = read_u32_be_slice(&buf, 20);
160 let calculated = normal_sum_slice(&buf[..block_size], 20);
161 if checksum != calculated {
162 continue;
163 }
164
165 let name_offset = block_size - FILE_LOCATION + 108; let name_len = buf[name_offset].min(MAX_NAME_LEN as u8);
168 let mut disk_name = [0u8; MAX_NAME_LEN];
169 disk_name[..name_len as usize]
170 .copy_from_slice(&buf[name_offset + 1..name_offset + 1 + name_len as usize]);
171
172 let date_offset = block_size - FILE_LOCATION + 0x1A4 - (BLOCK_SIZE - FILE_LOCATION);
176 let creation_date = AmigaDate::new(
177 read_i32_be_slice(&buf, date_offset),
178 read_i32_be_slice(&buf, date_offset + 4),
179 read_i32_be_slice(&buf, date_offset + 8),
180 );
181
182 let mod_offset = block_size - FILE_LOCATION + 0x1D8 - (BLOCK_SIZE - FILE_LOCATION);
183 let last_modified = AmigaDate::new(
184 read_i32_be_slice(&buf, mod_offset),
185 read_i32_be_slice(&buf, mod_offset + 4),
186 read_i32_be_slice(&buf, mod_offset + 8),
187 );
188
189 return Ok(ProbeResult {
190 fs_type,
191 fs_flags,
192 root_block: root_block_num,
193 log_blocksize,
194 block_size,
195 hash_table_size,
196 disk_name,
197 disk_name_len: name_len,
198 creation_date,
199 last_modified,
200 });
201 }
202 }
203
204 Err(AffsError::InvalidDosType)
205 }
206
207 fn read_sectors(device: &D, start_sector: u64, buf: &mut [u8]) -> Result<()> {
209 let num_sectors = buf.len() / BLOCK_SIZE;
210 let mut sector_buf = [0u8; BLOCK_SIZE];
211
212 for i in 0..num_sectors {
213 device
214 .read_sector(start_sector + i as u64, &mut sector_buf)
215 .map_err(|()| AffsError::BlockReadError)?;
216 buf[i * BLOCK_SIZE..(i + 1) * BLOCK_SIZE].copy_from_slice(§or_buf);
217 }
218
219 Ok(())
220 }
221
222 fn read_block_into(&self, block: u32, buf: &mut [u8]) -> Result<()> {
224 let start_sector = (block as u64) << self.log_blocksize;
225 Self::read_sectors(self.device, start_sector, &mut buf[..self.block_size])
226 }
227
228 #[inline]
230 pub const fn fs_type(&self) -> FsType {
231 self.fs_type
232 }
233
234 #[inline]
236 pub const fn fs_flags(&self) -> FsFlags {
237 self.fs_flags
238 }
239
240 #[inline]
242 pub const fn root_block(&self) -> u32 {
243 self.root_block
244 }
245
246 #[inline]
248 pub const fn total_blocks(&self) -> u32 {
249 self.total_blocks
250 }
251
252 #[inline]
254 pub const fn block_size(&self) -> usize {
255 self.block_size
256 }
257
258 #[inline]
260 pub const fn log_blocksize(&self) -> u8 {
261 self.log_blocksize
262 }
263
264 #[inline]
266 pub fn disk_name(&self) -> &[u8] {
267 &self.disk_name[..self.disk_name_len as usize]
268 }
269
270 #[inline]
272 pub fn disk_name_str(&self) -> Option<&str> {
273 crate::utf8::from_utf8(self.disk_name())
274 }
275
276 #[inline]
278 pub fn label(&self) -> &[u8] {
279 self.disk_name()
280 }
281
282 #[inline]
284 pub fn label_str(&self) -> Option<&str> {
285 self.disk_name_str()
286 }
287
288 #[inline]
290 pub const fn creation_date(&self) -> AmigaDate {
291 self.creation_date
292 }
293
294 #[inline]
296 pub const fn last_modified(&self) -> AmigaDate {
297 self.last_modified
298 }
299
300 #[inline]
305 pub fn mtime(&self) -> i64 {
306 self.last_modified.to_unix_timestamp()
307 }
308
309 #[inline]
311 pub const fn hash_table_size(&self) -> u32 {
312 self.hash_table_size
313 }
314
315 #[inline]
317 pub const fn is_intl(&self) -> bool {
318 self.fs_flags.intl || self.fs_flags.dircache
319 }
320
321 pub fn read_symlink(&self, block: u32, out: &mut [u8]) -> Result<usize> {
330 let mut buf = [0u8; MAX_BLOCK_SIZE];
331 self.read_block_into(block, &mut buf)?;
332
333 let sec_type = read_i32_be_slice(&buf, self.block_size - 4);
335 if sec_type != ST_LSOFT {
336 return Err(AffsError::NotASymlink);
337 }
338
339 Ok(read_symlink_target_with_block_size(
340 &buf[..self.block_size],
341 self.block_size,
342 out,
343 ))
344 }
345
346 pub fn read_root_dir(&self) -> Result<VarDirIter<'_, D>> {
348 let mut buf = [0u8; MAX_BLOCK_SIZE];
349 self.read_block_into(self.root_block, &mut buf)?;
350
351 let mut hash_table = [0u32; 256]; let ht_size = self.hash_table_size as usize;
354 for (i, slot) in hash_table.iter_mut().enumerate().take(ht_size.min(256)) {
355 *slot = read_u32_be_slice(&buf, SYMLINK_OFFSET + i * 4);
356 }
357
358 Ok(VarDirIter::new(
359 self.device,
360 hash_table,
361 ht_size,
362 self.log_blocksize,
363 self.block_size,
364 ))
365 }
366
367 pub fn read_dir(&self, block: u32) -> Result<VarDirIter<'_, D>> {
369 if block == self.root_block {
370 return self.read_root_dir();
371 }
372
373 let mut buf = [0u8; MAX_BLOCK_SIZE];
374 self.read_block_into(block, &mut buf)?;
375
376 let block_type = read_i32_be_slice(&buf, 0);
378 if block_type != T_HEADER {
379 return Err(AffsError::InvalidBlockType);
380 }
381
382 let sec_type = read_i32_be_slice(&buf, self.block_size - 4);
384 if sec_type != ST_DIR && sec_type != ST_LDIR {
385 return Err(AffsError::NotADirectory);
386 }
387
388 let mut hash_table = [0u32; 256];
390 let ht_size = self.hash_table_size as usize;
391 for (i, slot) in hash_table.iter_mut().enumerate().take(ht_size.min(256)) {
392 *slot = read_u32_be_slice(&buf, SYMLINK_OFFSET + i * 4);
393 }
394
395 Ok(VarDirIter::new(
396 self.device,
397 hash_table,
398 ht_size,
399 self.log_blocksize,
400 self.block_size,
401 ))
402 }
403}
404
405#[derive(Debug, Clone)]
407pub struct VarDirEntry {
408 pub name: [u8; MAX_NAME_LEN],
410 pub name_len: u8,
412 pub entry_type: EntryType,
414 pub block: u32,
416 pub parent: u32,
418 pub size: u32,
420 pub date: AmigaDate,
422}
423
424impl VarDirEntry {
425 #[inline]
427 pub fn name(&self) -> &[u8] {
428 &self.name[..self.name_len as usize]
429 }
430
431 #[inline]
433 pub fn name_str(&self) -> Option<&str> {
434 crate::utf8::from_utf8(self.name())
435 }
436
437 #[inline]
439 pub const fn is_dir(&self) -> bool {
440 self.entry_type.is_dir()
441 }
442
443 #[inline]
445 pub const fn is_file(&self) -> bool {
446 self.entry_type.is_file()
447 }
448
449 #[inline]
451 pub const fn is_symlink(&self) -> bool {
452 matches!(self.entry_type, EntryType::SoftLink)
453 }
454}
455
456pub struct VarDirIter<'a, D: SectorDevice> {
458 device: &'a D,
459 hash_table: [u32; 256],
460 hash_table_size: usize,
461 hash_index: usize,
462 current_chain: u32,
463 log_blocksize: u8,
464 block_size: usize,
465 buf: [u8; MAX_BLOCK_SIZE],
466}
467
468impl<'a, D: SectorDevice> VarDirIter<'a, D> {
469 fn new(
470 device: &'a D,
471 hash_table: [u32; 256],
472 hash_table_size: usize,
473 log_blocksize: u8,
474 block_size: usize,
475 ) -> Self {
476 Self {
477 device,
478 hash_table,
479 hash_table_size,
480 hash_index: 0,
481 current_chain: 0,
482 log_blocksize,
483 block_size,
484 buf: [0u8; MAX_BLOCK_SIZE],
485 }
486 }
487
488 fn read_block_into(&mut self, block: u32) -> Result<()> {
489 let start_sector = (block as u64) << self.log_blocksize;
490 let num_sectors = 1usize << self.log_blocksize;
491 let mut sector_buf = [0u8; BLOCK_SIZE];
492
493 for i in 0..num_sectors {
494 self.device
495 .read_sector(start_sector + i as u64, &mut sector_buf)
496 .map_err(|()| AffsError::BlockReadError)?;
497 self.buf[i * BLOCK_SIZE..(i + 1) * BLOCK_SIZE].copy_from_slice(§or_buf);
498 }
499
500 Ok(())
501 }
502
503 fn parse_entry(&self, block: u32) -> Option<VarDirEntry> {
504 let buf = &self.buf[..self.block_size];
505
506 let sec_type = read_i32_be_slice(buf, self.block_size - 4);
508 let entry_type = EntryType::from_sec_type(sec_type)?;
509
510 let name_offset = self.block_size - FILE_LOCATION + 108;
512 let name_len = buf[name_offset].min(MAX_NAME_LEN as u8);
513 let mut name = [0u8; MAX_NAME_LEN];
514 name[..name_len as usize]
515 .copy_from_slice(&buf[name_offset + 1..name_offset + 1 + name_len as usize]);
516
517 let size_offset = self.block_size - FILE_LOCATION + 12;
520 let size = read_u32_be_slice(buf, size_offset);
521
522 let parent = read_u32_be_slice(buf, self.block_size - 12);
524
525 let date_offset = self.block_size - FILE_LOCATION + 0x1A4 - (BLOCK_SIZE - FILE_LOCATION);
527 let date = AmigaDate::new(
528 read_i32_be_slice(buf, date_offset),
529 read_i32_be_slice(buf, date_offset + 4),
530 read_i32_be_slice(buf, date_offset + 8),
531 );
532
533 Some(VarDirEntry {
534 name,
535 name_len,
536 entry_type,
537 block,
538 parent,
539 size,
540 date,
541 })
542 }
543}
544
545impl<D: SectorDevice> Iterator for VarDirIter<'_, D> {
546 type Item = Result<VarDirEntry>;
547
548 fn next(&mut self) -> Option<Self::Item> {
549 loop {
550 if self.current_chain != 0 {
552 if let Err(e) = self.read_block_into(self.current_chain) {
553 return Some(Err(e));
554 }
555
556 let block = self.current_chain;
557
558 self.current_chain = read_u32_be_slice(&self.buf, self.block_size - 16);
560
561 if let Some(entry) = self.parse_entry(block) {
562 return Some(Ok(entry));
563 }
564 continue;
565 }
566
567 while self.hash_index < self.hash_table_size {
569 let block = self.hash_table[self.hash_index];
570 self.hash_index += 1;
571
572 if block != 0 {
573 self.current_chain = block;
574 break;
575 }
576 }
577
578 if self.current_chain == 0 {
579 return None;
580 }
581 }
582 }
583}
584
585#[cfg(test)]
586mod tests {
587 use super::*;
588
589 struct DummySectorDevice;
590
591 impl SectorDevice for DummySectorDevice {
592 fn read_sector(&self, _sector: u64, _buf: &mut [u8; 512]) -> core::result::Result<(), ()> {
593 Err(())
594 }
595 }
596
597 #[test]
598 fn test_var_reader_error_on_bad_device() {
599 let device = DummySectorDevice;
600 let result = AffsReaderVar::new(&device, 1760);
601 assert!(result.is_err());
602 }
603
604 struct DummyGoodDevice;
607
608 impl DummyGoodDevice {
609 fn write_u32_be(buf: &mut [u8], offset: usize, val: u32) {
610 let bytes = val.to_be_bytes();
611 buf[offset..offset + 4].copy_from_slice(&bytes);
612 }
613
614 fn write_i32_be(buf: &mut [u8], offset: usize, val: i32) {
615 let bytes = val.to_be_bytes();
616 buf[offset..offset + 4].copy_from_slice(&bytes);
617 }
618 }
619
620 impl SectorDevice for DummyGoodDevice {
621 fn read_sector(&self, sector: u64, buf: &mut [u8; 512]) -> core::result::Result<(), ()> {
622 for b in buf.iter_mut() {
627 *b = 0;
628 }
629
630 match sector {
631 0 => {
632 let mut boot = [0u8; 1024];
634 boot.fill(0);
635 boot[0..3].copy_from_slice(b"DOS");
636 boot[3] = DOSFS_FFS; DummyGoodDevice::write_u32_be(&mut boot, 8, 2); buf.copy_from_slice(&boot[0..512]);
640 Ok(())
641 }
642 1 => {
643 let mut boot = [0u8; 1024];
645 boot.fill(0);
646 boot[0..3].copy_from_slice(b"DOS");
647 boot[3] = DOSFS_FFS;
648 DummyGoodDevice::write_u32_be(&mut boot, 8, 2);
649 buf.copy_from_slice(&boot[512..1024]);
650 Ok(())
651 }
652 2 => {
653 let mut rb = [0u8; 512];
655 rb.fill(0);
656 DummyGoodDevice::write_i32_be(&mut rb, 0, T_HEADER);
658 DummyGoodDevice::write_u32_be(&mut rb, 12, 4);
660 DummyGoodDevice::write_i32_be(&mut rb, 512 - 4, ST_ROOT);
663 DummyGoodDevice::write_u32_be(&mut rb, SYMLINK_OFFSET, 5);
665 let name_offset = 512 - FILE_LOCATION + 108;
667 rb[name_offset] = 4; rb[name_offset + 1..name_offset + 1 + 4].copy_from_slice(b"test");
669 let checksum = normal_sum_slice(&rb[..512], 20);
672 DummyGoodDevice::write_u32_be(&mut rb, 20, checksum);
673 buf.copy_from_slice(&rb);
674 Ok(())
675 }
676 5 => {
677 let mut eb = [0u8; 512];
679 eb.fill(0);
680 DummyGoodDevice::write_i32_be(&mut eb, 0, T_HEADER);
681 DummyGoodDevice::write_i32_be(&mut eb, 512 - 4, ST_FILE);
683 let name_offset = 512 - FILE_LOCATION + 108;
685 eb[name_offset] = 4;
686 eb[name_offset + 1..name_offset + 1 + 4].copy_from_slice(b"file");
687 let size_offset = 512 - FILE_LOCATION + 12;
689 DummyGoodDevice::write_u32_be(&mut eb, size_offset, 123);
690 DummyGoodDevice::write_u32_be(&mut eb, 512 - 12, 2);
692 buf.copy_from_slice(&eb);
693 Ok(())
694 }
695 _ => Err(()),
696 }
697 }
698 }
699
700 #[test]
701 fn test_var_probe_and_dir_iter() {
702 let device = DummyGoodDevice;
703 let reader = AffsReaderVar::new(&device, 100).expect("probe should succeed");
705 assert_eq!(reader.block_size(), 512);
706 assert_eq!(reader.root_block(), 2);
707 assert_eq!(reader.disk_name_str(), Some("test"));
708
709 let mut iter = reader.read_root_dir().expect("read_root_dir");
711 let first = iter.next().expect("entry").expect("ok entry");
712 assert_eq!(first.name_str(), Some("file"));
713 assert_eq!(first.size, 123);
714 assert_eq!(first.block, 5);
715 }
716}