1use crate::core::char;
2use crate::core::iter::FromIterator;
3use crate::core::{fmt, str};
4use crate::io;
5use crate::io::prelude::*;
6use crate::io::Cursor;
7#[cfg(all(not(feature = "std"), feature = "alloc"))]
8use alloc::string::String;
9#[cfg(not(feature = "unicode"))]
10use core::iter;
11
12use crate::byteorder_ext::{ReadBytesExt, WriteBytesExt};
13use byteorder::LittleEndian;
14
15#[cfg(feature = "lfn")]
16use crate::dir::LfnBuffer;
17use crate::dir::{Dir, DirRawStream};
18use crate::file::File;
19use crate::fs::{FatType, FileSystem, OemCpConverter, ReadWriteSeek};
20use crate::time::{Date, DateTime};
21
22bitflags! {
23 #[derive(Default)]
25 pub struct FileAttributes: u8 {
26 const READ_ONLY = 0x01;
27 const HIDDEN = 0x02;
28 const SYSTEM = 0x04;
29 const VOLUME_ID = 0x08;
30 const DIRECTORY = 0x10;
31 const ARCHIVE = 0x20;
32 const LFN = Self::READ_ONLY.bits | Self::HIDDEN.bits
33 | Self::SYSTEM.bits | Self::VOLUME_ID.bits;
34 }
35}
36
37pub(crate) const DIR_ENTRY_SIZE: u64 = 32;
39
40pub(crate) const DIR_ENTRY_DELETED_FLAG: u8 = 0xE5;
42pub(crate) const DIR_ENTRY_REALLY_E5_FLAG: u8 = 0x05;
43
44pub(crate) const SFN_SIZE: usize = 11;
46
47pub(crate) const SFN_PADDING: u8 = b' ';
49
50pub(crate) const LFN_PART_LEN: usize = 13;
52
53#[cfg(feature = "lfn")]
55pub(crate) const LFN_ENTRY_LAST_FLAG: u8 = 0x40;
56
57#[cfg(feature = "unicode")]
59fn char_to_uppercase(c: char) -> char::ToUppercase {
60 c.to_uppercase()
61}
62#[cfg(not(feature = "unicode"))]
63fn char_to_uppercase(c: char) -> iter::Once<char> {
64 iter::once(c.to_ascii_uppercase())
65}
66
67#[derive(Clone, Debug, Default)]
69pub(crate) struct ShortName {
70 name: [u8; 12],
71 len: u8,
72}
73
74impl ShortName {
75 pub(crate) fn new(raw_name: &[u8; SFN_SIZE]) -> Self {
76 let name_len =
78 raw_name[0..8].iter().rposition(|x| *x != SFN_PADDING).map(|p| p + 1).unwrap_or(0);
79 let ext_len =
80 raw_name[8..11].iter().rposition(|x| *x != SFN_PADDING).map(|p| p + 1).unwrap_or(0);
81 let mut name = [SFN_PADDING; 12];
82 name[..name_len].copy_from_slice(&raw_name[..name_len]);
83 let total_len = if ext_len > 0 {
84 name[name_len] = b'.';
85 name[name_len + 1..name_len + 1 + ext_len].copy_from_slice(&raw_name[8..8 + ext_len]);
86 name_len + 1 + ext_len
88 } else {
89 name_len
91 };
92 if name[0] == DIR_ENTRY_REALLY_E5_FLAG {
94 name[0] = 0xE5;
95 }
96 ShortName { name, len: total_len as u8 }
98 }
99
100 fn as_bytes(&self) -> &[u8] {
101 &self.name[..self.len as usize]
102 }
103
104 #[cfg(feature = "alloc")]
105 fn to_string<OCC: OemCpConverter>(&self, oem_cp_converter: &OCC) -> String {
106 let char_iter = self.as_bytes().iter().cloned().map(|c| oem_cp_converter.decode(c));
108 String::from_iter(char_iter)
110 }
111
112 fn eq_ignore_case<OCC: OemCpConverter>(&self, name: &str, oem_cp_converter: &OCC) -> bool {
113 let byte_iter = self.as_bytes().iter().cloned();
115 let char_iter = byte_iter.map(|c| oem_cp_converter.decode(c));
116 let uppercase_char_iter = char_iter.flat_map(char_to_uppercase);
118 uppercase_char_iter.eq(name.chars().flat_map(char_to_uppercase))
119 }
120}
121
122#[allow(dead_code)]
123#[derive(Clone, Debug, Default)]
124pub(crate) struct DirFileEntryData {
125 name: [u8; SFN_SIZE],
126 attrs: FileAttributes,
127 reserved_0: u8,
128 create_time_0: u8,
129 create_time_1: u16,
130 create_date: u16,
131 access_date: u16,
132 first_cluster_hi: u16,
133 modify_time: u16,
134 modify_date: u16,
135 first_cluster_lo: u16,
136 size: u32,
137}
138
139impl DirFileEntryData {
140 pub(crate) fn new(name: [u8; SFN_SIZE], attrs: FileAttributes) -> Self {
141 DirFileEntryData { name, attrs, ..Default::default() }
142 }
143
144 pub(crate) fn renamed(&self, new_name: [u8; SFN_SIZE]) -> Self {
145 let mut sfn_entry = self.clone();
146 sfn_entry.name = new_name;
147 sfn_entry
148 }
149
150 pub(crate) fn name(&self) -> &[u8; SFN_SIZE] {
151 &self.name
152 }
153
154 #[cfg(feature = "alloc")]
155 fn lowercase_name(&self) -> ShortName {
156 let mut name_copy: [u8; SFN_SIZE] = self.name;
157 if self.lowercase_basename() {
158 for c in &mut name_copy[..8] {
159 *c = (*c as char).to_ascii_lowercase() as u8;
160 }
161 }
162 if self.lowercase_ext() {
163 for c in &mut name_copy[8..] {
164 *c = (*c as char).to_ascii_lowercase() as u8;
165 }
166 }
167 ShortName::new(&name_copy)
168 }
169
170 pub(crate) fn first_cluster(&self, fat_type: FatType) -> Option<u32> {
171 let first_cluster_hi = if fat_type == FatType::Fat32 { self.first_cluster_hi } else { 0 };
172 let n = ((first_cluster_hi as u32) << 16) | self.first_cluster_lo as u32;
173 if n == 0 {
174 None
175 } else {
176 Some(n)
177 }
178 }
179
180 pub(crate) fn set_first_cluster(&mut self, cluster: Option<u32>, fat_type: FatType) {
181 let n = cluster.unwrap_or(0);
182 if fat_type == FatType::Fat32 {
183 self.first_cluster_hi = (n >> 16) as u16;
184 }
185 self.first_cluster_lo = (n & 0xFFFF) as u16;
186 }
187
188 pub(crate) fn size(&self) -> Option<u32> {
189 if self.is_file() {
190 Some(self.size)
191 } else {
192 None
193 }
194 }
195
196 fn set_size(&mut self, size: u32) {
197 self.size = size;
198 }
199
200 pub(crate) fn is_dir(&self) -> bool {
201 self.attrs.contains(FileAttributes::DIRECTORY)
202 }
203
204 fn is_file(&self) -> bool {
205 !self.is_dir()
206 }
207
208 fn lowercase_basename(&self) -> bool {
209 self.reserved_0 & (1 << 3) != 0
210 }
211
212 fn lowercase_ext(&self) -> bool {
213 self.reserved_0 & (1 << 4) != 0
214 }
215
216 pub(crate) fn created(&self) -> DateTime {
217 DateTime::decode(self.create_date, self.create_time_1, self.create_time_0)
218 }
219
220 pub(crate) fn accessed(&self) -> Date {
221 Date::decode(self.access_date)
222 }
223
224 pub(crate) fn modified(&self) -> DateTime {
225 DateTime::decode(self.modify_date, self.modify_time, 0)
226 }
227
228 pub(crate) fn set_created(&mut self, date_time: DateTime) {
229 self.create_date = date_time.date.encode();
230 let encoded_time = date_time.time.encode();
231 self.create_time_1 = encoded_time.0;
232 self.create_time_0 = encoded_time.1;
233 }
234
235 pub(crate) fn set_accessed(&mut self, date: Date) {
236 self.access_date = date.encode();
237 }
238
239 pub(crate) fn set_modified(&mut self, date_time: DateTime) {
240 self.modify_date = date_time.date.encode();
241 self.modify_time = date_time.time.encode().0;
242 }
243
244 pub(crate) fn serialize<W: Write>(&self, wrt: &mut W) -> io::Result<()> {
245 wrt.write_all(&self.name)?;
246 wrt.write_u8(self.attrs.bits())?;
247 wrt.write_u8(self.reserved_0)?;
248 wrt.write_u8(self.create_time_0)?;
249 wrt.write_u16::<LittleEndian>(self.create_time_1)?;
250 wrt.write_u16::<LittleEndian>(self.create_date)?;
251 wrt.write_u16::<LittleEndian>(self.access_date)?;
252 wrt.write_u16::<LittleEndian>(self.first_cluster_hi)?;
253 wrt.write_u16::<LittleEndian>(self.modify_time)?;
254 wrt.write_u16::<LittleEndian>(self.modify_date)?;
255 wrt.write_u16::<LittleEndian>(self.first_cluster_lo)?;
256 wrt.write_u32::<LittleEndian>(self.size)?;
257 Ok(())
258 }
259
260 pub(crate) fn is_deleted(&self) -> bool {
261 self.name[0] == DIR_ENTRY_DELETED_FLAG
262 }
263
264 pub(crate) fn set_deleted(&mut self) {
265 self.name[0] = DIR_ENTRY_DELETED_FLAG;
266 }
267
268 pub(crate) fn is_end(&self) -> bool {
269 self.name[0] == 0
270 }
271
272 pub(crate) fn is_volume(&self) -> bool {
273 self.attrs.contains(FileAttributes::VOLUME_ID)
274 }
275}
276
277#[allow(dead_code)]
278#[derive(Clone, Debug, Default)]
279pub(crate) struct DirLfnEntryData {
280 order: u8,
281 name_0: [u16; 5],
282 attrs: FileAttributes,
283 entry_type: u8,
284 checksum: u8,
285 name_1: [u16; 6],
286 reserved_0: u16,
287 name_2: [u16; 2],
288}
289
290impl DirLfnEntryData {
291 pub(crate) fn new(order: u8, checksum: u8) -> Self {
292 DirLfnEntryData { order, checksum, attrs: FileAttributes::LFN, ..Default::default() }
293 }
294
295 pub(crate) fn copy_name_from_slice(&mut self, lfn_part: &[u16; LFN_PART_LEN]) {
296 self.name_0.copy_from_slice(&lfn_part[0..5]);
297 self.name_1.copy_from_slice(&lfn_part[5..5 + 6]);
298 self.name_2.copy_from_slice(&lfn_part[11..11 + 2]);
299 }
300
301 pub(crate) fn copy_name_to_slice(&self, lfn_part: &mut [u16]) {
302 debug_assert!(lfn_part.len() == LFN_PART_LEN);
303 lfn_part[0..5].copy_from_slice(&self.name_0);
304 lfn_part[5..11].copy_from_slice(&self.name_1);
305 lfn_part[11..13].copy_from_slice(&self.name_2);
306 }
307
308 pub(crate) fn serialize<W: Write>(&self, wrt: &mut W) -> io::Result<()> {
309 wrt.write_u8(self.order)?;
310 for ch in self.name_0.iter() {
311 wrt.write_u16::<LittleEndian>(*ch)?;
312 }
313 wrt.write_u8(self.attrs.bits())?;
314 wrt.write_u8(self.entry_type)?;
315 wrt.write_u8(self.checksum)?;
316 for ch in self.name_1.iter() {
317 wrt.write_u16::<LittleEndian>(*ch)?;
318 }
319 wrt.write_u16::<LittleEndian>(self.reserved_0)?;
320 for ch in self.name_2.iter() {
321 wrt.write_u16::<LittleEndian>(*ch)?;
322 }
323 Ok(())
324 }
325
326 pub(crate) fn order(&self) -> u8 {
327 self.order
328 }
329
330 pub(crate) fn checksum(&self) -> u8 {
331 self.checksum
332 }
333
334 pub(crate) fn is_deleted(&self) -> bool {
335 self.order == DIR_ENTRY_DELETED_FLAG
336 }
337
338 pub(crate) fn set_deleted(&mut self) {
339 self.order = DIR_ENTRY_DELETED_FLAG;
340 }
341
342 pub(crate) fn is_end(&self) -> bool {
343 self.order == 0
344 }
345}
346
347#[derive(Clone, Debug)]
348pub(crate) enum DirEntryData {
349 File(DirFileEntryData),
350 Lfn(DirLfnEntryData),
351}
352
353impl DirEntryData {
354 pub(crate) fn serialize<W: Write>(&self, wrt: &mut W) -> io::Result<()> {
355 match self {
356 DirEntryData::File(file) => file.serialize(wrt),
357 DirEntryData::Lfn(lfn) => lfn.serialize(wrt),
358 }
359 }
360
361 pub(crate) fn deserialize<R: Read>(rdr: &mut R) -> io::Result<Self> {
362 let mut name = [0; SFN_SIZE];
363 match rdr.read_exact(&mut name) {
364 Err(ref err) if err.kind() == io::ErrorKind::UnexpectedEof => {
365 return Ok(DirEntryData::File(DirFileEntryData { ..Default::default() }));
368 }
369 Err(err) => return Err(err),
370 _ => {}
371 }
372 let attrs = FileAttributes::from_bits_truncate(rdr.read_u8()?);
373 if attrs & FileAttributes::LFN == FileAttributes::LFN {
374 let mut data = DirLfnEntryData { attrs, ..Default::default() };
376 let mut cur = Cursor::new(&name);
378 data.order = cur.read_u8()?;
379 cur.read_u16_into::<LittleEndian>(&mut data.name_0)?;
380 data.entry_type = rdr.read_u8()?;
381 data.checksum = rdr.read_u8()?;
382 rdr.read_u16_into::<LittleEndian>(&mut data.name_1)?;
383 data.reserved_0 = rdr.read_u16::<LittleEndian>()?;
384 rdr.read_u16_into::<LittleEndian>(&mut data.name_2)?;
385 Ok(DirEntryData::Lfn(data))
386 } else {
387 let data = DirFileEntryData {
389 name,
390 attrs,
391 reserved_0: rdr.read_u8()?,
392 create_time_0: rdr.read_u8()?,
393 create_time_1: rdr.read_u16::<LittleEndian>()?,
394 create_date: rdr.read_u16::<LittleEndian>()?,
395 access_date: rdr.read_u16::<LittleEndian>()?,
396 first_cluster_hi: rdr.read_u16::<LittleEndian>()?,
397 modify_time: rdr.read_u16::<LittleEndian>()?,
398 modify_date: rdr.read_u16::<LittleEndian>()?,
399 first_cluster_lo: rdr.read_u16::<LittleEndian>()?,
400 size: rdr.read_u32::<LittleEndian>()?,
401 };
402 Ok(DirEntryData::File(data))
403 }
404 }
405
406 pub(crate) fn is_deleted(&self) -> bool {
407 match self {
408 DirEntryData::File(file) => file.is_deleted(),
409 DirEntryData::Lfn(lfn) => lfn.is_deleted(),
410 }
411 }
412
413 pub(crate) fn set_deleted(&mut self) {
414 match self {
415 DirEntryData::File(file) => file.set_deleted(),
416 DirEntryData::Lfn(lfn) => lfn.set_deleted(),
417 }
418 }
419
420 pub(crate) fn is_end(&self) -> bool {
421 match self {
422 DirEntryData::File(file) => file.is_end(),
423 DirEntryData::Lfn(lfn) => lfn.is_end(),
424 }
425 }
426}
427
428#[derive(Clone, Debug)]
429pub(crate) struct DirEntryEditor {
430 data: DirFileEntryData,
431 pos: u64,
433 pub(crate) offset_range: (u64, u64),
435 dirty: bool,
436}
437
438impl DirEntryEditor {
439 fn new(data: DirFileEntryData, pos: u64, offset_range: (u64, u64)) -> Self {
440 DirEntryEditor { data, pos, offset_range, dirty: false }
441 }
442
443 pub(crate) fn inner(&self) -> &DirFileEntryData {
444 &self.data
445 }
446
447 pub(crate) fn mark_deleted(&mut self) {
449 assert!(!self.data.is_deleted());
450 self.data.set_deleted();
451 self.dirty = false;
452 }
453
454 pub(crate) fn set_first_cluster(&mut self, first_cluster: Option<u32>, fat_type: FatType) {
455 if first_cluster != self.data.first_cluster(fat_type) {
456 self.data.set_first_cluster(first_cluster, fat_type);
457 self.maybe_set_dirty();
458 }
459 }
460
461 pub(crate) fn set_size(&mut self, size: u32) {
462 match self.data.size() {
463 Some(n) if size != n => {
464 self.data.set_size(size);
465 self.maybe_set_dirty();
466 }
467 _ => {}
468 }
469 }
470
471 pub(crate) fn set_created(&mut self, date_time: DateTime) {
472 if date_time != self.data.created() {
473 self.data.set_created(date_time);
474 self.maybe_set_dirty();
475 }
476 }
477
478 pub(crate) fn set_accessed(&mut self, date: Date) {
479 if date != self.data.accessed() {
480 self.data.set_accessed(date);
481 self.maybe_set_dirty();
482 }
483 }
484
485 pub(crate) fn set_modified(&mut self, date_time: DateTime) {
486 if date_time != self.data.modified() {
487 self.data.set_modified(date_time);
488 self.maybe_set_dirty();
489 }
490 }
491
492 pub(crate) fn flush<IO: ReadWriteSeek, TP, OCC>(
493 &mut self,
494 fs: &FileSystem<IO, TP, OCC>,
495 ) -> io::Result<()> {
496 if self.dirty {
497 self.write(fs)?;
498 self.dirty = false;
499 }
500 Ok(())
501 }
502
503 fn maybe_set_dirty(&mut self) {
505 if !self.data.is_deleted() {
506 self.dirty = true;
507 }
508 }
509
510 fn write<IO: ReadWriteSeek, TP, OCC>(&self, fs: &FileSystem<IO, TP, OCC>) -> io::Result<()> {
511 let mut disk = fs.disk.borrow_mut();
512 disk.seek(io::SeekFrom::Start(self.pos))?;
513 self.data.serialize(&mut *disk)
514 }
515}
516
517#[derive(Clone)]
521pub struct DirEntry<'a, IO: ReadWriteSeek, TP, OCC> {
522 pub(crate) data: DirFileEntryData,
523 pub(crate) short_name: ShortName,
524 #[cfg(feature = "lfn")]
525 pub(crate) lfn_utf16: LfnBuffer,
526 pub(crate) entry_pos: u64,
527 pub(crate) offset_range: (u64, u64),
528 pub(crate) fs: &'a FileSystem<IO, TP, OCC>,
529}
530
531impl<'a, IO: ReadWriteSeek, TP, OCC: OemCpConverter> DirEntry<'a, IO, TP, OCC> {
532 #[cfg(feature = "alloc")]
536 pub fn short_file_name(&self) -> String {
537 self.short_name.to_string(&self.fs.options.oem_cp_converter)
538 }
539
540 pub fn short_file_name_as_bytes(&self) -> &[u8] {
544 self.short_name.as_bytes()
545 }
546
547 #[cfg(feature = "lfn")]
551 pub fn long_file_name_as_ucs2_units(&self) -> Option<&[u16]> {
552 if self.lfn_utf16.len() > 0 {
553 Some(self.lfn_utf16.as_ucs2_units())
554 } else {
555 None
556 }
557 }
558
559 #[cfg(feature = "alloc")]
561 pub fn file_name(&self) -> String {
562 #[cfg(feature = "lfn")]
563 {
564 let lfn_opt = self.long_file_name_as_ucs2_units();
565 if let Some(lfn) = lfn_opt {
566 return String::from_utf16_lossy(lfn);
567 }
568 }
569
570 self.data.lowercase_name().to_string(&self.fs.options.oem_cp_converter)
571 }
572
573 pub fn attributes(&self) -> FileAttributes {
575 self.data.attrs
576 }
577
578 pub fn is_dir(&self) -> bool {
580 self.data.is_dir()
581 }
582
583 pub fn is_file(&self) -> bool {
585 self.data.is_file()
586 }
587
588 pub(crate) fn first_cluster(&self) -> Option<u32> {
589 self.data.first_cluster(self.fs.fat_type())
590 }
591
592 pub(crate) fn editor(&self) -> DirEntryEditor {
593 DirEntryEditor::new(self.data.clone(), self.entry_pos, self.offset_range)
594 }
595
596 pub(crate) fn is_same_entry(&self, other: &DirEntry<IO, TP, OCC>) -> bool {
597 self.entry_pos == other.entry_pos
598 }
599
600 pub fn to_file(&self) -> File<'a, IO, TP, OCC> {
604 assert!(!self.is_dir(), "Not a file entry");
605 File::new(self.first_cluster(), Some(self.editor()), self.fs)
606 }
607
608 pub fn to_dir(&self) -> Dir<'a, IO, TP, OCC> {
612 assert!(self.is_dir(), "Not a directory entry");
613 match self.first_cluster() {
614 Some(n) => {
615 let file = File::new(Some(n), Some(self.editor()), self.fs);
616 Dir::new(DirRawStream::File(Some(file)), self.fs, false)
617 }
618 None => self.fs.root_dir(),
619 }
620 }
621
622 pub fn len(&self) -> u64 {
624 self.data.size as u64
625 }
626
627 pub fn created(&self) -> DateTime {
631 self.data.created()
632 }
633
634 pub fn accessed(&self) -> Date {
636 self.data.accessed()
637 }
638
639 pub fn modified(&self) -> DateTime {
643 self.data.modified()
644 }
645
646 pub(crate) fn raw_short_name(&self) -> &[u8; SFN_SIZE] {
647 &self.data.name
648 }
649
650 #[cfg(feature = "lfn")]
651 fn eq_name_lfn(&self, name: &str) -> bool {
652 if let Some(lfn) = self.long_file_name_as_ucs2_units() {
653 if char::decode_utf16(lfn.iter().cloned()).any(|r| r.is_err()) {
654 return false;
656 }
657 let self_name_uppercase_iter = char::decode_utf16(lfn.iter().cloned())
659 .map(|r| r.unwrap())
660 .flat_map(char_to_uppercase);
661 let other_name_uppercase_iter = name.chars().flat_map(char_to_uppercase);
662 if self_name_uppercase_iter.eq(other_name_uppercase_iter) {
664 return true;
665 }
666 }
667 false
668 }
669
670 pub fn eq_name(&self, name: &str) -> bool {
672 #[cfg(feature = "lfn")]
673 {
674 if self.eq_name_lfn(name) {
675 return true;
676 }
677 }
678
679 self.short_name.eq_ignore_case(name, &self.fs.options.oem_cp_converter)
680 }
681}
682
683impl<IO: ReadWriteSeek, TP, OCC> fmt::Debug for DirEntry<'_, IO, TP, OCC> {
684 fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
685 self.data.fmt(f)
686 }
687}
688
689#[cfg(test)]
690mod tests {
691 use super::*;
692 use crate::fs::LossyOemCpConverter;
693
694 #[test]
695 fn short_name_with_ext() {
696 let oem_cp_conv = LossyOemCpConverter::new();
697 assert_eq!(ShortName::new(b"FOO BAR").to_string(&oem_cp_conv), "FOO.BAR");
698 assert_eq!(ShortName::new(b"LOOK AT M E").to_string(&oem_cp_conv), "LOOK AT.M E");
699 assert_eq!(
700 ShortName::new(b"\x99OOK AT M \x99").to_string(&oem_cp_conv),
701 "\u{FFFD}OOK AT.M \u{FFFD}"
702 );
703 assert_eq!(
704 ShortName::new(b"\x99OOK AT M \x99")
705 .eq_ignore_case("\u{FFFD}OOK AT.M \u{FFFD}", &oem_cp_conv),
706 true
707 );
708 }
709
710 #[test]
711 fn short_name_without_ext() {
712 let oem_cp_conv = LossyOemCpConverter::new();
713 assert_eq!(ShortName::new(b"FOO ").to_string(&oem_cp_conv), "FOO");
714 assert_eq!(ShortName::new(&b"LOOK AT ").to_string(&oem_cp_conv), "LOOK AT");
715 }
716
717 #[test]
718 fn short_name_eq_ignore_case() {
719 let oem_cp_conv = LossyOemCpConverter::new();
720 let raw_short_name: &[u8; SFN_SIZE] = b"\x99OOK AT M \x99";
721 assert_eq!(
722 ShortName::new(raw_short_name)
723 .eq_ignore_case("\u{FFFD}OOK AT.M \u{FFFD}", &oem_cp_conv),
724 true
725 );
726 assert_eq!(
727 ShortName::new(raw_short_name)
728 .eq_ignore_case("\u{FFFD}ook AT.m \u{FFFD}", &oem_cp_conv),
729 true
730 );
731 }
732
733 #[test]
734 fn short_name_05_changed_to_e5() {
735 let raw_short_name = [0x05; SFN_SIZE];
736 assert_eq!(
737 ShortName::new(&raw_short_name).as_bytes(),
738 [0xE5, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, b'.', 0x05, 0x05, 0x05]
739 );
740 }
741
742 #[test]
743 fn lowercase_short_name() {
744 let oem_cp_conv = LossyOemCpConverter::new();
745 let raw_short_name: &[u8; SFN_SIZE] = b"FOO RS ";
746 let mut raw_entry = DirFileEntryData {
747 name: *raw_short_name,
748 reserved_0: (1 << 3) | (1 << 4),
749 ..Default::default()
750 };
751 assert_eq!(raw_entry.lowercase_name().to_string(&oem_cp_conv), "foo.rs");
752 raw_entry.reserved_0 = 1 << 3;
753 assert_eq!(raw_entry.lowercase_name().to_string(&oem_cp_conv), "foo.RS");
754 raw_entry.reserved_0 = 1 << 4;
755 assert_eq!(raw_entry.lowercase_name().to_string(&oem_cp_conv), "FOO.rs");
756 raw_entry.reserved_0 = 0;
757 assert_eq!(raw_entry.lowercase_name().to_string(&oem_cp_conv), "FOO.RS");
758 }
759}