diff --git a/src/fs.rs b/src/fs.rs index 19d6246..6993e2d 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -9,7 +9,7 @@ use byteorder_ext::{ReadBytesExt, WriteBytesExt}; use file::File; use dir::{DirRawStream, Dir}; use dir_entry::DIR_ENTRY_SIZE; -use table::{ClusterIterator, alloc_cluster, read_fat_flags}; +use table::{ClusterIterator, alloc_cluster, read_fat_flags, count_free_clusters}; #[cfg(all(not(feature = "std"), feature = "alloc"))] use alloc::{String, string::ToString}; @@ -283,6 +283,17 @@ impl FsOptions { } } +/// FAT filesystem statistics +#[derive(Debug, Clone, Copy)] +pub struct FileSystemStats { + /// Cluster size in bytes + pub cluster_size: u32, + /// Number of total clusters in filesystem usable for file allocation + pub total_clusters: u32, + /// Number of free clusters + pub free_clusters: u32, +} + pub(crate) type FileSystemRef<'a, 'b> = &'a FileSystem<'b>; /// FAT filesystem main struct. @@ -430,6 +441,7 @@ impl <'a> FileSystem<'a> { Ok(cluster) } + /// Returns status flags for this volume. pub fn read_status_flags(&self) -> io::Result { let bpb_status = self.bpb.status_flags(); let fat_status = read_fat_flags(&mut self.fat_slice(), self.fat_type)?; @@ -438,6 +450,25 @@ impl <'a> FileSystem<'a> { io_error: bpb_status.io_error || fat_status.io_error, }) } + + /// Returns filesystem statistics like number of total and free clusters. + /// + /// For FAT32 volumes number of free clusters from FSInfo sector is returned (may be incorrect). + /// For other filesystems number is computed on each call (scans entire FAT so it takes more time). + pub fn stats(&self) -> io::Result { + let free_clusters = match self.fs_info.borrow().free_cluster_count { + Some(n) => n, + _ => { + let mut fat = self.fat_slice(); + count_free_clusters(&mut fat, self.fat_type, self.total_clusters)? + } + }; + Ok(FileSystemStats { + cluster_size: self.cluster_size(), + total_clusters: self.total_clusters, + free_clusters, + }) + } } #[derive(Clone)] diff --git a/src/table.rs b/src/table.rs index cf997af..cac50b4 100644 --- a/src/table.rs +++ b/src/table.rs @@ -24,10 +24,11 @@ enum FatValue { } trait FatTrait { + fn get_raw(fat: &mut ReadSeek, cluster: u32) -> io::Result; fn get(fat: &mut ReadSeek, cluster: u32) -> io::Result; fn set(fat: &mut DiskSlice, cluster: u32, value: FatValue) -> io::Result<()>; fn find_free(fat: &mut ReadSeek, start_cluster: u32, end_cluster: u32) -> io::Result; - fn get_raw(fat: &mut ReadSeek, cluster: u32) -> io::Result; + fn count_free(fat: &mut ReadSeek, end_cluster: u32) -> io::Result; } fn read_fat(fat: &mut ReadSeek, fat_type: FatType, cluster: u32) -> io::Result { @@ -83,8 +84,6 @@ pub(crate) fn alloc_cluster(fat: &mut DiskSlice, fat_type: FatType, prev_cluster Ok(new_cluster) } - - pub(crate) fn read_fat_flags(fat: &mut DiskSlice, fat_type: FatType) -> io::Result { // check MSB (except in FAT12) let val = match fat_type { @@ -107,6 +106,15 @@ pub(crate) fn read_fat_flags(fat: &mut DiskSlice, fat_type: FatType) -> io::Resu }) } +pub(crate) fn count_free_clusters(fat: &mut ReadSeek, fat_type: FatType, total_clusters: u32) -> io::Result { + let end_cluster = total_clusters + RESERVED_FAT_ENTRIES; + match fat_type { + FatType::Fat12 => Fat12::count_free(fat, end_cluster), + FatType::Fat16 => Fat16::count_free(fat, end_cluster), + FatType::Fat32 => Fat32::count_free(fat, end_cluster), + } +} + impl FatTrait for Fat12 { fn get_raw(fat: &mut ReadSeek, cluster: u32) -> io::Result { let fat_offset = cluster + (cluster / 2); @@ -173,6 +181,37 @@ impl FatTrait for Fat12 { }; } } + + fn count_free(fat: &mut ReadSeek, end_cluster: u32) -> io::Result { + let mut count = 0; + let mut cluster = RESERVED_FAT_ENTRIES; + fat.seek(io::SeekFrom::Start((cluster*3/2) as u64))?; + let mut prev_packed_val = 0u16; + loop { + let res = match cluster & 1 { + 0 => fat.read_u16::(), + _ => fat.read_u8().map(|n| n as u16), + }; + let packed_val = match res { + Err(ref err) if err.kind() == io::ErrorKind::UnexpectedEof => break, + Err(err) => return Err(err), + Ok(n) => n, + }; + let val = match cluster & 1 { + 0 => packed_val & 0x0FFF, + _ => (packed_val << 8) | (prev_packed_val >> 12), + }; + prev_packed_val = packed_val; + if val == 0 { + count += 1; + } + cluster += 1; + if cluster >= end_cluster { + break; + } + } + Ok(count) + } } impl FatTrait for Fat16 { @@ -217,6 +256,25 @@ impl FatTrait for Fat16 { } } } + + fn count_free(fat: &mut ReadSeek, end_cluster: u32) -> io::Result { + let mut count = 0; + let mut cluster = RESERVED_FAT_ENTRIES; + fat.seek(io::SeekFrom::Start((cluster*2) as u64))?; + loop { + match fat.read_u16::() { + Err(ref err) if err.kind() == io::ErrorKind::UnexpectedEof => break, + Err(err) => return Err(err), + Ok(0) => count += 1, + _ => {}, + } + cluster += 1; + if cluster >= end_cluster { + break; + } + } + Ok(count) + } } impl FatTrait for Fat32 { @@ -261,6 +319,25 @@ impl FatTrait for Fat32 { } } } + + fn count_free(fat: &mut ReadSeek, end_cluster: u32) -> io::Result { + let mut count = 0; + let mut cluster = RESERVED_FAT_ENTRIES; + fat.seek(io::SeekFrom::Start((cluster*4) as u64))?; + loop { + match fat.read_u32::() { + Err(ref err) if err.kind() == io::ErrorKind::UnexpectedEof => break, + Err(err) => return Err(err), + Ok(0) => count += 1, + _ => {}, + } + cluster += 1; + if cluster >= end_cluster { + break; + } + } + Ok(count) + } } pub(crate) struct ClusterIterator<'a, 'b: 'a> { diff --git a/tests/read.rs b/tests/read.rs index 1baf745..accf7da 100644 --- a/tests/read.rs +++ b/tests/read.rs @@ -210,3 +210,32 @@ fn test_status_flags_fat32() { call_with_fs(&|fs| test_status_flags(fs), FAT32_IMG) } +#[test] +fn test_stats_fat12() { + call_with_fs(&|fs| { + let stats = fs.stats().unwrap(); + assert_eq!(stats.cluster_size, 512); + assert_eq!(stats.total_clusters, 1955); // 1000 * 1024 / 512 = 2000 + assert_eq!(stats.free_clusters, 1920); + }, FAT12_IMG) +} + +#[test] +fn test_stats_fat16() { + call_with_fs(&|fs| { + let stats = fs.stats().unwrap(); + assert_eq!(stats.cluster_size, 512); + assert_eq!(stats.total_clusters, 4927); // 2500 * 1024 / 512 = 5000 + assert_eq!(stats.free_clusters, 4892); + }, FAT16_IMG) +} + +#[test] +fn test_stats_fat32() { + call_with_fs(&|fs| { + let stats = fs.stats().unwrap(); + assert_eq!(stats.cluster_size, 512); + assert_eq!(stats.total_clusters, 66922); // 34000 * 1024 / 512 = 68000 + assert_eq!(stats.free_clusters, 66886); + }, FAT32_IMG) +}