Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better volume label support #144

Merged
merged 4 commits into from
Oct 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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]),
thejpster marked this conversation as resolved.
Show resolved Hide resolved
}
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