Skip to content

Commit

Permalink
Merge pull request #111 from rust-embedded-community/mkdir
Browse files Browse the repository at this point in the history
Support making directories
  • Loading branch information
thejpster authored Dec 18, 2023
2 parents 85d32e6 + 43f9281 commit 3b5c026
Show file tree
Hide file tree
Showing 8 changed files with 332 additions and 43 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ The format is based on [Keep a Changelog] and this project adheres to [Semantic

* `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.
* Renamed the old types to `RawVolume`, `RawDirectory` and `RawFile`
* New method `make_dir_in_dir`
* Fixed long-standing bug that caused an integer overflow when a FAT32 directory
was longer than one cluster ([#74])

[#74]: https://github.com/rust-embedded-community/embedded-sdmmc-rs/issues/74

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

Expand Down
11 changes: 5 additions & 6 deletions examples/list_dir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,11 @@ fn list_dir(
""
}
);
if entry.attributes.is_directory() {
if entry.name != embedded_sdmmc::ShortFileName::parent_dir()
&& entry.name != embedded_sdmmc::ShortFileName::this_dir()
{
children.push(entry.name.clone());
}
if entry.attributes.is_directory()
&& entry.name != embedded_sdmmc::ShortFileName::parent_dir()
&& entry.name != embedded_sdmmc::ShortFileName::this_dir()
{
children.push(entry.name.clone());
}
})?;
for child_name in children {
Expand Down
21 changes: 19 additions & 2 deletions examples/shell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ impl Context {
println!("\thexdump <file> -> print a binary file");
println!("\tcd .. -> go up a level");
println!("\tcd <dir> -> change into <dir>");
println!("\tmkdir <dir> -> create a directory called <dir>");
println!("\tquit -> exits the program");
} else if line == "0:" {
self.current_volume = 0;
Expand All @@ -59,11 +60,17 @@ impl Context {
};
self.volume_mgr.iterate_dir(s.directory, |entry| {
println!(
"{:12} {:9} {} {:?}",
entry.name, entry.size, entry.mtime, entry.attributes
"{:12} {:9} {} {} {:X?} {:?}",
entry.name,
entry.size,
entry.ctime,
entry.mtime,
entry.cluster,
entry.attributes
);
})?;
} else if let Some(arg) = line.strip_prefix("cd ") {
let arg = arg.trim();
let Some(s) = &mut self.volumes[self.current_volume] else {
println!("This volume isn't available");
return Ok(());
Expand All @@ -77,6 +84,7 @@ impl Context {
s.path.push(arg.to_owned());
}
} else if let Some(arg) = line.strip_prefix("cat ") {
let arg = arg.trim();
let Some(s) = &mut self.volumes[self.current_volume] else {
println!("This volume isn't available");
return Ok(());
Expand All @@ -99,6 +107,7 @@ impl Context {
println!("I'm afraid that file isn't UTF-8 encoded");
}
} else if let Some(arg) = line.strip_prefix("hexdump ") {
let arg = arg.trim();
let Some(s) = &mut self.volumes[self.current_volume] else {
println!("This volume isn't available");
return Ok(());
Expand Down Expand Up @@ -136,6 +145,14 @@ impl Context {
}
println!();
}
} else if let Some(arg) = line.strip_prefix("mkdir ") {
let arg = arg.trim();
let Some(s) = &mut self.volumes[self.current_volume] else {
println!("This volume isn't available");
return Ok(());
};
// make the dir
self.volume_mgr.make_dir_in_dir(s.directory, arg)?;
} else {
println!("Unknown command {line:?} - try 'help' for help");
}
Expand Down
44 changes: 36 additions & 8 deletions src/fat/volume.rs
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ impl FatVolume {
&mut self,
block_device: &D,
time_source: &T,
dir: &DirectoryInfo,
dir_cluster: ClusterId,
name: ShortFileName,
attributes: Attributes,
) -> Result<DirEntry, Error<D::Error>>
Expand All @@ -292,12 +292,12 @@ impl FatVolume {
// a specially reserved space on disk (see
// `first_root_dir_block`). Other directories can have any size
// as they are made of regular clusters.
let mut current_cluster = Some(dir.cluster);
let mut first_dir_block_num = match dir.cluster {
let mut current_cluster = Some(dir_cluster);
let mut first_dir_block_num = match dir_cluster {
ClusterId::ROOT_DIR => self.lba_start + fat16_info.first_root_dir_block,
_ => self.cluster_to_block(dir.cluster),
_ => self.cluster_to_block(dir_cluster),
};
let dir_size = match dir.cluster {
let dir_size = match dir_cluster {
ClusterId::ROOT_DIR => {
let len_bytes =
u32::from(fat16_info.root_entries_count) * OnDiskDirEntry::LEN_U32;
Expand Down Expand Up @@ -363,11 +363,11 @@ impl FatVolume {
FatSpecificInfo::Fat32(fat32_info) => {
// All directories on FAT32 have a cluster chain but the root
// dir starts in a specified cluster.
let mut current_cluster = match dir.cluster {
let mut current_cluster = match dir_cluster {
ClusterId::ROOT_DIR => Some(fat32_info.first_root_dir_cluster),
_ => Some(dir.cluster),
_ => Some(dir_cluster),
};
let mut first_dir_block_num = self.cluster_to_block(dir.cluster);
let mut first_dir_block_num = self.cluster_to_block(dir_cluster);
let mut blocks = [Block::new()];

let dir_size = BlockCount(u32::from(self.blocks_per_cluster));
Expand Down Expand Up @@ -1006,6 +1006,34 @@ impl FatVolume {
}
Ok(())
}

/// Writes a Directory Entry to the disk
pub(crate) fn write_entry_to_disk<D>(
&self,
block_device: &D,
entry: &DirEntry,
) -> Result<(), Error<D::Error>>
where
D: BlockDevice,
{
let fat_type = match self.fat_specific_info {
FatSpecificInfo::Fat16(_) => FatType::Fat16,
FatSpecificInfo::Fat32(_) => FatType::Fat32,
};
let mut blocks = [Block::new()];
block_device
.read(&mut blocks, entry.entry_block, "read")
.map_err(Error::DeviceError)?;
let block = &mut blocks[0];

let start = usize::try_from(entry.entry_offset).map_err(|_| Error::ConversionError)?;
block[start..start + 32].copy_from_slice(&entry.serialize(fat_type)[..]);

block_device
.write(&blocks, entry.entry_block)
.map_err(Error::DeviceError)?;
Ok(())
}
}

/// Load the boot parameter block from the start of the given partition and
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,8 @@ where
InvalidOffset,
/// Disk is full
DiskFull,
/// A directory with that name already exists
DirAlreadyExists,
}

impl<E> From<E> for Error<E>
Expand Down
171 changes: 144 additions & 27 deletions src/volume_mgr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
use byteorder::{ByteOrder, LittleEndian};
use core::convert::TryFrom;

use crate::fat::{self, BlockCache, RESERVED_ENTRIES};
use crate::fat::{self, BlockCache, FatType, OnDiskDirEntry, RESERVED_ENTRIES};

use crate::filesystem::{
Attributes, ClusterId, DirEntry, DirectoryInfo, FileInfo, Mode, RawDirectory, RawFile,
Expand Down Expand Up @@ -432,8 +432,7 @@ where
match &self.open_volumes[volume_idx].volume_type {
VolumeType::Fat(fat) => {
file.entry.mtime = self.time_source.get_timestamp();
let fat_type = fat.get_fat_type();
self.write_entry_to_disk(fat_type, &file.entry)?;
fat.write_entry_to_disk(&self.block_device, &file.entry)?;
}
};

Expand Down Expand Up @@ -518,7 +517,7 @@ where
VolumeType::Fat(fat) => fat.write_new_directory_entry(
&self.block_device,
&self.time_source,
directory_info,
directory_info.cluster,
sfn,
att,
)?,
Expand Down Expand Up @@ -789,8 +788,7 @@ where
// If you have a length, you must have a cluster
assert!(file_info.entry.cluster.0 != 0);
}
let fat_type = fat.get_fat_type();
self.write_entry_to_disk(fat_type, &file_info.entry)?;
fat.write_entry_to_disk(&self.block_device, &file_info.entry)?;
}
};
}
Expand Down Expand Up @@ -866,6 +864,146 @@ where
Ok(self.open_files[file_idx].current_offset)
}

/// Create a directory in a given directory.
pub fn make_dir_in_dir<N>(
&mut self,
directory: RawDirectory,
name: N,
) -> Result<(), Error<D::Error>>
where
N: ToShortFileName,
{
// This check is load-bearing - we do an unchecked push later.
if self.open_dirs.is_full() {
return Err(Error::TooManyOpenDirs);
}

let parent_directory_idx = self.get_dir_by_id(directory)?;
let parent_directory_info = &self.open_dirs[parent_directory_idx];
let volume_id = self.open_dirs[parent_directory_idx].volume_id;
let volume_idx = self.get_volume_by_id(volume_id)?;
let volume_info = &self.open_volumes[volume_idx];
let sfn = name.to_short_filename().map_err(Error::FilenameError)?;

debug!("Creating directory '{}'", sfn);
debug!(
"Parent dir is in cluster {:?}",
parent_directory_info.cluster
);

// Does an entry exist with this name?
let maybe_dir_entry = match &volume_info.volume_type {
VolumeType::Fat(fat) => {
fat.find_directory_entry(&self.block_device, parent_directory_info, &sfn)
}
};

match maybe_dir_entry {
Ok(entry) if entry.attributes.is_directory() => {
return Err(Error::DirAlreadyExists);
}
Ok(_entry) => {
return Err(Error::FileAlreadyExists);
}
Err(Error::FileNotFound) => {
// perfect, let's make it
}
Err(e) => {
// Some other error - tell them about it
return Err(e);
}
};

let att = Attributes::create_from_fat(Attributes::DIRECTORY);

// Need mutable access for this
match &mut self.open_volumes[volume_idx].volume_type {
VolumeType::Fat(fat) => {
debug!("Making dir entry");
let mut new_dir_entry_in_parent = fat.write_new_directory_entry(
&self.block_device,
&self.time_source,
parent_directory_info.cluster,
sfn,
att,
)?;
if new_dir_entry_in_parent.cluster == ClusterId::EMPTY {
new_dir_entry_in_parent.cluster =
fat.alloc_cluster(&self.block_device, None, false)?;
// update the parent dir with the cluster of the new dir
fat.write_entry_to_disk(&self.block_device, &new_dir_entry_in_parent)?;
}
let new_dir_start_block = fat.cluster_to_block(new_dir_entry_in_parent.cluster);
debug!("Made new dir entry {:?}", new_dir_entry_in_parent);
let now = self.time_source.get_timestamp();
let fat_type = fat.get_fat_type();
// A blank block
let mut blocks = [Block::new()];
// make the "." entry
let dot_entry_in_child = DirEntry {
name: crate::ShortFileName::this_dir(),
mtime: now,
ctime: now,
attributes: att,
// point at ourselves
cluster: new_dir_entry_in_parent.cluster,
size: 0,
entry_block: new_dir_start_block,
entry_offset: 0,
};
debug!("New dir has {:?}", dot_entry_in_child);
let mut offset = 0;
blocks[0][offset..offset + OnDiskDirEntry::LEN]
.copy_from_slice(&dot_entry_in_child.serialize(fat_type)[..]);
offset += OnDiskDirEntry::LEN;
// make the ".." entry
let dot_dot_entry_in_child = DirEntry {
name: crate::ShortFileName::parent_dir(),
mtime: now,
ctime: now,
attributes: att,
// point at our parent
cluster: match fat_type {
FatType::Fat16 => {
// On FAT16, indicate parent is root using Cluster(0)
if parent_directory_info.cluster == ClusterId::ROOT_DIR {
ClusterId::EMPTY
} else {
parent_directory_info.cluster
}
}
FatType::Fat32 => parent_directory_info.cluster,
},
size: 0,
entry_block: new_dir_start_block,
entry_offset: OnDiskDirEntry::LEN_U32,
};
debug!("New dir has {:?}", dot_dot_entry_in_child);
blocks[0][offset..offset + OnDiskDirEntry::LEN]
.copy_from_slice(&dot_dot_entry_in_child.serialize(fat_type)[..]);

self.block_device
.write(&blocks, new_dir_start_block)
.map_err(Error::DeviceError)?;

// Now zero the rest of the cluster
for b in blocks[0].iter_mut() {
*b = 0;
}
for block in new_dir_start_block
.range(BlockCount(u32::from(fat.blocks_per_cluster)))
.skip(1)
{
self.block_device
.write(&blocks, block)
.map_err(Error::DeviceError)?;
}
}
};

Ok(())
}

fn get_volume_by_id(&self, volume: RawVolume) -> Result<usize, Error<D::Error>> {
for (idx, v) in self.open_volumes.iter().enumerate() {
if v.volume_id == volume {
Expand Down Expand Up @@ -928,27 +1066,6 @@ where
let available = Block::LEN - block_offset;
Ok((block_idx, block_offset, available))
}

/// Writes a Directory Entry to the disk
fn write_entry_to_disk(
&self,
fat_type: fat::FatType,
entry: &DirEntry,
) -> Result<(), Error<D::Error>> {
let mut blocks = [Block::new()];
self.block_device
.read(&mut blocks, entry.entry_block, "read")
.map_err(Error::DeviceError)?;
let block = &mut blocks[0];

let start = usize::try_from(entry.entry_offset).map_err(|_| Error::ConversionError)?;
block[start..start + 32].copy_from_slice(&entry.serialize(fat_type)[..]);

self.block_device
.write(&blocks, entry.entry_block)
.map_err(Error::DeviceError)?;
Ok(())
}
}

/// Transform mode variants (ReadWriteCreate_Or_Append) to simple modes ReadWriteAppend or
Expand Down
Loading

0 comments on commit 3b5c026

Please sign in to comment.