1use alloc::format;
4use alloc::string::String;
5use alloc::vec::Vec;
6use core::ops::DerefMut;
7
8use super::super::{
9 dir::{DirectoryEntry, FatDir, FileEntry},
10 fat_table::{Fat, Fat12, Fat16, Fat32, FatType},
11 fs::FatVolume,
12 io::{Read, Seek},
13};
14use crate::error::Result;
15
16#[derive(Debug, Clone)]
18pub struct FatStatistics {
19 pub fat_type: FatType,
21 pub total_clusters: u32,
23 pub free_clusters: u32,
25 pub used_clusters: u32,
27 pub bad_clusters: u32,
29 pub reserved_clusters: u32,
31 pub cluster_size: usize,
33 pub sector_size: usize,
35 pub total_capacity: u64,
37 pub used_space: u64,
39 pub free_space: u64,
41 pub file_count: u32,
43 pub directory_count: u32,
45}
46
47impl FatStatistics {
48 pub fn used_percentage(&self) -> f64 {
50 if self.total_capacity == 0 {
51 0.0
52 } else {
53 (self.used_space as f64 / self.total_capacity as f64) * 100.0
54 }
55 }
56
57 pub fn free_percentage(&self) -> f64 {
59 if self.total_capacity == 0 {
60 0.0
61 } else {
62 (self.free_space as f64 / self.total_capacity as f64) * 100.0
63 }
64 }
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69pub enum ClusterState {
70 Free,
72 Reserved,
74 Bad,
76 Used(u32),
78 EndOfChain,
80}
81
82#[derive(Debug, Clone)]
84pub struct FileFragmentInfo {
85 pub path: String,
87 pub size: usize,
89 pub fragments: u32,
91 pub first_cluster: u32,
93}
94
95impl FileFragmentInfo {
96 pub fn fragmentation_ratio(&self, cluster_size: usize) -> f64 {
98 if self.size == 0 {
99 return 1.0;
100 }
101 let ideal_clusters = self.size.div_ceil(cluster_size);
102 if ideal_clusters == 0 {
103 return 1.0;
104 }
105 self.fragments as f64 / ideal_clusters as f64
106 }
107}
108
109#[derive(Debug, Clone)]
111pub struct FragmentationReport {
112 pub total_files: u32,
114 pub fragmented_files: u32,
116 pub total_fragments: u32,
118 pub most_fragmented: Vec<FileFragmentInfo>,
120 pub average_fragments: f64,
122 pub fragmentation_percentage: f64,
124}
125
126pub trait FatAnalysisExt<DATA: Read + Seek> {
128 fn statistics(&self) -> Result<FatStatistics>;
133
134 fn fragmentation_report(&self, max_files: usize) -> Result<FragmentationReport>;
140
141 fn scan_fat(&self) -> Result<Vec<ClusterState>>;
143
144 fn get_cluster_chain(&self, first_cluster: u32) -> Result<Vec<u32>>;
146}
147
148impl<DATA: Read + Seek> FatAnalysisExt<DATA> for FatVolume<DATA> {
149 fn statistics(&self) -> Result<FatStatistics> {
150 let fat_type = self.fat_type();
151 let cluster_size = self.info.cluster_size;
152 let mut data = self.data.lock();
153 let sector_size = data.sector_size;
154
155 let mut free_clusters = 0u32;
157 let mut used_clusters = 0u32;
158 let mut bad_clusters = 0u32;
159 let total_clusters = self.info.max_cluster;
160
161 for cluster in 2..=total_clusters {
163 let state = self.read_cluster_state(data.deref_mut(), cluster)?;
164 match state {
165 ClusterState::Free => free_clusters += 1,
166 ClusterState::Used(_) | ClusterState::EndOfChain => used_clusters += 1,
167 ClusterState::Bad => bad_clusters += 1,
168 ClusterState::Reserved => {}
169 }
170 }
171
172 drop(data);
173
174 let (file_count, directory_count) = self.count_entries()?;
176
177 let data_clusters = total_clusters - 1; let total_capacity = data_clusters as u64 * cluster_size as u64;
179 let used_space = used_clusters as u64 * cluster_size as u64;
180 let free_space = free_clusters as u64 * cluster_size as u64;
181
182 Ok(FatStatistics {
183 fat_type,
184 total_clusters,
185 free_clusters,
186 used_clusters,
187 bad_clusters,
188 reserved_clusters: 2, cluster_size,
190 sector_size,
191 total_capacity,
192 used_space,
193 free_space,
194 file_count,
195 directory_count,
196 })
197 }
198
199 fn fragmentation_report(&self, max_files: usize) -> Result<FragmentationReport> {
200 let mut all_files = Vec::new();
201 self.collect_files_recursive(&self.root_dir(), String::new(), &mut all_files)?;
202
203 let mut total_fragments = 0u32;
204 let mut fragmented_files = 0u32;
205
206 let mut file_infos: Vec<FileFragmentInfo> = Vec::new();
208 for (path, entry) in &all_files {
209 if entry.is_directory() {
210 continue;
211 }
212
213 let first_cluster = entry.cluster().0 as u32;
214 if first_cluster < 2 {
215 continue;
217 }
218
219 let chain = self.get_cluster_chain(first_cluster)?;
220 let fragments = count_fragments(&chain);
221
222 total_fragments += fragments;
223 if fragments > 1 {
224 fragmented_files += 1;
225 }
226
227 file_infos.push(FileFragmentInfo {
228 path: path.clone(),
229 size: entry.len() as usize,
230 fragments,
231 first_cluster,
232 });
233 }
234
235 file_infos.sort_by(|a, b| b.fragments.cmp(&a.fragments));
237
238 let most_fragmented: Vec<FileFragmentInfo> =
240 file_infos.into_iter().take(max_files).collect();
241
242 let total_files = all_files.iter().filter(|(_, e)| e.is_file()).count() as u32;
243 let average_fragments = if total_files > 0 {
244 total_fragments as f64 / total_files as f64
245 } else {
246 0.0
247 };
248 let fragmentation_percentage = if total_files > 0 {
249 (fragmented_files as f64 / total_files as f64) * 100.0
250 } else {
251 0.0
252 };
253
254 Ok(FragmentationReport {
255 total_files,
256 fragmented_files,
257 total_fragments,
258 most_fragmented,
259 average_fragments,
260 fragmentation_percentage,
261 })
262 }
263
264 fn scan_fat(&self) -> Result<Vec<ClusterState>> {
265 let mut data = self.data.lock();
266 let total_clusters = self.info.max_cluster;
267 let mut states = Vec::with_capacity(total_clusters as usize + 1);
268
269 states.push(ClusterState::Reserved);
271 states.push(ClusterState::Reserved);
272
273 for cluster in 2..=total_clusters {
274 let state = self.read_cluster_state(data.deref_mut(), cluster)?;
275 states.push(state);
276 }
277
278 Ok(states)
279 }
280
281 fn get_cluster_chain(&self, first_cluster: u32) -> Result<Vec<u32>> {
282 let mut chain = Vec::new();
283 let mut current = first_cluster;
284 let mut data = self.data.lock();
285 let max_clusters = self.info.max_cluster;
286
287 let mut iterations = 0usize;
289 let max_iterations = max_clusters as usize;
290
291 while current >= 2 && current <= max_clusters {
292 chain.push(current);
293 iterations += 1;
294
295 if iterations > max_iterations {
296 break;
298 }
299
300 match self.fat.next_cluster(data.deref_mut(), current as usize)? {
301 Some(next) => current = next,
302 None => break,
303 }
304 }
305
306 Ok(chain)
307 }
308}
309
310impl<DATA: Read + Seek> FatVolume<DATA> {
312 fn read_cluster_state<T: Read + Seek>(
314 &self,
315 reader: &mut T,
316 cluster: u32,
317 ) -> Result<ClusterState> {
318 match &self.fat {
319 Fat::Fat12(fat12) => {
320 let entry = fat12.read_entry(reader, cluster as usize)?;
321 Ok(classify_fat12_entry(entry))
322 }
323 Fat::Fat16(fat16) => {
324 let entry = fat16.read_entry(reader, cluster as usize)?;
325 Ok(classify_fat16_entry(entry))
326 }
327 Fat::Fat32(fat32) => {
328 let entry = fat32.read_entry(reader, cluster as usize)?;
329 Ok(classify_fat32_entry(entry))
330 }
331 }
332 }
333
334 fn count_entries(&self) -> Result<(u32, u32)> {
336 let mut files = 0u32;
337 let mut dirs = 0u32;
338 self.count_entries_recursive(&self.root_dir(), &mut files, &mut dirs)?;
339 Ok((files, dirs))
340 }
341
342 fn count_entries_recursive<'a>(
343 &'a self,
344 dir: &FatDir<'a, DATA>,
345 files: &mut u32,
346 dirs: &mut u32,
347 ) -> Result<()> {
348 for entry in dir.entries() {
349 let entry = entry?;
350 let DirectoryEntry::Entry(file_entry) = entry;
351
352 let name = file_entry.name();
353 if name == "." || name == ".." {
354 continue;
355 }
356
357 if file_entry.is_directory() {
358 *dirs += 1;
359 let subdir = FatDir {
360 data: self,
361 cluster: file_entry.cluster(),
362 fixed_root: None,
363 };
364 self.count_entries_recursive(&subdir, files, dirs)?;
365 } else {
366 *files += 1;
367 }
368 }
369 Ok(())
370 }
371
372 fn collect_files_recursive<'a>(
374 &'a self,
375 dir: &FatDir<'a, DATA>,
376 path_prefix: String,
377 files: &mut Vec<(String, FileEntry)>,
378 ) -> Result<()> {
379 for entry in dir.entries() {
380 let entry = entry?;
381 let DirectoryEntry::Entry(file_entry) = entry;
382
383 let name = file_entry.name();
384 if name == "." || name == ".." {
385 continue;
386 }
387
388 let full_path = if path_prefix.is_empty() {
389 format!("/{name}")
390 } else {
391 format!("{path_prefix}/{name}")
392 };
393
394 if file_entry.is_directory() {
395 let subdir = FatDir {
396 data: self,
397 cluster: file_entry.cluster(),
398 fixed_root: None,
399 };
400 self.collect_files_recursive(&subdir, full_path, files)?;
401 } else {
402 files.push((full_path, file_entry));
403 }
404 }
405 Ok(())
406 }
407}
408
409impl Fat12 {
411 pub fn read_entry<T: Read + Seek>(&self, reader: &mut T, cluster: usize) -> Result<u16> {
413 let byte_offset = self.entry_byte_offset(cluster);
414 reader.seek(super::super::io::SeekFrom::Start(byte_offset as u64))?;
415
416 let mut bytes = [0u8; 2];
417 reader.read_exact(&mut bytes)?;
418
419 let value = if cluster.is_multiple_of(2) {
420 u16::from(bytes[0]) | (u16::from(bytes[1] & 0x0F) << 8)
421 } else {
422 (u16::from(bytes[0]) >> 4) | (u16::from(bytes[1]) << 4)
423 };
424
425 Ok(value)
426 }
427}
428
429impl Fat16 {
430 pub fn read_entry<T: Read + Seek>(&self, reader: &mut T, cluster: usize) -> Result<u16> {
432 let offset = self.entry_offset(cluster);
433 reader.seek(super::super::io::SeekFrom::Start(offset as u64))?;
434
435 let mut bytes = [0u8; 2];
436 reader.read_exact(&mut bytes)?;
437
438 Ok(u16::from_le_bytes(bytes))
439 }
440}
441
442impl Fat32 {
443 pub fn read_entry<T: Read + Seek>(&self, reader: &mut T, cluster: usize) -> Result<u32> {
445 let offset = self.entry_offset(cluster);
446 reader.seek(super::super::io::SeekFrom::Start(offset as u64))?;
447
448 let mut bytes = [0u8; 4];
449 reader.read_exact(&mut bytes)?;
450
451 Ok(u32::from_le_bytes(bytes))
452 }
453}
454
455fn classify_fat12_entry(entry: u16) -> ClusterState {
457 let masked = entry & 0x0FFF;
458 match masked {
459 0x000 => ClusterState::Free,
460 0x001 => ClusterState::Reserved,
461 0xFF7 => ClusterState::Bad,
462 0xFF8..=0xFFF => ClusterState::EndOfChain,
463 n => ClusterState::Used(n as u32),
464 }
465}
466
467fn classify_fat16_entry(entry: u16) -> ClusterState {
469 match entry {
470 0x0000 => ClusterState::Free,
471 0x0001 => ClusterState::Reserved,
472 0xFFF7 => ClusterState::Bad,
473 0xFFF8..=0xFFFF => ClusterState::EndOfChain,
474 n => ClusterState::Used(n as u32),
475 }
476}
477
478fn classify_fat32_entry(entry: u32) -> ClusterState {
480 let masked = entry & 0x0FFF_FFFF;
481 match masked {
482 0x0000_0000 => ClusterState::Free,
483 0x0000_0001 => ClusterState::Reserved,
484 0x0FFF_FFF7 => ClusterState::Bad,
485 0x0FFF_FFF8..=0x0FFF_FFFF => ClusterState::EndOfChain,
486 n => ClusterState::Used(n),
487 }
488}
489
490fn count_fragments(chain: &[u32]) -> u32 {
492 if chain.is_empty() {
493 return 0;
494 }
495 if chain.len() == 1 {
496 return 1;
497 }
498
499 let mut fragments = 1u32;
500 for window in chain.windows(2) {
501 if window[1] != window[0] + 1 {
502 fragments += 1;
503 }
504 }
505 fragments
506}
507
508#[cfg(test)]
509mod tests {
510 use super::*;
511
512 #[test]
513 fn test_count_fragments() {
514 assert_eq!(count_fragments(&[]), 0);
515 assert_eq!(count_fragments(&[5]), 1);
516 assert_eq!(count_fragments(&[5, 6, 7]), 1);
517 assert_eq!(count_fragments(&[5, 6, 10]), 2);
518 assert_eq!(count_fragments(&[5, 10, 15]), 3);
519 assert_eq!(count_fragments(&[5, 6, 7, 10, 11, 15]), 3);
520 }
521
522 #[test]
523 fn test_classify_fat12() {
524 assert_eq!(classify_fat12_entry(0x000), ClusterState::Free);
525 assert_eq!(classify_fat12_entry(0x001), ClusterState::Reserved);
526 assert_eq!(classify_fat12_entry(0xFF7), ClusterState::Bad);
527 assert_eq!(classify_fat12_entry(0xFF8), ClusterState::EndOfChain);
528 assert_eq!(classify_fat12_entry(0xFFF), ClusterState::EndOfChain);
529 assert_eq!(classify_fat12_entry(0x123), ClusterState::Used(0x123));
530 }
531
532 #[test]
533 fn test_classify_fat16() {
534 assert_eq!(classify_fat16_entry(0x0000), ClusterState::Free);
535 assert_eq!(classify_fat16_entry(0x0001), ClusterState::Reserved);
536 assert_eq!(classify_fat16_entry(0xFFF7), ClusterState::Bad);
537 assert_eq!(classify_fat16_entry(0xFFF8), ClusterState::EndOfChain);
538 assert_eq!(classify_fat16_entry(0xFFFF), ClusterState::EndOfChain);
539 assert_eq!(classify_fat16_entry(0x1234), ClusterState::Used(0x1234));
540 }
541
542 #[test]
543 fn test_classify_fat32() {
544 assert_eq!(classify_fat32_entry(0x0000_0000), ClusterState::Free);
545 assert_eq!(classify_fat32_entry(0x0000_0001), ClusterState::Reserved);
546 assert_eq!(classify_fat32_entry(0x0FFF_FFF7), ClusterState::Bad);
547 assert_eq!(classify_fat32_entry(0x0FFF_FFF8), ClusterState::EndOfChain);
548 assert_eq!(classify_fat32_entry(0x0FFF_FFFF), ClusterState::EndOfChain);
549 assert_eq!(
550 classify_fat32_entry(0x0012_3456),
551 ClusterState::Used(0x0012_3456)
552 );
553 }
554}