exfat/cluster_heap/directory/
mod.rs

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