RustForge

foundry

Foundry OS: the operating system built on the Rustbelt kernel

authorTyler Fowler <tyler@localhost>2026-10-30 10:38:16 -0700
commit0094a45ff3029bf74d17d16bdf577bb9e6b9a344
parent97b0cc346c0cd9dda26e765fdd20cf91ecf13b28
treebrowse files at this commit
WIP stage 15: select/ppoll/readv wrappers, more nbtest/ptytest checks, headless fixes
Mnbtest.rs+34 -0
Mptytest.rs+35 -1
Mulib.rs+24 -0

3 files changed, 93 insertions, 1 deletion

@@ -105,6 +105,40 @@ fn main() -> i64 {     c.check(&buf[..n.max(0) as usize] == b"HELLO vectored world", "pwrite overwrote the start");     close(fd);     unlink("/tmp/nbtest.txt");+    // readv: one read filling several buffers in turn.+    let fd = open("/tmp/nbtest.txt", O_CREAT | O_TRUNC | O_RDWR);+    write(fd, b"abcdefgh");+    lseek(fd, 0, SEEK_SET);+    let (mut a, mut b) = ([0u8; 3], [0u8; 5]);+    let n = readv(fd, &mut [&mut a, &mut b]);+    c.check(n == 8 && &a == b"abc" && &b == b"defgh", "readv fills the buffers in order");+    close(fd);+    unlink("/tmp/nbtest.txt");++    // select and ppoll on a pipe: not readable, then readable.+    let (r, w) = pipe2(0).unwrap();+    let (mut rs, mut ws) = ([0u64; 16], [0u64; 16]);+    fd_set(&mut rs, r);+    let t0 = now_us();+    c.check(select(r + 1, &mut rs, &mut ws, 20_000) == 0 && now_us() - t0 >= 20_000, "select: times out after 20 ms on an empty pipe");+    write(w, b"!");+    let (mut rs, mut ws) = ([0u64; 16], [0u64; 16]);+    fd_set(&mut rs, r);+    fd_set(&mut ws, w);+    c.check(select(w.max(r) + 1, &mut rs, &mut ws, 0) == 2 && fd_isset(&rs, r) && fd_isset(&ws, w), "select: pipe readable + writable");+    let mut p = [PollFd { fd: r as i32, events: POLLIN, revents: 0 }];+    c.check(ppoll_us(&mut p, 0) == 1 && p[0].revents & POLLIN != 0, "ppoll: POLLIN");+    close(r);+    close(w);++    // getcwd and uname, which libc start-up and `uname -a` use.+    let mut cwd = [0u8; 64];+    let n = getcwd(&mut cwd);+    c.check(n > 0 && cwd[0] == b'/', "getcwd gives an absolute path");+    let mut ub = [0u8; 6 * 65];+    let u = uname(&mut ub);+    c.check(u[0] == "Rustbelt" && u[3].starts_with("stage 15"), "uname: Rustbelt, stage 15");+     let (secs, _) = clock_gettime(0);     c.check(secs > 1_600_000_000, "CLOCK_REALTIME is a real date (after 2020)");     let t0 = now_us();
@@ -82,7 +82,12 @@ fn main() -> i64 {         if fd < 0 {             exit(3);         }-        write(fd, b"ready\n");+        // /dev/tty is "my controlling terminal", whichever that is.+        let me = open("/dev/tty", O_RDWR);+        if me < 0 {+            exit(4);+        }+        write(me, b"ready\n");         loop {             pause();         }@@ -98,6 +103,35 @@ fn main() -> i64 {     let got = read_some(master, &mut buf);     c.check(got >= 2 && &buf[..2] == b"^C", "^C is echoed as \"^C\""); +    // ^\ is SIGQUIT (it would dump core on Unix).+    let pid = fork();+    if pid == 0 {+        setsid();+        if open(&path, O_RDWR) < 0 {+            exit(3);+        }+        loop {+            pause();+        }+    }+    sleep_ms(100); // let the child open the pty first+    write(master, b"\x1c");+    let status = match wait4(pid, 0) {+        Ok((_, s)) => s,+        Err(_) => 0,+    };+    c.check(wifsignaled(status) && wtermsig(status) == SIGQUIT, "^\\ on the master: SIGQUIT");+    set_nonblocking(master, true);+    while read(master, &mut buf) > 0 {} // drop the "^\\" echo+    set_nonblocking(master, false);+    // Without a controlling terminal, /dev/tty doesn't exist for us.+    let pid = fork();+    if pid == 0 {+        setsid();+        exit(if open("/dev/tty", O_RDWR) < 0 { 0 } else { 1 });+    }+    c.check(matches!(wait4(pid, 0), Ok((_, 0))), "no controlling terminal: /dev/tty fails");+     // Hang-up: closing the master gives the slave end-of-file.     close(master);     c.check(read(slave, &mut buf) == 0, "master closed: the slave reads end-of-file");
@@ -1179,6 +1179,8 @@ impl Heap { // --- Stage 15: what a libc's plumbing needs ----------------------------------  pub const SYS_POLL: u64 = 7;+pub const SYS_SELECT: u64 = 23;+pub const SYS_PPOLL: u64 = 271; pub const SYS_PREAD64: u64 = 17; pub const SYS_PWRITE64: u64 = 18; pub const SYS_READV: u64 = 19;@@ -1218,6 +1220,10 @@ pub fn pwrite(fd: i64, buf: &[u8], offset: u64) -> i64 {     syscall6(SYS_PWRITE64, fd as u64, buf.as_ptr() as u64, buf.len() as u64, offset, 0, 0) } /// writev: several buffers in one call (`struct iovec {base, len}`).+pub fn readv(fd: i64, bufs: &mut [&mut [u8]]) -> i64 {+    let iov: alloc::vec::Vec<[u64; 2]> = bufs.iter_mut().map(|b| [b.as_mut_ptr() as u64, b.len() as u64]).collect();+    syscall3(SYS_READV, fd as u64, iov.as_ptr() as u64, iov.len() as u64)+} pub fn writev(fd: i64, bufs: &[&[u8]]) -> i64 {     let iov: alloc::vec::Vec<[u64; 2]> = bufs.iter().map(|b| [b.as_ptr() as u64, b.len() as u64]).collect();     syscall3(SYS_WRITEV, fd as u64, iov.as_ptr() as u64, iov.len() as u64)@@ -1250,6 +1256,24 @@ pub struct PollFd { pub fn poll(fds: &mut [PollFd], timeout_ms: i64) -> i64 {     syscall3(SYS_POLL, fds.as_mut_ptr() as u64, fds.len() as u64, timeout_ms as u64) }+/// ppoll: poll with a timespec timeout (and a signal mask, unused here).+pub fn ppoll_us(fds: &mut [PollFd], timeout_us: u64) -> i64 {+    let ts = [timeout_us / 1_000_000, timeout_us % 1_000_000 * 1000];+    syscall5(SYS_PPOLL, fds.as_mut_ptr() as u64, fds.len() as u64, ts.as_ptr() as u64, 0, 8)+}+/// select(nfds, read set, write set, NULL, timeval): the 1983 interface.+/// The sets are bitmaps of fds (64 per word); on return only the ready+/// ones are still set.+pub fn select(nfds: i64, read: &mut [u64; 16], write: &mut [u64; 16], timeout_us: u64) -> i64 {+    let tv = [timeout_us / 1_000_000, timeout_us % 1_000_000];+    syscall5(SYS_SELECT, nfds as u64, read.as_mut_ptr() as u64, write.as_mut_ptr() as u64, 0, tv.as_ptr() as u64)+}+pub fn fd_set(set: &mut [u64; 16], fd: i64) {+    set[fd as usize / 64] |= 1 << (fd % 64);+}+pub fn fd_isset(set: &[u64; 16], fd: i64) -> bool {+    set[fd as usize / 64] & (1 << (fd % 64)) != 0+}  pub const EPOLL_CTL_ADD: u64 = 1; pub const EPOLL_CTL_DEL: u64 = 2;