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