fatfs/
dir_entry.rs

1#[cfg(all(not(feature = "std"), feature = "alloc"))]
2use alloc::{string::String, vec::Vec};
3use core::char;
4use core::iter::FromIterator;
5use core::{fmt, str};
6use io;
7use io::prelude::*;
8use io::Cursor;
9
10use byteorder::LittleEndian;
11use byteorder_ext::{ReadBytesExt, WriteBytesExt};
12
13use dir::{Dir, DirRawStream};
14use file::File;
15use fs::{FatType, FileSystem, OemCpConverter, ReadWriteSeek};
16use time::{Date, DateTime};
17
18bitflags! {
19    /// A FAT file attributes.
20    #[derive(Default)]
21    pub struct FileAttributes: u8 {
22        const READ_ONLY  = 0x01;
23        const HIDDEN     = 0x02;
24        const SYSTEM     = 0x04;
25        const VOLUME_ID  = 0x08;
26        const DIRECTORY  = 0x10;
27        const ARCHIVE    = 0x20;
28        const LFN        = Self::READ_ONLY.bits | Self::HIDDEN.bits
29                         | Self::SYSTEM.bits | Self::VOLUME_ID.bits;
30    }
31}
32
33// Size of single directory entry in bytes
34pub(crate) const DIR_ENTRY_SIZE: u64 = 32;
35
36// Directory entry flags available in first byte of the short name
37pub(crate) const DIR_ENTRY_DELETED_FLAG: u8 = 0xE5;
38pub(crate) const DIR_ENTRY_REALLY_E5_FLAG: u8 = 0x05;
39
40// Length in characters of a LFN fragment packed in one directory entry
41pub(crate) const LFN_PART_LEN: usize = 13;
42
43// Bit used in order field to mark last LFN entry
44pub(crate) const LFN_ENTRY_LAST_FLAG: u8 = 0x40;
45
46/// Decoded file short name
47#[derive(Clone, Debug, Default)]
48pub(crate) struct ShortName {
49    name: [u8; 12],
50    len: u8,
51}
52
53impl ShortName {
54    const PADDING: u8 = b' ';
55
56    pub(crate) fn new(raw_name: &[u8; 11]) -> Self {
57        // get name components length by looking for space character
58        let name_len = raw_name[0..8].iter().rposition(|x| *x != Self::PADDING).map(|p| p + 1).unwrap_or(0);
59        let ext_len = raw_name[8..11].iter().rposition(|x| *x != Self::PADDING).map(|p| p + 1).unwrap_or(0);
60        let mut name = [Self::PADDING; 12];
61        name[..name_len].copy_from_slice(&raw_name[..name_len]);
62        let total_len = if ext_len > 0 {
63            name[name_len] = b'.';
64            name[name_len + 1..name_len + 1 + ext_len].copy_from_slice(&raw_name[8..8 + ext_len]);
65            // Return total name length
66            name_len + 1 + ext_len
67        } else {
68            // No extension - return length of name part
69            name_len
70        };
71        // FAT encodes character 0xE5 as 0x05 because 0xE5 marks deleted files
72        if name[0] == DIR_ENTRY_REALLY_E5_FLAG {
73            name[0] = 0xE5;
74        }
75        // Short names in FAT filesystem are encoded in OEM code-page
76        ShortName { name, len: total_len as u8 }
77    }
78
79    fn as_bytes(&self) -> &[u8] {
80        &self.name[..self.len as usize]
81    }
82
83    #[cfg(feature = "alloc")]
84    fn to_string(&self, oem_cp_converter: &OemCpConverter) -> String {
85        // Strip non-ascii characters from short name
86        let char_iter = self.as_bytes().iter().cloned().map(|c| oem_cp_converter.decode(c));
87        // Build string from character iterator
88        String::from_iter(char_iter)
89    }
90
91    fn eq_ignore_case(&self, name: &str, oem_cp_converter: &OemCpConverter) -> bool {
92        // Strip non-ascii characters from short name
93        let byte_iter = self.as_bytes().iter().cloned();
94        let char_iter = byte_iter.map(|c| oem_cp_converter.decode(c));
95        let uppercase_char_iter = char_iter.flat_map(|c| c.to_uppercase());
96        // Build string from character iterator
97        uppercase_char_iter.eq(name.chars().flat_map(|c| c.to_uppercase()))
98    }
99}
100
101#[allow(dead_code)]
102#[derive(Clone, Debug, Default)]
103pub(crate) struct DirFileEntryData {
104    name: [u8; 11],
105    attrs: FileAttributes,
106    reserved_0: u8,
107    create_time_0: u8,
108    create_time_1: u16,
109    create_date: u16,
110    access_date: u16,
111    first_cluster_hi: u16,
112    modify_time: u16,
113    modify_date: u16,
114    first_cluster_lo: u16,
115    size: u32,
116}
117
118impl DirFileEntryData {
119    pub(crate) fn new(name: [u8; 11], attrs: FileAttributes) -> Self {
120        DirFileEntryData { name, attrs, ..Default::default() }
121    }
122
123    pub(crate) fn renamed(&self, new_name: [u8; 11]) -> Self {
124        let mut sfn_entry = self.clone();
125        sfn_entry.name = new_name;
126        sfn_entry
127    }
128
129    pub(crate) fn name(&self) -> &[u8; 11] {
130        &self.name
131    }
132
133    #[cfg(feature = "alloc")]
134    fn lowercase_name(&self) -> ShortName {
135        let mut name_copy: [u8; 11] = self.name;
136        if self.lowercase_basename() {
137            for c in &mut name_copy[..8] {
138                *c = (*c as char).to_ascii_lowercase() as u8;
139            }
140        }
141        if self.lowercase_ext() {
142            for c in &mut name_copy[8..] {
143                *c = (*c as char).to_ascii_lowercase() as u8;
144            }
145        }
146        ShortName::new(&name_copy)
147    }
148
149    pub(crate) fn first_cluster(&self, fat_type: FatType) -> Option<u32> {
150        let first_cluster_hi = if fat_type == FatType::Fat32 { self.first_cluster_hi } else { 0 };
151        let n = ((first_cluster_hi as u32) << 16) | self.first_cluster_lo as u32;
152        if n == 0 {
153            None
154        } else {
155            Some(n)
156        }
157    }
158
159    pub(crate) fn set_first_cluster(&mut self, cluster: Option<u32>, fat_type: FatType) {
160        let n = cluster.unwrap_or(0);
161        if fat_type == FatType::Fat32 {
162            self.first_cluster_hi = (n >> 16) as u16;
163        }
164        self.first_cluster_lo = (n & 0xFFFF) as u16;
165    }
166
167    pub(crate) fn size(&self) -> Option<u32> {
168        if self.is_file() {
169            Some(self.size)
170        } else {
171            None
172        }
173    }
174
175    fn set_size(&mut self, size: u32) {
176        self.size = size;
177    }
178
179    pub(crate) fn is_dir(&self) -> bool {
180        self.attrs.contains(FileAttributes::DIRECTORY)
181    }
182
183    fn is_file(&self) -> bool {
184        !self.is_dir()
185    }
186
187    fn lowercase_basename(&self) -> bool {
188        self.reserved_0 & (1 << 3) != 0
189    }
190
191    fn lowercase_ext(&self) -> bool {
192        self.reserved_0 & (1 << 4) != 0
193    }
194
195    fn created(&self) -> DateTime {
196        DateTime::decode(self.create_date, self.create_time_1, self.create_time_0)
197    }
198
199    fn accessed(&self) -> Date {
200        Date::decode(self.access_date)
201    }
202
203    fn modified(&self) -> DateTime {
204        DateTime::decode(self.modify_date, self.modify_time, 0)
205    }
206
207    pub(crate) fn set_created(&mut self, date_time: DateTime) {
208        self.create_date = date_time.date.encode();
209        let encoded_time = date_time.time.encode();
210        self.create_time_1 = encoded_time.0;
211        self.create_time_0 = encoded_time.1;
212    }
213
214    pub(crate) fn set_accessed(&mut self, date: Date) {
215        self.access_date = date.encode();
216    }
217
218    pub(crate) fn set_modified(&mut self, date_time: DateTime) {
219        self.modify_date = date_time.date.encode();
220        self.modify_time = date_time.time.encode().0;
221    }
222
223    pub(crate) fn serialize(&self, wrt: &mut Write) -> io::Result<()> {
224        wrt.write_all(&self.name)?;
225        wrt.write_u8(self.attrs.bits())?;
226        wrt.write_u8(self.reserved_0)?;
227        wrt.write_u8(self.create_time_0)?;
228        wrt.write_u16::<LittleEndian>(self.create_time_1)?;
229        wrt.write_u16::<LittleEndian>(self.create_date)?;
230        wrt.write_u16::<LittleEndian>(self.access_date)?;
231        wrt.write_u16::<LittleEndian>(self.first_cluster_hi)?;
232        wrt.write_u16::<LittleEndian>(self.modify_time)?;
233        wrt.write_u16::<LittleEndian>(self.modify_date)?;
234        wrt.write_u16::<LittleEndian>(self.first_cluster_lo)?;
235        wrt.write_u32::<LittleEndian>(self.size)?;
236        Ok(())
237    }
238
239    pub(crate) fn is_deleted(&self) -> bool {
240        self.name[0] == DIR_ENTRY_DELETED_FLAG
241    }
242
243    pub(crate) fn set_deleted(&mut self) {
244        self.name[0] = DIR_ENTRY_DELETED_FLAG;
245    }
246
247    pub(crate) fn is_end(&self) -> bool {
248        self.name[0] == 0
249    }
250
251    pub(crate) fn is_volume(&self) -> bool {
252        self.attrs.contains(FileAttributes::VOLUME_ID)
253    }
254}
255
256#[allow(dead_code)]
257#[derive(Clone, Debug, Default)]
258pub(crate) struct DirLfnEntryData {
259    order: u8,
260    name_0: [u16; 5],
261    attrs: FileAttributes,
262    entry_type: u8,
263    checksum: u8,
264    name_1: [u16; 6],
265    reserved_0: u16,
266    name_2: [u16; 2],
267}
268
269impl DirLfnEntryData {
270    pub(crate) fn new(order: u8, checksum: u8) -> Self {
271        DirLfnEntryData { order, checksum, attrs: FileAttributes::LFN, ..Default::default() }
272    }
273
274    pub(crate) fn copy_name_from_slice(&mut self, lfn_part: &[u16; LFN_PART_LEN]) {
275        self.name_0.copy_from_slice(&lfn_part[0..5]);
276        self.name_1.copy_from_slice(&lfn_part[5..5 + 6]);
277        self.name_2.copy_from_slice(&lfn_part[11..11 + 2]);
278    }
279
280    pub(crate) fn copy_name_to_slice(&self, lfn_part: &mut [u16]) {
281        debug_assert!(lfn_part.len() == LFN_PART_LEN);
282        lfn_part[0..5].copy_from_slice(&self.name_0);
283        lfn_part[5..11].copy_from_slice(&self.name_1);
284        lfn_part[11..13].copy_from_slice(&self.name_2);
285    }
286
287    pub(crate) fn serialize(&self, wrt: &mut Write) -> io::Result<()> {
288        wrt.write_u8(self.order)?;
289        for ch in self.name_0.iter() {
290            wrt.write_u16::<LittleEndian>(*ch)?;
291        }
292        wrt.write_u8(self.attrs.bits())?;
293        wrt.write_u8(self.entry_type)?;
294        wrt.write_u8(self.checksum)?;
295        for ch in self.name_1.iter() {
296            wrt.write_u16::<LittleEndian>(*ch)?;
297        }
298        wrt.write_u16::<LittleEndian>(self.reserved_0)?;
299        for ch in self.name_2.iter() {
300            wrt.write_u16::<LittleEndian>(*ch)?;
301        }
302        Ok(())
303    }
304
305    #[cfg(feature = "alloc")]
306    pub(crate) fn order(&self) -> u8 {
307        self.order
308    }
309
310    #[cfg(feature = "alloc")]
311    pub(crate) fn checksum(&self) -> u8 {
312        self.checksum
313    }
314
315    pub(crate) fn is_deleted(&self) -> bool {
316        self.order == DIR_ENTRY_DELETED_FLAG
317    }
318
319    pub(crate) fn set_deleted(&mut self) {
320        self.order = DIR_ENTRY_DELETED_FLAG;
321    }
322
323    pub(crate) fn is_end(&self) -> bool {
324        self.order == 0
325    }
326}
327
328#[derive(Clone, Debug)]
329pub(crate) enum DirEntryData {
330    File(DirFileEntryData),
331    Lfn(DirLfnEntryData),
332}
333
334impl DirEntryData {
335    pub(crate) fn serialize(&self, wrt: &mut Write) -> io::Result<()> {
336        match self {
337            &DirEntryData::File(ref file) => file.serialize(wrt),
338            &DirEntryData::Lfn(ref lfn) => lfn.serialize(wrt),
339        }
340    }
341
342    pub(crate) fn deserialize(rdr: &mut Read) -> io::Result<Self> {
343        let mut name = [0; 11];
344        match rdr.read_exact(&mut name) {
345            Err(ref err) if err.kind() == io::ErrorKind::UnexpectedEof => {
346                // entries can occupy all clusters of directory so there is no zero entry at the end
347                // handle it here by returning non-existing empty entry
348                return Ok(DirEntryData::File(DirFileEntryData { ..Default::default() }));
349            },
350            Err(err) => return Err(err),
351            _ => {},
352        }
353        let attrs = FileAttributes::from_bits_truncate(rdr.read_u8()?);
354        if attrs & FileAttributes::LFN == FileAttributes::LFN {
355            // read long name entry
356            let mut data = DirLfnEntryData { attrs, ..Default::default() };
357            // use cursor to divide name into order and LFN name_0
358            let mut cur = Cursor::new(&name);
359            data.order = cur.read_u8()?;
360            cur.read_u16_into::<LittleEndian>(&mut data.name_0)?;
361            data.entry_type = rdr.read_u8()?;
362            data.checksum = rdr.read_u8()?;
363            rdr.read_u16_into::<LittleEndian>(&mut data.name_1)?;
364            data.reserved_0 = rdr.read_u16::<LittleEndian>()?;
365            rdr.read_u16_into::<LittleEndian>(&mut data.name_2)?;
366            Ok(DirEntryData::Lfn(data))
367        } else {
368            // read short name entry
369            let data = DirFileEntryData {
370                name,
371                attrs,
372                reserved_0: rdr.read_u8()?,
373                create_time_0: rdr.read_u8()?,
374                create_time_1: rdr.read_u16::<LittleEndian>()?,
375                create_date: rdr.read_u16::<LittleEndian>()?,
376                access_date: rdr.read_u16::<LittleEndian>()?,
377                first_cluster_hi: rdr.read_u16::<LittleEndian>()?,
378                modify_time: rdr.read_u16::<LittleEndian>()?,
379                modify_date: rdr.read_u16::<LittleEndian>()?,
380                first_cluster_lo: rdr.read_u16::<LittleEndian>()?,
381                size: rdr.read_u32::<LittleEndian>()?,
382            };
383            Ok(DirEntryData::File(data))
384        }
385    }
386
387    pub(crate) fn is_deleted(&self) -> bool {
388        match self {
389            &DirEntryData::File(ref file) => file.is_deleted(),
390            &DirEntryData::Lfn(ref lfn) => lfn.is_deleted(),
391        }
392    }
393
394    pub(crate) fn set_deleted(&mut self) {
395        match self {
396            &mut DirEntryData::File(ref mut file) => file.set_deleted(),
397            &mut DirEntryData::Lfn(ref mut lfn) => lfn.set_deleted(),
398        }
399    }
400
401    pub(crate) fn is_end(&self) -> bool {
402        match self {
403            &DirEntryData::File(ref file) => file.is_end(),
404            &DirEntryData::Lfn(ref lfn) => lfn.is_end(),
405        }
406    }
407}
408
409#[derive(Clone, Debug)]
410pub(crate) struct DirEntryEditor {
411    data: DirFileEntryData,
412    pos: u64,
413    dirty: bool,
414}
415
416impl DirEntryEditor {
417    fn new(data: DirFileEntryData, pos: u64) -> Self {
418        DirEntryEditor { data, pos, dirty: false }
419    }
420
421    pub(crate) fn inner(&self) -> &DirFileEntryData {
422        &self.data
423    }
424
425    pub(crate) fn set_first_cluster(&mut self, first_cluster: Option<u32>, fat_type: FatType) {
426        if first_cluster != self.data.first_cluster(fat_type) {
427            self.data.set_first_cluster(first_cluster, fat_type);
428            self.dirty = true;
429        }
430    }
431
432    pub(crate) fn set_size(&mut self, size: u32) {
433        match self.data.size() {
434            Some(n) if size != n => {
435                self.data.set_size(size);
436                self.dirty = true;
437            },
438            _ => {},
439        }
440    }
441
442    pub(crate) fn set_created(&mut self, date_time: DateTime) {
443        if date_time != self.data.created() {
444            self.data.set_created(date_time);
445            self.dirty = true;
446        }
447    }
448
449    pub(crate) fn set_accessed(&mut self, date: Date) {
450        if date != self.data.accessed() {
451            self.data.set_accessed(date);
452            self.dirty = true;
453        }
454    }
455
456    pub(crate) fn set_modified(&mut self, date_time: DateTime) {
457        if date_time != self.data.modified() {
458            self.data.set_modified(date_time);
459            self.dirty = true;
460        }
461    }
462
463    pub(crate) fn flush<T: ReadWriteSeek>(&mut self, fs: &FileSystem<T>) -> io::Result<()> {
464        if self.dirty {
465            self.write(fs)?;
466            self.dirty = false;
467        }
468        Ok(())
469    }
470
471    fn write<T: ReadWriteSeek>(&self, fs: &FileSystem<T>) -> io::Result<()> {
472        let mut disk = fs.disk.borrow_mut();
473        disk.seek(io::SeekFrom::Start(self.pos))?;
474        self.data.serialize(&mut *disk)
475    }
476}
477
478/// A FAT directory entry.
479///
480/// `DirEntry` is returned by `DirIter` when reading a directory.
481#[derive(Clone)]
482pub struct DirEntry<'a, T: ReadWriteSeek + 'a> {
483    pub(crate) data: DirFileEntryData,
484    pub(crate) short_name: ShortName,
485    #[cfg(feature = "alloc")]
486    pub(crate) lfn_utf16: Vec<u16>,
487    #[cfg(not(feature = "alloc"))]
488    pub(crate) lfn_utf16: (),
489    pub(crate) entry_pos: u64,
490    pub(crate) offset_range: (u64, u64),
491    pub(crate) fs: &'a FileSystem<T>,
492}
493
494impl<'a, T: ReadWriteSeek> DirEntry<'a, T> {
495    /// Returns short file name.
496    ///
497    /// Non-ASCII characters are replaced by the replacement character (U+FFFD).
498    #[cfg(feature = "alloc")]
499    pub fn short_file_name(&self) -> String {
500        self.short_name.to_string(self.fs.options.oem_cp_converter)
501    }
502
503    /// Returns short file name as byte array slice.
504    ///
505    /// Characters are encoded in the OEM codepage.
506    pub fn short_file_name_as_bytes(&self) -> &[u8] {
507        self.short_name.as_bytes()
508    }
509
510    /// Returns long file name or if it doesn't exist fallbacks to short file name.
511    #[cfg(feature = "alloc")]
512    pub fn file_name(&self) -> String {
513        if self.lfn_utf16.is_empty() {
514            self.data.lowercase_name().to_string(self.fs.options.oem_cp_converter)
515        } else {
516            String::from_utf16_lossy(&self.lfn_utf16)
517        }
518    }
519
520    /// Returns file attributes.
521    pub fn attributes(&self) -> FileAttributes {
522        self.data.attrs
523    }
524
525    /// Checks if entry belongs to directory.
526    pub fn is_dir(&self) -> bool {
527        self.data.is_dir()
528    }
529
530    /// Checks if entry belongs to regular file.
531    pub fn is_file(&self) -> bool {
532        self.data.is_file()
533    }
534
535    pub(crate) fn first_cluster(&self) -> Option<u32> {
536        self.data.first_cluster(self.fs.fat_type())
537    }
538
539    fn editor(&self) -> DirEntryEditor {
540        DirEntryEditor::new(self.data.clone(), self.entry_pos)
541    }
542
543    pub(crate) fn is_same_entry(&self, other: &DirEntry<T>) -> bool {
544        self.entry_pos == other.entry_pos
545    }
546
547    /// Returns `File` struct for this entry.
548    ///
549    /// Panics if this is not a file.
550    pub fn to_file(&self) -> File<'a, T> {
551        assert!(!self.is_dir(), "Not a file entry");
552        File::new(self.first_cluster(), Some(self.editor()), self.fs)
553    }
554
555    /// Returns `Dir` struct for this entry.
556    ///
557    /// Panics if this is not a directory.
558    pub fn to_dir(&self) -> Dir<'a, T> {
559        assert!(self.is_dir(), "Not a directory entry");
560        match self.first_cluster() {
561            Some(n) => {
562                let file = File::new(Some(n), Some(self.editor()), self.fs);
563                Dir::new(DirRawStream::File(file), self.fs)
564            },
565            None => self.fs.root_dir(),
566        }
567    }
568
569    /// Returns file size or 0 for directory.
570    pub fn len(&self) -> u64 {
571        self.data.size as u64
572    }
573
574    /// Returns file creation date and time.
575    ///
576    /// Resolution of the time field is 1/100s.
577    pub fn created(&self) -> DateTime {
578        self.data.created()
579    }
580
581    /// Returns file last access date.
582    pub fn accessed(&self) -> Date {
583        self.data.accessed()
584    }
585
586    /// Returns file last modification date and time.
587    ///
588    /// Resolution of the time field is 2s.
589    pub fn modified(&self) -> DateTime {
590        self.data.modified()
591    }
592
593    pub(crate) fn raw_short_name(&self) -> &[u8; 11] {
594        &self.data.name
595    }
596
597    #[cfg(feature = "alloc")]
598    pub(crate) fn eq_name(&self, name: &str) -> bool {
599        let self_name = self.file_name();
600        let self_name_lowercase_iter = self_name.chars().flat_map(|c| c.to_uppercase());
601        let other_name_lowercase_iter = name.chars().flat_map(|c| c.to_uppercase());
602        let long_name_matches = self_name_lowercase_iter.eq(other_name_lowercase_iter);
603        let short_name_matches = self.short_name.eq_ignore_case(name, self.fs.options.oem_cp_converter);
604        long_name_matches || short_name_matches
605    }
606    #[cfg(not(feature = "alloc"))]
607    pub(crate) fn eq_name(&self, name: &str) -> bool {
608        self.short_name.eq_ignore_case(name, self.fs.options.oem_cp_converter)
609    }
610}
611
612impl<'a, T: ReadWriteSeek> fmt::Debug for DirEntry<'a, T> {
613    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
614        self.data.fmt(f)
615    }
616}
617
618#[cfg(test)]
619mod tests {
620    use super::*;
621    use fs::LOSSY_OEM_CP_CONVERTER;
622
623    #[test]
624    fn short_name_with_ext() {
625        let mut raw_short_name = [0u8; 11];
626        raw_short_name.copy_from_slice(b"FOO     BAR");
627        assert_eq!(ShortName::new(&raw_short_name).to_string(&LOSSY_OEM_CP_CONVERTER), "FOO.BAR");
628        raw_short_name.copy_from_slice(b"LOOK AT M E");
629        assert_eq!(ShortName::new(&raw_short_name).to_string(&LOSSY_OEM_CP_CONVERTER), "LOOK AT.M E");
630        raw_short_name[0] = 0x99;
631        raw_short_name[10] = 0x99;
632        assert_eq!(ShortName::new(&raw_short_name).to_string(&LOSSY_OEM_CP_CONVERTER), "\u{FFFD}OOK AT.M \u{FFFD}");
633        assert_eq!(
634            ShortName::new(&raw_short_name).eq_ignore_case("\u{FFFD}OOK AT.M \u{FFFD}", &LOSSY_OEM_CP_CONVERTER),
635            true
636        );
637    }
638
639    #[test]
640    fn short_name_without_ext() {
641        let mut raw_short_name = [0u8; 11];
642        raw_short_name.copy_from_slice(b"FOO        ");
643        assert_eq!(ShortName::new(&raw_short_name).to_string(&LOSSY_OEM_CP_CONVERTER), "FOO");
644        raw_short_name.copy_from_slice(b"LOOK AT    ");
645        assert_eq!(ShortName::new(&raw_short_name).to_string(&LOSSY_OEM_CP_CONVERTER), "LOOK AT");
646    }
647
648    #[test]
649    fn short_name_eq_ignore_case() {
650        let mut raw_short_name = [0u8; 11];
651        raw_short_name.copy_from_slice(b"LOOK AT M E");
652        raw_short_name[0] = 0x99;
653        raw_short_name[10] = 0x99;
654        assert_eq!(
655            ShortName::new(&raw_short_name).eq_ignore_case("\u{FFFD}OOK AT.M \u{FFFD}", &LOSSY_OEM_CP_CONVERTER),
656            true
657        );
658        assert_eq!(
659            ShortName::new(&raw_short_name).eq_ignore_case("\u{FFFD}ook AT.m \u{FFFD}", &LOSSY_OEM_CP_CONVERTER),
660            true
661        );
662    }
663
664    #[test]
665    fn short_name_05_changed_to_e5() {
666        let raw_short_name = [0x05; 11];
667        assert_eq!(
668            ShortName::new(&raw_short_name).as_bytes(),
669            [0xE5, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, b'.', 0x05, 0x05, 0x05]
670        );
671    }
672
673    #[test]
674    fn lowercase_short_name() {
675        let mut raw_short_name = [0u8; 11];
676        raw_short_name.copy_from_slice(b"FOO     RS ");
677        let mut raw_entry =
678            DirFileEntryData { name: raw_short_name, reserved_0: (1 << 3) | (1 << 4), ..Default::default() };
679        assert_eq!(raw_entry.lowercase_name().to_string(&LOSSY_OEM_CP_CONVERTER), "foo.rs");
680        raw_entry.reserved_0 = 1 << 3;
681        assert_eq!(raw_entry.lowercase_name().to_string(&LOSSY_OEM_CP_CONVERTER), "foo.RS");
682        raw_entry.reserved_0 = 1 << 4;
683        assert_eq!(raw_entry.lowercase_name().to_string(&LOSSY_OEM_CP_CONVERTER), "FOO.rs");
684        raw_entry.reserved_0 = 0;
685        assert_eq!(raw_entry.lowercase_name().to_string(&LOSSY_OEM_CP_CONVERTER), "FOO.RS");
686    }
687}