diff options
Diffstat (limited to 'rsnix/src/fat.rs')
| -rw-r--r-- | rsnix/src/fat.rs | 934 |
1 files changed, 934 insertions, 0 deletions
diff --git a/rsnix/src/fat.rs b/rsnix/src/fat.rs new file mode 100644 index 0000000..70fa688 --- /dev/null +++ b/rsnix/src/fat.rs | |||
| @@ -0,0 +1,934 @@ | |||
| 1 | // --------------------------------------------------------------------------- | ||
| 2 | // FAT32 — read/write a FAT32 volume over the AHCI sector interface. | ||
| 3 | // | ||
| 4 | // Mounts the volume's boot sector (BPB), caches the whole FAT in RAM, | ||
| 5 | // follows cluster chains, and exposes file/directory operations used by | ||
| 6 | // the VFS's Disk nodes: list, find, read, write, truncate, create (files | ||
| 7 | // and dirs), remove. Long filenames are read back and written for names | ||
| 8 | // that don't fit 8.3; short names are generated the standard way | ||
| 9 | // (uppercased, 8+3, "~N" truncation). All timestamps are zero — this is | ||
| 10 | // a toy kernel, not a backup tool. | ||
| 11 | // | ||
| 12 | // Single-threaded like the rest of the kernel; no caching beyond the FAT | ||
| 13 | // itself, so every op re-reads directory sectors (cheap: the disk is | ||
| 14 | // polled DMA at µs scale in QEMU). | ||
| 15 | // --------------------------------------------------------------------------- | ||
| 16 | |||
| 17 | extern crate alloc; | ||
| 18 | |||
| 19 | use crate::ahci; | ||
| 20 | use alloc::string::String; | ||
| 21 | use alloc::vec; | ||
| 22 | use alloc::vec::Vec; | ||
| 23 | |||
| 24 | const SECTOR: usize = 512; | ||
| 25 | const EOC_MASK: u32 = 0x0FFF_FFFF; | ||
| 26 | const EOC: u32 = 0x0FFF_FFFF; | ||
| 27 | |||
| 28 | /// One directory-listed entry (or a resolved file). | ||
| 29 | pub struct Entry { | ||
| 30 | pub name: String, | ||
| 31 | pub is_dir: bool, | ||
| 32 | pub size: u64, | ||
| 33 | pub cluster: u32, | ||
| 34 | } | ||
| 35 | |||
| 36 | struct Volume { | ||
| 37 | sectors_per_cluster: u8, | ||
| 38 | reserved: u16, | ||
| 39 | fat_count: u8, | ||
| 40 | fat_size: u32, // sectors per FAT | ||
| 41 | root_cluster: u32, | ||
| 42 | first_data: u32, // LBA of cluster 2 | ||
| 43 | cluster_count: u32, | ||
| 44 | fsinfo: Option<u32>, // sector number of the FSInfo structure | ||
| 45 | fat: Vec<u8>, // FAT 1 cached in RAM | ||
| 46 | } | ||
| 47 | |||
| 48 | static mut VOL: Option<Volume> = None; | ||
| 49 | |||
| 50 | pub fn is_mounted() -> bool { | ||
| 51 | unsafe { VOL.is_some() } | ||
| 52 | } | ||
| 53 | |||
| 54 | pub fn root_cluster() -> u32 { | ||
| 55 | unsafe { VOL.as_ref() }.map(|v| v.root_cluster).unwrap_or(0) | ||
| 56 | } | ||
| 57 | |||
| 58 | // --- sector I/O ----------------------------------------------------------- | ||
| 59 | |||
| 60 | fn read_sector(lba: u64, out: &mut [u8; SECTOR]) -> Result<(), ()> { | ||
| 61 | ahci::read_sectors(lba, 1)?; | ||
| 62 | unsafe { core::ptr::copy(ahci::buf(), out.as_mut_ptr(), SECTOR) }; | ||
| 63 | Ok(()) | ||
| 64 | } | ||
| 65 | |||
| 66 | fn write_sector(lba: u64, data: &[u8; SECTOR]) -> Result<(), ()> { | ||
| 67 | unsafe { core::ptr::copy(data.as_ptr(), ahci::buf(), SECTOR) }; | ||
| 68 | ahci::write_sectors(lba, 1) | ||
| 69 | } | ||
| 70 | |||
| 71 | // --- cluster math ---------------------------------------------------------- | ||
| 72 | |||
| 73 | fn v() -> &'static mut Volume { | ||
| 74 | unsafe { VOL.as_mut() }.unwrap() | ||
| 75 | } | ||
| 76 | |||
| 77 | fn spc() -> usize { | ||
| 78 | v().sectors_per_cluster as usize | ||
| 79 | } | ||
| 80 | |||
| 81 | fn cluster_size() -> usize { | ||
| 82 | spc() * SECTOR | ||
| 83 | } | ||
| 84 | |||
| 85 | fn cluster_lba(c: u32) -> u64 { | ||
| 86 | v().first_data as u64 + ((c as u64 - 2) * spc() as u64) | ||
| 87 | } | ||
| 88 | |||
| 89 | fn fat_off(c: u32) -> usize { | ||
| 90 | c as usize * 4 | ||
| 91 | } | ||
| 92 | |||
| 93 | fn read_fat_entry(c: u32) -> u32 { | ||
| 94 | let off = fat_off(c); | ||
| 95 | u32::from_le_bytes([ | ||
| 96 | v().fat[off], | ||
| 97 | v().fat[off + 1], | ||
| 98 | v().fat[off + 2], | ||
| 99 | v().fat[off + 3], | ||
| 100 | ]) & EOC_MASK | ||
| 101 | } | ||
| 102 | |||
| 103 | /// Update a FAT entry in the cache and write the affected sector back to | ||
| 104 | /// both FAT copies. | ||
| 105 | fn write_fat_entry(c: u32, val: u32) -> Result<(), ()> { | ||
| 106 | let off = fat_off(c); | ||
| 107 | v().fat[off..off + 4].copy_from_slice(&(val & EOC_MASK).to_le_bytes()); | ||
| 108 | let sector_idx = off / SECTOR; | ||
| 109 | let mut buf = [0u8; SECTOR]; | ||
| 110 | buf.copy_from_slice(&v().fat[sector_idx * SECTOR..(sector_idx + 1) * SECTOR]); | ||
| 111 | let lba1 = v().reserved as u64 + sector_idx as u64; | ||
| 112 | let lba2 = lba1 + v().fat_size as u64; | ||
| 113 | write_sector(lba1, &buf)?; | ||
| 114 | write_sector(lba2, &buf) | ||
| 115 | } | ||
| 116 | |||
| 117 | fn is_eoc(c: u32) -> bool { | ||
| 118 | c >= 0x0FFF_FFF8 | ||
| 119 | } | ||
| 120 | |||
| 121 | fn next_cluster(c: u32) -> Result<u32, ()> { | ||
| 122 | let n = read_fat_entry(c); | ||
| 123 | if is_eoc(n) { | ||
| 124 | Err(()) | ||
| 125 | } else { | ||
| 126 | Ok(n) | ||
| 127 | } | ||
| 128 | } | ||
| 129 | |||
| 130 | /// Allocate a fresh cluster: mark it EOC in the FAT and zero its data. | ||
| 131 | fn alloc_cluster() -> Result<u32, ()> { | ||
| 132 | let total = v().cluster_count + 2; | ||
| 133 | for c in 2..total { | ||
| 134 | if read_fat_entry(c) == 0 { | ||
| 135 | write_fat_entry(c, EOC)?; | ||
| 136 | let zero = vec![0u8; cluster_size()]; | ||
| 137 | write_cluster(c, &zero)?; | ||
| 138 | fsinfo_adjust(-1); | ||
| 139 | return Ok(c); | ||
| 140 | } | ||
| 141 | } | ||
| 142 | crate::serial_println!("fat: disk full"); | ||
| 143 | Err(()) | ||
| 144 | } | ||
| 145 | |||
| 146 | fn free_chain(mut c: u32) { | ||
| 147 | let mut freed = 0u64; | ||
| 148 | let mut guard = 0; | ||
| 149 | while !is_eoc(read_fat_entry(c)) && guard < 1_000_000 { | ||
| 150 | let nxt = read_fat_entry(c); | ||
| 151 | let _ = write_fat_entry(c, 0); | ||
| 152 | c = nxt; | ||
| 153 | freed += 1; | ||
| 154 | guard += 1; | ||
| 155 | } | ||
| 156 | let _ = write_fat_entry(c, 0); | ||
| 157 | freed += 1; | ||
| 158 | if freed > 0 { | ||
| 159 | fsinfo_adjust(freed as i64); | ||
| 160 | } | ||
| 161 | } | ||
| 162 | |||
| 163 | /// Keep FSInfo.FSFreeCount in sync with cluster allocations/frees so | ||
| 164 | /// fsck and other tools see an accurate free count. Falls back to a full | ||
| 165 | /// recount when the stored value is the "unknown" marker. | ||
| 166 | fn fsinfo_adjust(delta: i64) { | ||
| 167 | let Some(s) = v().fsinfo else { | ||
| 168 | return; | ||
| 169 | }; | ||
| 170 | let mut b = [0u8; SECTOR]; | ||
| 171 | if read_sector(s as u64, &mut b).is_err() { | ||
| 172 | return; | ||
| 173 | } | ||
| 174 | if &b[0..4] != b"RRaA" || &b[484..488] != b"rrAa" { | ||
| 175 | return; // not a valid FSInfo structure | ||
| 176 | } | ||
| 177 | let mut n = u32::from_le_bytes([b[488], b[489], b[490], b[491]]); | ||
| 178 | if n == 0xFFFF_FFFF { | ||
| 179 | n = free_clusters(); | ||
| 180 | } | ||
| 181 | let n = (n as i64 + delta).max(0) as u32; | ||
| 182 | b[488..492].copy_from_slice(&n.to_le_bytes()); | ||
| 183 | let _ = write_sector(s as u64, &b); | ||
| 184 | } | ||
| 185 | |||
| 186 | /// Rewrite FSInfo.FSFreeCount with the true free-cluster count. | ||
| 187 | fn fsinfo_recount() { | ||
| 188 | let Some(s) = v().fsinfo else { | ||
| 189 | return; | ||
| 190 | }; | ||
| 191 | let mut b = [0u8; SECTOR]; | ||
| 192 | if read_sector(s as u64, &mut b).is_err() { | ||
| 193 | return; | ||
| 194 | } | ||
| 195 | if &b[0..4] != b"RRaA" || &b[484..488] != b"rrAa" { | ||
| 196 | return; | ||
| 197 | } | ||
| 198 | let n = free_clusters(); | ||
| 199 | b[488..492].copy_from_slice(&n.to_le_bytes()); | ||
| 200 | let _ = write_sector(s as u64, &b); | ||
| 201 | } | ||
| 202 | |||
| 203 | /// Count free clusters by scanning the cached FAT. | ||
| 204 | fn free_clusters() -> u32 { | ||
| 205 | let mut n = 0u32; | ||
| 206 | for c in 2..v().cluster_count + 2 { | ||
| 207 | if read_fat_entry(c) == 0 { | ||
| 208 | n += 1; | ||
| 209 | } | ||
| 210 | } | ||
| 211 | n | ||
| 212 | } | ||
| 213 | |||
| 214 | fn read_cluster(c: u32, buf: &mut [u8]) -> Result<(), ()> { | ||
| 215 | if spc() <= 8 { | ||
| 216 | ahci::read_sectors(cluster_lba(c), spc() as u32)?; | ||
| 217 | unsafe { core::ptr::copy(ahci::buf(), buf.as_mut_ptr(), cluster_size()) }; | ||
| 218 | } else { | ||
| 219 | for s in 0..spc() { | ||
| 220 | ahci::read_sectors(cluster_lba(c) + s as u64, 1)?; | ||
| 221 | unsafe { | ||
| 222 | core::ptr::copy(ahci::buf(), buf.as_mut_ptr().add(s * SECTOR), SECTOR); | ||
| 223 | } | ||
| 224 | } | ||
| 225 | } | ||
| 226 | Ok(()) | ||
| 227 | } | ||
| 228 | |||
| 229 | fn write_cluster(c: u32, buf: &[u8]) -> Result<(), ()> { | ||
| 230 | if spc() <= 8 { | ||
| 231 | unsafe { core::ptr::copy(buf.as_ptr(), ahci::buf(), cluster_size()) }; | ||
| 232 | ahci::write_sectors(cluster_lba(c), spc() as u32) | ||
| 233 | } else { | ||
| 234 | for s in 0..spc() { | ||
| 235 | unsafe { | ||
| 236 | core::ptr::copy(buf.as_ptr().add(s * SECTOR), ahci::buf(), SECTOR); | ||
| 237 | } | ||
| 238 | ahci::write_sectors(cluster_lba(c) + s as u64, 1)?; | ||
| 239 | } | ||
| 240 | Ok(()) | ||
| 241 | } | ||
| 242 | } | ||
| 243 | |||
| 244 | // --- directory scanning ---------------------------------------------------- | ||
| 245 | |||
| 246 | /// Checksum of an SFN's 11 bytes; used to bind LFN entries to their SFN. | ||
| 247 | fn lfn_checksum(sfn: &[u8; 11]) -> u8 { | ||
| 248 | let mut s: u8 = 0; | ||
| 249 | for &b in sfn { | ||
| 250 | s = s.rotate_right(1).wrapping_add(b); | ||
| 251 | } | ||
| 252 | s | ||
| 253 | } | ||
| 254 | |||
| 255 | fn sfn_decode(e: &[u8]) -> String { | ||
| 256 | let nts = e[12]; | ||
| 257 | let mut s = String::new(); | ||
| 258 | for &b in &e[0..8] { | ||
| 259 | if b == b' ' || b == 0 { | ||
| 260 | break; | ||
| 261 | } | ||
| 262 | let c = if nts & 0x08 != 0 { | ||
| 263 | b.to_ascii_lowercase() | ||
| 264 | } else { | ||
| 265 | b | ||
| 266 | }; | ||
| 267 | s.push(c as char); | ||
| 268 | } | ||
| 269 | let mut ext = String::new(); | ||
| 270 | for &b in &e[8..11] { | ||
| 271 | if b == b' ' || b == 0 { | ||
| 272 | break; | ||
| 273 | } | ||
| 274 | let c = if nts & 0x10 != 0 { | ||
| 275 | b.to_ascii_lowercase() | ||
| 276 | } else { | ||
| 277 | b | ||
| 278 | }; | ||
| 279 | ext.push(c as char); | ||
| 280 | } | ||
| 281 | if !ext.is_empty() { | ||
| 282 | s.push('.'); | ||
| 283 | s.push_str(&ext); | ||
| 284 | } | ||
| 285 | if s.is_empty() { | ||
| 286 | s.push('?'); | ||
| 287 | } | ||
| 288 | s | ||
| 289 | } | ||
| 290 | |||
| 291 | fn utf16_to_string(v: &[u16]) -> String { | ||
| 292 | let mut s = String::new(); | ||
| 293 | for &c in v { | ||
| 294 | if c == 0 { | ||
| 295 | break; | ||
| 296 | } | ||
| 297 | if let Some(ch) = char::from_u32(c as u32) { | ||
| 298 | s.push(ch); | ||
| 299 | } | ||
| 300 | } | ||
| 301 | s | ||
| 302 | } | ||
| 303 | |||
| 304 | struct DirItem { | ||
| 305 | name: String, | ||
| 306 | attr: u8, | ||
| 307 | cluster: u32, | ||
| 308 | size: u32, | ||
| 309 | sector: u64, // LBA of the entry's sector (for in-place updates) | ||
| 310 | offset: usize, | ||
| 311 | } | ||
| 312 | |||
| 313 | /// Scan a directory's cluster chain, reassembling long filenames. | ||
| 314 | fn scan_dir(dir_cluster: u32) -> Result<Vec<DirItem>, ()> { | ||
| 315 | let cs = cluster_size(); | ||
| 316 | let mut buf = vec![0u8; cs]; | ||
| 317 | let mut items = Vec::new(); | ||
| 318 | let mut lfn: Vec<u16> = Vec::new(); | ||
| 319 | let mut lfn_sum = 0u8; | ||
| 320 | let mut cur: Option<u32> = Some(dir_cluster); | ||
| 321 | let mut guard = 0; | ||
| 322 | while let Some(c) = cur { | ||
| 323 | guard += 1; | ||
| 324 | if guard > 1_000_000 { | ||
| 325 | return Err(()); // loop protection on a corrupt chain | ||
| 326 | } | ||
| 327 | read_cluster(c, &mut buf)?; | ||
| 328 | let mut done = false; | ||
| 329 | for (i, e) in buf.chunks_exact(32).enumerate() { | ||
| 330 | match e[0] { | ||
| 331 | 0x00 => { | ||
| 332 | done = true; | ||
| 333 | break; | ||
| 334 | } | ||
| 335 | 0xE5 => lfn.clear(), // deleted entry; drop any partial LFN | ||
| 336 | _ => { | ||
| 337 | let attr = e[11]; | ||
| 338 | if attr == 0x0F { | ||
| 339 | // LFN entry: 13 UTF-16 chars (bytes 1-10, 14-25, 28-31). | ||
| 340 | for k in 0..13 { | ||
| 341 | let pos = if k < 5 { | ||
| 342 | 1 + k * 2 | ||
| 343 | } else if k < 11 { | ||
| 344 | 14 + (k - 5) * 2 | ||
| 345 | } else { | ||
| 346 | 28 + (k - 11) * 2 | ||
| 347 | }; | ||
| 348 | let ch = u16::from_le_bytes([e[pos], e[pos + 1]]); | ||
| 349 | if ch == 0x0000 { | ||
| 350 | break; | ||
| 351 | } | ||
| 352 | if ch != 0xFFFF { | ||
| 353 | lfn.push(ch); | ||
| 354 | } | ||
| 355 | } | ||
| 356 | lfn_sum = e[13]; | ||
| 357 | } else { | ||
| 358 | // Short-name entry: bind any collected LFN. | ||
| 359 | let mut sfn = [0u8; 11]; | ||
| 360 | sfn.copy_from_slice(&e[0..11]); | ||
| 361 | let name = if !lfn.is_empty() && lfn_checksum(&sfn) == lfn_sum { | ||
| 362 | utf16_to_string(&lfn) | ||
| 363 | } else { | ||
| 364 | sfn_decode(e) | ||
| 365 | }; | ||
| 366 | let cluster = u16::from_le_bytes([e[26], e[27]]) as u32 | ||
| 367 | | ((u16::from_le_bytes([e[20], e[21]]) as u32) << 16); | ||
| 368 | items.push(DirItem { | ||
| 369 | name, | ||
| 370 | attr, | ||
| 371 | cluster, | ||
| 372 | size: u32::from_le_bytes([e[28], e[29], e[30], e[31]]), | ||
| 373 | sector: cluster_lba(c) + (i * 32 / SECTOR) as u64, | ||
| 374 | offset: (i * 32) % SECTOR, | ||
| 375 | }); | ||
| 376 | lfn.clear(); | ||
| 377 | } | ||
| 378 | } | ||
| 379 | } | ||
| 380 | } | ||
| 381 | if done { | ||
| 382 | break; | ||
| 383 | } | ||
| 384 | cur = next_cluster(c).ok(); | ||
| 385 | } | ||
| 386 | Ok(items) | ||
| 387 | } | ||
| 388 | |||
| 389 | /// Public listing: no `.` / `..`. | ||
| 390 | pub fn list_dir(dir_cluster: u32) -> Result<Vec<Entry>, ()> { | ||
| 391 | Ok(scan_dir(dir_cluster)? | ||
| 392 | .into_iter() | ||
| 393 | .filter(|i| i.name != "." && i.name != "..") | ||
| 394 | .map(|i| Entry { | ||
| 395 | name: i.name, | ||
| 396 | is_dir: i.attr & 0x10 != 0, | ||
| 397 | size: i.size as u64, | ||
| 398 | cluster: i.cluster, | ||
| 399 | }) | ||
| 400 | .collect()) | ||
| 401 | } | ||
| 402 | |||
| 403 | /// Find an entry by name (case-insensitive), returning the scan record | ||
| 404 | /// (which knows the entry's sector for in-place updates). | ||
| 405 | fn find_item(parent_dir_cluster: u32, name: &str) -> Result<DirItem, ()> { | ||
| 406 | for i in scan_dir(parent_dir_cluster)? { | ||
| 407 | if i.name.eq_ignore_ascii_case(name) { | ||
| 408 | return Ok(i); | ||
| 409 | } | ||
| 410 | } | ||
| 411 | Err(()) | ||
| 412 | } | ||
| 413 | |||
| 414 | /// Find an entry by name (case-insensitive). | ||
| 415 | pub fn find(parent_dir_cluster: u32, name: &str) -> Result<Entry, ()> { | ||
| 416 | find_item(parent_dir_cluster, name).map(|i| Entry { | ||
| 417 | name: i.name, | ||
| 418 | is_dir: i.attr & 0x10 != 0, | ||
| 419 | size: i.size as u64, | ||
| 420 | cluster: i.cluster, | ||
| 421 | }) | ||
| 422 | } | ||
| 423 | |||
| 424 | // --- file data -------------------------------------------------------------- | ||
| 425 | |||
| 426 | pub fn read_file( | ||
| 427 | parent_dir_cluster: u32, | ||
| 428 | name: &str, | ||
| 429 | buf: &mut [u8], | ||
| 430 | pos: u64, | ||
| 431 | ) -> Result<usize, ()> { | ||
| 432 | if buf.is_empty() { | ||
| 433 | return Ok(0); | ||
| 434 | } | ||
| 435 | let it = find(parent_dir_cluster, name)?; | ||
| 436 | if it.is_dir || it.cluster == 0 { | ||
| 437 | return Err(()); | ||
| 438 | } | ||
| 439 | if pos >= it.size { | ||
| 440 | return Ok(0); | ||
| 441 | } | ||
| 442 | let cs = cluster_size() as u64; | ||
| 443 | let mut cluster = it.cluster; | ||
| 444 | let mut skip = pos; | ||
| 445 | while skip >= cs { | ||
| 446 | cluster = next_cluster(cluster)?; | ||
| 447 | skip -= cs; | ||
| 448 | } | ||
| 449 | let mut cbuf = vec![0u8; cluster_size()]; | ||
| 450 | let mut remaining = buf.len() as u64; | ||
| 451 | if remaining > it.size - pos { | ||
| 452 | remaining = it.size - pos; | ||
| 453 | } | ||
| 454 | let mut out = 0usize; | ||
| 455 | while remaining > 0 { | ||
| 456 | read_cluster(cluster, &mut cbuf)?; | ||
| 457 | let from = skip as usize; | ||
| 458 | let n = ((cs as usize) - from).min(remaining as usize); | ||
| 459 | buf[out..out + n].copy_from_slice(&cbuf[from..from + n]); | ||
| 460 | out += n; | ||
| 461 | remaining -= n as u64; | ||
| 462 | if remaining > 0 { | ||
| 463 | cluster = next_cluster(cluster)?; | ||
| 464 | } | ||
| 465 | skip = 0; | ||
| 466 | } | ||
| 467 | Ok(out) | ||
| 468 | } | ||
| 469 | |||
| 470 | pub fn write_file(parent_dir_cluster: u32, name: &str, buf: &[u8], pos: u64) -> Result<usize, ()> { | ||
| 471 | if buf.is_empty() { | ||
| 472 | return Ok(0); | ||
| 473 | } | ||
| 474 | let it = find_item(parent_dir_cluster, name)?; | ||
| 475 | if it.attr & 0x10 != 0 { | ||
| 476 | return Err(()); | ||
| 477 | } | ||
| 478 | let cs = cluster_size() as u64; | ||
| 479 | let end = pos + buf.len() as u64; | ||
| 480 | |||
| 481 | // Ensure a first cluster exists. | ||
| 482 | let mut cluster = it.cluster; | ||
| 483 | if cluster == 0 { | ||
| 484 | cluster = alloc_cluster()?; | ||
| 485 | update_entry(&it, Some(cluster), None)?; | ||
| 486 | } | ||
| 487 | // Walk to the cluster containing `pos`, extending the chain as needed. | ||
| 488 | let mut skip = pos; | ||
| 489 | let mut cur = cluster; | ||
| 490 | while skip >= cs { | ||
| 491 | match next_cluster(cur) { | ||
| 492 | Ok(n) => cur = n, | ||
| 493 | Err(_) => { | ||
| 494 | let n = alloc_cluster()?; | ||
| 495 | write_fat_entry(cur, n)?; | ||
| 496 | cur = n; | ||
| 497 | } | ||
| 498 | } | ||
| 499 | skip -= cs; | ||
| 500 | } | ||
| 501 | // Write the data. | ||
| 502 | let mut cbuf = vec![0u8; cluster_size()]; | ||
| 503 | let mut remaining = buf.len() as u64; | ||
| 504 | let mut in_off = 0usize; | ||
| 505 | while remaining > 0 { | ||
| 506 | read_cluster(cur, &mut cbuf)?; | ||
| 507 | let from = skip as usize; | ||
| 508 | let n = ((cs as usize) - from).min(remaining as usize); | ||
| 509 | cbuf[from..from + n].copy_from_slice(&buf[in_off..in_off + n]); | ||
| 510 | write_cluster(cur, &cbuf)?; | ||
| 511 | in_off += n; | ||
| 512 | remaining -= n as u64; | ||
| 513 | if remaining > 0 { | ||
| 514 | // Make sure the chain continues past this cluster. | ||
| 515 | match next_cluster(cur) { | ||
| 516 | Ok(n) => cur = n, | ||
| 517 | Err(_) => { | ||
| 518 | let n = alloc_cluster()?; | ||
| 519 | write_fat_entry(cur, n)?; | ||
| 520 | cur = n; | ||
| 521 | } | ||
| 522 | } | ||
| 523 | } | ||
| 524 | skip = 0; | ||
| 525 | } | ||
| 526 | if end > it.size as u64 { | ||
| 527 | update_entry(&it, None, Some(end as u32))?; | ||
| 528 | } | ||
| 529 | Ok(buf.len()) | ||
| 530 | } | ||
| 531 | |||
| 532 | pub fn set_len(parent_dir_cluster: u32, name: &str, len: u64) -> Result<(), ()> { | ||
| 533 | let it = find_item(parent_dir_cluster, name)?; | ||
| 534 | if it.attr & 0x10 != 0 { | ||
| 535 | return Err(()); | ||
| 536 | } | ||
| 537 | let cs = cluster_size() as u64; | ||
| 538 | let needed = if len == 0 { | ||
| 539 | 0 | ||
| 540 | } else { | ||
| 541 | ((len - 1) / cs + 1) as usize | ||
| 542 | }; | ||
| 543 | let have = chain_len(it.cluster); | ||
| 544 | if it.cluster == 0 { | ||
| 545 | if needed == 0 { | ||
| 546 | update_entry(&it, None, Some(0))?; | ||
| 547 | } else { | ||
| 548 | let first = alloc_cluster()?; | ||
| 549 | if needed > 1 { | ||
| 550 | extend_chain(first, needed - 1)?; | ||
| 551 | } | ||
| 552 | update_entry(&it, Some(first), Some(len as u32))?; | ||
| 553 | } | ||
| 554 | } else if needed < have { | ||
| 555 | truncate_chain(it.cluster, needed); | ||
| 556 | update_entry( | ||
| 557 | &it, | ||
| 558 | if needed == 0 { Some(0) } else { None }, | ||
| 559 | Some(len as u32), | ||
| 560 | )?; | ||
| 561 | } else if needed > have { | ||
| 562 | extend_chain(it.cluster, needed - have)?; | ||
| 563 | update_entry(&it, None, Some(len as u32))?; | ||
| 564 | } else { | ||
| 565 | update_entry(&it, None, Some(len as u32))?; | ||
| 566 | } | ||
| 567 | Ok(()) | ||
| 568 | } | ||
| 569 | |||
| 570 | fn chain_len(mut c: u32) -> usize { | ||
| 571 | let mut n = 0usize; | ||
| 572 | let mut guard = 0; | ||
| 573 | while c != 0 && !is_eoc(read_fat_entry(c)) && guard < 1_000_000 { | ||
| 574 | c = read_fat_entry(c); | ||
| 575 | n += 1; | ||
| 576 | guard += 1; | ||
| 577 | } | ||
| 578 | if c != 0 { | ||
| 579 | n += 1; | ||
| 580 | } | ||
| 581 | n | ||
| 582 | } | ||
| 583 | |||
| 584 | fn truncate_chain(mut c: u32, keep: usize) { | ||
| 585 | if keep == 0 { | ||
| 586 | free_chain(c); | ||
| 587 | return; | ||
| 588 | } | ||
| 589 | let mut idx = 0usize; | ||
| 590 | let mut guard = 0; | ||
| 591 | while idx + 1 < keep { | ||
| 592 | let n = read_fat_entry(c); | ||
| 593 | if is_eoc(n) { | ||
| 594 | return; | ||
| 595 | } | ||
| 596 | c = n; | ||
| 597 | idx += 1; | ||
| 598 | guard += 1; | ||
| 599 | if guard > 1_000_000 { | ||
| 600 | return; | ||
| 601 | } | ||
| 602 | } | ||
| 603 | let nxt = read_fat_entry(c); | ||
| 604 | if !is_eoc(nxt) { | ||
| 605 | free_chain(nxt); | ||
| 606 | let _ = write_fat_entry(c, EOC); | ||
| 607 | } | ||
| 608 | } | ||
| 609 | |||
| 610 | fn extend_chain(c: u32, add: usize) -> Result<(), ()> { | ||
| 611 | let mut last = c; | ||
| 612 | let mut guard = 0; | ||
| 613 | loop { | ||
| 614 | let n = read_fat_entry(last); | ||
| 615 | if is_eoc(n) { | ||
| 616 | break; | ||
| 617 | } | ||
| 618 | last = n; | ||
| 619 | guard += 1; | ||
| 620 | if guard > 1_000_000 { | ||
| 621 | return Err(()); | ||
| 622 | } | ||
| 623 | } | ||
| 624 | for _ in 0..add { | ||
| 625 | let n = alloc_cluster()?; | ||
| 626 | write_fat_entry(last, n)?; | ||
| 627 | last = n; | ||
| 628 | } | ||
| 629 | Ok(()) | ||
| 630 | } | ||
| 631 | |||
| 632 | /// Rewrite one field of a directory entry in place (its sector was cached | ||
| 633 | /// at scan time; the parent dir chain never moves while we hold it). | ||
| 634 | fn update_entry(it: &DirItem, cluster: Option<u32>, size: Option<u32>) -> Result<(), ()> { | ||
| 635 | let mut sec = [0u8; SECTOR]; | ||
| 636 | read_sector(it.sector, &mut sec)?; | ||
| 637 | let e = &mut sec[it.offset..it.offset + 32]; | ||
| 638 | if let Some(c) = cluster { | ||
| 639 | e[20..22].copy_from_slice(&(((c >> 16) as u16).to_le_bytes())); | ||
| 640 | e[26..28].copy_from_slice(&((c as u16).to_le_bytes())); | ||
| 641 | } | ||
| 642 | if let Some(s) = size { | ||
| 643 | e[28..32].copy_from_slice(&s.to_le_bytes()); | ||
| 644 | } | ||
| 645 | write_sector(it.sector, &sec) | ||
| 646 | } | ||
| 647 | |||
| 648 | // --- creation / removal ------------------------------------------------------ | ||
| 649 | |||
| 650 | /// Uppercased 8.3 short name for `name`; `needs_lfn` if the original name | ||
| 651 | /// can't round-trip through the short name (too long or lowercase). | ||
| 652 | fn to_8_3(name: &str) -> Option<([u8; 11], bool)> { | ||
| 653 | if name.is_empty() || name.contains('/') || name == "." || name == ".." { | ||
| 654 | return None; | ||
| 655 | } | ||
| 656 | let upper = name.to_ascii_uppercase(); | ||
| 657 | let (n, e) = match upper.rfind('.') { | ||
| 658 | Some(i) => (&upper[..i], Some(&upper[i + 1..])), | ||
| 659 | None => (upper.as_str(), None), | ||
| 660 | }; | ||
| 661 | if n.is_empty() { | ||
| 662 | return None; | ||
| 663 | } | ||
| 664 | let mut sfn = [b' '; 11]; | ||
| 665 | let nb = n.as_bytes(); | ||
| 666 | for i in 0..8 { | ||
| 667 | if i < nb.len() { | ||
| 668 | sfn[i] = nb[i]; | ||
| 669 | } | ||
| 670 | } | ||
| 671 | if let Some(ext) = e { | ||
| 672 | let eb = ext.as_bytes(); | ||
| 673 | for i in 0..3 { | ||
| 674 | if i < eb.len() { | ||
| 675 | sfn[8 + i] = eb[i]; | ||
| 676 | } | ||
| 677 | } | ||
| 678 | } | ||
| 679 | let needs = nb.len() > 8 || e.map(|x| x.len() > 3).unwrap_or(false) || name != upper; | ||
| 680 | Some((sfn, needs)) | ||
| 681 | } | ||
| 682 | |||
| 683 | /// Write the LFN entries (if any) + SFN entry for `name` into the parent | ||
| 684 | /// directory, extending its chain with a fresh zeroed cluster if needed. | ||
| 685 | fn add_dir_entry(parent: u32, name: &str, is_dir: bool, cluster: u32) -> Result<(), ()> { | ||
| 686 | let (sfn, needs_lfn) = to_8_3(name).ok_or(())?; | ||
| 687 | let lfn_chars: Vec<u16> = name.encode_utf16().collect(); | ||
| 688 | let groups = if needs_lfn { | ||
| 689 | lfn_chars.len().div_ceil(13) | ||
| 690 | } else { | ||
| 691 | 0 | ||
| 692 | }; | ||
| 693 | let needed = groups + 1; | ||
| 694 | let cs = cluster_size(); | ||
| 695 | let mut buf = vec![0u8; cs]; | ||
| 696 | let mut cur: Option<u32> = Some(parent); | ||
| 697 | let mut guard = 0; | ||
| 698 | loop { | ||
| 699 | guard += 1; | ||
| 700 | if guard > 1_000_000 { | ||
| 701 | return Err(()); | ||
| 702 | } | ||
| 703 | let c = cur.ok_or(())?; | ||
| 704 | read_cluster(c, &mut buf)?; | ||
| 705 | let slots = cs / 32; | ||
| 706 | let mut run = 0usize; | ||
| 707 | let mut start = 0usize; | ||
| 708 | for i in 0..slots { | ||
| 709 | if buf[i * 32] == 0x00 || buf[i * 32] == 0xE5 { | ||
| 710 | run += 1; | ||
| 711 | if run == 1 { | ||
| 712 | start = i; | ||
| 713 | } | ||
| 714 | if run >= needed { | ||
| 715 | break; | ||
| 716 | } | ||
| 717 | } else { | ||
| 718 | run = 0; | ||
| 719 | } | ||
| 720 | } | ||
| 721 | if run >= needed { | ||
| 722 | if groups > 0 { | ||
| 723 | for g in 0..groups { | ||
| 724 | let seq = (groups - g) as u8 | if g == groups - 1 { 0x40 } else { 0 }; | ||
| 725 | let e = &mut buf[(start + g) * 32..(start + g) * 32 + 32]; | ||
| 726 | e.fill(0); | ||
| 727 | e[0] = seq; | ||
| 728 | e[11] = 0x0F; | ||
| 729 | e[13] = lfn_checksum(&sfn); | ||
| 730 | for k in 0..13 { | ||
| 731 | let ci = g * 13 + k; | ||
| 732 | let ch: u16 = if ci < lfn_chars.len() { | ||
| 733 | lfn_chars[ci] | ||
| 734 | } else if ci == lfn_chars.len() { | ||
| 735 | 0 | ||
| 736 | } else { | ||
| 737 | 0xFFFF | ||
| 738 | }; | ||
| 739 | let pos = if k < 5 { | ||
| 740 | 1 + k * 2 | ||
| 741 | } else if k < 11 { | ||
| 742 | 14 + (k - 5) * 2 | ||
| 743 | } else { | ||
| 744 | 28 + (k - 11) * 2 | ||
| 745 | }; | ||
| 746 | e[pos] = (ch & 0xFF) as u8; | ||
| 747 | e[pos + 1] = (ch >> 8) as u8; | ||
| 748 | } | ||
| 749 | } | ||
| 750 | } | ||
| 751 | let e = &mut buf[(start + groups) * 32..(start + groups) * 32 + 32]; | ||
| 752 | e.fill(0); | ||
| 753 | e[0..11].copy_from_slice(&sfn); | ||
| 754 | e[11] = if is_dir { 0x10 } else { 0x20 }; | ||
| 755 | e[20..22].copy_from_slice(&(((cluster >> 16) as u16).to_le_bytes())); | ||
| 756 | e[26..28].copy_from_slice(&((cluster as u16).to_le_bytes())); | ||
| 757 | write_cluster(c, &buf)?; | ||
| 758 | return Ok(()); | ||
| 759 | } | ||
| 760 | // Chain full: extend with a fresh zeroed cluster and retry. | ||
| 761 | match next_cluster(c) { | ||
| 762 | Ok(n) => cur = Some(n), | ||
| 763 | Err(_) => { | ||
| 764 | let n = alloc_cluster()?; | ||
| 765 | write_fat_entry(c, n)?; | ||
| 766 | cur = Some(n); | ||
| 767 | } | ||
| 768 | } | ||
| 769 | } | ||
| 770 | } | ||
| 771 | |||
| 772 | pub fn create(parent_dir_cluster: u32, name: &str, is_dir: bool) -> Result<(), ()> { | ||
| 773 | if find(parent_dir_cluster, name).is_ok() { | ||
| 774 | return Err(()); | ||
| 775 | } | ||
| 776 | let cluster = if is_dir { | ||
| 777 | let c = alloc_cluster()?; | ||
| 778 | // "." and ".." entries. | ||
| 779 | let cs = cluster_size(); | ||
| 780 | let mut buf = vec![0u8; cs]; | ||
| 781 | let dot = &mut buf[0..32]; | ||
| 782 | dot.fill(0); | ||
| 783 | dot[0] = b'.'; | ||
| 784 | dot[1..11].fill(b' '); // SFN is space-padded | ||
| 785 | dot[11] = 0x10; | ||
| 786 | dot[20..22].copy_from_slice(&(((c >> 16) as u16).to_le_bytes())); | ||
| 787 | dot[26..28].copy_from_slice(&((c as u16).to_le_bytes())); | ||
| 788 | let dotdot = &mut buf[32..64]; | ||
| 789 | dotdot.fill(0); | ||
| 790 | dotdot[0] = b'.'; | ||
| 791 | dotdot[1] = b'.'; | ||
| 792 | dotdot[2..11].fill(b' '); // SFN is space-padded | ||
| 793 | dotdot[11] = 0x10; | ||
| 794 | // ".." points at the parent; the root has cluster number 0. | ||
| 795 | let parent_cluster = if parent_dir_cluster == v().root_cluster { | ||
| 796 | 0 | ||
| 797 | } else { | ||
| 798 | parent_dir_cluster | ||
| 799 | }; | ||
| 800 | dotdot[20..22].copy_from_slice(&(((parent_cluster >> 16) as u16).to_le_bytes())); | ||
| 801 | dotdot[26..28].copy_from_slice(&((parent_cluster as u16).to_le_bytes())); | ||
| 802 | write_cluster(c, &buf)?; | ||
| 803 | c | ||
| 804 | } else { | ||
| 805 | 0 | ||
| 806 | }; | ||
| 807 | add_dir_entry(parent_dir_cluster, name, is_dir, cluster) | ||
| 808 | } | ||
| 809 | |||
| 810 | pub fn remove(parent_dir_cluster: u32, name: &str) -> Result<(), ()> { | ||
| 811 | let it = find_item(parent_dir_cluster, name)?; | ||
| 812 | if it.attr & 0x10 != 0 { | ||
| 813 | // Only empty directories may be removed. | ||
| 814 | let kids = list_dir(it.cluster)?; | ||
| 815 | if !kids.is_empty() { | ||
| 816 | return Err(()); | ||
| 817 | } | ||
| 818 | free_chain(it.cluster); | ||
| 819 | } else if it.cluster != 0 { | ||
| 820 | free_chain(it.cluster); | ||
| 821 | } | ||
| 822 | // Mark the SFN and its leading LFN entries deleted. | ||
| 823 | let mut sec = [0u8; SECTOR]; | ||
| 824 | read_sector(it.sector, &mut sec)?; | ||
| 825 | let mut off = it.offset; | ||
| 826 | sec[off] = 0xE5; | ||
| 827 | while off >= 32 { | ||
| 828 | let prev = &sec[off - 32..off]; | ||
| 829 | if prev[11] == 0x0F { | ||
| 830 | sec[off - 32] = 0xE5; | ||
| 831 | off -= 32; | ||
| 832 | } else { | ||
| 833 | break; | ||
| 834 | } | ||
| 835 | } | ||
| 836 | write_sector(it.sector, &sec) | ||
| 837 | } | ||
| 838 | |||
| 839 | // --- mount ------------------------------------------------------------------- | ||
| 840 | |||
| 841 | /// Parse the BPB, cache the FAT and validate the volume. Returns true if | ||
| 842 | /// a usable FAT32 volume is mounted. | ||
| 843 | pub fn init() -> bool { | ||
| 844 | let mut bpb = [0u8; SECTOR]; | ||
| 845 | if read_sector(0, &mut bpb).is_err() { | ||
| 846 | crate::serial_println!("fat: no disk"); | ||
| 847 | return false; | ||
| 848 | } | ||
| 849 | let bytes_per_sector = u16::from_le_bytes([bpb[11], bpb[12]]); | ||
| 850 | if bytes_per_sector != 512 { | ||
| 851 | crate::serial_println!("fat: unsupported sector size {}", bytes_per_sector); | ||
| 852 | return false; | ||
| 853 | } | ||
| 854 | let spc = bpb[13]; | ||
| 855 | if spc == 0 || spc & (spc - 1) != 0 { | ||
| 856 | crate::serial_println!("fat: bad sectors/cluster {}", spc); | ||
| 857 | return false; | ||
| 858 | } | ||
| 859 | let reserved = u16::from_le_bytes([bpb[14], bpb[15]]); | ||
| 860 | let fat_count = bpb[16]; | ||
| 861 | let total16 = u16::from_le_bytes([bpb[19], bpb[20]]); | ||
| 862 | let total32 = u32::from_le_bytes([bpb[32], bpb[33], bpb[34], bpb[35]]); | ||
| 863 | let total = if total16 != 0 { | ||
| 864 | total16 as u32 | ||
| 865 | } else { | ||
| 866 | total32 | ||
| 867 | }; | ||
| 868 | let fat_size = u32::from_le_bytes([bpb[36], bpb[37], bpb[38], bpb[39]]); | ||
| 869 | let root_cluster = u32::from_le_bytes([bpb[44], bpb[45], bpb[46], bpb[47]]); | ||
| 870 | let fsinfo_sec = u16::from_le_bytes([bpb[48], bpb[49]]) as u32; | ||
| 871 | if fat_size == 0 || root_cluster < 2 || reserved == 0 || fat_count == 0 { | ||
| 872 | crate::serial_println!("fat: not FAT32 (root={} fat={})", root_cluster, fat_size); | ||
| 873 | return false; | ||
| 874 | } | ||
| 875 | let first_data = reserved as u32 + fat_count as u32 * fat_size; | ||
| 876 | if first_data >= total { | ||
| 877 | crate::serial_println!("fat: bad geometry"); | ||
| 878 | return false; | ||
| 879 | } | ||
| 880 | let cluster_count = (total - first_data) / spc as u32; | ||
| 881 | |||
| 882 | // Load the whole FAT into RAM (chunked through the 4 KiB DMA buffer). | ||
| 883 | let fat_bytes = fat_size as usize * SECTOR; | ||
| 884 | let mut fat = alloc::vec![0u8; fat_bytes]; | ||
| 885 | let mut lba = reserved as u64; | ||
| 886 | let mut done = 0usize; | ||
| 887 | while done < fat.len() { | ||
| 888 | let n = ((fat.len() - done) / SECTOR).min(8) as u32; | ||
| 889 | if ahci::read_sectors(lba, n).is_err() { | ||
| 890 | crate::serial_println!("fat: FAT read failed at lba {}", lba); | ||
| 891 | return false; | ||
| 892 | } | ||
| 893 | unsafe { | ||
| 894 | core::ptr::copy(ahci::buf(), fat.as_mut_ptr().add(done), n as usize * SECTOR); | ||
| 895 | } | ||
| 896 | lba += n as u64; | ||
| 897 | done += n as usize * SECTOR; | ||
| 898 | } | ||
| 899 | // Sanity: FAT[0] holds the media byte, FAT[1] should be an EOC. | ||
| 900 | let e0 = u32::from_le_bytes([fat[0], fat[1], fat[2], fat[3]]); | ||
| 901 | let e1 = u32::from_le_bytes([fat[4], fat[5], fat[6], fat[7]]); | ||
| 902 | if e0 & 0xFF != 0xF8 || e1 & EOC_MASK != EOC { | ||
| 903 | crate::serial_println!( | ||
| 904 | "fat: bad FAT markers ({:#x} {:#x})", | ||
| 905 | e0 & 0xFF, | ||
| 906 | e1 & EOC_MASK | ||
| 907 | ); | ||
| 908 | return false; | ||
| 909 | } | ||
| 910 | unsafe { | ||
| 911 | VOL = Some(Volume { | ||
| 912 | sectors_per_cluster: spc, | ||
| 913 | reserved, | ||
| 914 | fat_count, | ||
| 915 | fat_size, | ||
| 916 | root_cluster, | ||
| 917 | first_data, | ||
| 918 | cluster_count, | ||
| 919 | fsinfo: (fsinfo_sec != 0 && fsinfo_sec < total).then_some(fsinfo_sec), | ||
| 920 | fat, | ||
| 921 | }); | ||
| 922 | } | ||
| 923 | // Re-sync the FSInfo free-cluster count (may be stale from earlier | ||
| 924 | // sessions that predate FSInfo maintenance). | ||
| 925 | fsinfo_recount(); | ||
| 926 | let count = list_dir(root_cluster).map(|v| v.len()).unwrap_or(0); | ||
| 927 | crate::serial_println!( | ||
| 928 | "fat: FAT32 mounted — {} clusters ({} bytes each), root: {} entries", | ||
| 929 | cluster_count, | ||
| 930 | cluster_size(), | ||
| 931 | count | ||
| 932 | ); | ||
| 933 | true | ||
| 934 | } | ||
