foundry
Foundry OS: the operating system built on the Rustbelt kernel
| author | Tyler Fowler <tyler@localhost> | 2026-10-30 01:52:32 -0700 |
|---|---|---|
| commit | f856a3aa4c884f7c0d06d17dfccaecef3b2dd9ec | |
| parent | 642316995676e40b82513422d30d6645005ca7fd | |
| tree | browse files at this commit | |
Stage 8: ext2 driver (0.8.0) Hand-written ext2 (src/fs/ext2.rs, no crate) plugged into the VFS: superblock, group descriptors, bitmaps, inodes with direct/single/ double/triple indirect blocks, directories with file types, fast and slow symlinks; 1 KiB and 4 KiB blocks, revision 0 and 1. Writes: create, write/append, truncate, mkdir, unlink, rmdir with bitmap allocation and free counts in the superblock and group descriptors. - VFS: symlink following (ELOOP after 8), lookup_nofollow, readlink; Stat gains mode; new readlink syscall (89); mount_disks probes ext2 (magic 0xEF53) before FAT and mounts unpartitioned disks - build.rs: mke2fs -d images from disk2/ (big.txt, sparse.bin, symlinks): ext2.img (raw, vdb -> /mnt/disk1 = C:), plus 4K-block and revision-0 images for tests - ls -l shows permissions and symlink targets - tests/ext2.rs (11 tests); scripts/ext2-check.sh runs Rustbelt on writable copies, then host e2fsck -fn and debugfs - self-test and boot.png use drive C:; README ext2 section
| M | ls.rs | +46 -8 |
| M | ulib.rs | +11 -0 |
2 files changed, 57 insertions, 8 deletions
@@ -2,7 +2,9 @@ //! //! On the console the names are printed in columns; into a pipe or file //! one per line (like the real `ls`, so `ls | wc -l` counts files).-//! -l adds sizes, DOS style: `<DIR>` or the size in bytes.+//! -l adds sizes, DOS style: `<DIR>` or the size in bytes. On file systems+//! with Unix permissions (ext2) it also shows them, like `-rw-r--r--`, and+//! where symlinks point: `hello-link -> hello.txt`. #![no_std] #![no_main] @@ -18,7 +20,10 @@ use ulib::*; struct Entry { name: String, dir: bool,+ link: bool, size: u64,+ mode: u64, // 0 = no permissions on this file system+ target: String, // symlinks (with -l): where they point } fn main() -> i64 {@@ -46,7 +51,8 @@ fn main() -> i64 { continue; } if st.kind != KIND_DIR {- print_entries(&[Entry { name: String::from(*path), dir: false, size: st.size }], long, columns);+ let entry = Entry { name: String::from(*path), dir: false, link: false, size: st.size, mode: st.mode, target: String::new() };+ print_entries(&[entry], long, columns); continue; } if paths.len() > 1 {@@ -79,7 +85,8 @@ fn read_dir(path: &str, long: bool) -> Result<Vec<Entry>, i64> { } for_each_dirent(&buf, n as usize, |name, d_type| { if name != "." && name != ".." {- entries.push(Entry { name: String::from(name), dir: d_type == 4, size: 0 });+ let (dir, link) = (d_type == 4, d_type == 10); // DT_DIR, DT_LNK+ entries.push(Entry { name: String::from(name), dir, link, size: 0, mode: 0, target: String::new() }); } }); }@@ -87,9 +94,17 @@ fn read_dir(path: &str, long: bool) -> Result<Vec<Entry>, i64> { entries.sort_by(|a, b| a.name.cmp(&b.name)); if long { let mut st = Stat::default();- for e in entries.iter_mut().filter(|e| !e.dir) {- if stat(&format!("{}/{}", path, e.name), &mut st) == 0 {+ for e in entries.iter_mut() {+ let full = format!("{}/{}", path, e.name);+ if e.link {+ let mut buf = [0u8; 256];+ let n = readlink(&full, &mut buf);+ if n >= 0 {+ e.target = String::from(core::str::from_utf8(&buf[..n as usize]).unwrap_or("?"));+ }+ } else if stat(&full, &mut st) == 0 { e.size = st.size;+ e.mode = st.mode; } } }@@ -99,15 +114,23 @@ fn read_dir(path: &str, long: bool) -> Result<Vec<Entry>, i64> { fn print_entries(entries: &[Entry], long: bool, columns: bool) { if long { for e in entries {+ // `drwxr-xr-x ` in front if the file system has permissions.+ let perms = if e.mode != 0 || e.link { format!("{} ", mode_string(e)) } else { String::new() }; if e.dir {- println!(" <DIR> {}/", e.name);+ println!("{} <DIR> {}/", perms, e.name);+ } else if e.link {+ println!("{} {} -> {}", perms, e.name, e.target); } else {- println!("{:>10} {}", e.size, e.name);+ println!("{}{:>10} {}", perms, e.size, e.name); } } return; }- let shown = |e: &Entry| if e.dir { format!("{}/", e.name) } else { e.name.clone() };+ let shown = |e: &Entry| match (e.dir, e.link) {+ (true, _) => format!("{}/", e.name),+ (_, true) => format!("{}@", e.name), // like `ls -F`+ _ => e.name.clone(),+ }; if !columns { entries.iter().for_each(|e| println!("{}", shown(e))); return;@@ -124,3 +147,18 @@ fn print_entries(entries: &[Entry], long: bool, columns: bool) { } } }++/// `-rw-r--r--` style: type, then rwx for owner, group and others.+fn mode_string(e: &Entry) -> String {+ if e.link {+ return String::from("lrwxrwxrwx"); // symlink permissions are never used+ }+ let mut s = String::from(if e.dir { "d" } else { "-" });+ for shift in [6, 3, 0] {+ let bits = (e.mode >> shift) & 7;+ s.push(if bits & 4 != 0 { 'r' } else { '-' });+ s.push(if bits & 2 != 0 { 'w' } else { '-' });+ s.push(if bits & 1 != 0 { 'x' } else { '-' });+ }+ s+}
@@ -37,6 +37,7 @@ pub const SYS_CHDIR: u64 = 80; pub const SYS_MKDIR: u64 = 83; pub const SYS_RMDIR: u64 = 84; pub const SYS_UNLINK: u64 = 87;+pub const SYS_READLINK: u64 = 89; pub const SYS_GETDENTS64: u64 = 217; pub const SYS_SPAWN: u64 = 500; pub const SYS_MOUNTS: u64 = 501;@@ -69,11 +70,13 @@ pub const ENOTEMPTY: i64 = 39; pub struct Stat { pub kind: u64, // KIND_* pub size: u64,+ pub mode: u64, // permission bits like 0o644; 0 = the file system has none } pub const KIND_FILE: u64 = 1; pub const KIND_DIR: u64 = 2; pub const KIND_CONSOLE: u64 = 3; pub const KIND_PIPE: u64 = 4;+pub const KIND_SYMLINK: u64 = 5; /// Same layout as the kernel's `syscall::SpawnOptions`. #[repr(C)]@@ -209,6 +212,10 @@ pub fn mkdir(path: &str) -> i64 { pub fn rmdir(path: &str) -> i64 { syscall3(SYS_RMDIR, path.as_ptr() as u64, path.len() as u64, 0) }+/// The target of a symlink, into `buf`; returns its length or -errno.+pub fn readlink(path: &str, buf: &mut [u8]) -> i64 {+ syscall5(SYS_READLINK, path.as_ptr() as u64, path.len() as u64, buf.as_mut_ptr() as u64, buf.len() as u64, 0)+} pub fn unlink(path: &str) -> i64 { syscall3(SYS_UNLINK, path.as_ptr() as u64, path.len() as u64, 0) }@@ -324,13 +331,17 @@ pub fn strerror(errno: i64) -> &'static str { 22 => "Invalid argument", 24 => "Too many open files", 25 => "Not a terminal",+ 27 => "File too large", 28 => "No space left on device", 29 => "Illegal seek",+ 30 => "Read-only file system", 32 => "Broken pipe", 34 => "Result too large", 36 => "File name too long", 38 => "Function not implemented", 39 => "Directory not empty",+ 40 => "Too many levels of symbolic links",+ 95 => "Operation not supported", _ => "Unknown error", } }