Skip to content

Commit 3b5c026

Browse files
authored
Merge pull request #111 from rust-embedded-community/mkdir
Support making directories
2 parents 85d32e6 + 43f9281 commit 3b5c026

File tree

8 files changed

+332
-43
lines changed

8 files changed

+332
-43
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ The format is based on [Keep a Changelog] and this project adheres to [Semantic
88

99
* `Volume`, `Directory` and `File` are now smart! They hold references to the thing they were made from, and will clean themselves up when dropped. The trade-off is you can can't open multiple volumes, directories or files at the same time.
1010
* Renamed the old types to `RawVolume`, `RawDirectory` and `RawFile`
11+
* New method `make_dir_in_dir`
12+
* Fixed long-standing bug that caused an integer overflow when a FAT32 directory
13+
was longer than one cluster ([#74])
14+
15+
[#74]: https://github.com/rust-embedded-community/embedded-sdmmc-rs/issues/74
1116

1217
## [Version 0.6.0] - 2023-10-20
1318

examples/list_dir.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,11 @@ fn list_dir(
7676
""
7777
}
7878
);
79-
if entry.attributes.is_directory() {
80-
if entry.name != embedded_sdmmc::ShortFileName::parent_dir()
81-
&& entry.name != embedded_sdmmc::ShortFileName::this_dir()
82-
{
83-
children.push(entry.name.clone());
84-
}
79+
if entry.attributes.is_directory()
80+
&& entry.name != embedded_sdmmc::ShortFileName::parent_dir()
81+
&& entry.name != embedded_sdmmc::ShortFileName::this_dir()
82+
{
83+
children.push(entry.name.clone());
8584
}
8685
})?;
8786
for child_name in children {

examples/shell.rs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ impl Context {
4141
println!("\thexdump <file> -> print a binary file");
4242
println!("\tcd .. -> go up a level");
4343
println!("\tcd <dir> -> change into <dir>");
44+
println!("\tmkdir <dir> -> create a directory called <dir>");
4445
println!("\tquit -> exits the program");
4546
} else if line == "0:" {
4647
self.current_volume = 0;
@@ -59,11 +60,17 @@ impl Context {
5960
};
6061
self.volume_mgr.iterate_dir(s.directory, |entry| {
6162
println!(
62-
"{:12} {:9} {} {:?}",
63-
entry.name, entry.size, entry.mtime, entry.attributes
63+
"{:12} {:9} {} {} {:X?} {:?}",
64+
entry.name,
65+
entry.size,
66+
entry.ctime,
67+
entry.mtime,
68+
entry.cluster,
69+
entry.attributes
6470
);
6571
})?;
6672
} else if let Some(arg) = line.strip_prefix("cd ") {
73+
let arg = arg.trim();
6774
let Some(s) = &mut self.volumes[self.current_volume] else {
6875
println!("This volume isn't available");
6976
return Ok(());
@@ -77,6 +84,7 @@ impl Context {
7784
s.path.push(arg.to_owned());
7885
}
7986
} else if let Some(arg) = line.strip_prefix("cat ") {
87+
let arg = arg.trim();
8088
let Some(s) = &mut self.volumes[self.current_volume] else {
8189
println!("This volume isn't available");
8290
return Ok(());
@@ -99,6 +107,7 @@ impl Context {
99107
println!("I'm afraid that file isn't UTF-8 encoded");
100108
}
101109
} else if let Some(arg) = line.strip_prefix("hexdump ") {
110+
let arg = arg.trim();
102111
let Some(s) = &mut self.volumes[self.current_volume] else {
103112
println!("This volume isn't available");
104113
return Ok(());
@@ -136,6 +145,14 @@ impl Context {
136145
}
137146
println!();
138147
}
148+
} else if let Some(arg) = line.strip_prefix("mkdir ") {
149+
let arg = arg.trim();
150+
let Some(s) = &mut self.volumes[self.current_volume] else {
151+
println!("This volume isn't available");
152+
return Ok(());
153+
};
154+
// make the dir
155+
self.volume_mgr.make_dir_in_dir(s.directory, arg)?;
139156
} else {
140157
println!("Unknown command {line:?} - try 'help' for help");
141158
}

src/fat/volume.rs

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ impl FatVolume {
278278
&mut self,
279279
block_device: &D,
280280
time_source: &T,
281-
dir: &DirectoryInfo,
281+
dir_cluster: ClusterId,
282282
name: ShortFileName,
283283
attributes: Attributes,
284284
) -> Result<DirEntry, Error<D::Error>>
@@ -292,12 +292,12 @@ impl FatVolume {
292292
// a specially reserved space on disk (see
293293
// `first_root_dir_block`). Other directories can have any size
294294
// as they are made of regular clusters.
295-
let mut current_cluster = Some(dir.cluster);
296-
let mut first_dir_block_num = match dir.cluster {
295+
let mut current_cluster = Some(dir_cluster);
296+
let mut first_dir_block_num = match dir_cluster {
297297
ClusterId::ROOT_DIR => self.lba_start + fat16_info.first_root_dir_block,
298-
_ => self.cluster_to_block(dir.cluster),
298+
_ => self.cluster_to_block(dir_cluster),
299299
};
300-
let dir_size = match dir.cluster {
300+
let dir_size = match dir_cluster {
301301
ClusterId::ROOT_DIR => {
302302
let len_bytes =
303303
u32::from(fat16_info.root_entries_count) * OnDiskDirEntry::LEN_U32;
@@ -363,11 +363,11 @@ impl FatVolume {
363363
FatSpecificInfo::Fat32(fat32_info) => {
364364
// All directories on FAT32 have a cluster chain but the root
365365
// dir starts in a specified cluster.
366-
let mut current_cluster = match dir.cluster {
366+
let mut current_cluster = match dir_cluster {
367367
ClusterId::ROOT_DIR => Some(fat32_info.first_root_dir_cluster),
368-
_ => Some(dir.cluster),
368+
_ => Some(dir_cluster),
369369
};
370-
let mut first_dir_block_num = self.cluster_to_block(dir.cluster);
370+
let mut first_dir_block_num = self.cluster_to_block(dir_cluster);
371371
let mut blocks = [Block::new()];
372372

373373
let dir_size = BlockCount(u32::from(self.blocks_per_cluster));
@@ -1006,6 +1006,34 @@ impl FatVolume {
10061006
}
10071007
Ok(())
10081008
}
1009+
1010+
/// Writes a Directory Entry to the disk
1011+
pub(crate) fn write_entry_to_disk<D>(
1012+
&self,
1013+
block_device: &D,
1014+
entry: &DirEntry,
1015+
) -> Result<(), Error<D::Error>>
1016+
where
1017+
D: BlockDevice,
1018+
{
1019+
let fat_type = match self.fat_specific_info {
1020+
FatSpecificInfo::Fat16(_) => FatType::Fat16,
1021+
FatSpecificInfo::Fat32(_) => FatType::Fat32,
1022+
};
1023+
let mut blocks = [Block::new()];
1024+
block_device
1025+
.read(&mut blocks, entry.entry_block, "read")
1026+
.map_err(Error::DeviceError)?;
1027+
let block = &mut blocks[0];
1028+
1029+
let start = usize::try_from(entry.entry_offset).map_err(|_| Error::ConversionError)?;
1030+
block[start..start + 32].copy_from_slice(&entry.serialize(fat_type)[..]);
1031+
1032+
block_device
1033+
.write(&blocks, entry.entry_block)
1034+
.map_err(Error::DeviceError)?;
1035+
Ok(())
1036+
}
10091037
}
10101038

10111039
/// Load the boot parameter block from the start of the given partition and

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,8 @@ where
222222
InvalidOffset,
223223
/// Disk is full
224224
DiskFull,
225+
/// A directory with that name already exists
226+
DirAlreadyExists,
225227
}
226228

227229
impl<E> From<E> for Error<E>

src/volume_mgr.rs

Lines changed: 144 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
use byteorder::{ByteOrder, LittleEndian};
66
use core::convert::TryFrom;
77

8-
use crate::fat::{self, BlockCache, RESERVED_ENTRIES};
8+
use crate::fat::{self, BlockCache, FatType, OnDiskDirEntry, RESERVED_ENTRIES};
99

1010
use crate::filesystem::{
1111
Attributes, ClusterId, DirEntry, DirectoryInfo, FileInfo, Mode, RawDirectory, RawFile,
@@ -432,8 +432,7 @@ where
432432
match &self.open_volumes[volume_idx].volume_type {
433433
VolumeType::Fat(fat) => {
434434
file.entry.mtime = self.time_source.get_timestamp();
435-
let fat_type = fat.get_fat_type();
436-
self.write_entry_to_disk(fat_type, &file.entry)?;
435+
fat.write_entry_to_disk(&self.block_device, &file.entry)?;
437436
}
438437
};
439438

@@ -518,7 +517,7 @@ where
518517
VolumeType::Fat(fat) => fat.write_new_directory_entry(
519518
&self.block_device,
520519
&self.time_source,
521-
directory_info,
520+
directory_info.cluster,
522521
sfn,
523522
att,
524523
)?,
@@ -789,8 +788,7 @@ where
789788
// If you have a length, you must have a cluster
790789
assert!(file_info.entry.cluster.0 != 0);
791790
}
792-
let fat_type = fat.get_fat_type();
793-
self.write_entry_to_disk(fat_type, &file_info.entry)?;
791+
fat.write_entry_to_disk(&self.block_device, &file_info.entry)?;
794792
}
795793
};
796794
}
@@ -866,6 +864,146 @@ where
866864
Ok(self.open_files[file_idx].current_offset)
867865
}
868866

867+
/// Create a directory in a given directory.
868+
pub fn make_dir_in_dir<N>(
869+
&mut self,
870+
directory: RawDirectory,
871+
name: N,
872+
) -> Result<(), Error<D::Error>>
873+
where
874+
N: ToShortFileName,
875+
{
876+
// This check is load-bearing - we do an unchecked push later.
877+
if self.open_dirs.is_full() {
878+
return Err(Error::TooManyOpenDirs);
879+
}
880+
881+
let parent_directory_idx = self.get_dir_by_id(directory)?;
882+
let parent_directory_info = &self.open_dirs[parent_directory_idx];
883+
let volume_id = self.open_dirs[parent_directory_idx].volume_id;
884+
let volume_idx = self.get_volume_by_id(volume_id)?;
885+
let volume_info = &self.open_volumes[volume_idx];
886+
let sfn = name.to_short_filename().map_err(Error::FilenameError)?;
887+
888+
debug!("Creating directory '{}'", sfn);
889+
debug!(
890+
"Parent dir is in cluster {:?}",
891+
parent_directory_info.cluster
892+
);
893+
894+
// Does an entry exist with this name?
895+
let maybe_dir_entry = match &volume_info.volume_type {
896+
VolumeType::Fat(fat) => {
897+
fat.find_directory_entry(&self.block_device, parent_directory_info, &sfn)
898+
}
899+
};
900+
901+
match maybe_dir_entry {
902+
Ok(entry) if entry.attributes.is_directory() => {
903+
return Err(Error::DirAlreadyExists);
904+
}
905+
Ok(_entry) => {
906+
return Err(Error::FileAlreadyExists);
907+
}
908+
Err(Error::FileNotFound) => {
909+
// perfect, let's make it
910+
}
911+
Err(e) => {
912+
// Some other error - tell them about it
913+
return Err(e);
914+
}
915+
};
916+
917+
let att = Attributes::create_from_fat(Attributes::DIRECTORY);
918+
919+
// Need mutable access for this
920+
match &mut self.open_volumes[volume_idx].volume_type {
921+
VolumeType::Fat(fat) => {
922+
debug!("Making dir entry");
923+
let mut new_dir_entry_in_parent = fat.write_new_directory_entry(
924+
&self.block_device,
925+
&self.time_source,
926+
parent_directory_info.cluster,
927+
sfn,
928+
att,
929+
)?;
930+
if new_dir_entry_in_parent.cluster == ClusterId::EMPTY {
931+
new_dir_entry_in_parent.cluster =
932+
fat.alloc_cluster(&self.block_device, None, false)?;
933+
// update the parent dir with the cluster of the new dir
934+
fat.write_entry_to_disk(&self.block_device, &new_dir_entry_in_parent)?;
935+
}
936+
let new_dir_start_block = fat.cluster_to_block(new_dir_entry_in_parent.cluster);
937+
debug!("Made new dir entry {:?}", new_dir_entry_in_parent);
938+
let now = self.time_source.get_timestamp();
939+
let fat_type = fat.get_fat_type();
940+
// A blank block
941+
let mut blocks = [Block::new()];
942+
// make the "." entry
943+
let dot_entry_in_child = DirEntry {
944+
name: crate::ShortFileName::this_dir(),
945+
mtime: now,
946+
ctime: now,
947+
attributes: att,
948+
// point at ourselves
949+
cluster: new_dir_entry_in_parent.cluster,
950+
size: 0,
951+
entry_block: new_dir_start_block,
952+
entry_offset: 0,
953+
};
954+
debug!("New dir has {:?}", dot_entry_in_child);
955+
let mut offset = 0;
956+
blocks[0][offset..offset + OnDiskDirEntry::LEN]
957+
.copy_from_slice(&dot_entry_in_child.serialize(fat_type)[..]);
958+
offset += OnDiskDirEntry::LEN;
959+
// make the ".." entry
960+
let dot_dot_entry_in_child = DirEntry {
961+
name: crate::ShortFileName::parent_dir(),
962+
mtime: now,
963+
ctime: now,
964+
attributes: att,
965+
// point at our parent
966+
cluster: match fat_type {
967+
FatType::Fat16 => {
968+
// On FAT16, indicate parent is root using Cluster(0)
969+
if parent_directory_info.cluster == ClusterId::ROOT_DIR {
970+
ClusterId::EMPTY
971+
} else {
972+
parent_directory_info.cluster
973+
}
974+
}
975+
FatType::Fat32 => parent_directory_info.cluster,
976+
},
977+
size: 0,
978+
entry_block: new_dir_start_block,
979+
entry_offset: OnDiskDirEntry::LEN_U32,
980+
};
981+
debug!("New dir has {:?}", dot_dot_entry_in_child);
982+
blocks[0][offset..offset + OnDiskDirEntry::LEN]
983+
.copy_from_slice(&dot_dot_entry_in_child.serialize(fat_type)[..]);
984+
985+
self.block_device
986+
.write(&blocks, new_dir_start_block)
987+
.map_err(Error::DeviceError)?;
988+
989+
// Now zero the rest of the cluster
990+
for b in blocks[0].iter_mut() {
991+
*b = 0;
992+
}
993+
for block in new_dir_start_block
994+
.range(BlockCount(u32::from(fat.blocks_per_cluster)))
995+
.skip(1)
996+
{
997+
self.block_device
998+
.write(&blocks, block)
999+
.map_err(Error::DeviceError)?;
1000+
}
1001+
}
1002+
};
1003+
1004+
Ok(())
1005+
}
1006+
8691007
fn get_volume_by_id(&self, volume: RawVolume) -> Result<usize, Error<D::Error>> {
8701008
for (idx, v) in self.open_volumes.iter().enumerate() {
8711009
if v.volume_id == volume {
@@ -928,27 +1066,6 @@ where
9281066
let available = Block::LEN - block_offset;
9291067
Ok((block_idx, block_offset, available))
9301068
}
931-
932-
/// Writes a Directory Entry to the disk
933-
fn write_entry_to_disk(
934-
&self,
935-
fat_type: fat::FatType,
936-
entry: &DirEntry,
937-
) -> Result<(), Error<D::Error>> {
938-
let mut blocks = [Block::new()];
939-
self.block_device
940-
.read(&mut blocks, entry.entry_block, "read")
941-
.map_err(Error::DeviceError)?;
942-
let block = &mut blocks[0];
943-
944-
let start = usize::try_from(entry.entry_offset).map_err(|_| Error::ConversionError)?;
945-
block[start..start + 32].copy_from_slice(&entry.serialize(fat_type)[..]);
946-
947-
self.block_device
948-
.write(&blocks, entry.entry_block)
949-
.map_err(Error::DeviceError)?;
950-
Ok(())
951-
}
9521069
}
9531070

9541071
/// Transform mode variants (ReadWriteCreate_Or_Append) to simple modes ReadWriteAppend or

0 commit comments

Comments
 (0)