Skip to main content

vfat_rs/api/
directory.rs

1use alloc::format;
2use alloc::string::{String, ToString};
3use alloc::vec::Vec;
4use core::mem;
5
6use log::{debug, info};
7use snafu::ensure;
8
9use crate::api::raw_directory_entry::EntryId::Deleted;
10use crate::api::raw_directory_entry::{
11    Attributes, LongFileNameEntry, RegularDirectoryEntry, UnknownDirectoryEntry,
12    VfatDirectoryEntry, unknown_entry_convert_to_bytes_2,
13};
14use crate::api::{DirectoryEntry, File, Metadata, VfatMetadataTrait};
15use crate::cluster::cluster_reader::ClusterChainReader;
16use crate::{ClusterId, VfatFS};
17use crate::{PathBuf, error};
18
19use crate::SECTOR_SIZE;
20const ENTRIES_AMOUNT: usize = SECTOR_SIZE / size_of::<UnknownDirectoryEntry>();
21const BUF_SIZE: usize = size_of::<UnknownDirectoryEntry>() * ENTRIES_AMOUNT;
22
23/// Converts a raw byte buffer into an array of [`UnknownDirectoryEntry`] values.
24pub fn unknown_entry_convert_from_bytes_entries(
25    entries: [u8; BUF_SIZE],
26) -> [UnknownDirectoryEntry; ENTRIES_AMOUNT] {
27    // Initialize result array using const fn from_fn for Copy types
28    let mut result: [UnknownDirectoryEntry; ENTRIES_AMOUNT] =
29        [UnknownDirectoryEntry::from([0u8; 32]); ENTRIES_AMOUNT];
30
31    // Parse each 32-byte chunk into an UnknownDirectoryEntry
32    for (i, chunk) in entries
33        .chunks_exact(size_of::<UnknownDirectoryEntry>())
34        .enumerate()
35    {
36        let entry_bytes: [u8; 32] = chunk.try_into().expect("chunk size mismatch");
37        result[i] = UnknownDirectoryEntry::from(entry_bytes);
38    }
39    result
40}
41
42/// The kind of entry to create inside a directory.
43#[derive(Debug)]
44pub enum EntryType {
45    /// A regular file.
46    File,
47    /// A subdirectory.
48    Directory,
49    // Link
50}
51
52/// A directory is composed of "DirectoryEntry" elements.
53/// Every directory has at least two pseudo directories: "." (current directory) and ".." (parent directory)
54///
55#[derive(Debug)]
56pub struct Directory {
57    pub(crate) vfat_filesystem: VfatFS,
58    /// Metadata for this directory (name, path, cluster, timestamps, etc.).
59    pub metadata: Metadata,
60    // An optimization, if we already created an entry, we know the offset of the last position.
61    last_entry_spot: Option<usize>,
62}
63
64impl Directory {
65    pub(crate) fn new(vfat_filesystem: VfatFS, metadata: Metadata) -> Self {
66        Self {
67            vfat_filesystem,
68            metadata,
69            last_entry_spot: None,
70        }
71    }
72
73    /// Returns true if an entry called "name" is contained in this directory
74    ///
75    pub fn contains(&self, name: &str) -> error::Result<bool> {
76        let lock = self.vfat_filesystem.fs_lock.clone();
77        let _guard = lock.read();
78        self.contains_unlocked(name)
79    }
80
81    pub(crate) fn contains_unlocked(&self, name: &str) -> error::Result<bool> {
82        for entry in self.contents_unlocked()? {
83            if entry.name() == name {
84                return Ok(true);
85            }
86        }
87        Ok(false)
88    }
89    /// Create a new file in this directory
90    ///
91    pub fn create_file(&mut self, name: String) -> error::Result<File> {
92        let lock = self.vfat_filesystem.fs_lock.clone();
93        let _guard = lock.write();
94        Ok(self.create(name, EntryType::File)?.into_file_unchecked())
95    }
96
97    /// Create a new directory in this directory
98    ///
99    pub fn create_directory(&mut self, name: String) -> error::Result<Directory> {
100        let lock = self.vfat_filesystem.fs_lock.clone();
101        let _guard = lock.write();
102        Ok(self
103            .create(name, EntryType::Directory)?
104            .into_directory_unchecked())
105    }
106
107    /// Used to create a new entry in this directory
108    fn create(&mut self, name: String, entry_type: EntryType) -> error::Result<DirectoryEntry> {
109        info!(
110            "Creating {:?} entry with name '{:?}' in directory '{:?}'",
111            entry_type,
112            name,
113            self.metadata.name()
114        );
115        if self.contains_unlocked(&name)? {
116            return Err(error::VfatRsError::NameAlreadyInUse { target: name });
117        }
118
119        // 1. Create metadata:
120        let metadata = self.create_metadata_for_new_entry(name.as_str(), &entry_type)?;
121
122        // 2. Based on the name, create one or more LFN and the Regular entry.
123        let existing_short_names = self.collect_short_names()?;
124        let entries: Vec<UnknownDirectoryEntry> = VfatDirectoryEntry::new_vfat_entry(
125            name.as_str(),
126            metadata.cluster,
127            Self::attributes_from_entry(&entry_type),
128            &existing_short_names,
129        )?;
130        let entries_len = entries.len();
131        let first_empty_spot_offset = if let Some(spot) = self.last_entry_spot {
132            spot
133        } else {
134            self.find_first_empty_spot_offset(entries_len)?
135        };
136
137        info!(
138            "Going to use as metadata: {:?}. self metadatapath= '{}', selfmetadata name = '{}'. My attributes: {:?}, cluster: {:?}",
139            metadata,
140            self.metadata.full_path().display(),
141            self.metadata.name(),
142            self.metadata.attributes,
143            self.metadata.cluster
144        );
145        info!(
146            "Found spot: {:?}, Going to append entries: {:?}",
147            first_empty_spot_offset, entries
148        );
149
150        let mut ccw = self
151            .vfat_filesystem
152            .cluster_chain_writer(self.metadata.cluster);
153        ccw.seek(first_empty_spot_offset)?;
154
155        for unknown_entry in entries.into_iter() {
156            let entry: [u8; size_of::<UnknownDirectoryEntry>()] = unknown_entry.into();
157            ccw.write(&entry)?;
158        }
159
160        if let EntryType::Directory = entry_type {
161            let entries =
162                VfatDirectoryEntry::create_pseudo_dir_entries(metadata.cluster, ClusterId::new(0));
163            let mut cw = self.vfat_filesystem.cluster_chain_writer(metadata.cluster);
164            let buf = unknown_entry_convert_to_bytes_2(entries);
165            cw.write(&buf)?;
166        }
167        // Invalidate cached spot so next operation re-scans for deleted entries
168        self.last_entry_spot = None;
169
170        Ok(match entry_type {
171            EntryType::Directory => {
172                DirectoryEntry::new_directory(metadata, self.vfat_filesystem.clone())
173            }
174            EntryType::File => DirectoryEntry::new_file(metadata, self.vfat_filesystem.clone()),
175        })
176    }
177
178    /// Searches for a contiguous run of reusable slots (deleted 0xE5 or end-of-entries 0x00)
179    /// that can fit `slots_needed` entries. Returns the byte offset of the first slot in
180    /// the run. Falls back to end-of-entries / end-of-cluster if no deleted run is large enough.
181    fn find_first_empty_spot_offset(&self, slots_needed: usize) -> error::Result<usize> {
182        let mut cluster_chain_reader = self.cluster_chain_reader();
183        let mut buff = [0u8; BUF_SIZE];
184        let mut offset = 0;
185        // Track the start and length of the current contiguous reusable run
186        let mut run_start: Option<usize> = None;
187        let mut run_len: usize = 0;
188
189        while cluster_chain_reader.read(&mut buff)? > 0 {
190            let unknown_entries: [UnknownDirectoryEntry; ENTRIES_AMOUNT] =
191                unknown_entry_convert_from_bytes_entries(buff);
192            for entry in unknown_entries.iter() {
193                if entry.is_end_of_entries() {
194                    // End-of-entries: any accumulated run (or this position) works
195                    if let Some(start) = run_start
196                        && run_len >= slots_needed
197                    {
198                        return Ok(start);
199                    }
200                    // Fall back to end-of-entries position (original behavior)
201                    return Ok(offset);
202                } else if entry.is_deleted() {
203                    if run_start.is_none() {
204                        run_start = Some(offset);
205                        run_len = 0;
206                    }
207                    run_len += 1;
208                    if run_len >= slots_needed {
209                        return Ok(run_start.unwrap());
210                    }
211                } else {
212                    // Live entry — reset the run
213                    run_start = None;
214                    run_len = 0;
215                }
216                offset += size_of::<UnknownDirectoryEntry>();
217            }
218            buff = [0u8; BUF_SIZE];
219        }
220        // Navigated the full cluster chain — append at the end
221        Ok(offset)
222    }
223
224    fn create_metadata_for_new_entry(
225        &mut self,
226        entry_name: &str,
227        entry_type: &EntryType,
228    ) -> error::Result<Metadata> {
229        let path = PathBuf::from(format!(
230            "{}{}",
231            self.metadata.full_path().display(),
232            entry_name
233        ));
234        let attributes = Self::attributes_from_entry(entry_type);
235        let cluster_id = match entry_type {
236            // No need to allocate a new cluster
237            EntryType::File => ClusterId::new(0),
238            // Allocate for directory
239            EntryType::Directory => self.vfat_filesystem.allocate_cluster_new_entry()?,
240        };
241        debug!("Going to use as cluster id: {}", cluster_id);
242        let size = 0;
243        let metadata = Metadata::new(
244            self.vfat_filesystem
245                .time_manager
246                .get_current_vfat_timestamp(),
247            self.vfat_filesystem
248                .time_manager
249                .get_current_vfat_timestamp(),
250            entry_name,
251            size,
252            path,
253            cluster_id,
254            self.metadata.full_path().clone(),
255            attributes,
256        );
257        Ok(metadata)
258    }
259
260    /// Returns an entry from inside this directory.
261    fn get_entry(&mut self, target_filename: &str) -> error::Result<DirectoryEntry> {
262        self.contents_unlocked()?
263            .into_iter()
264            .find(|name| {
265                debug!(
266                    "Checking name: {} == {}",
267                    name.metadata.name(),
268                    target_filename
269                );
270                name.metadata.name() == target_filename
271            })
272            .ok_or_else(|| error::VfatRsError::FileNotFound {
273                target: target_filename.to_string(),
274            })
275    }
276
277    /// Delete the entry named `target_name` from this directory.
278    pub fn delete(&mut self, target_name: String) -> error::Result<()> {
279        let lock = self.vfat_filesystem.fs_lock.clone();
280        let _guard = lock.write();
281        self.delete_unlocked(target_name)
282    }
283
284    fn delete_unlocked(&mut self, target_name: String) -> error::Result<()> {
285        info!("Starting delete routine for entry: '{}'. ", target_name);
286
287        const PSEUDO_CURRENT_FOLDER: &str = ".";
288        const PSEUDO_PARENT_FOLDER: &str = "..";
289        const PSEUDO_FOLDERS: &[&str; 2] = &[PSEUDO_PARENT_FOLDER, PSEUDO_CURRENT_FOLDER];
290
291        ensure!(
292            !PSEUDO_FOLDERS.contains(&target_name.as_str()),
293            error::CannotDeletePseudoDirSnafu {
294                target: target_name,
295            }
296        );
297
298        let mut target_entry = self.get_entry(&target_name)?;
299
300        if target_entry.is_dir() {
301            let directory = target_entry.into_directory_unchecked();
302            let contents = directory.contents_unlocked()?;
303            if contents.len() > PSEUDO_FOLDERS.len() {
304                return Err(error::VfatRsError::NonEmptyDirectory {
305                    target: directory.metadata.name().to_string(),
306                    contents: contents
307                        .into_iter()
308                        .map(|entry| entry.name().to_string())
309                        .filter(|entry_name| !PSEUDO_FOLDERS.contains(&entry_name.as_str()))
310                        .collect::<Vec<_>>()
311                        .join(", "),
312                });
313            }
314            target_entry = directory.into();
315        }
316        info!("Found target entry: {:?}", target_entry);
317
318        self.delete_cluster_chain(&target_entry)?;
319        self.delete_entry(target_name)?;
320        Ok(())
321    }
322
323    /// Find a named entry in this directory, returning its index and associated LFN entries.
324    fn find_entry_index(
325        &self,
326        target_name: &str,
327    ) -> error::Result<(usize, RegularDirectoryEntry, Vec<LongFileNameEntry>)> {
328        let entries = self.contents_direntry()?;
329        let mut lfn_name_buff: Vec<(u8, String)> = Vec::new();
330        let mut lfn_entries_buff: Vec<LongFileNameEntry> = Vec::new();
331
332        for (index, dir_entry) in entries.into_iter().enumerate() {
333            match dir_entry {
334                VfatDirectoryEntry::LongFileName(lfn) => {
335                    lfn_name_buff.push((lfn.sequence_number.get_position(), lfn.collect_name()));
336                    lfn_entries_buff.push(lfn);
337                }
338                VfatDirectoryEntry::Deleted(_) => {
339                    lfn_name_buff.clear();
340                    lfn_entries_buff.clear();
341                }
342                VfatDirectoryEntry::Regular(regular) => {
343                    let name = if !lfn_name_buff.is_empty() {
344                        Self::string_from_lfn(mem::take(&mut lfn_name_buff))
345                    } else {
346                        regular.full_name()
347                    };
348                    if name == target_name {
349                        return Ok((index, regular, mem::take(&mut lfn_entries_buff)));
350                    }
351                    lfn_entries_buff.clear();
352                }
353                VfatDirectoryEntry::EndOfEntries(_) => break,
354            }
355        }
356        Err(error::VfatRsError::FileNotFound {
357            target: target_name.to_string(),
358        })
359    }
360
361    fn delete_entry(&mut self, target_name: String) -> error::Result<()> {
362        info!("Running delete entry");
363        let (index, regular, lfn_entries) = self.find_entry_index(&target_name)?;
364
365        // set all the lfn entries as deleted.
366        for (i, lfn) in lfn_entries.into_iter().rev().enumerate() {
367            let mut unknown: UnknownDirectoryEntry = lfn.into();
368            unknown.set_id(Deleted);
369            self.update_entry_by_index(unknown, index - i - 1)?;
370        }
371        let mut unknown: UnknownDirectoryEntry = regular.into();
372        unknown.set_id(Deleted);
373        self.update_entry_by_index(unknown, index)?;
374        Ok(())
375    }
376    fn delete_cluster_chain(&mut self, entry: &DirectoryEntry) -> error::Result<()> {
377        info!(
378            "Deleting entry's associated clusters starting at {:?}",
379            entry.metadata.cluster
380        );
381        self.vfat_filesystem
382            .delete_fat_cluster_chain(entry.metadata.cluster)
383    }
384
385    fn contents_direntry(&self) -> error::Result<Vec<VfatDirectoryEntry>> {
386        info!("Directory contents, cluster: {:?}", self.metadata.cluster);
387
388        let mut buf = [0; BUF_SIZE];
389        let filter_invalid =
390            |entry: &VfatDirectoryEntry| !matches!(*entry, VfatDirectoryEntry::EndOfEntries(_));
391        let mut cluster_chain_reader = self.cluster_chain_reader();
392
393        let mut entries = Vec::new();
394        while cluster_chain_reader.read(&mut buf)? > 0 {
395            let unknown_entries: [UnknownDirectoryEntry; ENTRIES_AMOUNT] =
396                unknown_entry_convert_from_bytes_entries(buf);
397            entries.extend(
398                unknown_entries
399                    .iter()
400                    .map(VfatDirectoryEntry::from)
401                    .filter(filter_invalid),
402            );
403        }
404        Ok(entries)
405    }
406
407    /// Collect the 8.3 short names of all regular entries in this directory.
408    fn collect_short_names(&self) -> error::Result<Vec<[u8; 8]>> {
409        Ok(self
410            .contents_direntry()?
411            .into_iter()
412            .filter_map(|e| {
413                if let VfatDirectoryEntry::Regular(r) = e {
414                    Some(r.file_name)
415                } else {
416                    None
417                }
418            })
419            .collect())
420    }
421
422    /// Returns the total number of raw directory entry slots in use (regular,
423    /// LFN, and deleted — everything except end-of-entries markers).
424    /// Useful for verifying that deleted slots are being reclaimed.
425    pub fn raw_entry_count(&self) -> error::Result<usize> {
426        Ok(self.contents_direntry()?.len())
427    }
428
429    /// Returns all entries (files and subdirectories) contained in this directory.
430    pub fn contents(&self) -> error::Result<Vec<DirectoryEntry>> {
431        let lock = self.vfat_filesystem.fs_lock.clone();
432        let _guard = lock.read();
433        self.contents_unlocked()
434    }
435
436    pub(crate) fn contents_unlocked(&self) -> error::Result<Vec<DirectoryEntry>> {
437        info!("Directory contents, cluster: {:?}", self.metadata.cluster);
438
439        let entries = self.contents_direntry()?;
440        let mut contents = Vec::new();
441
442        let mut lfn_buff: Vec<(u8, String)> = Vec::new();
443        for dir_entry in entries {
444            debug!("Found entry: {:?}", dir_entry);
445            match dir_entry {
446                VfatDirectoryEntry::LongFileName(lfn) => {
447                    lfn_buff.push((lfn.sequence_number.get_position(), lfn.collect_name()))
448                }
449                VfatDirectoryEntry::Deleted(_) => {
450                    lfn_buff.clear();
451                }
452                VfatDirectoryEntry::Regular(regular) => {
453                    let name = if !lfn_buff.is_empty() {
454                        Self::string_from_lfn(mem::take(&mut lfn_buff))
455                    } else {
456                        regular.full_name()
457                    };
458
459                    let path = PathBuf::from(format!(
460                        "{}{name}{}",
461                        self.metadata.full_path().display(),
462                        if regular.is_dir() { "/" } else { "" }
463                    ));
464
465                    let metadata = Metadata::new(
466                        regular.creation_time,
467                        regular.last_modification_time,
468                        name,
469                        regular.file_size,
470                        path,
471                        regular.cluster(),
472                        self.metadata.full_path().clone(),
473                        regular.attributes,
474                    );
475
476                    debug!("Metadata: {:?}", metadata);
477
478                    let new_fn = if regular.is_dir() {
479                        DirectoryEntry::new_directory
480                    } else {
481                        DirectoryEntry::new_file
482                    };
483
484                    contents.push(new_fn(metadata, self.vfat_filesystem.clone()));
485                }
486                // The for loop stops on EndOfEntries
487                VfatDirectoryEntry::EndOfEntries(_) => {
488                    panic!("This cannot happen! Found EndOfEntries")
489                }
490            }
491        }
492        Ok(contents)
493    }
494
495    pub(crate) fn update_entry(&mut self, metadata: Metadata) -> error::Result<()> {
496        let target_name = metadata.name().to_string();
497        info!("Running update entry on target name: {}", target_name);
498        let regular: RegularDirectoryEntry = metadata.into();
499        self.update_entry_inner(target_name, regular.into())
500    }
501
502    fn cluster_chain_reader(&self) -> ClusterChainReader {
503        self.vfat_filesystem
504            .cluster_chain_reader(self.metadata.cluster)
505    }
506
507    // create a string from a vec
508    fn string_from_lfn(mut lfn_vec: Vec<(u8, String)>) -> String {
509        // lfn are not assumed to be created in order, hence we need to
510        // sort using the sequence number
511        lfn_vec.sort();
512        // Build the string.
513        lfn_vec
514            .into_iter()
515            .map(|(_, s)| s)
516            .fold(String::new(), |mut acc, s| {
517                acc.push_str(&s);
518                acc
519            })
520    }
521
522    /// Rename or move `target_name` to `destination_path`.
523    pub fn rename(
524        &mut self,
525        target_name: String,
526        destination_path: crate::PathBuf,
527    ) -> error::Result<()> {
528        let lock = self.vfat_filesystem.fs_lock.clone();
529        let _guard = lock.write();
530        self.rename_unlocked(target_name, destination_path)
531    }
532
533    fn rename_unlocked(
534        &mut self,
535        target_name: String,
536        destination_path: crate::PathBuf,
537    ) -> error::Result<()> {
538        let dest_str = destination_path.display().to_string();
539        let dest_trimmed = dest_str.trim_end_matches('/');
540
541        // Extract new name (last path component) and parent directory path
542        let (dest_parent_str, new_name) = match dest_trimmed.rfind('/') {
543            Some(0) => ("/".to_string(), dest_trimmed[1..].to_string()),
544            Some(pos) => (
545                dest_trimmed[..pos].to_string(),
546                dest_trimmed[pos + 1..].to_string(),
547            ),
548            None => {
549                return Err(error::VfatRsError::FileNotFound { target: dest_str });
550            }
551        };
552        if new_name.is_empty() {
553            return Err(error::VfatRsError::FileNotFound { target: dest_str });
554        }
555        let dest_parent: crate::PathBuf = dest_parent_str.as_str().into();
556
557        let target_entry = self.get_entry(&target_name)?;
558        let mut metadata = target_entry.metadata;
559
560        // Determine if this is a same-directory rename or cross-directory move
561        let source_parent_path = self.metadata.full_path();
562        if dest_parent == *source_parent_path {
563            // Same directory: use existing in-place rename
564            return self.inner_rename(target_name, new_name, &mut metadata);
565        }
566
567        // Cross-directory move
568        // If moving a directory, guard against circular moves
569        if metadata.attributes.is_directory() {
570            let parent_str = source_parent_path.display().to_string();
571            let sep = if parent_str.ends_with('/') { "" } else { "/" };
572            let source_entry_path: crate::PathBuf =
573                alloc::format!("{}{}{}", parent_str, sep, target_name).into();
574            if destination_path.starts_with(&source_entry_path) {
575                return Err(error::VfatRsError::CircularMove {
576                    source_path: source_entry_path.display().to_string(),
577                    destination_path: dest_str,
578                });
579            }
580        }
581
582        // Resolve destination directory
583        let dest_dir_entry = self
584            .vfat_filesystem
585            .get_from_absolute_path_unlocked(dest_parent.clone())?;
586        let mut dest_dir = dest_dir_entry.into_directory_or_not_found()?;
587
588        // POSIX semantics: if destination name already exists, delete it
589        if dest_dir.contains_unlocked(&new_name)? {
590            dest_dir.delete_unlocked(new_name.clone())?;
591        }
592
593        // Write new entries in the destination directory
594        let attributes = metadata.attributes;
595        let existing_short_names = dest_dir.collect_short_names()?;
596        let entries: Vec<UnknownDirectoryEntry> = VfatDirectoryEntry::new_vfat_entry(
597            new_name.as_str(),
598            metadata.cluster,
599            attributes,
600            &existing_short_names,
601        )?;
602        let entries_len = entries.len();
603        let first_empty_spot_offset = if let Some(spot) = dest_dir.last_entry_spot {
604            spot
605        } else {
606            dest_dir.find_first_empty_spot_offset(entries_len)?
607        };
608        let mut ccw = dest_dir
609            .vfat_filesystem
610            .cluster_chain_writer(dest_dir.metadata.cluster);
611        ccw.seek(first_empty_spot_offset)?;
612        for unknown_entry in entries.into_iter() {
613            let entry: [u8; size_of::<UnknownDirectoryEntry>()] = unknown_entry.into();
614            ccw.write(&entry)?;
615        }
616        dest_dir.last_entry_spot = None;
617
618        // Delete old entries from source directory
619        self.delete_entry(target_name)?;
620
621        // For directory moves, update the ".." pseudo-entry to point to new parent
622        if metadata.attributes.is_directory() && !metadata.has_no_cluster_allocated() {
623            Self::update_dotdot_cluster(
624                &self.vfat_filesystem,
625                metadata.cluster,
626                dest_dir.metadata.cluster,
627            )?;
628        }
629
630        metadata.name = new_name;
631        Ok(())
632    }
633
634    /// Update the ".." pseudo-entry inside a directory to point to a new parent cluster.
635    fn update_dotdot_cluster(
636        vfat: &VfatFS,
637        dir_cluster: ClusterId,
638        new_parent_cluster: ClusterId,
639    ) -> error::Result<()> {
640        // ".." is always the second entry (index 1)
641        let dotdot_index = 1;
642        let index_offset = size_of::<UnknownDirectoryEntry>() * dotdot_index;
643
644        // Read the existing ".." entry
645        let mut buf = [0u8; size_of::<UnknownDirectoryEntry>()];
646        let mut reader = vfat.cluster_chain_reader(dir_cluster);
647        // Skip the "." entry
648        let mut skip_buf = [0u8; size_of::<UnknownDirectoryEntry>()];
649        reader.read(&mut skip_buf)?;
650        reader.read(&mut buf)?;
651
652        let unknown = UnknownDirectoryEntry::from(buf);
653        let mut regular: RegularDirectoryEntry = unknown.into();
654
655        // Update cluster to new parent (root cluster 2 maps to 0 per FAT convention)
656        let new_parent = if new_parent_cluster == ClusterId::new(2) {
657            ClusterId::new(0)
658        } else {
659            new_parent_cluster
660        };
661        let (high, low) = new_parent.into_high_low();
662        regular.high_16bits = high;
663        regular.low_16bits = low;
664
665        // Write it back
666        let updated: UnknownDirectoryEntry = regular.into();
667        let write_buf: [u8; size_of::<UnknownDirectoryEntry>()] = updated.into();
668        let mut writer = vfat.cluster_chain_writer(dir_cluster);
669        writer.seek(index_offset)?;
670        writer.write(&write_buf)?;
671        Ok(())
672    }
673
674    fn inner_rename(
675        &mut self,
676        target_name: String,
677        new_name: String,
678        metadata: &mut Metadata,
679    ) -> error::Result<()> {
680        // create lfn from existing file
681        // delete old file
682        let attributes = metadata.attributes;
683        let existing_short_names = self.collect_short_names()?;
684        let entries: Vec<UnknownDirectoryEntry> = VfatDirectoryEntry::new_vfat_entry(
685            new_name.as_str(),
686            metadata.cluster,
687            attributes,
688            &existing_short_names,
689        )?;
690        let entries_len = entries.len();
691        let first_empty_spot_offset = if let Some(spot) = self.last_entry_spot {
692            spot
693        } else {
694            self.find_first_empty_spot_offset(entries_len)?
695        };
696        let mut ccw = self
697            .vfat_filesystem
698            .cluster_chain_writer(self.metadata.cluster);
699        ccw.seek(first_empty_spot_offset)?;
700
701        for unknown_entry in entries.into_iter() {
702            let entry: [u8; size_of::<UnknownDirectoryEntry>()] = unknown_entry.into();
703            ccw.write(&entry)?;
704        }
705        metadata.name = new_name;
706
707        // Invalidate cached spot so next operation re-scans for deleted entries
708        self.last_entry_spot = None;
709        self.delete_entry(target_name)?;
710        Ok(())
711    }
712
713    // TODO: Currently this doesn't support renaming file, just updating/replacing metadatas...
714    fn update_entry_inner(
715        &mut self,
716        target_name: String,
717        new_entry: UnknownDirectoryEntry,
718    ) -> error::Result<()> {
719        debug!("Running update entry routine...");
720        let (index, _, _) = self.find_entry_index(&target_name)?;
721        self.update_entry_by_index(new_entry, index)
722    }
723
724    // Replace entry with index `index` with input `entry`.
725    // TODO: when reading the file, keep the index around to avoid scanning to locate the file again.
726    pub(crate) fn update_entry_by_index(
727        &self,
728        entry: UnknownDirectoryEntry,
729        index: usize,
730    ) -> error::Result<()> {
731        let index_offset = size_of::<UnknownDirectoryEntry>() * index;
732        let buf: [u8; size_of::<UnknownDirectoryEntry>()] = entry.into();
733
734        let mut ccw = self
735            .vfat_filesystem
736            .cluster_chain_writer(self.metadata.cluster);
737        ccw.seek(index_offset)?;
738        ccw.write(&buf)?;
739        Ok(())
740    }
741
742    fn attributes_from_entry(entry: &EntryType) -> Attributes {
743        match entry {
744            EntryType::Directory => Attributes::new_directory(),
745            EntryType::File => Attributes(0),
746        }
747    }
748}
749
750impl VfatMetadataTrait for Directory {
751    fn metadata(&self) -> &Metadata {
752        &self.metadata
753    }
754}
755
756#[cfg(test)]
757mod test {
758    extern crate std;
759
760    use crate::api::raw_directory_entry::EntryId;
761
762    #[test]
763    fn valid_entry_id() {
764        let id: u8 = 0x10;
765        assert!(matches!(EntryId::from(id), EntryId::Valid(_)));
766        //id
767    }
768}