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(super) const MAX_DIRECTORY_DEPTH: u32 = 64;
131
132pub trait FatAnalysisExt<DATA: Read + Seek> {
134 fn statistics(&self) -> Result<FatStatistics>;
139
140 fn fragmentation_report(&self, max_files: usize) -> Result<FragmentationReport>;
146
147 fn scan_fat(&self) -> Result<Vec<ClusterState>>;
149
150 fn get_cluster_chain(&self, first_cluster: u32) -> Result<Vec<u32>>;
152}
153
154impl<DATA: Read + Seek> FatAnalysisExt<DATA> for FatVolume<DATA> {
155 fn statistics(&self) -> Result<FatStatistics> {
156 let fat_type = self.fat_type();
157 let cluster_size = self.info.cluster_size;
158 let mut data = self.data.lock();
159 let sector_size = data.sector_size;
160
161 let mut free_clusters = 0u32;
163 let mut used_clusters = 0u32;
164 let mut bad_clusters = 0u32;
165 let total_clusters = self.info.max_cluster;
166
167 for cluster in 2..=total_clusters {
169 let state = self.read_cluster_state(data.deref_mut(), cluster)?;
170 match state {
171 ClusterState::Free => free_clusters += 1,
172 ClusterState::Used(_) | ClusterState::EndOfChain => used_clusters += 1,
173 ClusterState::Bad => bad_clusters += 1,
174 ClusterState::Reserved => {}
175 }
176 }
177
178 drop(data);
179
180 let (file_count, directory_count) = self.count_entries()?;
182
183 let data_clusters = total_clusters - 1; let total_capacity = data_clusters as u64 * cluster_size as u64;
185 let used_space = used_clusters as u64 * cluster_size as u64;
186 let free_space = free_clusters as u64 * cluster_size as u64;
187
188 Ok(FatStatistics {
189 fat_type,
190 total_clusters,
191 free_clusters,
192 used_clusters,
193 bad_clusters,
194 reserved_clusters: 2, cluster_size,
196 sector_size,
197 total_capacity,
198 used_space,
199 free_space,
200 file_count,
201 directory_count,
202 })
203 }
204
205 fn fragmentation_report(&self, max_files: usize) -> Result<FragmentationReport> {
206 let mut all_files = Vec::new();
207 self.collect_files_recursive(&self.root_dir(), String::new(), 0, &mut all_files)?;
208
209 let mut total_fragments = 0u32;
210 let mut fragmented_files = 0u32;
211
212 let mut file_infos: Vec<FileFragmentInfo> = Vec::new();
214 for (path, entry) in &all_files {
215 if entry.is_directory() {
216 continue;
217 }
218
219 let first_cluster = entry.cluster().0 as u32;
220 if first_cluster < 2 {
221 continue;
223 }
224
225 let chain = self.get_cluster_chain(first_cluster)?;
226 let fragments = count_fragments(&chain);
227
228 total_fragments += fragments;
229 if fragments > 1 {
230 fragmented_files += 1;
231 }
232
233 file_infos.push(FileFragmentInfo {
234 path: path.clone(),
235 size: entry.len() as usize,
236 fragments,
237 first_cluster,
238 });
239 }
240
241 file_infos.sort_by(|a, b| b.fragments.cmp(&a.fragments));
243
244 let most_fragmented: Vec<FileFragmentInfo> =
246 file_infos.into_iter().take(max_files).collect();
247
248 let total_files = all_files.iter().filter(|(_, e)| e.is_file()).count() as u32;
249 let average_fragments = if total_files > 0 {
250 total_fragments as f64 / total_files as f64
251 } else {
252 0.0
253 };
254 let fragmentation_percentage = if total_files > 0 {
255 (fragmented_files as f64 / total_files as f64) * 100.0
256 } else {
257 0.0
258 };
259
260 Ok(FragmentationReport {
261 total_files,
262 fragmented_files,
263 total_fragments,
264 most_fragmented,
265 average_fragments,
266 fragmentation_percentage,
267 })
268 }
269
270 fn scan_fat(&self) -> Result<Vec<ClusterState>> {
271 let mut data = self.data.lock();
272 let total_clusters = self.info.max_cluster;
273 const MAX_PREALLOC_BYTES: usize = 16 * 1024 * 1024;
278 let prealloc = (MAX_PREALLOC_BYTES / core::mem::size_of::<ClusterState>())
279 .min(total_clusters as usize + 1);
280 let mut states = Vec::with_capacity(prealloc);
281
282 states.push(ClusterState::Reserved);
284 states.push(ClusterState::Reserved);
285
286 for cluster in 2..=total_clusters {
287 let state = self.read_cluster_state(data.deref_mut(), cluster)?;
288 states.push(state);
289 }
290
291 Ok(states)
292 }
293
294 fn get_cluster_chain(&self, first_cluster: u32) -> Result<Vec<u32>> {
295 let mut chain = Vec::new();
296 let mut current = first_cluster;
297 let mut data = self.data.lock();
298 let max_clusters = self.info.max_cluster;
299
300 let mut iterations = 0usize;
302 let max_iterations = max_clusters as usize;
303
304 while current >= 2 && current <= max_clusters {
305 chain.push(current);
306 iterations += 1;
307
308 if iterations > max_iterations {
309 break;
311 }
312
313 match self.fat.next_cluster(data.deref_mut(), current as usize)? {
314 Some(next) => current = next,
315 None => break,
316 }
317 }
318
319 Ok(chain)
320 }
321}
322
323impl<DATA: Read + Seek> FatVolume<DATA> {
325 fn read_cluster_state<T: Read + Seek>(
327 &self,
328 reader: &mut T,
329 cluster: u32,
330 ) -> Result<ClusterState> {
331 match &self.fat {
332 Fat::Fat12(fat12) => {
333 let entry = fat12.read_entry(reader, cluster as usize)?;
334 Ok(classify_fat12_entry(entry))
335 }
336 Fat::Fat16(fat16) => {
337 let entry = fat16.read_entry(reader, cluster as usize)?;
338 Ok(classify_fat16_entry(entry))
339 }
340 Fat::Fat32(fat32) => {
341 let entry = fat32.read_entry(reader, cluster as usize)?;
342 Ok(classify_fat32_entry(entry))
343 }
344 }
345 }
346
347 fn count_entries(&self) -> Result<(u32, u32)> {
349 let mut files = 0u32;
350 let mut dirs = 0u32;
351 self.count_entries_recursive(&self.root_dir(), 0, &mut files, &mut dirs)?;
352 Ok((files, dirs))
353 }
354
355 fn count_entries_recursive<'a>(
356 &'a self,
357 dir: &FatDir<'a, DATA>,
358 depth: u32,
359 files: &mut u32,
360 dirs: &mut u32,
361 ) -> Result<()> {
362 if depth > MAX_DIRECTORY_DEPTH {
363 return Err(crate::error::Error::CorruptFilesystem {
364 context: "directory nesting depth limit exceeded",
365 });
366 }
367 for entry in dir.entries() {
368 let entry = entry?;
369 let DirectoryEntry::Entry(file_entry) = entry;
370
371 let name = file_entry.name();
372 if name == "." || name == ".." {
373 continue;
374 }
375
376 if file_entry.is_directory() {
377 *dirs += 1;
378 let subdir = FatDir {
379 data: self,
380 cluster: file_entry.cluster(),
381 fixed_root: None,
382 };
383 self.count_entries_recursive(&subdir, depth + 1, files, dirs)?;
384 } else {
385 *files += 1;
386 }
387 }
388 Ok(())
389 }
390
391 fn collect_files_recursive<'a>(
393 &'a self,
394 dir: &FatDir<'a, DATA>,
395 path_prefix: String,
396 depth: u32,
397 files: &mut Vec<(String, FileEntry)>,
398 ) -> Result<()> {
399 if depth > MAX_DIRECTORY_DEPTH {
400 return Err(crate::error::Error::CorruptFilesystem {
401 context: "directory nesting depth limit exceeded",
402 });
403 }
404 for entry in dir.entries() {
405 let entry = entry?;
406 let DirectoryEntry::Entry(file_entry) = entry;
407
408 let name = file_entry.name();
409 if name == "." || name == ".." {
410 continue;
411 }
412
413 let full_path = if path_prefix.is_empty() {
414 format!("/{name}")
415 } else {
416 format!("{path_prefix}/{name}")
417 };
418
419 if file_entry.is_directory() {
420 let subdir = FatDir {
421 data: self,
422 cluster: file_entry.cluster(),
423 fixed_root: None,
424 };
425 self.collect_files_recursive(&subdir, full_path, depth + 1, files)?;
426 } else {
427 files.push((full_path, file_entry));
428 }
429 }
430 Ok(())
431 }
432}
433
434impl Fat12 {
436 pub fn read_entry<T: Read + Seek>(&self, reader: &mut T, cluster: usize) -> Result<u16> {
438 let byte_offset = self.entry_byte_offset(cluster);
439 reader.seek(super::super::io::SeekFrom::Start(byte_offset as u64))?;
440
441 let mut bytes = [0u8; 2];
442 reader.read_exact(&mut bytes)?;
443
444 let value = if cluster.is_multiple_of(2) {
445 u16::from(bytes[0]) | (u16::from(bytes[1] & 0x0F) << 8)
446 } else {
447 (u16::from(bytes[0]) >> 4) | (u16::from(bytes[1]) << 4)
448 };
449
450 Ok(value)
451 }
452}
453
454impl Fat16 {
455 pub fn read_entry<T: Read + Seek>(&self, reader: &mut T, cluster: usize) -> Result<u16> {
457 let offset = self.entry_offset(cluster);
458 reader.seek(super::super::io::SeekFrom::Start(offset as u64))?;
459
460 let mut bytes = [0u8; 2];
461 reader.read_exact(&mut bytes)?;
462
463 Ok(u16::from_le_bytes(bytes))
464 }
465}
466
467impl Fat32 {
468 pub fn read_entry<T: Read + Seek>(&self, reader: &mut T, cluster: usize) -> Result<u32> {
470 let offset = self.entry_offset(cluster);
471 reader.seek(super::super::io::SeekFrom::Start(offset as u64))?;
472
473 let mut bytes = [0u8; 4];
474 reader.read_exact(&mut bytes)?;
475
476 Ok(u32::from_le_bytes(bytes))
477 }
478}
479
480fn classify_fat12_entry(entry: u16) -> ClusterState {
482 let masked = entry & 0x0FFF;
483 match masked {
484 0x000 => ClusterState::Free,
485 0x001 => ClusterState::Reserved,
486 0xFF7 => ClusterState::Bad,
487 0xFF8..=0xFFF => ClusterState::EndOfChain,
488 n => ClusterState::Used(n as u32),
489 }
490}
491
492fn classify_fat16_entry(entry: u16) -> ClusterState {
494 match entry {
495 0x0000 => ClusterState::Free,
496 0x0001 => ClusterState::Reserved,
497 0xFFF7 => ClusterState::Bad,
498 0xFFF8..=0xFFFF => ClusterState::EndOfChain,
499 n => ClusterState::Used(n as u32),
500 }
501}
502
503fn classify_fat32_entry(entry: u32) -> ClusterState {
505 let masked = entry & 0x0FFF_FFFF;
506 match masked {
507 0x0000_0000 => ClusterState::Free,
508 0x0000_0001 => ClusterState::Reserved,
509 0x0FFF_FFF7 => ClusterState::Bad,
510 0x0FFF_FFF8..=0x0FFF_FFFF => ClusterState::EndOfChain,
511 n => ClusterState::Used(n),
512 }
513}
514
515fn count_fragments(chain: &[u32]) -> u32 {
517 if chain.is_empty() {
518 return 0;
519 }
520 if chain.len() == 1 {
521 return 1;
522 }
523
524 let mut fragments = 1u32;
525 for window in chain.windows(2) {
526 if window[1] != window[0] + 1 {
527 fragments += 1;
528 }
529 }
530 fragments
531}
532
533#[cfg(test)]
534mod tests {
535 use super::*;
536
537 #[test]
538 fn test_count_fragments() {
539 assert_eq!(count_fragments(&[]), 0);
540 assert_eq!(count_fragments(&[5]), 1);
541 assert_eq!(count_fragments(&[5, 6, 7]), 1);
542 assert_eq!(count_fragments(&[5, 6, 10]), 2);
543 assert_eq!(count_fragments(&[5, 10, 15]), 3);
544 assert_eq!(count_fragments(&[5, 6, 7, 10, 11, 15]), 3);
545 }
546
547 #[test]
548 fn test_classify_fat12() {
549 assert_eq!(classify_fat12_entry(0x000), ClusterState::Free);
550 assert_eq!(classify_fat12_entry(0x001), ClusterState::Reserved);
551 assert_eq!(classify_fat12_entry(0xFF7), ClusterState::Bad);
552 assert_eq!(classify_fat12_entry(0xFF8), ClusterState::EndOfChain);
553 assert_eq!(classify_fat12_entry(0xFFF), ClusterState::EndOfChain);
554 assert_eq!(classify_fat12_entry(0x123), ClusterState::Used(0x123));
555 }
556
557 #[test]
558 fn test_classify_fat16() {
559 assert_eq!(classify_fat16_entry(0x0000), ClusterState::Free);
560 assert_eq!(classify_fat16_entry(0x0001), ClusterState::Reserved);
561 assert_eq!(classify_fat16_entry(0xFFF7), ClusterState::Bad);
562 assert_eq!(classify_fat16_entry(0xFFF8), ClusterState::EndOfChain);
563 assert_eq!(classify_fat16_entry(0xFFFF), ClusterState::EndOfChain);
564 assert_eq!(classify_fat16_entry(0x1234), ClusterState::Used(0x1234));
565 }
566
567 #[test]
568 fn test_classify_fat32() {
569 assert_eq!(classify_fat32_entry(0x0000_0000), ClusterState::Free);
570 assert_eq!(classify_fat32_entry(0x0000_0001), ClusterState::Reserved);
571 assert_eq!(classify_fat32_entry(0x0FFF_FFF7), ClusterState::Bad);
572 assert_eq!(classify_fat32_entry(0x0FFF_FFF8), ClusterState::EndOfChain);
573 assert_eq!(classify_fat32_entry(0x0FFF_FFFF), ClusterState::EndOfChain);
574 assert_eq!(
575 classify_fat32_entry(0x0012_3456),
576 ClusterState::Used(0x0012_3456)
577 );
578 }
579}