Skip to content

Commit a340b4d

Browse files
committed
Better support for volume labels.
They can contain spaces! But not full-stops.
1 parent db06494 commit a340b4d

File tree

7 files changed

+246
-124
lines changed

7 files changed

+246
-124
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: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,13 @@ 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] {
88+
pub fn volume_label(&self) -> [u8; 11] {
89+
let mut result = [0u8; 11];
8990
match self.fat_type {
90-
FatType::Fat16 => &self.data[43..=53],
91-
FatType::Fat32 => &self.data[71..=81],
91+
FatType::Fat16 => result.copy_from_slice(&self.data[43..=53]),
92+
FatType::Fat32 => result.copy_from_slice(&self.data[71..=81]),
9293
}
94+
result
9395
}
9496

9597
// FAT32 only functions

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: 128 additions & 17 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

@@ -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

0 commit comments

Comments
 (0)