@@ -6,6 +6,7 @@ use crate::{
6
6
Bpb , Fat16Info , Fat32Info , FatSpecificInfo , FatType , InfoSector , OnDiskDirEntry ,
7
7
RESERVED_ENTRIES ,
8
8
} ,
9
+ filesystem:: FilenameError ,
9
10
trace, warn, Attributes , Block , BlockCount , BlockDevice , BlockIdx , ClusterId , DirEntry ,
10
11
DirectoryInfo , Error , ShortFileName , TimeSource , VolumeType ,
11
12
} ;
@@ -14,26 +15,121 @@ use core::convert::TryFrom;
14
15
15
16
use super :: BlockCache ;
16
17
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.
18
30
#[ cfg_attr( feature = "defmt-log" , derive( defmt:: Format ) ) ]
19
- #[ derive( Clone , PartialEq , Eq ) ]
31
+ #[ derive( PartialEq , Eq , Clone ) ]
20
32
pub struct VolumeName {
21
- data : [ u8 ; 11 ] ,
33
+ pub ( crate ) contents : [ u8 ; Self :: TOTAL_LEN ] ,
22
34
}
23
35
24
36
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
+ }
28
106
}
29
107
}
30
108
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
+ }
36
125
}
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 )
37
133
}
38
134
}
39
135
@@ -498,7 +594,7 @@ impl FatVolume {
498
594
// Can quit early
499
595
return Ok ( ( ) ) ;
500
596
} else if dir_entry. is_valid ( ) && !dir_entry. is_lfn ( ) {
501
- // Safe, since Block::LEN always fits on a u32
597
+ // Block::LEN always fits on a u32
502
598
let start = ( i * OnDiskDirEntry :: LEN ) as u32 ;
503
599
let entry = dir_entry. get_entry ( FatType :: Fat16 , block_idx, start) ;
504
600
func ( & entry) ;
@@ -554,7 +650,7 @@ impl FatVolume {
554
650
// Can quit early
555
651
return Ok ( ( ) ) ;
556
652
} else if dir_entry. is_valid ( ) && !dir_entry. is_lfn ( ) {
557
- // Safe, since Block::LEN always fits on a u32
653
+ // Block::LEN always fits on a u32
558
654
let start = ( i * OnDiskDirEntry :: LEN ) as u32 ;
559
655
let entry = dir_entry. get_entry ( FatType :: Fat32 , block, start) ;
560
656
func ( & entry) ;
@@ -680,7 +776,7 @@ impl FatVolume {
680
776
break ;
681
777
} else if dir_entry. matches ( match_name) {
682
778
// Found it
683
- // Safe, since Block::LEN always fits on a u32
779
+ // Block::LEN always fits on a u32
684
780
let start = ( i * OnDiskDirEntry :: LEN ) as u32 ;
685
781
return Ok ( dir_entry. get_entry ( fat_type, block, start) ) ;
686
782
}
@@ -1104,10 +1200,12 @@ where
1104
1200
let first_root_dir_block =
1105
1201
fat_start + BlockCount ( u32:: from ( bpb. num_fats ( ) ) * bpb. fat_size ( ) ) ;
1106
1202
let first_data_block = first_root_dir_block + BlockCount ( root_dir_blocks) ;
1107
- let mut volume = FatVolume {
1203
+ let volume = FatVolume {
1108
1204
lba_start,
1109
1205
num_blocks,
1110
- name : VolumeName { data : [ 0u8 ; 11 ] } ,
1206
+ name : VolumeName {
1207
+ contents : bpb. volume_label ( ) ,
1208
+ } ,
1111
1209
blocks_per_cluster : bpb. blocks_per_cluster ( ) ,
1112
1210
first_data_block : ( first_data_block) ,
1113
1211
fat_start : BlockCount ( u32:: from ( bpb. reserved_block_count ( ) ) ) ,
@@ -1119,7 +1217,6 @@ where
1119
1217
first_root_dir_block,
1120
1218
} ) ,
1121
1219
} ;
1122
- volume. name . data [ ..] . copy_from_slice ( bpb. volume_label ( ) ) ;
1123
1220
Ok ( VolumeType :: Fat ( volume) )
1124
1221
}
1125
1222
FatType :: Fat32 => {
@@ -1138,10 +1235,12 @@ where
1138
1235
let info_sector =
1139
1236
InfoSector :: create_from_bytes ( info_block) . map_err ( Error :: FormatError ) ?;
1140
1237
1141
- let mut volume = FatVolume {
1238
+ let volume = FatVolume {
1142
1239
lba_start,
1143
1240
num_blocks,
1144
- name : VolumeName { data : [ 0u8 ; 11 ] } ,
1241
+ name : VolumeName {
1242
+ contents : bpb. volume_label ( ) ,
1243
+ } ,
1145
1244
blocks_per_cluster : bpb. blocks_per_cluster ( ) ,
1146
1245
first_data_block : BlockCount ( first_data_block) ,
1147
1246
fat_start : BlockCount ( u32:: from ( bpb. reserved_block_count ( ) ) ) ,
@@ -1153,12 +1252,24 @@ where
1153
1252
first_root_dir_cluster : ClusterId ( bpb. first_root_dir_cluster ( ) ) ,
1154
1253
} ) ,
1155
1254
} ;
1156
- volume. name . data [ ..] . copy_from_slice ( bpb. volume_label ( ) ) ;
1157
1255
Ok ( VolumeType :: Fat ( volume) )
1158
1256
}
1159
1257
}
1160
1258
}
1161
1259
1260
+ #[ cfg( test) ]
1261
+ mod tests {
1262
+ use super :: * ;
1263
+
1264
+ #[ test]
1265
+ fn volume_name ( ) {
1266
+ let sfn = VolumeName {
1267
+ contents : * b"Hello \xA3 99 " ,
1268
+ } ;
1269
+ assert_eq ! ( sfn, VolumeName :: create_from_str( "Hello £99" ) . unwrap( ) )
1270
+ }
1271
+ }
1272
+
1162
1273
// ****************************************************************************
1163
1274
//
1164
1275
// End Of File
0 commit comments