diff --git a/src/dir.rs b/src/dir.rs index 24fa8be..2208405 100644 --- a/src/dir.rs +++ b/src/dir.rs @@ -1,4 +1,5 @@ use std::ascii::AsciiExt; +use std::fmt; use std::io::prelude::*; use std::io; use std::io::{ErrorKind, SeekFrom}; @@ -23,8 +24,8 @@ bitflags! { } #[allow(dead_code)] -#[derive(Clone)] -pub struct FatDirEntry { +#[derive(Clone, Copy, Debug)] +pub struct FatDirEntryData { name: [u8; 11], attrs: FatFileAttributes, reserved_0: u8, @@ -37,44 +38,38 @@ pub struct FatDirEntry { modify_date: u16, first_cluster_lo: u16, size: u32, +} + +#[derive(Clone)] +pub struct FatDirEntry { + data: FatDirEntryData, state: FatSharedStateRef, } -fn convert_date(dos_date: u16) -> Date { - let (year, month, day) = ((dos_date >> 9) + 1980, (dos_date >> 5) & 0xF, dos_date & 0x1F); - Local.ymd(year as i32, month as u32, day as u32) -} - -fn convert_date_time(dos_date: u16, dos_time: u16) -> DateTime { - let (hour, min, sec) = (dos_time >> 11, (dos_time >> 5) & 0x3F, (dos_time & 0x1F) * 2); - convert_date(dos_date).and_hms(hour as u32, min as u32, sec as u32) -} - impl FatDirEntry { - pub fn get_name(&self) -> String { - let name = str::from_utf8(&self.name[0..8]).unwrap().trim_right(); - let ext = str::from_utf8(&self.name[8..11]).unwrap().trim_right(); + let name = str::from_utf8(&self.data.name[0..8]).unwrap().trim_right(); + let ext = str::from_utf8(&self.data.name[8..11]).unwrap().trim_right(); if ext == "" { name.to_string() } else { format!("{}.{}", name, ext) } } pub fn get_attrs(&self) -> FatFileAttributes { - self.attrs + self.data.attrs } pub fn is_dir(&self) -> bool { - self.attrs.contains(FatFileAttributes::DIRECTORY) + self.data.attrs.contains(FatFileAttributes::DIRECTORY) } pub fn get_cluster(&self) -> u32 { - ((self.first_cluster_hi as u32) << 16) | self.first_cluster_lo as u32 + ((self.data.first_cluster_hi as u32) << 16) | self.data.first_cluster_lo as u32 } pub fn get_file(&self) -> FatFile { if self.is_dir() { panic!("This is a directory"); } - FatFile::new(self.get_cluster(), Some(self.size), self.state.clone()) + FatFile::new(self.get_cluster(), Some(self.data.size), self.state.clone()) } pub fn get_dir(&self) -> FatDir { @@ -86,19 +81,35 @@ impl FatDirEntry { } pub fn get_size(&self) -> u32 { - self.size + self.data.size } pub fn get_create_time(&self) -> DateTime { - convert_date_time(self.create_date, self.create_time_1) + Self::convert_date_time(self.data.create_date, self.data.create_time_1) } pub fn get_access_date(&self) -> Date { - convert_date(self.access_date) + Self::convert_date(self.data.access_date) } pub fn get_modify_time(&self) -> DateTime { - convert_date_time(self.modify_date, self.modify_time) + Self::convert_date_time(self.data.modify_date, self.data.modify_time) + } + + fn convert_date(dos_date: u16) -> Date { + let (year, month, day) = ((dos_date >> 9) + 1980, (dos_date >> 5) & 0xF, dos_date & 0x1F); + Local.ymd(year as i32, month as u32, day as u32) + } + + fn convert_date_time(dos_date: u16, dos_time: u16) -> DateTime { + let (hour, min, sec) = (dos_time >> 11, (dos_time >> 5) & 0x3F, (dos_time & 0x1F) * 2); + Self::convert_date(dos_date).and_hms(hour as u32, min as u32, sec as u32) + } +} + +impl fmt::Debug for FatDirEntry { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + self.data.fmt(f) } } @@ -122,11 +133,11 @@ impl FatDir { self.rdr.seek(SeekFrom::Start(0)).unwrap(); } - fn read_dir_entry(&mut self) -> io::Result { + fn read_dir_entry_data(&mut self) -> io::Result { let mut name = [0; 11]; self.rdr.read(&mut name)?; let attrs = FatFileAttributes::from_bits(self.rdr.read_u8()?).expect("invalid attributes"); - Ok(FatDirEntry { + Ok(FatDirEntryData { name, attrs, reserved_0: self.rdr.read_u8()?, @@ -139,7 +150,6 @@ impl FatDir { modify_date: self.rdr.read_u16::()?, first_cluster_lo: self.rdr.read_u16::()?, size: self.rdr.read_u32::()?, - state: self.state.clone(), }) } @@ -185,21 +195,24 @@ impl Iterator for FatDir { fn next(&mut self) -> Option> { loop { - let r = self.read_dir_entry(); - let e = match r { - Ok(e) => e, - Err(_) => return Some(r), + let res = self.read_dir_entry_data(); + let data = match res { + Ok(data) => data, + Err(err) => return Some(Err(err)), }; - if e.name[0] == 0 { + if data.name[0] == 0 { return None; // end of dir } - if e.name[0] == 0xE5 { + if data.name[0] == 0xE5 { continue; // deleted } - if e.attrs == FatFileAttributes::LFN { + if data.attrs == FatFileAttributes::LFN { continue; // FIXME: support LFN } - return Some(Ok(e)) + return Some(Ok(FatDirEntry { + data, + state: self.state.clone(), + })); } } } diff --git a/src/file.rs b/src/file.rs index d3d267c..6ca3eb6 100644 --- a/src/file.rs +++ b/src/file.rs @@ -5,7 +5,7 @@ use std::io; use fs::FatSharedStateRef; -#[allow(dead_code)] + pub struct FatFile { first_cluster: u32, size: Option, diff --git a/src/fs.rs b/src/fs.rs index 8ebc178..1d3c6ee 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -24,7 +24,6 @@ pub enum FatType { pub trait ReadSeek: Read + Seek {} impl ReadSeek for T where T: Read + Seek {} -#[allow(dead_code)] pub(crate) struct FatSharedState { pub rdr: Box, pub fat_type: FatType, @@ -55,13 +54,12 @@ impl FatSharedState { pub(crate) type FatSharedStateRef = Rc>; -#[allow(dead_code)] pub struct FatFileSystem { pub(crate) state: FatSharedStateRef, } #[allow(dead_code)] -#[derive(Default, Debug)] +#[derive(Default, Debug, Clone)] pub(crate) struct FatBiosParameterBlock { bytes_per_sector: u16, sectors_per_cluster: u8, @@ -115,7 +113,6 @@ impl Default for FatBootRecord { impl FatFileSystem { - //pub fn new(rdr: T) -> io::Result { pub fn new(mut rdr: Box) -> io::Result { let boot = Self::read_boot_record(&mut *rdr)?; if boot.boot_sig != [0x55, 0xAA] { @@ -124,25 +121,13 @@ impl FatFileSystem { let total_sectors = if boot.bpb.total_sectors_16 == 0 { boot.bpb.total_sectors_32 } else { boot.bpb.total_sectors_16 as u32 }; let table_size = if boot.bpb.table_size_16 == 0 { boot.bpb.table_size_32 } else { boot.bpb.table_size_16 as u32 }; - let root_dir_sectors = ((boot.bpb.root_entry_count * 32) + (boot.bpb.bytes_per_sector - 1)) / (boot.bpb.bytes_per_sector); - let first_data_sector = boot.bpb.reserved_sector_count as u32 + (boot.bpb.table_count as u32 * table_size) + root_dir_sectors as u32; + let root_dir_sectors = (((boot.bpb.root_entry_count * 32) + (boot.bpb.bytes_per_sector - 1)) / boot.bpb.bytes_per_sector) as u32; + let first_data_sector = boot.bpb.reserved_sector_count as u32 + (boot.bpb.table_count as u32 * table_size) + root_dir_sectors; let first_fat_sector = boot.bpb.reserved_sector_count as u32; let data_sectors = total_sectors - (boot.bpb.reserved_sector_count as u32 + (boot.bpb.table_count as u32 * table_size) + root_dir_sectors as u32); let total_clusters = data_sectors / boot.bpb.sectors_per_cluster as u32; let fat_type = Self::fat_type_from_clusters(total_clusters); - { - let oem_name_str = str::from_utf8(&boot.oem_name).unwrap().trim_right(); - let volume_label_str = str::from_utf8(&boot.bpb.volume_label).unwrap().trim_right(); - let fat_type_label_str = str::from_utf8(&boot.bpb.fat_type_label).unwrap().trim_right(); - - println!("fat_type {:?}", fat_type); - println!("volume_id {}", boot.bpb.volume_id); - println!("oem_name {}", oem_name_str); - println!("volume_label {}", volume_label_str); - println!("fat_type_label {}", fat_type_label_str); - } - let fat_offset = boot.bpb.reserved_sector_count * boot.bpb.bytes_per_sector; rdr.seek(SeekFrom::Start(fat_offset as u64))?; let table_size_bytes = table_size * boot.bpb.bytes_per_sector as u32; @@ -153,22 +138,44 @@ impl FatFileSystem { _ => panic!("TODO: exfat") }; - let rdr_box = Box::new(rdr); let state = FatSharedState { - rdr: rdr_box, - fat_type: fat_type, - boot: boot, - first_data_sector: first_data_sector, - first_fat_sector: first_fat_sector, - root_dir_sectors: root_dir_sectors as u32, - table: table, + rdr, + fat_type, + boot, + first_data_sector, + first_fat_sector, + root_dir_sectors, + table, }; + let state_rc = Rc::new(RefCell::new(state)); Ok(FatFileSystem { - state: Rc::new(RefCell::new(state)), + state: state_rc, }) } + pub fn get_type(&self) -> FatType { + self.state.borrow().fat_type + } + + pub fn get_volume_id(&self) -> u32 { + self.state.borrow().boot.bpb.volume_id + } + + pub fn get_volume_label(&self) -> String { + str::from_utf8(&self.state.borrow().boot.bpb.volume_label).unwrap().trim_right().to_string() + } + + pub fn root_dir(&mut self) -> FatDir { + let state = self.state.borrow(); + let root_rdr: Box = match state.fat_type { + FatType::Fat12 | FatType::Fat16 => Box::new(FatSlice::from_sectors( + state.first_data_sector - state.root_dir_sectors, state.root_dir_sectors, self.state.clone())), + _ => Box::new(FatFile::new(state.boot.bpb.root_cluster, None, self.state.clone())) // FIXME + }; + FatDir::new(root_rdr, self.state.clone()) + } + fn read_bpb(rdr: &mut Read) -> io::Result { let mut bpb: FatBiosParameterBlock = Default::default(); bpb.bytes_per_sector = rdr.read_u16::()?; @@ -239,16 +246,6 @@ impl FatFileSystem { rdr.read(&mut boot.boot_sig)?; Ok(boot) } - - pub fn root_dir(&mut self) -> FatDir { - let state = self.state.borrow(); - let root_rdr: Box = match state.fat_type { - FatType::Fat12 | FatType::Fat16 => Box::new(FatSlice::from_sectors( - state.first_data_sector - state.root_dir_sectors, state.root_dir_sectors, self.state.clone())), - _ => Box::new(FatFile::new(state.boot.bpb.root_cluster, None, self.state.clone())) // FIXME - }; - FatDir::new(root_rdr, self.state.clone()) - } } struct FatSlice { diff --git a/tests/integration-test.rs b/tests/integration-test.rs index e54c280..80e76aa 100644 --- a/tests/integration-test.rs +++ b/tests/integration-test.rs @@ -8,11 +8,17 @@ use std::str; use rustfat::FatFileSystem; const TEST_TEXT: &'static str = "Rust is cool!\n"; +const FAT12_IMG: &'static str = "resources/fat12.img"; +const FAT16_IMG: &'static str = "resources/fat16.img"; +const FAT32_IMG: &'static str = "resources/fat32.img"; -fn test_img(name: &str) { - let file = File::open(name).unwrap(); +fn open_fs(filename: &str) -> FatFileSystem { + let file = File::open(filename).unwrap(); let buf_rdr = BufReader::new(file); - let mut fs = FatFileSystem::new(Box::new(buf_rdr)).unwrap(); + FatFileSystem::new(Box::new(buf_rdr)).unwrap() +} + +fn test_root_dir(mut fs: FatFileSystem) { let mut root_dir = fs.root_dir(); let entries = root_dir.list().unwrap(); let names = entries.iter().map(|e| e.get_name()).collect::>(); @@ -21,54 +27,122 @@ fn test_img(name: &str) { let entries = root_dir.list().unwrap(); let names2 = entries.iter().map(|e| e.get_name()).collect::>(); assert_eq!(names2, names); - - { - let mut short_file = entries[1].get_file(); - let mut buf = Vec::new(); - short_file.read_to_end(&mut buf).unwrap(); - assert_eq!(str::from_utf8(&buf).unwrap(), TEST_TEXT); - - short_file.seek(SeekFrom::Start(5)).unwrap(); - buf.clear(); - short_file.read_to_end(&mut buf).unwrap(); - assert_eq!(str::from_utf8(&buf).unwrap(), &TEST_TEXT[5..]); - } - - { - let mut long_file = entries[0].get_file(); - let mut buf = Vec::new(); - long_file.read_to_end(&mut buf).unwrap(); - assert_eq!(str::from_utf8(&buf).unwrap(), TEST_TEXT.repeat(1000)); - } - - { - let mut root_dir = fs.root_dir(); - let mut dir = root_dir.get_dir("very/long/path/").unwrap(); - let entries = dir.list().unwrap(); - let names = entries.iter().map(|e| e.get_name()).collect::>(); - assert_eq!(names, [".", "..", "TEST.TXT"]); - } - - { - let mut root_dir = fs.root_dir(); - let mut file = root_dir.get_file("very/long/path/test.txt").unwrap(); - let mut buf = Vec::new(); - file.read_to_end(&mut buf).unwrap(); - assert_eq!(str::from_utf8(&buf).unwrap(), TEST_TEXT); - } } #[test] -fn fat12() { - test_img("resources/fat12.img"); +fn test_root_dir_fat12() { + test_root_dir(open_fs(FAT12_IMG)); } #[test] -fn fat16() { - test_img("resources/fat16.img"); +fn test_root_dir_fat16() { + test_root_dir(open_fs(FAT16_IMG)); } #[test] -fn fat32() { - test_img("resources/fat32.img"); +fn test_root_dir_fat32() { + test_root_dir(open_fs(FAT32_IMG)); +} + +fn test_read_seek_short_file(mut fs: FatFileSystem) { + let mut root_dir = fs.root_dir(); + let mut short_file = root_dir.get_file("short.txt").unwrap(); + let mut buf = Vec::new(); + short_file.read_to_end(&mut buf).unwrap(); + assert_eq!(str::from_utf8(&buf).unwrap(), TEST_TEXT); + + short_file.seek(SeekFrom::Start(5)).unwrap(); + let mut buf2 = [0; 5]; + short_file.read_exact(&mut buf2).unwrap(); + assert_eq!(str::from_utf8(&buf2).unwrap(), &TEST_TEXT[5..10]); +} + +#[test] +fn test_read_seek_short_file_fat12() { + test_read_seek_short_file(open_fs(FAT12_IMG)) +} + +#[test] +fn test_read_seek_short_file_fat16() { + test_read_seek_short_file(open_fs(FAT16_IMG)) +} + +#[test] +fn test_read_seek_short_file_fat32() { + test_read_seek_short_file(open_fs(FAT32_IMG)) +} + +fn test_read_long_file(mut fs: FatFileSystem) { + let mut root_dir = fs.root_dir(); + let mut long_file = root_dir.get_file("long.txt").unwrap(); + let mut buf = Vec::new(); + long_file.read_to_end(&mut buf).unwrap(); + assert_eq!(str::from_utf8(&buf).unwrap(), TEST_TEXT.repeat(1000)); + + long_file.seek(SeekFrom::Start(2017)).unwrap(); + buf.clear(); + let mut buf2 = [0; 10]; + long_file.read_exact(&mut buf2).unwrap(); + assert_eq!(str::from_utf8(&buf2).unwrap(), &TEST_TEXT.repeat(1000)[2017..2027]); +} + +#[test] +fn test_read_long_file_fat12() { + test_read_long_file(open_fs(FAT12_IMG)) +} + +#[test] +fn test_read_long_file_fat16() { + test_read_long_file(open_fs(FAT16_IMG)) +} + +#[test] +fn test_read_long_file_fat32() { + test_read_long_file(open_fs(FAT32_IMG)) +} + +fn test_get_dir_by_path(mut fs: FatFileSystem) { + let mut root_dir = fs.root_dir(); + let mut dir = root_dir.get_dir("very/long/path/").unwrap(); + let entries = dir.list().unwrap(); + let names = entries.iter().map(|e| e.get_name()).collect::>(); + assert_eq!(names, [".", "..", "TEST.TXT"]); +} + +#[test] +fn test_get_dir_by_path_fat12() { + test_get_dir_by_path(open_fs(FAT12_IMG)) +} + +#[test] +fn test_get_dir_by_path_fat16() { + test_get_dir_by_path(open_fs(FAT16_IMG)) +} + +#[test] +fn test_get_dir_by_path_fat32() { + test_get_dir_by_path(open_fs(FAT32_IMG)) +} + +fn test_get_file_by_path(mut fs: FatFileSystem) { + let mut root_dir = fs.root_dir(); + let mut file = root_dir.get_file("very/long/path/test.txt").unwrap(); + let mut buf = Vec::new(); + file.read_to_end(&mut buf).unwrap(); + assert_eq!(str::from_utf8(&buf).unwrap(), TEST_TEXT); +} + +#[test] +fn test_get_file_by_path_fat12() { + test_get_file_by_path(open_fs(FAT12_IMG)) +} + +#[test] +fn test_get_file_by_path_fat16() { + test_get_file_by_path(open_fs(FAT16_IMG)) +} + +#[test] +fn test_get_file_by_path_fat32() { + test_get_file_by_path(open_fs(FAT32_IMG)) }