diff options
Diffstat (limited to 'rsnix/src/fat.rs')
| -rw-r--r-- | rsnix/src/fat.rs | 201 |
1 files changed, 142 insertions, 59 deletions
diff --git a/rsnix/src/fat.rs b/rsnix/src/fat.rs index 616a08f..c486dfe 100644 --- a/rsnix/src/fat.rs +++ b/rsnix/src/fat.rs | |||
| @@ -34,6 +34,8 @@ pub struct Entry { | |||
| 34 | } | 34 | } |
| 35 | 35 | ||
| 36 | struct Volume { | 36 | struct Volume { |
| 37 | disk: usize, // AHCI disk index this volume was mounted from | ||
| 38 | uid: String, // unique mount name: "LABEL-XXXXXXXX" | ||
| 37 | sectors_per_cluster: u8, | 39 | sectors_per_cluster: u8, |
| 38 | reserved: u16, | 40 | reserved: u16, |
| 39 | fat_count: u8, | 41 | fat_count: u8, |
| @@ -45,14 +47,34 @@ struct Volume { | |||
| 45 | fat: Vec<u8>, // FAT 1 cached in RAM | 47 | fat: Vec<u8>, // FAT 1 cached in RAM |
| 46 | } | 48 | } |
| 47 | 49 | ||
| 48 | static mut VOL: Option<Volume> = None; | 50 | static mut VOLS: Vec<Volume> = Vec::new(); |
| 51 | static mut ACTIVE_VOL: usize = 0; | ||
| 49 | 52 | ||
| 50 | pub fn is_mounted() -> bool { | 53 | pub fn volume_count() -> usize { |
| 51 | unsafe { VOL.is_some() } | 54 | unsafe { VOLS.len() } |
| 52 | } | 55 | } |
| 53 | 56 | ||
| 54 | pub fn root_cluster() -> u32 { | 57 | /// Unique mount name of volume `vol` ("RSDOS-5BD1E55F"). |
| 55 | unsafe { VOL.as_ref() }.map(|v| v.root_cluster).unwrap_or(0) | 58 | pub fn uid(vol: usize) -> &'static str { |
| 59 | unsafe { VOLS.get(vol) }.map(|v| v.uid.as_str()).unwrap_or("") | ||
| 60 | } | ||
| 61 | |||
| 62 | pub fn root_cluster(vol: usize) -> u32 { | ||
| 63 | unsafe { VOLS.get(vol) }.map(|v| v.root_cluster).unwrap_or(0) | ||
| 64 | } | ||
| 65 | |||
| 66 | /// Point all FAT operations at volume `vol`; also selects the backing | ||
| 67 | /// AHCI disk. Called by every public entry point (single-threaded | ||
| 68 | /// kernel, so a process-wide "current volume" is fine). | ||
| 69 | fn select_vol(vol: usize) -> Result<(), ()> { | ||
| 70 | let disk = unsafe { VOLS.get(vol) }.map(|v| v.disk).ok_or(())?; | ||
| 71 | unsafe { | ||
| 72 | ACTIVE_VOL = vol; | ||
| 73 | } | ||
| 74 | if !crate::ahci::select(disk) { | ||
| 75 | return Err(()); | ||
| 76 | } | ||
| 77 | Ok(()) | ||
| 56 | } | 78 | } |
| 57 | 79 | ||
| 58 | // --- sector I/O ----------------------------------------------------------- | 80 | // --- sector I/O ----------------------------------------------------------- |
| @@ -71,7 +93,7 @@ fn write_sector(lba: u64, data: &[u8; SECTOR]) -> Result<(), ()> { | |||
| 71 | // --- cluster math ---------------------------------------------------------- | 93 | // --- cluster math ---------------------------------------------------------- |
| 72 | 94 | ||
| 73 | fn v() -> &'static mut Volume { | 95 | fn v() -> &'static mut Volume { |
| 74 | unsafe { VOL.as_mut() }.unwrap() | 96 | unsafe { VOLS.get_mut(ACTIVE_VOL) }.unwrap() |
| 75 | } | 97 | } |
| 76 | 98 | ||
| 77 | fn spc() -> usize { | 99 | fn spc() -> usize { |
| @@ -336,7 +358,19 @@ fn scan_dir(dir_cluster: u32) -> Result<Vec<DirItem>, ()> { | |||
| 336 | _ => { | 358 | _ => { |
| 337 | let attr = e[11]; | 359 | let attr = e[11]; |
| 338 | if attr == 0x0F { | 360 | if attr == 0x0F { |
| 339 | // LFN entry: 13 UTF-16 chars (bytes 1-10, 14-25, 28-31). | 361 | // LFN entry: 13 UTF-16 chars (bytes 1-10, 14-25, |
| 362 | // 28-31). Entries are stored last-first on disk; | ||
| 363 | // place each chunk by its sequence number so | ||
| 364 | // multi-entry names reassemble in the right order. | ||
| 365 | let seq = (e[0] & 0x1F) as usize; | ||
| 366 | if seq == 0 || seq > 20 { | ||
| 367 | continue; | ||
| 368 | } | ||
| 369 | let need = seq * 13; | ||
| 370 | if lfn.len() < need { | ||
| 371 | lfn.resize(need, 0); | ||
| 372 | } | ||
| 373 | let base = (seq - 1) * 13; | ||
| 340 | for k in 0..13 { | 374 | for k in 0..13 { |
| 341 | let pos = if k < 5 { | 375 | let pos = if k < 5 { |
| 342 | 1 + k * 2 | 376 | 1 + k * 2 |
| @@ -347,11 +381,9 @@ fn scan_dir(dir_cluster: u32) -> Result<Vec<DirItem>, ()> { | |||
| 347 | }; | 381 | }; |
| 348 | let ch = u16::from_le_bytes([e[pos], e[pos + 1]]); | 382 | let ch = u16::from_le_bytes([e[pos], e[pos + 1]]); |
| 349 | if ch == 0x0000 { | 383 | if ch == 0x0000 { |
| 350 | break; | 384 | break; // end of the name; rest is 0xFFFF padding |
| 351 | } | ||
| 352 | if ch != 0xFFFF { | ||
| 353 | lfn.push(ch); | ||
| 354 | } | 385 | } |
| 386 | lfn[base + k] = ch; | ||
| 355 | } | 387 | } |
| 356 | lfn_sum = e[13]; | 388 | lfn_sum = e[13]; |
| 357 | } else { | 389 | } else { |
| @@ -359,7 +391,8 @@ fn scan_dir(dir_cluster: u32) -> Result<Vec<DirItem>, ()> { | |||
| 359 | let mut sfn = [0u8; 11]; | 391 | let mut sfn = [0u8; 11]; |
| 360 | sfn.copy_from_slice(&e[0..11]); | 392 | sfn.copy_from_slice(&e[0..11]); |
| 361 | let name = if !lfn.is_empty() && lfn_checksum(&sfn) == lfn_sum { | 393 | let name = if !lfn.is_empty() && lfn_checksum(&sfn) == lfn_sum { |
| 362 | utf16_to_string(&lfn) | 394 | let end = lfn.iter().position(|&c| c == 0).unwrap_or(lfn.len()); |
| 395 | utf16_to_string(&lfn[..end]) | ||
| 363 | } else { | 396 | } else { |
| 364 | sfn_decode(e) | 397 | sfn_decode(e) |
| 365 | }; | 398 | }; |
| @@ -387,7 +420,8 @@ fn scan_dir(dir_cluster: u32) -> Result<Vec<DirItem>, ()> { | |||
| 387 | } | 420 | } |
| 388 | 421 | ||
| 389 | /// Public listing: no `.` / `..`. | 422 | /// Public listing: no `.` / `..`. |
| 390 | pub fn list_dir(dir_cluster: u32) -> Result<Vec<Entry>, ()> { | 423 | pub fn list_dir(vol: usize, dir_cluster: u32) -> Result<Vec<Entry>, ()> { |
| 424 | select_vol(vol)?; | ||
| 391 | Ok(scan_dir(dir_cluster)? | 425 | Ok(scan_dir(dir_cluster)? |
| 392 | .into_iter() | 426 | .into_iter() |
| 393 | .filter(|i| i.name != "." && i.name != "..") | 427 | .filter(|i| i.name != "." && i.name != "..") |
| @@ -412,7 +446,8 @@ fn find_item(parent_dir_cluster: u32, name: &str) -> Result<DirItem, ()> { | |||
| 412 | } | 446 | } |
| 413 | 447 | ||
| 414 | /// Find an entry by name (case-insensitive). | 448 | /// Find an entry by name (case-insensitive). |
| 415 | pub fn find(parent_dir_cluster: u32, name: &str) -> Result<Entry, ()> { | 449 | pub fn find(vol: usize, parent_dir_cluster: u32, name: &str) -> Result<Entry, ()> { |
| 450 | select_vol(vol)?; | ||
| 416 | find_item(parent_dir_cluster, name).map(|i| Entry { | 451 | find_item(parent_dir_cluster, name).map(|i| Entry { |
| 417 | name: i.name, | 452 | name: i.name, |
| 418 | is_dir: i.attr & 0x10 != 0, | 453 | is_dir: i.attr & 0x10 != 0, |
| @@ -424,15 +459,17 @@ pub fn find(parent_dir_cluster: u32, name: &str) -> Result<Entry, ()> { | |||
| 424 | // --- file data -------------------------------------------------------------- | 459 | // --- file data -------------------------------------------------------------- |
| 425 | 460 | ||
| 426 | pub fn read_file( | 461 | pub fn read_file( |
| 462 | vol: usize, | ||
| 427 | parent_dir_cluster: u32, | 463 | parent_dir_cluster: u32, |
| 428 | name: &str, | 464 | name: &str, |
| 429 | buf: &mut [u8], | 465 | buf: &mut [u8], |
| 430 | pos: u64, | 466 | pos: u64, |
| 431 | ) -> Result<usize, ()> { | 467 | ) -> Result<usize, ()> { |
| 468 | select_vol(vol)?; | ||
| 432 | if buf.is_empty() { | 469 | if buf.is_empty() { |
| 433 | return Ok(0); | 470 | return Ok(0); |
| 434 | } | 471 | } |
| 435 | let it = find(parent_dir_cluster, name)?; | 472 | let it = find(vol, parent_dir_cluster, name)?; |
| 436 | if it.is_dir || it.cluster == 0 { | 473 | if it.is_dir || it.cluster == 0 { |
| 437 | return Err(()); | 474 | return Err(()); |
| 438 | } | 475 | } |
| @@ -467,7 +504,8 @@ pub fn read_file( | |||
| 467 | Ok(out) | 504 | Ok(out) |
| 468 | } | 505 | } |
| 469 | 506 | ||
| 470 | pub fn write_file(parent_dir_cluster: u32, name: &str, buf: &[u8], pos: u64) -> Result<usize, ()> { | 507 | pub fn write_file(vol: usize, parent_dir_cluster: u32, name: &str, buf: &[u8], pos: u64) -> Result<usize, ()> { |
| 508 | select_vol(vol)?; | ||
| 471 | if buf.is_empty() { | 509 | if buf.is_empty() { |
| 472 | return Ok(0); | 510 | return Ok(0); |
| 473 | } | 511 | } |
| @@ -529,7 +567,8 @@ pub fn write_file(parent_dir_cluster: u32, name: &str, buf: &[u8], pos: u64) -> | |||
| 529 | Ok(buf.len()) | 567 | Ok(buf.len()) |
| 530 | } | 568 | } |
| 531 | 569 | ||
| 532 | pub fn set_len(parent_dir_cluster: u32, name: &str, len: u64) -> Result<(), ()> { | 570 | pub fn set_len(vol: usize, parent_dir_cluster: u32, name: &str, len: u64) -> Result<(), ()> { |
| 571 | select_vol(vol)?; | ||
| 533 | let it = find_item(parent_dir_cluster, name)?; | 572 | let it = find_item(parent_dir_cluster, name)?; |
| 534 | if it.attr & 0x10 != 0 { | 573 | if it.attr & 0x10 != 0 { |
| 535 | return Err(()); | 574 | return Err(()); |
| @@ -721,8 +760,11 @@ fn add_dir_entry(parent: u32, name: &str, is_dir: bool, cluster: u32) -> Result< | |||
| 721 | if run >= needed { | 760 | if run >= needed { |
| 722 | if groups > 0 { | 761 | if groups > 0 { |
| 723 | for g in 0..groups { | 762 | for g in 0..groups { |
| 724 | let seq = (groups - g) as u8 | if g == groups - 1 { 0x40 } else { 0 }; | 763 | // LFN entries are stored last-first on disk: the tail |
| 725 | let e = &mut buf[(start + g) * 32..(start + g) * 32 + 32]; | 764 | // chunk (highest seq, LAST flag) goes first, and the |
| 765 | // head chunk (seq 1) lands immediately before the SFN. | ||
| 766 | let seq = (g + 1) as u8 | if g == groups - 1 { 0x40 } else { 0 }; | ||
| 767 | let e = &mut buf[(start + groups - 1 - g) * 32..(start + groups - 1 - g) * 32 + 32]; | ||
| 726 | e.fill(0); | 768 | e.fill(0); |
| 727 | e[0] = seq; | 769 | e[0] = seq; |
| 728 | e[11] = 0x0F; | 770 | e[11] = 0x0F; |
| @@ -769,8 +811,9 @@ fn add_dir_entry(parent: u32, name: &str, is_dir: bool, cluster: u32) -> Result< | |||
| 769 | } | 811 | } |
| 770 | } | 812 | } |
| 771 | 813 | ||
| 772 | pub fn create(parent_dir_cluster: u32, name: &str, is_dir: bool) -> Result<(), ()> { | 814 | pub fn create(vol: usize, parent_dir_cluster: u32, name: &str, is_dir: bool) -> Result<(), ()> { |
| 773 | if find(parent_dir_cluster, name).is_ok() { | 815 | select_vol(vol)?; |
| 816 | if find(vol, parent_dir_cluster, name).is_ok() { | ||
| 774 | return Err(()); | 817 | return Err(()); |
| 775 | } | 818 | } |
| 776 | let cluster = if is_dir { | 819 | let cluster = if is_dir { |
| @@ -807,11 +850,12 @@ pub fn create(parent_dir_cluster: u32, name: &str, is_dir: bool) -> Result<(), ( | |||
| 807 | add_dir_entry(parent_dir_cluster, name, is_dir, cluster) | 850 | add_dir_entry(parent_dir_cluster, name, is_dir, cluster) |
| 808 | } | 851 | } |
| 809 | 852 | ||
| 810 | pub fn remove(parent_dir_cluster: u32, name: &str) -> Result<(), ()> { | 853 | pub fn remove(vol: usize, parent_dir_cluster: u32, name: &str) -> Result<(), ()> { |
| 854 | select_vol(vol)?; | ||
| 811 | let it = find_item(parent_dir_cluster, name)?; | 855 | let it = find_item(parent_dir_cluster, name)?; |
| 812 | if it.attr & 0x10 != 0 { | 856 | if it.attr & 0x10 != 0 { |
| 813 | // Only empty directories may be removed. | 857 | // Only empty directories may be removed. |
| 814 | let kids = list_dir(it.cluster)?; | 858 | let kids = list_dir(vol, it.cluster)?; |
| 815 | if !kids.is_empty() { | 859 | if !kids.is_empty() { |
| 816 | return Err(()); | 860 | return Err(()); |
| 817 | } | 861 | } |
| @@ -847,35 +891,59 @@ pub fn init() -> bool { | |||
| 847 | crate::serial_println!("fat: no disk"); | 891 | crate::serial_println!("fat: no disk"); |
| 848 | return false; | 892 | return false; |
| 849 | } | 893 | } |
| 894 | let mut mounted = false; | ||
| 850 | for d in 0..disks { | 895 | for d in 0..disks { |
| 851 | if !crate::ahci::select(d) { | 896 | if !crate::ahci::select(d) { |
| 852 | continue; | 897 | continue; |
| 853 | } | 898 | } |
| 854 | if try_mount() { | 899 | match try_mount(d) { |
| 855 | return true; | 900 | Some(volume) => { |
| 901 | let uid = volume.uid.clone(); | ||
| 902 | let vol = unsafe { | ||
| 903 | VOLS.push(volume); | ||
| 904 | VOLS.len() - 1 | ||
| 905 | }; | ||
| 906 | // Re-sync the FSInfo free-cluster count (may be stale | ||
| 907 | // from earlier sessions). | ||
| 908 | if select_vol(vol).is_ok() { | ||
| 909 | fsinfo_recount(); | ||
| 910 | } | ||
| 911 | let count = list_dir(vol, root_cluster(vol)) | ||
| 912 | .map(|es| es.len()) | ||
| 913 | .unwrap_or(0); | ||
| 914 | crate::serial_println!( | ||
| 915 | "fat: mounted \"{}\" at /disk/{} ({} clusters, {} B/cluster, root: {} entries)", | ||
| 916 | uid, | ||
| 917 | uid, | ||
| 918 | v().cluster_count, | ||
| 919 | cluster_size(), | ||
| 920 | count | ||
| 921 | ); | ||
| 922 | mounted = true; | ||
| 923 | } | ||
| 924 | None => crate::serial_println!("fat: no FAT32 volume on disk {}", d), | ||
| 856 | } | 925 | } |
| 857 | crate::serial_println!("fat: no FAT32 volume on disk {}", d); | ||
| 858 | } | 926 | } |
| 859 | false | 927 | mounted |
| 860 | } | 928 | } |
| 861 | 929 | ||
| 862 | /// Parse the BPB of the selected disk, cache the FAT and validate the | 930 | /// Parse the BPB of the selected disk, cache the FAT and validate the |
| 863 | /// volume. | 931 | /// volume. Returns the ready-to-mount volume or None. |
| 864 | fn try_mount() -> bool { | 932 | fn try_mount(d: usize) -> Option<Volume> { |
| 865 | let mut bpb = [0u8; SECTOR]; | 933 | let mut bpb = [0u8; SECTOR]; |
| 866 | if read_sector(0, &mut bpb).is_err() { | 934 | if read_sector(0, &mut bpb).is_err() { |
| 867 | crate::serial_println!("fat: no disk"); | 935 | crate::serial_println!("fat: no disk"); |
| 868 | return false; | 936 | return None; |
| 869 | } | 937 | } |
| 870 | let bytes_per_sector = u16::from_le_bytes([bpb[11], bpb[12]]); | 938 | let bytes_per_sector = u16::from_le_bytes([bpb[11], bpb[12]]); |
| 871 | if bytes_per_sector != 512 { | 939 | if bytes_per_sector != 512 { |
| 872 | crate::serial_println!("fat: unsupported sector size {}", bytes_per_sector); | 940 | crate::serial_println!("fat: unsupported sector size {}", bytes_per_sector); |
| 873 | return false; | 941 | return None; |
| 874 | } | 942 | } |
| 875 | let spc = bpb[13]; | 943 | let spc = bpb[13]; |
| 876 | if spc == 0 || spc & (spc - 1) != 0 { | 944 | if spc == 0 || spc & (spc - 1) != 0 { |
| 877 | crate::serial_println!("fat: bad sectors/cluster {}", spc); | 945 | crate::serial_println!("fat: bad sectors/cluster {}", spc); |
| 878 | return false; | 946 | return None; |
| 879 | } | 947 | } |
| 880 | let reserved = u16::from_le_bytes([bpb[14], bpb[15]]); | 948 | let reserved = u16::from_le_bytes([bpb[14], bpb[15]]); |
| 881 | let fat_count = bpb[16]; | 949 | let fat_count = bpb[16]; |
| @@ -891,12 +959,12 @@ fn try_mount() -> bool { | |||
| 891 | let fsinfo_sec = u16::from_le_bytes([bpb[48], bpb[49]]) as u32; | 959 | let fsinfo_sec = u16::from_le_bytes([bpb[48], bpb[49]]) as u32; |
| 892 | if fat_size == 0 || root_cluster < 2 || reserved == 0 || fat_count == 0 { | 960 | if fat_size == 0 || root_cluster < 2 || reserved == 0 || fat_count == 0 { |
| 893 | crate::serial_println!("fat: not FAT32 (root={} fat={})", root_cluster, fat_size); | 961 | crate::serial_println!("fat: not FAT32 (root={} fat={})", root_cluster, fat_size); |
| 894 | return false; | 962 | return None; |
| 895 | } | 963 | } |
| 896 | let first_data = reserved as u32 + fat_count as u32 * fat_size; | 964 | let first_data = reserved as u32 + fat_count as u32 * fat_size; |
| 897 | if first_data >= total { | 965 | if first_data >= total { |
| 898 | crate::serial_println!("fat: bad geometry"); | 966 | crate::serial_println!("fat: bad geometry"); |
| 899 | return false; | 967 | return None; |
| 900 | } | 968 | } |
| 901 | let cluster_count = (total - first_data) / spc as u32; | 969 | let cluster_count = (total - first_data) / spc as u32; |
| 902 | 970 | ||
| @@ -909,7 +977,7 @@ fn try_mount() -> bool { | |||
| 909 | let n = ((fat.len() - done) / SECTOR).min(8) as u32; | 977 | let n = ((fat.len() - done) / SECTOR).min(8) as u32; |
| 910 | if ahci::read_sectors(lba, n).is_err() { | 978 | if ahci::read_sectors(lba, n).is_err() { |
| 911 | crate::serial_println!("fat: FAT read failed at lba {}", lba); | 979 | crate::serial_println!("fat: FAT read failed at lba {}", lba); |
| 912 | return false; | 980 | return None; |
| 913 | } | 981 | } |
| 914 | unsafe { | 982 | unsafe { |
| 915 | core::ptr::copy(ahci::buf(), fat.as_mut_ptr().add(done), n as usize * SECTOR); | 983 | core::ptr::copy(ahci::buf(), fat.as_mut_ptr().add(done), n as usize * SECTOR); |
| @@ -926,30 +994,45 @@ fn try_mount() -> bool { | |||
| 926 | e0 & 0xFF, | 994 | e0 & 0xFF, |
| 927 | e1 & EOC_MASK | 995 | e1 & EOC_MASK |
| 928 | ); | 996 | ); |
| 929 | return false; | 997 | return None; |
| 930 | } | 998 | } |
| 931 | unsafe { | 999 | // Volume identity: sanitized label + volume serial → "RSDOS-5BD1E55F". |
| 932 | VOL = Some(Volume { | 1000 | // Unique per format, stable across reboots, human-recognizable, and |
| 933 | sectors_per_cluster: spc, | 1001 | // machine-parseable (^[A-Z0-9_]+-[0-9A-F]{8}$). |
| 934 | reserved, | 1002 | let serial = u32::from_le_bytes([bpb[67], bpb[68], bpb[69], bpb[70]]); |
| 935 | fat_count, | 1003 | let mut label = String::new(); |
| 936 | fat_size, | 1004 | for &c in &bpb[71..82] { |
| 937 | root_cluster, | 1005 | if c == 0 || c == b' ' { |
| 938 | first_data, | 1006 | continue; // strip padding/NULs |
| 939 | cluster_count, | 1007 | } |
| 940 | fsinfo: (fsinfo_sec != 0 && fsinfo_sec < total).then_some(fsinfo_sec), | 1008 | let ch = (c as char).to_ascii_uppercase(); |
| 941 | fat, | 1009 | label.push(if ch.is_ascii_alphanumeric() { ch } else { '_' }); |
| 942 | }); | 1010 | } |
| 943 | } | 1011 | if label.is_empty() { |
| 944 | // Re-sync the FSInfo free-cluster count (may be stale from earlier | 1012 | label = String::from("DISK"); |
| 945 | // sessions that predate FSInfo maintenance). | 1013 | } |
| 946 | fsinfo_recount(); | 1014 | // Deduplicate against already-mounted volumes (e.g. cloned images). |
| 947 | let count = list_dir(root_cluster).map(|v| v.len()).unwrap_or(0); | 1015 | let mut uid = alloc::format!("{}-{:08X}", label, serial); |
| 948 | crate::serial_println!( | 1016 | let mut n = 2u32; |
| 949 | "fat: FAT32 mounted — {} clusters ({} bytes each), root: {} entries", | 1017 | loop { |
| 1018 | let taken = unsafe { VOLS.iter() }.any(|v| v.uid == uid); | ||
| 1019 | if !taken { | ||
| 1020 | break; | ||
| 1021 | } | ||
| 1022 | uid = alloc::format!("{}-{:08X}-{}", label, serial, n); | ||
| 1023 | n += 1; | ||
| 1024 | } | ||
| 1025 | Some(Volume { | ||
| 1026 | disk: d, | ||
| 1027 | uid, | ||
| 1028 | sectors_per_cluster: spc, | ||
| 1029 | reserved, | ||
| 1030 | fat_count, | ||
| 1031 | fat_size, | ||
| 1032 | root_cluster, | ||
| 1033 | first_data, | ||
| 950 | cluster_count, | 1034 | cluster_count, |
| 951 | cluster_size(), | 1035 | fsinfo: (fsinfo_sec != 0 && fsinfo_sec < total).then_some(fsinfo_sec), |
| 952 | count | 1036 | fat, |
| 953 | ); | 1037 | }) |
| 954 | true | ||
| 955 | } | 1038 | } |
