Skip to main content

a2fuse/prodos/
directory.rs

1use std::collections::HashSet;
2
3use crate::error::{A2FuseError, Result};
4
5use super::block::BlockDevice;
6use super::path::decode_filename_with_case;
7use super::types::{AccessFlags, ProdosTimestamp, StorageType};
8
9pub const PRODOS_ENTRY_LENGTH: usize = 39;
10pub const PRODOS_ENTRIES_PER_BLOCK: usize = 13;
11
12#[derive(Clone, Debug, Eq, PartialEq)]
13pub struct DirectoryEntry {
14    pub name: String,
15    pub storage_type: StorageType,
16    pub file_type: u8,
17    pub key_pointer: u16,
18    pub blocks_used: u16,
19    pub eof: u32,
20    pub creation: Option<ProdosTimestamp>,
21    pub modification: Option<ProdosTimestamp>,
22    pub access: AccessFlags,
23    pub aux_type: u16,
24    pub header_pointer: u16,
25}
26
27impl DirectoryEntry {
28    pub fn parse(bytes: &[u8]) -> Result<Option<Self>> {
29        if bytes.len() < PRODOS_ENTRY_LENGTH {
30            return Err(A2FuseError::InvalidDirectoryEntry(format!(
31                "expected {PRODOS_ENTRY_LENGTH} bytes, got {}",
32                bytes.len()
33            )));
34        }
35
36        let storage_nibble = bytes[0] >> 4;
37        let name_length = usize::from(bytes[0] & 0x0f);
38        if storage_nibble == 0 {
39            return Ok(None);
40        }
41        if name_length == 0 || name_length > 15 {
42            return Err(A2FuseError::InvalidDirectoryEntry(format!(
43                "invalid filename length {name_length}"
44            )));
45        }
46        let storage_type = StorageType::from_nibble(storage_nibble).ok_or_else(|| {
47            A2FuseError::InvalidDirectoryEntry(format!("unknown storage type {storage_nibble:#x}"))
48        })?;
49
50        Ok(Some(Self {
51            name: decode_filename_with_case(&bytes[1..1 + name_length], read_u16(bytes, 0x1c)),
52            storage_type,
53            file_type: bytes[0x10],
54            key_pointer: read_u16(bytes, 0x11),
55            blocks_used: read_u16(bytes, 0x13),
56            eof: read_u24(bytes, 0x15),
57            creation: ProdosTimestamp::decode(read_u16(bytes, 0x18), read_u16(bytes, 0x1a)),
58            access: AccessFlags(bytes[0x1e]),
59            aux_type: read_u16(bytes, 0x1f),
60            modification: ProdosTimestamp::decode(read_u16(bytes, 0x21), read_u16(bytes, 0x23)),
61            header_pointer: read_u16(bytes, 0x25),
62        }))
63    }
64
65    pub fn is_directory(&self) -> bool {
66        self.storage_type == StorageType::Subdirectory
67    }
68
69    pub fn is_file(&self) -> bool {
70        self.storage_type.is_regular_file()
71    }
72}
73
74#[derive(Clone, Debug, Eq, PartialEq)]
75pub struct Directory {
76    pub key_block: u16,
77    pub entries: Vec<DirectoryEntry>,
78}
79
80impl Directory {
81    pub fn read(device: &BlockDevice, key_block: u16) -> Result<Self> {
82        let mut entries = Vec::new();
83        let mut block_number = key_block;
84        let mut visited = HashSet::new();
85        let mut first_block = true;
86
87        while block_number != 0 {
88            if !visited.insert(block_number) {
89                return Err(A2FuseError::InvalidDirectory(format!(
90                    "directory block chain contains a cycle at block {block_number}"
91                )));
92            }
93
94            let block = device.read_block(block_number)?;
95            let next_block = u16::from_le_bytes([block[2], block[3]]);
96
97            for slot in 0..PRODOS_ENTRIES_PER_BLOCK {
98                if first_block && slot == 0 {
99                    continue;
100                }
101                let start = 4 + slot * PRODOS_ENTRY_LENGTH;
102                let end = start + PRODOS_ENTRY_LENGTH;
103                if let Some(entry) = DirectoryEntry::parse(&block[start..end])?
104                    && !matches!(
105                        entry.storage_type,
106                        StorageType::VolumeHeader | StorageType::SubdirectoryHeader
107                    )
108                {
109                    entries.push(entry);
110                }
111            }
112
113            first_block = false;
114            block_number = next_block;
115        }
116
117        Ok(Self { key_block, entries })
118    }
119}
120
121fn read_u16(bytes: &[u8], offset: usize) -> u16 {
122    u16::from_le_bytes([bytes[offset], bytes[offset + 1]])
123}
124
125fn read_u24(bytes: &[u8], offset: usize) -> u32 {
126    u32::from(bytes[offset])
127        | (u32::from(bytes[offset + 1]) << 8)
128        | (u32::from(bytes[offset + 2]) << 16)
129}