Skip to content

Commit 504b440

Browse files
authored
Merge pull request #144 from rust-embedded-community/more-volume-labels
Better volume label support
2 parents 0b860bc + 3548e14 commit 504b440

File tree

9 files changed

+264
-144
lines changed

9 files changed

+264
-144
lines changed

examples/shell.rs

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -230,10 +230,17 @@ impl Context {
230230
let dir = self.resolve_existing_directory(path)?;
231231
let mut dir = dir.to_directory(&mut self.volume_mgr);
232232
dir.iterate_dir(|entry| {
233-
println!(
234-
"{:12} {:9} {} {} {:08X?} {:?}",
235-
entry.name, entry.size, entry.ctime, entry.mtime, entry.cluster, entry.attributes
236-
);
233+
if !entry.attributes.is_volume() && !entry.attributes.is_lfn() {
234+
println!(
235+
"{:12} {:9} {} {} {:08X?} {:?}",
236+
entry.name,
237+
entry.size,
238+
entry.ctime,
239+
entry.mtime,
240+
entry.cluster,
241+
entry.attributes
242+
);
243+
}
237244
})?;
238245
Ok(())
239246
}
@@ -310,6 +317,8 @@ impl Context {
310317
for fragment in full_path.iterate_components().filter(|s| !s.is_empty()) {
311318
if fragment == ".." {
312319
s.path.pop();
320+
} else if fragment == "." {
321+
// do nothing
313322
} else {
314323
s.path.push(fragment.to_owned());
315324
}
@@ -533,7 +542,11 @@ fn main() -> Result<(), Error> {
533542
for volume_no in 0..4 {
534543
match ctx.volume_mgr.open_raw_volume(VolumeIdx(volume_no)) {
535544
Ok(volume) => {
536-
println!("Volume # {}: found", Context::volume_to_letter(volume_no));
545+
println!(
546+
"Volume # {}: found, label: {:?}",
547+
Context::volume_to_letter(volume_no),
548+
ctx.volume_mgr.get_root_volume_label(volume)?
549+
);
537550
match ctx.volume_mgr.open_root_dir(volume) {
538551
Ok(root_dir) => {
539552
ctx.volumes[volume_no] = Some(VolumeState {

src/fat/bpb.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -85,23 +85,23 @@ impl<'a> Bpb<'a> {
8585
// FAT16/FAT32 functions
8686

8787
/// Get the Volume Label string for this volume
88-
pub fn volume_label(&self) -> &[u8] {
89-
if self.fat_type != FatType::Fat32 {
90-
&self.data[43..=53]
91-
} else {
92-
&self.data[71..=81]
88+
pub fn volume_label(&self) -> [u8; 11] {
89+
let mut result = [0u8; 11];
90+
match self.fat_type {
91+
FatType::Fat16 => result.copy_from_slice(&self.data[43..=53]),
92+
FatType::Fat32 => result.copy_from_slice(&self.data[71..=81]),
9393
}
94+
result
9495
}
9596

9697
// FAT32 only functions
9798

9899
/// On a FAT32 volume, return the free block count from the Info Block. On
99100
/// a FAT16 volume, returns None.
100101
pub fn fs_info_block(&self) -> Option<BlockCount> {
101-
if self.fat_type != FatType::Fat32 {
102-
None
103-
} else {
104-
Some(BlockCount(u32::from(self.fs_info())))
102+
match self.fat_type {
103+
FatType::Fat16 => None,
104+
FatType::Fat32 => Some(BlockCount(u32::from(self.fs_info()))),
105105
}
106106
}
107107

src/fat/mod.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,11 @@ mod test {
139139
"#;
140140
let results = [
141141
Expected::Short(DirEntry {
142-
name: ShortFileName::create_from_str_mixed_case("boot").unwrap(),
142+
name: unsafe {
143+
VolumeName::create_from_str("boot")
144+
.unwrap()
145+
.to_short_filename()
146+
},
143147
mtime: Timestamp::from_calendar(2015, 11, 21, 19, 35, 18).unwrap(),
144148
ctime: Timestamp::from_calendar(2015, 11, 21, 19, 35, 18).unwrap(),
145149
attributes: Attributes::create_from_fat(Attributes::VOLUME),
@@ -349,7 +353,7 @@ mod test {
349353
assert_eq!(bpb.fat_size16(), 32);
350354
assert_eq!(bpb.total_blocks32(), 122_880);
351355
assert_eq!(bpb.footer(), 0xAA55);
352-
assert_eq!(bpb.volume_label(), b"boot ");
356+
assert_eq!(bpb.volume_label(), *b"boot ");
353357
assert_eq!(bpb.fat_size(), 32);
354358
assert_eq!(bpb.total_blocks(), 122_880);
355359
assert_eq!(bpb.fat_type, FatType::Fat16);

src/fat/volume.rs

Lines changed: 134 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use crate::{
66
Bpb, Fat16Info, Fat32Info, FatSpecificInfo, FatType, InfoSector, OnDiskDirEntry,
77
RESERVED_ENTRIES,
88
},
9+
filesystem::FilenameError,
910
trace, warn, Attributes, Block, BlockCount, BlockDevice, BlockIdx, ClusterId, DirEntry,
1011
DirectoryInfo, Error, ShortFileName, TimeSource, VolumeType,
1112
};
@@ -14,26 +15,121 @@ use core::convert::TryFrom;
1415

1516
use super::BlockCache;
1617

17-
/// The name given to a particular FAT formatted volume.
18+
/// An MS-DOS 11 character volume label.
19+
///
20+
/// ISO-8859-1 encoding is assumed. Trailing spaces are trimmed. Reserved
21+
/// characters are not allowed. There is no file extension, unlike with a
22+
/// filename.
23+
///
24+
/// Volume labels can be found in the BIOS Parameter Block, and in a root
25+
/// directory entry with the 'Volume Label' bit set. Both places should have the
26+
/// same contents, but they can get out of sync.
27+
///
28+
/// MS-DOS FDISK would show you the one in the BPB, but DIR would show you the
29+
/// one in the root directory.
1830
#[cfg_attr(feature = "defmt-log", derive(defmt::Format))]
19-
#[derive(Clone, PartialEq, Eq)]
31+
#[derive(PartialEq, Eq, Clone)]
2032
pub struct VolumeName {
21-
data: [u8; 11],
33+
pub(crate) contents: [u8; Self::TOTAL_LEN],
2234
}
2335

2436
impl VolumeName {
25-
/// Create a new VolumeName
26-
pub fn new(data: [u8; 11]) -> VolumeName {
27-
VolumeName { data }
37+
const TOTAL_LEN: usize = 11;
38+
39+
/// Get name
40+
pub fn name(&self) -> &[u8] {
41+
self.contents.trim_ascii_end()
42+
}
43+
44+
/// Create a new MS-DOS volume label.
45+
pub fn create_from_str(name: &str) -> Result<VolumeName, FilenameError> {
46+
let mut sfn = VolumeName {
47+
contents: [b' '; Self::TOTAL_LEN],
48+
};
49+
50+
let mut idx = 0;
51+
for ch in name.chars() {
52+
match ch {
53+
// Microsoft say these are the invalid characters
54+
'\u{0000}'..='\u{001F}'
55+
| '"'
56+
| '*'
57+
| '+'
58+
| ','
59+
| '/'
60+
| ':'
61+
| ';'
62+
| '<'
63+
| '='
64+
| '>'
65+
| '?'
66+
| '['
67+
| '\\'
68+
| ']'
69+
| '.'
70+
| '|' => {
71+
return Err(FilenameError::InvalidCharacter);
72+
}
73+
x if x > '\u{00FF}' => {
74+
// We only handle ISO-8859-1 which is Unicode Code Points
75+
// \U+0000 to \U+00FF. This is above that.
76+
return Err(FilenameError::InvalidCharacter);
77+
}
78+
_ => {
79+
let b = ch as u8;
80+
if idx < Self::TOTAL_LEN {
81+
sfn.contents[idx] = b;
82+
} else {
83+
return Err(FilenameError::NameTooLong);
84+
}
85+
idx += 1;
86+
}
87+
}
88+
}
89+
if idx == 0 {
90+
return Err(FilenameError::FilenameEmpty);
91+
}
92+
Ok(sfn)
93+
}
94+
95+
/// Convert to a Short File Name
96+
///
97+
/// # Safety
98+
///
99+
/// Volume Labels can contain things that Short File Names cannot, so only
100+
/// do this conversion if you are creating the name of a directory entry
101+
/// with the 'Volume Label' attribute.
102+
pub unsafe fn to_short_filename(self) -> ShortFileName {
103+
ShortFileName {
104+
contents: self.contents,
105+
}
28106
}
29107
}
30108

31-
impl core::fmt::Debug for VolumeName {
32-
fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
33-
match core::str::from_utf8(&self.data) {
34-
Ok(s) => write!(fmt, "{:?}", s),
35-
Err(_e) => write!(fmt, "{:?}", &self.data),
109+
impl core::fmt::Display for VolumeName {
110+
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
111+
let mut printed = 0;
112+
for &c in self.name().iter() {
113+
// converting a byte to a codepoint means you are assuming
114+
// ISO-8859-1 encoding, because that's how Unicode was designed.
115+
write!(f, "{}", c as char)?;
116+
printed += 1;
117+
}
118+
if let Some(mut width) = f.width() {
119+
if width > printed {
120+
width -= printed;
121+
for _ in 0..width {
122+
write!(f, "{}", f.fill())?;
123+
}
124+
}
36125
}
126+
Ok(())
127+
}
128+
}
129+
130+
impl core::fmt::Debug for VolumeName {
131+
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
132+
write!(f, "VolumeName(\"{}\")", self)
37133
}
38134
}
39135

@@ -491,8 +587,8 @@ impl FatVolume {
491587
// Can quit early
492588
return Ok(());
493589
} else if dir_entry.is_valid() && !dir_entry.is_lfn() {
494-
// Safe, since Block::LEN always fits on a u32
495-
let start = u32::try_from(start).unwrap();
590+
// Block::LEN always fits on a u32
591+
let start = start as u32;
496592
let entry = dir_entry.get_entry(FatType::Fat16, block_idx, start);
497593
func(&entry);
498594
}
@@ -546,8 +642,8 @@ impl FatVolume {
546642
// Can quit early
547643
return Ok(());
548644
} else if dir_entry.is_valid() && !dir_entry.is_lfn() {
549-
// Safe, since Block::LEN always fits on a u32
550-
let start = u32::try_from(start).unwrap();
645+
// Block::LEN always fits on a u32
646+
let start = start as u32;
551647
let entry = dir_entry.get_entry(FatType::Fat32, block, start);
552648
func(&entry);
553649
}
@@ -673,8 +769,8 @@ impl FatVolume {
673769
break;
674770
} else if dir_entry.matches(match_name) {
675771
// Found it
676-
// Safe, since Block::LEN always fits on a u32
677-
let start = u32::try_from(start).unwrap();
772+
// Block::LEN always fits on a u32
773+
let start = start as u32;
678774
return Ok(dir_entry.get_entry(fat_type, block, start));
679775
}
680776
}
@@ -1091,10 +1187,12 @@ where
10911187
let first_root_dir_block =
10921188
fat_start + BlockCount(u32::from(bpb.num_fats()) * bpb.fat_size());
10931189
let first_data_block = first_root_dir_block + BlockCount(root_dir_blocks);
1094-
let mut volume = FatVolume {
1190+
let volume = FatVolume {
10951191
lba_start,
10961192
num_blocks,
1097-
name: VolumeName { data: [0u8; 11] },
1193+
name: VolumeName {
1194+
contents: bpb.volume_label(),
1195+
},
10981196
blocks_per_cluster: bpb.blocks_per_cluster(),
10991197
first_data_block: (first_data_block),
11001198
fat_start: BlockCount(u32::from(bpb.reserved_block_count())),
@@ -1106,7 +1204,6 @@ where
11061204
first_root_dir_block,
11071205
}),
11081206
};
1109-
volume.name.data[..].copy_from_slice(bpb.volume_label());
11101207
Ok(VolumeType::Fat(volume))
11111208
}
11121209
FatType::Fat32 => {
@@ -1128,10 +1225,12 @@ where
11281225
let info_sector =
11291226
InfoSector::create_from_bytes(info_block).map_err(Error::FormatError)?;
11301227

1131-
let mut volume = FatVolume {
1228+
let volume = FatVolume {
11321229
lba_start,
11331230
num_blocks,
1134-
name: VolumeName { data: [0u8; 11] },
1231+
name: VolumeName {
1232+
contents: bpb.volume_label(),
1233+
},
11351234
blocks_per_cluster: bpb.blocks_per_cluster(),
11361235
first_data_block: BlockCount(first_data_block),
11371236
fat_start: BlockCount(u32::from(bpb.reserved_block_count())),
@@ -1143,12 +1242,24 @@ where
11431242
first_root_dir_cluster: ClusterId(bpb.first_root_dir_cluster()),
11441243
}),
11451244
};
1146-
volume.name.data[..].copy_from_slice(bpb.volume_label());
11471245
Ok(VolumeType::Fat(volume))
11481246
}
11491247
}
11501248
}
11511249

1250+
#[cfg(test)]
1251+
mod tests {
1252+
use super::*;
1253+
1254+
#[test]
1255+
fn volume_name() {
1256+
let sfn = VolumeName {
1257+
contents: *b"Hello \xA399 ",
1258+
};
1259+
assert_eq!(sfn, VolumeName::create_from_str("Hello £99").unwrap())
1260+
}
1261+
}
1262+
11521263
// ****************************************************************************
11531264
//
11541265
// End Of File

src/filesystem/directory.rs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
use core::convert::TryFrom;
2-
31
use crate::blockdevice::BlockIdx;
42
use crate::fat::{FatType, OnDiskDirEntry};
53
use crate::filesystem::{Attributes, ClusterId, Handle, ShortFileName, Timestamp};
@@ -262,16 +260,12 @@ impl DirEntry {
262260
[0u8; 2]
263261
} else {
264262
// Safe due to the AND operation
265-
u16::try_from((cluster_number >> 16) & 0x0000_FFFF)
266-
.unwrap()
267-
.to_le_bytes()
263+
(((cluster_number >> 16) & 0x0000_FFFF) as u16).to_le_bytes()
268264
};
269265
data[20..22].copy_from_slice(&cluster_hi[..]);
270266
data[22..26].copy_from_slice(&self.mtime.serialize_to_fat()[..]);
271267
// Safe due to the AND operation
272-
let cluster_lo = u16::try_from(cluster_number & 0x0000_FFFF)
273-
.unwrap()
274-
.to_le_bytes();
268+
let cluster_lo = ((cluster_number & 0x0000_FFFF) as u16).to_le_bytes();
275269
data[26..28].copy_from_slice(&cluster_lo[..]);
276270
data[28..32].copy_from_slice(&self.size.to_le_bytes()[..]);
277271
data

0 commit comments

Comments
 (0)