Skip to content

Commit

Permalink
Merge pull request #144 from rust-embedded-community/more-volume-labels
Browse files Browse the repository at this point in the history
Better volume label support
  • Loading branch information
thejpster authored Oct 11, 2024
2 parents 0b860bc + 3548e14 commit 504b440
Show file tree
Hide file tree
Showing 9 changed files with 264 additions and 144 deletions.
23 changes: 18 additions & 5 deletions examples/shell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,10 +230,17 @@ impl Context {
let dir = self.resolve_existing_directory(path)?;
let mut dir = dir.to_directory(&mut self.volume_mgr);
dir.iterate_dir(|entry| {
println!(
"{:12} {:9} {} {} {:08X?} {:?}",
entry.name, entry.size, entry.ctime, entry.mtime, entry.cluster, entry.attributes
);
if !entry.attributes.is_volume() && !entry.attributes.is_lfn() {
println!(
"{:12} {:9} {} {} {:08X?} {:?}",
entry.name,
entry.size,
entry.ctime,
entry.mtime,
entry.cluster,
entry.attributes
);
}
})?;
Ok(())
}
Expand Down Expand Up @@ -310,6 +317,8 @@ impl Context {
for fragment in full_path.iterate_components().filter(|s| !s.is_empty()) {
if fragment == ".." {
s.path.pop();
} else if fragment == "." {
// do nothing
} else {
s.path.push(fragment.to_owned());
}
Expand Down Expand Up @@ -533,7 +542,11 @@ fn main() -> Result<(), Error> {
for volume_no in 0..4 {
match ctx.volume_mgr.open_raw_volume(VolumeIdx(volume_no)) {
Ok(volume) => {
println!("Volume # {}: found", Context::volume_to_letter(volume_no));
println!(
"Volume # {}: found, label: {:?}",
Context::volume_to_letter(volume_no),
ctx.volume_mgr.get_root_volume_label(volume)?
);
match ctx.volume_mgr.open_root_dir(volume) {
Ok(root_dir) => {
ctx.volumes[volume_no] = Some(VolumeState {
Expand Down
18 changes: 9 additions & 9 deletions src/fat/bpb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,23 +85,23 @@ impl<'a> Bpb<'a> {
// FAT16/FAT32 functions

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

// FAT32 only functions

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

Expand Down
8 changes: 6 additions & 2 deletions src/fat/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,11 @@ mod test {
"#;
let results = [
Expected::Short(DirEntry {
name: ShortFileName::create_from_str_mixed_case("boot").unwrap(),
name: unsafe {
VolumeName::create_from_str("boot")
.unwrap()
.to_short_filename()
},
mtime: Timestamp::from_calendar(2015, 11, 21, 19, 35, 18).unwrap(),
ctime: Timestamp::from_calendar(2015, 11, 21, 19, 35, 18).unwrap(),
attributes: Attributes::create_from_fat(Attributes::VOLUME),
Expand Down Expand Up @@ -349,7 +353,7 @@ mod test {
assert_eq!(bpb.fat_size16(), 32);
assert_eq!(bpb.total_blocks32(), 122_880);
assert_eq!(bpb.footer(), 0xAA55);
assert_eq!(bpb.volume_label(), b"boot ");
assert_eq!(bpb.volume_label(), *b"boot ");
assert_eq!(bpb.fat_size(), 32);
assert_eq!(bpb.total_blocks(), 122_880);
assert_eq!(bpb.fat_type, FatType::Fat16);
Expand Down
157 changes: 134 additions & 23 deletions src/fat/volume.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::{
Bpb, Fat16Info, Fat32Info, FatSpecificInfo, FatType, InfoSector, OnDiskDirEntry,
RESERVED_ENTRIES,
},
filesystem::FilenameError,
trace, warn, Attributes, Block, BlockCount, BlockDevice, BlockIdx, ClusterId, DirEntry,
DirectoryInfo, Error, ShortFileName, TimeSource, VolumeType,
};
Expand All @@ -14,26 +15,121 @@ use core::convert::TryFrom;

use super::BlockCache;

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

impl VolumeName {
/// Create a new VolumeName
pub fn new(data: [u8; 11]) -> VolumeName {
VolumeName { data }
const TOTAL_LEN: usize = 11;

/// Get name
pub fn name(&self) -> &[u8] {
self.contents.trim_ascii_end()
}

/// Create a new MS-DOS volume label.
pub fn create_from_str(name: &str) -> Result<VolumeName, FilenameError> {
let mut sfn = VolumeName {
contents: [b' '; Self::TOTAL_LEN],
};

let mut idx = 0;
for ch in name.chars() {
match ch {
// Microsoft say these are the invalid characters
'\u{0000}'..='\u{001F}'
| '"'
| '*'
| '+'
| ','
| '/'
| ':'
| ';'
| '<'
| '='
| '>'
| '?'
| '['
| '\\'
| ']'
| '.'
| '|' => {
return Err(FilenameError::InvalidCharacter);
}
x if x > '\u{00FF}' => {
// We only handle ISO-8859-1 which is Unicode Code Points
// \U+0000 to \U+00FF. This is above that.
return Err(FilenameError::InvalidCharacter);
}
_ => {
let b = ch as u8;
if idx < Self::TOTAL_LEN {
sfn.contents[idx] = b;
} else {
return Err(FilenameError::NameTooLong);
}
idx += 1;
}
}
}
if idx == 0 {
return Err(FilenameError::FilenameEmpty);
}
Ok(sfn)
}

/// Convert to a Short File Name
///
/// # Safety
///
/// Volume Labels can contain things that Short File Names cannot, so only
/// do this conversion if you are creating the name of a directory entry
/// with the 'Volume Label' attribute.
pub unsafe fn to_short_filename(self) -> ShortFileName {
ShortFileName {
contents: self.contents,
}
}
}

impl core::fmt::Debug for VolumeName {
fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
match core::str::from_utf8(&self.data) {
Ok(s) => write!(fmt, "{:?}", s),
Err(_e) => write!(fmt, "{:?}", &self.data),
impl core::fmt::Display for VolumeName {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
let mut printed = 0;
for &c in self.name().iter() {
// converting a byte to a codepoint means you are assuming
// ISO-8859-1 encoding, because that's how Unicode was designed.
write!(f, "{}", c as char)?;
printed += 1;
}
if let Some(mut width) = f.width() {
if width > printed {
width -= printed;
for _ in 0..width {
write!(f, "{}", f.fill())?;
}
}
}
Ok(())
}
}

impl core::fmt::Debug for VolumeName {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(f, "VolumeName(\"{}\")", self)
}
}

Expand Down Expand Up @@ -491,8 +587,8 @@ impl FatVolume {
// Can quit early
return Ok(());
} else if dir_entry.is_valid() && !dir_entry.is_lfn() {
// Safe, since Block::LEN always fits on a u32
let start = u32::try_from(start).unwrap();
// Block::LEN always fits on a u32
let start = start as u32;
let entry = dir_entry.get_entry(FatType::Fat16, block_idx, start);
func(&entry);
}
Expand Down Expand Up @@ -546,8 +642,8 @@ impl FatVolume {
// Can quit early
return Ok(());
} else if dir_entry.is_valid() && !dir_entry.is_lfn() {
// Safe, since Block::LEN always fits on a u32
let start = u32::try_from(start).unwrap();
// Block::LEN always fits on a u32
let start = start as u32;
let entry = dir_entry.get_entry(FatType::Fat32, block, start);
func(&entry);
}
Expand Down Expand Up @@ -673,8 +769,8 @@ impl FatVolume {
break;
} else if dir_entry.matches(match_name) {
// Found it
// Safe, since Block::LEN always fits on a u32
let start = u32::try_from(start).unwrap();
// Block::LEN always fits on a u32
let start = start as u32;
return Ok(dir_entry.get_entry(fat_type, block, start));
}
}
Expand Down Expand Up @@ -1091,10 +1187,12 @@ where
let first_root_dir_block =
fat_start + BlockCount(u32::from(bpb.num_fats()) * bpb.fat_size());
let first_data_block = first_root_dir_block + BlockCount(root_dir_blocks);
let mut volume = FatVolume {
let volume = FatVolume {
lba_start,
num_blocks,
name: VolumeName { data: [0u8; 11] },
name: VolumeName {
contents: bpb.volume_label(),
},
blocks_per_cluster: bpb.blocks_per_cluster(),
first_data_block: (first_data_block),
fat_start: BlockCount(u32::from(bpb.reserved_block_count())),
Expand All @@ -1106,7 +1204,6 @@ where
first_root_dir_block,
}),
};
volume.name.data[..].copy_from_slice(bpb.volume_label());
Ok(VolumeType::Fat(volume))
}
FatType::Fat32 => {
Expand All @@ -1128,10 +1225,12 @@ where
let info_sector =
InfoSector::create_from_bytes(info_block).map_err(Error::FormatError)?;

let mut volume = FatVolume {
let volume = FatVolume {
lba_start,
num_blocks,
name: VolumeName { data: [0u8; 11] },
name: VolumeName {
contents: bpb.volume_label(),
},
blocks_per_cluster: bpb.blocks_per_cluster(),
first_data_block: BlockCount(first_data_block),
fat_start: BlockCount(u32::from(bpb.reserved_block_count())),
Expand All @@ -1143,12 +1242,24 @@ where
first_root_dir_cluster: ClusterId(bpb.first_root_dir_cluster()),
}),
};
volume.name.data[..].copy_from_slice(bpb.volume_label());
Ok(VolumeType::Fat(volume))
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn volume_name() {
let sfn = VolumeName {
contents: *b"Hello \xA399 ",
};
assert_eq!(sfn, VolumeName::create_from_str("Hello £99").unwrap())
}
}

// ****************************************************************************
//
// End Of File
Expand Down
10 changes: 2 additions & 8 deletions src/filesystem/directory.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use core::convert::TryFrom;

use crate::blockdevice::BlockIdx;
use crate::fat::{FatType, OnDiskDirEntry};
use crate::filesystem::{Attributes, ClusterId, Handle, ShortFileName, Timestamp};
Expand Down Expand Up @@ -262,16 +260,12 @@ impl DirEntry {
[0u8; 2]
} else {
// Safe due to the AND operation
u16::try_from((cluster_number >> 16) & 0x0000_FFFF)
.unwrap()
.to_le_bytes()
(((cluster_number >> 16) & 0x0000_FFFF) as u16).to_le_bytes()
};
data[20..22].copy_from_slice(&cluster_hi[..]);
data[22..26].copy_from_slice(&self.mtime.serialize_to_fat()[..]);
// Safe due to the AND operation
let cluster_lo = u16::try_from(cluster_number & 0x0000_FFFF)
.unwrap()
.to_le_bytes();
let cluster_lo = ((cluster_number & 0x0000_FFFF) as u16).to_le_bytes();
data[26..28].copy_from_slice(&cluster_lo[..]);
data[28..32].copy_from_slice(&self.size.to_le_bytes()[..]);
data
Expand Down
Loading

0 comments on commit 504b440

Please sign in to comment.