forked from M-Labs/rust-fatfs
e4b72836f0
Corruption happens if: * files are created in non-root directory * directory size becomes greater or equal to cluster size Creating last directory entry in the directory cluster corrupts the cluster directly preceding the directory cluster. For 512 bytes long cluster creating 8th directory entry (counted from 1) will cause first corruption. Directory entry that causes the corruption will not be updated correctly. Fixes #42
385 lines
13 KiB
Rust
385 lines
13 KiB
Rust
extern crate env_logger;
|
|
extern crate fatfs;
|
|
extern crate fscommon;
|
|
|
|
use std::fs;
|
|
use std::io;
|
|
use std::io::prelude::*;
|
|
use std::mem;
|
|
use std::str;
|
|
|
|
use fatfs::FsOptions;
|
|
use fscommon::BufStream;
|
|
|
|
const FAT12_IMG: &str = "fat12.img";
|
|
const FAT16_IMG: &str = "fat16.img";
|
|
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";
|
|
|
|
type FileSystem = fatfs::FileSystem<BufStream<fs::File>>;
|
|
|
|
fn call_with_tmp_img(f: &Fn(&str) -> (), filename: &str, test_seq: u32) {
|
|
let _ = env_logger::try_init();
|
|
let img_path = format!("{}/{}", IMG_DIR, filename);
|
|
let tmp_path = format!("{}/{}-{}", TMP_DIR, test_seq, filename);
|
|
fs::create_dir(TMP_DIR).ok();
|
|
fs::copy(&img_path, &tmp_path).unwrap();
|
|
f(tmp_path.as_str());
|
|
fs::remove_file(tmp_path).unwrap();
|
|
}
|
|
|
|
fn open_filesystem_rw(tmp_path: &str) -> FileSystem {
|
|
let file = fs::OpenOptions::new().read(true).write(true).open(&tmp_path).unwrap();
|
|
let buf_file = BufStream::new(file);
|
|
let options = FsOptions::new().update_accessed_date(true);
|
|
FileSystem::new(buf_file, options).unwrap()
|
|
}
|
|
|
|
fn call_with_fs(f: &Fn(FileSystem) -> (), filename: &str, test_seq: u32) {
|
|
let callback = |tmp_path: &str| {
|
|
let fs = open_filesystem_rw(tmp_path);
|
|
f(fs);
|
|
};
|
|
call_with_tmp_img(&callback, filename, test_seq);
|
|
}
|
|
|
|
fn test_write_short_file(fs: FileSystem) {
|
|
let root_dir = fs.root_dir();
|
|
let mut file = root_dir.open_file("short.txt").expect("open file");
|
|
file.truncate().unwrap();
|
|
file.write_all(&TEST_STR.as_bytes()).unwrap();
|
|
file.seek(io::SeekFrom::Start(0)).unwrap();
|
|
let mut buf = Vec::new();
|
|
file.read_to_end(&mut buf).unwrap();
|
|
assert_eq!(TEST_STR, str::from_utf8(&buf).unwrap());
|
|
}
|
|
|
|
#[test]
|
|
fn test_write_file_fat12() {
|
|
call_with_fs(&test_write_short_file, FAT12_IMG, 1)
|
|
}
|
|
|
|
#[test]
|
|
fn test_write_file_fat16() {
|
|
call_with_fs(&test_write_short_file, FAT16_IMG, 1)
|
|
}
|
|
|
|
#[test]
|
|
fn test_write_file_fat32() {
|
|
call_with_fs(&test_write_short_file, FAT32_IMG, 1)
|
|
}
|
|
|
|
fn test_write_long_file(fs: FileSystem) {
|
|
let root_dir = fs.root_dir();
|
|
let mut file = root_dir.open_file("long.txt").expect("open file");
|
|
file.truncate().unwrap();
|
|
let test_str = TEST_STR.repeat(1000);
|
|
file.write_all(&test_str.as_bytes()).unwrap();
|
|
file.seek(io::SeekFrom::Start(0)).unwrap();
|
|
let mut buf = Vec::new();
|
|
file.read_to_end(&mut buf).unwrap();
|
|
assert_eq!(test_str, str::from_utf8(&buf).unwrap());
|
|
file.seek(io::SeekFrom::Start(1234)).unwrap();
|
|
file.truncate().unwrap();
|
|
file.seek(io::SeekFrom::Start(0)).unwrap();
|
|
buf.clear();
|
|
file.read_to_end(&mut buf).unwrap();
|
|
assert_eq!(&test_str[..1234], str::from_utf8(&buf).unwrap());
|
|
}
|
|
|
|
#[test]
|
|
fn test_write_long_file_fat12() {
|
|
call_with_fs(&test_write_long_file, FAT12_IMG, 2)
|
|
}
|
|
|
|
#[test]
|
|
fn test_write_long_file_fat16() {
|
|
call_with_fs(&test_write_long_file, FAT16_IMG, 2)
|
|
}
|
|
|
|
#[test]
|
|
fn test_write_long_file_fat32() {
|
|
call_with_fs(&test_write_long_file, FAT32_IMG, 2)
|
|
}
|
|
|
|
fn test_remove(fs: FileSystem) {
|
|
let root_dir = fs.root_dir();
|
|
assert!(root_dir.remove("very/long/path").is_err());
|
|
let dir = root_dir.open_dir("very/long/path").unwrap();
|
|
let mut names = dir.iter().map(|r| r.unwrap().file_name()).collect::<Vec<String>>();
|
|
assert_eq!(names, [".", "..", "test.txt"]);
|
|
root_dir.remove("very/long/path/test.txt").unwrap();
|
|
names = dir.iter().map(|r| r.unwrap().file_name()).collect::<Vec<String>>();
|
|
assert_eq!(names, [".", ".."]);
|
|
assert!(root_dir.remove("very/long/path").is_ok());
|
|
|
|
names = root_dir.iter().map(|r| r.unwrap().file_name()).collect::<Vec<String>>();
|
|
assert_eq!(names, ["long.txt", "short.txt", "very", "very-long-dir-name"]);
|
|
root_dir.remove("long.txt").unwrap();
|
|
names = root_dir.iter().map(|r| r.unwrap().file_name()).collect::<Vec<String>>();
|
|
assert_eq!(names, ["short.txt", "very", "very-long-dir-name"]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_remove_fat12() {
|
|
call_with_fs(&test_remove, FAT12_IMG, 3)
|
|
}
|
|
|
|
#[test]
|
|
fn test_remove_fat16() {
|
|
call_with_fs(&test_remove, FAT16_IMG, 3)
|
|
}
|
|
|
|
#[test]
|
|
fn test_remove_fat32() {
|
|
call_with_fs(&test_remove, FAT32_IMG, 3)
|
|
}
|
|
|
|
fn test_create_file(fs: FileSystem) {
|
|
let root_dir = fs.root_dir();
|
|
let dir = root_dir.open_dir("very/long/path").unwrap();
|
|
let mut names = dir.iter().map(|r| r.unwrap().file_name()).collect::<Vec<String>>();
|
|
assert_eq!(names, [".", "..", "test.txt"]);
|
|
{
|
|
// test some invalid names
|
|
assert!(root_dir.create_file("very/long/path/:").is_err());
|
|
assert!(root_dir.create_file("very/long/path/\0").is_err());
|
|
// create file
|
|
let mut file = root_dir.create_file("very/long/path/new-file-with-long-name.txt").unwrap();
|
|
file.write_all(&TEST_STR.as_bytes()).unwrap();
|
|
}
|
|
// check for dir entry
|
|
names = dir.iter().map(|r| r.unwrap().file_name()).collect::<Vec<String>>();
|
|
assert_eq!(names, [".", "..", "test.txt", "new-file-with-long-name.txt"]);
|
|
names = dir.iter().map(|r| r.unwrap().short_file_name()).collect::<Vec<String>>();
|
|
assert_eq!(names, [".", "..", "TEST.TXT", "NEW-FI~1.TXT"]);
|
|
{
|
|
// check contents
|
|
let mut file = root_dir.open_file("very/long/path/new-file-with-long-name.txt").unwrap();
|
|
let mut content = String::new();
|
|
file.read_to_string(&mut content).unwrap();
|
|
assert_eq!(&content, &TEST_STR);
|
|
}
|
|
// Create enough entries to allocate next cluster
|
|
for i in 0..512 / 32 {
|
|
let name = format!("test{}", i);
|
|
dir.create_file(&name).unwrap();
|
|
}
|
|
names = dir.iter().map(|r| r.unwrap().file_name()).collect::<Vec<String>>();
|
|
assert_eq!(names.len(), 4 + 512 / 32);
|
|
// check creating existing file opens it
|
|
{
|
|
let mut file = root_dir.create_file("very/long/path/new-file-with-long-name.txt").unwrap();
|
|
let mut content = String::new();
|
|
file.read_to_string(&mut content).unwrap();
|
|
assert_eq!(&content, &TEST_STR);
|
|
}
|
|
// check using create_file with existing directory fails
|
|
assert!(root_dir.create_file("very").is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_create_file_fat12() {
|
|
call_with_fs(&test_create_file, FAT12_IMG, 4)
|
|
}
|
|
|
|
#[test]
|
|
fn test_create_file_fat16() {
|
|
call_with_fs(&test_create_file, FAT16_IMG, 4)
|
|
}
|
|
|
|
#[test]
|
|
fn test_create_file_fat32() {
|
|
call_with_fs(&test_create_file, FAT32_IMG, 4)
|
|
}
|
|
|
|
fn test_create_dir(fs: FileSystem) {
|
|
let root_dir = fs.root_dir();
|
|
let parent_dir = root_dir.open_dir("very/long/path").unwrap();
|
|
let mut names = parent_dir.iter().map(|r| r.unwrap().file_name()).collect::<Vec<String>>();
|
|
assert_eq!(names, [".", "..", "test.txt"]);
|
|
{
|
|
let subdir = root_dir.create_dir("very/long/path/new-dir-with-long-name").unwrap();
|
|
names = subdir.iter().map(|r| r.unwrap().file_name()).collect::<Vec<String>>();
|
|
assert_eq!(names, [".", ".."]);
|
|
}
|
|
// check if new entry is visible in parent
|
|
names = parent_dir.iter().map(|r| r.unwrap().file_name()).collect::<Vec<String>>();
|
|
assert_eq!(names, [".", "..", "test.txt", "new-dir-with-long-name"]);
|
|
{
|
|
// Check if new directory can be opened and read
|
|
let subdir = root_dir.open_dir("very/long/path/new-dir-with-long-name").unwrap();
|
|
names = subdir.iter().map(|r| r.unwrap().file_name()).collect::<Vec<String>>();
|
|
assert_eq!(names, [".", ".."]);
|
|
}
|
|
// Check if '.' is alias for new directory
|
|
{
|
|
let subdir = root_dir.open_dir("very/long/path/new-dir-with-long-name/.").unwrap();
|
|
names = subdir.iter().map(|r| r.unwrap().file_name()).collect::<Vec<String>>();
|
|
assert_eq!(names, [".", ".."]);
|
|
}
|
|
// Check if '..' is alias for parent directory
|
|
{
|
|
let subdir = root_dir.open_dir("very/long/path/new-dir-with-long-name/..").unwrap();
|
|
names = subdir.iter().map(|r| r.unwrap().file_name()).collect::<Vec<String>>();
|
|
assert_eq!(names, [".", "..", "test.txt", "new-dir-with-long-name"]);
|
|
}
|
|
// check if creating existing directory returns it
|
|
{
|
|
let subdir = root_dir.create_dir("very").unwrap();
|
|
names = subdir.iter().map(|r| r.unwrap().file_name()).collect::<Vec<String>>();
|
|
assert_eq!(names, [".", "..", "long"]);
|
|
}
|
|
// check short names validity after create_dir
|
|
{
|
|
let subdir = root_dir.create_dir("test").unwrap();
|
|
names = subdir.iter().map(|r| r.unwrap().short_file_name()).collect::<Vec<String>>();
|
|
assert_eq!(names, [".", ".."]);
|
|
}
|
|
|
|
// check using create_dir with existing file fails
|
|
assert!(root_dir.create_dir("very/long/path/test.txt").is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_create_dir_fat12() {
|
|
call_with_fs(&test_create_dir, FAT12_IMG, 5)
|
|
}
|
|
|
|
#[test]
|
|
fn test_create_dir_fat16() {
|
|
call_with_fs(&test_create_dir, FAT16_IMG, 5)
|
|
}
|
|
|
|
#[test]
|
|
fn test_create_dir_fat32() {
|
|
call_with_fs(&test_create_dir, FAT32_IMG, 5)
|
|
}
|
|
|
|
fn test_rename_file(fs: FileSystem) {
|
|
let root_dir = fs.root_dir();
|
|
let parent_dir = root_dir.open_dir("very/long/path").unwrap();
|
|
let entries = parent_dir.iter().map(|r| r.unwrap()).collect::<Vec<_>>();
|
|
let names = entries.iter().map(|r| r.file_name()).collect::<Vec<_>>();
|
|
assert_eq!(names, [".", "..", "test.txt"]);
|
|
assert_eq!(entries[2].len(), 14);
|
|
let stats = fs.stats().unwrap();
|
|
|
|
parent_dir.rename("test.txt", &parent_dir, "new-long-name.txt").unwrap();
|
|
let entries = parent_dir.iter().map(|r| r.unwrap()).collect::<Vec<_>>();
|
|
let names = entries.iter().map(|r| r.file_name()).collect::<Vec<_>>();
|
|
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", &root_dir, "moved-file.txt").unwrap();
|
|
let entries = root_dir.iter().map(|r| r.unwrap()).collect::<Vec<_>>();
|
|
let names = entries.iter().map(|r| r.file_name()).collect::<Vec<_>>();
|
|
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);
|
|
|
|
assert!(root_dir.rename("moved-file.txt", &root_dir, "short.txt").is_err());
|
|
let entries = root_dir.iter().map(|r| r.unwrap()).collect::<Vec<_>>();
|
|
let names = entries.iter().map(|r| r.file_name()).collect::<Vec<_>>();
|
|
assert_eq!(names, ["long.txt", "short.txt", "very", "very-long-dir-name", "moved-file.txt"]);
|
|
|
|
assert!(root_dir.rename("moved-file.txt", &root_dir, "moved-file.txt").is_ok());
|
|
|
|
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)
|
|
}
|
|
|
|
fn test_dirty_flag(tmp_path: &str) {
|
|
// Open filesystem, make change, and forget it - should become dirty
|
|
let fs = open_filesystem_rw(tmp_path);
|
|
let status_flags = fs.read_status_flags().unwrap();
|
|
assert_eq!(status_flags.dirty(), false);
|
|
assert_eq!(status_flags.io_error(), false);
|
|
fs.root_dir().create_file("abc.txt").unwrap();
|
|
mem::forget(fs);
|
|
// Check if volume is dirty now
|
|
let fs = open_filesystem_rw(tmp_path);
|
|
let status_flags = fs.read_status_flags().unwrap();
|
|
assert_eq!(status_flags.dirty(), true);
|
|
assert_eq!(status_flags.io_error(), false);
|
|
fs.unmount().unwrap();
|
|
// Make sure remounting does not clear the dirty flag
|
|
let fs = open_filesystem_rw(tmp_path);
|
|
let status_flags = fs.read_status_flags().unwrap();
|
|
assert_eq!(status_flags.dirty(), true);
|
|
assert_eq!(status_flags.io_error(), false);
|
|
}
|
|
|
|
#[test]
|
|
fn test_dirty_flag_fat12() {
|
|
call_with_tmp_img(&test_dirty_flag, FAT12_IMG, 7)
|
|
}
|
|
|
|
#[test]
|
|
fn test_dirty_flag_fat16() {
|
|
call_with_tmp_img(&test_dirty_flag, FAT16_IMG, 7)
|
|
}
|
|
|
|
#[test]
|
|
fn test_dirty_flag_fat32() {
|
|
call_with_tmp_img(&test_dirty_flag, FAT32_IMG, 7)
|
|
}
|
|
|
|
fn test_multiple_files_in_directory(fs: FileSystem) {
|
|
let dir = fs.root_dir().create_dir("/TMP").unwrap();
|
|
for i in 0..8 {
|
|
let name = format!("T{}.TXT", i);
|
|
let mut file = dir.create_file(&name).unwrap();
|
|
file.write_all(TEST_STR.as_bytes()).unwrap();
|
|
file.flush().unwrap();
|
|
|
|
let file = dir.iter()
|
|
.map(|r| r.unwrap())
|
|
.filter(|e| e.file_name() == name)
|
|
.next()
|
|
.unwrap();
|
|
|
|
assert_eq!(TEST_STR.len() as u64, file.len(), "Wrong file len on iteration {}", i);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_multiple_files_in_directory_fat12() {
|
|
call_with_fs(&test_multiple_files_in_directory, FAT12_IMG, 8)
|
|
}
|
|
|
|
#[test]
|
|
fn test_multiple_files_in_directory_fat16() {
|
|
call_with_fs(&test_multiple_files_in_directory, FAT16_IMG, 8)
|
|
}
|
|
|
|
#[test]
|
|
fn test_multiple_files_in_directory_fat32() {
|
|
call_with_fs(&test_multiple_files_in_directory, FAT32_IMG, 8)
|
|
}
|