exfat/cluster_heap/directory/
mod.rs

1mod entry_iter;
2
3use core::fmt::Debug;
4use core::mem::{self, MaybeUninit};
5use core::ops::Deref;
6use core::slice;
7
8use alloc::rc::Rc;
9
10use super::entryset::{EntryIndex, EntrySet};
11use super::file::File;
12use super::meta::MetaFileDirectory;
13use super::metadata::Metadata;
14use crate::error::{DataError, Error, ImplementationError, InputError, OperationError};
15use crate::file::{FileOptions, MAX_FILENAME_SIZE, TouchOptions};
16use crate::fs::SectorIndex;
17use crate::io::{self, Block, Wrap};
18use crate::region::data::entry_type::{EntryType, RawEntryType};
19use crate::region::data::entryset::primary::{DateTime, FileDirectory, name_hash};
20use crate::region::data::entryset::secondary::{Filename, Secondary, StreamExtension};
21use crate::region::data::entryset::{ENTRY_SIZE, RawEntry, checksum};
22use crate::types::ClusterID;
23use crate::upcase_table::UpcaseTable;
24use entry_iter::EntryIter;
25
26pub struct Directory<B: Deref<Target = [Block]>, E: Debug, IO>
27where
28    IO: io::IO<Block<'static> = B, Error = E>,
29{
30    pub(crate) meta: MetaFileDirectory<IO>,
31    pub(crate) upcase_table: Rc<UpcaseTable>,
32    #[cfg(feature = "async")]
33    closed: bool,
34}
35
36impl<B: Deref<Target = [Block]>, E: Debug, IO> Directory<B, E, IO>
37where
38    IO: io::IO<Block<'static> = B, Error = E>,
39{
40    pub(crate) fn new(meta: MetaFileDirectory<IO>, upcase_table: Rc<UpcaseTable>) -> Self {
41        match () {
42            #[cfg(not(feature = "async"))]
43            _ => Self { meta, upcase_table },
44            #[cfg(feature = "async")]
45            _ => Self { meta, upcase_table, closed: false },
46        }
47    }
48}
49
50pub enum FileOrDirectory<B: Deref<Target = [Block]>, E: Debug, IO>
51where
52    IO: io::IO<Block<'static> = B, Error = E>,
53{
54    File(File<B, E, IO>),
55    Directory(Directory<B, E, IO>),
56}
57
58type FileOrDir<B, E, IO> = FileOrDirectory<B, E, IO>;
59
60#[cfg_attr(not(feature = "async"), maybe_async::must_be_sync)]
61impl<B: Deref<Target = [Block]>, E: Debug, IO> Directory<B, E, IO>
62where
63    IO: io::IO<Block<'static> = B, Error = E>,
64{
65    async fn walk_matches<F, H, R>(&mut self, f: F, mut h: H) -> Result<Option<R>, Error<E>>
66    where
67        F: Fn(&FileDirectory, &Secondary<StreamExtension>) -> bool,
68        H: FnMut(&EntrySet) -> Option<R>,
69    {
70        let mut iter = EntryIter::new(&mut self.meta).await?;
71        let mut file_directory: FileDirectory;
72        let mut stream_extension: Secondary<StreamExtension>;
73        loop {
74            let Some(entry) = iter.next().await? else { break };
75            let entry_type: RawEntryType = entry[0].into();
76            match entry_type.entry_type() {
77                Ok(EntryType::FileDirectory) => (),
78                Ok(_) => continue,
79                Err(t) => {
80                    warn!("Unexpected entry type {}", t);
81                    return Err(DataError::Metadata.into());
82                }
83            };
84            file_directory = unsafe { mem::transmute(*entry) };
85            if file_directory.secondary_count < 2 {
86                return Err(DataError::Metadata.into());
87            }
88            let entryset_sector_index = iter.sector_index;
89            let entryset_index = iter.index;
90            let entry = iter.next().await?.unwrap();
91            stream_extension = unsafe { mem::transmute(*entry) };
92            if !f(&file_directory, &stream_extension) {
93                iter.skip(file_directory.secondary_count - 2).await?;
94                continue;
95            }
96            let array: MaybeUninit<[u16; MAX_FILENAME_SIZE / 2]> = MaybeUninit::uninit();
97            let mut array: [u16; MAX_FILENAME_SIZE / 2] = unsafe { array.assume_init() };
98            for i in 0..(file_directory.secondary_count - 1) as usize {
99                if cfg!(feature = "max-filename-size-30") && (i + 1) * 15 > array.len() {
100                    continue;
101                }
102                let entry: &Filename = unsafe { mem::transmute(iter.next().await?.unwrap()) };
103                let slice = &unsafe { entry.filename.assume_init_ref() }[..];
104                array[i * 15..(i + 1) * 15].copy_from_slice(slice);
105            }
106            let name_length = stream_extension.custom_defined.name_length as usize;
107            for i in 0..name_length {
108                array[i] = u16::from_le(array[i]);
109            }
110            let slice = unsafe { slice::from_raw_parts(&array[0], name_length) };
111            let mut buf: [u8; MAX_FILENAME_SIZE] = unsafe { mem::transmute(array) };
112            let mut cursor = 0;
113            for &ch in slice {
114                let ch = unsafe { char::from_u32_unchecked(ch as u32) };
115                ch.encode_utf8(&mut buf[cursor..]);
116                cursor += ch.len_utf8();
117            }
118            let entryset = EntrySet {
119                name_bytes: buf,
120                name_length: cursor as u8,
121                file_directory,
122                stream_extension,
123                entry_index: EntryIndex::new(entryset_sector_index, entryset_index as u8),
124            };
125            if let Some(retval) = h(&entryset) {
126                return Ok(Some(retval));
127            }
128        }
129        Ok(None)
130    }
131
132    /// Walk through directory, including not inuse entries
133    pub async fn walk<H>(&mut self, mut h: H) -> Result<Option<EntrySet>, Error<E>>
134    where
135        H: FnMut(&EntrySet) -> bool,
136    {
137        self.walk_matches(
138            |_, _| true,
139            |entryset| if h(entryset) { Some(entryset.clone()) } else { None },
140        )
141        .await
142    }
143
144    /// Find a file or directory matching specified name
145    pub async fn find(&mut self, name: &str) -> Result<Option<EntrySet>, Error<E>> {
146        let name_length = name.chars().count();
147        let upcase_table = self.upcase_table.clone();
148        let hash = name_hash(&self.upcase_table.to_upper(name));
149        self.walk_matches(
150            |file_directory, stream_extension| -> bool {
151                let entry_type = file_directory.entry_type;
152                if !entry_type.in_use() {
153                    return false;
154                }
155                let length = stream_extension.custom_defined.name_length;
156                let name_hash = stream_extension.custom_defined.name_hash.to_ne();
157                length as usize == name_length && name_hash == hash
158            },
159            |entryset| match upcase_table.equals(name, &entryset.name()) {
160                true => Some(entryset.clone()),
161                false => None,
162            },
163        )
164        .await
165    }
166
167    /// Change current directory timestamp
168    pub async fn touch(&mut self, datetime: DateTime, opts: TouchOptions) -> Result<(), Error<E>> {
169        self.meta.touch(datetime, opts).await?;
170        self.meta.io.acquire().await.wrap().flush().await
171    }
172
173    /// Open a file or directory
174    pub async fn open(&mut self, entryset: &EntrySet) -> Result<FileOrDir<B, E, IO>, Error<E>> {
175        trace!("Open {} with entry index {}", entryset.name(), entryset.entry_index);
176        let mut context = self.meta.context.acquire().await;
177        if !context.opened_entries.add(entryset.id(&self.meta.fs)) {
178            return Err(OperationError::AlreadyOpen.into());
179        }
180        let cluster_id = entryset.stream_extension.first_cluster.to_ne();
181        let file_attributes = entryset.file_directory.file_attributes();
182        let sector_index = SectorIndex::new(cluster_id.into(), 0);
183        let meta = MetaFileDirectory {
184            io: self.meta.io.clone(),
185            context: self.meta.context.clone(),
186            metadata: Metadata::new(entryset.clone()),
187            options: FileOptions::default(),
188            sector_index,
189            ..self.meta
190        };
191        let (length, capacity) = (meta.metadata.length(), meta.metadata.capacity());
192        trace!("Cluster id {} length {} capacity {}", cluster_id, length, capacity);
193        if file_attributes.directory() > 0 {
194            let upcase_table = self.upcase_table.clone();
195            Ok(FileOrDirectory::Directory(Directory::new(meta, upcase_table)))
196        } else {
197            Ok(FileOrDirectory::File(File::new(meta, sector_index)))
198        }
199    }
200
201    async fn lookup_free(&mut self, size: u8) -> Result<(EntryIndex, bool), Error<E>> {
202        let mut best: Option<EntryIndex> = None;
203        let mut best_count = u8::MAX;
204
205        let mut candidate = EntryIndex::default();
206        let mut free_count = 0;
207
208        let mut sector_index = self.meta.sector_index;
209        let mut skip = 0;
210
211        loop {
212            let mut io = self.meta.io.acquire().await.wrap();
213            let sector = io.read(sector_index.id(&self.meta.fs)).await?;
214            let entries: &[[RawEntry; 16]] = unsafe { mem::transmute(&*sector) };
215            for (i, entry) in entries.iter().map(|e| e.iter()).flatten().enumerate() {
216                if skip > 0 {
217                    skip -= 1;
218                    continue;
219                }
220                let entry_type: RawEntryType = entry[0].into();
221                if entry_type.is_end_of_directory() {
222                    let tail = best.is_none();
223                    return Ok((best.unwrap_or(EntryIndex::new(sector_index, i as u8)), tail));
224                }
225                match (free_count == 0, entry_type.in_use()) {
226                    (true, false) => candidate = EntryIndex::new(sector_index, i as u8),
227                    (false, false) => free_count += 1,
228                    (false, true) => {
229                        if free_count >= size && free_count < best_count {
230                            best = Some(candidate);
231                            best_count = free_count;
232                        }
233                        free_count = 0;
234                    }
235                    _ => (),
236                }
237                if !entry_type.in_use() {
238                    continue;
239                }
240                skip = match entry_type.entry_type() {
241                    Ok(EntryType::FileDirectory) => {
242                        let file_directory: &FileDirectory = unsafe { mem::transmute(entry) };
243                        file_directory.secondary_count
244                    }
245                    Ok(_) => 0,
246                    Err(_) => return Err(DataError::Metadata.into()),
247                }
248            }
249            drop(io);
250            sector_index = self.meta.next(sector_index).await?;
251        }
252    }
253
254    /// Create a file (directory not supported yet)
255    pub async fn create(&mut self, name: &str, directory: bool) -> Result<(), Error<E>> {
256        if directory {
257            return Err(ImplementationError::CreateDirectoryNotSupported.into());
258        }
259        trace!("Create file {}", name);
260        let name_length = name.chars().count();
261        if name_length > 255 {
262            return Err(InputError::NameTooLong.into());
263        }
264        if self.find(name).await?.is_some() {
265            return Err(OperationError::AlreadyExists.into());
266        }
267
268        let num_entries = ((name.len() + 14) / 15) as u8 + 2;
269        let (free_entry_index, tail) = self.lookup_free(num_entries).await?;
270        let mut write_entry_index = free_entry_index;
271        let sector_index = free_entry_index.sector_index;
272        let sector_size = self.meta.fs.sector_size() as usize;
273        let capacity = sector_size / ENTRY_SIZE;
274        let out_of_capacity = free_entry_index.index + num_entries + tail as u8 >= capacity as u8;
275        if out_of_capacity {
276            let sector_index = match self.meta.next(sector_index).await {
277                Ok(sector_index) => sector_index,
278                Err(Error::Operation(OperationError::EOF)) => {
279                    SectorIndex::new(self.meta.allocate(sector_index.cluster_id).await?, 0)
280                }
281                Err(e) => return Err(e),
282            };
283            write_entry_index = EntryIndex::new(sector_index, 0);
284        }
285
286        debug!("Write entryset at entry-ref {}", write_entry_index);
287
288        let hash = name_hash(&self.upcase_table.to_upper(name));
289        let stream_extension = Secondary::new(StreamExtension::new(name.len() as u8, hash));
290        let mut file_directory = FileDirectory::new(num_entries - 1, directory);
291        let sum = checksum(&file_directory, &stream_extension, name);
292        file_directory.set_checksum = sum.into();
293
294        let sector_id = write_entry_index.sector_index.id(&self.meta.fs);
295        let offset = write_entry_index.index as usize * ENTRY_SIZE;
296        let bytes: &[u8; ENTRY_SIZE] = unsafe { mem::transmute(&file_directory) };
297        let mut io = self.meta.io.acquire().await.wrap();
298        io.write(sector_id, offset, bytes).await?;
299        let bytes: &[u8; ENTRY_SIZE] = unsafe { mem::transmute(&stream_extension) };
300        io.write(sector_id, offset + ENTRY_SIZE, bytes).await?;
301
302        let mut chars = name.chars();
303        let mut filename = Filename::default();
304        for index in 2..(num_entries as usize) {
305            let buf = unsafe { filename.filename.assume_init_mut() };
306            for i in 0..15 {
307                buf[i] = u16::to_le(chars.next().unwrap_or('\0') as u16)
308            }
309            let bytes: &[u8; ENTRY_SIZE] = unsafe { mem::transmute(&filename) };
310            io.write(sector_id, offset + index * ENTRY_SIZE, bytes).await?;
311        }
312        if tail {
313            let offset = offset + (num_entries as usize + 2) * ENTRY_SIZE;
314            io.write(sector_id, offset, &[0]).await?;
315        };
316        // Fill free entries afterwards to avoid corrupting metadata
317        if out_of_capacity {
318            let sector_id = sector_index.id(&self.meta.fs);
319            let byte: u8 = RawEntryType::new(EntryType::Filename, false).into();
320            for i in free_entry_index.index as usize..(sector_size / ENTRY_SIZE) {
321                io.write(sector_id, i * ENTRY_SIZE, &[byte]).await?;
322            }
323        }
324        io.flush().await
325    }
326
327    /// Delete a file or directory
328    pub async fn delete(&mut self, entryset: &EntrySet) -> Result<(), Error<E>> {
329        debug!("Delete file or directory {} entry-ref {}", entryset.name(), entryset.entry_index);
330        let file_or_directory = self.open(entryset).await?;
331        let meta = match file_or_directory {
332            FileOrDirectory::Directory(mut directory) => {
333                if directory.walk(|_| true).await?.is_some() {
334                    #[cfg(all(feature = "async", not(feature = "std")))]
335                    directory.close().await?;
336                    return Err(OperationError::DirectoryNotEmpty.into());
337                }
338                directory.meta.metadata.clone()
339            }
340            FileOrDirectory::File(file) => file.meta.metadata.clone(),
341        };
342
343        let fs_info = self.meta.fs;
344        let mut sector_id = meta.entry_index.sector_index.id(&fs_info);
345        let secondary_count = meta.file_directory.secondary_count as usize;
346        let last_index = meta.entry_index.index as usize + secondary_count;
347        let sector_size = fs_info.sector_size() as usize;
348        let next_sector_id = match last_index * ENTRY_SIZE > sector_size {
349            true => self.meta.next(meta.entry_index.sector_index).await?.id(&fs_info),
350            false => sector_id,
351        };
352
353        let mut offset = meta.entry_index.index as usize * ENTRY_SIZE;
354        let mut io = self.meta.io.acquire().await.wrap();
355        io.write(sector_id, offset, &[EntryType::FileDirectory.into(); 1]).await?;
356        offset = (offset + ENTRY_SIZE) % sector_size;
357        if offset == 0 {
358            sector_id = next_sector_id;
359        }
360        io.write(sector_id, offset, &[EntryType::StreamExtension.into(); 1]).await?;
361        for _ in 0..(secondary_count - 1) {
362            offset = (offset + ENTRY_SIZE) % sector_size;
363            if offset == 0 {
364                sector_id = next_sector_id;
365            }
366            io.write(sector_id, offset, &[EntryType::Filename.into(); 1]).await?;
367        }
368        drop(io);
369
370        let stream_extension = &meta.stream_extension;
371        let cluster_id: ClusterID = stream_extension.first_cluster.to_ne().into();
372        let fat_chain = meta.stream_extension.general_secondary_flags.fat_chain();
373        if cluster_id.valid() {
374            let mut context = self.meta.context.acquire().await;
375            context.allocation_bitmap.release(cluster_id, fat_chain).await?;
376        }
377        self.meta.io.acquire().await.wrap().flush().await
378    }
379
380    #[cfg(feature = "async")]
381    /// `no_std` async only which must be explicitly called
382    pub async fn close(mut self) -> Result<(), Error<E>> {
383        self.closed = true;
384        self.meta.close().await
385    }
386}
387
388impl<B: Deref<Target = [Block]>, E: Debug, IO> Drop for Directory<B, E, IO>
389where
390    IO: io::IO<Block<'static> = B, Error = E>,
391{
392    fn drop(&mut self) {
393        #[cfg(feature = "async")]
394        if !self.closed {
395            panic!("Close must be explicitly called");
396        }
397        #[cfg(not(feature = "async"))]
398        self.meta.close().unwrap();
399    }
400}