Skip to main content

fatfs/
dir.rs

1#[cfg(all(not(feature = "std"), feature = "alloc", feature = "lfn"))]
2use alloc::vec::Vec;
3use core::num;
4use core::str;
5#[cfg(feature = "lfn")]
6use core::{iter, slice};
7
8use crate::dir_entry::{
9    DirEntry, DirEntryData, DirFileEntryData, DirLfnEntryData, FileAttributes, ShortName, DIR_ENTRY_SIZE,
10};
11#[cfg(feature = "lfn")]
12use crate::dir_entry::{LFN_ENTRY_LAST_FLAG, LFN_PART_LEN};
13use crate::dir_entry::{SFN_PADDING, SFN_SIZE};
14use crate::error::{Error, IoError};
15use crate::file::File;
16use crate::fs::{DiskSlice, FileSystem, FsIoAdapter, OemCpConverter, ReadWriteSeek};
17use crate::io::{self, IoBase, Read, Seek, SeekFrom, Write};
18use crate::time::TimeProvider;
19
20const LFN_PADDING: u16 = 0xFFFF;
21
22pub(crate) enum DirRawStream<'a, IO: ReadWriteSeek, TP, OCC> {
23    File(File<'a, IO, TP, OCC>),
24    Root(DiskSlice<FsIoAdapter<'a, IO, TP, OCC>, FsIoAdapter<'a, IO, TP, OCC>>),
25}
26
27impl<IO: ReadWriteSeek, TP, OCC> DirRawStream<'_, IO, TP, OCC> {
28    fn abs_pos(&self) -> Option<u64> {
29        match self {
30            DirRawStream::File(file) => file.abs_pos(),
31            DirRawStream::Root(slice) => Some(slice.abs_pos()),
32        }
33    }
34
35    fn first_cluster(&self) -> Option<u32> {
36        match self {
37            DirRawStream::File(file) => file.first_cluster(),
38            DirRawStream::Root(_) => None,
39        }
40    }
41
42    pub(crate) fn is_root_dir(&self) -> bool {
43        match self {
44            DirRawStream::File(file) => file.is_root_dir(),
45            DirRawStream::Root(_) => true,
46        }
47    }
48}
49
50// Note: derive cannot be used because of invalid bounds. See: https://github.com/rust-lang/rust/issues/26925
51impl<IO: ReadWriteSeek, TP, OCC> Clone for DirRawStream<'_, IO, TP, OCC> {
52    fn clone(&self) -> Self {
53        match self {
54            DirRawStream::File(file) => DirRawStream::File(file.clone()),
55            DirRawStream::Root(raw) => DirRawStream::Root(raw.clone()),
56        }
57    }
58}
59
60impl<IO: ReadWriteSeek, TP, OCC> IoBase for DirRawStream<'_, IO, TP, OCC> {
61    type Error = Error<IO::Error>;
62}
63
64impl<IO: ReadWriteSeek, TP: TimeProvider, OCC> Read for DirRawStream<'_, IO, TP, OCC> {
65    fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
66        match self {
67            DirRawStream::File(file) => file.read(buf),
68            DirRawStream::Root(raw) => raw.read(buf),
69        }
70    }
71}
72
73impl<IO: ReadWriteSeek, TP: TimeProvider, OCC> Write for DirRawStream<'_, IO, TP, OCC> {
74    fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
75        match self {
76            DirRawStream::File(file) => file.write(buf),
77            DirRawStream::Root(raw) => raw.write(buf),
78        }
79    }
80    fn flush(&mut self) -> Result<(), Self::Error> {
81        match self {
82            DirRawStream::File(file) => file.flush(),
83            DirRawStream::Root(raw) => raw.flush(),
84        }
85    }
86}
87
88impl<IO: ReadWriteSeek, TP, OCC> Seek for DirRawStream<'_, IO, TP, OCC> {
89    fn seek(&mut self, pos: SeekFrom) -> Result<u64, Self::Error> {
90        match self {
91            DirRawStream::File(file) => file.seek(pos),
92            DirRawStream::Root(raw) => raw.seek(pos),
93        }
94    }
95}
96
97fn split_path(path: &str) -> (&str, Option<&str>) {
98    let trimmed_path = path.trim_matches('/');
99    trimmed_path.find('/').map_or((trimmed_path, None), |n| {
100        (&trimmed_path[..n], Some(&trimmed_path[n + 1..]))
101    })
102}
103
104enum DirEntryOrShortName<'a, IO: ReadWriteSeek, TP, OCC> {
105    DirEntry(DirEntry<'a, IO, TP, OCC>),
106    ShortName([u8; SFN_SIZE]),
107}
108
109/// A FAT filesystem directory.
110///
111/// This struct is created by the `open_dir` or `create_dir` methods on `Dir`.
112/// The root directory is returned by the `root_dir` method on `FileSystem`.
113pub struct Dir<'a, IO: ReadWriteSeek, TP, OCC> {
114    stream: DirRawStream<'a, IO, TP, OCC>,
115    fs: &'a FileSystem<IO, TP, OCC>,
116}
117
118impl<'a, IO: ReadWriteSeek, TP, OCC> Dir<'a, IO, TP, OCC> {
119    pub(crate) fn new(stream: DirRawStream<'a, IO, TP, OCC>, fs: &'a FileSystem<IO, TP, OCC>) -> Self {
120        Dir { stream, fs }
121    }
122
123    /// Creates directory entries iterator.
124    #[must_use]
125    #[allow(clippy::iter_not_returning_iterator)]
126    pub fn iter(&self) -> DirIter<'a, IO, TP, OCC> {
127        DirIter::new(self.stream.clone(), self.fs, true)
128    }
129}
130
131impl<'a, IO: ReadWriteSeek, TP: TimeProvider, OCC: OemCpConverter> Dir<'a, IO, TP, OCC> {
132    fn find_entry(
133        &self,
134        name: &str,
135        is_dir: Option<bool>,
136        mut short_name_gen: Option<&mut ShortNameGenerator>,
137    ) -> Result<DirEntry<'a, IO, TP, OCC>, Error<IO::Error>> {
138        for r in self.iter() {
139            let e = r?;
140            // compare name ignoring case
141            if e.eq_name(name) {
142                // check if file or directory is expected
143                if is_dir.is_some() && Some(e.is_dir()) != is_dir {
144                    if e.is_dir() {
145                        error!("Is a directory");
146                    } else {
147                        error!("Not a directory");
148                    }
149                    return Err(Error::InvalidInput);
150                }
151                return Ok(e);
152            }
153            // update short name generator state
154            if let Some(ref mut gen) = short_name_gen {
155                gen.add_existing(e.raw_short_name());
156            }
157        }
158        Err(Error::NotFound) //("No such file or directory"))
159    }
160
161    #[allow(clippy::type_complexity)]
162    pub(crate) fn find_volume_entry(&self) -> Result<Option<DirEntry<'a, IO, TP, OCC>>, Error<IO::Error>> {
163        for r in DirIter::new(self.stream.clone(), self.fs, false) {
164            let e = r?;
165            if e.data.is_volume() {
166                return Ok(Some(e));
167            }
168        }
169        Ok(None)
170    }
171
172    fn check_for_existence(
173        &self,
174        name: &str,
175        is_dir: Option<bool>,
176    ) -> Result<DirEntryOrShortName<'a, IO, TP, OCC>, Error<IO::Error>> {
177        let mut short_name_gen = ShortNameGenerator::new(name);
178        loop {
179            // find matching entry
180            let r = self.find_entry(name, is_dir, Some(&mut short_name_gen));
181            match r {
182                // file not found - continue with short name generation
183                Err(Error::NotFound) => {}
184                // unexpected error - return it
185                Err(err) => return Err(err),
186                // directory already exists - return it
187                Ok(e) => return Ok(DirEntryOrShortName::DirEntry(e)),
188            };
189            // try to generate short name
190            if let Ok(name) = short_name_gen.generate() {
191                return Ok(DirEntryOrShortName::ShortName(name));
192            }
193            // there were too many collisions in short name generation
194            // try different checksum in the next iteration
195            short_name_gen.next_iteration();
196        }
197    }
198
199    /// Returns the underlying file object if the directory stream is not a root
200    /// directory in FAT12 & FAT16.
201    pub fn as_file(&self) -> Option<&File<'a, IO, TP, OCC>> {
202        match &self.stream {
203            DirRawStream::File(file) => Some(file),
204            DirRawStream::Root(_) => None,
205        }
206    }
207
208    /// Opens existing subdirectory.
209    ///
210    /// `path` is a '/' separated directory path relative to self directory.
211    ///
212    /// # Errors
213    ///
214    /// Errors that can be returned:
215    ///
216    /// * `Error::NotFound` will be returned if `path` does not point to any existing directory entry.
217    /// * `Error::InvalidInput` will be returned if `path` points to a file that is not a directory.
218    /// * `Error::Io` will be returned if the underlying storage object returned an I/O error.
219    pub fn open_dir(&self, path: &str) -> Result<Self, Error<IO::Error>> {
220        trace!("Dir::open_dir {}", path);
221        let (name, rest_opt) = split_path(path);
222        let e = self.find_entry(name, Some(true), None)?;
223        match rest_opt {
224            Some(rest) => e.to_dir().open_dir(rest),
225            None => Ok(e.to_dir()),
226        }
227    }
228
229    /// Opens existing file.
230    ///
231    /// `path` is a '/' separated file path relative to self directory.
232    ///
233    /// # Errors
234    ///
235    /// Errors that can be returned:
236    ///
237    /// * `Error::NotFound` will be returned if `path` points to a non-existing directory entry.
238    /// * `Error::InvalidInput` will be returned if `path` points to a file that is a directory.
239    /// * `Error::Io` will be returned if the underlying storage object returned an I/O error.
240    pub fn open_file(&self, path: &str) -> Result<File<'a, IO, TP, OCC>, Error<IO::Error>> {
241        trace!("Dir::open_file {}", path);
242        // traverse path
243        let (name, rest_opt) = split_path(path);
244        if let Some(rest) = rest_opt {
245            let e = self.find_entry(name, Some(true), None)?;
246            return e.to_dir().open_file(rest);
247        }
248        // convert entry to a file
249        let e = self.find_entry(name, Some(false), None)?;
250        Ok(e.to_file())
251    }
252
253    /// Creates new or opens existing file=.
254    ///
255    /// `path` is a '/' separated file path relative to `self` directory.
256    /// File is never truncated when opening. It can be achieved by calling `File::truncate` method after opening.
257    ///
258    /// # Errors
259    ///
260    /// Errors that can be returned:
261    ///
262    /// * `Error::InvalidInput` will be returned if `path` points to an existing file that is a directory.
263    /// * `Error::InvalidFileNameLength` will be returned if the file name is empty or if it is too long.
264    /// * `Error::UnsupportedFileNameCharacter` will be returned if the file name contains an invalid character.
265    /// * `Error::NotEnoughSpace` will be returned if there is not enough free space to create a new file.
266    /// * `Error::Io` will be returned if the underlying storage object returned an I/O error.
267    pub fn create_file(&self, path: &str) -> Result<File<'a, IO, TP, OCC>, Error<IO::Error>> {
268        trace!("Dir::create_file {}", path);
269        // traverse path
270        let (name, rest_opt) = split_path(path);
271        if let Some(rest) = rest_opt {
272            return self.find_entry(name, Some(true), None)?.to_dir().create_file(rest);
273        }
274        // this is final filename in the path
275        let r = self.check_for_existence(name, Some(false))?;
276        match r {
277            // file does not exist - create it
278            DirEntryOrShortName::ShortName(short_name) => {
279                let sfn_entry = self.create_sfn_entry(short_name, FileAttributes::from_bits_truncate(0), None);
280                Ok(self.write_entry(name, sfn_entry)?.to_file())
281            }
282            // file already exists - return it
283            DirEntryOrShortName::DirEntry(e) => Ok(e.to_file()),
284        }
285    }
286
287    /// Creates new directory or opens existing.
288    ///
289    /// `path` is a '/' separated path relative to self directory.
290    ///
291    /// # Errors
292    ///
293    /// Errors that can be returned:
294    ///
295    /// * `Error::InvalidInput` will be returned if `path` points to an existing file that is not a directory.
296    /// * `Error::InvalidFileNameLength` will be returned if the file name is empty or if it is too long.
297    /// * `Error::UnsupportedFileNameCharacter` will be returned if the file name contains an invalid character.
298    /// * `Error::NotEnoughSpace` will be returned if there is not enough free space to create a new directory.
299    /// * `Error::Io` will be returned if the underlying storage object returned an I/O error.
300    pub fn create_dir(&self, path: &str) -> Result<Self, Error<IO::Error>> {
301        trace!("Dir::create_dir {}", path);
302        // traverse path
303        let (name, rest_opt) = split_path(path);
304        if let Some(rest) = rest_opt {
305            return self.find_entry(name, Some(true), None)?.to_dir().create_dir(rest);
306        }
307        // this is final filename in the path
308        let r = self.check_for_existence(name, Some(true))?;
309        match r {
310            // directory does not exist - create it
311            DirEntryOrShortName::ShortName(short_name) => {
312                // alloc cluster for directory data
313                let cluster = self.fs.alloc_cluster(None, true)?;
314                // create entry in parent directory
315                let sfn_entry = self.create_sfn_entry(short_name, FileAttributes::DIRECTORY, Some(cluster));
316                let entry = self.write_entry(name, sfn_entry)?;
317                let dir = entry.to_dir();
318                // create special entries "." and ".."
319                let dot_sfn = ShortNameGenerator::generate_dot();
320                let sfn_entry = self.create_sfn_entry(dot_sfn, FileAttributes::DIRECTORY, entry.first_cluster());
321                dir.write_entry(".", sfn_entry)?;
322                let dotdot_sfn = ShortNameGenerator::generate_dotdot();
323                // cluster of the root dir shall be set to 0 in directory entries.
324                let dotdot_cluster = if self.stream.is_root_dir() {
325                    None
326                } else {
327                    self.stream.first_cluster()
328                };
329                let sfn_entry = self.create_sfn_entry(dotdot_sfn, FileAttributes::DIRECTORY, dotdot_cluster);
330                dir.write_entry("..", sfn_entry)?;
331                Ok(dir)
332            }
333            // directory already exists - return it
334            DirEntryOrShortName::DirEntry(e) => Ok(e.to_dir()),
335        }
336    }
337
338    fn is_empty(&self) -> Result<bool, Error<IO::Error>> {
339        trace!("Dir::is_empty");
340        // check if directory contains no files
341        for r in self.iter() {
342            let e = r?;
343            let name = e.short_file_name_as_bytes();
344            // ignore special entries "." and ".."
345            if name != b"." && name != b".." {
346                return Ok(false);
347            }
348        }
349        Ok(true)
350    }
351
352    /// Removes existing file or directory.
353    ///
354    /// `path` is a '/' separated file path relative to self directory.
355    /// Make sure there is no reference to this file (no File instance) or filesystem corruption
356    /// can happen.
357    ///
358    /// # Errors
359    ///
360    /// Errors that can be returned:
361    ///
362    /// * `Error::NotFound` will be returned if `path` points to a non-existing directory entry.
363    /// * `Error::InvalidInput` will be returned if `path` points to a file that is not a directory.
364    /// * `Error::DirectoryIsNotEmpty` will be returned if the specified directory is not empty.
365    /// * `Error::Io` will be returned if the underlying storage object returned an I/O error.
366    pub fn remove(&self, path: &str) -> Result<(), Error<IO::Error>> {
367        trace!("Dir::remove {}", path);
368        // traverse path
369        let (name, rest_opt) = split_path(path);
370        if let Some(rest) = rest_opt {
371            let e = self.find_entry(name, Some(true), None)?;
372            return e.to_dir().remove(rest);
373        }
374        // in case of directory check if it is empty
375        let e = self.find_entry(name, None, None)?;
376        if e.is_dir() && !e.to_dir().is_empty()? {
377            return Err(Error::DirectoryIsNotEmpty);
378        }
379        // free data
380        if let Some(n) = e.first_cluster() {
381            self.fs.free_cluster_chain(n)?;
382        }
383        // free long and short name entries
384        let mut stream = self.stream.clone();
385        stream.seek(SeekFrom::Start(e.offset_range.0))?;
386        let num = ((e.offset_range.1 - e.offset_range.0) / u64::from(DIR_ENTRY_SIZE)) as usize;
387        for _ in 0..num {
388            let mut data = DirEntryData::deserialize(&mut stream)?;
389            trace!("removing dir entry {:?}", data);
390            data.set_deleted();
391            stream.seek(SeekFrom::Current(-i64::from(DIR_ENTRY_SIZE)))?;
392            data.serialize(&mut stream)?;
393        }
394        Ok(())
395    }
396
397    /// Renames or moves existing file or directory.
398    ///
399    /// `src_path` is a '/' separated source file path relative to self directory.
400    /// `dst_path` is a '/' separated destination file path relative to `dst_dir`.
401    /// `dst_dir` can be set to self directory if rename operation without moving is needed.
402    /// Make sure there is no reference to this file (no File instance) or filesystem corruption
403    /// can happen.
404    ///
405    /// # Errors
406    ///
407    /// Errors that can be returned:
408    ///
409    /// * `Error::NotFound` will be returned if `src_path` points to a non-existing directory entry or if `dst_path`
410    ///   stripped from the last component does not point to an existing directory.
411    /// * `Error::AlreadyExists` will be returned if `dst_path` points to an existing directory entry.
412    /// * `Error::Io` will be returned if the underlying storage object returned an I/O error.
413    pub fn rename(&self, src_path: &str, dst_dir: &Dir<IO, TP, OCC>, dst_path: &str) -> Result<(), Error<IO::Error>> {
414        trace!("Dir::rename {} {}", src_path, dst_path);
415        // traverse source path
416        let (src_name, src_rest_opt) = split_path(src_path);
417        if let Some(rest) = src_rest_opt {
418            let e = self.find_entry(src_name, Some(true), None)?;
419            return e.to_dir().rename(rest, dst_dir, dst_path);
420        }
421        // traverse destination path
422        let (dst_name, dst_rest_opt) = split_path(dst_path);
423        if let Some(rest) = dst_rest_opt {
424            let e = dst_dir.find_entry(dst_name, Some(true), None)?;
425            return self.rename(src_path, &e.to_dir(), rest);
426        }
427        // move/rename file
428        self.rename_internal(src_path, dst_dir, dst_path)
429    }
430
431    fn rename_internal(
432        &self,
433        src_name: &str,
434        dst_dir: &Dir<IO, TP, OCC>,
435        dst_name: &str,
436    ) -> Result<(), Error<IO::Error>> {
437        trace!("Dir::rename_internal {} {}", src_name, dst_name);
438        // find existing file
439        let e = self.find_entry(src_name, None, None)?;
440        // check if destionation filename is unused
441        let r = dst_dir.check_for_existence(dst_name, None)?;
442        let short_name = match r {
443            // destination file already exist
444            DirEntryOrShortName::DirEntry(ref dst_e) => {
445                // check if source and destination entry is the same
446                if e.is_same_entry(dst_e) {
447                    // nothing to do
448                    return Ok(());
449                }
450                // destination file exists and it is not the same as source file - fail
451                return Err(Error::AlreadyExists);
452            }
453            // destionation file does not exist, short name has been generated
454            DirEntryOrShortName::ShortName(short_name) => short_name,
455        };
456        // free long and short name entries
457        let mut stream = self.stream.clone();
458        stream.seek(SeekFrom::Start(e.offset_range.0))?;
459        let num = ((e.offset_range.1 - e.offset_range.0) / u64::from(DIR_ENTRY_SIZE)) as usize;
460        for _ in 0..num {
461            let mut data = DirEntryData::deserialize(&mut stream)?;
462            trace!("removing LFN entry {:?}", data);
463            data.set_deleted();
464            stream.seek(SeekFrom::Current(-i64::from(DIR_ENTRY_SIZE)))?;
465            data.serialize(&mut stream)?;
466        }
467        // save new directory entry
468        let sfn_entry = e.data.renamed(short_name);
469        dst_dir.write_entry(dst_name, sfn_entry)?;
470        Ok(())
471    }
472
473    fn find_free_entries(&self, num_entries: u32) -> Result<DirRawStream<'a, IO, TP, OCC>, Error<IO::Error>> {
474        let mut stream = self.stream.clone();
475        let mut first_free: u32 = 0;
476        let mut num_free: u32 = 0;
477        let mut i: u32 = 0;
478        loop {
479            let raw_entry = DirEntryData::deserialize(&mut stream)?;
480            if raw_entry.is_end() {
481                // first unused entry - all remaining space can be used
482                if num_free == 0 {
483                    first_free = i;
484                }
485                let pos = u64::from(first_free * DIR_ENTRY_SIZE);
486                stream.seek(io::SeekFrom::Start(pos))?;
487                return Ok(stream);
488            } else if raw_entry.is_deleted() {
489                // free entry - calculate number of free entries in a row
490                if num_free == 0 {
491                    first_free = i;
492                }
493                num_free += 1;
494                if num_free == num_entries {
495                    // enough space for new file
496                    let pos = u64::from(first_free * DIR_ENTRY_SIZE);
497                    stream.seek(io::SeekFrom::Start(pos))?;
498                    return Ok(stream);
499                }
500            } else {
501                // used entry - start counting from 0
502                num_free = 0;
503            }
504            i += 1;
505        }
506    }
507
508    fn create_sfn_entry(
509        &self,
510        short_name: [u8; SFN_SIZE],
511        attrs: FileAttributes,
512        first_cluster: Option<u32>,
513    ) -> DirFileEntryData {
514        let mut raw_entry = DirFileEntryData::new(short_name, attrs);
515        raw_entry.set_first_cluster(first_cluster, self.fs.fat_type());
516        let now = self.fs.options.time_provider.get_current_date_time();
517        raw_entry.set_created(now);
518        raw_entry.set_accessed(now.date);
519        raw_entry.set_modified(now);
520        raw_entry
521    }
522
523    #[cfg(feature = "lfn")]
524    fn encode_lfn_utf16(name: &str) -> LfnBuffer {
525        LfnBuffer::from_ucs2_units(name.encode_utf16())
526    }
527    #[cfg(not(feature = "lfn"))]
528    fn encode_lfn_utf16(_name: &str) -> LfnBuffer {
529        LfnBuffer {}
530    }
531
532    #[allow(clippy::type_complexity)]
533    fn alloc_and_write_lfn_entries(
534        &self,
535        lfn_utf16: &LfnBuffer,
536        short_name: &[u8; SFN_SIZE],
537    ) -> Result<(DirRawStream<'a, IO, TP, OCC>, u64), Error<IO::Error>> {
538        // get short name checksum
539        let lfn_chsum = lfn_checksum(short_name);
540        // create LFN entries generator
541        let lfn_iter = LfnEntriesGenerator::new(lfn_utf16.as_ucs2_units(), lfn_chsum);
542        // find space for new entries (multiple LFN entries and 1 SFN entry)
543        let num_entries = lfn_iter.len() as u32 + 1;
544        let mut stream = self.find_free_entries(num_entries)?;
545        let start_pos = stream.seek(io::SeekFrom::Current(0))?;
546        // write LFN entries before SFN entry
547        for lfn_entry in lfn_iter {
548            lfn_entry.serialize(&mut stream)?;
549        }
550        Ok((stream, start_pos))
551    }
552
553    fn alloc_sfn_entry(&self) -> Result<(DirRawStream<'a, IO, TP, OCC>, u64), Error<IO::Error>> {
554        let mut stream = self.find_free_entries(1)?;
555        let start_pos = stream.seek(io::SeekFrom::Current(0))?;
556        Ok((stream, start_pos))
557    }
558
559    fn write_entry(
560        &self,
561        name: &str,
562        raw_entry: DirFileEntryData,
563    ) -> Result<DirEntry<'a, IO, TP, OCC>, Error<IO::Error>> {
564        trace!("Dir::write_entry {}", name);
565        // check if name doesn't contain unsupported characters
566        validate_long_name(name)?;
567        // convert long name to UTF-16
568        let lfn_utf16 = Self::encode_lfn_utf16(name);
569        // write LFN entries, except for . and .., which need to be at
570        // the first two slots and don't need LFNs anyway
571        let (mut stream, start_pos) = if name == "." || name == ".." {
572            self.alloc_sfn_entry()?
573        } else {
574            self.alloc_and_write_lfn_entries(&lfn_utf16, raw_entry.name())?
575        };
576        // write short name entry
577        raw_entry.serialize(&mut stream)?;
578        // Get position directory stream after entries were written
579        let end_pos = stream.seek(io::SeekFrom::Current(0))?;
580        // Get current absolute position on the storage
581        // Unwrapping is safe because abs_pos() returns None only if stream is at position 0. This is not
582        // the case because an entry was just written
583        // Note: if current position is on the cluster boundary then a position in the cluster containing the entry is
584        // returned
585        let end_abs_pos = stream.abs_pos().unwrap();
586        // Calculate SFN entry start position on the storage
587        let start_abs_pos = end_abs_pos - u64::from(DIR_ENTRY_SIZE);
588        // return new logical entry descriptor
589        let short_name = ShortName::new(raw_entry.name());
590        Ok(DirEntry {
591            data: raw_entry,
592            short_name,
593            #[cfg(feature = "lfn")]
594            lfn_utf16,
595            fs: self.fs,
596            entry_pos: start_abs_pos,
597            offset_range: (start_pos, end_pos),
598        })
599    }
600}
601
602// Note: derive cannot be used because of invalid bounds. See: https://github.com/rust-lang/rust/issues/26925
603impl<IO: ReadWriteSeek, TP: TimeProvider, OCC: OemCpConverter> Clone for Dir<'_, IO, TP, OCC> {
604    fn clone(&self) -> Self {
605        Self {
606            stream: self.stream.clone(),
607            fs: self.fs,
608        }
609    }
610}
611
612/// An iterator over the directory entries.
613///
614/// This struct is created by the `iter` method on `Dir`.
615pub struct DirIter<'a, IO: ReadWriteSeek, TP, OCC> {
616    stream: DirRawStream<'a, IO, TP, OCC>,
617    fs: &'a FileSystem<IO, TP, OCC>,
618    skip_volume: bool,
619    err: bool,
620}
621
622impl<'a, IO: ReadWriteSeek, TP, OCC> DirIter<'a, IO, TP, OCC> {
623    fn new(stream: DirRawStream<'a, IO, TP, OCC>, fs: &'a FileSystem<IO, TP, OCC>, skip_volume: bool) -> Self {
624        DirIter {
625            stream,
626            fs,
627            skip_volume,
628            err: false,
629        }
630    }
631}
632
633impl<'a, IO: ReadWriteSeek, TP: TimeProvider, OCC> DirIter<'a, IO, TP, OCC> {
634    fn should_skip_entry(&self, raw_entry: &DirEntryData) -> bool {
635        if raw_entry.is_deleted() {
636            return true;
637        }
638        match raw_entry {
639            DirEntryData::File(sfn_entry) => self.skip_volume && sfn_entry.is_volume(),
640            DirEntryData::Lfn(_) => false,
641        }
642    }
643
644    #[allow(clippy::type_complexity)]
645    fn read_dir_entry(&mut self) -> Result<Option<DirEntry<'a, IO, TP, OCC>>, Error<IO::Error>> {
646        trace!("DirIter::read_dir_entry");
647        let mut lfn_builder = LongNameBuilder::new();
648        let mut offset = self.stream.seek(SeekFrom::Current(0))?;
649        let mut begin_offset = offset;
650        loop {
651            let raw_entry = DirEntryData::deserialize(&mut self.stream)?;
652            offset += u64::from(DIR_ENTRY_SIZE);
653            // Check if this is end of dir
654            if raw_entry.is_end() {
655                return Ok(None);
656            }
657            // Check if this is deleted or volume ID entry
658            if self.should_skip_entry(&raw_entry) {
659                trace!("skip entry");
660                lfn_builder.clear();
661                begin_offset = offset;
662                continue;
663            }
664            match raw_entry {
665                DirEntryData::File(data) => {
666                    // Get current absolute position on the storage
667                    // Unwrapping is safe because abs_pos() returns None only if stream is at position 0. This is not
668                    // the case because an entry was just read
669                    // Note: if current position is on the cluster boundary then a position in the cluster containing the entry is
670                    // returned
671                    let end_abs_pos = self.stream.abs_pos().unwrap();
672                    // Calculate SFN entry start position on the storage
673                    let abs_pos = end_abs_pos - u64::from(DIR_ENTRY_SIZE);
674                    // Check if LFN checksum is valid
675                    lfn_builder.validate_chksum(data.name());
676                    // Return directory entry
677                    let short_name = ShortName::new(data.name());
678                    trace!("file entry {:?}", data.name());
679                    return Ok(Some(DirEntry {
680                        data,
681                        short_name,
682                        #[cfg(feature = "lfn")]
683                        lfn_utf16: lfn_builder.into_buf(),
684                        fs: self.fs,
685                        entry_pos: abs_pos,
686                        offset_range: (begin_offset, offset),
687                    }));
688                }
689                DirEntryData::Lfn(data) => {
690                    // Append to LFN buffer
691                    trace!("lfn entry");
692                    lfn_builder.process(&data);
693                }
694            }
695        }
696    }
697}
698
699// Note: derive cannot be used because of invalid bounds. See: https://github.com/rust-lang/rust/issues/26925
700impl<IO: ReadWriteSeek, TP, OCC> Clone for DirIter<'_, IO, TP, OCC> {
701    fn clone(&self) -> Self {
702        Self {
703            stream: self.stream.clone(),
704            fs: self.fs,
705            err: self.err,
706            skip_volume: self.skip_volume,
707        }
708    }
709}
710
711impl<'a, IO: ReadWriteSeek, TP: TimeProvider, OCC> Iterator for DirIter<'a, IO, TP, OCC> {
712    type Item = Result<DirEntry<'a, IO, TP, OCC>, Error<IO::Error>>;
713
714    fn next(&mut self) -> Option<Self::Item> {
715        if self.err {
716            return None;
717        }
718        let r = self.read_dir_entry();
719        match r {
720            Ok(Some(e)) => Some(Ok(e)),
721            Ok(None) => None,
722            Err(err) => {
723                self.err = true;
724                Some(Err(err))
725            }
726        }
727    }
728}
729
730#[rustfmt::skip]
731fn validate_long_name<E: IoError>(name: &str) -> Result<(), Error<E>> {
732    // check if length is valid
733    if name.is_empty() {
734        return Err(Error::InvalidFileNameLength);
735    }
736    if name.len() > MAX_LONG_NAME_LEN {
737        return Err(Error::InvalidFileNameLength);
738    }
739    // check if there are only valid characters
740    for c in name.chars() {
741        match c {
742            'a'..='z' | 'A'..='Z' | '0'..='9'
743            | '\u{80}'..='\u{FFFF}'
744            | '$' | '%' | '\'' | '-' | '_' | '@' | '~' | '`' | '!' | '(' | ')' | '{' | '}' | '.' | ' ' | '+' | ','
745            | ';' | '=' | '[' | ']' | '^' | '#' | '&' => {},
746            _ => return Err(Error::UnsupportedFileNameCharacter),
747        }
748    }
749    Ok(())
750}
751
752fn lfn_checksum(short_name: &[u8; SFN_SIZE]) -> u8 {
753    let mut chksum = num::Wrapping(0_u8);
754    for b in short_name {
755        chksum = (chksum << 7) + (chksum >> 1) + num::Wrapping(*b);
756    }
757    chksum.0
758}
759
760#[cfg(all(feature = "lfn", feature = "alloc"))]
761#[derive(Clone)]
762pub(crate) struct LfnBuffer {
763    ucs2_units: Vec<u16>,
764}
765
766const MAX_LONG_NAME_LEN: usize = 255;
767
768#[cfg(feature = "lfn")]
769const MAX_LONG_DIR_ENTRIES: usize = (MAX_LONG_NAME_LEN + LFN_PART_LEN - 1) / LFN_PART_LEN;
770
771#[cfg(all(feature = "lfn", not(feature = "alloc")))]
772const LONG_NAME_BUFFER_LEN: usize = MAX_LONG_DIR_ENTRIES * LFN_PART_LEN;
773
774#[cfg(all(feature = "lfn", not(feature = "alloc")))]
775#[derive(Clone)]
776pub(crate) struct LfnBuffer {
777    ucs2_units: [u16; LONG_NAME_BUFFER_LEN],
778    len: usize,
779}
780
781#[cfg(all(feature = "lfn", feature = "alloc"))]
782impl LfnBuffer {
783    fn new() -> Self {
784        Self {
785            ucs2_units: Vec::<u16>::new(),
786        }
787    }
788
789    fn from_ucs2_units<I: Iterator<Item = u16>>(usc2_units: I) -> Self {
790        Self {
791            ucs2_units: usc2_units.collect(),
792        }
793    }
794
795    fn clear(&mut self) {
796        self.ucs2_units.clear();
797    }
798
799    pub(crate) fn len(&self) -> usize {
800        self.ucs2_units.len()
801    }
802
803    fn set_len(&mut self, len: usize) {
804        self.ucs2_units.resize(len, 0_u16);
805    }
806
807    pub(crate) fn as_ucs2_units(&self) -> &[u16] {
808        &self.ucs2_units
809    }
810}
811
812#[cfg(all(feature = "lfn", not(feature = "alloc")))]
813impl LfnBuffer {
814    fn new() -> Self {
815        Self {
816            ucs2_units: [0_u16; LONG_NAME_BUFFER_LEN],
817            len: 0,
818        }
819    }
820
821    fn from_ucs2_units<I: Iterator<Item = u16>>(usc2_units: I) -> Self {
822        let mut lfn = Self {
823            ucs2_units: [0_u16; LONG_NAME_BUFFER_LEN],
824            len: 0,
825        };
826        for (i, usc2_unit) in usc2_units.enumerate() {
827            lfn.ucs2_units[i] = usc2_unit;
828            lfn.len += 1;
829        }
830        lfn
831    }
832
833    fn clear(&mut self) {
834        self.ucs2_units = [0_u16; LONG_NAME_BUFFER_LEN];
835        self.len = 0;
836    }
837
838    pub(crate) fn len(&self) -> usize {
839        self.len
840    }
841
842    fn set_len(&mut self, len: usize) {
843        self.len = len;
844    }
845
846    pub(crate) fn as_ucs2_units(&self) -> &[u16] {
847        &self.ucs2_units[..self.len]
848    }
849}
850
851#[cfg(not(feature = "lfn"))]
852#[derive(Clone)]
853pub(crate) struct LfnBuffer {}
854
855#[cfg(not(feature = "lfn"))]
856impl LfnBuffer {
857    pub(crate) fn as_ucs2_units(&self) -> &[u16] {
858        &[]
859    }
860}
861
862#[cfg(feature = "lfn")]
863struct LongNameBuilder {
864    buf: LfnBuffer,
865    chksum: u8,
866    index: u8,
867}
868
869#[cfg(feature = "lfn")]
870impl LongNameBuilder {
871    fn new() -> Self {
872        Self {
873            buf: LfnBuffer::new(),
874            chksum: 0,
875            index: 0,
876        }
877    }
878
879    fn clear(&mut self) {
880        self.buf.clear();
881        self.index = 0;
882    }
883
884    fn into_buf(mut self) -> LfnBuffer {
885        // Check if last processed entry had index 1
886        if self.index == 1 {
887            self.truncate();
888        } else if !self.is_empty() {
889            warn!("unfinished LFN sequence {}", self.index);
890            self.clear();
891        }
892        self.buf
893    }
894
895    fn truncate(&mut self) {
896        // Truncate 0 and 0xFFFF characters from LFN buffer
897        let ucs2_units = &self.buf.ucs2_units;
898        let new_len = ucs2_units
899            .iter()
900            .rposition(|c| *c != 0xFFFF && *c != 0)
901            .map_or(0, |n| n + 1);
902        self.buf.set_len(new_len);
903    }
904
905    fn is_empty(&self) -> bool {
906        // Check if any LFN entry has been processed
907        // Note: index 0 is not a valid index in LFN and can be seen only after struct initialization
908        self.index == 0
909    }
910
911    fn process(&mut self, data: &DirLfnEntryData) {
912        let is_last = (data.order() & LFN_ENTRY_LAST_FLAG) != 0;
913        let index = data.order() & 0x1F;
914        if index == 0 || usize::from(index) > MAX_LONG_DIR_ENTRIES {
915            // Corrupted entry
916            warn!("currupted lfn entry! {:x}", data.order());
917            self.clear();
918            return;
919        }
920        if is_last {
921            // last entry is actually first entry in stream
922            self.index = index;
923            self.chksum = data.checksum();
924            self.buf.set_len(usize::from(index) * LFN_PART_LEN);
925        } else if self.index == 0 || index != self.index - 1 || data.checksum() != self.chksum {
926            // Corrupted entry
927            warn!(
928                "currupted lfn entry! {:x} {:x} {:x} {:x}",
929                data.order(),
930                self.index,
931                data.checksum(),
932                self.chksum
933            );
934            self.clear();
935            return;
936        } else {
937            // Decrement LFN index only for non-last entries
938            self.index -= 1;
939        }
940        let pos = LFN_PART_LEN * usize::from(index - 1);
941        // copy name parts into LFN buffer
942        data.copy_name_to_slice(&mut self.buf.ucs2_units[pos..pos + 13]);
943    }
944
945    fn validate_chksum(&mut self, short_name: &[u8; SFN_SIZE]) {
946        if self.is_empty() {
947            // Nothing to validate - no LFN entries has been processed
948            return;
949        }
950        let chksum = lfn_checksum(short_name);
951        if chksum != self.chksum {
952            warn!("checksum mismatch {:x} {:x} {:?}", chksum, self.chksum, short_name);
953            self.clear();
954        }
955    }
956}
957
958// Dummy implementation for non-alloc build
959#[cfg(not(feature = "lfn"))]
960struct LongNameBuilder {}
961#[cfg(not(feature = "lfn"))]
962impl LongNameBuilder {
963    fn new() -> Self {
964        LongNameBuilder {}
965    }
966    fn clear(&mut self) {}
967    fn into_vec(self) {}
968    fn truncate(&mut self) {}
969    fn process(&mut self, _data: &DirLfnEntryData) {}
970    fn validate_chksum(&mut self, _short_name: &[u8; SFN_SIZE]) {}
971}
972
973#[cfg(feature = "lfn")]
974struct LfnEntriesGenerator<'a> {
975    name_parts_iter: iter::Rev<slice::Chunks<'a, u16>>,
976    checksum: u8,
977    index: usize,
978    num: usize,
979    ended: bool,
980}
981
982#[cfg(feature = "lfn")]
983impl<'a> LfnEntriesGenerator<'a> {
984    fn new(name_utf16: &'a [u16], checksum: u8) -> Self {
985        let num_entries = (name_utf16.len() + LFN_PART_LEN - 1) / LFN_PART_LEN;
986        // create generator using reverse iterator over chunks - first chunk can be shorter
987        LfnEntriesGenerator {
988            checksum,
989            name_parts_iter: name_utf16.chunks(LFN_PART_LEN).rev(),
990            index: 0,
991            num: num_entries,
992            ended: false,
993        }
994    }
995}
996
997#[cfg(feature = "lfn")]
998impl Iterator for LfnEntriesGenerator<'_> {
999    type Item = DirLfnEntryData;
1000
1001    fn next(&mut self) -> Option<Self::Item> {
1002        if self.ended {
1003            return None;
1004        }
1005
1006        // get next part from reverse iterator
1007        if let Some(name_part) = self.name_parts_iter.next() {
1008            let lfn_index = self.num - self.index;
1009            let mut order = lfn_index as u8;
1010            if self.index == 0 {
1011                // this is last name part (written as first)
1012                order |= LFN_ENTRY_LAST_FLAG;
1013            }
1014            debug_assert!(order > 0);
1015            let mut lfn_part = [LFN_PADDING; LFN_PART_LEN];
1016            lfn_part[..name_part.len()].copy_from_slice(name_part);
1017            if name_part.len() < LFN_PART_LEN {
1018                // name is only zero-terminated if its length is not multiplicity of LFN_PART_LEN
1019                lfn_part[name_part.len()] = 0;
1020            }
1021            // create and return new LFN entry
1022            let mut lfn_entry = DirLfnEntryData::new(order, self.checksum);
1023            lfn_entry.copy_name_from_slice(&lfn_part);
1024            self.index += 1;
1025            Some(lfn_entry)
1026        } else {
1027            // end of name
1028            self.ended = true;
1029            None
1030        }
1031    }
1032
1033    fn size_hint(&self) -> (usize, Option<usize>) {
1034        self.name_parts_iter.size_hint()
1035    }
1036}
1037
1038// name_parts_iter is ExactSizeIterator so size_hint returns one limit
1039#[cfg(feature = "lfn")]
1040impl ExactSizeIterator for LfnEntriesGenerator<'_> {}
1041
1042// Dummy implementation for non-alloc build
1043#[cfg(not(feature = "lfn"))]
1044struct LfnEntriesGenerator {}
1045#[cfg(not(feature = "lfn"))]
1046impl LfnEntriesGenerator {
1047    fn new(_name_utf16: &[u16], _checksum: u8) -> Self {
1048        LfnEntriesGenerator {}
1049    }
1050}
1051#[cfg(not(feature = "lfn"))]
1052impl Iterator for LfnEntriesGenerator {
1053    type Item = DirLfnEntryData;
1054
1055    fn next(&mut self) -> Option<Self::Item> {
1056        None
1057    }
1058
1059    fn size_hint(&self) -> (usize, Option<usize>) {
1060        (0, Some(0))
1061    }
1062}
1063#[cfg(not(feature = "lfn"))]
1064impl ExactSizeIterator for LfnEntriesGenerator {}
1065
1066#[derive(Default, Debug, Clone)]
1067struct ShortNameGenerator {
1068    chksum: u16,
1069    long_prefix_bitmap: u16,
1070    prefix_chksum_bitmap: u16,
1071    name_fits: bool,
1072    lossy_conv: bool,
1073    exact_match: bool,
1074    basename_len: usize,
1075    short_name: [u8; SFN_SIZE],
1076}
1077
1078impl ShortNameGenerator {
1079    fn new(name: &str) -> Self {
1080        // padded by ' '
1081        let mut short_name = [SFN_PADDING; SFN_SIZE];
1082        // find extension after last dot
1083        // Note: short file name cannot start with the extension
1084        let dot_index_opt = name[1..].rfind('.').map(|index| index + 1);
1085        // copy basename (part of filename before a dot)
1086        let basename_src = dot_index_opt.map_or(name, |dot_index| &name[..dot_index]);
1087        let (basename_len, basename_fits, basename_lossy) =
1088            Self::copy_short_name_part(&mut short_name[0..8], basename_src);
1089        // copy file extension if exists
1090        let (name_fits, lossy_conv) = dot_index_opt.map_or((basename_fits, basename_lossy), |dot_index| {
1091            let (_, ext_fits, ext_lossy) = Self::copy_short_name_part(&mut short_name[8..11], &name[dot_index + 1..]);
1092            (basename_fits && ext_fits, basename_lossy || ext_lossy)
1093        });
1094        let chksum = Self::checksum(name);
1095        Self {
1096            chksum,
1097            name_fits,
1098            lossy_conv,
1099            basename_len,
1100            short_name,
1101            ..Self::default()
1102        }
1103    }
1104
1105    fn generate_dot() -> [u8; SFN_SIZE] {
1106        let mut short_name = [SFN_PADDING; SFN_SIZE];
1107        short_name[0] = b'.';
1108        short_name
1109    }
1110
1111    fn generate_dotdot() -> [u8; SFN_SIZE] {
1112        let mut short_name = [SFN_PADDING; SFN_SIZE];
1113        short_name[0] = b'.';
1114        short_name[1] = b'.';
1115        short_name
1116    }
1117
1118    fn copy_short_name_part(dst: &mut [u8], src: &str) -> (usize, bool, bool) {
1119        let mut dst_pos = 0;
1120        let mut lossy_conv = false;
1121        for c in src.chars() {
1122            if dst_pos == dst.len() {
1123                // result buffer is full
1124                return (dst_pos, false, lossy_conv);
1125            }
1126            // Make sure character is allowed in 8.3 name
1127            #[rustfmt::skip]
1128            let fixed_c = match c {
1129                // strip spaces and dots
1130                ' ' | '.' => {
1131                    lossy_conv = true;
1132                    continue;
1133                },
1134                // copy allowed characters
1135                'A'..='Z' | 'a'..='z' | '0'..='9'
1136                | '!' | '#' | '$' | '%' | '&' | '\'' | '(' | ')' | '-' | '@' | '^' | '_' | '`' | '{' | '}' | '~' => c,
1137                // replace disallowed characters by underscore
1138                _ => '_',
1139            };
1140            // Update 'lossy conversion' flag
1141            lossy_conv = lossy_conv || (fixed_c != c);
1142            // short name is always uppercase
1143            let upper = fixed_c.to_ascii_uppercase();
1144            dst[dst_pos] = upper as u8; // SAFE: upper is in range 0x20-0x7F
1145            dst_pos += 1;
1146        }
1147        (dst_pos, true, lossy_conv)
1148    }
1149
1150    fn add_existing(&mut self, short_name: &[u8; SFN_SIZE]) {
1151        // check for exact match collision
1152        if short_name == &self.short_name {
1153            self.exact_match = true;
1154        }
1155        // check for long prefix form collision (TEXTFI~1.TXT)
1156        self.check_for_long_prefix_collision(short_name);
1157
1158        // check for short prefix + checksum form collision (TE021F~1.TXT)
1159        self.check_for_short_prefix_collision(short_name);
1160    }
1161
1162    fn check_for_long_prefix_collision(&mut self, short_name: &[u8; SFN_SIZE]) {
1163        // check for long prefix form collision (TEXTFI~1.TXT)
1164        let long_prefix_len = 6.min(self.basename_len);
1165        if short_name[long_prefix_len] != b'~' {
1166            return;
1167        }
1168        if let Some(num_suffix) = char::from(short_name[long_prefix_len + 1]).to_digit(10) {
1169            let long_prefix_matches = short_name[..long_prefix_len] == self.short_name[..long_prefix_len];
1170            let ext_matches = short_name[8..] == self.short_name[8..];
1171            if long_prefix_matches && ext_matches {
1172                self.long_prefix_bitmap |= 1 << num_suffix;
1173            }
1174        }
1175    }
1176
1177    fn check_for_short_prefix_collision(&mut self, short_name: &[u8; SFN_SIZE]) {
1178        // check for short prefix + checksum form collision (TE021F~1.TXT)
1179        let short_prefix_len = 2.min(self.basename_len);
1180        if short_name[short_prefix_len + 4] != b'~' {
1181            return;
1182        }
1183        if let Some(num_suffix) = char::from(short_name[short_prefix_len + 4 + 1]).to_digit(10) {
1184            let short_prefix_matches = short_name[..short_prefix_len] == self.short_name[..short_prefix_len];
1185            let ext_matches = short_name[8..] == self.short_name[8..];
1186            if short_prefix_matches && ext_matches {
1187                let chksum_res = str::from_utf8(&short_name[short_prefix_len..short_prefix_len + 4])
1188                    .map(|s| u16::from_str_radix(s, 16));
1189                if chksum_res == Ok(Ok(self.chksum)) {
1190                    self.prefix_chksum_bitmap |= 1 << num_suffix;
1191                }
1192            }
1193        }
1194    }
1195
1196    fn checksum(name: &str) -> u16 {
1197        // BSD checksum algorithm
1198        let mut chksum = num::Wrapping(0_u16);
1199        for c in name.chars() {
1200            chksum = (chksum >> 1) + (chksum << 15) + num::Wrapping(c as u16);
1201        }
1202        chksum.0
1203    }
1204
1205    fn generate(&self) -> Result<[u8; SFN_SIZE], Error<()>> {
1206        if !self.lossy_conv && self.name_fits && !self.exact_match {
1207            // If there was no lossy conversion and name fits into
1208            // 8.3 convention and there is no collision return it as is
1209            return Ok(self.short_name);
1210        }
1211        // Try using long 6-characters prefix
1212        for i in 1..5 {
1213            if self.long_prefix_bitmap & (1 << i) == 0 {
1214                return Ok(self.build_prefixed_name(i, false));
1215            }
1216        }
1217        // Try prefix with checksum
1218        for i in 1..10 {
1219            if self.prefix_chksum_bitmap & (1 << i) == 0 {
1220                return Ok(self.build_prefixed_name(i, true));
1221            }
1222        }
1223        // Too many collisions - fail
1224        Err(Error::AlreadyExists)
1225    }
1226
1227    fn next_iteration(&mut self) {
1228        // Try different checksum in next iteration
1229        self.chksum = (num::Wrapping(self.chksum) + num::Wrapping(1)).0;
1230        // Zero bitmaps
1231        self.long_prefix_bitmap = 0;
1232        self.prefix_chksum_bitmap = 0;
1233    }
1234
1235    fn build_prefixed_name(&self, num: u32, with_chksum: bool) -> [u8; SFN_SIZE] {
1236        let mut buf = [SFN_PADDING; SFN_SIZE];
1237        let prefix_len = if with_chksum {
1238            let prefix_len = 2.min(self.basename_len);
1239            buf[..prefix_len].copy_from_slice(&self.short_name[..prefix_len]);
1240            buf[prefix_len..prefix_len + 4].copy_from_slice(&Self::u16_to_hex(self.chksum));
1241            prefix_len + 4
1242        } else {
1243            let prefix_len = 6.min(self.basename_len);
1244            buf[..prefix_len].copy_from_slice(&self.short_name[..prefix_len]);
1245            prefix_len
1246        };
1247        buf[prefix_len] = b'~';
1248        buf[prefix_len + 1] = char::from_digit(num, 10).unwrap() as u8; // SAFE: num is in range [1, 9]
1249        buf[8..].copy_from_slice(&self.short_name[8..]);
1250        buf
1251    }
1252
1253    fn u16_to_hex(x: u16) -> [u8; 4] {
1254        // Unwrapping below is safe because each line takes 4 bits of `x` and shifts them to the right so they form
1255        // a number in range [0, 15]
1256        let x_u32 = u32::from(x);
1257        let mut hex_bytes = [
1258            char::from_digit((x_u32 >> 12) & 0xF, 16).unwrap() as u8,
1259            char::from_digit((x_u32 >> 8) & 0xF, 16).unwrap() as u8,
1260            char::from_digit((x_u32 >> 4) & 0xF, 16).unwrap() as u8,
1261            char::from_digit(x_u32 & 0xF, 16).unwrap() as u8,
1262        ];
1263        hex_bytes.make_ascii_uppercase();
1264        hex_bytes
1265    }
1266}
1267
1268#[cfg(test)]
1269mod tests {
1270    use super::*;
1271
1272    #[test]
1273    fn test_split_path() {
1274        assert_eq!(split_path("aaa/bbb/ccc"), ("aaa", Some("bbb/ccc")));
1275        assert_eq!(split_path("aaa/bbb"), ("aaa", Some("bbb")));
1276        assert_eq!(split_path("aaa"), ("aaa", None));
1277    }
1278
1279    #[test]
1280    fn test_generate_short_name() {
1281        assert_eq!(ShortNameGenerator::new("Foo").generate().ok(), Some(*b"FOO        "));
1282        assert_eq!(ShortNameGenerator::new("Foo.b").generate().ok(), Some(*b"FOO     B  "));
1283        assert_eq!(
1284            ShortNameGenerator::new("Foo.baR").generate().ok(),
1285            Some(*b"FOO     BAR")
1286        );
1287        assert_eq!(
1288            ShortNameGenerator::new("Foo+1.baR").generate().ok(),
1289            Some(*b"FOO_1~1 BAR")
1290        );
1291        assert_eq!(
1292            ShortNameGenerator::new("ver +1.2.text").generate().ok(),
1293            Some(*b"VER_12~1TEX")
1294        );
1295        assert_eq!(
1296            ShortNameGenerator::new(".bashrc.swp").generate().ok(),
1297            Some(*b"BASHRC~1SWP")
1298        );
1299        assert_eq!(ShortNameGenerator::new(".foo").generate().ok(), Some(*b"FOO~1      "));
1300    }
1301
1302    #[test]
1303    fn test_short_name_checksum_overflow() {
1304        ShortNameGenerator::checksum("\u{FF5A}\u{FF5A}\u{FF5A}\u{FF5A}");
1305    }
1306
1307    #[test]
1308    fn test_lfn_checksum_overflow() {
1309        lfn_checksum(&[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]);
1310    }
1311
1312    #[test]
1313    fn test_generate_short_name_collisions_long() {
1314        let mut buf: [u8; SFN_SIZE];
1315        let mut gen = ShortNameGenerator::new("TextFile.Mine.txt");
1316        buf = gen.generate().unwrap();
1317        assert_eq!(&buf, b"TEXTFI~1TXT");
1318        gen.add_existing(&buf);
1319        buf = gen.generate().unwrap();
1320        assert_eq!(&buf, b"TEXTFI~2TXT");
1321        gen.add_existing(&buf);
1322        buf = gen.generate().unwrap();
1323        assert_eq!(&buf, b"TEXTFI~3TXT");
1324        gen.add_existing(&buf);
1325        buf = gen.generate().unwrap();
1326        assert_eq!(&buf, b"TEXTFI~4TXT");
1327        gen.add_existing(&buf);
1328        buf = gen.generate().unwrap();
1329        assert_eq!(&buf, b"TE527D~1TXT");
1330        gen.add_existing(&buf);
1331        buf = gen.generate().unwrap();
1332        assert_eq!(&buf, b"TE527D~2TXT");
1333        for i in 3..10 {
1334            gen.add_existing(&buf);
1335            buf = gen.generate().unwrap();
1336            assert_eq!(&buf, format!("TE527D~{}TXT", i).as_bytes());
1337        }
1338        gen.add_existing(&buf);
1339        assert!(gen.generate().is_err());
1340        gen.next_iteration();
1341        for _i in 0..4 {
1342            buf = gen.generate().unwrap();
1343            gen.add_existing(&buf);
1344        }
1345        buf = gen.generate().unwrap();
1346        assert_eq!(&buf, b"TE527E~1TXT");
1347    }
1348
1349    #[test]
1350    fn test_generate_short_name_collisions_short() {
1351        let mut buf: [u8; SFN_SIZE];
1352        let mut gen = ShortNameGenerator::new("x.txt");
1353        buf = gen.generate().unwrap();
1354        assert_eq!(&buf, b"X       TXT");
1355        gen.add_existing(&buf);
1356        buf = gen.generate().unwrap();
1357        assert_eq!(&buf, b"X~1     TXT");
1358        gen.add_existing(&buf);
1359        buf = gen.generate().unwrap();
1360        assert_eq!(&buf, b"X~2     TXT");
1361        gen.add_existing(&buf);
1362        buf = gen.generate().unwrap();
1363        assert_eq!(&buf, b"X~3     TXT");
1364        gen.add_existing(&buf);
1365        buf = gen.generate().unwrap();
1366        assert_eq!(&buf, b"X~4     TXT");
1367        gen.add_existing(&buf);
1368        buf = gen.generate().unwrap();
1369        assert_eq!(&buf, b"X40DA~1 TXT");
1370        gen.add_existing(&buf);
1371        buf = gen.generate().unwrap();
1372        assert_eq!(&buf, b"X40DA~2 TXT");
1373    }
1374}