RustForge

foundry

Foundry OS: the operating system built on the Rustbelt kernel

authorTyler Fowler <tyler@localhost>2026-10-30 01:32:12 -0700
commit642316995676e40b82513422d30d6645005ca7fd
parentb10b990e384b578fd41000614e51b20a726c3103
treebrowse files at this commit
Shell: drive letters start at A: (root) instead of C:

/ is A:, mounted disks get B:, C:, ... in mount order (the FAT disk is B:).
C: for the system drive is a floppy-era leftover; Rustbelt has no floppies.
Updated shparse, sh help text, selftest.sh, disk readme, tests, scripts,
README and boot.png.
Msh.rs+3 -3
Mshparse.rs+12 -12

2 files changed, 15 insertions, 15 deletions

@@ -41,7 +41,7 @@ Rustbelt shell commands   run [--trace] prog args   run a program (--trace prints its syscalls)   start prog args           run it in the background (or: prog args &)   a | b | c                 pipes;  > file  >> file  < file  redirection-  C: is /, D: E: ... are the disks (see `drives`): cd D:, cat D:/readme.txt+  A: is /, B: C: ... are the disks (see `drives`): cd B:, cat B:/readme.txt ";  /// A background job started with `start` or `&`.@@ -137,7 +137,7 @@ impl Shell {         String::from_utf8_lossy(&buf[..n]).into_owned()     } -    /// `C:/ $ `, `D:/notes $ `+    /// `A:/ $ `, `B:/notes $ `     fn prompt(&self) -> String {         format!("{} $ ", shparse::display(&self.cwd(), &self.drives))     }@@ -381,7 +381,7 @@ impl Shell {     }      /// Find a program: names with a `/` are paths; others are looked up in-    /// the `bin` directory of every drive (our PATH): C:/bin, D:/bin, ...+    /// the `bin` directory of every drive (our PATH): A:/bin, B:/bin, ...     fn find_program(&self, name: &str) -> Option<String> {         if name.contains('/') {             return Some(name.to_string());
@@ -1,5 +1,5 @@ //! The shell's parser: command line -> tokens -> pipeline, and the drive-//! letter mapping (`D:/notes` <-> `/mnt/disk0/notes`).+//! letter mapping (`B:/notes` <-> `/mnt/disk0/notes`). //! //! Pure functions with no syscalls, so the same file is compiled into the //! shell (user/sh.rs) *and* into the kernel's test suite (tests/shell.rs),@@ -20,7 +20,7 @@ use alloc::{format, string::String, vec::Vec};  /// One word of a command. `quoted` words are never drive-translated, so-/// `grep "C:" file` searches for the text C:.+/// `grep "A:" file` searches for the text A:. #[derive(Debug, Clone, PartialEq)] pub struct Word {     pub text: String,@@ -161,17 +161,17 @@ pub struct Drive {  /// Assign letters from the kernel's mount table (the text from the /// `mounts` syscall: one "path fstype" per line, in mount order).-/// C: is always the root; D:, E:, ... go to the mounts under /mnt (the+/// A: is always the root; B:, C:, ... go to the mounts under /mnt (the /// disks), in the order they were mounted. Other mounts such as /tmp are-/// reachable through C: (C:/tmp).+/// reachable through A: (A:/tmp). pub fn assign_drives(mount_table: &str) -> Vec<Drive> {     let mut drives = Vec::new();     let root_fs = mount_table         .lines()         .find_map(|l| l.split_once(' ').filter(|(p, _)| *p == "/").map(|(_, fs)| fs))         .unwrap_or("ramfs");-    drives.push(Drive { letter: 'C', path: String::from("/"), fs: String::from(root_fs.trim()) });-    let mut letter = b'D';+    drives.push(Drive { letter: 'A', path: String::from("/"), fs: String::from(root_fs.trim()) });+    let mut letter = b'B';     for line in mount_table.lines() {         if let Some((path, fs)) = line.split_once(' ') {             if path.starts_with("/mnt/") && letter <= b'Z' {@@ -183,7 +183,7 @@ pub fn assign_drives(mount_table: &str) -> Vec<Drive> {     drives } -/// Does `word` start with a drive letter ("D:", "d:/x", "E:notes")?+/// Does `word` start with a drive letter ("B:", "b:/x", "C:notes")? pub fn drive_letter(word: &str) -> Option<char> {     let b = word.as_bytes();     if b.len() >= 2 && b[0].is_ascii_alphabetic() && b[1] == b':' {@@ -193,9 +193,9 @@ pub fn drive_letter(word: &str) -> Option<char> {     } } -/// `D:/notes/todo.txt` -> `/mnt/disk0/notes/todo.txt`. Letters are case-/// insensitive; `D:` and `D:/` are the drive's root, and `D:notes` means-/// `D:/notes` (drives have no current directory of their own). `None` if+/// `B:/notes/todo.txt` -> `/mnt/disk0/notes/todo.txt`. Letters are case+/// insensitive; `B:` and `B:/` are the drive's root, and `B:notes` means+/// `B:/notes` (drives have no current directory of their own). `None` if /// `word` doesn't start with a known drive letter. pub fn translate(word: &str, drives: &[Drive]) -> Option<String> {     let letter = drive_letter(word)?;@@ -210,8 +210,8 @@ pub fn translate(word: &str, drives: &[Drive]) -> Option<String> {     }) } -/// The other direction, for the prompt: `/mnt/disk0/bin` -> `D:/bin`,-/// `/tmp` -> `C:/tmp`. Picks the drive with the longest matching path.+/// The other direction, for the prompt: `/mnt/disk0/bin` -> `B:/bin`,+/// `/tmp` -> `A:/tmp`. Picks the drive with the longest matching path. pub fn display(path: &str, drives: &[Drive]) -> String {     let mut best: Option<(&Drive, &str)> = None;     for d in drives {