diff options
Diffstat (limited to 'rsnix/src/fs.rs')
| -rw-r--r-- | rsnix/src/fs.rs | 93 |
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. | ||
| 472 | pub 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. | ||
| 510 | pub 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. |
| 468 | pub fn set_len(h: usize, len: u64) -> Result<(), ()> { | 558 | pub 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(()) |
