From 66c976bc3b068eff563b00d2793bab94d9b15c75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Harabie=C5=84?= Date: Wed, 6 Jun 2018 13:56:59 +0200 Subject: [PATCH] Add method for renaming/moving a file --- TODO.md | 1 - src/dir.rs | 80 +++++++++++++++++++++++++++++++++++++++++------- src/dir_entry.rs | 6 ++++ tests/write.rs | 50 ++++++++++++++++++++++++++++++ 4 files changed, 125 insertions(+), 12 deletions(-) diff --git a/TODO.md b/TODO.md index 732938d..a830fe9 100644 --- a/TODO.md +++ b/TODO.md @@ -1,5 +1,4 @@ TODO ==== -* move file API * format volume API * better no_std support diff --git a/src/dir.rs b/src/dir.rs index 02c523e..f44f564 100644 --- a/src/dir.rs +++ b/src/dir.rs @@ -147,7 +147,8 @@ impl <'a, 'b> Dir<'a, 'b> { match r { Err(ref err) if err.kind() == ErrorKind::NotFound => { let short_name = short_name_gen.generate()?; - Ok(self.create_entry(name, short_name, FileAttributes::from_bits_truncate(0), None)?.to_file()) + let sfn_entry = self.create_sfn_entry(short_name, FileAttributes::from_bits_truncate(0), None); + Ok(self.write_entry(name, sfn_entry)?.to_file()) }, Err(err) => Err(err), Ok(e) => Ok(e.to_file()), @@ -172,13 +173,16 @@ impl <'a, 'b> Dir<'a, 'b> { let cluster = self.fs.alloc_cluster(None)?; // create entry in parent directory let short_name = short_name_gen.generate()?; - let entry = self.create_entry(name, short_name, FileAttributes::DIRECTORY, Some(cluster))?; + let sfn_entry = self.create_sfn_entry(short_name, FileAttributes::DIRECTORY, Some(cluster)); + let entry = self.write_entry(name, sfn_entry)?; let mut dir = entry.to_dir(); // create special entries "." and ".." let dot_sfn = ShortNameGenerator::new(".").generate().unwrap(); - dir.create_entry(".", dot_sfn, FileAttributes::DIRECTORY, entry.first_cluster())?; + let sfn_entry = self.create_sfn_entry(dot_sfn, FileAttributes::DIRECTORY, entry.first_cluster()); + dir.write_entry(".", sfn_entry)?; let dotdot_sfn = ShortNameGenerator::new("..").generate().unwrap(); - dir.create_entry("..", dotdot_sfn, FileAttributes::DIRECTORY, self.stream.first_cluster())?; + let sfn_entry = self.create_sfn_entry(dotdot_sfn, FileAttributes::DIRECTORY, self.stream.first_cluster()); + dir.write_entry("..", sfn_entry)?; Ok(dir) }, Err(err) => Err(err), @@ -236,6 +240,56 @@ impl <'a, 'b> Dir<'a, 'b> { } } + /// Renames or moves existing file or directory. + /// + /// Destination directory can be cloned source directory in case of rename without moving operation. + /// Make sure there is no reference to this file (no File instance) or filesystem corruption + /// can happen. + pub fn rename(&mut self, src_path: &str, dst_dir: &mut Dir, dst_path: &str) -> io::Result<()> { + // traverse source path + let (name, rest_opt) = split_path(src_path); + if let Some(rest) = rest_opt { + let e = self.find_entry(name, None)?; + return e.to_dir().rename(rest, dst_dir, dst_path); + } + // traverse destination path + let (name, rest_opt) = split_path(dst_path); + if let Some(rest) = rest_opt { + let e = dst_dir.find_entry(name, None)?; + return self.rename(src_path, &mut e.to_dir(), rest); + } + // move/rename file + self.rename_internal(src_path, dst_dir, dst_path) + } + + fn rename_internal(&mut self, src_name: &str, dst_dir: &mut Dir, dst_name: &str) -> io::Result<()> { + trace!("moving {} to {}", src_name, dst_name); + // find existing file + let e = self.find_entry(src_name, None)?; + // free long and short name entries + let mut stream = self.stream.clone(); + stream.seek(SeekFrom::Start(e.offset_range.0 as u64))?; + let num = (e.offset_range.1 - e.offset_range.0) as usize / DIR_ENTRY_SIZE as usize; + for _ in 0..num { + let mut data = DirEntryData::deserialize(&mut stream)?; + trace!("removing LFN entry {:?}", data); + data.set_free(); + stream.seek(SeekFrom::Current(-(DIR_ENTRY_SIZE as i64)))?; + data.serialize(&mut stream)?; + } + // check if destionation filename is unused + let mut short_name_gen = ShortNameGenerator::new(dst_name); + let r = dst_dir.find_entry(dst_name, Some(&mut short_name_gen)); + if r.is_ok() { + return Err(io::Error::new(ErrorKind::AlreadyExists, "destination file already exists")) + } + // save new directory entry + let short_name = short_name_gen.generate()?; + let sfn_entry = e.data.renamed(short_name); + dst_dir.write_entry(dst_name, sfn_entry)?; + Ok(()) + } + fn find_free_entries(&mut self, num_entries: usize) -> io::Result> { let mut stream = self.stream.clone(); let mut first_free = 0; @@ -293,18 +347,22 @@ impl <'a, 'b> Dir<'a, 'b> { Ok((stream, start_pos)) } - fn create_entry(&mut self, name: &str, short_name: [u8; 11], attrs: FileAttributes, first_cluster: Option) -> io::Result> { - trace!("create_entry {}", name); - // check if name doesn't contain unsupported characters - validate_long_name(name)?; - // generate long entries - let (mut stream, start_pos) = self.create_lfn_entries(&name, &short_name)?; - // create and write short name entry + fn create_sfn_entry(&self, short_name: [u8; 11], attrs: FileAttributes, first_cluster: Option) -> DirFileEntryData { let mut raw_entry = DirFileEntryData::new(short_name, attrs); raw_entry.set_first_cluster(first_cluster, self.fs.fat_type()); raw_entry.reset_created(); raw_entry.reset_accessed(); raw_entry.reset_modified(); + raw_entry + } + + fn write_entry(&mut self, name: &str, raw_entry: DirFileEntryData) -> io::Result> { + trace!("write_entry {}", name); + // check if name doesn't contain unsupported characters + validate_long_name(name)?; + // generate long entries + let (mut stream, start_pos) = self.create_lfn_entries(&name, raw_entry.name())?; + // write short name entry raw_entry.serialize(&mut stream)?; let end_pos = stream.seek(io::SeekFrom::Current(0))?; let abs_pos = stream.abs_pos().map(|p| p - DIR_ENTRY_SIZE); diff --git a/src/dir_entry.rs b/src/dir_entry.rs index 14fd1c2..605be08 100644 --- a/src/dir_entry.rs +++ b/src/dir_entry.rs @@ -101,6 +101,12 @@ impl DirFileEntryData { } } + pub(crate) fn renamed(&self, new_name: [u8; 11]) -> Self { + let mut sfn_entry = self.clone(); + sfn_entry.name = new_name; + sfn_entry + } + pub(crate) fn name(&self) -> &[u8; 11] { &self.name } diff --git a/tests/write.rs b/tests/write.rs index 1696f15..3d5c992 100644 --- a/tests/write.rs +++ b/tests/write.rs @@ -14,6 +14,7 @@ const FAT32_IMG: &str = "fat32.img"; const IMG_DIR: &str = "resources"; const TMP_DIR: &str = "tmp"; const TEST_STR: &str = "Hi there Rust programmer!\n"; +const TEST_STR2: &str = "Rust is cool!\n"; fn call_with_fs(f: &Fn(FileSystem) -> (), filename: &str, test_seq: u32) { let _ = env_logger::try_init(); @@ -219,3 +220,52 @@ fn test_create_dir_fat16() { fn test_create_dir_fat32() { call_with_fs(&test_create_dir, FAT32_IMG, 5) } + +fn test_rename_file(fs: FileSystem) { + let mut root_dir = fs.root_dir(); + let mut parent_dir = root_dir.open_dir("very/long/path").unwrap(); + let entries = parent_dir.iter().map(|r| r.unwrap()).collect::>(); + let names = entries.iter().map(|r| r.file_name()).collect::>(); + assert_eq!(names, [".", "..", "test.txt"]); + assert_eq!(entries[2].len(), 14); + let stats = fs.stats().unwrap(); + + let mut parent_dir_cloned = parent_dir.clone(); + parent_dir.rename("test.txt", &mut parent_dir_cloned, "new-long-name.txt").unwrap(); + let entries = parent_dir.iter().map(|r| r.unwrap()).collect::>(); + let names = entries.iter().map(|r| r.file_name()).collect::>(); + assert_eq!(names, [".", "..", "new-long-name.txt"]); + assert_eq!(entries[2].len(), TEST_STR2.len() as u64); + let mut file = parent_dir.open_file("new-long-name.txt").unwrap(); + let mut buf = Vec::new(); + file.read_to_end(&mut buf).unwrap(); + assert_eq!(str::from_utf8(&buf).unwrap(), TEST_STR2); + + parent_dir.rename("new-long-name.txt", &mut root_dir, "moved-file.txt").unwrap(); + let entries = root_dir.iter().map(|r| r.unwrap()).collect::>(); + let names = entries.iter().map(|r| r.file_name()).collect::>(); + assert_eq!(names, ["long.txt", "short.txt", "very", "very-long-dir-name", "moved-file.txt"]); + assert_eq!(entries[4].len(), TEST_STR2.len() as u64); + let mut file = root_dir.open_file("moved-file.txt").unwrap(); + let mut buf = Vec::new(); + file.read_to_end(&mut buf).unwrap(); + assert_eq!(str::from_utf8(&buf).unwrap(), TEST_STR2); + + let new_stats = fs.stats().unwrap(); + assert_eq!(new_stats.free_clusters, stats.free_clusters); +} + +#[test] +fn test_rename_file_fat12() { + call_with_fs(&test_rename_file, FAT12_IMG, 6) +} + +#[test] +fn test_rename_file_fat16() { + call_with_fs(&test_rename_file, FAT16_IMG, 6) +} + +#[test] +fn test_rename_file_fat32() { + call_with_fs(&test_rename_file, FAT32_IMG, 6) +}