summaryrefslogtreecommitdiffstats
path: root/rsnix/src/fat.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--rsnix/src/fat.rs934
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
17extern crate alloc;
18
19use crate::ahci;
20use alloc::string::String;
21use alloc::vec;
22use alloc::vec::Vec;
23
24const SECTOR: usize = 512;
25const EOC_MASK: u32 = 0x0FFF_FFFF;
26const EOC: u32 = 0x0FFF_FFFF;
27
28/// One directory-listed entry (or a resolved file).
29pub struct Entry {
30 pub name: String,
31 pub is_dir: bool,
32 pub size: u64,
33 pub cluster: u32,
34}
35
36struct 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
48static mut VOL: Option<Volume> = None;
49
50pub fn is_mounted() -> bool {
51 unsafe { VOL.is_some() }
52}
53
54pub fn root_cluster() -> u32 {
55 unsafe { VOL.as_ref() }.map(|v| v.root_cluster).unwrap_or(0)
56}
57
58// --- sector I/O -----------------------------------------------------------
59
60fn 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
66fn 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
73fn v() -> &'static mut Volume {
74 unsafe { VOL.as_mut() }.unwrap()
75}
76
77fn spc() -> usize {
78 v().sectors_per_cluster as usize
79}
80
81fn cluster_size() -> usize {
82 spc() * SECTOR
83}
84
85fn cluster_lba(c: u32) -> u64 {
86 v().first_data as u64 + ((c as u64 - 2) * spc() as u64)
87}
88
89fn fat_off(c: u32) -> usize {
90 c as usize * 4
91}
92
93fn 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.
105fn 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
117fn is_eoc(c: u32) -> bool {
118 c >= 0x0FFF_FFF8
119}
120
121fn 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.
131fn 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
146fn 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.
166fn 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.
187fn 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.
204fn 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
214fn 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
229fn 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.
247fn 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
255fn 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
291fn 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
304struct 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.
314fn 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 `.` / `..`.
390pub 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).
405fn 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).
415pub 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
426pub 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
470pub 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
532pub 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
570fn 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
584fn 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
610fn 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).
634fn 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).
652fn 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.
685fn 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
772pub 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
810pub 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.
843pub 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}