Skip to content

Commit

Permalink
Better support for volume labels.
Browse files Browse the repository at this point in the history
They can contain spaces! But not .
  • Loading branch information
thejpster committed Oct 6, 2024
1 parent a8f1d85 commit 11f6f41
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 82 deletions.
2 changes: 1 addition & 1 deletion src/fat/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ mod test {
"#;
let results = [
Expected::Short(DirEntry {
name: ShortFileName::create_from_str_mixed_case("boot").unwrap(),
name: ShortFileName::create_volume_label_from_str("boot").unwrap(),
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
161 changes: 80 additions & 81 deletions src/filesystem/filename.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,15 @@ impl ToShortFileName for &str {
}
}

/// How filenames should be handled
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum FilenameKind {
/// Stored mixed case
VolumeLabel,
/// Converted to upper case
DirectoryEntry,
}

/// An MS-DOS 8.3 filename. 7-bit ASCII only. All lower-case is converted to
/// upper-case by default.
#[cfg_attr(feature = "defmt-log", derive(defmt::Format))]
Expand Down Expand Up @@ -82,6 +91,26 @@ impl ShortFileName {

/// Create a new MS-DOS 8.3 space-padded file name as stored in the directory entry.
pub fn create_from_str(name: &str) -> Result<ShortFileName, FilenameError> {
Self::create_from_str_inner(name, FilenameKind::DirectoryEntry)
}

/// Create a new Volume Label.
pub fn create_volume_label_from_str(name: &str) -> Result<ShortFileName, FilenameError> {
Self::create_from_str_inner(name, FilenameKind::VolumeLabel)
}

/// Create a new MS-DOS 8.3 space-padded file name as stored in the
/// directory entry.
///
/// The Unicode string must contain only code points from ISO-8859-1 (i.e.
/// Code Points \U+0000 to \U+00FF).
///
/// Convert the filename to upper case if specified by the `case_handling`
/// argument.
fn create_from_str_inner(
name: &str,
case_handling: FilenameKind,
) -> Result<ShortFileName, FilenameError> {
let mut sfn = ShortFileName {
contents: [b' '; Self::FILENAME_MAX_LEN],
};
Expand All @@ -98,92 +127,42 @@ impl ShortFileName {

let mut idx = 0;
let mut seen_dot = false;
for ch in name.bytes() {
for ch in name.chars() {
match ch {
// Microsoft say these are the invalid characters
0x00..=0x1F
| 0x20
| 0x22
| 0x2A
| 0x2B
| 0x2C
| 0x2F
| 0x3A
| 0x3B
| 0x3C
| 0x3D
| 0x3E
| 0x3F
| 0x5B
| 0x5C
| 0x5D
| 0x7C => {
'\u{0000}'..='\u{001F}'
| '"'
| '*'
| '+'
| ','
| '/'
| ':'
| ';'
| '<'
| '='
| '>'
| '?'
| '['
| '\\'
| ']'
| '|' => {
return Err(FilenameError::InvalidCharacter);
}
// Denotes the start of the file extension
b'.' => {
if (1..=Self::FILENAME_BASE_MAX_LEN).contains(&idx) {
idx = Self::FILENAME_BASE_MAX_LEN;
seen_dot = true;
} else {
return Err(FilenameError::MisplacedPeriod);
}
}
_ => {
let ch = ch.to_ascii_uppercase();
if seen_dot {
if (Self::FILENAME_BASE_MAX_LEN..Self::FILENAME_MAX_LEN).contains(&idx) {
sfn.contents[idx] = ch;
} else {
return Err(FilenameError::NameTooLong);
}
} else if idx < Self::FILENAME_BASE_MAX_LEN {
sfn.contents[idx] = ch;
} else {
return Err(FilenameError::NameTooLong);
}
idx += 1;
' ' if case_handling == FilenameKind::DirectoryEntry => {
// can't have spaces in regular file names
return Err(FilenameError::InvalidCharacter);
}
}
}
if idx == 0 {
return Err(FilenameError::FilenameEmpty);
}
Ok(sfn)
}

/// Create a new MS-DOS 8.3 space-padded file name as stored in the directory entry.
/// Use this for volume labels with mixed case.
pub fn create_from_str_mixed_case(name: &str) -> Result<ShortFileName, FilenameError> {
let mut sfn = ShortFileName {
contents: [b' '; Self::FILENAME_MAX_LEN],
};
let mut idx = 0;
let mut seen_dot = false;
for ch in name.bytes() {
match ch {
// Microsoft say these are the invalid characters
0x00..=0x1F
| 0x20
| 0x22
| 0x2A
| 0x2B
| 0x2C
| 0x2F
| 0x3A
| 0x3B
| 0x3C
| 0x3D
| 0x3E
| 0x3F
| 0x5B
| 0x5C
| 0x5D
| 0x7C => {
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);
}
// Denotes the start of the file extension
b'.' => {
'.' if case_handling == FilenameKind::VolumeLabel => {
// not allowed
return Err(FilenameError::MisplacedPeriod);
}
'.' => {
// Denotes the start of the file extension
if (1..=Self::FILENAME_BASE_MAX_LEN).contains(&idx) {
idx = Self::FILENAME_BASE_MAX_LEN;
seen_dot = true;
Expand All @@ -192,14 +171,23 @@ impl ShortFileName {
}
}
_ => {
let b = if case_handling == FilenameKind::DirectoryEntry {
ch.to_ascii_uppercase() as u8
} else {
ch as u8
};
if seen_dot {
if (Self::FILENAME_BASE_MAX_LEN..Self::FILENAME_MAX_LEN).contains(&idx) {
sfn.contents[idx] = ch;
sfn.contents[idx] = b;
} else {
return Err(FilenameError::NameTooLong);
}
} else if idx < Self::FILENAME_BASE_MAX_LEN {
sfn.contents[idx] = ch;
sfn.contents[idx] = b;
} else if idx < Self::FILENAME_MAX_LEN
&& case_handling == FilenameKind::VolumeLabel
{
sfn.contents[idx] = b;
} else {
return Err(FilenameError::NameTooLong);
}
Expand Down Expand Up @@ -337,6 +325,17 @@ mod test {
assert!(ShortFileName::create_from_str("123456789").is_err());
assert!(ShortFileName::create_from_str("12345678.ABCD").is_err());
}

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

// ****************************************************************************
Expand Down

0 comments on commit 11f6f41

Please sign in to comment.