summaryrefslogtreecommitdiffstats
path: root/rsnix/src/fs.rs
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-08-02 15:21:34 +0200
committersalmonik <salmonik@tuta.io>2026-08-02 15:21:34 +0200
commitc7d92071e1dd11c45999457b16554b4abaa73b28 (patch)
treeaff7c7f19adcffe1dd91e1c2514af02c77fd524e /rsnix/src/fs.rs
parentv0.5.5: fix real-hardware boot crash — shadow back in heap as first allocation (diff)
downloadrs-dos-c7d92071e1dd11c45999457b16554b4abaa73b28.tar.gz
rs-dos-c7d92071e1dd11c45999457b16554b4abaa73b28.zip
v0.5.5: crash reports go to a dedicated CRASHLOG USB stick
The fault handler now writes crash.log to the volume labeled CRASHLOG (label-selected via fat::crash_vol), falling back to slot 0. On real hardware the boot USB may be unwritable, so a second stick labeled CRASHLOG survives the write; the shell auto-prints the previous crash report from it at next boot (read_crash_log resolves /disk/<uid>/crash.log through the FAT path, since Disk nodes hold no cached children). - fat.rs: volume_label() + crash_vol() (no allocation) - fs.rs: write_crash_log targets crash_vol(); read_crash_log prefers the CRASHLOG disk node; drop now-unused first_disk() - mkdisk.sh: --label option - test-fault.sh: boots with a dedicated CRASHLOG stick (mkfs -F 32 -n CRASHLOG), verifies crash.log lands there and NOT on the ESP
Diffstat (limited to '')
-rw-r--r--rsnix/src/fs.rs93
1 files changed, 91 insertions, 2 deletions
diff --git a/rsnix/src/fs.rs b/rsnix/src/fs.rs
index 5fc6d7a..24d8fcd 100644
--- a/rsnix/src/fs.rs
+++ b/rsnix/src/fs.rs
@@ -464,9 +464,98 @@ pub fn write(h: usize, buf: &[u8], pos: u64) -> Result<usize, ()> {
464 } 464 }
465} 465}
466 466
467/// Handle of `crash.log` on the crash-log volume, if it exists. The
468/// crash-log volume is the one labeled CRASHLOG (a dedicated stick that
469/// survives reboot writes), else the first mounted volume. Resolves the
470/// FAT path (`/disk/<uid>/crash.log`) because Disk nodes hold no cached
471/// children.
472pub fn read_crash_log() -> Option<usize> {
473 let disk_dir = lookup(0, "disk")?;
474 let children = match &nodes()[disk_dir].data {
475 NodeData::Dir(v) => v.clone(),
476 _ => return None,
477 };
478 // Prefer a disk node whose name starts with "CRASHLOG-".
479 let mut first = None;
480 for &c in &children {
481 if nodes()[c].removed || !matches!(nodes()[c].data, NodeData::Disk(_)) {
482 continue;
483 }
484 let name = &nodes()[c].name;
485 let label = name.split_once('-').map(|(l, _)| l).unwrap_or(name);
486 if label.eq_ignore_ascii_case("CRASHLOG") {
487 return resolve(&alloc::format!("/disk/{}/crash.log", name));
488 }
489 if first.is_none() {
490 first = Some(c);
491 }
492 }
493 first.and_then(|disk| {
494 let name = nodes()[disk].name.clone();
495 resolve(&alloc::format!("/disk/{}/crash.log", name))
496 })
497}
498
499/// Persist a crash report to the mounted FAT volume so it survives a reboot
500/// (real hardware has no serial console). Best-effort: any failure is
501/// ignored — the caller (fault handler) must never fault here; the
502/// in-dump ISR guard catches that case and halts.
503///
504/// Goes straight at the FAT layer (the crash-log volume + its root
505/// cluster) rather than through the fs node table: the fault may have
506/// interrupted the fs mid-operation, and a stale DiskInfo/ACTIVE_VOL
507/// would make node-based writes fail with "no volume at slot N". The
508/// crash-log volume is the one labeled CRASHLOG (a dedicated stick that
509/// survives reboot writes), else slot 0 (first mounted) as before.
510pub fn write_crash_log(lines: &[&str]) {
511 // Assemble the report on the stack (heap-free — a corrupted heap is
512 // exactly the situation that produces crash reports).
513 let mut text = [0u8; 640];
514 let mut n = 0usize;
515 for line in lines {
516 for &b in line.as_bytes() {
517 if n < text.len() {
518 text[n] = b;
519 n += 1;
520 }
521 }
522 if n < text.len() {
523 text[n] = b'\n';
524 n += 1;
525 }
526 }
527 let Some(vol) = crate::fat::crash_vol() else {
528 crate::serial::write_raw("crashlog: no volume\r\n");
529 return;
530 };
531 // Trace the target so a test can verify the right stick got the log.
532 let label = crate::fat::volume_label(vol).unwrap_or("?");
533 let mut tb = [0u8; 48];
534 let tn = {
535 let mut w = crate::strbuf::FmtBuf::new(&mut tb);
536 let _ = core::fmt::write(&mut w, core::format_args!("crashlog: vol {} ({})\r\n", vol, label));
537 w.len()
538 };
539 crate::serial::write_raw(core::str::from_utf8(&tb[..tn]).unwrap_or("?"));
540 let root = crate::fat::root_cluster(vol);
541 if crate::fat::create(vol, root, "crash.log", false).is_err()
542 && crate::fat::find(vol, root, "crash.log").is_err()
543 {
544 crate::serial::write_raw("crashlog: create failed\r\n");
545 return;
546 }
547 if crate::fat::set_len(vol, root, "crash.log", 0).is_err() {
548 crate::serial::write_raw("crashlog: set_len failed\r\n");
549 return;
550 }
551 match crate::fat::write_file(vol, root, "crash.log", &text[..n], 0) {
552 Ok(_) => crate::serial::write_raw("crashlog: wrote\r\n"),
553 Err(_) => crate::serial::write_raw("crashlog: write failed\r\n"),
554 }
555}
556
467/// Truncate/resize a regular file. 557/// Truncate/resize a regular file.
468pub fn set_len(h: usize, len: u64) -> Result<(), ()> { 558pub fn set_len(h: usize, len: u64) -> Result<(), ()> { match &mut nodes()[h].data {
469 match &mut nodes()[h].data {
470 NodeData::Regular(v) => { 559 NodeData::Regular(v) => {
471 v.resize(len as usize, 0); 560 v.resize(len as usize, 0);
472 Ok(()) 561 Ok(())