diff options
Diffstat (limited to '')
| -rw-r--r-- | rsnix/src/fat.rs | 130 |
1 files changed, 105 insertions, 25 deletions
diff --git a/rsnix/src/fat.rs b/rsnix/src/fat.rs index 52aa641..13d19df 100644 --- a/rsnix/src/fat.rs +++ b/rsnix/src/fat.rs | |||
| @@ -38,7 +38,6 @@ struct Volume { | |||
| 38 | uid: String, // unique mount name: "LABEL-XXXXXXXX" | 38 | uid: String, // unique mount name: "LABEL-XXXXXXXX" |
| 39 | sectors_per_cluster: u8, | 39 | sectors_per_cluster: u8, |
| 40 | reserved: u16, | 40 | reserved: u16, |
| 41 | fat_count: u8, | ||
| 42 | fat_size: u32, // sectors per FAT | 41 | fat_size: u32, // sectors per FAT |
| 43 | root_cluster: u32, | 42 | root_cluster: u32, |
| 44 | first_data: u32, // LBA of cluster 2 | 43 | first_data: u32, // LBA of cluster 2 |
| @@ -50,28 +49,36 @@ struct Volume { | |||
| 50 | static mut VOLS: Vec<Volume> = Vec::new(); | 49 | static mut VOLS: Vec<Volume> = Vec::new(); |
| 51 | static mut ACTIVE_VOL: usize = 0; | 50 | static mut ACTIVE_VOL: usize = 0; |
| 52 | 51 | ||
| 52 | /// Raw-pointer accessors for the volume statics. The kernel is | ||
| 53 | /// single-threaded, so `&'static` refs are sound; the `addr_of_mut!` | ||
| 54 | /// round-trip keeps `static_mut_refs` (warn today, deny in edition | ||
| 55 | /// 2024) quiet. | ||
| 56 | fn vols() -> &'static mut Vec<Volume> { | ||
| 57 | unsafe { &mut *core::ptr::addr_of_mut!(VOLS) } | ||
| 58 | } | ||
| 59 | |||
| 60 | fn active_vol() -> &'static mut usize { | ||
| 61 | unsafe { &mut *core::ptr::addr_of_mut!(ACTIVE_VOL) } | ||
| 62 | } | ||
| 63 | |||
| 53 | pub fn volume_count() -> usize { | 64 | pub fn volume_count() -> usize { |
| 54 | unsafe { VOLS.len() } | 65 | vols().len() |
| 55 | } | 66 | } |
| 56 | 67 | ||
| 57 | /// Unique mount name of volume `vol` ("RSDOS-5BD1E55F"). | 68 | /// Unique mount name of volume `vol` ("RSDOS-5BD1E55F"). |
| 58 | pub fn uid(vol: usize) -> &'static str { | 69 | pub fn uid(vol: usize) -> &'static str { |
| 59 | unsafe { VOLS.get(vol) } | 70 | vols().get(vol).map(|v| v.uid.as_str()).unwrap_or("") |
| 60 | .map(|v| v.uid.as_str()) | ||
| 61 | .unwrap_or("") | ||
| 62 | } | 71 | } |
| 63 | 72 | ||
| 64 | pub fn root_cluster(vol: usize) -> u32 { | 73 | pub fn root_cluster(vol: usize) -> u32 { |
| 65 | unsafe { VOLS.get(vol) } | 74 | vols().get(vol).map(|v| v.root_cluster).unwrap_or(0) |
| 66 | .map(|v| v.root_cluster) | ||
| 67 | .unwrap_or(0) | ||
| 68 | } | 75 | } |
| 69 | 76 | ||
| 70 | /// Point all FAT operations at volume `vol`; also selects the backing | 77 | /// Point all FAT operations at volume `vol`; also selects the backing |
| 71 | /// AHCI disk. Called by every public entry point (single-threaded | 78 | /// AHCI disk. Called by every public entry point (single-threaded |
| 72 | /// kernel, so a process-wide "current volume" is fine). | 79 | /// kernel, so a process-wide "current volume" is fine). |
| 73 | fn select_vol(vol: usize) -> Result<(), ()> { | 80 | fn select_vol(vol: usize) -> Result<(), ()> { |
| 74 | let disk = unsafe { VOLS.get(vol) }.map(|v| v.disk).ok_or(())?; | 81 | let disk = vols().get(vol).map(|v| v.disk).ok_or(())?; |
| 75 | unsafe { | 82 | unsafe { |
| 76 | ACTIVE_VOL = vol; | 83 | ACTIVE_VOL = vol; |
| 77 | } | 84 | } |
| @@ -97,7 +104,7 @@ fn write_sector(lba: u64, data: &[u8; SECTOR]) -> Result<(), ()> { | |||
| 97 | // --- cluster math ---------------------------------------------------------- | 104 | // --- cluster math ---------------------------------------------------------- |
| 98 | 105 | ||
| 99 | fn v() -> &'static mut Volume { | 106 | fn v() -> &'static mut Volume { |
| 100 | unsafe { VOLS.get_mut(ACTIVE_VOL) }.unwrap() | 107 | vols().get_mut(*active_vol()).unwrap() |
| 101 | } | 108 | } |
| 102 | 109 | ||
| 103 | fn spc() -> usize { | 110 | fn spc() -> usize { |
| @@ -351,13 +358,13 @@ fn scan_dir(dir_cluster: u32) -> Result<Vec<DirItem>, ()> { | |||
| 351 | return Err(()); // loop protection on a corrupt chain | 358 | return Err(()); // loop protection on a corrupt chain |
| 352 | } | 359 | } |
| 353 | read_cluster(c, &mut buf)?; | 360 | read_cluster(c, &mut buf)?; |
| 354 | let mut done = false; | ||
| 355 | for (i, e) in buf.chunks_exact(32).enumerate() { | 361 | for (i, e) in buf.chunks_exact(32).enumerate() { |
| 356 | match e[0] { | 362 | match e[0] { |
| 357 | 0x00 => { | 363 | // 0x00 ends the entries in THIS cluster, not the whole |
| 358 | done = true; | 364 | // directory: a dir that spills into a second cluster keeps |
| 359 | break; | 365 | // its 0x00 marker in the first cluster, so we must keep |
| 360 | } | 366 | // walking the chain to see the rest. |
| 367 | 0x00 => break, | ||
| 361 | 0xE5 => lfn.clear(), // deleted entry; drop any partial LFN | 368 | 0xE5 => lfn.clear(), // deleted entry; drop any partial LFN |
| 362 | _ => { | 369 | _ => { |
| 363 | let attr = e[11]; | 370 | let attr = e[11]; |
| @@ -415,9 +422,6 @@ fn scan_dir(dir_cluster: u32) -> Result<Vec<DirItem>, ()> { | |||
| 415 | } | 422 | } |
| 416 | } | 423 | } |
| 417 | } | 424 | } |
| 418 | if done { | ||
| 419 | break; | ||
| 420 | } | ||
| 421 | cur = next_cluster(c).ok(); | 425 | cur = next_cluster(c).ok(); |
| 422 | } | 426 | } |
| 423 | Ok(items) | 427 | Ok(items) |
| @@ -729,10 +733,77 @@ fn to_8_3(name: &str) -> Option<([u8; 11], bool)> { | |||
| 729 | Some((sfn, needs)) | 733 | Some((sfn, needs)) |
| 730 | } | 734 | } |
| 731 | 735 | ||
| 736 | /// Collect the raw short names already present in a directory. | ||
| 737 | fn dir_short_names(parent: u32) -> Result<Vec<[u8; 11]>, ()> { | ||
| 738 | let cs = cluster_size(); | ||
| 739 | let mut buf = vec![0u8; cs]; | ||
| 740 | let mut out = Vec::new(); | ||
| 741 | let mut cur: Option<u32> = Some(parent); | ||
| 742 | let mut guard = 0; | ||
| 743 | while let Some(c) = cur { | ||
| 744 | guard += 1; | ||
| 745 | if guard > 1_000_000 { | ||
| 746 | return Err(()); // loop protection on a corrupt chain | ||
| 747 | } | ||
| 748 | read_cluster(c, &mut buf)?; | ||
| 749 | for e in buf.chunks_exact(32) { | ||
| 750 | match e[0] { | ||
| 751 | // Same rule as scan_dir: 0x00 ends this cluster's entries | ||
| 752 | // but the directory may continue in the next cluster. | ||
| 753 | 0x00 => break, | ||
| 754 | 0xE5 => {} | ||
| 755 | _ => { | ||
| 756 | if e[11] != 0x0F { | ||
| 757 | let mut sfn = [0u8; 11]; | ||
| 758 | sfn.copy_from_slice(&e[0..11]); | ||
| 759 | out.push(sfn); | ||
| 760 | } | ||
| 761 | } | ||
| 762 | } | ||
| 763 | } | ||
| 764 | cur = next_cluster(c).ok(); | ||
| 765 | } | ||
| 766 | Ok(out) | ||
| 767 | } | ||
| 768 | |||
| 769 | /// Make `base` unique within a directory: on collision, append a numeric | ||
| 770 | /// ~N tail (DISK-TE~1, DISK-TE~2, …) — the standard FAT short-name | ||
| 771 | /// disambiguation that fsck and other OSes expect. Without it, two files | ||
| 772 | /// whose long names share an 8.3 prefix would get the same short name and | ||
| 773 | /// fsck reports them as duplicate directory entries. | ||
| 774 | fn unique_short_name(base: &[u8; 11], used: &[[u8; 11]]) -> [u8; 11] { | ||
| 775 | if !used.contains(base) { | ||
| 776 | return *base; | ||
| 777 | } | ||
| 778 | let mut name_len = 0; | ||
| 779 | while name_len < 8 && base[name_len] != b' ' { | ||
| 780 | name_len += 1; | ||
| 781 | } | ||
| 782 | for n in 1..=999u32 { | ||
| 783 | let tail = alloc::format!("~{}", n); | ||
| 784 | let tb = tail.as_bytes(); | ||
| 785 | let keep = name_len.saturating_sub(tb.len()); | ||
| 786 | let mut cand = [b' '; 11]; | ||
| 787 | cand[..keep].copy_from_slice(&base[..keep]); | ||
| 788 | cand[keep..keep + tb.len()].copy_from_slice(tb); | ||
| 789 | cand[8..11].copy_from_slice(&base[8..11]); | ||
| 790 | if !used.contains(&cand) { | ||
| 791 | return cand; | ||
| 792 | } | ||
| 793 | } | ||
| 794 | *base // pathological; keep the colliding name | ||
| 795 | } | ||
| 796 | |||
| 732 | /// Write the LFN entries (if any) + SFN entry for `name` into the parent | 797 | /// Write the LFN entries (if any) + SFN entry for `name` into the parent |
| 733 | /// directory, extending its chain with a fresh zeroed cluster if needed. | 798 | /// directory, extending its chain with a fresh zeroed cluster if needed. |
| 734 | fn add_dir_entry(parent: u32, name: &str, is_dir: bool, cluster: u32) -> Result<(), ()> { | 799 | fn add_dir_entry(parent: u32, name: &str, is_dir: bool, cluster: u32) -> Result<(), ()> { |
| 735 | let (sfn, needs_lfn) = to_8_3(name).ok_or(())?; | 800 | let (base_sfn, mut needs_lfn) = to_8_3(name).ok_or(())?; |
| 801 | // Short names must be unique within the directory; a plain truncation | ||
| 802 | // collides when two long names share an 8.3 prefix. | ||
| 803 | let sfn = unique_short_name(&base_sfn, &dir_short_names(parent)?); | ||
| 804 | if sfn != base_sfn { | ||
| 805 | needs_lfn = true; // keep the original name via an LFN entry | ||
| 806 | } | ||
| 736 | let lfn_chars: Vec<u16> = name.encode_utf16().collect(); | 807 | let lfn_chars: Vec<u16> = name.encode_utf16().collect(); |
| 737 | let groups = if needs_lfn { | 808 | let groups = if needs_lfn { |
| 738 | lfn_chars.len().div_ceil(13) | 809 | lfn_chars.len().div_ceil(13) |
| @@ -810,6 +881,18 @@ fn add_dir_entry(parent: u32, name: &str, is_dir: bool, cluster: u32) -> Result< | |||
| 810 | write_cluster(c, &buf)?; | 881 | write_cluster(c, &buf)?; |
| 811 | return Ok(()); | 882 | return Ok(()); |
| 812 | } | 883 | } |
| 884 | // Chain full: pack this cluster before extending. A mid-chain | ||
| 885 | // 0x00 would look like the end of the directory to other FAT | ||
| 886 | // readers (Windows, mtools stop at the first 0x00), hiding every | ||
| 887 | // entry written into the new cluster. Marking the free slots as | ||
| 888 | // 0xE5 (deleted) keeps them skippable while the chain walk finds | ||
| 889 | // the continuation. | ||
| 890 | for i in 0..slots { | ||
| 891 | if buf[i * 32] == 0x00 { | ||
| 892 | buf[i * 32] = 0xE5; | ||
| 893 | } | ||
| 894 | } | ||
| 895 | write_cluster(c, &buf)?; | ||
| 813 | // Chain full: extend with a fresh zeroed cluster and retry. | 896 | // Chain full: extend with a fresh zeroed cluster and retry. |
| 814 | match next_cluster(c) { | 897 | match next_cluster(c) { |
| 815 | Ok(n) => cur = Some(n), | 898 | Ok(n) => cur = Some(n), |
| @@ -910,10 +993,8 @@ pub fn init() -> bool { | |||
| 910 | match try_mount(d) { | 993 | match try_mount(d) { |
| 911 | Some(volume) => { | 994 | Some(volume) => { |
| 912 | let uid = volume.uid.clone(); | 995 | let uid = volume.uid.clone(); |
| 913 | let vol = unsafe { | 996 | vols().push(volume); |
| 914 | VOLS.push(volume); | 997 | let vol = vols().len() - 1; |
| 915 | VOLS.len() - 1 | ||
| 916 | }; | ||
| 917 | // Re-sync the FSInfo free-cluster count (may be stale | 998 | // Re-sync the FSInfo free-cluster count (may be stale |
| 918 | // from earlier sessions). | 999 | // from earlier sessions). |
| 919 | if select_vol(vol).is_ok() { | 1000 | if select_vol(vol).is_ok() { |
| @@ -1026,7 +1107,7 @@ fn try_mount(d: usize) -> Option<Volume> { | |||
| 1026 | let mut uid = alloc::format!("{}-{:08X}", label, serial); | 1107 | let mut uid = alloc::format!("{}-{:08X}", label, serial); |
| 1027 | let mut n = 2u32; | 1108 | let mut n = 2u32; |
| 1028 | loop { | 1109 | loop { |
| 1029 | let taken = unsafe { VOLS.iter() }.any(|v| v.uid == uid); | 1110 | let taken = vols().iter().any(|v| v.uid == uid); |
| 1030 | if !taken { | 1111 | if !taken { |
| 1031 | break; | 1112 | break; |
| 1032 | } | 1113 | } |
| @@ -1038,7 +1119,6 @@ fn try_mount(d: usize) -> Option<Volume> { | |||
| 1038 | uid, | 1119 | uid, |
| 1039 | sectors_per_cluster: spc, | 1120 | sectors_per_cluster: spc, |
| 1040 | reserved, | 1121 | reserved, |
| 1041 | fat_count, | ||
| 1042 | fat_size, | 1122 | fat_size, |
| 1043 | root_cluster, | 1123 | root_cluster, |
| 1044 | first_data, | 1124 | first_data, |
