RustForge

foundry

Foundry OS: the operating system built on the Rustbelt kernel

authorGrok Bot <grokbot@localhost>2026-10-31 18:52:17 -0700
commit2ca603aac89fc7515b60714f32d6479db6365cbd
parent82182faf902deb074eb806d65d60582560aae102
treebrowse files at this commit
Move the shell, tools and net/sys programs into the rust/ layout; drop the ABI test fixtures

Pure renames (git log --follow works). The ABI test programs stay in the
kernel repo as fixtures (approved list, proposal section 7); the image build
compiles them from the pinned kernel commit instead:
hello evil spin demo child overflow forktest cowtest mmaptest sigtest
threads echoserver ptytest nbtest fputest pathtest libctest coretest
unixtest rawabi vdsotest piehello piethreads dynhello remaptest.
Dimport/kernel-user/child.rs+0 -41
Dimport/kernel-user/coretest.rs+0 -926
Dimport/kernel-user/cowtest.rs+0 -107
Dimport/kernel-user/demo.rs+0 -157
Dimport/kernel-user/dynhello.c+0 -13
Dimport/kernel-user/echoserver.rs+0 -206
Dimport/kernel-user/evil.rs+0 -17
Dimport/kernel-user/forktest.rs+0 -186
Dimport/kernel-user/fputest.rs+0 -457
Dimport/kernel-user/hello.rs+0 -57
Dimport/kernel-user/libctest.rs+0 -655
Dimport/kernel-user/mmaptest.rs+0 -130
Dimport/kernel-user/nbtest.rs+0 -151
Dimport/kernel-user/overflow.rs+0 -29
Dimport/kernel-user/pathtest.rs+0 -177
Dimport/kernel-user/piehello.c+0 -65
Dimport/kernel-user/piethreads.c+0 -122
Dimport/kernel-user/ptytest.rs+0 -213
Dimport/kernel-user/rawabi.c+0 -274
Dimport/kernel-user/remaptest.c+0 -221
Dimport/kernel-user/sigtest.rs+0 -189
Dimport/kernel-user/spin.rs+0 -22
Dimport/kernel-user/threads.rs+0 -135
Dimport/kernel-user/unixtest.rs+0 -889
Dimport/kernel-user/vdsotest.c+0 -251
Rimport/kernel-user/cat.rs → rust/coreutils-rs/src/bin/cat.rs+0 -0
Rimport/kernel-user/cp.rs → rust/coreutils-rs/src/bin/cp.rs+0 -0
Rimport/kernel-user/grep.rs → rust/coreutils-rs/src/bin/grep.rs+0 -0
Rimport/kernel-user/ls.rs → rust/coreutils-rs/src/bin/ls.rs+0 -0
Rimport/kernel-user/mkdir.rs → rust/coreutils-rs/src/bin/mkdir.rs+0 -0
Rimport/kernel-user/rm.rs → rust/coreutils-rs/src/bin/rm.rs+0 -0
Rimport/kernel-user/sleep.rs → rust/coreutils-rs/src/bin/sleep.rs+0 -0
Rimport/kernel-user/touch.rs → rust/coreutils-rs/src/bin/touch.rs+0 -0
Rimport/kernel-user/wc.rs → rust/coreutils-rs/src/bin/wc.rs+0 -0
Rimport/kernel-user/link.ld → rust/link.ld+0 -0
Rimport/kernel-user/fetch.rs → rust/net/src/bin/fetch.rs+0 -0
Rimport/kernel-user/host.rs → rust/net/src/bin/host.rs+0 -0
Rimport/kernel-user/httpd.rs → rust/net/src/bin/httpd.rs+0 -0
Rimport/kernel-user/ifconfig.rs → rust/net/src/bin/ifconfig.rs+0 -0
Rimport/kernel-user/ping.rs → rust/net/src/bin/ping.rs+0 -0
Rimport/kernel-user/ulib.rs → rust/rbrt/ulib.rs+0 -0
Rimport/kernel-user/sh.rs → rust/rbsh/src/main.rs+0 -0
Rimport/kernel-user/shparse.rs → rust/rbsh/src/parse.rs+0 -0
Rimport/kernel-user/getty.rs → rust/sys/src/bin/getty.rs+0 -0
Rimport/kernel-user/random.rs → rust/sys/src/bin/random.rs+0 -0
Rimport/kernel-user/sysinfo.rs → rust/sys/src/bin/sysinfo.rs+0 -0
Rimport/kernel-user/tile.rs → rust/sys/src/bin/tile.rs+0 -0

47 files changed, 0 insertions, 5690 deletions

import/kernel-user/child.rs
@@ -1,41 +0,0 @@-//! Helper program that `demo` (and the tests) spawn:-//!   child             -> print my arguments, exit 0-//!   child <number>    -> exit with that number (tests wait() exit codes)-//!   child pipe        -> write a line to stdout (demo reads it via a pipe)-//!   child cat         -> copy stdin to stdout until EOF-#![no_std]-#![no_main]--#[macro_use]-#[path = "ulib.rs"]-mod ulib;--use ulib::*;--fn main() -> i64 {-    match arg(1) {-        Some("pipe") => {-            println!("hello through a pipe from pid {}", getpid());-            0-        }-        Some("cat") => {-            let mut buf = [0u8; 64];-            loop {-                let n = read(0, &mut buf);-                if n <= 0 {-                    return n;-                }-                write(1, &buf[..n as usize]);-            }-        }-        Some(number) if number.parse::<i64>().is_ok() => number.parse().unwrap(),-        _ => {-            print!("[child] pid {} got {} args:", getpid(), argc());-            for i in 0..argc() {-                print!(" {:?}", arg(i).unwrap_or("?"));-            }-            println!();-            0-        }-    }-}
import/kernel-user/coretest.rs
@@ -1,926 +0,0 @@-//! coretest -- stage 17, "coreutils batch 2": the system calls coreutils-//! and busybox use beyond what libctest (stage 16) covers. Run with one-//! group name; exit code = number of failed checks:-//!   links    link/linkat, symlink/symlinkat on ramfs and ext2; EPERM on-//!            FAT/exFAT, EROFS on btrfs; rename over a second hard link-//!   owners   chown/fchown/lchown/fchownat (stored, not enforced)-//!   statfs   statfs/fstatfs magic numbers and sizes, statx, st_dev-//!   info     sysinfo (99), times, getrusage, CPU clocks, clock_getres-//!   sockets  getsockname/getpeername, get/setsockopt, shutdown, socketpair-//!   timers   alarm, setitimer/getitimer, timer_create & co.-//!   ids      setuid & co., groups, priority, sethostname-//!   misc     flock, preadv/pwritev, sendfile, ppoll/pselect6 masks,-//!            getdents while deleting-#![no_std]-#![no_main]--#[macro_use]-#[path = "ulib.rs"]-mod ulib;--extern crate alloc;--use alloc::format;-use alloc::vec::Vec;-use core::sync::atomic::{AtomicI64, AtomicU64, Ordering};-use ulib::*;--// Stage 17 syscall numbers (Linux's).-const SYS_SHUTDOWN: u64 = 48;-const SYS_GETSOCKNAME: u64 = 51;-const SYS_GETPEERNAME: u64 = 52;-const SYS_SOCKETPAIR: u64 = 53;-const SYS_GETSOCKOPT: u64 = 55;-const SYS_GETITIMER: u64 = 36;-const SYS_ALARM: u64 = 37;-const SYS_SETITIMER: u64 = 38;-const SYS_SENDFILE: u64 = 40;-const SYS_FLOCK: u64 = 73;-const SYS_LINK: u64 = 86;-const SYS_SYMLINK: u64 = 88;-const SYS_CHOWN: u64 = 92;-const SYS_FCHOWN: u64 = 93;-const SYS_LCHOWN: u64 = 94;-const SYS_GETRUSAGE: u64 = 98;-const SYS_SYSINFO: u64 = 99;-const SYS_TIMES: u64 = 100;-const SYS_GETGID: u64 = 104;-const SYS_SETUID: u64 = 105;-const SYS_SETGID: u64 = 106;-const SYS_GETEUID: u64 = 107;-const SYS_SETREUID: u64 = 113;-const SYS_GETGROUPS: u64 = 115;-const SYS_SETGROUPS: u64 = 116;-const SYS_SETRESUID: u64 = 117;-const SYS_GETRESUID: u64 = 118;-const SYS_GETRESGID: u64 = 120;-const SYS_STATFS: u64 = 137;-const SYS_FSTATFS: u64 = 138;-const SYS_GETPRIORITY: u64 = 140;-const SYS_SETPRIORITY: u64 = 141;-const SYS_SETHOSTNAME: u64 = 170;-const SYS_TIMER_CREATE: u64 = 222;-const SYS_TIMER_SETTIME: u64 = 223;-const SYS_TIMER_GETTIME: u64 = 224;-const SYS_TIMER_GETOVERRUN: u64 = 225;-const SYS_TIMER_DELETE: u64 = 226;-const SYS_CLOCK_GETRES: u64 = 229;-const SYS_FCHOWNAT: u64 = 260;-const SYS_LINKAT: u64 = 265;-const SYS_SYMLINKAT: u64 = 266;-const SYS_PSELECT6: u64 = 270;-const SYS_PREADV: u64 = 295;-const SYS_PWRITEV: u64 = 296;-const SYS_STATX: u64 = 332;--const AT_FDCWD: i64 = -100;-const AT_SYMLINK_NOFOLLOW: u64 = 0x100;-const AT_SYMLINK_FOLLOW: u64 = 0x400;-const AT_EMPTY_PATH: u64 = 0x1000;-const O_RDWR: u64 = 2;-const EROFS: i64 = 30;-const EMLINK_OR_EPERM: [i64; 2] = [EPERM, 31];-const EACCES: i64 = 13;-const ENOTCONN: i64 = 107;-const ECONNREFUSED: i64 = 111;-const ENOPROTOOPT: i64 = 92;-const EAFNOSUPPORT: i64 = 97;-const EPIPE: i64 = 32;-const SIGALRM: i32 = 14;--fn cpath(s: &str) -> Vec<u8> {-    let mut v = Vec::from(s.as_bytes());-    v.push(0);-    v-}--fn p2(nr: u64, a: &str, b: &str) -> i64 {-    let (a, b) = (cpath(a), cpath(b));-    syscall3(nr, a.as_ptr() as u64, b.as_ptr() as u64, 0)-}--fn write_file(path: &str, data: &[u8]) -> i64 {-    let fd = open(path, O_WRONLY | O_CREAT | O_TRUNC);-    if fd < 0 {-        return fd;-    }-    let n = write(fd, data);-    close(fd);-    n-}--fn read_file(path: &str) -> Vec<u8> {-    let fd = open(path, O_RDONLY);-    let mut out = Vec::new();-    if fd < 0 {-        return out;-    }-    let mut buf = [0u8; 512];-    loop {-        let n = read(fd, &mut buf);-        if n <= 0 {-            break;-        }-        out.extend_from_slice(&buf[..n as usize]);-    }-    close(fd);-    out-}--fn st(path: &str) -> Stat {-    let mut s = Stat::default();-    if stat(path, &mut s) < 0 {-        s.ino = 0;-        s.nlink = 0;-    }-    s-}--fn lst(path: &str) -> Stat {-    let mut s = Stat::default();-    lstat(path, &mut s);-    s-}--/// Burn CPU in user mode for `ms` (looking at the clock only now and then,-/// so it's nearly all user time).-fn spin_ms(ms: u64) {-    let end = now_us() + ms * 1000;-    let mut x = 0u64;-    while now_us() < end {-        for _ in 0..200_000 {-            x = core::hint::black_box(x.wrapping_mul(31).wrapping_add(7));-        }-    }-}--fn main() -> i64 {-    let group = arg(1).unwrap_or("");-    let mut c = Checks::new("coretest");-    match group {-        "links" => links(&mut c),-        "owners" => owners(&mut c),-        "statfs" => statfs(&mut c),-        "info" => info(&mut c),-        "sockets" => sockets(&mut c),-        "timers" => timers(&mut c),-        "ids" => ids(&mut c),-        "misc" => misc(&mut c),-        _ => c.check(false, "usage: coretest links|owners|statfs|info|sockets|timers|ids|misc"),-    }-    c.done()-}--// --- links -----------------------------------------------------------------------fn links_on(c: &mut Checks, dir: &str) {-    let (a, b, l, d) = (format!("{}/a.txt", dir), format!("{}/b.txt", dir), format!("{}/l", dir), format!("{}/d", dir));-    unlink(&a);-    unlink(&b);-    unlink(&l);-    rmdir(&d);-    write_file(&a, b"hello");-    c.check(p2(SYS_LINK, &a, &b) == 0, &format!("{}: link a.txt b.txt", dir));-    let (sa, sb) = (st(&a), st(&b));-    c.check(sa.ino == sb.ino && sa.ino != 0, &format!("{}: both names, one inode number", dir));-    c.check(sa.nlink == 2 && sb.nlink == 2, &format!("{}: st_nlink is 2", dir));-    let fd = open(&b, O_WRONLY);-    lseek(fd, 0, 2);-    write(fd, b" world");-    close(fd);-    c.check(read_file(&a) == b"hello world", &format!("{}: a write through one name shows through the other", dir));-    c.check(p2(SYS_LINK, &a, &b) == -EEXIST, &format!("{}: link onto an existing name: EEXIST", dir));-    // rename onto another hard link of the same file: nothing happens (POSIX).-    c.check(rename(&a, &b) == 0 && st(&a).nlink == 2 && st(&b).nlink == 2, &format!("{}: rename a.txt over its own link b.txt: both stay", dir));-    c.check(unlink(&a) == 0 && st(&b).nlink == 1 && read_file(&b) == b"hello world", &format!("{}: unlink one name, the data stays", dir));-    // symlinks-    c.check(p2(SYS_SYMLINK, "b.txt", &l) == 0, &format!("{}: symlink b.txt l", dir));-    let mut buf = [0u8; 64];-    let n = readlink(&l, &mut buf);-    c.check(n == 5 && &buf[..5] == b"b.txt", &format!("{}: readlink gives the target as written", dir));-    c.check(lst(&l).is_symlink() && st(&l).is_file() && st(&l).ino == st(&b).ino, &format!("{}: lstat sees the link, stat the file", dir));-    c.check(read_file(&l) == b"hello world", &format!("{}: open follows the symlink", dir));-    c.check(p2(SYS_SYMLINK, "x", &l) == -EEXIST, &format!("{}: symlink onto an existing name: EEXIST", dir));-    // linkat: the symlink itself, or (AT_SYMLINK_FOLLOW) what it points to.-    let (l2, l3) = (format!("{}/l2", dir), format!("{}/l3", dir));-    let (cl, cl2, cl3) = (cpath(&l), cpath(&l2), cpath(&l3));-    let r = syscall5(SYS_LINKAT, AT_FDCWD as u64, cl.as_ptr() as u64, AT_FDCWD as u64, cl2.as_ptr() as u64, 0);-    c.check(r == 0 && lst(&l2).is_symlink(), &format!("{}: linkat without FOLLOW links the symlink itself", dir));-    let r = syscall5(SYS_LINKAT, AT_FDCWD as u64, cl.as_ptr() as u64, AT_FDCWD as u64, cl3.as_ptr() as u64, AT_SYMLINK_FOLLOW);-    c.check(r == 0 && lst(&l3).is_file() && st(&b).nlink == 2, &format!("{}: linkat AT_SYMLINK_FOLLOW links the file", dir));-    // symlinkat relative to a directory fd; a dangling one is fine.-    mkdir(&d);-    let dfd = open(&d, O_RDONLY | O_DIRECTORY);-    let (t, name) = (cpath("../nowhere"), cpath("dangling"));-    c.check(syscall3(SYS_SYMLINKAT, t.as_ptr() as u64, dfd as u64, name.as_ptr() as u64) == 0, &format!("{}: symlinkat(dirfd) makes a dangling link", dir));-    c.check(lst(&format!("{}/dangling", d)).is_symlink() && open(&format!("{}/dangling", d), O_RDONLY) == -ENOENT, &format!("{}: ...which lstat sees and open can't follow", dir));-    close(dfd);-    c.check(p2(SYS_LINK, &d, &format!("{}/dlink", dir)) == -EPERM, &format!("{}: hard link to a directory: EPERM", dir));-    // A directory moved into another one (native rename).-    let e = format!("{}/e", dir);-    rmdir(&e);-    mkdir(&e);-    c.check(rename(&d, &format!("{}/d", e)) == 0 && lst(&format!("{}/d/dangling", e)).is_symlink(), &format!("{}: rename moves a full directory", dir));-    // A long target (ext2 keeps short ones in the inode, long ones in a block).-    let long: alloc::string::String = core::iter::repeat('x').take(100).collect();-    let ll = format!("{}/long", dir);-    unlink(&ll);-    c.check(p2(SYS_SYMLINK, &long, &ll) == 0 && readlink(&ll, &mut [0u8; 200]) == 100, &format!("{}: a 100-byte symlink target", dir));-    // clean up-    for f in [&b, &l, &l2, &l3, &ll, &format!("{}/d/dangling", e)] {-        unlink(f);-    }-    rmdir(&format!("{}/d", e));-    rmdir(&e);-    c.check(access(&b) == -ENOENT && access(&e) == -ENOENT, &format!("{}: all cleaned up", dir));-}--fn links(c: &mut Checks) {-    mkdir("/tmp/lk");-    links_on(c, "/tmp/lk");-    rmdir("/tmp/lk");-    mkdir("/mnt/disk1/lk");-    links_on(c, "/mnt/disk1/lk");-    rmdir("/mnt/disk1/lk");-    // Across file systems: EXDEV.-    write_file("/tmp/x.txt", b"x");-    c.check(p2(SYS_LINK, "/tmp/x.txt", "/mnt/disk1/x.txt") == -EXDEV, "link across file systems: EXDEV");-    unlink("/tmp/x.txt");-    // FAT and exFAT have no links on disk; btrfs is mounted read-only.-    write_file("/mnt/disk0/lk.txt", b"x");-    c.check(EMLINK_OR_EPERM.contains(&-p2(SYS_LINK, "/mnt/disk0/lk.txt", "/mnt/disk0/lk2.txt")), "FAT: link is EPERM");-    c.check(p2(SYS_SYMLINK, "lk.txt", "/mnt/disk0/lk3") == -EPERM, "FAT: symlink is EPERM");-    unlink("/mnt/disk0/lk.txt");-    write_file("/mnt/disk2/lk.txt", b"x");-    c.check(p2(SYS_SYMLINK, "lk.txt", "/mnt/disk2/lk3") == -EPERM, "exFAT: symlink is EPERM");-    unlink("/mnt/disk2/lk.txt");-    c.check(p2(SYS_LINK, "/mnt/disk3/hello.txt", "/mnt/disk3/h2") == -EROFS, "btrfs: link is EROFS");-    c.check(p2(SYS_SYMLINK, "hello.txt", "/mnt/disk3/h3") == -EROFS, "btrfs: symlink is EROFS");-    let s = st("/mnt/disk3/hello.txt");-    c.check(s.nlink >= 1 && s.ino != 0, "btrfs: stat shows the real link count and inode");-}--// --- owners ----------------------------------------------------------------------fn chown(nr: u64, path: &str, uid: u32, gid: u32) -> i64 {-    let p = cpath(path);-    syscall3(nr, p.as_ptr() as u64, uid as u64, gid as u64)-}--fn owners(c: &mut Checks) {-    for dir in ["/tmp", "/mnt/disk1"] {-        let (f, l) = (format!("{}/own.txt", dir), format!("{}/own.lnk", dir));-        unlink(&f);-        unlink(&l);-        write_file(&f, b"x");-        p2(SYS_SYMLINK, "own.txt", &l);-        c.check(chown(SYS_CHOWN, &f, 1000, 100) == 0, &format!("{}: chown 1000:100", dir));-        let s = st(&f);-        c.check(s.uid == 1000 && s.gid == 100, &format!("{}: stat shows 1000:100", dir));-        c.check(chown(SYS_CHOWN, &f, u32::MAX, 50) == 0 && st(&f).uid == 1000 && st(&f).gid == 50, &format!("{}: -1 leaves the uid alone", dir));-        c.check(chown(SYS_LCHOWN, &l, 7, 7) == 0 && lst(&l).uid == 7 && st(&f).uid == 1000, &format!("{}: lchown changes the link, not the file", dir));-        c.check(chown(SYS_CHOWN, &l, 8, 8) == 0 && st(&f).uid == 8 && lst(&l).uid == 7, &format!("{}: chown through a link changes the file", dir));-        let fd = open(&f, O_RDONLY);-        c.check(syscall3(SYS_FCHOWN, fd as u64, 9, 9) == 0 && st(&f).uid == 9, &format!("{}: fchown", dir));-        let empty = cpath("");-        let r = syscall5(SYS_FCHOWNAT, fd as u64, empty.as_ptr() as u64, 0, 0, AT_EMPTY_PATH);-        c.check(r == 0 && st(&f).uid == 0, &format!("{}: fchownat(fd, \"\", AT_EMPTY_PATH)", dir));-        close(fd);-        let name = cpath("own.lnk");-        let dfd = open(dir, O_RDONLY | O_DIRECTORY);-        let r = syscall5(SYS_FCHOWNAT, dfd as u64, name.as_ptr() as u64, 3, 3, AT_SYMLINK_NOFOLLOW);-        c.check(r == 0 && lst(&l).uid == 3 && st(&f).uid == 0, &format!("{}: fchownat(dirfd, AT_SYMLINK_NOFOLLOW)", dir));-        close(dfd);-        unlink(&f);-        unlink(&l);-    }-    // Directories too.-    mkdir("/tmp/ownd");-    c.check(chown(SYS_CHOWN, "/tmp/ownd", 5, 6) == 0 && st("/tmp/ownd").uid == 5, "chown a directory");-    rmdir("/tmp/ownd");-    // FAT: no owners on disk. Root:root (what it shows) is fine, anything else EPERM.-    write_file("/mnt/disk0/own.txt", b"x");-    c.check(chown(SYS_CHOWN, "/mnt/disk0/own.txt", 0, 0) == 0, "FAT: chown to 0:0 (no change) succeeds");-    c.check(chown(SYS_CHOWN, "/mnt/disk0/own.txt", 1000, 1000) == -EPERM, "FAT: chown to 1000 is EPERM");-    unlink("/mnt/disk0/own.txt");-    write_file("/mnt/disk2/own.txt", b"x");-    c.check(chown(SYS_CHOWN, "/mnt/disk2/own.txt", 1000, u32::MAX) == -EPERM, "exFAT: chown to 1000 is EPERM");-    unlink("/mnt/disk2/own.txt");-    c.check(chown(SYS_CHOWN, "/mnt/disk3/hello.txt", 1, 1) == -EROFS, "btrfs: chown is EROFS");-    c.check(chown(SYS_CHOWN, "/tmp/missing", 1, 1) == -ENOENT, "chown of a missing file: ENOENT");-    // Pipes: nothing to own, accepted.-    let (r, w) = pipe().unwrap();-    c.check(syscall3(SYS_FCHOWN, r as u64, 1, 1) == 0, "fchown on a pipe: accepted");-    close(r);-    close(w);-}--// --- statfs / statx ----------------------------------------------------------------fn statfs_of(path: &str) -> (i64, [u64; 15]) {-    let mut buf = [0u64; 15];-    let p = cpath(path);-    (syscall3(SYS_STATFS, p.as_ptr() as u64, buf.as_mut_ptr() as u64, 0), buf)-}--fn statfs(c: &mut Checks) {-    let cases: [(&str, u64, &str); 6] = [-        ("/", 0x8584_58f6, "ramfs"),-        ("/tmp", 0x8584_58f6, "ramfs"),-        ("/mnt/disk0", 0x4d44, "FAT (MSDOS_SUPER_MAGIC)"),-        ("/mnt/disk1/", 0xEF53, "ext2"),-        ("/mnt/disk2", 0x2011_BAB0, "exFAT"),-        ("/mnt/disk3/hello.txt", 0x9123_683E, "btrfs"),-    ];-    for (path, magic, what) in cases {-        let (r, b) = statfs_of(path);-        c.check(r == 0 && b[0] == magic, &format!("statfs {}: f_type = {}", path, what));-        if path.starts_with("/mnt") {-            c.check(b[1] >= 512 && b[2] > 0 && b[3] <= b[2] && b[4] <= b[3], &format!("statfs {}: sizes make sense (bsize {}, {} blocks, {} free)", path, b[1], b[2], b[3]));-            c.check(b[8] >= 255, &format!("statfs {}: f_namelen {}", path, b[8]));-        }-    }-    let (_, b) = statfs_of("/mnt/disk3");-    c.check(b[10] & 1 == 1, "btrfs: ST_RDONLY in f_flags");-    let (_, b) = statfs_of("/mnt/disk1");-    c.check(b[10] & 1 == 0 && b[5] > 0 && b[6] <= b[5], "ext2: writable, counts inodes");-    // Writing a file uses up free blocks on ext2.-    let (_, before) = statfs_of("/mnt/disk1");-    write_file("/mnt/disk1/sf.bin", &[7u8; 64 * 1024]);-    let (_, after) = statfs_of("/mnt/disk1");-    c.check(after[3] < before[3], "ext2: a 64 KiB file lowers f_bfree");-    unlink("/mnt/disk1/sf.bin");-    c.check(statfs_of("/nope").0 == -ENOENT, "statfs of a missing path: ENOENT");-    let fd = open("/mnt/disk0", O_RDONLY | O_DIRECTORY);-    let mut b = [0u64; 15];-    c.check(syscall3(SYS_FSTATFS, fd as u64, b.as_mut_ptr() as u64, 0) == 0 && b[0] == 0x4d44, "fstatfs on a FAT directory fd");-    close(fd);-    let (r, w) = pipe().unwrap();-    c.check(syscall3(SYS_FSTATFS, r as u64, b.as_mut_ptr() as u64, 0) == 0 && b[0] == 0x5049_5045, "fstatfs on a pipe: PIPEFS_MAGIC");-    close(r);-    close(w);-    // st_dev tells file systems apart.-    c.check(st("/tmp").dev != st("/mnt/disk1").dev && st("/mnt/disk1/").dev == st("/mnt/disk1/lost+found").dev, "st_dev: one per file system");-    // Stage 18 fix check (Clarke): a mount point itself reports the-    // *mounted* file system's st_dev and root inode, not the directory it-    // covers. find -xdev, du -x and cp -x compare st_dev with this.-    write_file("/tmp/devchk", b"x");-    c.check(st("/tmp").dev == st("/tmp/devchk").dev && st("/tmp").dev != st("/").dev, "stat(\"/tmp\"): the mounted ramfs's st_dev, not /'s");-    c.check(st("/dev").dev == st("/dev/null").dev && st("/dev").dev != st("/").dev, "stat(\"/dev\"): devfs's st_dev");-    c.check(st("/mnt/disk1").dev == st("/mnt/disk1/lost+found").dev && st("/mnt/disk1").ino == 2, "stat(\"/mnt/disk1\"): ext2's st_dev and its root inode (2)");-    let mut ls = Stat::default();-    c.check(lstat("/tmp", &mut ls) == 0 && ls.dev == st("/tmp/devchk").dev, "lstat of a mount point: the mounted fs's st_dev too");-    let fd = open("/mnt/disk1", O_RDONLY | O_DIRECTORY);-    let mut fs_ = Stat::default();-    c.check(fstat(fd, &mut fs_) == 0 && fs_.dev == st("/mnt/disk1/lost+found").dev && fs_.ino == 2, "fstat of an fd opened on a mount point: same");-    close(fd);-    unlink("/tmp/devchk");-    // statx-    write_file("/tmp/sx.txt", b"twelve bytes");-    let mut sx = [0u8; 256];-    let p = cpath("/tmp/sx.txt");-    let r = syscall5(SYS_STATX, AT_FDCWD as u64, p.as_ptr() as u64, 0, 0x7ff, sx.as_mut_ptr() as u64);-    let u32at = |b: &[u8], o: usize| u32::from_le_bytes(b[o..o + 4].try_into().unwrap());-    let u64at = |b: &[u8], o: usize| u64::from_le_bytes(b[o..o + 8].try_into().unwrap());-    let s = st("/tmp/sx.txt");-    c.check(r == 0 && u32at(&sx, 0) & 0x7ff == 0x7ff, "statx: basic fields in stx_mask");-    c.check(u64at(&sx, 40) == 12 && u64at(&sx, 32) == s.ino && u32at(&sx, 16) == 1, "statx: size, inode number, nlink match stat");-    c.check(u32::from(u16::from_le_bytes([sx[28], sx[29]])) == s.mode && u64at(&sx, 112) as i64 == s.mtime[0], "statx: mode and mtime match stat");-    c.check(u32at(&sx, 140) as u64 == s.dev, "statx: device minor = st_dev");-    let fd = open("/tmp/sx.txt", O_RDONLY);-    let empty = cpath("");-    let r = syscall5(SYS_STATX, fd as u64, empty.as_ptr() as u64, AT_EMPTY_PATH, 0x7ff, sx.as_mut_ptr() as u64);-    c.check(r == 0 && u64at(&sx, 40) == 12, "statx(fd, \"\", AT_EMPTY_PATH)");-    close(fd);-    let r = syscall5(SYS_STATX, AT_FDCWD as u64, p.as_ptr() as u64, 0x4, 0x7ff, sx.as_mut_ptr() as u64);-    c.check(r == -EINVAL, "statx with an unknown flag: EINVAL");-    p2(SYS_SYMLINK, "sx.txt", "/tmp/sx.lnk");-    let pl = cpath("/tmp/sx.lnk");-    syscall5(SYS_STATX, AT_FDCWD as u64, pl.as_ptr() as u64, AT_SYMLINK_NOFOLLOW, 0x7ff, sx.as_mut_ptr() as u64);-    c.check(u32::from(u16::from_le_bytes([sx[28], sx[29]])) & S_IFMT == S_IFLNK, "statx AT_SYMLINK_NOFOLLOW: the link itself");-    unlink("/tmp/sx.lnk");-    unlink("/tmp/sx.txt");-}--// --- info: sysinfo, times, getrusage, clocks -----------------------------------------fn clock_ns(clock: u64) -> (i64, u64) {-    let mut ts = [0u64; 2];-    let r = syscall3(SYS_CLOCK_GETTIME, clock, ts.as_mut_ptr() as u64, 0);-    (r, ts[0] * 1_000_000_000 + ts[1])-}--fn info(c: &mut Checks) {-    let mut si = [0u64; 14];-    c.check(syscall3(SYS_SYSINFO, si.as_mut_ptr() as u64, 0, 0) == 0, "sysinfo (99): 0");-    let procs = si[10] & 0xffff;-    let mem_unit = si[13] & 0xffff_ffff;-    c.check(si[0] > 0 && si[4] > si[5] && si[5] > 0, "sysinfo: uptime, totalram > freeram > 0");-    c.check(procs >= 1 && mem_unit == 1, "sysinfo: procs >= 1, mem_unit 1");-    c.check(sysinfo().cpus == 4, "Rustbelt's own sysinfo (502) still works");-    // CPU time: spin 300 ms, it shows up everywhere.-    let mut tms = [0u64; 4];-    let t0 = syscall3(SYS_TIMES, tms.as_mut_ptr() as u64, 0, 0);-    let (_, p0) = clock_ns(2);-    spin_ms(300);-    let t1 = syscall3(SYS_TIMES, tms.as_mut_ptr() as u64, 0, 0);-    c.check(t1 - t0 >= 25 && t1 - t0 <= 60, &format!("times: returns ticks (100/s): {} after 300 ms", t1 - t0));-    c.check(tms[0] + tms[1] >= 15, &format!("times: utime+stime {} ticks after spinning 300 ms", tms[0] + tms[1]));-    c.check(tms[0] >= tms[1], "times: spinning is mostly user time");-    let (r, p1) = clock_ns(2);-    c.check(r == 0 && p1 - p0 >= 150_000_000, "CLOCK_PROCESS_CPUTIME_ID advanced >= 150 ms");-    let (r, t) = clock_ns(3);-    c.check(r == 0 && t >= 150_000_000, "CLOCK_THREAD_CPUTIME_ID too");-    let mut ru = [0u64; 18];-    c.check(syscall3(SYS_GETRUSAGE, 0, ru.as_mut_ptr() as u64, 0) == 0, "getrusage(RUSAGE_SELF)");-    c.check(ru[0] * 1_000_000 + ru[1] >= 150_000 && ru[4] > 0, "getrusage: ru_utime >= 150 ms, ru_maxrss > 0");-    // Children's times count once they're reaped.-    let pid = fork();-    if pid == 0 {-        spin_ms(200);-        exit(0);-    }-    wait4(pid, 0).ok();-    syscall3(SYS_TIMES, tms.as_mut_ptr() as u64, 0, 0);-    c.check(tms[2] + tms[3] >= 10, &format!("times: the reaped child's {} ticks are in cutime+cstime", tms[2] + tms[3]));-    syscall3(SYS_GETRUSAGE, (-1i64) as u64, ru.as_mut_ptr() as u64, 0);-    c.check(ru[0] * 1_000_000 + ru[1] + ru[2] * 1_000_000 + ru[3] >= 100_000, "getrusage(RUSAGE_CHILDREN) has it too");-    c.check(syscall3(SYS_GETRUSAGE, 5, ru.as_mut_ptr() as u64, 0) == -EINVAL, "getrusage(5): EINVAL");-    let mut res = [0u64; 2];-    c.check(syscall3(SYS_CLOCK_GETRES, 1, res.as_mut_ptr() as u64, 0) == 0 && res == [0, 1], "clock_getres(MONOTONIC): 1 ns (stage 19; was 1 µs)");-    c.check(syscall3(SYS_CLOCK_GETRES, 2, res.as_mut_ptr() as u64, 0) == 0 && res == [0, 10_000_000], "clock_getres(PROCESS_CPUTIME): 10 ms");-    c.check(syscall3(SYS_CLOCK_GETRES, 0, 0, 0) == 0, "clock_getres with a NULL pointer: 0");-    c.check(syscall3(SYS_CLOCK_GETRES, 42, res.as_mut_ptr() as u64, 0) == -EINVAL, "clock_getres(42): EINVAL");-}--// --- sockets -------------------------------------------------------------------------fn getsockopt_int(fd: i64, level: u64, opt: u64) -> (i64, u32) {-    let mut v = 0u32;-    let mut len = 4u32;-    let r = syscall5(SYS_GETSOCKOPT, fd as u64, level, opt, &mut v as *mut u32 as u64, &mut len as *mut u32 as u64);-    (r, v)-}--fn setsockopt_int(fd: i64, level: u64, opt: u64, v: u32) -> i64 {-    syscall5(SYS_SETSOCKOPT, fd as u64, level, opt, &v as *const u32 as u64, 4)-}--fn sockname(nr: u64, fd: i64) -> (i64, SockAddrIn) {-    let mut sa = SockAddrIn::default();-    let mut len = 16u32;-    let r = syscall3(nr, fd as u64, &mut sa as *mut _ as u64, &mut len as *mut u32 as u64);-    (r, sa)-}--fn sockets(c: &mut Checks) {-    sigaction_raw(13, 1, 0, 0); // SIGPIPE: SIG_IGN, so a write after shutdown is just EPIPE-    let srv = socket(SOCK_STREAM, 0);-    c.check(setsockopt_int(srv, 1, 2, 1) == 0, "setsockopt SO_REUSEADDR: 0 (was ENOPROTOOPT)");-    c.check(getsockopt_int(srv, 1, 2) == (0, 1), "getsockopt SO_REUSEADDR reads it back");-    c.check(setsockopt_int(srv, 1, 15, 1) == 0 && setsockopt_int(srv, 1, 9, 1) == 0, "SO_REUSEPORT, SO_KEEPALIVE: 0");-    c.check(setsockopt_int(srv, 6, 1, 1) == 0, "TCP_NODELAY: 0");-    c.check(setsockopt_int(srv, 1, 99, 1) == -ENOPROTOOPT, "an unknown option: ENOPROTOOPT");-    c.check(bind(srv, 7091) == 0 && listen(srv, 4) == 0, "bind 7091 + listen");-    let (r, sa) = sockname(SYS_GETSOCKNAME, srv);-    c.check(r == 0 && sa.port() == 7091 && sa.family == 2, "getsockname on the listener: port 7091");-    c.check(getsockopt_int(srv, 1, 30) == (0, 1), "SO_ACCEPTCONN: listening");-    c.check(sockname(SYS_GETPEERNAME, srv).0 == -ENOTCONN, "getpeername on a listener: ENOTCONN");-    let cl = socket(SOCK_STREAM, 0);-    c.check(syscall3(SYS_SHUTDOWN, cl as u64, 1, 0) == -ENOTCONN, "shutdown before connecting: ENOTCONN");-    c.check(connect(cl, Ip([127, 0, 0, 1]), 7091) == 0, "connect to 127.0.0.1:7091");-    let (conn, _) = accept(srv);-    c.check(conn >= 0, "accept");-    let (r, me) = sockname(SYS_GETSOCKNAME, cl);-    let (r2, peer) = sockname(SYS_GETPEERNAME, conn);-    c.check(r == 0 && r2 == 0 && me.addr == [127, 0, 0, 1] && me.port() == peer.port() && me.port() != 0, "client's getsockname = server's getpeername");-    let (_, p) = sockname(SYS_GETPEERNAME, cl);-    c.check(p.port() == 7091 && p.addr == [127, 0, 0, 1], "client's getpeername: 127.0.0.1:7091");-    c.check(getsockopt_int(cl, 1, 3) == (0, 1), "SO_TYPE: SOCK_STREAM");-    c.check(getsockopt_int(cl, 1, 4) == (0, 0), "SO_ERROR: 0");-    let (r, rcv) = getsockopt_int(cl, 1, 8);-    c.check(r == 0 && rcv > 0, "SO_RCVBUF > 0");-    // shutdown(SHUT_WR): the other side reads end-of-file, but can still answer.-    write(cl, b"ping");-    c.check(syscall3(SYS_SHUTDOWN, cl as u64, 1, 0) == 0, "shutdown(SHUT_WR)");-    let mut buf = [0u8; 16];-    let mut got = Vec::new();-    loop {-        let n = read(conn, &mut buf);-        if n <= 0 {-            c.check(n == 0, "server reads end-of-file after the data");-            break;-        }-        got.extend_from_slice(&buf[..n as usize]);-    }-    c.check(got == b"ping", "server got \"ping\"");-    c.check(write(cl, b"x") == -EPIPE, "client write after SHUT_WR: EPIPE");-    write(conn, b"pong");-    let n = read(cl, &mut buf);-    c.check(n == 4 && &buf[..4] == b"pong", "client still reads the answer");-    close(conn);-    close(cl);-    close(srv);-    // UDP-    let u = socket(SOCK_DGRAM, 0);-    c.check(getsockopt_int(u, 1, 3) == (0, 2), "UDP: SO_TYPE is SOCK_DGRAM");-    bind(u, 7092);-    c.check(sockname(SYS_GETSOCKNAME, u).1.port() == 7092, "UDP getsockname: 7092");-    close(u);-    // Nonblocking connect to a closed port: SO_ERROR says why.-    let nb = socket(SOCK_STREAM, 0);-    set_nonblocking(nb, true);-    let r = connect(nb, Ip([127, 0, 0, 1]), 7093);-    c.check(r == -EINPROGRESS || r == -ECONNREFUSED, "nonblocking connect to a closed port: EINPROGRESS");-    if r == -EINPROGRESS {-        let mut fds = [PollFd { fd: nb as i32, events: POLLOUT, revents: 0 }];-        poll(&mut fds, 1000);-        c.check(getsockopt_int(nb, 1, 4) == (0, ECONNREFUSED as u32), "SO_ERROR: ECONNREFUSED");-        c.check(getsockopt_int(nb, 1, 4) == (0, 0), "SO_ERROR is cleared once read");-    }-    close(nb);-    let mut sv = [0i32; 2];-    c.check(syscall5(SYS_SOCKETPAIR, 1, 1, 0, sv.as_mut_ptr() as u64, 0) == 0, "socketpair(AF_UNIX) works (stage 18)");-    close(sv[0] as i64);-    close(sv[1] as i64);-    c.check(syscall5(SYS_SOCKETPAIR, 3, 1, 0, sv.as_mut_ptr() as u64, 0) == -EAFNOSUPPORT, "socketpair(family 3): EAFNOSUPPORT");-    c.check(syscall3(SYS_SHUTDOWN, 0, 1, 0) == -88, "shutdown on a non-socket: ENOTSOCK");-}--// --- timers ----------------------------------------------------------------------------static ALRM: AtomicI64 = AtomicI64::new(0);-static TCODE: AtomicI64 = AtomicI64::new(0);-static TVAL: AtomicU64 = AtomicU64::new(0);-static TOVR: AtomicI64 = AtomicI64::new(-1);--extern "C" fn on_alarm(_sig: i32) {-    ALRM.fetch_add(1, Ordering::SeqCst);-}--extern "C" fn on_timer(_sig: i32, info: *const SigInfo, _ctx: *mut u8) {-    unsafe {-        TCODE.store((*info).code as i64, Ordering::SeqCst);-        TVAL.store(*((info as *const u8).add(24) as *const u64), Ordering::SeqCst);-        TOVR.store(*((info as *const u8).add(20) as *const i32) as i64, Ordering::SeqCst); // si_overrun-    }-    ALRM.fetch_add(1, Ordering::SeqCst);-}--fn wait_for(n: i64, ms: u64) -> bool {-    let end = now_us() + ms * 1000;-    while ALRM.load(Ordering::SeqCst) < n && now_us() < end {-        sleep_ms(5);-    }-    ALRM.load(Ordering::SeqCst) >= n-}--/// Sleep the whole `ms`: a signal cuts nanosleep short (EINTR), so go back-/// to sleep until the time is really up.-fn sleep_through(ms: u64) {-    let end = now_us() + ms * 1000;-    loop {-        let now = now_us();-        if now >= end {-            return;-        }-        nanosleep_us(end - now);-    }-}--fn itimer(value_us: u64, interval_us: u64) -> [u64; 4] {-    let new = [interval_us / 1_000_000, interval_us % 1_000_000, value_us / 1_000_000, value_us % 1_000_000];-    let mut old = [0u64; 4];-    syscall3(SYS_SETITIMER, 0, new.as_ptr() as u64, old.as_mut_ptr() as u64);-    old-}--fn timers(c: &mut Checks) {-    signal(SIGALRM, on_alarm);-    ALRM.store(0, Ordering::SeqCst);-    c.check(syscall3(SYS_ALARM, 5, 0, 0) == 0, "alarm(5): no alarm before");-    c.check(syscall3(SYS_ALARM, 0, 0, 0) == 5, "alarm(0) cancels it: 5 seconds were left");-    // One-shot setitimer.-    let t0 = now_us();-    itimer(100_000, 0);-    let mut cur = [0u64; 4];-    syscall3(SYS_GETITIMER, 0, cur.as_mut_ptr() as u64, 0);-    c.check(cur[2] == 0 && cur[3] > 0 && cur[3] <= 100_000, "getitimer: time left <= 100 ms");-    c.check(wait_for(1, 1000), "setitimer 100 ms: SIGALRM arrives");-    let dt = now_us() - t0;-    c.check(dt >= 100_000 && dt < 400_000, &format!("...after {} ms (never early)", dt / 1000));-    sleep_through(150);-    c.check(ALRM.load(Ordering::SeqCst) == 1, "one-shot: only once");-    // pause() wakes up for it.-    syscall3(SYS_ALARM, 1, 0, 0);-    let t0 = now_us();-    pause();-    c.check(ALRM.load(Ordering::SeqCst) == 2 && now_us() - t0 >= 900_000, "alarm(1) + pause: woken after a second");-    // Periodic.-    ALRM.store(0, Ordering::SeqCst);-    itimer(60_000, 60_000);-    sleep_through(500);-    let old = itimer(0, 0);-    let n = ALRM.load(Ordering::SeqCst);-    c.check((3..=9).contains(&n), &format!("periodic 60 ms timer: {} signals in 500 ms", n));-    c.check(old[0] == 0 && old[1] == 60_000, "setitimer returns the old interval");-    sleep_through(150);-    c.check(ALRM.load(Ordering::SeqCst) == n, "disarmed: no more signals");-    c.check(syscall3(SYS_SETITIMER, 1, cur.as_ptr() as u64, 0) == -EINVAL, "ITIMER_VIRTUAL: EINVAL (not supported)");-    // POSIX timers.-    sigaction_info(SIGUSR1, on_timer, 0);-    ALRM.store(0, Ordering::SeqCst);-    let mut sev = [0u64; 8];-    sev[0] = 42; // sigval-    sev[1] = SIGUSR1 as u64; // signo, notify = SIGEV_SIGNAL (0)-    let mut id = -1i32;-    let r = syscall3(SYS_TIMER_CREATE, 1, sev.as_ptr() as u64, &mut id as *mut i32 as u64);-    c.check(r == 0 && id >= 0, "timer_create(CLOCK_MONOTONIC, SIGUSR1)");-    let spec = [0u64, 0, 0, 80_000_000]; // interval 0, value 80 ms-    c.check(syscall6(SYS_TIMER_SETTIME, id as u64, 0, spec.as_ptr() as u64, 0, 0, 0) == 0, "timer_settime 80 ms");-    let mut now = [0u64; 4];-    syscall3(SYS_TIMER_GETTIME, id as u64, now.as_mut_ptr() as u64, 0);-    c.check(now[2] == 0 && now[3] > 0 && now[3] <= 80_000_000, "timer_gettime: time left <= 80 ms");-    c.check(wait_for(1, 1000), "the timer's signal arrives");-    c.check(TCODE.load(Ordering::SeqCst) == -2 && TVAL.load(Ordering::SeqCst) == 42, "si_code SI_TIMER, si_value 42");-    c.check(syscall3(SYS_TIMER_GETOVERRUN, id as u64, 0, 0) == 0, "timer_getoverrun: 0");-    // Absolute time on CLOCK_MONOTONIC, periodic.-    ALRM.store(0, Ordering::SeqCst);-    let (s, ns) = clock_gettime(1);-    let at = s * 1_000_000_000 + ns + 50_000_000;-    let spec = [0u64, 50_000_000, at / 1_000_000_000, at % 1_000_000_000];-    c.check(syscall6(SYS_TIMER_SETTIME, id as u64, 1, spec.as_ptr() as u64, 0, 0, 0) == 0, "timer_settime TIMER_ABSTIME, every 50 ms");-    c.check(wait_for(3, 1000), "...fires repeatedly");-    let zero = [0u64; 4];-    let mut old = [0u64; 4];-    syscall6(SYS_TIMER_SETTIME, id as u64, 0, zero.as_ptr() as u64, old.as_mut_ptr() as u64, 0, 0);-    c.check(old[1] == 50_000_000, "disarming returns the old interval");-    c.check(syscall3(SYS_TIMER_DELETE, id as u64, 0, 0) == 0, "timer_delete");-    c.check(syscall3(SYS_TIMER_DELETE, id as u64, 0, 0) == -EINVAL, "timer_delete again: EINVAL");-    // Overruns (stage 18 fix, found by Clarke): a 20 ms timer whose signal-    // is blocked for 0.5 s expires ~25 times but sends one signal; the-    // other ~24 add up as overruns. timer_getoverrun reports the count of-    // the last *delivered* signal: 0 while it is still pending.-    ALRM.store(0, Ordering::SeqCst);-    TOVR.store(-1, Ordering::SeqCst);-    let r = syscall3(SYS_TIMER_CREATE, 1, sev.as_ptr() as u64, &mut id as *mut i32 as u64);-    let old_mask = sigprocmask(SIG_BLOCK, sigbit(SIGUSR1)) as u64;-    let spec = [0u64, 20_000_000, 0, 20_000_000]; // every 20 ms-    syscall6(SYS_TIMER_SETTIME, id as u64, 0, spec.as_ptr() as u64, 0, 0, 0);-    sleep_through(500);-    c.check(r == 0 && syscall3(SYS_TIMER_GETOVERRUN, id as u64, 0, 0) == 0, "overrun: 0 before the first signal is delivered");-    sigprocmask(SIG_SETMASK, old_mask); // delivered now-    let first = TOVR.load(Ordering::SeqCst);-    let got = syscall3(SYS_TIMER_GETOVERRUN, id as u64, 0, 0);-    c.check(ALRM.load(Ordering::SeqCst) == 1 && (18..=30).contains(&first), &format!("blocked 0.5 s: one signal, si_overrun {} (Linux: ~24)", first));-    c.check(got == first, &format!("timer_getoverrun = the delivered signal's si_overrun ({})", got));-    c.check(wait_for(2, 1000), "unblocked: the next signal comes");-    let next = TOVR.load(Ordering::SeqCst);-    // (Not 0: timerd and signal delivery both work in ~55 ms ticks.)-    c.check((0..=10).contains(&next), &format!("...with a fresh count ({}), not the old one added", next));-    syscall3(SYS_TIMER_DELETE, id as u64, 0, 0);-    // SIGEV_NONE: nothing is sent, the time just runs down.-    sev[1] = 1u64 << 32; // notify = SIGEV_NONE-    syscall3(SYS_TIMER_CREATE, 0, sev.as_ptr() as u64, &mut id as *mut i32 as u64);-    let spec = [0u64, 0, 10, 0];-    syscall6(SYS_TIMER_SETTIME, id as u64, 0, spec.as_ptr() as u64, 0, 0, 0);-    sleep_through(50);-    syscall3(SYS_TIMER_GETTIME, id as u64, now.as_mut_ptr() as u64, 0);-    c.check(now[2] == 9 || (now[2] < 10 && now[2] >= 9), "SIGEV_NONE on CLOCK_REALTIME: the time runs down");-    syscall3(SYS_TIMER_DELETE, id as u64, 0, 0);-    c.check(syscall3(SYS_TIMER_CREATE, 2, 0, &mut id as *mut i32 as u64) == -EINVAL, "timer_create on a CPU clock: EINVAL");-    sev[1] = (99u64 << 32) | SIGUSR1 as u64;-    c.check(syscall3(SYS_TIMER_CREATE, 1, sev.as_ptr() as u64, &mut id as *mut i32 as u64) == -EINVAL, "an unknown sigev_notify: EINVAL");-    // A child's timers don't survive it.-    let pid = fork();-    if pid == 0 {-        syscall3(SYS_ALARM, 100, 0, 0);-        let mut id = 0i32;-        syscall3(SYS_TIMER_CREATE, 1, 0, &mut id as *mut i32 as u64);-        exit(0);-    }-    wait4(pid, 0).ok();-    c.check(syscall3(SYS_ALARM, 0, 0, 0) == 0, "the child's alarm isn't ours");-}--// --- ids, priority, hostname ----------------------------------------------------------------fn res(nr: u64) -> [u32; 3] {-    let mut v = [0u32; 3];-    syscall3(nr, &mut v[0] as *mut u32 as u64, &mut v[1] as *mut u32 as u64, &mut v[2] as *mut u32 as u64);-    v-}--fn in_child(f: impl FnOnce() -> i64) -> i64 {-    let pid = fork();-    if pid == 0 {-        exit(f());-    }-    match wait4(pid, 0) {-        Ok((_, s)) => wexitstatus(s) as i64,-        Err(e) => e,-    }-}--fn ids(c: &mut Checks) {-    c.check(syscall3(SYS_GETUID, 0, 0, 0) == 0 && syscall3(SYS_GETEUID, 0, 0, 0) == 0 && syscall3(SYS_GETGID, 0, 0, 0) == 0, "we start as root");-    c.check(res(SYS_GETRESUID) == [0, 0, 0], "getresuid: 0 0 0");-    // Root drops to 1000 for good.-    let r = in_child(|| {-        let mut fails = 0;-        let groups = [5u32, 6];-        fails += (syscall3(SYS_SETGROUPS, 2, groups.as_ptr() as u64, 0) != 0) as i64;-        let mut got = [0u32; 8];-        fails += (syscall3(SYS_GETGROUPS, 0, 0, 0) != 2) as i64;-        fails += (syscall3(SYS_GETGROUPS, 8, got.as_mut_ptr() as u64, 0) != 2 || got[..2] != groups) as i64;-        fails += (syscall3(SYS_GETGROUPS, 1, got.as_mut_ptr() as u64, 0) != -EINVAL) as i64;-        fails += (syscall3(SYS_SETGID, 100, 0, 0) != 0 || res(SYS_GETRESGID) != [100, 100, 100]) as i64;-        fails += (syscall3(SYS_SETUID, 1000, 0, 0) != 0 || res(SYS_GETRESUID) != [1000, 1000, 1000]) as i64;-        fails += (syscall3(SYS_SETUID, 0, 0, 0) != -EPERM) as i64;-        fails += (syscall3(SYS_SETGROUPS, 0, 0, 0) != -EPERM) as i64;-        let name = b"evil";-        fails += (syscall3(SYS_SETHOSTNAME, name.as_ptr() as u64, 4, 0) != -EPERM) as i64;-        // A file it makes is its own.-        unlink("/tmp/u1000.txt");-        write_file("/tmp/u1000.txt", b"x");-        let s = st("/tmp/u1000.txt");-        fails += (s.uid != 1000 || s.gid != 100) as i64;-        fails += (syscall3(SYS_SETPRIORITY, 0, 0, 5) != 0) as i64;-        fails += (syscall3(SYS_SETPRIORITY, 0, 0, 0) != -EACCES) as i64; // only root raises priority-        fails-    });-    c.check(r == 0, "child: setgroups/getgroups, setgid, setuid(1000) for good, then EPERM; its files are 1000:100; nice only up");-    c.check(st("/tmp/u1000.txt").uid == 1000, "the file stays 1000's");-    unlink("/tmp/u1000.txt");-    // setresuid keeps root in the saved id: it can come back.-    let r = in_child(|| {-        let mut fails = 0;-        fails += (syscall3(SYS_SETRESUID, 1000, 1000, 0) != 0) as i64;-        fails += (syscall3(SYS_GETEUID, 0, 0, 0) != 1000) as i64;-        fails += (syscall3(SYS_SETRESUID, 2000, u64::MAX, u64::MAX) != -EPERM) as i64;-        fails += (syscall3(SYS_SETUID, 0, 0, 0) != 0 || res(SYS_GETRESUID) != [1000, 0, 0]) as i64;-        // setreuid(-1, 1000): the effective id goes back to the real one, so-        // (Linux's rule) the saved id stays 0...-        fails += (syscall3(SYS_SETREUID, u32::MAX as u64, 1000, 0) != 0 || res(SYS_GETRESUID) != [1000, 1000, 0]) as i64;-        // ...while setting the real id copies the effective one into it.-        fails += (syscall3(SYS_SETUID, 0, 0, 0) != 0 || syscall3(SYS_SETREUID, 1000, 1000, 0) != 0 || res(SYS_GETRESUID) != [1000, 1000, 1000]) as i64;-        fails += (syscall3(SYS_SETUID, 0, 0, 0) != -EPERM) as i64; // no way back now-        fails-    });-    c.check(r == 0, "child: setresuid(1000,1000,0), back to euid 0 via the saved id, setreuid");-    c.check(res(SYS_GETRESUID) == [0, 0, 0], "the parent is still root");-    // Priority.-    c.check(syscall3(SYS_GETPRIORITY, 0, 0, 0) == 20, "getpriority: 20 (nice 0; the raw call returns 20 - nice)");-    c.check(syscall3(SYS_SETPRIORITY, 0, 0, 7) == 0 && syscall3(SYS_GETPRIORITY, 0, 0, 0) == 13, "setpriority nice 7 -> 13");-    c.check(in_child(|| syscall3(SYS_GETPRIORITY, 0, 0, 0)) == 13, "a child inherits it");-    c.check(syscall3(SYS_SETPRIORITY, 0, 0, (-25i64) as u64) == 0 && syscall3(SYS_GETPRIORITY, 0, 0, 0) == 40, "nice -25 is clamped to -20 (root may)");-    syscall3(SYS_SETPRIORITY, 0, 0, 0);-    c.check(syscall3(SYS_GETPRIORITY, 0, 99999, 0) == -ESRCH, "getpriority of no such process: ESRCH");-    c.check(syscall3(SYS_GETPRIORITY, 7, 0, 0) == -EINVAL, "getpriority with a bad `which`: EINVAL");-    // Host name.-    let name = b"rustbelt-test";-    c.check(syscall3(SYS_SETHOSTNAME, name.as_ptr() as u64, name.len() as u64, 0) == 0, "sethostname rustbelt-test");-    let mut u = [0u8; 6 * 65];-    c.check(uname(&mut u)[1] == "rustbelt-test", "uname shows it");-    c.check(syscall3(SYS_SETHOSTNAME, name.as_ptr() as u64, 65, 0) == -EINVAL, "a 65-byte name: EINVAL");-    let q = b"qemu";-    syscall3(SYS_SETHOSTNAME, q.as_ptr() as u64, 4, 0);-    c.check(uname(&mut u)[1] == "qemu", "back to qemu");-}--// --- misc: flock, preadv/pwritev, sendfile, sigmasks, getdents --------------------------------static USR1: AtomicI64 = AtomicI64::new(0);-extern "C" fn on_usr1(_sig: i32) {-    USR1.fetch_add(1, Ordering::SeqCst);-}--fn misc(c: &mut Checks) {-    // flock: locks belong to the open file (not the process, not the fd).-    write_file("/tmp/fl.txt", b"x");-    let a = open("/tmp/fl.txt", O_RDWR);-    let b = open("/tmp/fl.txt", O_RDWR);-    c.check(syscall3(SYS_FLOCK, a as u64, 2, 0) == 0, "flock LOCK_EX");-    c.check(syscall3(SYS_FLOCK, b as u64, 2 | 4, 0) == -EAGAIN, "another open file: LOCK_EX|LOCK_NB is EAGAIN");-    c.check(syscall3(SYS_FLOCK, b as u64, 1 | 4, 0) == -EAGAIN, "...and LOCK_SH|LOCK_NB too");-    let a2 = dup(a);-    c.check(syscall3(SYS_FLOCK, a2 as u64, 2 | 4, 0) == 0, "a dup of the lock holder may lock again");-    c.check(syscall3(SYS_FLOCK, a as u64, 1, 0) == 0 && syscall3(SYS_FLOCK, b as u64, 1 | 4, 0) == 0, "converted to shared: others may share it");-    c.check(syscall3(SYS_FLOCK, b as u64, 8, 0) == 0 && syscall3(SYS_FLOCK, a as u64, 2, 0) == 0, "LOCK_UN, then LOCK_EX again");-    // A child blocks until the holder lets go.-    let pid = fork();-    if pid == 0 {-        // Our copies of a, a2 and b share the parent's open files: close-        // them, or the lock could never go away (like on Linux).-        close(a);-        close(a2);-        close(b);-        let fd = open("/tmp/fl.txt", O_RDWR);-        let t0 = now_us();-        let r = syscall3(SYS_FLOCK, fd as u64, 2, 0);-        exit(if r == 0 && now_us() - t0 >= 80_000 { 0 } else { 1 });-    }-    sleep_ms(150);-    close(a); // a2 still holds it-    sleep_ms(30);-    c.check(try_wait(pid) == -EAGAIN, "closing one of two fds keeps the lock");-    close(a2); // last fd: released-    let r = wait4(pid, 0);-    c.check(matches!(r, Ok((_, s)) if wexitstatus(s) == 0), "a blocked flock waits, and gets it when the last fd closes");-    c.check(syscall3(SYS_FLOCK, b as u64, 2 | 4, 0) == 0, "the child's lock went with it");-    c.check(syscall3(SYS_FLOCK, b as u64, 3, 0) == -EINVAL, "flock with a bad op: EINVAL");-    close(b);-    unlink("/tmp/fl.txt");-    // preadv / pwritev-    write_file("/tmp/pv.txt", b"0123456789");-    let fd = open("/tmp/pv.txt", O_RDWR);-    let (mut x, mut y) = ([0u8; 3], [0u8; 4]);-    let iov = [x.as_mut_ptr() as u64, 3, y.as_mut_ptr() as u64, 4];-    c.check(syscall5(SYS_PREADV, fd as u64, iov.as_ptr() as u64, 2, 2, 0) == 7 && &x == b"234" && &y == b"5678", "preadv at offset 2 into two buffers");-    c.check(lseek(fd, 0, 1) == 0, "...without moving the file offset");-    let (p, q) = (b"ab", b"cd");-    let iov = [p.as_ptr() as u64, 2, q.as_ptr() as u64, 2];-    c.check(syscall5(SYS_PWRITEV, fd as u64, iov.as_ptr() as u64, 2, 8, 0) == 4, "pwritev at 8 (past the end)");-    close(fd);-    c.check(read_file("/tmp/pv.txt") == b"01234567abcd", "the file grew to 0123456789 -> 01234567abcd");-    // sendfile-    let src = open("/tmp/pv.txt", O_RDONLY);-    let dst = open("/tmp/sf.txt", O_WRONLY | O_CREAT | O_TRUNC);-    c.check(syscall5(SYS_SENDFILE, dst as u64, src as u64, 0, 100, 0) == 12, "sendfile file -> file: 12 bytes");-    c.check(lseek(src, 0, 1) == 12, "...moving the input offset");-    let mut off = 4u64;-    let (r, w) = pipe().unwrap();-    c.check(syscall5(SYS_SENDFILE, w as u64, src as u64, &mut off as *mut u64 as u64, 3, 0) == 3 && off == 7, "sendfile at an offset into a pipe updates *offset");-    let mut buf = [0u8; 8];-    c.check(read(r, &mut buf) == 3 && &buf[..3] == b"456", "the pipe got 456");-    c.check(syscall5(SYS_SENDFILE, dst as u64, r as u64, 0, 3, 0) == -EINVAL, "sendfile from a pipe: EINVAL");-    close(r);-    close(w);-    close(src);-    close(dst);-    c.check(read_file("/tmp/sf.txt") == b"01234567abcd", "the copy matches");-    unlink("/tmp/sf.txt");-    unlink("/tmp/pv.txt");-    // ppoll / pselect6: the mask size must be 8.-    let mask = 0u64;-    let ts = [0u64, 0];-    let mut fds = [PollFd { fd: 0, events: POLLIN, revents: 0 }];-    c.check(syscall5(SYS_PPOLL, fds.as_mut_ptr() as u64, 1, ts.as_ptr() as u64, &mask as *const u64 as u64, 4) == -EINVAL, "ppoll with sigsetsize 4: EINVAL");-    let arg = [&mask as *const u64 as u64, 16];-    c.check(syscall6(SYS_PSELECT6, 0, 0, 0, 0, ts.as_ptr() as u64, arg.as_ptr() as u64) == -EINVAL, "pselect6 with sigsetsize 16: EINVAL");-    let arg = [&mask as *const u64 as u64, 8];-    c.check(syscall6(SYS_PSELECT6, 0, 0, 0, 0, ts.as_ptr() as u64, arg.as_ptr() as u64) == 0, "pselect6 with sigsetsize 8: fine");-    // ppoll's mask: a blocked, pending signal is let in for the wait only.-    signal(SIGUSR1, on_usr1);-    USR1.store(0, Ordering::SeqCst);-    let old = sigprocmask(SIG_BLOCK, sigbit(SIGUSR1)) as u64;-    kill(getpid(), SIGUSR1);-    let (r, w) = pipe().unwrap();-    let mut fds = [PollFd { fd: r as i32, events: POLLIN, revents: 0 }];-    let ts = [1u64, 0];-    let open_mask = old & !sigbit(SIGUSR1);-    let rr = syscall5(SYS_PPOLL, fds.as_mut_ptr() as u64, 1, ts.as_ptr() as u64, &open_mask as *const u64 as u64, 8);-    c.check(rr == -EINTR && USR1.load(Ordering::SeqCst) == 1, "ppoll with SIGUSR1 unblocked: EINTR, handler ran");-    c.check(sigprocmask(SIG_BLOCK, 0) as u64 & sigbit(SIGUSR1) != 0, "the old mask (SIGUSR1 blocked) is back");-    sigprocmask(SIG_SETMASK, old);-    close(r);-    close(w);-    // getdents64 while deleting (rm -r): nothing is skipped.-    for dir in ["/tmp/gd", "/mnt/disk1/gd"] {-        mkdir(dir);-        for i in 0..40 {-            write_file(&format!("{}/f{}", dir, i), b"x");-        }-        let fd = open(dir, O_RDONLY | O_DIRECTORY);-        let mut buf = [0u8; 256]; // small: many getdents calls-        let mut seen = 0;-        let mut ino_ok = true;-        loop {-            let n = getdents64(fd, &mut buf);-            if n <= 0 {-                break;-            }-            let mut names = Vec::new();-            let mut pos = 0usize;-            while pos < n as usize {-                let ino = u64::from_le_bytes(buf[pos..pos + 8].try_into().unwrap());-                let reclen = u16::from_le_bytes([buf[pos + 16], buf[pos + 17]]) as usize;-                let raw = &buf[pos + 19..pos + reclen];-                let name = core::str::from_utf8(&raw[..raw.iter().position(|&b| b == 0).unwrap_or(raw.len())]).unwrap_or("?");-                if name != "." && name != ".." {-                    names.push((alloc::string::String::from(name), ino));-                }-                pos += reclen;-            }-            for (name, ino) in names {-                let path = format!("{}/{}", dir, name);-                ino_ok &= st(&path).ino == ino;-                unlink(&path);-                seen += 1;-            }-        }-        close(fd);-        c.check(seen == 40 && rmdir(dir) == 0, &format!("{}: deleting while reading: all 40 seen, directory empty", dir));-        c.check(ino_ok, &format!("{}: d_ino matches st_ino", dir));-    }-}
import/kernel-user/cowtest.rs
@@ -1,107 +0,0 @@-//! cowtest -- checks that fork really gives the child its *own* memory.-//!-//! fork doesn't copy anything up front: parent and child share every page,-//! read-only, and the first write to a page makes the copy (copy-on-write).-//! This checks that the copies are made at the right moments: writes in the-//! child don't show in the parent and vice versa -- for global data, the-//! heap and the stack -- while MAP_SHARED memory *is* shared.-#![no_std]-#![no_main]--extern crate alloc;--#[macro_use]-#[path = "ulib.rs"]-mod ulib;--use alloc::vec;-use ulib::*;--/// 64 KiB of global data (16 pages, in .bss).-static mut GLOBAL: [u8; 65536] = [0; 65536];--fn global() -> &'static mut [u8; 65536] {-    unsafe { &mut *core::ptr::addr_of_mut!(GLOBAL) }-}--fn all(buf: &[u8], value: u8) -> bool {-    buf.iter().all(|&b| b == value)-}--fn main() -> i64 {-    let mut c = Checks::new("cowtest");-    global().fill(1);-    let mut heap = vec![2u8; 100_000];-    let mut stack = [3u8; 2000];--    // 1. The child writes; the parent must not see it.-    let pid = fork();-    if pid == 0 {-        let before = all(global(), 1) && all(&heap, 2) && all(&stack, 3);-        global().fill(0xc1);-        heap.fill(0xc2);-        stack.fill(0xc3);-        let after = all(global(), 0xc1) && all(&heap, 0xc2) && all(core::hint::black_box(&stack), 0xc3);-        exit(if before && after { 0 } else { 1 });-    }-    c.check(wait(pid) == 0, "the child starts with the parent's data and can change its copy");-    c.check(all(global(), 1), "the child's writes to global data stay in the child");-    c.check(all(&heap, 2), "... and to the heap");-    c.check(all(core::hint::black_box(&stack), 3), "... and to the stack");--    // 2. The parent writes after fork; the child must keep the old data.-    // A pipe orders things: the child only checks after we wrote.-    let (r, w) = pipe().unwrap_or((-1, -1));-    let pid = fork();-    if pid == 0 {-        close(w);-        let mut b = [0u8];-        read(r, &mut b); // wait for the parent's go-        exit(if all(global(), 1) && all(&heap, 2) { 0 } else { 1 });-    }-    close(r);-    global().fill(0x77);-    heap.fill(0x77);-    write(w, b"!");-    close(w);-    c.check(wait(pid) == 0, "the parent's writes after fork stay in the parent");-    c.check(all(global(), 0x77), "and the parent sees its own writes");--    // 3. A grandchild: three generations share pages, each writes.-    let pid = fork();-    if pid == 0 {-        let pid2 = fork();-        if pid2 == 0 {-            global()[0] = 3;-            exit(if global()[1] == 0x77 { 0 } else { 1 });-        }-        global()[0] = 2;-        let code = wait(pid2);-        exit(if code == 0 && global()[0] == 2 { 0 } else { 1 });-    }-    c.check(wait(pid) == 0 && global()[0] == 0x77, "three generations: each keeps its own copy");--    // 4. MAP_SHARED memory is *not* copied: both see the same pages.-    let shared = mmap(0, 8192, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);-    let private = mmap(0, 8192, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);-    c.check(shared > 0 && private > 0, "mmap shared + private anonymous memory");-    let (sp, pp) = (shared as *mut u8, private as *mut u8);-    unsafe {-        sp.write_volatile(10);-        pp.write_volatile(10);-    }-    let pid = fork();-    if pid == 0 {-        unsafe {-            sp.write_volatile(42);-            sp.add(4096).write_volatile(43); // a page nobody touched before fork-            pp.write_volatile(42);-        }-        exit(0);-    }-    wait(pid);-    let (s0, s1, p0) = unsafe { (sp.read_volatile(), sp.add(4096).read_volatile(), pp.read_volatile()) };-    c.check(s0 == 42 && s1 == 43, "MAP_SHARED: the child's writes show up in the parent");-    c.check(p0 == 10, "MAP_PRIVATE: they don't");-    c.done()-}
import/kernel-user/demo.rs
@@ -1,157 +0,0 @@-//! Stage 6 demo: this program was itself loaded from the FAT32 disk.-//! It exercises the syscalls the shell (next stage) will need: cwd, reading-//! a file on the FAT disk, stat, lseek, getdents64, mkdir/unlink/rmdir,-//! pipes, and spawn + wait. Exit code = number of failed checks.-#![no_std]-#![no_main]--#[macro_use]-#[path = "ulib.rs"]-mod ulib;--use ulib::*;--static mut FAILURES: i64 = 0;--fn check(ok: bool, what: &str) {-    if !ok {-        println!("[demo] CHECK FAILED: {}", what);-        unsafe { FAILURES += 1 };-    }-}--fn cwd_is(expected: &str) -> bool {-    let mut buf = [0u8; 64];-    let n = getcwd(&mut buf);-    n >= 0 && &buf[..n as usize] == expected.as_bytes()-}--fn main() -> i64 {-    println!("[demo] pid {} started from /mnt/disk0/bin/demo", getpid());--    // --- current directory ---------------------------------------------------    check(cwd_is("/"), "cwd starts at /");-    check(chdir("/mnt/disk0") == 0, "chdir /mnt/disk0");-    check(cwd_is("/mnt/disk0"), "getcwd after chdir");--    // --- read a file from the FAT disk (relative path!) ----------------------    let fd = open("readme.txt", 0);-    check(fd >= 3, "open readme.txt");-    let mut buf = [0u8; 256];-    let n = read(fd, &mut buf).max(0) as usize;-    let text = core::str::from_utf8(&buf[..n]).unwrap_or("?");-    print!("[demo] readme.txt from FAT32: {}", text.lines().next().unwrap_or(""));-    println!();-    check(text.starts_with("Hello from the FAT32 disk!"), "readme.txt contents");--    // --- stat / fstat / lseek -------------------------------------------------    let mut st = Stat::default();-    check(stat("readme.txt", &mut st) == 0 && st.is_file() && st.size == n as i64, "stat readme.txt");-    check(fstat(1, &mut st) == 0 && st.is_char_device() && isatty(1), "fstat stdout = a terminal");-    check(stat("bin", &mut st) == 0 && st.is_dir(), "stat bin is a directory");-    check(lseek(fd, 6, SEEK_SET) == 6, "lseek SEEK_SET");-    let mut word = [0u8; 4];-    check(read(fd, &mut word) == 4 && &word == b"from", "read after lseek");-    check(lseek(fd, 0, SEEK_END) == n as i64, "lseek SEEK_END = size");-    check(lseek(1, 0, SEEK_SET) == -ESPIPE, "can't seek the console");-    close(fd);--    // --- list a directory with getdents64 ------------------------------------    let dir = open("bin", 0);-    let mut dents = [0u8; 1024];-    let mut len = getdents64(dir, &mut dents);-    check(len > 0, "getdents64 bin");-    let (mut saw_demo, mut saw_child) = (false, false);-    print!("[demo] /mnt/disk0/bin:");-    // One call returns as many whole records as fit in the buffer; keep-    // calling until it returns 0 (the end of the directory).-    while len > 0 {-        for_each_dirent(&dents, len as usize, |name, d_type| {-            print!(" {}{}", name, if d_type == 4 { "/" } else { "" });-            saw_demo |= name == "demo";-            saw_child |= name == "child";-        });-        len = getdents64(dir, &mut dents);-    }-    println!();-    check(saw_demo && saw_child, "bin contains demo and child");-    check(len == 0 && getdents64(dir, &mut dents) == 0, "getdents64 at end returns 0");-    close(dir);--    // --- relative paths, "..", errors -----------------------------------------    check(chdir("notes") == 0 && cwd_is("/mnt/disk0/notes"), "chdir notes");-    check(chdir("../..") == 0 && cwd_is("/mnt"), "chdir ../..");-    check(chdir("disk0/readme.txt") == -ENOTDIR, "chdir to a file -> ENOTDIR");-    check(chdir("/nope") == -ENOENT, "chdir to nothing -> ENOENT");--    // --- create and delete (ramfs /tmp and the FAT disk) ----------------------    for base in ["/tmp", "/mnt/disk0/notes"] {-        let mut path_buf = [0u8; 64];-        let dir = join(&mut path_buf, base, "/newdir");-        check(mkdir(dir) == 0, "mkdir");-        check(mkdir(dir) == -EEXIST, "mkdir again -> EEXIST");-        let mut file_buf = [0u8; 64];-        let file = join(&mut file_buf, dir, "/f.txt");-        let fd = open(file, O_WRONLY | O_CREAT);-        check(write(fd, b"bytes") == 5, "write new file");-        close(fd);-        check(rmdir(dir) == -ENOTEMPTY, "rmdir non-empty -> ENOTEMPTY");-        check(unlink(dir) == -EISDIR, "unlink a directory -> EISDIR");-        check(unlink(file) == 0, "unlink file");-        check(rmdir(dir) == 0, "rmdir empty dir");-        check(stat(dir, &mut st) == -ENOENT, "gone after rmdir");-    }-    check(rmdir("/tmp") == -EBUSY, "rmdir a mount point -> EBUSY");-    println!("[demo] mkdir/unlink/rmdir work on ramfs and FAT32");--    // --- spawn a child with its stdout connected to a pipe --------------------    chdir("/mnt/disk0");-    let (r, w) = pipe().unwrap_or((-1, -1));-    let opts = SpawnOptions { stdout: w, ..SpawnOptions::INHERIT };-    let pid = spawn("bin/child", "child\0pipe", &opts);-    check(pid > 0, "spawn child (pipe)");-    close(w); // only the child holds the write end now: EOF when it exits-    let mut got = [0u8; 128];-    let mut total = 0;-    loop {-        let n = read(r, &mut got[total..]); // blocks until data or EOF-        if n <= 0 {-            break;-        }-        total += n as usize;-    }-    close(r);-    let line = core::str::from_utf8(&got[..total]).unwrap_or("?").trim_end();-    println!("[demo] read from pipe: {:?}", line);-    check(line.starts_with("hello through a pipe"), "pipe contents");-    check(wait(pid) == 0, "child (pipe) exit code 0");--    // --- spawn + wait: exit codes ----------------------------------------------    let pid = spawn("bin/child", "child\0with\0three args", &SpawnOptions::INHERIT);-    check(wait(pid) == 0, "child with args exits 0");-    let pid = spawn("bin/child", "child\042", &SpawnOptions::INHERIT);-    let code = wait(pid);-    println!("[demo] spawned child 42 (pid {}), wait -> exit code {}", pid, code);-    check(code == 42, "wait returns the exit code");-    check(wait(pid) == -ECHILD, "second wait -> ECHILD");-    let pid = spawn("/mnt/disk0/bin/evil", "", &SpawnOptions::INHERIT);-    check(wait(pid) == 139, "killed child -> 139");-    check(spawn("bin/nope", "", &SpawnOptions::INHERIT) == -ENOENT, "spawn missing -> ENOENT");-    check(spawn("readme.txt", "", &SpawnOptions::INHERIT) == -8, "spawn non-ELF -> ENOEXEC");-    check(wait(1) == -ECHILD, "wait for a non-child -> ECHILD");--    sleep_ms(100); // blocks in the kernel (no busy loop)--    let failures = unsafe { FAILURES };-    if failures == 0 {-        println!("[demo] all stage 6 checks passed, exiting with 0");-    }-    failures-}--/// Concatenate two strings into `buf` (no heap in user programs).-fn join<'a>(buf: &'a mut [u8], a: &str, b: &str) -> &'a str {-    buf[..a.len()].copy_from_slice(a.as_bytes());-    buf[a.len()..a.len() + b.len()].copy_from_slice(b.as_bytes());-    core::str::from_utf8(&buf[..a.len() + b.len()]).unwrap()-}
import/kernel-user/dynhello.c
@@ -1,13 +0,0 @@-/*- * dynhello.c -- stage 19: a *dynamically linked* program (plain- * `musl-gcc`, no -static). Its ELF file has a PT_INTERP header naming- * the dynamic loader /lib/ld-musl-x86_64.so.1, which Rustbelt doesn't- * have, so execve must fail with ENOEXEC ("Exec format error").- */-#include <stdio.h>--int main(void)-{-	puts("hello from a dynamic program (you shouldn't see this on Rustbelt)");-	return 0;-}
import/kernel-user/echoserver.rs
@@ -1,206 +0,0 @@-//! echoserver -- an event-loop echo server over loopback (127.0.0.1).-//!-//! One process serves several clients at once without threads: it never-//! blocks on any single socket. Every socket is nonblocking, and the loop-//! asks the kernel "which of my sockets are ready?" (epoll_wait, or plain-//! poll), then only accepts/reads/writes those:-//!-//!     loop {-//!         ready = epoll_wait(...)        // sleep until something happens-//!         for each ready fd:-//!             listener -> accept new clients, watch them too-//!             client   -> read; echo it back; 0 bytes = client left-//!     }-//!-//! The self-test forks 3 client processes that talk to it over loopback,-//! once with epoll and once with poll, then checks UDP over loopback.-//! With an argument (`echoserver PORT`) it just serves forever.-#![no_std]-#![no_main]--#[macro_use]-#[path = "ulib.rs"]-mod ulib;--extern crate alloc;--use alloc::format;-use alloc::vec::Vec;-use ulib::*;--const LOOPBACK: Ip = Ip([127, 0, 0, 1]);-const CLIENTS: usize = 3;-const ROUNDS: usize = 3;--/// A client process: connect, send ROUNDS messages, check each echo.-fn client(port: u16, id: usize) -> i64 {-    let fd = socket(SOCK_STREAM, 0);-    if connect(fd, LOOPBACK, port) < 0 {-        return 10;-    }-    for round in 0..ROUNDS {-        let msg = format!("hello {} from client {}", round, id);-        write_all(fd, msg.as_bytes());-        let mut buf = [0u8; 64];-        let mut got = 0;-        while got < msg.len() {-            let n = read(fd, &mut buf[got..]);-            if n <= 0 {-                return 11;-            }-            got += n as usize;-        }-        if &buf[..got] != msg.as_bytes() {-            return 12;-        }-    }-    close(fd); // the server sees end-of-file-    0-}--fn listener(port: u16) -> i64 {-    let fd = socket(SOCK_STREAM | O_NONBLOCK, 0); // SOCK_NONBLOCK-    if bind(fd, port) < 0 || listen(fd, 4) < 0 {-        return -1;-    }-    fd-}--/// Accept everyone waiting (nonblocking: stop at EAGAIN).-fn accept_all(lfd: i64, mut add: impl FnMut(i64)) {-    loop {-        let (fd, _) = accept(lfd);-        if fd < 0 {-            break; // -EAGAIN: nobody else waiting-        }-        set_nonblocking(fd, true);-        add(fd);-    }-}--/// Read what's there and echo it back. Returns false when the client left.-fn echo(fd: i64, served: &mut usize) -> bool {-    let mut buf = [0u8; 512];-    loop {-        let n = read(fd, &mut buf);-        if n == -EAGAIN {-            return true; // drained for now-        }-        if n <= 0 {-            return false; // 0 = end of file (or an error)-        }-        write_all(fd, &buf[..n as usize]);-        *served += 1;-    }-}--/// The epoll flavour: register fds once, then wait for just the ready ones.-/// Returns the number of clients served to the end (or -1 on a stall).-fn serve_epoll(lfd: i64, want: usize) -> i64 {-    let ep = epoll_create1(0);-    epoll_ctl(ep, EPOLL_CTL_ADD, lfd, EPOLLIN, lfd as u64);-    let (mut finished, mut served) = (0, 0);-    let mut events = [EpollEvent::default(); 8];-    while finished < want {-        let n = epoll_wait(ep, &mut events, 5000);-        if n <= 0 {-            close(ep);-            return -1; // 5 s with nothing happening: something's wrong-        }-        for ev in &events[..n as usize] {-            let fd = ev.data as i64;-            if fd == lfd {-                accept_all(lfd, |c| {-                    epoll_ctl(ep, EPOLL_CTL_ADD, c, EPOLLIN, c as u64);-                });-            } else if !echo(fd, &mut served) {-                epoll_ctl(ep, EPOLL_CTL_DEL, fd, 0, 0);-                close(fd);-                finished += 1;-            }-        }-    }-    close(ep);-    println!("echoserver: epoll: {} clients, {} echoes", finished, served);-    finished as i64-}--/// The poll flavour: pass the whole fd list on every call.-fn serve_poll(lfd: i64, want: usize) -> i64 {-    let mut fds = alloc::vec![PollFd { fd: lfd as i32, events: POLLIN, revents: 0 }];-    let (mut finished, mut served) = (0, 0);-    while finished < want {-        if poll(&mut fds, 5000) <= 0 {-            return -1;-        }-        let mut new = Vec::new();-        let mut gone = Vec::new();-        for p in fds.iter() {-            if p.revents == 0 {-                continue;-            }-            if p.fd as i64 == lfd {-                accept_all(lfd, |c| new.push(c));-            } else if !echo(p.fd as i64, &mut served) {-                close(p.fd as i64);-                gone.push(p.fd);-                finished += 1;-            }-        }-        fds.retain(|p| !gone.contains(&p.fd));-        fds.extend(new.into_iter().map(|c| PollFd { fd: c as i32, events: POLLIN, revents: 0 }));-    }-    println!("echoserver: poll: {} clients, {} echoes", finished, served);-    finished as i64-}--/// Start the clients, run the server, collect the clients' results.-fn round(c: &mut Checks, port: u16, use_epoll: bool) {-    let lfd = listener(port);-    c.check(lfd >= 0, "listen on a loopback port");-    let mut pids = Vec::new();-    for id in 0..CLIENTS {-        let pid = fork();-        if pid == 0 {-            close(lfd);-            exit(client(port, id));-        }-        pids.push(pid);-    }-    let finished = if use_epoll { serve_epoll(lfd, CLIENTS) } else { serve_poll(lfd, CLIENTS) };-    close(lfd);-    let ok = pids.iter().all(|&pid| wait(pid) == 0);-    let what = if use_epoll { "epoll" } else { "poll" };-    c.check(finished == CLIENTS as i64, &format!("{}: served {} clients to the end", what, CLIENTS));-    c.check(ok, &format!("{}: every client got its echoes back", what));-}--fn main() -> i64 {-    if let Some(port) = arg(1).and_then(|a| a.parse().ok()) {-        let lfd = listener(port);-        if lfd < 0 {-            eprintln!("echoserver: can't listen on port {}", port);-            return 1;-        }-        println!("echoserver: listening on port {} (epoll)", port);-        loop {-            serve_epoll(lfd, usize::MAX);-        }-    }-    let mut c = Checks::new("echoserver");-    round(&mut c, 7070, true);-    round(&mut c, 7071, false);--    // UDP over loopback: a datagram to ourselves.-    let a = socket(SOCK_DGRAM, 0);-    let b = socket(SOCK_DGRAM, 0);-    bind(b, 7072);-    c.check(sendto(a, b"ping", LOOPBACK, 7072) == 4, "UDP sendto 127.0.0.1");-    set_recv_timeout(b, 2000);-    let mut buf = [0u8; 16];-    let (n, from) = recvfrom(b, &mut buf);-    c.check(n == 4 && &buf[..4] == b"ping" && from.ip() == LOOPBACK, "UDP datagram arrives, from 127.0.0.1");-    close(a);-    close(b);-    c.done()-}
import/kernel-user/evil.rs
@@ -1,17 +0,0 @@-//! A misbehaving program: tries to read kernel memory directly. The kernel's-//! pages aren't USER_ACCESSIBLE, so the CPU raises a page fault, and the-//! kernel kills this process (and carries on).-#![no_std]-#![no_main]--#[macro_use]-#[path = "ulib.rs"]-mod ulib;--fn main() -> i64 {-    let kernel_code = 0x20_0000 as *const u64; // where the kernel is loaded-    println!("[evil] pid {}: reading kernel memory at {:p}...", ulib::getpid(), kernel_code);-    let value = unsafe { core::ptr::read_volatile(kernel_code) };-    println!("[evil] got {:#x} -- this must never be printed!", value);-    1-}
import/kernel-user/forktest.rs
@@ -1,186 +0,0 @@-//! forktest -- checks fork, execve, wait4, vfork and fd inheritance.-//!-//!   forktest          run all the checks (exit code = failed checks)-//!   forktest bomb     fork until the kernel says no (EAGAIN), then clean-//!                     up: the process table is bounded, nothing leaks-//!   forktest args ... (used by the exec check) verify argv, envp and the-//!                     auxiliary vector the kernel built for us-#![no_std]-#![no_main]--extern crate alloc;--#[macro_use]-#[path = "ulib.rs"]-mod ulib;--use alloc::vec::Vec;-use ulib::*;--const SELF: &str = "/mnt/disk0/bin/forktest";--fn main() -> i64 {-    match arg(1) {-        Some("bomb") => bomb(),-        Some("args") => check_args(),-        _ => all(),-    }-}--fn all() -> i64 {-    let mut c = Checks::new("forktest");-    let me = getpid();--    // 1. The basic dance: fork returns twice.-    let pid = fork();-    if pid == 0 {-        // The child: a new pid, and its parent is the process that forked.-        exit(if getppid() == me && getpid() != me { 7 } else { 1 });-    }-    c.check(pid > 0, "fork returns the child's pid in the parent");-    let r = wait4(pid, 0);-    c.check(matches!(r, Ok((p, s)) if p == pid && wifexited(s) && wexitstatus(s) == 7), "child saw getppid() == parent; wait4 gives its exit code 7");--    // 2. Many children, reaped in any order with wait4(-1).-    let mut sum = 0;-    for i in 1..=10 {-        if fork() == 0 {-            exit(i);-        }-    }-    let mut reaped = 0;-    while let Ok((_, status)) = wait4(-1, 0) {-        sum += wexitstatus(status);-        reaped += 1;-    }-    c.check(reaped == 10 && sum == 55, "10 children, wait4(-1) reaps them all (exit codes add up to 55)");-    c.check(wait4(-1, 0) == Err(-ECHILD), "no children left: ECHILD");--    // 3. execve: the child becomes another program (here: forktest itself-    // in "args" mode, which checks what it was given).-    let pid = fork();-    if pid == 0 {-        let err = execve(SELF, &["forktest", "args", "two words", ""], &["FOO=bar", "EMPTY="]);-        exit(100 + (-err).min(50)); // execve only returns on failure-    }-    let code = wait(pid);-    c.check(code == 0, "execve: argv, envp and auxv arrive the Linux way (see the [forktest args] lines)");-    let pid = fork();-    if pid == 0 {-        let err = execve("/mnt/disk0/bin/no-such-program", &["x"], &[]);-        exit(if err == -ENOENT { 0 } else { 1 });-    }-    c.check(wait(pid) == 0, "execve of a missing file fails with ENOENT (and the caller keeps running)");--    // 4. vfork: the parent waits until the child has exec'd.-    let pid = vfork();-    if pid == 0 {-        execve("/mnt/disk0/bin/child", &["child", "5"], &[]);-        exit(99);-    }-    c.check(wait(pid) == 5, "vfork + execve child 5 -> exit code 5");--    // 5. fds are inherited: the child writes into our pipe via dup2.-    let (r, w) = pipe2(O_CLOEXEC).unwrap_or((-1, -1));-    c.check(fcntl(r, F_GETFD, 0) == FD_CLOEXEC as i64, "pipe2(O_CLOEXEC) sets FD_CLOEXEC");-    let pid = fork();-    if pid == 0 {-        dup2(w, 1); // stdout = the pipe (dup2 clears close-on-exec on fd 1)-        execve("/mnt/disk0/bin/child", &["child", "pipe"], &[]);-        exit(99);-    }-    close(w); // else the read below would never see end-of-file-    let mut out = Vec::new();-    let mut buf = [0u8; 128];-    loop {-        let n = read(r, &mut buf);-        if n <= 0 {-            break;-        }-        out.extend_from_slice(&buf[..n as usize]);-    }-    close(r);-    c.check(wait(pid) == 0 && out.starts_with(b"hello through a pipe"), "fork + dup2 + execve: the child's stdout comes through our pipe");--    // 6. A killed child: the status says which signal.-    let pid = fork();-    if pid == 0 {-        unsafe { core::ptr::read_volatile(0x10 as *const u8) }; // NULL-ish pointer-        exit(0);-    }-    let r = wait4(pid, 0);-    c.check(matches!(r, Ok((_, s)) if wifsignaled(s) && wtermsig(s) == SIGSEGV), "a child that touches address 0x10 dies of SIGSEGV");-    c.done()-}--/// The exec'd side of check 3.-fn check_args() -> i64 {-    let mut c = Checks::new("forktest args");-    c.check(argc() == 4, "argc == 4");-    c.check(arg(0) == Some("forktest") && arg(2) == Some("two words") && arg(3) == Some(""), "argv[0..4] as passed (incl. an empty one)");-    c.check(env(0) == Some("FOO=bar") && env(1) == Some("EMPTY=") && env(2).is_none(), "envp exactly as passed");-    c.check(getenv("FOO") == Some("bar"), "getenv(\"FOO\") == \"bar\"");-    c.check(getauxval(AT_PAGESZ) == Some(4096), "AT_PAGESZ = 4096");-    let entry = getauxval(AT_ENTRY).unwrap_or(0);-    c.check(entry != 0 && entry == ulib_start(), "AT_ENTRY = the address of _start");-    // AT_PHDR points at our own ELF program headers: the first loadable-    // one (type 1 = PT_LOAD) should contain the entry point.-    let (phdr, phent, phnum) = (getauxval(AT_PHDR).unwrap_or(0), getauxval(AT_PHENT).unwrap_or(0), getauxval(AT_PHNUM).unwrap_or(0));-    let mut found = false;-    if phdr != 0 && phent == 56 {-        for i in 0..phnum {-            let h = (phdr + i * phent) as *const u64;-            let (ty, vaddr, memsz) = unsafe { (*(h as *const u32), *h.add(2), *h.add(5)) };-            found |= ty == 1 && (vaddr..vaddr + memsz).contains(&entry);-        }-    }-    c.check(found, "AT_PHDR/AT_PHENT/AT_PHNUM: our program headers, a PT_LOAD contains the entry");-    let random = getauxval(AT_RANDOM).map(|p| unsafe { *(p as *const [u8; 16]) });-    c.check(random.is_some_and(|r| r != [0; 16]), "AT_RANDOM: 16 random bytes");-    let execfn = getauxval(AT_EXECFN).map(|p| unsafe { cstr(p as *const u8) });-    c.check(execfn == Some(SELF), "AT_EXECFN = the path we were run as");-    let sp = &c as *const _ as u64;-    c.check(sp > 0xff_0000_0000 && sp < 0x100_0000_0000, "the stack is at the top of user space");-    c.done()-}--fn ulib_start() -> u64 {-    unsafe extern "C" {-        fn _start();-    }-    _start as usize as u64-}--/// Fork until fork fails. Every child just waits (pause) for a signal.-/// Then kill them all and reap them; the kernel tests check that no memory-/// leaked and that the system still works afterwards.-fn bomb() -> i64 {-    let mut pids = Vec::new();-    let error = loop {-        let pid = fork();-        if pid == 0 {-            loop {-                pause(); // until SIGKILL-            }-        }-        if pid < 0 {-            break pid;-        }-        pids.push(pid);-        if pids.len() > 1000 {-            break 0; // unbounded? stop anyway-        }-    };-    println!("[forktest bomb] {} children, then fork failed with {} ({})", pids.len(), error, strerror(error));-    for &pid in &pids {-        kill(pid, SIGKILL);-    }-    let mut reaped = 0;-    while let Ok((_, status)) = wait4(-1, 0) {-        if wifsignaled(status) && wtermsig(status) == SIGKILL {-            reaped += 1;-        }-    }-    println!("[forktest bomb] killed and reaped {}", reaped);-    if error == -EAGAIN && reaped == pids.len() && !pids.is_empty() { 0 } else { 1 }-}
import/kernel-user/fputest.rs
@@ -1,457 +0,0 @@-//! fputest -- every thread keeps its own FPU/SSE/AVX registers (stage 15.1).-//!-//! The kernel never uses the FPU registers itself, but user code does (any-//! C program's `double` maths, and libc's memcpy/strlen use SSE). So on a-//! context switch the kernel saves one thread's registers and loads the-//! next one's (XSAVE, or FXSAVE on older CPUs), a signal handler gets-//! clean registers and sigreturn puts the old ones back, fork copies them-//! and exec clears them. This program checks all of that:-//!-//! - threads + processes (spread over all CPUs) each load *distinct*-//!   values into xmm0-15 (all 256 bits of ymm0-15 if the CPU has AVX) and-//!   MXCSR, spin and yield for a while, then check nothing changed;-//! - a signal handler that scribbles over every register, synchronously-//!   (kill to ourselves) and asynchronously (another thread's tgkill while-//!   we spin): the interrupted code must see its own values afterwards;-//! - the handler starts with clean registers, finds the saved ones via-//!   uc_mcontext.fpstate (Linux's layout), and a value it changes there is-//!   what sigreturn loads;-//! - an unmasked SSE divide-by-zero raises SIGFPE (FPE_FLTDIV);-//! - fork copies the registers; exec starts with zeros and MXCSR 0x1f80.-//!-//! Our programs are built soft-float, so Rust code here never touches the-//! vector registers: only the `asm!` blocks below do. (That's also why the-//! asm can't name xmm registers as operands: it uses memory instead.)-#![no_std]-#![no_main]--#[macro_use]-#[path = "ulib.rs"]-mod ulib;--extern crate alloc;--use alloc::sync::Arc;-use alloc::vec::Vec;-use core::arch::asm;-use core::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering};-use ulib::*;--/// The 16 vector registers, 32 bytes each (only the low 16 used without AVX).-#[repr(C, align(32))]-#[derive(Clone, Copy, PartialEq)]-struct Regs([u8; 512]);--static AVX: AtomicBool = AtomicBool::new(false);--/// Can we use AVX? The CPU must have it (CPUID.1:ECX bit 28) *and* the-/// kernel must have turned on XSAVE (OSXSAVE, bit 27) with the AVX state-/// in XCR0 (bits 1 and 2): otherwise AVX instructions fault (#UD).-fn detect_avx() -> bool {-    let ecx = core::arch::x86_64::__cpuid(1).ecx;-    if ecx & (1 << 27) == 0 || ecx & (1 << 28) == 0 {-        return false;-    }-    let (lo, _hi): (u32, u32);-    unsafe { asm!("xgetbv", in("ecx") 0, out("eax") lo, out("edx") _hi, options(nomem, nostack)) };-    lo & 6 == 6-}-fn avx() -> bool {-    AVX.load(Ordering::Relaxed)-}--/// Load all vector registers from `r`.-fn load(r: &Regs) {-    unsafe {-        if avx() {-            asm!(-            "vmovdqu ymm0, [{p} + 0]",-            "vmovdqu ymm1, [{p} + 32]",-            "vmovdqu ymm2, [{p} + 64]",-            "vmovdqu ymm3, [{p} + 96]",-            "vmovdqu ymm4, [{p} + 128]",-            "vmovdqu ymm5, [{p} + 160]",-            "vmovdqu ymm6, [{p} + 192]",-            "vmovdqu ymm7, [{p} + 224]",-            "vmovdqu ymm8, [{p} + 256]",-            "vmovdqu ymm9, [{p} + 288]",-            "vmovdqu ymm10, [{p} + 320]",-            "vmovdqu ymm11, [{p} + 352]",-            "vmovdqu ymm12, [{p} + 384]",-            "vmovdqu ymm13, [{p} + 416]",-            "vmovdqu ymm14, [{p} + 448]",-            "vmovdqu ymm15, [{p} + 480]",-                p = in(reg) r.0.as_ptr(), options(nostack, readonly, preserves_flags));-        } else {-            asm!(-            "movdqu xmm0, [{p} + 0]",-            "movdqu xmm1, [{p} + 32]",-            "movdqu xmm2, [{p} + 64]",-            "movdqu xmm3, [{p} + 96]",-            "movdqu xmm4, [{p} + 128]",-            "movdqu xmm5, [{p} + 160]",-            "movdqu xmm6, [{p} + 192]",-            "movdqu xmm7, [{p} + 224]",-            "movdqu xmm8, [{p} + 256]",-            "movdqu xmm9, [{p} + 288]",-            "movdqu xmm10, [{p} + 320]",-            "movdqu xmm11, [{p} + 352]",-            "movdqu xmm12, [{p} + 384]",-            "movdqu xmm13, [{p} + 416]",-            "movdqu xmm14, [{p} + 448]",-            "movdqu xmm15, [{p} + 480]",-                p = in(reg) r.0.as_ptr(), options(nostack, readonly, preserves_flags));-        }-    }-}--/// Store all vector registers (the bytes AVX doesn't have stay 0).-fn store() -> Regs {-    let mut r = Regs([0; 512]);-    unsafe {-        if avx() {-            asm!(-            "vmovdqu [{p} + 0], ymm0",-            "vmovdqu [{p} + 32], ymm1",-            "vmovdqu [{p} + 64], ymm2",-            "vmovdqu [{p} + 96], ymm3",-            "vmovdqu [{p} + 128], ymm4",-            "vmovdqu [{p} + 160], ymm5",-            "vmovdqu [{p} + 192], ymm6",-            "vmovdqu [{p} + 224], ymm7",-            "vmovdqu [{p} + 256], ymm8",-            "vmovdqu [{p} + 288], ymm9",-            "vmovdqu [{p} + 320], ymm10",-            "vmovdqu [{p} + 352], ymm11",-            "vmovdqu [{p} + 384], ymm12",-            "vmovdqu [{p} + 416], ymm13",-            "vmovdqu [{p} + 448], ymm14",-            "vmovdqu [{p} + 480], ymm15",-                p = in(reg) r.0.as_mut_ptr(), options(nostack, preserves_flags));-        } else {-            asm!(-            "movdqu [{p} + 0], xmm0",-            "movdqu [{p} + 32], xmm1",-            "movdqu [{p} + 64], xmm2",-            "movdqu [{p} + 96], xmm3",-            "movdqu [{p} + 128], xmm4",-            "movdqu [{p} + 160], xmm5",-            "movdqu [{p} + 192], xmm6",-            "movdqu [{p} + 224], xmm7",-            "movdqu [{p} + 256], xmm8",-            "movdqu [{p} + 288], xmm9",-            "movdqu [{p} + 320], xmm10",-            "movdqu [{p} + 352], xmm11",-            "movdqu [{p} + 384], xmm12",-            "movdqu [{p} + 416], xmm13",-            "movdqu [{p} + 448], xmm14",-            "movdqu [{p} + 480], xmm15",-                p = in(reg) r.0.as_mut_ptr(), options(nostack, preserves_flags));-        }-    }-    r-}--fn get_mxcsr() -> u32 {-    let mut m = 0u32;-    unsafe { asm!("stmxcsr [{}]", in(reg) &mut m, options(nostack, preserves_flags)) };-    m-}-fn set_mxcsr(m: u32) {-    unsafe { asm!("ldmxcsr [{}]", in(reg) &m, options(nostack, readonly, preserves_flags)) };-}--/// A register image no other `seed` produces (only the parts this CPU has).-fn pattern(seed: u64) -> Regs {-    let mut r = Regs([0; 512]);-    let mut x = seed.wrapping_mul(0x9e37_79b9_7f4a_7c15) | 1;-    for (i, chunk) in r.0.chunks_mut(8).enumerate() {-        x ^= x << 13;-        x ^= x >> 7;-        x ^= x << 17;-        if avx() || i % 4 < 2 {-            chunk.copy_from_slice(&x.to_le_bytes());-        }-    }-    r-}-/// A distinct MXCSR per seed: all exceptions masked, rounding mode (bits-/// 13-14) and flush-to-zero / denormals-are-zero (15, 6) from the seed.-fn mxcsr_for(seed: u64) -> u32 {-    0x1f80 | ((seed as u32 & 3) << 13) | ((seed as u32 >> 2 & 1) << 15) | ((seed as u32 >> 3 & 1) << 6)-}--/// Load a pattern, spin and yield across many timer ticks (so the thread-/// is switched out and probably migrated), checking it now and then.-/// Returns how many checks found a wrong register.-fn hammer(seed: u64, rounds: u32) -> u32 {-    let want = pattern(seed);-    let mx = mxcsr_for(seed);-    load(&want);-    set_mxcsr(mx);-    let mut bad = 0;-    for round in 0..rounds {-        let mut x = round as u64;-        for _ in 0..20_000 {-            x = core::hint::black_box(x.wrapping_mul(6364136223846793005).wrapping_add(1));-        }-        if round % 3 == 0 {-            sched_yield();-        }-        if store() != want || get_mxcsr() != mx {-            bad += 1;-            load(&want); // keep going with good values-            set_mxcsr(mx);-        }-    }-    set_mxcsr(0x1f80);-    bad-}--// --- signal handlers -------------------------------------------------------------/// Offsets in the ucontext the kernel passes (same as Linux's): the-/// `fpstate` pointer in uc_mcontext, and inside the FXSAVE area: MXCSR,-/// xmm0, the software-reserved bytes (sw_bytes) and, in the XSAVE area,-/// the upper halves of ymm0-15.-const UC_FLAGS: usize = 0;-const UC_FPSTATE: usize = 40 + 23 * 8;-const FX_MXCSR: usize = 24;-const FX_XMM: usize = 160;-const FX_SW: usize = 464;-const XS_YMM_HI: usize = 576;-const MAGIC1: u32 = 0x4650_5853;-const MAGIC2: u32 = 0x4650_5845;--static SYNC_SEED: AtomicU64 = AtomicU64::new(0);-/// Bit flags the handler sets for main to check.-static H_CLEAN: AtomicU32 = AtomicU32::new(0); // registers zero + MXCSR default on entry-static H_SAVED: AtomicU32 = AtomicU32::new(0); // the frame held the interrupted values-static H_LAYOUT: AtomicU32 = AtomicU32::new(0); // magic numbers / flags right-static ASYNC_HITS: AtomicU32 = AtomicU32::new(0);-const NEW_XMM15: u64 = 0x1515_1515_cafe_f00d;--fn rd32(p: *const u8) -> u32 {-    unsafe { core::ptr::read_unaligned(p as *const u32) }-}-fn rd64(p: *const u8) -> u64 {-    unsafe { core::ptr::read_unaligned(p as *const u64) }-}--/// Scribble over every vector register and MXCSR (a handler that does-/// float maths, or calls memcpy, would).-fn clobber() {-    load(&pattern(0xdead));-    set_mxcsr(0x1f80 | (3 << 13));-}--extern "C" fn on_usr1(_sig: i32, _info: *const SigInfo, uc: *mut u8) {-    let clean = store() == Regs([0; 512]) && get_mxcsr() == 0x1f80;-    H_CLEAN.store(clean as u32, Ordering::SeqCst);-    let want = pattern(SYNC_SEED.load(Ordering::SeqCst));-    let fp = rd64(unsafe { uc.add(UC_FPSTATE) }) as *mut u8;-    if fp.is_null() || fp as u64 % 64 != 0 {-        H_SAVED.store(0, Ordering::SeqCst);-    } else {-        // xmm i is at FX_XMM + 16*i; our image has register i at 32*i.-        let mut ok = rd32(unsafe { fp.add(FX_MXCSR) }) == mxcsr_for(SYNC_SEED.load(Ordering::SeqCst));-        for i in 0..16 {-            for b in 0..16 {-                ok &= unsafe { *fp.add(FX_XMM + 16 * i + b) } == want.0[32 * i + b];-            }-        }-        let flags = rd64(unsafe { uc.add(UC_FLAGS) });-        let magic1 = rd32(unsafe { fp.add(FX_SW) });-        let layout = if magic1 == MAGIC1 {-            // XSAVE frame: sw_bytes say how big it is; magic2 at the end.-            let size = rd32(unsafe { fp.add(FX_SW + 16) }) as usize;-            let xfeatures = rd64(unsafe { fp.add(FX_SW + 8) });-            if avx() {-                for i in 0..16 {-                    for b in 0..16 {-                        ok &= unsafe { *fp.add(XS_YMM_HI + 16 * i + b) } == want.0[32 * i + 16 + b];-                    }-                }-            }-            flags & 1 != 0 && rd32(unsafe { fp.add(size) }) == MAGIC2 && xfeatures & 3 == 3 && (!avx() || xfeatures & 4 != 0)-        } else {-            // FXSAVE-only frame (no XSAVE): fine as long as we're not using AVX.-            magic1 == 0 && !avx()-        };-        H_SAVED.store(ok as u32, Ordering::SeqCst);-        H_LAYOUT.store(layout as u32, Ordering::SeqCst);-        // Change the saved xmm15: sigreturn must load what's in the frame.-        unsafe { core::ptr::write_unaligned(fp.add(FX_XMM + 16 * 15) as *mut u64, NEW_XMM15) };-    }-    clobber();-}--extern "C" fn on_usr2(_sig: i32) {-    clobber();-    ASYNC_HITS.fetch_add(1, Ordering::SeqCst);-}--static FPE_CODE: AtomicU32 = AtomicU32::new(0);-/// SIGFPE from an unmasked SSE exception. Returning would just run the-/// faulting instruction again, so mask all exceptions (and clear the-/// flags) in the *saved* MXCSR first: the retry then gives inf quietly.-extern "C" fn on_fpe(_sig: i32, info: *const SigInfo, uc: *mut u8) {-    FPE_CODE.store(unsafe { (*info).code } as u32, Ordering::SeqCst);-    let fp = rd64(unsafe { uc.add(UC_FPSTATE) }) as *mut u8;-    if !fp.is_null() {-        unsafe { core::ptr::write_unaligned(fp.add(FX_MXCSR) as *mut u32, 0x1f80) };-    }-}--// --- the tests -----------------------------------------------------------------------/// N threads in this process, each with its own seed; returns bad checks.-fn thread_round(base: u64, n: u64, rounds: u32) -> u32 {-    let bad = Arc::new(AtomicU32::new(0));-    let mut ts = Vec::new();-    for i in 0..n {-        let bad = bad.clone();-        ts.push(spawn_thread(0, move || {-            bad.fetch_add(hammer(base + i, rounds), Ordering::SeqCst);-        }));-    }-    let main_bad = hammer(base + 99, rounds);-    for t in ts {-        match t {-            Ok(t) => t.join(),-            Err(_) => return 1000,-        }-    }-    bad.load(Ordering::SeqCst) + main_bad-}--fn exec_check() -> i64 {-    // Run straight after exec: nothing has touched the vector registers.-    let clean = store() == Regs([0; 512]) && get_mxcsr() == 0x1f80;-    if clean { 0 } else { 1 }-}--fn main() -> i64 {-    AVX.store(detect_avx(), Ordering::Relaxed);-    if arg(1) == Some("exec-check") {-        return exec_check();-    }-    let mut c = Checks::new("fputest");-    println!(-        "fputest: {} registers, {} CPU(s)",-        if avx() { "ymm0-15 (AVX)" } else { "xmm0-15 (SSE)" },-        sysinfo().cpus-    );--    // 1. The registers start clean (main ran just after exec).-    c.check(exec_check() == 0, "a new program starts with zeroed registers and MXCSR 0x1f80");--    // 2. Threads and processes, all with distinct values, on all CPUs.-    let mut children = Vec::new();-    for p in 1..=2u64 {-        let pid = fork();-        if pid == 0 {-            exit(thread_round(p * 1000, 3, 60).min(100) as i64);-        }-        children.push(pid);-    }-    let bad = thread_round(0, 3, 60);-    let mut child_bad = 0;-    for pid in children {-        child_bad += wait(pid);-    }-    c.check(bad == 0, "4 threads kept their own vector registers + MXCSR");-    c.check(child_bad == 0, "so did 4 threads in each of 2 forked processes (12 at once)");--    // 3. fork copies the registers; exec clears them.-    let want = pattern(7);-    load(&want);-    set_mxcsr(mxcsr_for(7));-    let pid = fork();-    if pid == 0 {-        let same = store() == want && get_mxcsr() == mxcsr_for(7);-        if !same {-            exit(2);-        }-        execve("/mnt/disk0/bin/fputest", &["fputest", "exec-check"], &[]);-        exit(3);-    }-    let parent_same = store() == want;-    set_mxcsr(0x1f80);-    let code = wait(pid);-    c.check(parent_same && code != 2, "fork: the child starts with the parent's registers");-    c.check(code == 0, "exec: the new program starts with clean registers");--    // 4. A synchronous signal whose handler clobbers everything.-    sigaction_info(SIGUSR1, on_usr1, 0);-    SYNC_SEED.store(42, Ordering::SeqCst);-    let mut want = pattern(42);-    load(&want);-    set_mxcsr(mxcsr_for(42));-    kill(getpid(), SIGUSR1); // the handler runs on the way back from this-    let got = store();-    let mx = get_mxcsr();-    set_mxcsr(0x1f80);-    c.check(H_CLEAN.load(Ordering::SeqCst) == 1, "the handler starts with clean registers");-    c.check(H_SAVED.load(Ordering::SeqCst) == 1, "uc_mcontext.fpstate holds the interrupted registers");-    c.check(H_LAYOUT.load(Ordering::SeqCst) == 1, "the fpstate has Linux's layout (sw_bytes, magics, uc_flags)");-    want.0[32 * 15..32 * 15 + 8].copy_from_slice(&NEW_XMM15.to_le_bytes());-    c.check(got == want && mx == mxcsr_for(42), "after sigreturn: our registers back, with the handler's xmm15 edit");--    // 5. Asynchronous signals: another thread interrupts us while we spin.-    signal(SIGUSR2, on_usr2);-    let me = gettid();-    let sender = spawn_thread(0, move || {-        for _ in 0..40 {-            tgkill(getpid(), me, SIGUSR2);-            sleep_ms(2);-        }-    });-    let want = pattern(4242);-    load(&want);-    set_mxcsr(mxcsr_for(4242));-    let t0 = now_us();-    let mut bad = 0;-    while ASYNC_HITS.load(Ordering::SeqCst) < 40 && now_us() - t0 < 5_000_000 {-        if store() != want || get_mxcsr() != mxcsr_for(4242) {-            bad += 1;-            load(&want);-            set_mxcsr(mxcsr_for(4242));-        }-    }-    set_mxcsr(0x1f80);-    if let Ok(t) = sender {-        t.join();-    }-    let hits = ASYNC_HITS.load(Ordering::SeqCst);-    println!("fputest: {} async signals, each handler clobbered every register", hits);-    c.check(hits == 40 && bad == 0, "40 async signals with clobbering handlers changed nothing we saw");--    // 6. An unmasked SSE exception (divide by zero) -> SIGFPE, FPE_FLTDIV.-    sigaction_info(SIGFPE, on_fpe, 0);-    let one = 1.0f64.to_bits();-    let zero = 0u64;-    let mut out = 0u64;-    set_mxcsr(0x1f80 & !(1 << 9)); // unmask ZE (bit 9)-    unsafe {-        asm!(-            "movq xmm0, [{a}]",-            "divsd xmm0, [{b}]", // 1.0 / 0.0-            "movq [{o}], xmm0",-            a = in(reg) &one, b = in(reg) &zero, o = in(reg) &mut out,-            options(nostack)-        );-    }-    let mx = get_mxcsr();-    set_mxcsr(0x1f80);-    let code = FPE_CODE.load(Ordering::SeqCst);-    if code == 0 && out == f64::INFINITY.to_bits() && mx & 4 != 0 {-        // QEMU's software CPU (TCG) sets the flag but never raises #XM.-        println!("fputest: skipped SIGFPE check: this CPU doesn't raise SIMD exceptions (QEMU TCG)");-    } else {-        c.check(code == 3, "unmasked 1.0/0.0 -> SIGFPE with si_code FPE_FLTDIV");-        c.check(out == f64::INFINITY.to_bits() && mx & (1 << 9) != 0, "the handler's MXCSR edit took effect (retry gave inf)");-    }--    c.done()-}
import/kernel-user/hello.rs
@@ -1,57 +0,0 @@-//! First user program: says hello, uses the VFS through syscalls, and checks-//! that bad syscall arguments get the right errors. Exit code = failed checks.-#![no_std]-#![no_main]--#[macro_use]-#[path = "ulib.rs"]-mod ulib;--use ulib::*;--fn check(failures: &mut i64, ok: bool, what: &str) {-    if !ok {-        println!("[hello] CHECK FAILED: {}", what);-        *failures += 1;-    }-}--fn main() -> i64 {-    let mut failures = 0;-    println!("Hello from user mode! I'm pid {}.", getpid());--    // Read a file from the ramfs.-    let mut buf = [0u8; 128];-    let fd = open("/hello.txt", 0);-    check(&mut failures, fd >= 3, "open /hello.txt");-    let n = read(fd, &mut buf);-    let text = core::str::from_utf8(&buf[..n.max(0) as usize]).unwrap_or("<not utf-8>");-    print!("[hello] /hello.txt says: {}", text);-    check(&mut failures, text.starts_with("Hello from the ramfs!"), "contents of /hello.txt");-    check(&mut failures, read(fd, &mut buf) == 0, "read at EOF returns 0");-    check(&mut failures, close(fd) == 0, "close");--    // Create a file on the second mount (/tmp), then read it back.-    let fd = open("/tmp/note.txt", O_WRONLY | O_CREAT);-    check(&mut failures, write(fd, b"written from ring 3") == 19, "write /tmp/note.txt");-    close(fd);-    let fd = open("/tmp/note.txt", 0);-    let n = read(fd, &mut buf);-    check(&mut failures, &buf[..n.max(0) as usize] == b"written from ring 3", "read back /tmp/note.txt");-    close(fd);-    println!("[hello] wrote and read back /tmp/note.txt");--    // Error cases.-    check(&mut failures, open("/does/not/exist", 0) == -ENOENT, "ENOENT");-    check(&mut failures, read(42, &mut buf) == -EBADF, "EBADF");-    check(&mut failures, close(fd) == -EBADF, "double close -> EBADF");-    // Ask the kernel to print *kernel* memory for us (0x200000 = kernel code).-    let r = syscall3(SYS_WRITE, 1, 0x20_0000, 16);-    check(&mut failures, r == -EFAULT, "kernel pointer -> EFAULT");-    check(&mut failures, syscall3(999, 0, 0, 0) == -38, "unknown syscall -> ENOSYS");--    if failures == 0 {-        println!("[hello] all syscall checks passed, exiting with 0");-    }-    failures-}
import/kernel-user/libctest.rs
@@ -1,655 +0,0 @@-//! libctest -- stage 16, "libc batch 1": the system calls a C library-//! (musl) and its shell (dash) need beyond open/read/write. Run with one-//! group name; exit code = number of failed checks:-//!   wait     dash's `wait`: block SIGCHLD, fork, rt_sigsuspend until the-//!            handler ran (before 16 rt_sigsuspend was ENOSYS: dash spun-//!            forever)-//!   signals  rt_sigpending, rt_sigtimedwait, rt_sigqueueinfo (real-time-//!            signals queue up), sigaltstack + SA_ONSTACK, unknown sa_flags-//!   locks    fcntl: F_GETFL/F_SETFL, F_DUPFD limits, record locks between-//!            two processes (F_GETLK/F_SETLK/F_SETLKW, dropped on close/exit)-//!   files    truncate/ftruncate, fsync, chmod family, umask, utimensat and-//!            real stat times, on ramfs, ext2, FAT and (read-only) btrfs-//!   misc     rlimits, prctl, sched_getaffinity, madvise, robust futex lists-//!   orphans  children whose parent exits without waiting (the kernel test-//!            checks their memory comes back), and PR_SET_CHILD_SUBREAPER-#![no_std]-#![no_main]--#[macro_use]-#[path = "ulib.rs"]-mod ulib;--extern crate alloc;--use core::sync::atomic::{AtomicI64, AtomicU64, Ordering};-use ulib::*;--// Stage 16 syscall numbers (Linux's).-const SYS_MADVISE: u64 = 28;-const SYS_FSYNC: u64 = 74;-const SYS_FDATASYNC: u64 = 75;-const SYS_TRUNCATE: u64 = 76;-const SYS_FTRUNCATE: u64 = 77;-const SYS_CHMOD: u64 = 90;-const SYS_FCHMOD: u64 = 91;-const SYS_UMASK: u64 = 95;-const SYS_GETRLIMIT: u64 = 97;-const SYS_RT_SIGPENDING: u64 = 127;-const SYS_RT_SIGTIMEDWAIT: u64 = 128;-const SYS_RT_SIGQUEUEINFO: u64 = 129;-const SYS_RT_SIGSUSPEND: u64 = 130;-const SYS_SIGALTSTACK: u64 = 131;-const SYS_PRCTL: u64 = 157;-const SYS_SETRLIMIT: u64 = 160;-const SYS_GETTID: u64 = 186;-const SYS_SCHED_GETAFFINITY: u64 = 204;-const SYS_FCHMODAT: u64 = 268;-const SYS_SET_ROBUST_LIST: u64 = 273;-const SYS_GET_ROBUST_LIST: u64 = 274;-const SYS_UTIMENSAT: u64 = 280;-const SYS_PRLIMIT64: u64 = 302;--const O_RDWR: u64 = 2;-const O_NONBLOCK: u64 = 0o4000;-const F_DUPFD: u64 = 0;-const F_GETFL: u64 = 3;-const F_SETFL: u64 = 4;-const F_GETLK: u64 = 5;-const F_SETLK: u64 = 6;-const F_SETLKW: u64 = 7;-const F_RDLCK: i16 = 0;-const F_WRLCK: i16 = 1;-const F_UNLCK: i16 = 2;-const EROFS: i64 = 30;-const EMFILE: i64 = 24;-const ENOMEM: i64 = 12;-const EDEADLK: i64 = 35;-const EOPNOTSUPP: i64 = 95;-const SA_ONSTACK: u64 = 0x0800_0000;-const SS_ONSTACK: u32 = 1;-const SS_DISABLE: u32 = 2;-const SI_QUEUE: i32 = -1;-const RLIMIT_STACK: u64 = 3;-const RLIMIT_NOFILE: u64 = 7;-const UTIME_NOW: i64 = (1 << 30) - 1;-const UTIME_OMIT: i64 = (1 << 30) - 2;-const FUTEX_OWNER_DIED: u32 = 0x4000_0000;--fn sys4(nr: u64, a: u64, b: u64, c: u64, d: u64) -> i64 {-    syscall5(nr, a, b, c, d, 0)-}--/// `struct flock`.-#[repr(C)]-#[derive(Clone, Copy, Default)]-struct Flock {-    l_type: i16,-    l_whence: i16,-    l_start: i64,-    l_len: i64,-    l_pid: i32,-    _pad: i32,-}--fn lock(fd: i64, cmd: u64, l_type: i16, start: i64, len: i64) -> (i64, Flock) {-    let mut fl = Flock { l_type, l_whence: SEEK_SET as i16, l_start: start, l_len: len, ..Default::default() };-    let r = syscall3(SYS_FCNTL, fd as u64, cmd, &mut fl as *mut Flock as u64);-    (r, fl)-}--fn cstr(s: &str) -> alloc::vec::Vec<u8> {-    let mut v = alloc::vec::Vec::from(s.as_bytes());-    v.push(0);-    v-}--fn path_call(nr: u64, path: &str, b: u64) -> i64 {-    with_cpath(path, |p| syscall3(nr, p, b, 0))-}--fn file_size(path: &str) -> i64 {-    let mut st = Stat::default();-    if stat(path, &mut st) < 0 { -1 } else { st.size }-}--fn perm(path: &str) -> u32 {-    let mut st = Stat::default();-    stat(path, &mut st);-    st.perm()-}--fn write_file(path: &str, data: &[u8]) -> i64 {-    let fd = open(path, O_WRONLY | O_CREAT | O_TRUNC);-    if fd < 0 {-        return fd;-    }-    let n = write(fd, data);-    close(fd);-    n-}--fn umask(mask: u64) -> u64 {-    syscall3(SYS_UMASK, mask, 0, 0) as u64-}--static GOT: AtomicI64 = AtomicI64::new(0);-static SP_SEEN: AtomicU64 = AtomicU64::new(0);-static ONSTACK_FLAGS: AtomicU64 = AtomicU64::new(0);-static CHANGE_ON_STACK: AtomicI64 = AtomicI64::new(0);--extern "C" fn count_handler(_sig: i32) {-    GOT.fetch_add(1, Ordering::SeqCst);-}--/// SA_ONSTACK handler: where is my stack, does sigaltstack say SS_ONSTACK,-/// and may I change the alternate stack while on it (no: EPERM)?-extern "C" fn altstack_handler(_sig: i32) {-    let local = 0u8;-    SP_SEEN.store(&local as *const u8 as u64, Ordering::SeqCst);-    let mut old = [0u64; 3];-    syscall3(SYS_SIGALTSTACK, 0, old.as_mut_ptr() as u64, 0);-    ONSTACK_FLAGS.store(old[1], Ordering::SeqCst);-    let new = [0u64, SS_DISABLE as u64, 0];-    CHANGE_ON_STACK.store(syscall3(SYS_SIGALTSTACK, new.as_ptr() as u64, 0, 0), Ordering::SeqCst);-}--fn main() -> i64 {-    let group = arg(1).unwrap_or("");-    let mut c = Checks::new("libctest");-    match group {-        "wait" => wait_group(&mut c),-        "signals" => signals(&mut c),-        "locks" => locks(&mut c),-        "files" => files(&mut c),-        "misc" => misc(&mut c),-        "orphans" => orphans(&mut c),-        _ => c.check(false, "usage: libctest wait|signals|locks|files|misc|orphans"),-    }-    c.done()-}--// --- wait: what dash does for `wait` / a foreground job ------------------------fn wait_group(c: &mut Checks) {-    GOT.store(0, Ordering::SeqCst);-    signal(SIGCHLD, count_handler);-    let old = sigprocmask(SIG_BLOCK, sigbit(SIGCHLD)) as u64;-    let pid = fork();-    if pid == 0 {-        sleep_ms(60);-        exit(7);-    }-    // dash: while no child changed state, sigsuspend(old mask) -- each-    // return is a handled signal (EINTR). Bounded so a broken kernel fails-    // the check instead of hanging the test like dash did.-    let empty = old & !sigbit(SIGCHLD);-    let mut rounds = 0;-    let mut all_eintr = true;-    while GOT.load(Ordering::SeqCst) == 0 && rounds < 20 {-        let r = syscall3(SYS_RT_SIGSUSPEND, &empty as *const u64 as u64, 8, 0);-        all_eintr &= r == -EINTR;-        rounds += 1;-    }-    c.check(all_eintr, "rt_sigsuspend returns EINTR after a handler ran");-    c.check(GOT.load(Ordering::SeqCst) == 1 && rounds == 1, "one sigsuspend: woken by the child's SIGCHLD");-    let mask_now = sigprocmask(SIG_BLOCK, 0) as u64;-    c.check(mask_now & sigbit(SIGCHLD) != 0, "the old mask (SIGCHLD blocked) is back afterwards");-    let st = wait4(pid, 0);-    c.check(matches!(st, Ok((p, s)) if p == pid && wexitstatus(s) == 7), "wait4 then reaps the child (status 7)");-    c.check(syscall3(SYS_RT_SIGSUSPEND, &empty as *const u64 as u64, 4, 0) == -EINVAL, "rt_sigsuspend with size 4: EINVAL");-    // SIGCHLD that arrived while blocked: sigsuspend returns at once.-    let pid = fork();-    if pid == 0 {-        exit(0);-    }-    while try_wait(pid) == -EAGAIN && GOT.load(Ordering::SeqCst) == 1 {-        sleep_ms(5); // until the child is a zombie (SIGCHLD pending)-    }-    sleep_ms(20);-    let r = syscall3(SYS_RT_SIGSUSPEND, &empty as *const u64 as u64, 8, 0);-    c.check(r == -EINTR && GOT.load(Ordering::SeqCst) == 2, "a SIGCHLD already pending ends sigsuspend right away");-    wait4(-1, 0).ok();-    sigprocmask(SIG_SETMASK, old);-}--// --- signals -------------------------------------------------------------------fn timedwait(set: u64, info: &mut [u64; 16], timeout: Option<[i64; 2]>) -> i64 {-    let ts = timeout.unwrap_or([0, 0]);-    let tsp = if timeout.is_some() { &ts as *const [i64; 2] as u64 } else { 0 };-    sys4(SYS_RT_SIGTIMEDWAIT, &set as *const u64 as u64, info.as_mut_ptr() as u64, tsp, 8)-}--fn sigqueue(pid: i64, sig: i32, code: i32, value: u64) -> i64 {-    let mut info = [0u64; 16];-    info[0] = sig as u32 as u64; // si_signo (si_errno = 0)-    info[1] = code as u32 as u64; // si_code (low half of the 2nd word)-    info[2] = getpid() as u64; // si_pid, si_uid-    info[3] = value; // si_value-    syscall3(SYS_RT_SIGQUEUEINFO, pid as u64, sig as u64, info.as_ptr() as u64)-}--fn signals(c: &mut Checks) {-    let mut info = [0u64; 16];-    // rt_sigpending-    signal(SIGUSR1, count_handler);-    GOT.store(0, Ordering::SeqCst);-    let old = sigprocmask(SIG_BLOCK, sigbit(SIGUSR1) | sigbit(SIGUSR2)) as u64;-    kill(getpid(), SIGUSR1);-    let mut pending = 0u64;-    c.check(syscall3(SYS_RT_SIGPENDING, &mut pending as *mut u64 as u64, 8, 0) == 0, "rt_sigpending: 0");-    c.check(pending & sigbit(SIGUSR1) != 0 && GOT.load(Ordering::SeqCst) == 0, "a blocked SIGUSR1 shows as pending, not delivered");-    c.check(syscall3(SYS_RT_SIGPENDING, &mut pending as *mut u64 as u64, 16, 0) == -EINVAL, "rt_sigpending with size 16: EINVAL");-    sigprocmask(SIG_UNBLOCK, sigbit(SIGUSR1));-    c.check(GOT.load(Ordering::SeqCst) == 1, "unblocking delivers it");--    // rt_sigtimedwait: takes a pending signal without running a handler.-    kill(getpid(), SIGUSR2);-    let r = timedwait(sigbit(SIGUSR2), &mut info, Some([0, 0]));-    c.check(r == SIGUSR2 as i64 && info[0] as u32 == SIGUSR2 as u32, "sigtimedwait takes a pending SIGUSR2 (and fills siginfo)");-    c.check(timedwait(sigbit(SIGUSR2), &mut info, Some([0, 0])) == -EAGAIN, "nothing pending, timeout {0,0}: EAGAIN at once");-    let t0 = now_us();-    let r = timedwait(sigbit(SIGUSR2), &mut info, Some([0, 120_000_000]));-    c.check(r == -EAGAIN && now_us() - t0 >= 100_000, "nothing comes within 120 ms: EAGAIN after the timeout");-    let bad = timedwait(sigbit(SIGUSR2), &mut info, Some([0, 1_000_000_000]));-    c.check(bad == -EINVAL, "tv_nsec of 1e9: EINVAL");-    let me = getpid();-    let child = fork();-    if child == 0 {-        sleep_ms(50);-        kill(me, SIGUSR2);-        exit(0);-    }-    let r = timedwait(sigbit(SIGUSR2), &mut info, None);-    c.check(r == SIGUSR2 as i64 && (info[2] & 0xffff_ffff) as i64 == child, "sigtimedwait sleeps until another process sends it (si_pid = sender)");-    wait(child);--    // rt_sigqueueinfo: real-time signals queue, each with its own value.-    let rt = 40;-    sigprocmask(SIG_BLOCK, sigbit(rt));-    for v in [11u64, 22, 33] {-        sigqueue(me, rt, SI_QUEUE, v);-    }-    let mut values = alloc::vec::Vec::new();-    while timedwait(sigbit(rt), &mut info, Some([0, 0])) == rt as i64 {-        values.push(info[3]);-        c.check(info[1] as u32 as i32 == SI_QUEUE, "queued signal keeps si_code SI_QUEUE");-    }-    c.check(values == [11, 22, 33], "a real-time signal sent 3 times arrives 3 times, in order, with its values");-    sigqueue(me, SIGUSR2, SI_QUEUE, 1);-    sigqueue(me, SIGUSR2, SI_QUEUE, 2);-    let first = timedwait(sigbit(SIGUSR2), &mut info, Some([0, 0]));-    let second = timedwait(sigbit(SIGUSR2), &mut info, Some([0, 0]));-    c.check(first == SIGUSR2 as i64 && second == -EAGAIN, "a standard signal sent twice is pending once");-    let child = fork();-    if child == 0 {-        sleep_ms(200);-        exit(0);-    }-    c.check(sigqueue(child, SIGUSR2, 0, 0) == -EPERM, "si_code >= 0 (pretending to be kill) to another process: EPERM");-    c.check(sigqueue(child, 0, SI_QUEUE, 0) == 0, "signal 0 to a live process: 0");-    kill(child, SIGKILL);-    wait(child);-    c.check(sigqueue(99_999, SIGUSR2, SI_QUEUE, 0) == -ESRCH, "to a pid that doesn't exist: ESRCH");--    // sigaltstack-    let mut old_ss = [0u64; 3];-    syscall3(SYS_SIGALTSTACK, 0, old_ss.as_mut_ptr() as u64, 0);-    c.check(old_ss[1] as u32 == SS_DISABLE, "no alternate stack at first (SS_DISABLE)");-    let size = 64 * 1024u64;-    let stack = mmap(0, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0) as u64;-    let small = [stack, 0, 1024];-    c.check(syscall3(SYS_SIGALTSTACK, small.as_ptr() as u64, 0, 0) == -ENOMEM, "a 1 KiB alternate stack: ENOMEM");-    let badflags = [stack, 0x10, size];-    c.check(syscall3(SYS_SIGALTSTACK, badflags.as_ptr() as u64, 0, 0) == -EINVAL, "unknown ss_flags: EINVAL");-    let ss = [stack, 0, size];-    c.check(syscall3(SYS_SIGALTSTACK, ss.as_ptr() as u64, 0, 0) == 0, "set a 64 KiB alternate stack");-    sigaction_raw(SIGUSR1, altstack_handler as usize, SA_ONSTACK, 0);-    kill(me, SIGUSR1);-    let sp = SP_SEEN.load(Ordering::SeqCst);-    c.check((stack..stack + size).contains(&sp), "an SA_ONSTACK handler runs on the alternate stack");-    c.check(ONSTACK_FLAGS.load(Ordering::SeqCst) as u32 == SS_ONSTACK, "inside it, sigaltstack reports SS_ONSTACK");-    c.check(CHANGE_ON_STACK.load(Ordering::SeqCst) == -EPERM, "changing the alternate stack while on it: EPERM");-    syscall3(SYS_SIGALTSTACK, 0, old_ss.as_mut_ptr() as u64, 0);-    c.check(old_ss == [stack, 0, size], "afterwards it's still set, flags 0 (not on it)");-    let off = [0u64, SS_DISABLE as u64, 0];-    syscall3(SYS_SIGALTSTACK, off.as_ptr() as u64, 0, 0);-    munmap(stack, size);--    // Unknown sa_flags bits aren't stored (musl checks what it got back).-    let act = SigAction { handler: count_handler as usize, flags: 0x100 | 0x0400_0000, restorer: 0, mask: 0 };-    let mut back = SigAction::default();-    syscall5(SYS_RT_SIGACTION, SIGUSR2 as u64, &act as *const _ as u64, 0, 8, 0);-    syscall5(SYS_RT_SIGACTION, SIGUSR2 as u64, 0, &mut back as *mut _ as u64, 8, 0);-    c.check(back.flags & 0x100 == 0 && back.flags & 0x0400_0000 != 0, "unknown sa_flags bits are dropped, known ones kept");-    sigaction_raw(SIGUSR1, SIG_DFL, 0, 0);-    sigaction_raw(SIGUSR2, SIG_DFL, 0, 0);-    sigprocmask(SIG_SETMASK, old);-}--// --- locks ---------------------------------------------------------------------fn locks(c: &mut Checks) {-    let path = "/tmp/libctest-locks";-    write_file(path, b"0123456789abcdefghijklmnopqrstuvwxyz");-    // F_GETFL tells the access mode and O_APPEND; F_SETFL changes O_APPEND.-    let ro = open(path, O_RDONLY);-    let wa = open(path, O_WRONLY | O_APPEND);-    c.check(fcntl(ro, F_GETFL, 0) & 3 == 0, "F_GETFL of an O_RDONLY fd: O_RDONLY");-    c.check(fcntl(wa, F_GETFL, 0) as u64 & (3 | O_APPEND) == O_WRONLY | O_APPEND, "F_GETFL: O_WRONLY | O_APPEND");-    fcntl(wa, F_SETFL, O_NONBLOCK);-    c.check(fcntl(wa, F_GETFL, 0) as u64 & (O_APPEND | O_NONBLOCK) == O_NONBLOCK, "F_SETFL clears O_APPEND, sets O_NONBLOCK");-    c.check(lock(ro, F_SETLK, F_WRLCK, 0, 1).0 == -EBADF, "a write lock through a read-only fd: EBADF");-    c.check(lock(wa, F_SETLK, F_RDLCK, 0, 1).0 == -EBADF, "a read lock through a write-only fd: EBADF");-    c.check(lock(ro, F_SETLK, 7, 0, 1).0 == -EINVAL, "l_type 7: EINVAL");-    c.check(lock(ro, F_SETLK, F_RDLCK, -5, 1).0 == -EINVAL, "a range starting before 0: EINVAL");-    close(ro);-    close(wa);--    // Two processes.-    let fd = open(path, O_RDWR);-    c.check(lock(fd, F_SETLK, F_WRLCK, 0, 10).0 == 0, "parent: write lock on bytes 0..10");-    let (r, fl) = lock(fd, F_GETLK, F_WRLCK, 0, 10);-    c.check(r == 0 && fl.l_type == F_UNLCK, "F_GETLK: my own locks never conflict");-    let parent = getpid();-    let (rd, wr) = pipe().unwrap();-    let child = fork();-    if child == 0 {-        let mut k = Checks::new("libctest-child");-        let fd = open(path, O_RDWR);-        let (r, fl) = lock(fd, F_GETLK, F_RDLCK, 5, 1);-        k.check(r == 0 && fl.l_type == F_WRLCK && fl.l_pid as i64 == parent && fl.l_start == 0 && fl.l_len == 10, "child: F_GETLK names the parent's lock");-        k.check(lock(fd, F_SETLK, F_RDLCK, 9, 5).0 == -EAGAIN, "child: overlapping F_SETLK: EAGAIN");-        k.check(lock(fd, F_SETLK, F_WRLCK, 10, 10).0 == 0, "child: bytes 10..20 are free");-        write(wr, b"x"); // tell the parent we're about to wait-        let t0 = now_us();-        let r = lock(fd, F_SETLKW, F_WRLCK, 0, 10).0;-        k.check(r == 0 && now_us() - t0 >= 50_000, "child: F_SETLKW waits until the parent's lock goes away");-        // The parent now asks for 10..20 (ours) with F_SETLKW and sleeps;-        // us asking for its 30..40 would close the circle: EDEADLK.-        sleep_ms(100);-        k.check(lock(fd, F_SETLKW, F_WRLCK, 30, 5).0 == -EDEADLK, "child: a lock wait that would deadlock: EDEADLK");-        exit(k.done()); // exiting drops all our locks-    }-    let mut b = [0u8; 1];-    read(rd, &mut b);-    sleep_ms(100);-    c.check(lock(fd, F_SETLK, F_WRLCK, 30, 10).0 == 0, "parent: write lock on 30..40");-    let fd2 = open(path, O_RDONLY);-    close(fd2); // closing *any* fd of the file drops my locks on it-    // (the child now gets 0..10; we wait for its 10..20, which it holds-    // until it exits)-    c.check(lock(fd, F_SETLK, F_WRLCK, 30, 10).0 == 0, "parent: 30..40 again (the close dropped it)");-    let r = lock(fd, F_SETLKW, F_WRLCK, 10, 10).0;-    c.check(r == 0, "parent: F_SETLKW on the child's range succeeds once it exits");-    c.check(matches!(wait4(child, 0), Ok((_, s)) if wexitstatus(s) == 0), "child's checks passed");-    c.check(lock(fd, F_SETLK, F_WRLCK, 0, 0).0 == 0, "the whole file is free after the child exited");-    c.check(lock(fd, F_SETLK, F_UNLCK, 0, 0).0 == 0, "F_UNLCK");-    close(fd);-    close(rd);-    close(wr);-    unlink(path);--    // RLIMIT_NOFILE is the fd table's real size; F_DUPFD honours it.-    let mut lim = [0u64; 2];-    syscall3(SYS_GETRLIMIT, RLIMIT_NOFILE, lim.as_mut_ptr() as u64, 0);-    c.check(lim == [64, 64], "RLIMIT_NOFILE is 64 (the fd table size)");-    c.check(fcntl(0, F_DUPFD, 64) == -EINVAL, "F_DUPFD at 64: EINVAL");-    let small = [8u64, 64];-    syscall3(SYS_SETRLIMIT, RLIMIT_NOFILE, small.as_ptr() as u64, 0);-    c.check(fcntl(0, F_DUPFD, 8) == -EINVAL, "after setrlimit(NOFILE, 8): F_DUPFD at 8 is EINVAL");-    let mut fds = alloc::vec::Vec::new();-    loop {-        let fd = dup(0);-        if fd < 0 {-            c.check(fd == -EMFILE && fds.iter().all(|&f| f < 8), "dup fails with EMFILE at the soft limit");-            break;-        }-        fds.push(fd);-    }-    for fd in fds {-        close(fd);-    }-    let back = [64u64, 64];-    c.check(syscall3(SYS_SETRLIMIT, RLIMIT_NOFILE, back.as_ptr() as u64, 0) == 0, "raising it back up to the hard limit");-}--// --- files ---------------------------------------------------------------------fn files(c: &mut Checks) {-    // truncate / ftruncate on ramfs, ext2, FAT.-    for dir in ["/tmp", "/mnt/disk1", "/mnt/disk0"] {-        let path = alloc::format!("{}/trunc16.txt", dir);-        write_file(&path, b"hello world");-        let fd = open(&path, O_RDWR);-        c.check(syscall3(SYS_FTRUNCATE, fd as u64, 5, 0) == 0 && file_size(&path) == 5, &alloc::format!("{}: ftruncate to 5 bytes", dir));-        c.check(syscall3(SYS_FTRUNCATE, fd as u64, 9000, 0) == 0 && file_size(&path) == 9000, &alloc::format!("{}: ftruncate grows to 9000", dir));-        let mut buf = [0xffu8; 16];-        let n = syscall6(SYS_PREAD64_NR, fd as u64, buf.as_mut_ptr() as u64, 16, 0, 0, 0);-        c.check(n == 16 && &buf[..5] == b"hello" && buf[5..].iter().all(|&b| b == 0), &alloc::format!("{}: the old bytes stay, the new ones are zeros", dir));-        c.check(syscall3(SYS_FSYNC, fd as u64, 0, 0) == 0 && syscall3(SYS_FDATASYNC, fd as u64, 0, 0) == 0, &alloc::format!("{}: fsync, fdatasync: 0", dir));-        close(fd);-        c.check(path_call(SYS_TRUNCATE, &path, 3) == 0 && file_size(&path) == 3, &alloc::format!("{}: truncate(path, 3)", dir));-        let ro = open(&path, O_RDONLY);-        c.check(syscall3(SYS_FTRUNCATE, ro as u64, 0, 0) == -EINVAL, &alloc::format!("{}: ftruncate of a read-only fd: EINVAL", dir));-        c.check(syscall3(SYS_FTRUNCATE, ro as u64, -1i64 as u64, 0) == -EINVAL, &alloc::format!("{}: negative length: EINVAL", dir));-        close(ro);-        unlink(&path);-    }-    c.check(path_call(SYS_TRUNCATE, "/tmp", 0) == -EISDIR, "truncate of a directory: EISDIR");-    c.check(path_call(SYS_TRUNCATE, "/mnt/disk3/hello.txt", 0) == -EROFS, "truncate on btrfs (read-only here): EROFS");-    c.check(path_call(SYS_TRUNCATE, "/tmp/nope16", 0) == -ENOENT, "truncate of a missing file: ENOENT");-    let (rd, wr) = pipe().unwrap();-    c.check(syscall3(SYS_FSYNC, rd as u64, 0, 0) == -EINVAL, "fsync of a pipe: EINVAL");-    c.check(syscall3(SYS_FTRUNCATE, wr as u64, 0, 0) == -EINVAL, "ftruncate of a pipe: EINVAL");-    close(rd);-    close(wr);--    // umask: taken away from open(O_CREAT) and mkdir modes; per process.-    let old = umask(0o077);-    c.check(old == 0o022, "umask starts as 022 and returns the old value");-    write_file("/tmp/mask16", b"x");-    c.check(perm("/tmp/mask16") == 0o600, "with umask 077, open(O_CREAT, 0644) makes 0600");-    with_cpath("/tmp/maskdir16", |p| syscall3(SYS_MKDIR, p, 0o777, 0));-    c.check(perm("/tmp/maskdir16") == 0o700, "and mkdir(0777) makes 0700");-    let child = fork();-    if child == 0 {-        exit(if umask(0) == 0o077 { 0 } else { 1 });-    }-    c.check(matches!(wait4(child, 0), Ok((_, s)) if wexitstatus(s) == 0), "fork inherits the umask");-    c.check(umask(old) == 0o077, "umask(022) gives back 077");-    unlink("/tmp/mask16");-    with_cpath("/tmp/maskdir16", |p| syscall3(SYS_RMDIR, p, 0, 0));--    // chmod family.-    for dir in ["/tmp", "/mnt/disk1"] {-        let path = alloc::format!("{}/mode16", dir);-        write_file(&path, b"m");-        c.check(path_call(SYS_CHMOD, &path, 0o751) == 0 && perm(&path) == 0o751, &alloc::format!("{}: chmod 751", dir));-        let fd = open(&path, O_RDONLY);-        c.check(syscall3(SYS_FCHMOD, fd as u64, 0, 0) == 0 && perm(&path) == 0, &alloc::format!("{}: fchmod 000 (a real 0, not 'unknown')", dir));-        close(fd);-        let name = cstr("mode16");-        let dfd = open(dir, O_RDONLY | O_DIRECTORY);-        c.check(sys4(SYS_FCHMODAT, dfd as u64, name.as_ptr() as u64, 0o4644, 0) == 0 && perm(&path) == 0o4644, &alloc::format!("{}: fchmodat relative to a dir fd, setuid bit kept", dir));-        close(dfd);-        unlink(&path);-    }-    c.check(path_call(SYS_CHMOD, "/mnt/disk0/readme.txt", 0o600) == -EPERM, "chmod on FAT (no permissions to keep): EPERM");-    c.check(path_call(SYS_CHMOD, "/mnt/disk3/hello.txt", 0o600) == -EROFS, "chmod on btrfs: EROFS");-    // (Rustbelt can't make symlinks yet; btrfs has some.)-    let link = cstr("/mnt/disk3/hello-link");-    let r = sys4(452, AT_FDCWD as u64, link.as_ptr() as u64, 0o700, AT_SYMLINK_NOFOLLOW);-    c.check(r == -EOPNOTSUPP, "fchmodat2(AT_SYMLINK_NOFOLLOW) on a symlink: EOPNOTSUPP (like Linux)");--    // utimensat + stat times.-    for (dir, nsec_kept) in [("/tmp", true), ("/mnt/disk1", false)] {-        let path = alloc::format!("{}/times16", dir);-        write_file(&path, b"t");-        let mut st = Stat::default();-        stat(&path, &mut st);-        c.check(st.mtime[0] > 1_000_000_000, &alloc::format!("{}: a new file's mtime is now (not 1970)", dir));-        let p = cstr(&path);-        let times = [[1000i64, 5], [2000, 6]];-        let r = sys4(SYS_UTIMENSAT, AT_FDCWD as u64, p.as_ptr() as u64, times.as_ptr() as u64, 0);-        stat(&path, &mut st);-        let want = if nsec_kept { ([1000, 5], [2000, 6]) } else { ([1000, 0], [2000, 0]) };-        c.check(r == 0 && st.atime == want.0 && st.mtime == want.1, &alloc::format!("{}: utimensat sets atime and mtime", dir));-        c.check(st.ctime[0] > 1_000_000_000, &alloc::format!("{}: ... and ctime becomes now", dir));-        let omit = [[0i64, UTIME_OMIT], [3000, 0]];-        sys4(SYS_UTIMENSAT, AT_FDCWD as u64, p.as_ptr() as u64, omit.as_ptr() as u64, 0);-        stat(&path, &mut st);-        c.check(st.atime[0] == 1000 && st.mtime[0] == 3000, &alloc::format!("{}: UTIME_OMIT leaves atime alone", dir));-        let fd = open(&path, O_RDONLY);-        let now = [[0i64, UTIME_NOW], [0, UTIME_NOW]];-        c.check(sys4(SYS_UTIMENSAT, fd as u64, 0, now.as_ptr() as u64, 0) == 0, &alloc::format!("{}: futimens (NULL path, an fd): 0", dir));-        fstat(fd, &mut st);-        c.check(st.atime[0] > 1_000_000_000 && st.mtime[0] > 1_000_000_000, &alloc::format!("{}: UTIME_NOW, seen by fstat", dir));-        close(fd);-        unlink(&path);-    }-    let p = cstr("/tmp");-    let bad = [[0i64, 1_000_000_000], [0, 0]];-    c.check(sys4(SYS_UTIMENSAT, AT_FDCWD as u64, p.as_ptr() as u64, bad.as_ptr() as u64, 0) == -EINVAL, "utimensat with tv_nsec = 1e9: EINVAL");-    c.check(sys4(SYS_UTIMENSAT, AT_FDCWD as u64, p.as_ptr() as u64, 0, 0x8) == -EINVAL, "unknown flags: EINVAL");-    let h = cstr("/mnt/disk3/hello.txt");-    c.check(sys4(SYS_UTIMENSAT, AT_FDCWD as u64, h.as_ptr() as u64, 0, 0) == -EROFS, "utimensat on btrfs: EROFS");-    let mut st = Stat::default();-    stat("/mnt/disk3/hello.txt", &mut st);-    c.check(st.mtime[0] > 1_000_000_000, "btrfs files report their real mtime");-}--const SYS_PREAD64_NR: u64 = 17;--// --- misc ----------------------------------------------------------------------fn misc(c: &mut Checks) {-    // rlimits-    let mut lim = [0u64; 2];-    c.check(syscall3(SYS_GETRLIMIT, RLIMIT_STACK, lim.as_mut_ptr() as u64, 0) == 0 && lim[0] > 0 && lim[0] == lim[1], "RLIMIT_STACK: the real (fixed) stack size");-    c.check(syscall3(SYS_GETRLIMIT, 99, lim.as_mut_ptr() as u64, 0) == -EINVAL, "an unknown resource: EINVAL");-    let up = [64u64, 1000];-    c.check(syscall3(SYS_SETRLIMIT, RLIMIT_NOFILE, up.as_ptr() as u64, 0) == -EPERM, "raising NOFILE's hard limit past 64: EPERM");-    let inverted = [10u64, 5];-    c.check(syscall3(SYS_SETRLIMIT, RLIMIT_NOFILE, inverted.as_ptr() as u64, 0) == -EINVAL, "soft above hard: EINVAL");-    let new = [32u64, 64];-    let mut old = [0u64; 2];-    let r = sys4(SYS_PRLIMIT64, 0, RLIMIT_NOFILE, new.as_ptr() as u64, old.as_mut_ptr() as u64);-    c.check(r == 0 && old == [64, 64], "prlimit64 sets and returns the old value");-    let child = fork();-    if child == 0 {-        let mut l = [0u64; 2];-        syscall3(SYS_GETRLIMIT, RLIMIT_NOFILE, l.as_mut_ptr() as u64, 0);-        exit(if l == [32, 64] { 0 } else { 1 });-    }-    c.check(matches!(wait4(child, 0), Ok((_, s)) if wexitstatus(s) == 0), "fork inherits rlimits");-    let back = [64u64, 64];-    sys4(SYS_PRLIMIT64, getpid() as u64, RLIMIT_NOFILE, back.as_ptr() as u64, 0);-    c.check(sys4(SYS_PRLIMIT64, 99_999, RLIMIT_NOFILE, 0, old.as_mut_ptr() as u64) == -ESRCH, "prlimit64 of a missing pid: ESRCH");--    // prctl-    let name = cstr("libctest-worker-long");-    c.check(syscall3(SYS_PRCTL, 15, name.as_ptr() as u64, 0) == 0, "PR_SET_NAME");-    let mut got = [0xffu8; 16];-    syscall3(SYS_PRCTL, 16, got.as_mut_ptr() as u64, 0);-    c.check(&got[..15] == b"libctest-worker" && got[15] == 0, "PR_GET_NAME: cut to 15 bytes + NUL");-    let mut sig = -1i32;-    syscall3(SYS_PRCTL, 1, SIGTERM as u64, 0);-    syscall3(SYS_PRCTL, 2, &mut sig as *mut i32 as u64, 0);-    c.check(sig == SIGTERM, "PR_SET/GET_PDEATHSIG");-    c.check(syscall3(SYS_PRCTL, 1, 99, 0) == -EINVAL, "PR_SET_PDEATHSIG 99: EINVAL");-    syscall3(SYS_PRCTL, 1, 0, 0);-    c.check(syscall3(SYS_PRCTL, 3, 0, 0) == 1, "PR_GET_DUMPABLE: 1");-    c.check(syscall3(SYS_PRCTL, 4, 2, 0) == -EINVAL, "PR_SET_DUMPABLE 2: EINVAL");-    c.check(syscall5(SYS_PRCTL, 38, 1, 0, 0, 0) == 0 && syscall5(SYS_PRCTL, 39, 0, 0, 0, 0) == 1, "PR_SET/GET_NO_NEW_PRIVS");-    c.check(syscall3(SYS_PRCTL, 0x5555, 0, 0) == -EINVAL, "an unknown prctl: EINVAL");--    // sched_getaffinity: every CPU, returns the bytes written.-    let mut mask = [0u64; 2];-    let r = syscall3(SYS_SCHED_GETAFFINITY, 0, 16, mask.as_mut_ptr() as u64);-    let cpus = sysinfo().cpus as u32;-    c.check(r == 8 && mask[0].count_ones() == cpus && cpus >= 1, &alloc::format!("sched_getaffinity: {} CPU bits, returns 8", cpus));-    c.check(syscall3(SYS_SCHED_GETAFFINITY, 0, 4, mask.as_mut_ptr() as u64) == -EINVAL, "sched_getaffinity with 4 bytes: EINVAL");--    // madvise-    let page = mmap(0, 8192, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0) as u64;-    unsafe { core::ptr::write_bytes(page as *mut u8, 0xaa, 8192) };-    c.check(syscall3(SYS_MADVISE, page, 8192, 3) == 0, "MADV_WILLNEED: 0 (a hint)");-    c.check(syscall3(SYS_MADVISE, page, 8192, 4) == 0, "MADV_DONTNEED: 0");-    let zero = unsafe { core::slice::from_raw_parts(page as *const u8, 8192) }.iter().all(|&b| b == 0);-    c.check(zero, "after MADV_DONTNEED private anonymous pages read as zeros");-    c.check(syscall3(SYS_MADVISE, page + 1, 4096, 4) == -EINVAL, "unaligned address: EINVAL");-    c.check(syscall3(SYS_MADVISE, page, 4096, 999) == -EINVAL, "unknown advice: EINVAL");-    munmap(page, 8192);-    c.check(syscall3(SYS_MADVISE, page, 4096, 4) == -ENOMEM, "on unmapped memory: ENOMEM");--    // Robust futex lists.-    let mut head = [0u64; 3];-    c.check(syscall3(SYS_SET_ROBUST_LIST, head.as_ptr() as u64, 23, 0) == -EINVAL, "set_robust_list with len 23: EINVAL");-    c.check(syscall3(SYS_SET_ROBUST_LIST, head.as_ptr() as u64, 24, 0) == 0, "set_robust_list");-    let (mut h, mut l) = (0u64, 0u64);-    syscall3(SYS_GET_ROBUST_LIST, 0, &mut h as *mut u64 as u64, &mut l as *mut u64 as u64);-    c.check(h == head.as_ptr() as u64 && l == 24, "get_robust_list returns it");-    syscall3(SYS_SET_ROBUST_LIST, 0, 24, 0);-    // A child dies holding a "robust mutex" in shared memory: the kernel-    // marks the lock word FUTEX_OWNER_DIED, so the next locker knows.-    let shared = mmap(0, 4096, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0) as u64;-    let word = shared as *mut u32;-    let entry = shared + 64; // the mutex's list node; lock word at entry - 64-    let child = fork();-    if child == 0 {-        let tid = syscall3(SYS_GETTID, 0, 0, 0) as u32;-        head[0] = entry; // list: head -> entry -> head-        head[1] = (-64i64) as u64; // futex_offset: from the node to the word-        head[2] = 0;-        unsafe {-            *(entry as *mut u64) = head.as_ptr() as u64;-            word.write_volatile(tid); // "locked by me"-        }-        syscall3(SYS_SET_ROBUST_LIST, head.as_ptr() as u64, 24, 0);-        exit(0); // ... and die holding it-    }-    wait(child);-    let w = unsafe { word.read_volatile() };-    c.check(w & FUTEX_OWNER_DIED != 0 && w & 0x3fff_ffff == 0, "a robust lock held by a dead thread: FUTEX_OWNER_DIED, owner 0");-    munmap(shared, 4096);-    head[0] = 0;-}--// --- orphans -------------------------------------------------------------------fn orphans(c: &mut Checks) {-    // A child that leaves a running grandchild and a zombie grandchild-    // behind and exits without waiting for either.-    let child = fork();-    if child == 0 {-        if fork() == 0 {-            exit(0); // becomes a zombie: its parent never waits-        }-        if fork() == 0 {-            sleep_ms(40); // still running when its parent exits-            exit(0);-        }-        sleep_ms(10);-        exit(0);-    }-    c.check(matches!(wait4(child, 0), Ok((p, _)) if p == child), "the child exits without waiting for its own children");--    // With PR_SET_CHILD_SUBREAPER we adopt the orphan and can wait for it.-    c.check(syscall3(SYS_PRCTL, 36, 1, 0) == 0, "PR_SET_CHILD_SUBREAPER");-    let me = getpid();-    let child = fork();-    if child == 0 {-        if fork() == 0 {-            sleep_ms(40);-            exit(if getppid() == me { 5 } else { 6 }); // adopted by the subreaper?-        }-        exit(0);-    }-    c.check(matches!(wait4(child, 0), Ok((p, s)) if p == child && wexitstatus(s) == 0), "the middle process exits");-    let r = wait4(-1, 0);-    c.check(matches!(r, Ok((p, s)) if p != child && wexitstatus(s) == 5), "the subreaper reaps the orphaned grandchild, which saw getppid() = subreaper");-    c.check(wait4(-1, WNOHANG) == Err(-ECHILD), "no children left");-    sleep_ms(80); // let the first orphan finish before we exit-}
import/kernel-user/mmaptest.rs
@@ -1,130 +0,0 @@-//! mmaptest -- checks mmap / munmap / mprotect / brk and demand paging.-//!-//!   mmaptest         run all the checks (exit code = failed checks)-//!   mmaptest crash   write to read-only memory without a handler: the-//!                    default action of SIGSEGV ends the program (139)-//!-//! Faults are caught with a SIGSEGV handler that records what happened-//! (address, SEGV_MAPERR = nothing mapped / SEGV_ACCERR = not allowed) and-//! then *fixes* it (mprotect / mmap the page), so the faulting instruction-//! can run again when the handler returns. Garbage collectors and-//! user-space pagers use the same trick.-#![no_std]-#![no_main]--#[macro_use]-#[path = "ulib.rs"]-mod ulib;--use core::sync::atomic::{AtomicU64, Ordering::SeqCst};-use ulib::*;--const PAGE: u64 = 4096;-const SEGV_MAPERR: i32 = 1;-const SEGV_ACCERR: i32 = 2;--static FAULTS: AtomicU64 = AtomicU64::new(0);-static ADDR: AtomicU64 = AtomicU64::new(0);-static CODE: AtomicU64 = AtomicU64::new(0);--extern "C" fn on_segv(sig: i32, info: *const SigInfo, _context: *mut u8) {-    let info = unsafe { &*info };-    FAULTS.fetch_add(1, SeqCst);-    ADDR.store(info.addr_or_pid, SeqCst);-    CODE.store(info.code as u64, SeqCst);-    let page = info.addr_or_pid & !(PAGE - 1);-    // Fix it: make the page readable and writable (mapping it if needed).-    if sig != SIGSEGV || mprotect(page, PAGE, PROT_READ | PROT_WRITE) < 0 {-        mmap(page, PAGE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);-    }-}--fn peek(addr: u64) -> u8 {-    unsafe { (addr as *const u8).read_volatile() }-}-fn poke(addr: u64, value: u8) {-    unsafe { (addr as *mut u8).write_volatile(value) }-}--fn main() -> i64 {-    if arg(1) == Some("crash") {-        let p = mmap(0, PAGE, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0) as u64;-        println!("[mmaptest] writing to read-only memory at {:#x}...", p);-        poke(p, 1);-        println!("[mmaptest] not reached");-        return 1;-    }-    let mut c = Checks::new("mmaptest");-    let before = sysinfo().frames_in_use;--    // 1. Anonymous memory: zeroed, readable, writable.-    let a = mmap(0, 3 * PAGE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0) as u64;-    c.check(a as i64 > 0 && a % PAGE == 0, "mmap 3 pages of anonymous memory");-    c.check((0..3 * PAGE).all(|i| peek(a + i) == 0), "it starts zeroed");-    for i in 0..3 * PAGE {-        poke(a + i, i as u8);-    }-    c.check((0..3 * PAGE).all(|i| peek(a + i) == i as u8), "it keeps what we write");--    // 2. Demand paging: a big mapping costs (almost) nothing until touched.-    let big = mmap(0, 64 << 20, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0) as u64;-    let after_map = sysinfo().frames_in_use;-    poke(big, 1);-    poke(big + (32 << 20), 2);-    poke(big + (64 << 20) - 1, 3);-    let after_touch = sysinfo().frames_in_use;-    c.check(big as i64 > 0 && after_map < before + 8, "mmap 64 MiB: no memory used yet (pages come on first touch)");-    c.check(after_touch >= after_map + 3 && after_touch < after_map + 16, "touching 3 pages of it uses ~3 frames");-    c.check(munmap(big, 64 << 20) == 0, "munmap the 64 MiB");--    // 3. Faults become signals our handler sees.-    c.check(sigaction_info(SIGSEGV, on_segv, 0) == 0, "install a SIGSEGV handler (SA_SIGINFO)");-    c.check(mprotect(a + PAGE, PAGE, PROT_READ) == 0, "mprotect the middle page read-only");-    c.check(peek(a + PAGE + 5) == 5 && FAULTS.load(SeqCst) == 0, "reading it still works");-    poke(a + PAGE + 7, 99);-    c.check(FAULTS.load(SeqCst) == 1 && ADDR.load(SeqCst) == a + PAGE + 7 && CODE.load(SeqCst) == SEGV_ACCERR as u64, "writing it: SIGSEGV, si_addr = that byte, SEGV_ACCERR");-    c.check(peek(a + PAGE + 7) == 99, "the handler's mprotect fixed it and the write was retried");--    c.check(munmap(a + PAGE, PAGE) == 0, "munmap the middle page");-    c.check(peek(a) == 0 && peek(a + 2 * PAGE + 1) == 1, "the pages around the hole are untouched");-    let v = peek(a + PAGE + 7);-    c.check(FAULTS.load(SeqCst) == 2 && CODE.load(SeqCst) == SEGV_MAPERR as u64 && v == 0, "reading the hole: SEGV_MAPERR; the handler maps a fresh (zero) page");-    c.check(mprotect(a, PAGE, PROT_NONE) == 0 && { poke(a, 4); FAULTS.load(SeqCst) == 3 }, "PROT_NONE: even reading faults");-    c.check(munmap(a, 3 * PAGE) == 0, "munmap all of it");--    // 4. A file mapped read-only (private): the same bytes read() gives.-    let fd = open("/mnt/disk0/readme.txt", 0);-    let mut buf = [0u8; 211];-    let n = read(fd, &mut buf);-    let f = mmap(0, 2 * PAGE, PROT_READ, MAP_PRIVATE, fd, 0) as u64;-    c.check(fd >= 0 && n == 211 && f as i64 > 0, "mmap /mnt/disk0/readme.txt");-    c.check((0..211).all(|i| peek(f + i) == buf[i as usize]), "the mapping has the file's bytes");-    c.check(peek(f + 211) == 0 && peek(f + PAGE - 1) == 0, "after the end of the file: zeros");-    let faults = FAULTS.load(SeqCst);-    poke(f, b'J');-    c.check(FAULTS.load(SeqCst) == faults + 1 && peek(f) == b'J', "writing it faults; after mprotect we write our private copy");-    let mut again = [0u8; 1];-    lseek(fd, 0, SEEK_SET);-    read(fd, &mut again);-    c.check(again[0] == b'H', "the file itself is unchanged");-    c.check(mmap(0, PAGE, PROT_READ, MAP_SHARED, fd, 0) < 0, "MAP_SHARED of a file isn't supported (an error, not silent loss)");-    munmap(f, 2 * PAGE);-    close(fd);--    // 5. brk: grow the heap by 1 MiB, use the end, shrink back.-    let start = brk(0);-    let end = brk(start + (1 << 20));-    c.check(end == start + (1 << 20), "brk grows the heap by 1 MiB");-    poke(end - 1, 7);-    c.check(peek(end - 1) == 7, "the new end is usable");-    c.check(brk(start) == start, "brk shrinks it back");--    // 6. Bad requests.-    c.check(mmap(0, 0, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0) == -22, "mmap of 0 bytes: EINVAL");-    c.check(mmap(0x1000, PAGE, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0) == -22, "MAP_FIXED outside user space: EINVAL");--    sigaction_raw(SIGSEGV, SIG_DFL, 0, 0);-    let leaked = sysinfo().frames_in_use as i64 - before as i64;-    c.check(leaked < 16, "memory use is back to (about) where it started");-    c.done()-}
import/kernel-user/nbtest.rs
@@ -1,151 +0,0 @@-//! nbtest -- checks O_NONBLOCK: "would block" becomes EAGAIN instead.-//!-//! A blocking read on an empty pipe sleeps until data comes. With-//! O_NONBLOCK (set with fcntl(F_SETFL), ioctl(FIONBIO), pipe2 or-//! SOCK_NONBLOCK) it returns -EAGAIN at once ("try again later"), which-//! is what an event loop wants: it only reads when poll said "ready", and-//! must never get stuck on one fd.-#![no_std]-#![no_main]--#[macro_use]-#[path = "ulib.rs"]-mod ulib;--extern crate alloc;--use ulib::*;--const FIONBIO: u64 = 0x5421;--fn main() -> i64 {-    let mut c = Checks::new("nbtest");-    let mut buf = [0u8; 4096];--    // Pipes.-    let (r, w) = pipe2(O_NONBLOCK).unwrap();-    c.check(read(r, &mut buf) == -EAGAIN, "pipe: read with nothing there: EAGAIN");-    let mut p = [PollFd { fd: r as i32, events: POLLIN, revents: 0 }];-    c.check(poll(&mut p, 0) == 0, "pipe: poll says not readable");-    let mut total = 0;-    loop {-        let n = write(w, &buf);-        if n < 0 {-            c.check(n == -EAGAIN, "pipe: write to a full pipe: EAGAIN");-            break;-        }-        total += n;-    }-    println!("nbtest: the pipe took {} bytes before it was full", total);-    c.check(total == 65536, "pipe: holds 64 KiB, like Linux");-    let mut p = [PollFd { fd: w as i32, events: POLLOUT, revents: 0 }];-    c.check(poll(&mut p, 0) == 0, "pipe: poll says a full pipe isn't writable");-    c.check(read(r, &mut buf) == 4096, "pipe: then reading works");-    c.check(poll(&mut p, 0) == 1 && p[0].revents & POLLOUT != 0, "pipe: room again -> POLLOUT");-    close(w);-    while read(r, &mut buf) > 0 {}-    c.check(read(r, &mut buf) == 0, "pipe: writer gone -> end of file, not EAGAIN");-    close(r);--    // fcntl and FIONBIO switch it on and off.-    let (r, w) = pipe().unwrap();-    c.check(fcntl(r, F_GETFL, 0) as u64 & O_NONBLOCK == 0, "fcntl F_GETFL: blocking by default");-    set_nonblocking(r, true);-    c.check(fcntl(r, F_GETFL, 0) as u64 & O_NONBLOCK != 0, "fcntl F_SETFL O_NONBLOCK sticks");-    c.check(read(r, &mut buf) == -EAGAIN, "fcntl-made nonblocking pipe: EAGAIN");-    let off = 0i32;-    ioctl(r, FIONBIO, &off as *const i32 as u64);-    c.check(fcntl(r, F_GETFL, 0) as u64 & O_NONBLOCK == 0, "ioctl FIONBIO 0 turns it off");-    close(r);-    close(w);--    // Sockets (over loopback).-    let l = socket(SOCK_STREAM, 0);-    bind(l, 7080);-    listen(l, 2);-    set_nonblocking(l, true);-    c.check(accept(l).0 == -EAGAIN, "socket: accept with nobody calling: EAGAIN");-    let s = socket(SOCK_STREAM | O_NONBLOCK, 0);-    let r = connect(s, Ip([127, 0, 0, 1]), 7080);-    c.check(r == 0 || r == -EINPROGRESS, "socket: nonblocking connect: EINPROGRESS (or done at once)");-    let mut p = [PollFd { fd: s as i32, events: POLLOUT, revents: 0 }];-    c.check(poll(&mut p, 2000) == 1 && p[0].revents & POLLOUT != 0, "socket: poll POLLOUT = connected");-    let mut p = [PollFd { fd: l as i32, events: POLLIN, revents: 0 }];-    c.check(poll(&mut p, 2000) == 1, "socket: listener POLLIN = a connection to accept");-    let (a, _) = accept(l);-    c.check(a >= 0, "socket: accept it");-    c.check(read(s, &mut buf) == -EAGAIN, "socket: recv with no data: EAGAIN");-    write(a, b"hi");-    let mut p = [PollFd { fd: s as i32, events: POLLIN, revents: 0 }];-    poll(&mut p, 2000);-    c.check(read(s, &mut buf) == 2, "socket: data after POLLIN");-    close(a);-    close(s);-    close(l);--    // A terminal: the slave side of a pty.-    if let Ok((m, n)) = openpty() {-        let slave = open(&alloc::format!("/dev/pts/{}", n), O_RDWR | O_NOCTTY | O_NONBLOCK);-        c.check(read(slave, &mut buf) == -EAGAIN, "tty: read with no line typed: EAGAIN");-        close(slave);-        close(m);-    } else {-        c.check(false, "openpty");-    }--    // Small extras libc needs: pread/pwrite, writev, clocks, nanosleep.-    let fd = open("/tmp/nbtest.txt", O_CREAT | O_TRUNC | O_RDWR);-    writev(fd, &[b"hello ", b"vectored ", b"world"]);-    pwrite(fd, b"HELLO", 0);-    let n = pread(fd, &mut buf, 6);-    c.check(&buf[..n.max(0) as usize] == b"vectored world", "writev + pread at an offset");-    c.check(lseek(fd, 0, SEEK_CUR) == 20, "pread/pwrite leave the file position alone");-    lseek(fd, 0, SEEK_SET);-    let n = read(fd, &mut buf);-    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 19"), "uname: Rustbelt, stage 19");--    let (secs, _) = clock_gettime(0);-    c.check(secs > 1_600_000_000, "CLOCK_REALTIME is a real date (after 2020)");-    let t0 = now_us();-    nanosleep_us(30_000);-    c.check(now_us() - t0 >= 25_000, "nanosleep 30 ms");-    let mut link = [0u8; 64];-    let n = readlink("/proc/self/exe", &mut link);-    c.check(n > 0 && link[..n as usize].ends_with(b"nbtest"), "readlink /proc/self/exe = this program");-    c.done()-}
import/kernel-user/overflow.rs
@@ -1,29 +0,0 @@-//! A buggy program: a function that calls itself forever. Every call needs-//! a little more stack, so the stack grows down until it runs into the-//! *guard zone* below it, which is never mapped. The CPU raises a page-//! fault there and the kernel kills just this program, saying-//! "killed: stack overflow", instead of letting the stack quietly grow over-//! other memory.-#![no_std]-#![no_main]--#[macro_use]-#[path = "ulib.rs"]-mod ulib;--/// Uses 1 KiB of stack per call, then calls itself again.-fn recurse(depth: u64) -> u64 {-    let mut buf = [0u8; 1024];-    // `black_box` stops the compiler from removing the array or turning-    // the recursion into a loop.-    buf[0] = depth as u8;-    let buf = core::hint::black_box(buf);-    recurse(depth + 1) + buf[0] as u64-}--fn main() -> i64 {-    println!("[overflow] pid {}: recursing until the stack runs out...", ulib::getpid());-    let n = recurse(0);-    println!("[overflow] returned {} -- this must never be printed!", n);-    1-}
import/kernel-user/pathtest.rs
@@ -1,177 +0,0 @@-//! pathtest -- Linux's path syscalls: C strings, `struct stat`, *at() (stage 15.1).-//!-//! Since 15.1 every path argument is what C passes: a pointer to a-//! NUL-terminated string (before, our own programs passed pointer +-//! length, which no libc can). The kernel copies it in carefully, so:-//!   - a NULL, kernel-space, or unmapped pointer is EFAULT (not a crash),-//!     also when the string runs off the end of a mapped page;-//!   - more than PATH_MAX (4096) bytes without a NUL is ENAMETOOLONG;-//!   - "" is ENOENT.-//! Then: stat/fstat/lstat/newfstatat fill in Linux's 144-byte `struct-//! stat`, and the *at() calls take paths relative to a directory fd.-//! Exit code = number of failed checks.-#![no_std]-#![no_main]--#[macro_use]-#[path = "ulib.rs"]-mod ulib;--extern crate alloc;--use alloc::string::String;-use alloc::vec;-use ulib::*;--/// A kernel address: user code may never pass one of these.-const KERNEL_PTR: u64 = 0xffff_8000_0000_0000;--fn sys(nr: u64, a: u64, b: u64, c: u64) -> i64 {-    syscall3(nr, a, b, c)-}--fn main() -> i64 {-    let mut c = Checks::new("pathtest");-    let mut st = Stat::default();-    let stp = &mut st as *mut Stat as u64;--    // --- bad pointers: EFAULT, from every path call ----------------------------    for &(bad, what) in &[(0u64, "NULL"), (KERNEL_PTR, "a kernel address"), (0xf0_0000_0000, "an unmapped user address")] {-        let calls = [-            ("open", sys(SYS_OPEN, bad, 0, 0)),-            ("openat", syscall5(SYS_OPENAT, AT_FDCWD as u64, bad, 0, 0, 0)),-            ("stat", sys(SYS_STAT, bad, stp, 0)),-            ("lstat", sys(SYS_LSTAT, bad, stp, 0)),-            ("newfstatat", syscall5(SYS_NEWFSTATAT, AT_FDCWD as u64, bad, stp, 0, 0)),-            ("access", sys(SYS_ACCESS, bad, 0, 0)),-            ("mkdir", sys(SYS_MKDIR, bad, 0o755, 0)),-            ("rmdir", sys(SYS_RMDIR, bad, 0, 0)),-            ("unlink", sys(SYS_UNLINK, bad, 0, 0)),-            ("rename", sys(SYS_RENAME, bad, bad, 0)),-            ("chdir", sys(SYS_CHDIR, bad, 0, 0)),-            ("readlink", sys(SYS_READLINK, bad, stp, 64)),-            ("execve", sys(SYS_EXECVE, bad, 0, 0)),-        ];-        let failed: alloc::vec::Vec<&str> = calls.iter().filter(|(_, r)| *r != -EFAULT).map(|(n, _)| *n).collect();-        if !failed.is_empty() {-            println!("pathtest: not EFAULT for {}: {:?}", what, failed);-        }-        c.check(failed.is_empty(), &alloc::format!("13 path calls with {} as the path: EFAULT", what));-    }-    // A path that runs off the end of a mapped page into an unmapped one:-    // the kernel must notice at the page boundary.-    let two = mmap(0, 8192, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0) as u64;-    munmap(two + 4096, 4096);-    let tail = two + 4096 - 8;-    unsafe { core::ptr::copy_nonoverlapping(b"/tmp/abc".as_ptr(), tail as *mut u8, 8) }; // no NUL-    c.check(sys(SYS_STAT, tail, stp, 0) == -EFAULT, "a path running into an unmapped page: EFAULT");-    unsafe { *((two + 4095) as *mut u8) = 0 }; // now it ends just in time-    c.check(sys(SYS_STAT, tail, stp, 0) == -ENOENT, "the same path ending right at the page end: ENOENT");-    munmap(two, 4096);-    // Good path, bad `struct stat` pointer.-    c.check(sys(SYS_STAT, c"/".as_ptr() as u64, KERNEL_PTR, 0) == -EFAULT, "stat into a kernel address: EFAULT");-    c.check(sys(SYS_FSTAT, 0, 0, 0) == -EFAULT, "fstat into NULL: EFAULT");--    // --- lengths -------------------------------------------------------------------    let mut long = vec![b'a'; 5000]; // no NUL in the first 4096 bytes-    long.push(0);-    c.check(sys(SYS_STAT, long.as_ptr() as u64, stp, 0) == -ENAMETOOLONG, "no NUL within PATH_MAX: ENAMETOOLONG");-    // 4095 bytes + NUL is allowed (short components, so it's just missing).-    let mut max = String::new();-    while max.len() < 4095 {-        max.push_str("/d");-    }-    max.truncate(4095);-    c.check(stat(&max, &mut st) == -ENOENT, "a 4095-byte path is accepted (ENOENT)");-    c.check(sys(SYS_STAT, c"".as_ptr() as u64, stp, 0) == -ENOENT, "an empty path: ENOENT");--    // --- Linux's struct stat -----------------------------------------------------    c.check(core::mem::size_of::<Stat>() == 144, "struct stat is 144 bytes");-    c.check(stat("/mnt/disk0/bin", &mut st) == 0 && st.is_dir() && st.nlink == 2, "a directory: S_IFDIR, 2 links");-    let r = stat("/mnt/disk0/bin/pathtest", &mut st);-    let ino = st.ino;-    c.check(-        r == 0 && st.is_file() && st.size > 0 && st.blksize == 4096 && st.blocks == (st.size + 511) / 512 && st.ino != 0,-        "a file: S_IFREG, size, blksize 4096, 512-byte blocks, an inode number",-    );-    c.check(st.uid == 0 && st.gid == 0 && st.perm() != 0, "owned by root, with permission bits");-    let fd = open("/mnt/disk0/bin/pathtest", O_RDONLY);-    let mut st2 = Stat::default();-    c.check(fstat(fd, &mut st2) == 0 && st2.ino == ino && st2.size == st.size, "fstat on it: same inode and size");-    let mut st3 = Stat::default();-    c.check(fstatat(fd, "", &mut st3, AT_EMPTY_PATH) == 0 && st3.ino == ino, "newfstatat(fd, \"\", AT_EMPTY_PATH) = fstat");-    c.check(fstatat(fd, "x", &mut st3, 0) == -ENOTDIR, "a file as dirfd: ENOTDIR");-    close(fd);-    c.check(fstat(0, &mut st) == 0 && st.is_char_device() && isatty(0), "stdin: a character device and a tty");-    let (r, w) = pipe().unwrap();-    c.check(fstat(r, &mut st) == 0 && st.file_type() == S_IFIFO, "a pipe: S_IFIFO");-    close(r);-    close(w);--    // Symlinks: stat follows, lstat doesn't (on the ext2 disk, if it's there).-    let link = (0..4).map(|i| alloc::format!("/mnt/disk{}/hello-link", i)).find(|p| lstat(p, &mut st) == 0);-    if let Some(link) = link {-        c.check(lstat(&link, &mut st) == 0 && st.is_symlink() && st.size == 9, "lstat on a symlink: S_IFLNK, size = target length");-        c.check(stat(&link, &mut st) == 0 && st.is_file(), "stat on it: the file it points to");-        let mut buf = [0u8; 64];-        let n = readlink(&link, &mut buf);-        c.check(n == 9 && &buf[..9] == b"hello.txt", "readlink: the target, no NUL");-        let dir = &link[..link.rfind('/').unwrap()];-        let dfd = open(dir, O_RDONLY | O_DIRECTORY);-        let n = syscall5(SYS_READLINKAT, dfd as u64, c"hello-link".as_ptr() as u64, buf.as_mut_ptr() as u64, 64, 0);-        c.check(n == 9, "readlinkat(dirfd, \"hello-link\")");-        c.check(fstatat(dfd, "hello-link", &mut st, AT_SYMLINK_NOFOLLOW) == 0 && st.is_symlink(), "newfstatat AT_SYMLINK_NOFOLLOW = lstat");-        close(dfd);-    } else {-        println!("pathtest: no ext2 disk with hello-link: skipped symlink checks");-    }-    c.check(readlink("/mnt/disk0/bin", &mut [0u8; 8]) == -EINVAL, "readlink on a non-link: EINVAL");--    // --- open flags -----------------------------------------------------------------    c.check(open("/mnt/disk0/bin/pathtest", O_RDONLY | O_DIRECTORY) == -ENOTDIR, "O_DIRECTORY on a file: ENOTDIR");-    c.check(open("/tmp", O_WRONLY) == -EISDIR, "a directory opened for writing: EISDIR");-    let fd = open("/tmp/pt-excl", O_WRONLY | O_CREAT | O_EXCL);-    c.check(fd >= 0 && open("/tmp/pt-excl", O_WRONLY | O_CREAT | O_EXCL) == -EEXIST, "O_CREAT|O_EXCL: creates, then EEXIST");-    close(fd);-    c.check(access("/tmp/pt-excl") == 0 && access("/tmp/nope") == -ENOENT, "access: 0 / ENOENT");--    // --- *at() relative to a directory fd -------------------------------------------    c.check(mkdir("/tmp/ptdir") == 0, "mkdir /tmp/ptdir");-    let dfd = open("/tmp/ptdir", O_RDONLY | O_DIRECTORY);-    c.check(dfd >= 0, "open a directory with O_DIRECTORY");-    let fd = openat(dfd, "inner.txt", O_WRONLY | O_CREAT);-    c.check(fd >= 0 && write(fd, b"12345") == 5, "openat(dirfd, \"inner.txt\", O_CREAT) + write");-    close(fd);-    c.check(stat("/tmp/ptdir/inner.txt", &mut st) == 0 && st.size == 5, "...it's in that directory");-    c.check(openat(AT_FDCWD, "/tmp/ptdir/inner.txt", O_RDONLY) >= 0, "openat(AT_FDCWD, absolute)");-    c.check(openat(99, "inner.txt", O_RDONLY) == -EBADF, "openat with a bad dirfd: EBADF");-    c.check(openat(99, "/tmp/ptdir/inner.txt", O_RDONLY) >= 0, "...unless the path is absolute");-    c.check(syscall5(SYS_MKDIRAT, dfd as u64, c"sub".as_ptr() as u64, 0o755, 0, 0) == 0, "mkdirat(dirfd, \"sub\")");-    c.check(sys(SYS_FACCESSAT, dfd as u64, c"sub".as_ptr() as u64, 0) == 0, "faccessat(dirfd, \"sub\")");-    c.check(syscall5(SYS_UNLINKAT, dfd as u64, c"sub".as_ptr() as u64, 0, 0, 0) == -EISDIR, "unlinkat on a directory without AT_REMOVEDIR: EISDIR");-    c.check(syscall5(SYS_UNLINKAT, dfd as u64, c"sub".as_ptr() as u64, AT_REMOVEDIR, 0, 0) == 0, "unlinkat(AT_REMOVEDIR)");-    let r = syscall5(SYS_RENAMEAT, dfd as u64, c"inner.txt".as_ptr() as u64, AT_FDCWD as u64, c"/tmp/renamed.txt".as_ptr() as u64, 0);-    c.check(r == 0 && access("/tmp/ptdir/inner.txt") == -ENOENT, "renameat(dirfd, \"inner.txt\", AT_FDCWD, \"/tmp/renamed.txt\")");-    // fchdir: cd to the directory we have open.-    c.check(sys(SYS_FCHDIR, dfd as u64, 0, 0) == 0 && stat("../renamed.txt", &mut st) == 0 && st.size == 5, "fchdir(dirfd), then a relative path");-    chdir("/");-    close(dfd);--    // --- rename ------------------------------------------------------------------------    let fd = open("/tmp/pt-new", O_WRONLY | O_CREAT);-    write(fd, b"new contents");-    close(fd);-    c.check(rename("/tmp/pt-new", "/tmp/renamed.txt") == 0, "rename a file over another");-    c.check(stat("/tmp/renamed.txt", &mut st) == 0 && st.size == 12 && access("/tmp/pt-new") == -ENOENT, "...it replaced it");-    c.check(rename("/tmp/renamed.txt", "/mnt/disk0/x.txt") == -EXDEV, "rename across file systems: EXDEV");-    c.check(rename("/tmp/ptdir", "/tmp/ptdir2") == 0 && stat("/tmp/ptdir2", &mut st) == 0 && st.is_dir(), "rename an empty directory");-    c.check(rename("/tmp/ptdir2", "/tmp/ptdir2/in") == -EINVAL, "rename a directory into itself: EINVAL");-    c.check(rename("/tmp/nope", "/tmp/x") == -ENOENT, "rename a missing file: ENOENT");-    unlink("/tmp/renamed.txt");-    unlink("/tmp/pt-excl");-    rmdir("/tmp/ptdir2");--    // --- small things musl asks at startup -------------------------------------------    c.check(sys(SYS_GETUID, 0, 0, 0) == 0, "getuid: 0 (root)");-    c.done()-}
import/kernel-user/piehello.c
@@ -1,65 +0,0 @@-/*- * piehello.c -- stage 19: "hello" as a static PIE, built with the stock- * `musl-gcc -static-pie` (no special flags). The file's addresses start- * at 0; the kernel loads it at a random base and tells us where through- * the auxiliary vector. musl's start-up code (rcrt1.o) then fixes up the- * program's own pointers before main runs.- *- * Exit code = number of failed checks. `piehello where` prints the load- * address of main (tests/static_pie.rs runs it twice: different bases).- */-#define _GNU_SOURCE-#include <elf.h>-#include <link.h>-#include <stdio.h>-#include <string.h>-#include <sys/auxv.h>--static int fails;-#define CHECK(cond, what)                                    \-	do {                                                 \-		if (!(cond)) {                               \-			fails++;                             \-			printf("piehello: FAIL %s\n", what); \-		}                                            \-	} while (0)--/* Pointers in data: these need fixing up (relocating) at start-up. */-static const char *greeting = "hello from a static PIE";-static int (*say)(const char *) = puts;--extern char _start[];-extern const Elf64_Ehdr __ehdr_start; /* the linker's name for our ELF header */--int main(int argc, char **argv)-{-	if (argc > 1 && strcmp(argv[1], "where") == 0) {-		printf("%p\n", (void *)main);-		return 0;-	}-	say(greeting);-	unsigned long phdr = getauxval(AT_PHDR), entry = getauxval(AT_ENTRY);-	CHECK(getauxval(AT_BASE) == 0, "AT_BASE is 0 (no dynamic loader)");-	CHECK(entry == (unsigned long)_start, "AT_ENTRY is where _start is");-	CHECK(getauxval(AT_PHENT) == sizeof(Elf64_Phdr), "AT_PHENT");-	/* Our ELF header is loaded too, at the load bias (the file's lowest-	 * address is 0), and the program headers follow it, where AT_PHDR says. */-	unsigned long bias = (unsigned long)&__ehdr_start;-	CHECK(phdr == bias + __ehdr_start.e_phoff, "AT_PHDR points at the program headers");-	const Elf64_Phdr *ph = (const Elf64_Phdr *)phdr;-	int has_tls = 0, has_relro = 0, has_interp = 0;-	for (unsigned long i = 0; i < getauxval(AT_PHNUM); i++) {-		has_tls |= ph[i].p_type == PT_TLS;-		has_relro |= ph[i].p_type == PT_GNU_RELRO;-		has_interp |= ph[i].p_type == PT_INTERP;-	}-	CHECK(bias != 0 && bias % 4096 == 0, "a page-aligned, non-zero load bias");-	CHECK(bias >= 0x8000000000UL, "loaded in Rustbelt's user space");-	CHECK(has_relro && !has_interp, "PT_GNU_RELRO, no PT_INTERP");-	(void)has_tls;-	/* The relocated pointers point into the loaded image. */-	CHECK((unsigned long)greeting > bias, "relocated string pointer");-	/* RELRO: musl made it read-only after relocating; mprotect worked. */-	printf("argv[0] = %s, %d argument(s), bias %#lx\n", argv[0], argc, bias);-	return fails;-}
import/kernel-user/piethreads.c
@@ -1,122 +0,0 @@-/*- * piethreads.c -- stage 19: a static PIE (stock `musl-gcc -static-pie`)- * using pthreads, thread-local storage (PT_TLS), mutexes and stdio, plus- * musl's ttyname() on a pseudo-terminal. Exit code = failed checks.- */-#define _GNU_SOURCE-#include <errno.h>-#include <fcntl.h>-#include <pthread.h>-#include <stdio.h>-#include <stdlib.h>-#include <string.h>-#include <sys/stat.h>-#include <sys/sysmacros.h>-#include <time.h>-#include <unistd.h>--static int fails;-#define CHECK(cond, ...)                                     \-	do {                                                 \-		if (!(cond)) {                               \-			fails++;                             \-			printf("piethreads: FAIL ");         \-			printf(__VA_ARGS__);                 \-			printf("\n");                        \-		}                                            \-	} while (0)--#define THREADS 4-#define ROUNDS 10000--static __thread int mine = 7;        /* PT_TLS: initialized ... */-static __thread char scratch[64];    /* ... and zeroed (.tbss) */-static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;-static long shared;-static FILE *out;--static void *worker(void *arg)-{-	long id = (long)arg;-	if (mine != 7 || scratch[0] != 0)-		return (void *)1; /* every thread starts with a fresh TLS copy */-	mine = id;-	snprintf(scratch, sizeof scratch, "thread %ld", id);-	for (int i = 0; i < ROUNDS; i++) {-		pthread_mutex_lock(&lock);-		shared++;-		pthread_mutex_unlock(&lock);-	}-	pthread_mutex_lock(&lock);-	fprintf(out, "%s done\n", scratch);-	pthread_mutex_unlock(&lock);-	/* Nobody else changed our copy meanwhile. */-	return (void *)(long)(mine != id);-}--static void threads(void)-{-	const char *path = "/tmp/piethreads.txt";-	out = fopen(path, "w");-	CHECK(out != NULL, "fopen: errno %d", errno);-	if (!out)-		return;-	pthread_t t[THREADS];-	for (long i = 0; i < THREADS; i++)-		CHECK(pthread_create(&t[i], NULL, worker, (void *)i) == 0, "pthread_create %ld", i);-	for (int i = 0; i < THREADS; i++) {-		void *r;-		pthread_join(t[i], &r);-		CHECK(r == NULL, "thread %d saw wrong TLS", i);-	}-	CHECK(shared == (long)THREADS * ROUNDS, "counter %ld", shared);-	CHECK(mine == 7, "main thread's TLS changed");-	fclose(out);-	/* Read the file back through stdio: one line per thread. */-	FILE *in = fopen(path, "r");-	char line[64];-	int lines = 0;-	while (in && fgets(line, sizeof line, in))-		lines += strstr(line, " done\n") != NULL;-	if (in)-		fclose(in);-	CHECK(lines == THREADS, "%d lines in the file", lines);-	unlink(path);-}--/* musl's ttyname: readlink /proc/self/fd/N, then stat() that name and- * fstat() the fd -- both must give the same device and inode (stage 19). */-static void tty_names(void)-{-	int master = posix_openpt(O_RDWR | O_NOCTTY);-	CHECK(master >= 0, "posix_openpt: errno %d", errno);-	if (master < 0)-		return;-	CHECK(grantpt(master) == 0 && unlockpt(master) == 0, "grantpt/unlockpt");-	char *name = ptsname(master);-	CHECK(name && strncmp(name, "/dev/pts/", 9) == 0, "ptsname %s", name ? name : "(null)");-	int slave = name ? open(name, O_RDWR | O_NOCTTY) : -1;-	CHECK(slave >= 0, "open %s: errno %d", name, errno);-	if (slave >= 0) {-		char *tn = ttyname(slave);-		CHECK(tn && strcmp(tn, name) == 0, "ttyname(slave) = %s, errno %d", tn ? tn : "(null)", errno);-		struct stat st;-		CHECK(fstat(slave, &st) == 0 && S_ISCHR(st.st_mode) && major(st.st_rdev) == 136, "pts rdev");-		close(slave);-	}-	char *tm = ttyname(master);-	CHECK(tm && strcmp(tm, "/dev/ptmx") == 0, "ttyname(master) = %s", tm ? tm : "(null)");-	close(master);-	if (isatty(0)) {-		char *t0 = ttyname(0);-		CHECK(t0 != NULL, "ttyname(0): errno %d", errno);-	}-}--int main(void)-{-	threads();-	tty_names();-	printf("piethreads: %d failed\n", fails);-	return fails;-}
import/kernel-user/ptytest.rs
@@ -1,213 +0,0 @@-//! ptytest -- checks pseudo-terminals (ptys) and the line discipline.-//!-//! A pty is a pair: the *master* side is held by a terminal emulator (or-//! ssh, tmux, a compositor's terminal window), the *slave* side-//! (/dev/pts/N) looks exactly like a real terminal to the program running-//! on it. Between them sits the line discipline: in *canonical* mode it-//! collects a whole line (with backspace editing) before the program can-//! read it, echoes what's typed, turns ^C into SIGINT; in *raw* mode bytes-//! pass straight through (what editors and games use).-#![no_std]-#![no_main]--#[macro_use]-#[path = "ulib.rs"]-mod ulib;--extern crate alloc;--use alloc::format;-use alloc::string::String;-use ulib::*;--/// Read whatever the other side has (waits up to 1 s for the first byte).-fn read_some(fd: i64, buf: &mut [u8]) -> usize {-    let mut p = [PollFd { fd: fd as i32, events: POLLIN, revents: 0 }];-    if poll(&mut p, 1000) <= 0 {-        return 0;-    }-    read(fd, buf).max(0) as usize-}--/// musl's ttyname(fd), step by step (stage 19): the name is what-/// /proc/self/fd/N points to, but only if stat() of that name is the same-/// file (st_dev and st_ino) as fstat(fd). Otherwise ENODEV.-fn ttyname(fd: i64) -> Result<String, i64> {-    let mut buf = [0u8; 64];-    let n = readlink(&format!("/proc/self/fd/{}", fd), &mut buf);-    if n < 0 {-        return Err(n);-    }-    let name = String::from(core::str::from_utf8(&buf[..n as usize]).map_err(|_| -EINVAL)?);-    let (mut a, mut b) = (Stat::default(), Stat::default());-    if stat(&name, &mut a) != 0 || fstat(fd, &mut b) != 0 {-        return Err(-ENOENT);-    }-    if a.dev != b.dev || a.ino != b.ino {-        return Err(-ENODEV);-    }-    Ok(name)-}--/// Linux's device numbers (`makedev`).-fn makedev(major: u64, minor: u64) -> u64 {-    ((major & 0xffff_f000) << 32) | ((major & 0xfff) << 8) | ((minor & 0xffff_ff00) << 12) | (minor & 0xff)-}--/// Every /dev node has one identity: stat twice, or stat and fstat of an-/// open fd, agree; st_rdev and the mode are Linux's.-fn device_identities(c: &mut Checks, master: i64, slave: i64, n: u32) {-    for (path, major, minor, perm) in [-        ("/dev/null", 1, 3, 0o666),-        ("/dev/zero", 1, 5, 0o666),-        ("/dev/random", 1, 8, 0o666),-        ("/dev/urandom", 1, 9, 0o666),-        ("/dev/tty", 5, 0, 0o666),-        ("/dev/console", 5, 1, 0o600),-        ("/dev/ptmx", 5, 2, 0o666),-    ] {-        let (mut a, mut b, mut f) = (Stat::default(), Stat::default(), Stat::default());-        stat(path, &mut a);-        stat(path, &mut b);-        let fd = if path == "/dev/tty" { -1 } else { open(path, O_RDWR | O_NOCTTY) };-        let fd_same = fd < 0 || (fstat(fd, &mut f) == 0 && f.dev == a.dev && f.ino == a.ino && f.rdev == a.rdev && f.mode == a.mode);-        if fd >= 0 {-            close(fd); // (for /dev/ptmx: a new pty, gone again)-        }-        c.check(-            a.ino == b.ino && a.dev == b.dev && a.rdev == makedev(major, minor) && a.mode == S_IFCHR | perm && fd_same,-            &format!("{}: char device {}:{}, mode {:o}, one inode for stat and fstat", path, major, minor, perm),-        );-    }-    let (mut a, mut m) = (Stat::default(), Stat::default());-    stat(&format!("/dev/pts/{}", n), &mut a);-    c.check(a.rdev == makedev(136, n as u64) && a.mode == S_IFCHR | 0o620, "/dev/pts/N: char device 136:N, mode 620");-    stat("/dev/ptmx", &mut a);-    fstat(master, &mut m);-    c.check(m.ino == a.ino && m.rdev == makedev(5, 2), "a pty master fstats as /dev/ptmx");-    c.check(ttyname(slave) == Ok(format!("/dev/pts/{}", n)), "ttyname(pty slave) = /dev/pts/N");-    let console = open("/dev/console", O_RDWR | O_NOCTTY);-    c.check(ttyname(console).as_deref() == Ok("/dev/console"), "ttyname(console fd) = /dev/console");-    close(console);-    // The console fds the kernel gave us (if stdin is the console).-    if isatty(0) {-        c.check(ttyname(0).is_ok(), "ttyname(0) works for the console the kernel handed us");-    }-    let mut buf = [0u8; 64];-    let r = readlink("/proc/self/cwd", &mut buf);-    let mut cwd = [0u8; 64];-    let k = getcwd(&mut cwd);-    c.check(r > 0 && r == k && buf[..r as usize] == cwd[..k as usize], "/proc/self/cwd links to the current directory");-}--fn main() -> i64 {-    let mut c = Checks::new("ptytest");-    let (master, n) = match openpty() {-        Ok(x) => x,-        Err(e) => {-            println!("ptytest: openpty: {}", strerror(e));-            return 1;-        }-    };-    let path = format!("/dev/pts/{}", n);-    let slave = open(&path, O_RDWR | O_NOCTTY);-    c.check(slave >= 0, &format!("open {} (the slave side)", path));-    device_identities(&mut c, master, slave, n);-    let mut buf = [0u8; 64];--    // Canonical mode: a line is readable only once it's complete.-    write(master, b"hel");-    set_nonblocking(slave, true);-    c.check(read(slave, &mut buf) == -EAGAIN, "canonical: half a line can't be read yet (EAGAIN)");-    write(master, b"lo\x7f\x7fp me\r"); // two backspaces, then Enter ('\r')-    let got = read_some(slave, &mut buf);-    c.check(&buf[..got] == b"help me\n", "canonical: backspace edits the line, Enter ('\\r') ends it as '\\n'");-    let got = read_some(master, &mut buf);-    c.check(got > 0 && buf[..got].ends_with(b"\r\n"), "echo: what's typed comes back to the master");--    // Output processing: the program's '\n' reaches the terminal as "\r\n".-    write(slave, b"out\n");-    let got = read_some(master, &mut buf);-    c.check(&buf[..got] == b"out\r\n", "output: '\\n' becomes \"\\r\\n\" (ONLCR)");--    // Raw mode: every byte at once, no echo, no conversions.-    let saved = tcgetattr(slave).unwrap_or_default();-    let mut raw = saved;-    cfmakeraw(&mut raw);-    c.check(tcsetattr(slave, &raw) == 0, "TCSETS: switch to raw mode");-    write(master, b"x\r\x03");-    let got = read_some(slave, &mut buf);-    c.check(&buf[..got] == b"x\r\x03", "raw: bytes arrive at once, unchanged (even ^C)");-    set_nonblocking(master, true);-    c.check(read(master, &mut buf) == -EAGAIN, "raw: no echo");-    set_nonblocking(master, false);-    tcsetattr(slave, &saved);-    c.check(tcgetattr(slave).map(|t| t.lflag & ICANON != 0).unwrap_or(false), "TCGETS: canonical mode back on");-    // (We keep `slave` open: with no slave open at all, the master reports-    // a hang-up (POLLHUP) instead of waiting for the child to open it.)--    // ^C: a child makes the pty its controlling terminal (setsid + open)-    // and waits; typing ^C on the master must kill it with SIGINT.-    let pid = fork();-    if pid == 0 {-        setsid();-        let fd = open(&path, O_RDWR); // session leader: becomes our ctty-        if fd < 0 {-            exit(3);-        }-        // /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();-        }-    }-    let got = read_some(master, &mut buf); // "ready\r\n"-    c.check(&buf[..got] == b"ready\r\n", "child opened the pty as its controlling terminal");-    write(master, b"\x03");-    let status = match wait4(pid, 0) {-        Ok((_, s)) => s,-        Err(_) => 0,-    };-    c.check(wifsignaled(status) && wtermsig(status) == SIGINT, "^C on the master: SIGINT for the foreground job");-    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");-    close(slave);-    c.done()-}
import/kernel-user/rawabi.c
@@ -1,274 +0,0 @@-/*- * rawabi.c - a tiny static Linux program with no libc at all (stage 15.1).- *- * This file knows nothing about Rustbelt: it is written against the plain- * Linux x86_64 system call ABI (numbers, argument registers, C strings,- * struct layouts) the way musl's own syscall layer is. If it passes here- * and on a real Linux machine, musl's startup code and its file calls- * have a good chance of working too.- *- * Build (build.rs does this with the host's gcc):- *   cc -O2 -static -nostdlib -ffreestanding -fno-stack-protector -fpie -no-pie- *      -Wl,-Ttext-segment=0x8000400000 -o rawabi rawabi.c- * -fpie makes the code RIP-relative, so it works at Rustbelt's user base- * (0x80_0000_0000, above 4 GiB) while staying an ordinary ET_EXEC file.- *- * Run: rawabi [scratch-dir]   (default /tmp). Exit status = failed checks.- */--typedef unsigned long u64;-typedef long i64;--/* ---- raw system calls: number in rax, args in rdi rsi rdx r10 r8 r9 ---- */--static i64 sys6(i64 n, i64 a, i64 b, i64 c, i64 d, i64 e, i64 f) {-    register i64 r10 __asm__("r10") = d;-    register i64 r8 __asm__("r8") = e;-    register i64 r9 __asm__("r9") = f;-    i64 ret;-    __asm__ volatile("syscall"-                     : "=a"(ret)-                     : "a"(n), "D"(a), "S"(b), "d"(c), "r"(r10), "r"(r8), "r"(r9)-                     : "rcx", "r11", "memory");-    return ret;-}-#define sys0(n) sys6(n, 0, 0, 0, 0, 0, 0)-#define sys1(n, a) sys6(n, (i64)(a), 0, 0, 0, 0, 0)-#define sys2(n, a, b) sys6(n, (i64)(a), (i64)(b), 0, 0, 0, 0)-#define sys3(n, a, b, c) sys6(n, (i64)(a), (i64)(b), (i64)(c), 0, 0, 0)-#define sys4(n, a, b, c, d) sys6(n, (i64)(a), (i64)(b), (i64)(c), (i64)(d), 0, 0)--/* Linux x86_64 syscall numbers (arch/x86/entry/syscalls/syscall_64.tbl). */-enum {-    NR_read = 0, NR_write = 1, NR_close = 3, NR_stat = 4, NR_fstat = 5,-    NR_lseek = 8, NR_mmap = 9, NR_munmap = 11, NR_brk = 12,-    NR_rt_sigaction = 13, NR_rt_sigprocmask = 14, NR_ioctl = 16,-    NR_writev = 20, NR_access = 21, NR_getpid = 39, NR_fork = 57,-    NR_exit = 60, NR_wait4 = 61, NR_kill = 62, NR_uname = 63,-    NR_getcwd = 79, NR_chdir = 80, NR_rename = 82, NR_mkdir = 83,-    NR_rmdir = 84, NR_unlink = 87, NR_getuid = 102, NR_arch_prctl = 158,-    NR_getdents64 = 217, NR_set_tid_address = 218, NR_clock_gettime = 228,-    NR_clock_nanosleep = 230, NR_exit_group = 231, NR_openat = 257,-    NR_newfstatat = 262, NR_unlinkat = 263,-};--#define AT_FDCWD (-100)-#define AT_REMOVEDIR 0x200-#define O_RDONLY 0-#define O_WRONLY 1-#define O_CREAT 0100-#define O_TRUNC 01000-#define O_DIRECTORY 0200000-#define ENOENT 2-#define EFAULT 14-#define SIGUSR1 10-#define SA_RESTORER 0x04000000-#define ARCH_SET_FS 0x1002-#define S_IFMT 0170000-#define S_IFREG 0100000-#define S_IFDIR 0040000--/* Linux's x86_64 `struct stat` (144 bytes). */-struct kstat {-    u64 dev, ino, nlink;-    unsigned mode, uid, gid, pad0;-    u64 rdev;-    i64 size, blksize, blocks;-    i64 atime, atime_ns, mtime, mtime_ns, ctime, ctime_ns;-    i64 unused[3];-};-struct timespec { i64 sec, nsec; };-struct iovec { const void *base; u64 len; };-struct utsname { char f[6][65]; };          /* sysname, nodename, release, ... */-struct kaction { void (*handler)(int); u64 flags; void (*restorer)(void); u64 mask; };--/* gcc may emit calls to these even with -ffreestanding. */-void *memset(void *d, int c, u64 n) { char *p = d; while (n--) *p++ = (char)c; return d; }-void *memcpy(void *d, const void *s, u64 n) { char *p = d; const char *q = s; while (n--) *p++ = *q++; return d; }--/* ---- tiny output helpers ---- */--static u64 slen(const char *s) { u64 n = 0; while (s[n]) n++; return n; }-static void puts_(const char *s) { sys3(NR_write, 1, s, slen(s)); }-static void putu(u64 v) {-    char b[24]; int i = 23; b[i] = 0;-    do { b[--i] = (char)('0' + v % 10); v /= 10; } while (v);-    puts_(b + i);-}-static int seq(const char *a, const char *b) { while (*a && *a == *b) a++, b++; return *a == *b; }-/* Join dir and name into out (no bounds games: test names are short). */-static const char *join(char *out, const char *dir, const char *name) {-    char *p = out;-    while (*dir) *p++ = *dir++;-    *p++ = '/';-    while (*name) *p++ = *name++;-    *p = 0;-    return out;-}--static int failures;-static void check(int ok, const char *what) {-    puts_(ok ? "rawabi: ok   " : "rawabi: FAIL ");-    puts_(what);-    puts_("\n");-    if (!ok) failures++;-}--/* ---- SSE math: plain C doubles compile to SSE2 instructions on x86_64 ---- */--/* pi from the Leibniz series; `volatile` keeps the loop in xmm registers- * doing real work instead of being folded at compile time. */-static double leibniz_pi(int terms) {-    volatile double sum = 0.0;-    for (int k = 0; k < terms; k++)-        sum += (k & 1 ? -4.0 : 4.0) / (2.0 * k + 1.0);-    return sum;-}-static double sqrt_sd(double x) { double r; __asm__("sqrtsd %1, %0" : "=x"(r) : "x"(x)); return r; }-/* Print a double with 6 decimals using integer math only. */-static void putf(double v) {-    if (v < 0) { puts_("-"); v = -v; }-    u64 whole = (u64)v, frac = (u64)((v - (double)whole) * 1e6 + 0.5);-    if (frac >= 1000000) { whole++; frac -= 1000000; }-    putu(whole); puts_(".");-    for (u64 d = 100000; d > frac && d > 1; d /= 10) puts_("0");-    putu(frac);-}--/* ---- a signal handler that does its own SSE math ---- */--static volatile int got_signal;-static volatile double handler_result;-static void on_usr1(int sig) {-    /* Clobbers xmm registers the interrupted code may be using; the kernel-     * must save them in the signal frame and put them back at sigreturn. */-    handler_result = sqrt_sd(16.0) * sqrt_sd(0.25);   /* = 2, exactly */-    got_signal = sig;-}-/* The restorer: what the handler "returns" into. musl uses the same. */-__asm__(".text\n.global rawabi_restorer\nrawabi_restorer:\n"-        "  mov $15, %eax\n  syscall\n  hlt\n");-__attribute__((visibility("hidden"))) void rawabi_restorer(void);--static u64 tls_block[4];   /* a stand-in thread pointer, like musl's */--static int run(int argc, char **argv) {-    const char *dir = argc > 1 ? argv[1] : "/tmp";-    char work[256], f1[256], f2[256], cwd[256];--    puts_("rawabi: a static Linux binary with no libc, pid ");-    putu((u64)sys0(NR_getpid));-    puts_("\n");--    /* What musl's __init_tls and __init_libc do first. */-    tls_block[0] = (u64)tls_block;  /* x86_64 TLS: fs:0 points to itself */-    i64 r = sys2(NR_arch_prctl, ARCH_SET_FS, tls_block);-    u64 self;-    __asm__ volatile("mov %%fs:0, %0" : "=r"(self));-    check(r == 0 && self == (u64)tls_block, "arch_prctl(ARCH_SET_FS) + %fs:0");-    check(sys1(NR_set_tid_address, &tls_block[1]) == sys0(NR_getpid), "set_tid_address returns the tid");-    u64 oldmask = ~0ul;-    check(sys4(NR_rt_sigprocmask, 0, 0, &oldmask, 8) == 0 && oldmask == 0, "rt_sigprocmask(.., oldset, 8)");-    i64 uid = sys0(NR_getuid);   /* 0 on Rustbelt (no users yet) */-    check(uid >= 0, "getuid");--    struct utsname u;-    check(sys1(NR_uname, &u) == 0 && seq(u.f[0], "Rustbelt") | seq(u.f[0], "Linux"), "uname");-    puts_("rawabi: uname: "); puts_(u.f[0]); puts_(" "); puts_(u.f[2]); puts_("\n");--    /* Memory: brk and an anonymous mmap, both written to. */-    u64 cur = (u64)sys1(NR_brk, 0);-    u64 grown = (u64)sys1(NR_brk, cur + 65536);-    if (grown == cur + 65536) ((volatile char *)cur)[65535] = 1;-    check(grown == cur + 65536, "brk grows the heap");-    char *m = (char *)sys6(NR_mmap, 0, 8192, 3, 0x22, -1, 0); /* RW, PRIVATE|ANON */-    if ((u64)m < (u64)-4096) m[8191] = 7;-    check((u64)m < (u64)-4096 && sys2(NR_munmap, m, 8192) == 0, "mmap/munmap anonymous");--    /* Files, with C-string paths and the *at() calls musl uses. */-    join(work, dir, "rawabi.d");-    join(f1, work, "a.txt");-    join(f2, work, "b.txt");-    sys1(NR_unlink, f1); sys1(NR_unlink, f2); sys1(NR_rmdir, work);   /* leftovers */-    check(sys2(NR_mkdir, work, 0755) == 0, "mkdir");-    int fd = (int)sys4(NR_openat, AT_FDCWD, f1, O_WRONLY | O_CREAT | O_TRUNC, 0644);-    struct iovec iov[2] = { { "hello ", 6 }, { "from rawabi\n", 12 } };-    check(fd >= 0 && sys3(NR_writev, fd, iov, 2) == 18, "openat(AT_FDCWD, O_CREAT) + writev");-    sys1(NR_close, fd);--    struct kstat st;-    check(sys2(NR_stat, f1, &st) == 0 && (st.mode & S_IFMT) == S_IFREG && st.size == 18, "stat: regular file, 18 bytes");-    check(sys4(NR_newfstatat, AT_FDCWD, work, &st, 0) == 0 && (st.mode & S_IFMT) == S_IFDIR, "newfstatat: directory");-    check(sys2(NR_access, f1, 4) == 0 && sys2(NR_access, f2, 0) == -ENOENT, "access: R_OK / ENOENT");-    check(sys2(NR_stat, (char *)0, &st) == -EFAULT, "stat(NULL) = -EFAULT");--    check(sys2(NR_rename, f1, f2) == 0 && sys2(NR_stat, f1, &st) == -ENOENT, "rename");-    char buf[64];-    fd = (int)sys4(NR_openat, AT_FDCWD, f2, O_RDONLY, 0);-    i64 n = sys3(NR_read, fd, buf, sizeof buf);-    struct kstat fst;-    check(n == 18 && buf[0] == 'h' && buf[17] == '\n', "read back the renamed file");-    check(sys2(NR_fstat, fd, &fst) == 0 && fst.size == 18 && sys3(NR_lseek, fd, 6, 0) == 6, "fstat + lseek");-    sys1(NR_close, fd);--    /* getdents64 on the directory finds b.txt. */-    fd = (int)sys4(NR_openat, AT_FDCWD, work, O_RDONLY | O_DIRECTORY, 0);-    char dents[512];-    int found = 0;-    n = sys3(NR_getdents64, fd, dents, sizeof dents);-    for (i64 off = 0; off < n;) {-        unsigned short reclen = *(unsigned short *)(dents + off + 16);-        if (seq(dents + off + 19, "b.txt")) found = 1;-        off += reclen;-    }-    sys1(NR_close, fd);-    check(found, "getdents64 lists b.txt");--    check(sys1(NR_chdir, work) == 0 && sys2(NR_getcwd, cwd, sizeof cwd) > 0 && seq(cwd, work), "chdir + getcwd");-    check(sys3(NR_unlinkat, AT_FDCWD, "b.txt", 0) == 0, "unlinkat(AT_FDCWD, relative path)");-    sys1(NR_chdir, dir);-    check(sys3(NR_unlinkat, AT_FDCWD, work, AT_REMOVEDIR) == 0, "unlinkat(AT_REMOVEDIR)");--    /* Time. */-    struct timespec t0, t1, nap = { 0, 20 * 1000 * 1000 };-    sys2(NR_clock_gettime, 1, &t0);-    i64 slept = sys4(NR_clock_nanosleep, 1, 0, &nap, 0);-    sys2(NR_clock_gettime, 1, &t1);-    i64 ms = (t1.sec - t0.sec) * 1000 + (t1.nsec - t0.nsec) / 1000000;-    check(slept == 0 && ms >= 19, "clock_nanosleep(CLOCK_MONOTONIC, 20 ms)");--    /* SSE math, then a signal whose handler clobbers xmm registers. */-    double pi = leibniz_pi(200000);-    double root = sqrt_sd(pi * pi);-    puts_("rawabi: pi ~ "); putf(pi); puts_(", sqrt(pi^2) = "); putf(root); puts_("\n");-    check(pi > 3.14158 && pi < 3.14160 && root == pi, "SSE double math");-    struct kaction sa = { on_usr1, SA_RESTORER, rawabi_restorer, 0 };-    check(sys4(NR_rt_sigaction, SIGUSR1, &sa, 0, 8) == 0, "rt_sigaction with SA_RESTORER");-    double before = pi * 2.0;          /* lives in an xmm register across... */-    sys2(NR_kill, sys0(NR_getpid), SIGUSR1);   /* ...the signal */-    double after = before / 2.0;-    check(got_signal == SIGUSR1 && handler_result == 2.0 && after == pi, "xmm state survives a signal handler");--    /* fork + wait4 with a rusage buffer, the way musl's waitpid calls it. */-    i64 pid = sys0(NR_fork);-    if (pid == 0) {-        double c = leibniz_pi(1000);   /* the child has its own FPU state */-        sys1(NR_exit, c > 3.0 ? 7 : 1);-    }-    int status = 0;-    char rusage[144];-    check(pid > 0 && sys4(NR_wait4, pid, &status, 0, rusage) == pid && ((status >> 8) & 0xff) == 7,-          "fork + wait4(pid, &status, 0, &rusage)");--    puts_(failures ? "rawabi: some checks FAILED\n" : "rawabi: all checks passed\n");-    return failures;-}--/* The ELF entry point. The kernel leaves argc at (%rsp), then argv[];- * the stack is 16-byte aligned here, so align it the C way and call run(). */-__attribute__((used)) static void start_c(u64 *sp) {-    int argc = (int)sp[0];-    sys1(NR_exit_group, run(argc, (char **)(sp + 1)));-}-__asm__(".text\n.global _start\n_start:\n"-        "  xor %ebp, %ebp\n  mov %rsp, %rdi\n  and $-16, %rsp\n  call start_c\n  hlt\n");
import/kernel-user/remaptest.c
@@ -1,221 +0,0 @@-/*- * remaptest.c -- stage 19: mremap, checked from musl.- *- * Built with `musl-gcc -static-pie` (see build.rs). mremap makes an mmap- * region bigger or smaller, moving it if needed; realloc() uses it for big- * blocks so the bytes don't have to be copied.- *- *   remaptest      all checks (exit code = number of failed checks)- */-#define _GNU_SOURCE-#include <errno.h>-#include <fcntl.h>-#include <signal.h>-#include <stdio.h>-#include <stdlib.h>-#include <string.h>-#include <sys/mman.h>-#include <sys/wait.h>-#include <unistd.h>--#define PG 4096UL--static int fails;-#define CHECK(cond, ...)                                               \-	do {                                                           \-		if (!(cond)) {                                         \-			fails++;                                       \-			printf("remaptest: FAIL line %d: ", __LINE__); \-			printf(__VA_ARGS__);                           \-			printf("\n");                                  \-		}                                                      \-	} while (0)--static char *anon(size_t pages)-{-	char *p = mmap(0, pages * PG, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);-	return p == MAP_FAILED ? 0 : p;-}--static void fill(char *p, size_t pages, int seed)-{-	for (size_t i = 0; i < pages; i++)-		memset(p + i * PG, seed + (int)i, PG);-}--static int same(const char *p, size_t pages, int seed)-{-	for (size_t i = 0; i < pages; i++)-		for (size_t j = 0; j < PG; j += 512)-			if (p[i * PG + j] != (char)(seed + (int)i))-				return 0;-	return 1;-}--static int zero(const char *p, size_t len)-{-	for (size_t i = 0; i < len; i += 256)-		if (p[i])-			return 0;-	return 1;-}--/* Does touching `p` kill a child with SIGSEGV? (= nothing mapped there) */-static int faults(volatile char *p)-{-	pid_t pid = fork();-	if (pid == 0) {-		(void)*p;-		_exit(0);-	}-	int st = 0;-	waitpid(pid, &st, 0);-	return WIFSIGNALED(st) && WTERMSIG(st) == SIGSEGV;-}--static void grow_in_place(void)-{-	char *p = anon(8);-	CHECK(p, "mmap 8 pages");-	munmap(p + 3 * PG, 5 * PG); /* keep 3, leave room behind them */-	fill(p, 3, 'a');-	char *q = mremap(p, 3 * PG, 6 * PG, 0);-	CHECK(q == p, "grow in place: %p, wanted %p", q, p);-	if (q != p)-		return;-	CHECK(same(p, 3, 'a'), "grow in place kept the data");-	CHECK(zero(p + 3 * PG, 3 * PG), "the new part is zero");-	p[6 * PG - 1] = 1; /* and writable */-	q = mremap(p, 6 * PG, 2 * PG, 0);-	CHECK(q == p, "shrink: %p", q);-	CHECK(same(p, 2, 'a'), "shrink kept the data");-	CHECK(faults(p + 2 * PG), "the cut-off part is gone");-	CHECK(mremap(p, 2 * PG, 2 * PG, 0) == p, "same size: nothing to do");-	munmap(p, 2 * PG);-}--static void move(void)-{-	char *a = anon(2);-	char *b = anon(1); /* right below a: can't grow in place */-	CHECK(a && b && b + PG == a, "b (%p) sits right below a (%p)", b, a);-	fill(b, 1, 'm');-	errno = 0;-	CHECK(mremap(b, PG, 4 * PG, 0) == MAP_FAILED && errno == ENOMEM, "no room and no MAYMOVE: ENOMEM (%d)", errno);-	char *c = mremap(b, PG, 4 * PG, MREMAP_MAYMOVE);-	CHECK(c != MAP_FAILED && c != b, "MAYMOVE moved it: %p", c);-	if (c == MAP_FAILED)-		return;-	CHECK(same(c, 1, 'm'), "the moved page kept its bytes");-	CHECK(zero(c + PG, 3 * PG), "the rest is zero");-	CHECK(faults(b), "nothing left at the old address");--	/* MREMAP_FIXED: to an address we pick. */-	char *spot = anon(4);-	munmap(spot, 4 * PG);-	char *d = mremap(c, 4 * PG, 2 * PG, MREMAP_MAYMOVE | MREMAP_FIXED, spot);-	CHECK(d == spot, "MREMAP_FIXED: %p, wanted %p", d, spot);-	if (d == spot)-		CHECK(same(d, 1, 'm'), "MREMAP_FIXED kept the bytes");-	CHECK(faults(c), "and the old place is empty");-	munmap(spot, 2 * PG);-	munmap(a, 2 * PG);-}--static void errors(void)-{-	char *p = anon(2);-	errno = 0;-	CHECK(mremap(p + 1, PG, 2 * PG, MREMAP_MAYMOVE) == MAP_FAILED && errno == EINVAL, "unaligned: EINVAL (%d)", errno);-	errno = 0;-	CHECK(mremap(p, PG, 0, MREMAP_MAYMOVE) == MAP_FAILED && errno == EINVAL, "new_len 0: EINVAL (%d)", errno);-	errno = 0;-	CHECK(mremap(p, PG, 2 * PG, 64) == MAP_FAILED && errno == EINVAL, "unknown flag: EINVAL (%d)", errno);-	errno = 0;-	CHECK(mremap(p, PG, 2 * PG, MREMAP_FIXED, p + 8 * PG) == MAP_FAILED && errno == EINVAL,-	      "FIXED without MAYMOVE: EINVAL (%d)", errno);-	errno = 0;-	CHECK(mremap(p, 2 * PG, 4 * PG, MREMAP_MAYMOVE | MREMAP_FIXED, p + PG) == MAP_FAILED && errno == EINVAL,-	      "FIXED onto itself: EINVAL (%d)", errno);-	munmap(p, 2 * PG);-	errno = 0;-	CHECK(mremap(p, PG, 2 * PG, MREMAP_MAYMOVE) == MAP_FAILED && errno == EFAULT, "nothing mapped: EFAULT (%d)", errno);-}--static void file_backed(void)-{-	int fd = open("/tmp/remap.dat", O_RDWR | O_CREAT | O_TRUNC, 0644);-	CHECK(fd >= 0, "open /tmp/remap.dat");-	char page[PG];-	for (int i = 0; i < 3; i++) {-		memset(page, 'F' + i, PG);-		write(fd, page, PG);-	}-	char *keep = anon(1); /* so the mapping below can't grow in place */-	char *f = mmap(0, PG, PROT_READ, MAP_PRIVATE, fd, 0);-	(void)keep;-	CHECK(f != MAP_FAILED && f[0] == 'F', "file page 0");-	char *g = mremap(f, PG, 3 * PG, MREMAP_MAYMOVE);-	CHECK(g != MAP_FAILED, "mremap a file mapping");-	if (g != MAP_FAILED) {-		CHECK(g[0] == 'F' && g[PG] == 'G' && g[2 * PG + 7] == 'H', "moved file pages read the file: %c %c %c", g[0],-		      g[PG], g[2 * PG + 7]);-		munmap(g, 3 * PG);-	}-	munmap(keep, PG);-	close(fd);-	unlink("/tmp/remap.dat");-}--static void after_fork(void)-{-	char *p = anon(1);-	fill(p, 1, 'p');-	pid_t pid = fork();-	if (pid == 0) {-		char *q = mremap(p, PG, 16 * PG, MREMAP_MAYMOVE);-		if (q == MAP_FAILED || q[0] != 'p')-			_exit(1);-		q[0] = 'c'; /* copy-on-write must still work after the move */-		_exit(q[0] == 'c' ? 0 : 2);-	}-	int st = 0;-	waitpid(pid, &st, 0);-	CHECK(WIFEXITED(st) && WEXITSTATUS(st) == 0, "child moved and wrote: status %#x", st);-	CHECK(p[0] == 'p', "parent's copy unchanged: %c", p[0]);-	munmap(p, PG);-}--static void with_realloc(void)-{-	size_t n = 1 << 20;-	unsigned *v = malloc(n);-	CHECK(v, "malloc 1 MiB");-	for (size_t i = 0; i < n / 4; i++)-		v[i] = (unsigned)i * 2654435761u;-	for (size_t big = 4 << 20; big <= 32 << 20; big *= 2) {-		v = realloc(v, big);-		CHECK(v, "realloc %zu", big);-		if (!v)-			return;-		size_t bad = 0;-		for (size_t i = 0; i < n / 4; i++)-			bad += v[i] != (unsigned)i * 2654435761u;-		CHECK(bad == 0, "realloc to %zu MiB kept the data (%zu wrong)", big >> 20, bad);-	}-	v = realloc(v, 2 << 20);-	CHECK(v && v[12345] == 12345u * 2654435761u, "realloc down");-	free(v);-}--int main(void)-{-	grow_in_place();-	move();-	errors();-	file_backed();-	after_fork();-	with_realloc();-	printf("remaptest: %s (%d failed)\n", fails ? "FAILED" : "all passed", fails);-	return fails;-}
import/kernel-user/sigtest.rs
@@ -1,189 +0,0 @@-//! sigtest -- checks signals: handlers and sigreturn, masks, default-//! actions, SIGCHLD, stop/continue, SIGPIPE and interrupted system calls.-//!-//! A signal handler is a normal function that the kernel makes the program-//! "call" at a moment it didn't choose: it saves the interrupted registers-//! in a frame on the user stack, runs the handler, and when the handler-//! returns (into ulib's `__restore_rt`), rt_sigreturn puts everything back.-#![no_std]-#![no_main]--#[macro_use]-#[path = "ulib.rs"]-mod ulib;--use core::sync::atomic::{AtomicI64, Ordering::SeqCst};-use ulib::*;--static HITS: AtomicI64 = AtomicI64::new(0);-static LAST_SIG: AtomicI64 = AtomicI64::new(0);-static MASK_IN_HANDLER: AtomicI64 = AtomicI64::new(0);-static CHLD_PID: AtomicI64 = AtomicI64::new(0);-static CHLD_STATUS: AtomicI64 = AtomicI64::new(0);-static CHLD_CODE: AtomicI64 = AtomicI64::new(0);--extern "C" fn on_signal(sig: i32) {-    HITS.fetch_add(1, SeqCst);-    LAST_SIG.store(sig as i64, SeqCst);-    // While a handler runs, its own signal is blocked.-    MASK_IN_HANDLER.store(sigprocmask(SIG_BLOCK, 0), SeqCst);-}--extern "C" fn on_child(_sig: i32, info: *const SigInfo, _ctx: *mut u8) {-    let info = unsafe { &*info };-    CHLD_PID.store(info.addr_or_pid as u32 as i64, SeqCst);-    CHLD_STATUS.store(info.status as i64, SeqCst);-    CHLD_CODE.store(info.code as i64, SeqCst);-}--/// Busy work that keeps values in registers across a signal: if sigreturn-/// restored anything wrong, the sum comes out different.-#[inline(never)]-fn compute_with_signal(sig: i32) -> u64 {-    let mut x = core::hint::black_box(0x1234_5678_u64);-    let mut y = core::hint::black_box(0x0bad_cafe_u64);-    for i in 0..1000u64 {-        if i == 500 {-            kill(getpid(), sig); // the handler runs before kill returns-        }-        x = x.wrapping_mul(31).wrapping_add(i);-        y ^= x.rotate_left(7);-    }-    x ^ y-}--/// Fork a child that runs `f` and return its wait4 status.-fn in_child(f: fn()) -> i32 {-    let pid = fork();-    if pid == 0 {-        f();-        exit(0);-    }-    wait4(pid, 0).map(|(_, s)| s).unwrap_or(-1)-}--fn main() -> i64 {-    let mut c = Checks::new("sigtest");-    // 1. A handler round trip, with every register restored.-    sigaction_raw(SIGUSR2, SIG_IGN, 0, 0);-    let without = compute_with_signal(SIGUSR2); // ignored: nothing happens-    c.check(HITS.load(SeqCst) == 0, "SIG_IGN: an ignored signal does nothing");-    c.check(signal(SIGUSR1, on_signal) == 0, "rt_sigaction installs a handler");-    let with = compute_with_signal(SIGUSR1);-    c.check(HITS.load(SeqCst) == 1 && LAST_SIG.load(SeqCst) == SIGUSR1 as i64, "kill(getpid(), SIGUSR1) runs the handler once, with sig = 10");-    c.check(with == without, "after rt_sigreturn the interrupted code computes the same result");-    c.check(MASK_IN_HANDLER.load(SeqCst) as u64 & sigbit(SIGUSR1) != 0, "the signal is blocked while its handler runs");-    c.check(sigprocmask(SIG_BLOCK, 0) as u64 & sigbit(SIGUSR1) == 0, "... and unblocked again after sigreturn");--    // 2. Blocking: a blocked signal waits (pending) until unblocked.-    sigprocmask(SIG_BLOCK, sigbit(SIGUSR1));-    kill(getpid(), SIGUSR1);-    c.check(HITS.load(SeqCst) == 1, "blocked: kill doesn't run the handler yet");-    sigprocmask(SIG_UNBLOCK, sigbit(SIGUSR1));-    c.check(HITS.load(SeqCst) == 2, "unblocking delivers the pending signal");-    c.check(sigaction_raw(SIGKILL, SIG_IGN, 0, 0) == -22, "SIGKILL can't be caught or ignored (EINVAL)");--    // 3. Default actions, seen through wait4's status.-    let s = in_child(|| {-        kill(getpid(), SIGTERM);-    });-    c.check(wifsignaled(s) && wtermsig(s) == SIGTERM, "SIGTERM's default action terminates");-    let s = in_child(|| {-        kill(getpid(), SIGQUIT);-    });-    c.check(wifsignaled(s) && wtermsig(s) == SIGQUIT, "SIGQUIT (\"core\") terminates too (no core files here)");-    let s = in_child(|| {-        kill(getpid(), SIGCHLD); // default: ignore-        kill(getpid(), SIGURG_NUM);-    });-    c.check(wifexited(s) && wexitstatus(s) == 0, "SIGCHLD and SIGURG are ignored by default");-    let s = in_child(|| {-        // Write to a pipe nobody reads: SIGPIPE.-        let (r, w) = pipe().unwrap();-        close(r);-        write(w, b"x");-    });-    c.check(wifsignaled(s) && wtermsig(s) == SIGPIPE, "writing to a pipe without readers: SIGPIPE");-    let s = in_child(|| {-        sigaction_raw(SIGPIPE, SIG_IGN, 0, 0);-        let (r, w) = pipe().unwrap();-        close(r);-        exit(if write(w, b"x") == -32 { 0 } else { 1 });-    });-    c.check(wifexited(s) && wexitstatus(s) == 0, "... with SIGPIPE ignored, write fails with EPIPE");--    // 4. SIGCHLD: the parent hears when a child ends.-    sigaction_info(SIGCHLD, on_child, SA_RESTART);-    let pid = fork();-    if pid == 0 {-        exit(3);-    }-    while CHLD_PID.load(SeqCst) == 0 {-        pause();-    }-    c.check(CHLD_PID.load(SeqCst) == pid && CHLD_STATUS.load(SeqCst) == 3 && CHLD_CODE.load(SeqCst) == 1, "SIGCHLD arrives with si_pid, status 3, CLD_EXITED");-    c.check(wait(pid) == 3, "and the child can still be reaped");-    sigaction_raw(SIGCHLD, SIG_DFL, 0, 0);--    // 5. Stop and continue.-    let pid = fork();-    if pid == 0 {-        loop {-            sleep_ms(10);-        }-    }-    kill(pid, SIGSTOP);-    let r = wait4(pid, WUNTRACED);-    c.check(matches!(r, Ok((p, s)) if p == pid && wifstopped(s) && wstopsig(s) == SIGSTOP), "SIGSTOP stops a child; wait4(WUNTRACED) reports it");-    kill(pid, SIGCONT);-    let r = wait4(pid, WCONTINUED);-    c.check(matches!(r, Ok((_, s)) if wifcontinued(s)), "SIGCONT continues it; wait4(WCONTINUED) reports that");-    kill(pid, SIGKILL);-    let r = wait4(pid, 0);-    c.check(matches!(r, Ok((_, s)) if wifsignaled(s) && wtermsig(s) == SIGKILL), "SIGKILL ends it");--    // 6. Interrupted system calls: a signal during a blocking read.-    for restart in [false, true] {-        sigaction_raw(SIGUSR1, on_signal as usize, if restart { SA_RESTART } else { 0 }, 0);-        let (r, w) = pipe().unwrap();-        let me = getpid();-        let pid = fork();-        if pid == 0 {-            sleep_ms(120);-            kill(me, SIGUSR1); // interrupts the parent's read-            sleep_ms(120);-            write(w, b"y");-            exit(0);-        }-        let hits = HITS.load(SeqCst);-        let mut b = [0u8; 1];-        let n = read(r, &mut b);-        if restart {-            c.check(n == 1 && HITS.load(SeqCst) == hits + 1, "SA_RESTART: the handler runs, then read continues and gets the data");-        } else {-            c.check(n == -EINTR && HITS.load(SeqCst) == hits + 1, "no SA_RESTART: read returns EINTR after the handler");-        }-        wait(pid);-        close(r);-        close(w);-    }--    // 7. kill errors and process groups.-    c.check(kill(99999, SIGTERM) == -ESRCH, "kill of a pid that doesn't exist: ESRCH");-    c.check(kill(getpid(), 99) == -22, "kill with signal 99: EINVAL");-    c.check(kill(getpid(), 0) == 0, "signal 0 only checks that the process exists");-    let pid = fork();-    if pid == 0 {-        setpgid(0, 0);-        loop {-            pause();-        }-    }-    setpgid(pid, pid);-    c.check(kill(-pid, SIGTERM) == 0, "kill(-pgid) signals a whole process group");-    let r = wait4(pid, 0);-    c.check(matches!(r, Ok((_, s)) if wtermsig(s) == SIGTERM), "the group's member got it");-    c.done()-}--const SIGURG_NUM: i32 = 23;
import/kernel-user/spin.rs
@@ -1,22 +0,0 @@-//! A CPU hog: busy-loops without ever making a syscall in between. The timer-//! interrupt still preempts it, so everything else keeps running.-//! `spin` does 3 rounds of 30 million additions; `spin <n>` does n rounds.-#![no_std]-#![no_main]--#[macro_use]-#[path = "ulib.rs"]-mod ulib;--fn main() -> i64 {-    println!("[spin] pid {}: spinning without syscalls...", ulib::getpid());-    let rounds: u64 = ulib::arg(1).and_then(|a| a.parse().ok()).unwrap_or(3);-    let mut count: u64 = 0;-    for round in 1..=rounds {-        for _ in 0..30_000_000 {-            count = core::hint::black_box(count + 1);-        }-        println!("[spin] round {} done (count = {})", round, count);-    }-    0-}
import/kernel-user/threads.rs
@@ -1,135 +0,0 @@-//! threads [N] -- several threads in one process, pthread-style.-//!-//! Threads share *everything* a process owns: memory, open files, signal-//! handlers. Each has only its own registers, stack and TLS block (and a-//! thread id). ulib's `spawn_thread` = pthread_create: clone with-//! CLONE_VM | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD | ..., a fresh-//! stack, CLONE_SETTLS for the TLS block, and CLONE_CHILD_CLEARTID so-//! `join` can sleep on a futex until the kernel says "thread gone".-//!-//! The demo: a parallel sum. 1..=COUNT is split into N slices (default 4,-//! one per CPU); each thread adds up its slice, then adds its result to a-//! shared total under a futex mutex. Many small increments of a shared-//! counter (also under the mutex) check the mutex really excludes; a-//! counter bumped *without* it shows why that matters.-#![no_std]-#![no_main]--#[macro_use]-#[path = "ulib.rs"]-mod ulib;--extern crate alloc;--use alloc::sync::Arc;-use alloc::vec::Vec;-use core::cell::UnsafeCell;-use core::sync::atomic::{AtomicU32, AtomicU64, Ordering};-use ulib::*;--const COUNT: u64 = 40_000_000;-const BUMPS: u64 = 20_000;--/// Data guarded by a FutexMutex (a tiny `Mutex<T>`).-struct Shared {-    lock: FutexMutex,-    total: UnsafeCell<u64>,-    bumps: UnsafeCell<u64>,-    /// Bumped with a plain read + write, no lock: races lose updates.-    racy: UnsafeCell<u64>,-}-unsafe impl Sync for Shared {}-unsafe impl Send for Shared {}--fn main() -> i64 {-    let n: u64 = arg(1).and_then(|a| a.parse().ok()).unwrap_or(4).clamp(1, 16);-    let mut c = Checks::new("threads");-    let shared = Arc::new(Shared {-        lock: FutexMutex::new(),-        total: UnsafeCell::new(0),-        bumps: UnsafeCell::new(0),-        racy: UnsafeCell::new(0),-    });-    let cpus_seen = Arc::new(AtomicU64::new(0)); // bit i = some thread ran on CPU i-    let tids = Arc::new(AtomicU64::new(0)); // sum of the threads' tids-    let tls_ok = Arc::new(AtomicU32::new(0));-    let start = now_us();-    let mut threads = Vec::new();-    for i in 0..n {-        let (shared, cpus_seen, tids, tls_ok) = (shared.clone(), cpus_seen.clone(), tids.clone(), tls_ok.clone());-        let from = COUNT * i / n + 1;-        let to = COUNT * (i + 1) / n;-        // Each thread gets `i` as its TLS value.-        let t = spawn_thread(i, move || {-            let mut sum = 0u64;-            for x in from..=to {-                sum = sum.wrapping_add(core::hint::black_box(x));-            }-            cpus_seen.fetch_or(1 << sysinfo().this_cpu, Ordering::Relaxed);-            tids.fetch_add(gettid() as u64, Ordering::Relaxed);-            if tls_value() == i {-                tls_ok.fetch_add(1, Ordering::Relaxed);-            }-            for _ in 0..BUMPS {-                shared.lock.lock();-                unsafe { *shared.bumps.get() += 1 };-                shared.lock.unlock();-                unsafe {-                    let v = core::ptr::read_volatile(shared.racy.get());-                    core::ptr::write_volatile(shared.racy.get(), v + 1);-                }-            }-            shared.lock.lock();-            unsafe { *shared.total.get() += sum };-            shared.lock.unlock();-        });-        match t {-            Ok(t) => threads.push(t),-            Err(e) => {-                println!("threads: spawn_thread: {}", strerror(e));-                return 1;-            }-        }-    }-    let spawned_tids: u64 = threads.iter().map(|t| t.tid as u64).sum();-    for t in threads {-        t.join();-    }-    let ms = (now_us() - start) / 1000;-    let total = unsafe { *shared.total.get() };-    let bumps = unsafe { *shared.bumps.get() };-    let racy = unsafe { *shared.racy.get() };-    let cpus = cpus_seen.load(Ordering::Relaxed).count_ones();-    println!("threads: {} threads summed 1..={} = {} in {} ms on {} CPU(s)", n, COUNT, total, ms, cpus);-    println!("threads: locked counter {} (want {}), unlocked counter {} (races lose updates)", bumps, n * BUMPS, racy);-    c.check(total == COUNT * (COUNT + 1) / 2, "parallel sum is right");-    c.check(bumps == n * BUMPS, "the futex mutex never let two threads in at once");-    c.check(tls_ok.load(Ordering::Relaxed) == n as u32, "every thread saw its own TLS value (fs:[8])");-    c.check(tids.load(Ordering::Relaxed) == spawned_tids, "gettid in each thread = the tid clone returned");-    c.check(gettid() == getpid(), "the main thread's tid is the pid");--    // A futex wait whose value doesn't match returns at once (EAGAIN); one-    // that matches sleeps until its timeout.-    let word = AtomicU32::new(7);-    c.check(futex_wait(&word, 8, None) == -EAGAIN, "futex_wait with a stale value: EAGAIN");-    let t0 = now_us();-    let r = futex_wait(&word, 7, Some(50_000));-    c.check(r == -ETIMEDOUT && now_us() - t0 >= 40_000, "futex_wait times out after 50 ms");-    c.check(futex_wake(&word, 1) == 0, "futex_wake with nobody waiting wakes 0");--    // exit vs exit_group: a thread that calls exit (60) ends only itself.-    let alive = Arc::new(AtomicU32::new(0));-    let a2 = alive.clone();-    let t = spawn_thread(0, move || {-        a2.store(1, Ordering::SeqCst);-        // falls off the end: ulib calls exit(0) = just this thread-    })-    .unwrap();-    t.join();-    c.check(alive.load(Ordering::SeqCst) == 1, "a thread's exit ends only that thread");--    // tgkill: a signal for one particular thread (here: ourselves).-    c.check(tgkill(getpid(), gettid(), 0) == 0, "tgkill(pid, tid, 0) finds the thread");-    c.check(tgkill(getpid(), 99999, 0) == -ESRCH, "tgkill to a missing thread: ESRCH");-    c.done()-}
import/kernel-user/unixtest.rs
@@ -1,889 +0,0 @@-//! unixtest -- stage 18: AF_UNIX sockets and /proc/self. Run with one-//! group name; exit code = number of failed checks:-//!   stream    a path socket (ramfs, ext2), echo across fork, abstract-//!             names, autobind, EOF/ECONNRESET/EPIPE, SO_PEERCRED-//!   pair      socketpair: stream, seqpacket (message borders), dgram-//!   dgram     sendto/recvfrom between named sockets, MSG_TRUNC, connect-//!   rights    SCM_RIGHTS (a file and a pipe end), MSG_CTRUNC,-//!             MSG_CMSG_CLOEXEC, in-flight fds closed, SCM_CREDENTIALS-//!   nonblock  EAGAIN, buffer limits (backpressure), poll and epoll-//!             (EPOLLRDHUP, EPOLLHUP), nonblocking accept-//!   proc      /proc/self/fd and /proc/self/exe-#![no_std]-#![no_main]--#[macro_use]-#[path = "ulib.rs"]-mod ulib;--extern crate alloc;--use alloc::format;-use alloc::string::String;-use alloc::vec::Vec;-use ulib::*;--const SYS_SENDMSG: u64 = 46;-const SYS_RECVMSG: u64 = 47;-const SYS_SHUTDOWN: u64 = 48;-const SYS_GETSOCKNAME: u64 = 51;-const SYS_GETPEERNAME: u64 = 52;-const SYS_SOCKETPAIR: u64 = 53;-const SYS_GETSOCKOPT: u64 = 55;-const SYS_ACCEPT4: u64 = 288;--const AF_UNIX: u64 = 1;-const SOCK_STREAM: u64 = 1;-const SOCK_DGRAM: u64 = 2;-const SOCK_SEQPACKET: u64 = 5;-const SOCK_NONBLOCK: u64 = 0o4000;-const SOCK_CLOEXEC: u64 = 0o2000000;-const MSG_PEEK: u64 = 2;-const MSG_CTRUNC: i32 = 8;-const MSG_TRUNC: u64 = 0x20;-const MSG_DONTWAIT: u64 = 0x40;-const MSG_WAITALL: u64 = 0x100;-const MSG_NOSIGNAL: u64 = 0x4000;-const MSG_CMSG_CLOEXEC: u64 = 0x4000_0000;-const SOL_SOCKET: u64 = 1;-const SO_TYPE: u64 = 3;-const SO_ERROR: u64 = 4;-const SO_SNDBUF: u64 = 7;-const SO_RCVBUF: u64 = 8;-const SO_PASSCRED: u64 = 16;-const SO_PEERCRED: u64 = 17;-const SO_ACCEPTCONN: u64 = 30;-const SO_DOMAIN: u64 = 39;-const POLLIN: u16 = 1;-const POLLOUT: u16 = 4;-const POLLHUP: u16 = 0x10;-const EPOLLRDHUP: u32 = 0x2000;-const EPOLLHUP: u32 = 0x10;-const ENXIO: i64 = 6;-const EPIPE: i64 = 32;-const EMSGSIZE: i64 = 90;-const EPROTOTYPE: i64 = 91;-const ENOTSOCK: i64 = 88;-const EADDRINUSE: i64 = 98;-const ECONNRESET: i64 = 104;-const EISCONN: i64 = 106;-const ENOTCONN: i64 = 107;-const ECONNREFUSED: i64 = 111;-const EOPNOTSUPP: i64 = 95;--// --- helpers -----------------------------------------------------------------------/// struct sockaddr_un: (bytes, length to pass). A name starting with-/// '@' is abstract (a 0 byte in front, no trailing 0), as `ss` shows them.-fn sun(name: &str) -> ([u8; 110], u64) {-    let mut a = [0u8; 110];-    a[0] = AF_UNIX as u8;-    let b = name.as_bytes();-    if let Some(rest) = name.strip_prefix('@') {-        a[3..3 + rest.len()].copy_from_slice(rest.as_bytes());-        (a, 3 + rest.len() as u64)-    } else {-        a[2..2 + b.len()].copy_from_slice(b);-        (a, 2 + b.len() as u64 + 1)-    }-}--fn usock(ty: u64) -> i64 {-    syscall3(SYS_SOCKET, AF_UNIX, ty, 0)-}--fn ubind(fd: i64, name: &str) -> i64 {-    let (a, len) = sun(name);-    syscall3(SYS_BIND, fd as u64, a.as_ptr() as u64, len)-}--fn uconnect(fd: i64, name: &str) -> i64 {-    let (a, len) = sun(name);-    syscall3(SYS_CONNECT, fd as u64, a.as_ptr() as u64, len)-}--fn uaccept(fd: i64) -> i64 {-    syscall3(SYS_ACCEPT, fd as u64, 0, 0)-}--/// getsockname/getpeername: the name as text ('@' for abstract, "" for-/// unnamed), or the error.-fn name_of(nr: u64, fd: i64) -> Result<String, i64> {-    let mut a = [0u8; 110];-    let mut len = 110u32;-    let r = syscall3(nr, fd as u64, a.as_mut_ptr() as u64, &mut len as *mut u32 as u64);-    if r < 0 {-        return Err(r);-    }-    let path = &a[2..len as usize];-    Ok(match path.first() {-        None => String::new(),-        Some(0) => format!("@{}", core::str::from_utf8(&path[1..]).unwrap_or("?")),-        Some(_) => String::from(core::str::from_utf8(&path[..path.len() - 1]).unwrap_or("?")),-    })-}--fn pair(ty: u64) -> (i64, i64) {-    let mut sv = [-1i32; 2];-    let r = syscall5(SYS_SOCKETPAIR, AF_UNIX, ty, 0, sv.as_mut_ptr() as u64, 0);-    if r < 0 {-        return (r, r);-    }-    (sv[0] as i64, sv[1] as i64)-}--fn getopt(fd: i64, opt: u64) -> (i64, u32) {-    let mut v = 0u32;-    let mut len = 4u32;-    let r = syscall5(SYS_GETSOCKOPT, fd as u64, SOL_SOCKET, opt, &mut v as *mut u32 as u64, &mut len as *mut u32 as u64);-    (r, v)-}--fn setopt(fd: i64, opt: u64, v: u32) -> i64 {-    syscall5(SYS_SETSOCKOPT, fd as u64, SOL_SOCKET, opt, &v as *const u32 as u64, 4)-}--/// SO_PEERCRED: (pid, uid, gid).-fn peercred(fd: i64) -> (i64, [u32; 3]) {-    let mut v = [0u32; 3];-    let mut len = 12u32;-    let r = syscall5(SYS_GETSOCKOPT, fd as u64, SOL_SOCKET, SO_PEERCRED, v.as_mut_ptr() as u64, &mut len as *mut u32 as u64);-    (r, v)-}--fn send(fd: i64, data: &[u8], flags: u64) -> i64 {-    syscall6(SYS_SENDTO, fd as u64, data.as_ptr() as u64, data.len() as u64, flags, 0, 0)-}--fn recv(fd: i64, buf: &mut [u8], flags: u64) -> i64 {-    syscall6(SYS_RECVFROM, fd as u64, buf.as_mut_ptr() as u64, buf.len() as u64, flags, 0, 0)-}--fn send_to(fd: i64, data: &[u8], name: &str) -> i64 {-    let (a, len) = sun(name);-    syscall6(SYS_SENDTO, fd as u64, data.as_ptr() as u64, data.len() as u64, 0, a.as_ptr() as u64, len)-}--/// recvfrom with the sender's name.-fn recv_from(fd: i64, buf: &mut [u8], flags: u64) -> (i64, String) {-    let mut a = [0u8; 110];-    let mut len = 110u32;-    let n = syscall6(SYS_RECVFROM, fd as u64, buf.as_mut_ptr() as u64, buf.len() as u64, flags, a.as_mut_ptr() as u64, &mut len as *mut u32 as u64);-    let name = if len <= 2 {-        String::new()-    } else if a[2] == 0 {-        format!("@{}", core::str::from_utf8(&a[3..len as usize]).unwrap_or("?"))-    } else {-        String::from(core::str::from_utf8(&a[2..len as usize - 1]).unwrap_or("?"))-    };-    (n, name)-}--#[repr(C)]-struct MsgHdr {-    name: u64,-    namelen: u32,-    iov: u64,-    iovlen: u64,-    control: u64,-    controllen: u64,-    flags: i32,-}--#[repr(C)]-struct IoVec {-    base: u64,-    len: u64,-}--/// sendmsg: `parts` gathered, plus fds (SCM_RIGHTS) if any.-fn sendmsg(fd: i64, parts: &[&[u8]], fds: &[i32], flags: u64) -> i64 {-    let iov: Vec<IoVec> = parts.iter().map(|p| IoVec { base: p.as_ptr() as u64, len: p.len() as u64 }).collect();-    let mut ctl = Vec::new();-    if !fds.is_empty() {-        ctl.extend_from_slice(&(16 + 4 * fds.len() as u64).to_ne_bytes());-        ctl.extend_from_slice(&1i32.to_ne_bytes());-        ctl.extend_from_slice(&1i32.to_ne_bytes());-        for f in fds {-            ctl.extend_from_slice(&f.to_ne_bytes());-        }-        while ctl.len() % 8 != 0 {-            ctl.push(0);-        }-    }-    let h = MsgHdr { name: 0, namelen: 0, iov: iov.as_ptr() as u64, iovlen: iov.len() as u64, control: ctl.as_ptr() as u64, controllen: ctl.len() as u64, flags: 0 };-    syscall3(SYS_SENDMSG, fd as u64, &h as *const MsgHdr as u64, flags)-}--struct Got {-    n: i64,-    data: Vec<u8>,-    fds: Vec<i32>,-    cred: Option<[u32; 3]>,-    flags: i32,-}--/// recvmsg into two buffers of `len/2`, with `ctl_room` bytes of control.-fn recvmsg(fd: i64, len: usize, ctl_room: usize, flags: u64) -> Got {-    let mut a = alloc::vec![0u8; len / 2];-    let mut b = alloc::vec![0u8; len - len / 2];-    let iov = [IoVec { base: a.as_mut_ptr() as u64, len: a.len() as u64 }, IoVec { base: b.as_mut_ptr() as u64, len: b.len() as u64 }];-    let mut ctl = alloc::vec![0u8; ctl_room];-    let mut h = MsgHdr { name: 0, namelen: 0, iov: iov.as_ptr() as u64, iovlen: 2, control: ctl.as_mut_ptr() as u64, controllen: ctl_room as u64, flags: 0 };-    let n = syscall3(SYS_RECVMSG, fd as u64, &mut h as *mut MsgHdr as u64, flags);-    let mut got = Got { n, data: Vec::new(), fds: Vec::new(), cred: None, flags: h.flags };-    if n < 0 {-        return got;-    }-    a.extend_from_slice(&b);-    got.data = a[..(n as usize).min(len)].to_vec();-    let ctl = &ctl[..h.controllen as usize];-    let mut off = 0;-    while off + 16 <= ctl.len() {-        let clen = u64::from_ne_bytes(ctl[off..off + 8].try_into().unwrap()) as usize;-        let ty = i32::from_ne_bytes(ctl[off + 12..off + 16].try_into().unwrap());-        let body = &ctl[off + 16..(off + clen).min(ctl.len())];-        match ty {-            1 => got.fds.extend(body.chunks(4).map(|c| i32::from_ne_bytes(c.try_into().unwrap()))),-            2 => {-                let w: Vec<u32> = body.chunks(4).map(|c| u32::from_ne_bytes(c.try_into().unwrap())).collect();-                got.cred = Some([w[0], w[1], w[2]]);-            }-            _ => {}-        }-        off += (clen + 7) & !7;-    }-    got-}--fn read_all(fd: i64) -> Vec<u8> {-    let mut out = Vec::new();-    let mut buf = [0u8; 256];-    loop {-        let n = read(fd, &mut buf);-        if n <= 0 {-            break;-        }-        out.extend_from_slice(&buf[..n as usize]);-    }-    out-}--fn link(path: &str) -> String {-    let mut buf = [0u8; 256];-    let n = readlink(path, &mut buf);-    if n < 0 {-        return format!("error {}", n);-    }-    String::from(core::str::from_utf8(&buf[..n as usize]).unwrap_or("?"))-}--fn is_socket(st: &Stat) -> bool {-    st.file_type() == S_IFSOCK-}--fn main() -> i64 {-    let group = arg(1).unwrap_or("");-    let mut c = Checks::new("unixtest");-    sigaction_raw(13, 1, 0, 0); // SIGPIPE: SIG_IGN (EPIPE checks)-    match group {-        "stream" => stream(&mut c),-        "pair" => pairs(&mut c),-        "dgram" => dgram(&mut c),-        "rights" => rights(&mut c),-        "nonblock" => nonblock(&mut c),-        "proc" => proc(&mut c),-        _ => c.check(false, "usage: unixtest stream|pair|dgram|rights|nonblock|proc"),-    }-    c.done()-}--// --- stream ----------------------------------------------------------------------------/// The classic: a server binds a path, listens, a forked child connects-/// and talks to it.-fn echo_on(c: &mut Checks, path: &str) {-    unlink(path);-    let srv = usock(SOCK_STREAM);-    c.check(srv >= 0, &format!("{}: socket(AF_UNIX, SOCK_STREAM)", path));-    c.check(ubind(srv, path) == 0, &format!("{}: bind", path));-    let mut s = Stat::default();-    c.check(lstat(path, &mut s) == 0 && is_socket(&s), &format!("{}: bind made a socket file (S_IFSOCK)", path));-    c.check(s.perm() == 0o755, &format!("{}: ...mode 0777 minus the umask (0o{:o})", path, s.perm()));-    let other = usock(SOCK_STREAM);-    c.check(ubind(other, path) == -EADDRINUSE, &format!("{}: binding the name again: EADDRINUSE", path));-    c.check(ubind(srv, "/tmp/second.sock") == -EINVAL, &format!("{}: binding a bound socket again: EINVAL", path));-    c.check(uconnect(other, path) == -ECONNREFUSED, &format!("{}: connect before listen: ECONNREFUSED", path));-    close(other);-    c.check(listen(srv, 4) == 0, &format!("{}: listen", path));-    c.check(getopt(srv, SO_ACCEPTCONN) == (0, 1), &format!("{}: SO_ACCEPTCONN 1", path));-    c.check(name_of(SYS_GETSOCKNAME, srv).as_deref() == Ok(path), &format!("{}: getsockname gives the path", path));-    c.check(open(path, O_RDONLY) == -ENXIO, &format!("{}: open() on a socket file: ENXIO", path));-    let child = fork();-    if child == 0 {-        // The client.-        let cl = usock(SOCK_STREAM);-        let mut bad = 0;-        if uconnect(cl, path) != 0 {-            bad += 1;-        }-        if write(cl, b"hello, ") != 7 || write(cl, b"server") != 6 {-            bad += 1;-        }-        let mut buf = [0u8; 64];-        let n = read(cl, &mut buf);-        if n != 13 || &buf[..13] != b"HELLO, SERVER" {-            bad += 1;-        }-        if read(cl, &mut buf) != 0 {-            bad += 1; // the server closed: end-of-file-        }-        exit(bad);-    }-    let conn = uaccept(srv);-    c.check(conn >= 0, &format!("{}: accept", path));-    let (r, cred) = peercred(conn);-    c.check(r == 0 && cred[0] as i64 == child, &format!("{}: SO_PEERCRED on the server side: the client's pid", path));-    c.check(name_of(SYS_GETPEERNAME, conn).as_deref() == Ok(""), &format!("{}: getpeername: the client is unnamed", path));-    c.check(name_of(SYS_GETSOCKNAME, conn).as_deref() == Ok(path), &format!("{}: the accepted socket's name is the server's", path));-    let mut buf = [0u8; 13];-    let n = recv(conn, &mut buf, MSG_WAITALL);-    c.check(n == 13 && &buf == b"hello, server", &format!("{}: two writes read back as one stream (MSG_WAITALL)", path));-    let upper: Vec<u8> = buf.iter().map(|b| b.to_ascii_uppercase()).collect();-    c.check(write(conn, &upper) == 13, &format!("{}: server answers", path));-    close(conn);-    c.check(wait(child) == 0, &format!("{}: the client saw the echo and EOF", path));-    close(srv);-    let late = usock(SOCK_STREAM);-    c.check(uconnect(late, path) == -ECONNREFUSED, &format!("{}: connect after the server closed: ECONNREFUSED", path));-    close(late);-    c.check(unlink(path) == 0 && lstat(path, &mut s) == -ENOENT, &format!("{}: unlink removes the socket file", path));-    let late = usock(SOCK_STREAM);-    c.check(uconnect(late, path) == -ENOENT, &format!("{}: connect to a missing path: ENOENT", path));-    close(late);-}--fn stream(c: &mut Checks) {-    echo_on(c, "/tmp/echo.sock");-    mkdir("/mnt/disk1/us");-    echo_on(c, "/mnt/disk1/us/echo.sock");-    rmdir("/mnt/disk1/us");-    // FAT can't store a socket file.-    let s = usock(SOCK_STREAM);-    c.check(ubind(s, "/mnt/disk0/x.sock") == -EPERM, "bind on FAT: EPERM");-    close(s);-    // A relative path, from the cwd.-    chdir("/tmp");-    unlink("/tmp/rel.sock");-    let srv = usock(SOCK_STREAM);-    c.check(ubind(srv, "rel.sock") == 0 && listen(srv, 1) == 0, "bind a relative path");-    let cl = usock(SOCK_STREAM);-    c.check(uconnect(cl, "/tmp/rel.sock") == 0, "...and connect to it by its full path");-    c.check(name_of(SYS_GETSOCKNAME, srv).as_deref() == Ok("rel.sock"), "getsockname: the path as bound");-    // Connect to a regular file: ECONNREFUSED.-    let fd = open("/tmp/plain.txt", O_WRONLY | O_CREAT);-    close(fd);-    let x = usock(SOCK_STREAM);-    c.check(uconnect(x, "/tmp/plain.txt") == -ECONNREFUSED, "connect to a plain file: ECONNREFUSED");-    close(x);-    unlink("/tmp/plain.txt");-    // Close the server with the connection never accepted: the client-    // gets ECONNRESET, then EOF.-    close(srv);-    let mut buf = [0u8; 8];-    c.check(read(cl, &mut buf) == -ECONNRESET, "server closed before accept: ECONNRESET");-    c.check(read(cl, &mut buf) == 0, "...then end-of-file");-    c.check(send(cl, b"x", MSG_NOSIGNAL) == -EPIPE, "...and writing: EPIPE");-    close(cl);-    unlink("/tmp/rel.sock");--    // Abstract names: no file anywhere.-    let a = usock(SOCK_STREAM);-    c.check(ubind(a, "@rustbelt-test") == 0 && listen(a, 2) == 0, "bind an abstract name");-    c.check(name_of(SYS_GETSOCKNAME, a).as_deref() == Ok("@rustbelt-test"), "getsockname: @rustbelt-test");-    let b = usock(SOCK_STREAM);-    c.check(ubind(b, "@rustbelt-test") == -EADDRINUSE, "the same abstract name again: EADDRINUSE");-    c.check(uconnect(b, "@rustbelt-test") == 0, "connect to the abstract name");-    let conn = uaccept(a);-    c.check(name_of(SYS_GETPEERNAME, b).as_deref() == Ok("@rustbelt-test"), "getpeername: @rustbelt-test");-    // ECONNRESET: close with data the peer sent still unread.-    write(b, b"unread");-    sched_yield();-    close(conn);-    c.check(read(b, &mut buf) == -ECONNRESET, "peer closed with our data unread: ECONNRESET");-    c.check(getopt(b, SO_ERROR) == (0, 0), "SO_ERROR cleared after that");-    close(b);-    close(a);-    let again = usock(SOCK_STREAM);-    c.check(ubind(again, "@rustbelt-test") == 0, "the abstract name is free once its socket closed");-    close(again);-    // Autobind: bind with just the family.-    let au = usock(SOCK_STREAM);-    let fam = [AF_UNIX as u8, 0];-    c.check(syscall3(SYS_BIND, au as u64, fam.as_ptr() as u64, 2) == 0, "bind with only the family (autobind)");-    let n = name_of(SYS_GETSOCKNAME, au).unwrap_or_default();-    c.check(n.len() == 6 && n.starts_with('@') && n[1..].bytes().all(|b| b.is_ascii_hexdigit()), &format!("...gets an abstract name of 5 hex digits ({})", n));-    let au2 = usock(SOCK_STREAM);-    syscall3(SYS_BIND, au2 as u64, fam.as_ptr() as u64, 2);-    let n2 = name_of(SYS_GETSOCKNAME, au2).unwrap_or_default();-    c.check(n2.len() == 6 && n2 != n, &format!("...and a second socket gets another one ({})", n2));-    close(au2);-    close(au);-    // Odd calls.-    let s = usock(SOCK_STREAM);-    c.check(read(s, &mut buf) == -EINVAL, "read on an unconnected stream socket: EINVAL");-    c.check(send(s, b"x", MSG_NOSIGNAL) == -ENOTCONN, "send on it: ENOTCONN");-    c.check(name_of(SYS_GETPEERNAME, s) == Err(-ENOTCONN), "getpeername: ENOTCONN");-    c.check(name_of(SYS_GETSOCKNAME, s).as_deref() == Ok(""), "getsockname of an unbound socket: just the family");-    c.check(getopt(s, SO_DOMAIN) == (0, 1) && getopt(s, SO_TYPE) == (0, 1), "SO_DOMAIN AF_UNIX, SO_TYPE SOCK_STREAM");-    let (r, cred) = peercred(s);-    c.check(r == 0 && cred == [0, u32::MAX, u32::MAX], "SO_PEERCRED with no peer: pid 0, ids -1");-    c.check(listen(s, 1) == -EINVAL, "listen while unbound: EINVAL");-    close(s);-    c.check(syscall3(SYS_SOCKET, AF_UNIX, 9, 0) == -94, "socket type 9: ESOCKTNOSUPPORT");-    c.check(syscall3(SYS_SOCKET, AF_UNIX, SOCK_STREAM, 7) == -93, "protocol 7: EPROTONOSUPPORT");-    let d = usock(SOCK_DGRAM);-    c.check(listen(d, 1) == -EOPNOTSUPP, "listen on a datagram socket: EOPNOTSUPP");-    close(d);-    let fd = open("/dev/null", O_RDONLY);-    c.check(syscall3(SYS_SHUTDOWN, fd as u64, 1, 0) == -ENOTSOCK, "shutdown on a non-socket: ENOTSOCK");-    close(fd);-}--// --- socketpair --------------------------------------------------------------------------fn pairs(c: &mut Checks) {-    let (a, b) = pair(SOCK_STREAM);-    c.check(a >= 0 && b >= 0, "socketpair(AF_UNIX, SOCK_STREAM)");-    c.check(write(a, b"ab") == 2 && write(b, b"cd") == 2, "both ends write");-    let mut buf = [0u8; 8];-    c.check(read(b, &mut buf) == 2 && &buf[..2] == b"ab", "b reads a's bytes");-    c.check(read(a, &mut buf) == 2 && &buf[..2] == b"cd", "a reads b's bytes");-    let (r, cred) = peercred(a);-    c.check(r == 0 && cred[0] as i64 == getpid(), "SO_PEERCRED: our own pid");-    c.check(name_of(SYS_GETPEERNAME, a).as_deref() == Ok(""), "getpeername: unnamed (but no error)");-    // Across fork: the child talks through its copy.-    let child = fork();-    if child == 0 {-        close(a);-        let mut buf = [0u8; 16];-        let n = read(b, &mut buf);-        let ok = n == 4 && &buf[..4] == b"ping" && write(b, b"pong") == 4;-        exit(if ok { 0 } else { 1 });-    }-    write(a, b"ping");-    c.check(read(a, &mut buf) == 4 && &buf[..4] == b"pong", "a forked child answers through the pair");-    c.check(wait(child) == 0, "child happy");-    // Shutdown one way.-    c.check(syscall3(SYS_SHUTDOWN, a as u64, 1, 0) == 0, "shutdown(SHUT_WR)");-    c.check(read(b, &mut buf) == 0, "the other end reads EOF");-    c.check(send(a, b"x", MSG_NOSIGNAL) == -EPIPE, "writing after SHUT_WR: EPIPE");-    c.check(write(b, b"still") == 5 && read(a, &mut buf) == 5, "the other direction still works");-    c.check(syscall3(SYS_SHUTDOWN, a as u64, 7, 0) == -EINVAL, "shutdown(7): EINVAL");-    close(b);-    c.check(read(a, &mut buf) == 0, "peer closed: EOF");-    c.check(send(a, b"x", MSG_NOSIGNAL) == -EPIPE, "peer closed: EPIPE");-    close(a);-    // MSG_PEEK-    let (a, b) = pair(SOCK_STREAM);-    write(a, b"peekaboo");-    c.check(recv(b, &mut buf[..4], MSG_PEEK) == 4 && &buf[..4] == b"peek", "MSG_PEEK looks");-    c.check(read(b, &mut buf) == 8 && &buf == b"peekaboo", "...without taking");-    close(a);-    close(b);-    // SEQPACKET: connected, but message borders stay.-    let (a, b) = pair(SOCK_SEQPACKET);-    c.check(a >= 0, "socketpair(SOCK_SEQPACKET)");-    write(a, b"one");-    write(a, b"three");-    c.check(read(b, &mut buf) == 3 && &buf[..3] == b"one", "seqpacket: first message alone");-    c.check(read(b, &mut buf[..2]) == 2 && &buf[..2] == b"th", "seqpacket: a short read...");-    c.check(recv(b, &mut buf, MSG_DONTWAIT) == -EAGAIN, "...throws the rest away");-    c.check(getopt(a, SO_TYPE) == (0, 5), "SO_TYPE: SOCK_SEQPACKET");-    close(a);-    c.check(read(b, &mut buf) == 0, "seqpacket: peer closed, EOF");-    close(b);-    // DGRAM pair.-    let (a, b) = pair(SOCK_DGRAM);-    send(a, b"x", 0);-    send(a, b"yz", 0);-    c.check(read(b, &mut buf) == 1 && read(b, &mut buf) == 2, "dgram pair: two messages, two reads");-    c.check(send(a, b"", 0) == 0 && read(b, &mut buf) == 0, "an empty datagram is a message too");-    close(a);-    close(b);-    // AF_INET can't do pairs; other families don't exist.-    let mut sv = [0i32; 2];-    c.check(syscall5(SYS_SOCKETPAIR, 2, 1, 0, sv.as_mut_ptr() as u64, 0) == -EOPNOTSUPP, "socketpair(AF_INET): EOPNOTSUPP");-    c.check(syscall5(SYS_SOCKETPAIR, 1, 1, 0, 0, 0) == -EFAULT, "socketpair to a bad pointer: EFAULT");-    // CLOEXEC / NONBLOCK flags.-    let (a, b) = pair(SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK);-    c.check(fcntl(a, F_GETFD, 0) == FD_CLOEXEC as i64 && fcntl(b, F_GETFD, 0) == FD_CLOEXEC as i64, "SOCK_CLOEXEC sets FD_CLOEXEC");-    c.check(read(a, &mut buf) == -EAGAIN, "SOCK_NONBLOCK: EAGAIN");-    close(a);-    close(b);-}--// --- datagrams ----------------------------------------------------------------------------fn dgram(c: &mut Checks) {-    unlink("/tmp/d1.sock");-    let a = usock(SOCK_DGRAM);-    let b = usock(SOCK_DGRAM);-    c.check(ubind(a, "/tmp/d1.sock") == 0 && ubind(b, "@dgram-b") == 0, "bind two datagram sockets");-    c.check(send_to(b, b"to a", "/tmp/d1.sock") == 4, "sendto a path");-    c.check(send_to(a, b"to b!", "@dgram-b") == 5, "sendto an abstract name");-    let mut buf = [0u8; 16];-    let (n, from) = recv_from(a, &mut buf, 0);-    c.check(n == 4 && from == "@dgram-b", &format!("recvfrom says who sent it ({})", from));-    let (n, from) = recv_from(b, &mut buf, 0);-    c.check(n == 5 && from == "/tmp/d1.sock", &format!("...a path, too ({})", from));-    // Truncation.-    send_to(b, b"0123456789", "/tmp/d1.sock");-    c.check(recv(a, &mut buf[..4], MSG_TRUNC) == 10, "MSG_TRUNC: the real length");-    send_to(b, b"0123456789", "/tmp/d1.sock");-    let g = recvmsg(a, 4, 0, 0);-    c.check(g.n == 4 && g.flags & 0x20 != 0, "recvmsg: MSG_TRUNC in msg_flags");-    // From an unnamed socket.-    let u = usock(SOCK_DGRAM);-    send_to(u, b"anon", "@dgram-b");-    let (n, from) = recv_from(b, &mut buf, 0);-    c.check(n == 4 && from.is_empty(), "from an unbound socket: no name");-    c.check(send(u, b"x", 0) == -ENOTCONN, "send with no address, not connected: ENOTCONN");-    c.check(send_to(u, b"x", "@nobody-here") == -ECONNREFUSED, "sendto a name nobody has: ECONNREFUSED");-    // connect sets the default destination; then only that peer may send to us.-    c.check(uconnect(u, "@dgram-b") == 0 && send(u, b"conn", 0) == 4, "connect + send");-    c.check(recv(b, &mut buf, 0) == 4, "...arrives");-    c.check(uconnect(b, "/tmp/d1.sock") == 0, "b connects to a");-    c.check(send_to(u, b"x", "@dgram-b") == -EPERM, "b is connected to a: others get EPERM");-    c.check(name_of(SYS_GETPEERNAME, b).as_deref() == Ok("/tmp/d1.sock"), "getpeername on a connected datagram socket");-    // Disconnect with AF_UNSPEC.-    let unspec = [0u8; 2];-    c.check(syscall3(SYS_CONNECT, b as u64, unspec.as_ptr() as u64, 2) == 0 && send_to(u, b"ok", "@dgram-b") == 2, "connect(AF_UNSPEC) undoes it");-    recv(b, &mut buf, 0);-    // Wrong kinds.-    let s = usock(SOCK_STREAM);-    c.check(uconnect(s, "@dgram-b") == -EPROTOTYPE, "a stream socket connecting to a datagram one: EPROTOTYPE");-    close(s);-    c.check(send_to(b, &alloc::vec![0u8; 300_000], "/tmp/d1.sock") == -EMSGSIZE, "a datagram bigger than the buffer: EMSGSIZE");-    // The peer goes away.-    close(a);-    c.check(send_to(b, b"x", "/tmp/d1.sock") == -ECONNREFUSED, "sendto a closed socket's name: ECONNREFUSED");-    c.check(send(u, b"x", 0) >= 0, "(u is still connected to b)");-    close(b);-    c.check(send(u, b"x", 0) == -ECONNREFUSED, "connected peer closed: ECONNREFUSED");-    close(u);-    unlink("/tmp/d1.sock");-}--// --- SCM_RIGHTS -----------------------------------------------------------------------------fn rights(c: &mut Checks) {-    let fd = open("/tmp/rights.txt", O_RDWR | O_CREAT | O_TRUNC);-    write(fd, b"file data");-    lseek(fd, 0, SEEK_SET);-    let (pr, pw) = pipe().unwrap();-    let (a, b) = pair(SOCK_STREAM);-    c.check(sendmsg(a, &[b"fd", b"s!"], &[fd as i32, pr as i32], 0) == 4, "sendmsg with two fds (a file and a pipe end)");-    close(fd);-    close(pr);-    let g = recvmsg(b, 16, 64, 0);-    c.check(g.n == 4 && g.data == b"fds!", "recvmsg: the data, gathered and scattered");-    c.check(g.fds.len() == 2 && g.flags & MSG_CTRUNC == 0, &format!("recvmsg: two new fds ({:?})", g.fds));-    if g.fds.len() == 2 {-        let (f, p) = (g.fds[0] as i64, g.fds[1] as i64);-        c.check(read_all(f) == b"file data", "read the file through the received fd");-        write(pw, b"pipe data");-        close(pw);-        c.check(read_all(p) == b"pipe data", "read the pipe through the received end");-        c.check(fcntl(f, F_GETFD, 0) == 0, "no FD_CLOEXEC without MSG_CMSG_CLOEXEC");-        close(f);-        close(p);-    } else {-        close(pw);-    }-    // More fds than room: MSG_CTRUNC, the rest closed.-    let (pr, pw) = pipe().unwrap();-    let three = [pr as i32, pr as i32, pr as i32];-    c.check(sendmsg(a, &[b"3"], &three, 0) == 1, "send the same fd three times");-    close(pr);-    let g = recvmsg(b, 8, 16 + 4, MSG_CMSG_CLOEXEC); // CMSG_LEN(4)-    c.check(g.fds.len() == 1 && g.flags & MSG_CTRUNC != 0, &format!("room for one: one fd and MSG_CTRUNC ({:?})", g.fds));-    if let Some(&f) = g.fds.first() {-        c.check(fcntl(f as i64, F_GETFD, 0) == FD_CLOEXEC as i64, "MSG_CMSG_CLOEXEC sets FD_CLOEXEC");-        close(f as i64);-    }-    c.check(write(pw, b"x") == -EPIPE, "the copies that didn't fit were closed (the pipe has no reader)");-    close(pw);-    // In-flight fds die with the socket: send a pipe's read end, never-    // receive it, close the pair. The pipe's writer sees no reader left.-    let (pr, pw) = pipe().unwrap();-    sendmsg(a, &[b"x"], &[pr as i32], 0);-    close(pr);-    c.check(write(pw, b"y") == 1, "the read end is alive while in flight");-    close(a);-    close(b);-    c.check(write(pw, b"y") == -EPIPE, "closing the sockets closed the fd in flight");-    close(pw);-    // Data before an fd message merges with it, and the read stops right-    // after it (stage 19, like Linux): "aa", then "bb"+fd, then "cc".-    let (a, b) = pair(SOCK_STREAM);-    let (pr, pw) = pipe().unwrap();-    write(a, b"aa");-    sendmsg(a, &[b"bb"], &[pr as i32], 0);-    write(a, b"cc");-    let g = recvmsg(b, 16, 64, 0);-    c.check(g.n == 4 && g.data == b"aabb" && g.fds.len() == 1, "stream: a read takes the bytes before an fd message and the message, with its fd");-    for f in g.fds {-        close(f as i64);-    }-    let g = recvmsg(b, 16, 64, 0);-    c.check(g.n == 2 && g.data == b"cc" && g.fds.is_empty(), "...and stops after it");-    // Two fd messages never merge: one batch of fds per read.-    sendmsg(a, &[b"d"], &[pr as i32], 0);-    sendmsg(a, &[b"e"], &[pw as i32], 0);-    let g1 = recvmsg(b, 16, 64, 0);-    let g2 = recvmsg(b, 16, 64, 0);-    c.check(g1.n == 1 && g1.fds.len() == 1 && g2.n == 1 && g2.fds.len() == 1, "stream: two fd messages take two reads");-    for f in g1.fds.into_iter().chain(g2.fds) {-        close(f as i64);-    }-    // A plain read() merges too, and closes the fds it can't hand over.-    write(a, b"AB");-    sendmsg(a, &[b"CD"], &[pr as i32], 0);-    close(pr);-    let mut buf4 = [0u8; 8];-    c.check(read(b, &mut buf4) == 4 && &buf4[..4] == b"ABCD", "stream: read() returns 'ABCD' across an fd message");-    c.check(write(pw, b"y") == -EPIPE, "...and the fd it dropped was closed");-    close(pw);-    // read() can't take fds: they're closed.-    let (pr, pw) = pipe().unwrap();-    sendmsg(a, &[b"z"], &[pr as i32], 0);-    close(pr);-    let mut buf = [0u8; 4];-    c.check(read(b, &mut buf) == 1, "plain read of a message with an fd");-    c.check(write(pw, b"x") == -EPIPE, "...closed the fd");-    close(pw);-    // Bad fds, bad control messages.-    c.check(sendmsg(a, &[b"z"], &[999], 0) == -EBADF, "sending fd 999: EBADF");-    // SCM_CREDENTIALS with SO_PASSCRED.-    c.check(setopt(b, SO_PASSCRED, 1) == 0 && getopt(b, SO_PASSCRED) == (0, 1), "SO_PASSCRED on");-    write(a, b"who");-    let g = recvmsg(b, 8, 64, 0);-    c.check(g.cred.is_some_and(|cr| cr[0] as i64 == getpid() && cr[1] == 0), &format!("SCM_CREDENTIALS: our pid, uid 0 ({:?})", g.cred));-    close(a);-    close(b);-    // A datagram socket can carry fds too.-    let (a, b) = pair(SOCK_DGRAM);-    let (pr, pw) = pipe().unwrap();-    sendmsg(a, &[b"d"], &[pw as i32], 0);-    close(pw);-    let g = recvmsg(b, 8, 64, 0);-    c.check(g.fds.len() == 1 && write(g.fds[0] as i64, b"via dgram") == 9, "a pipe's write end through a datagram");-    for f in g.fds {-        close(f as i64);-    }-    c.check(read_all(pr) == b"via dgram", "...and the data arrives");-    close(pr);-    close(a);-    close(b);-    unlink("/tmp/rights.txt");-}--// --- nonblocking, buffers, poll -----------------------------------------------------------fn nonblock(c: &mut Checks) {-    let (a, b) = pair(SOCK_STREAM | SOCK_NONBLOCK);-    let mut buf = [0u8; 4096];-    c.check(read(a, &mut buf) == -EAGAIN, "nothing to read: EAGAIN");-    let mut fds = [PollFd { fd: a as i32, events: POLLIN | POLLOUT, revents: 0 }];-    c.check(poll(&mut fds, 0) == 1 && fds[0].revents == POLLOUT, "poll: writable, not readable");-    // Small buffers, so they fill fast. setsockopt doubles (like Linux).-    c.check(setopt(a, SO_SNDBUF, 4096) == 0 && getopt(a, SO_SNDBUF) == (0, 8192), "SO_SNDBUF 4096 reads back 8192");-    c.check(getopt(b, SO_RCVBUF).1 == 212_992, "SO_RCVBUF default 212992");-    let mut total = 0;-    loop {-        let n = write(a, &buf);-        if n < 0 {-            c.check(n == -EAGAIN, "full: EAGAIN");-            break;-        }-        total += n;-        if total > 1 << 20 {-            break;-        }-    }-    c.check(total == 8192, &format!("the buffer limit: {} bytes went in", total));-    fds[0].events = POLLOUT;-    c.check(poll(&mut fds, 0) == 0, "poll: not writable when full");-    c.check(read(b, &mut buf[..1000]) == 1000, "read some");-    c.check(poll(&mut fds, 0) == 1, "writable again");-    while read(b, &mut buf) > 0 {}-    // Blocking backpressure: a child writes 100 KB through a small-    // buffer; it has to wait for us to read.-    let (x, y) = pair(SOCK_STREAM);-    setopt(x, SO_SNDBUF, 2048);-    let child = fork();-    if child == 0 {-        let data = alloc::vec![7u8; 100_000];-        let n = write(x, &data);-        exit(if n == 100_000 { 0 } else { 1 });-    }-    close(x);-    sleep_ms(100);-    let mut got = 0;-    loop {-        let n = read(y, &mut buf);-        if n <= 0 {-            break;-        }-        got += n;-    }-    c.check(got == 100_000, &format!("a blocked writer got everything through ({} bytes)", got));-    c.check(wait(child) == 0, "...in one write() call");-    close(y);-    // epoll: EPOLLRDHUP when the peer shuts down writing, EPOLLHUP when it's gone.-    let ep = epoll_create1(0);-    epoll_ctl(ep, EPOLL_CTL_ADD, a, EPOLLIN | EPOLLRDHUP, 1);-    let mut ev = [EpollEvent::default(); 2];-    c.check(epoll_wait(ep, &mut ev, 0) == 0, "epoll: nothing yet");-    syscall3(SYS_SHUTDOWN, b as u64, 1, 0);-    let n = epoll_wait(ep, &mut ev, 100);-    let e = ev[0].events;-    c.check(n == 1 && e & EPOLLRDHUP != 0 && e & EPOLLIN != 0 && e & EPOLLHUP == 0, &format!("peer SHUT_WR: EPOLLIN|EPOLLRDHUP (0x{:x})", e));-    close(b);-    let n = epoll_wait(ep, &mut ev, 100);-    let e = ev[0].events;-    c.check(n == 1 && e & EPOLLHUP != 0, &format!("peer closed: EPOLLHUP (0x{:x})", e));-    close(ep);-    close(a);-    // A nonblocking listener; poll says when a connection waits.-    let l = usock(SOCK_STREAM | SOCK_NONBLOCK);-    ubind(l, "@nb-listen");-    listen(l, 1);-    c.check(uaccept(l) == -EAGAIN, "accept with nobody waiting: EAGAIN");-    let mut pf = [PollFd { fd: l as i32, events: POLLIN, revents: 0 }];-    c.check(poll(&mut pf, 0) == 0, "poll: listener not readable");-    let cl = usock(SOCK_STREAM);-    c.check(uconnect(cl, "@nb-listen") == 0, "connect (goes into the backlog)");-    c.check(poll(&mut pf, 0) == 1 && pf[0].revents == POLLIN, "poll: listener readable");-    let conn = syscall5(SYS_ACCEPT4, l as u64, 0, 0, SOCK_NONBLOCK | SOCK_CLOEXEC, 0);-    c.check(conn >= 0 && fcntl(conn, F_GETFD, 0) == FD_CLOEXEC as i64, "accept4(SOCK_CLOEXEC|SOCK_NONBLOCK)");-    c.check(read(conn, &mut buf) == -EAGAIN, "...the new socket is nonblocking");-    c.check(syscall5(SYS_ACCEPT4, l as u64, 0, 0, 1, 0) == -EINVAL, "accept4 with a bad flag: EINVAL");-    // A full backlog: a nonblocking connect says EAGAIN.-    let mut extra = Vec::new();-    let mut r = 0;-    for _ in 0..5 {-        let s = usock(SOCK_STREAM | SOCK_NONBLOCK);-        r = uconnect(s, "@nb-listen");-        extra.push(s);-        if r < 0 {-            break;-        }-    }-    c.check(r == -EAGAIN, "backlog full: nonblocking connect gets EAGAIN");-    for s in extra {-        close(s);-    }-    close(conn);-    close(cl);-    // Unconnected stream socket: POLLOUT|POLLHUP (like Linux).-    let s = usock(SOCK_STREAM);-    let mut pf = [PollFd { fd: s as i32, events: POLLIN | POLLOUT, revents: 0 }];-    poll(&mut pf, 0);-    c.check(pf[0].revents == POLLOUT | POLLHUP, "an unconnected stream socket polls OUT|HUP");-    close(s);-    close(l);-    // recv(MSG_DONTWAIT) on a blocking socket.-    let (a, b) = pair(SOCK_STREAM);-    c.check(recv(a, &mut buf, MSG_DONTWAIT) == -EAGAIN, "MSG_DONTWAIT: EAGAIN on a blocking socket");-    // SO_RCVTIMEO-    set_recv_timeout(a, 100);-    let t = now_us();-    c.check(read(a, &mut buf) == -EAGAIN && now_us() - t >= 90_000, "SO_RCVTIMEO: EAGAIN after the timeout");-    c.check(send_to(a, b"x", "@somewhere") == -EISCONN, "sendto with an address on a connected stream: EISCONN");-    let mut v = 0u32;-    c.check(ioctl(b, 0x541B, &mut v as *mut u32 as u64) == 0 && v == 0, "FIONREAD 0");-    write(a, b"12345");-    c.check(ioctl(b, 0x541B, &mut v as *mut u32 as u64) == 0 && v == 5, "FIONREAD 5");-    close(a);-    close(b);-}--// --- /proc/self -------------------------------------------------------------------------fn proc(c: &mut Checks) {-    let f = open("/tmp/proc.txt", O_RDWR | O_CREAT | O_TRUNC);-    write(f, b"proc data");-    let (pr, pw) = pipe().unwrap();-    let (a, b) = pair(SOCK_STREAM);-    // The listing.-    let dir = open("/proc/self/fd", O_RDONLY | O_DIRECTORY);-    c.check(dir >= 0, "open /proc/self/fd");-    let mut names = Vec::new();-    let mut buf = [0u8; 2048];-    loop {-        let n = getdents64(dir, &mut buf);-        if n <= 0 {-            break;-        }-        for_each_dirent(&buf, n as usize, |name, _| names.push(String::from(name)));-    }-    close(dir);-    let want = [0, 1, 2, f, pr, pw, a, b, dir];-    c.check(want.iter().all(|fd| names.iter().any(|n| *n == format!("{}", fd))), &format!("the listing has every fd ({:?})", names));-    c.check(!names.iter().any(|n| *n == "99"), "...and no others");-    // readlink-    c.check(link(&format!("/proc/self/fd/{}", f)) == "/tmp/proc.txt", "readlink: a file's path");-    let pl = link(&format!("/proc/self/fd/{}", pr));-    let mut st = Stat::default();-    fstat(pr, &mut st);-    c.check(pl == format!("pipe:[{}]", st.ino) && link(&format!("/proc/self/fd/{}", pw)) == pl, &format!("readlink: {} for both pipe ends (= st_ino)", pl));-    let sl = link(&format!("/proc/self/fd/{}", a));-    fstat(a, &mut st);-    c.check(sl == format!("socket:[{}]", st.ino) && is_socket(&st), &format!("readlink: {} (= st_ino, S_IFSOCK)", sl));-    c.check(link(&format!("/proc/self/fd/{}", b)) != sl, "the other end is another socket");-    let mut ls = Stat::default();-    c.check(lstat(&format!("/proc/self/fd/{}", f), &mut ls) == 0 && ls.is_symlink(), "lstat: a symlink");-    c.check(stat(&format!("/proc/self/fd/{}", a), &mut ls) == 0 && is_socket(&ls) && ls.ino == st.ino, "stat follows it, even to a socket");-    c.check(link("/proc/self/fd/99") == "error -2", "readlink of a closed fd: ENOENT");-    let exe = link("/proc/self/exe");-    c.check(exe.ends_with("/unixtest"), &format!("/proc/self/exe: {}", exe));-    // Opening an entry.-    let again = open(&format!("/proc/self/fd/{}", f), O_RDONLY);-    c.check(read_all(again) == b"proc data", "open /proc/self/fd/N of a file: a fresh offset");-    close(again);-    let rd = open(&format!("/proc/self/fd/{}", pr), O_RDONLY);-    write(pw, b"via proc");-    let mut b8 = [0u8; 8];-    c.check(rd >= 0 && read(rd, &mut b8) == 8 && &b8 == b"via proc", "open /proc/self/fd/N of a pipe: the same pipe");-    close(rd);-    c.check(open(&format!("/proc/self/fd/{}", a), O_RDWR) == -ENXIO, "open /proc/self/fd/N of a socket: ENXIO");-    c.check(open("/proc/self/fd/99", O_RDONLY) == -ENOENT, "open of a closed fd: ENOENT");-    let null = open("/dev/null", O_RDONLY);-    c.check(link(&format!("/proc/self/fd/{}", null)) == "/dev/null", "readlink: /dev/null");-    close(null);-    let ep = epoll_create1(0);-    c.check(link(&format!("/proc/self/fd/{}", ep)) == "anon_inode:[eventpoll]", "readlink: anon_inode:[eventpoll]");-    close(ep);-    let mut root = Vec::new();-    let d = open("/proc", O_RDONLY | O_DIRECTORY);-    let n = getdents64(d, &mut buf);-    if n > 0 {-        for_each_dirent(&buf, n as usize, |name, _| root.push(String::from(name)));-    }-    close(d);-    c.check(root.iter().any(|n| n == "self"), "/proc lists self");-    c.check(mkdir("/proc/self/x") < 0, "nothing can be made in /proc");-    // A child sees its own fds.-    let child = fork();-    if child == 0 {-        close(f);-        exit(if link(&format!("/proc/self/fd/{}", f)) == "error -2" { 0 } else { 1 });-    }-    c.check(wait(child) == 0 && link(&format!("/proc/self/fd/{}", f)) == "/tmp/proc.txt", "self is whoever asks");-    for fd in [f, pr, pw, a, b] {-        close(fd);-    }-    unlink("/tmp/proc.txt");-}
import/kernel-user/vdsotest.c
@@ -1,251 +0,0 @@-/*- * vdsotest.c -- stage 19: the vDSO, checked from a real C library (musl).- *- * Built with `musl-gcc -static` as a classic ET_EXEC program (linked at- * Rustbelt's user base, see build.rs). musl finds the vDSO through- * AT_SYSINFO_EHDR and then calls __vdso_clock_gettime instead of making a- * system call. We compare what the vDSO says with what the real system- * call says, check the clocks never go backwards, and that it all still- * works in a forked child and after execve.- *- *   vdsotest            all checks (exit code = number of failed checks)- *   vdsotest calls      just call the clock functions a lot, print nothing- *                       (tests/vdso.rs looks at the --trace output)- *   vdsotest exec       (used internally: the program after execve)- */-#define _GNU_SOURCE-#include <elf.h>-#include <errno.h>-#include <sched.h>-#include <signal.h>-#include <stdio.h>-#include <stdlib.h>-#include <string.h>-#include <sys/auxv.h>-#include <sys/mman.h>-#include <sys/syscall.h>-#include <sys/time.h>-#include <sys/wait.h>-#include <time.h>-#include <unistd.h>--static int fails;-#define CHECK(cond, ...)                                              \-	do {                                                          \-		if (!(cond)) {                                        \-			fails++;                                      \-			printf("vdsotest: FAIL line %d: ", __LINE__); \-			printf(__VA_ARGS__);                          \-			printf("\n");                                 \-		}                                                     \-	} while (0)--extern char _start[]; /* musl's entry point (crt1.o) */--static long long ns(struct timespec t) { return t.tv_sec * 1000000000LL + t.tv_nsec; }-static long long us(struct timeval t) { return t.tv_sec * 1000000LL + t.tv_usec; }--/* The real system call, bypassing the C library (and so the vDSO). */-static long long sys_ns(int clock)-{-	struct timespec t;-	if (syscall(SYS_clock_gettime, clock, &t) != 0)-		return -1;-	return ns(t);-}--static long long vdso_ns(int clock)-{-	struct timespec t;-	if (clock_gettime(clock, &t) != 0)-		return -1;-	return ns(t);-}--/* The image behind AT_SYSINFO_EHDR is an ELF shared object, and the page- * in front of it (the vvar page) is readable but can't be made writable. */-static void image(void)-{-	unsigned long base = getauxval(AT_SYSINFO_EHDR);-	CHECK(base != 0, "no AT_SYSINFO_EHDR");-	if (!base)-		return;-	const Elf64_Ehdr *eh = (const Elf64_Ehdr *)base;-	CHECK(memcmp(eh->e_ident, ELFMAG, SELFMAG) == 0, "vDSO has no ELF magic");-	CHECK(eh->e_type == ET_DYN, "vDSO e_type %d", eh->e_type);-	void *vvar = (void *)(base - 4096);-	unsigned seq = *(volatile unsigned *)vvar;-	CHECK(seq % 2 == 0, "vvar seq %u is odd", seq);-	errno = 0;-	CHECK(mprotect(vvar, 4096, PROT_READ | PROT_WRITE) == -1 && errno == EACCES, "mprotect(vvar, RW): errno %d", errno);-	/* Other auxv entries a C library looks at. */-	CHECK(getauxval(AT_PAGESZ) == 4096, "AT_PAGESZ");-	CHECK(getauxval(AT_CLKTCK) == 100, "AT_CLKTCK");-	CHECK(getauxval(AT_HWCAP) & (1 << 4), "AT_HWCAP has no TSC bit");-	CHECK(getauxval(AT_RANDOM) != 0, "AT_RANDOM");-	CHECK(getauxval(AT_SECURE) == 0, "AT_SECURE");-	CHECK(getauxval(AT_ENTRY) == (unsigned long)_start, "AT_ENTRY %lx", getauxval(AT_ENTRY));-	CHECK(getauxval(AT_PHNUM) > 0 && getauxval(AT_PHENT) == sizeof(Elf64_Phdr), "AT_PHNUM/AT_PHENT");-	CHECK(getauxval(AT_BASE) == 0, "AT_BASE (no dynamic loader)");-	const char *platform = (const char *)getauxval(AT_PLATFORM);-	CHECK(platform && strcmp(platform, "x86_64") == 0, "AT_PLATFORM");-	const char *execfn = (const char *)getauxval(AT_EXECFN);-	CHECK(execfn && strstr(execfn, "vdsotest"), "AT_EXECFN");-}--/* Each fast clock: vDSO, then syscall, then vDSO again must be in order- * (they compute the same formula from the same numbers); and the clocks- * never go backwards. */-static void clocks(void)-{-	static const int fast[] = { CLOCK_REALTIME, CLOCK_MONOTONIC, CLOCK_MONOTONIC_RAW, CLOCK_REALTIME_COARSE,-				    CLOCK_MONOTONIC_COARSE };-	for (unsigned i = 0; i < sizeof fast / sizeof fast[0]; i++) {-		int c = fast[i];-		for (int round = 0; round < 100; round++) {-			long long a = vdso_ns(c), b = sys_ns(c), d = vdso_ns(c);-			if (!(a > 0 && a <= b && b <= d)) {-				CHECK(0, "clock %d out of order: vdso %lld, syscall %lld, vdso %lld", c, a, b, d);-				break;-			}-		}-	}-	long long prev = vdso_ns(CLOCK_MONOTONIC);-	for (int i = 0; i < 100000; i++) {-		long long now = vdso_ns(CLOCK_MONOTONIC);-		if (now < prev) {-			CHECK(0, "CLOCK_MONOTONIC went back from %lld to %lld", prev, now);-			break;-		}-		prev = now;-	}-	/* Nanoseconds really: two readings in a row usually differ by less-	 * than a microsecond, but they must not be stuck on whole µs. */-	int sub_us = 0;-	for (int i = 0; i < 100 && !sub_us; i++)-		sub_us = vdso_ns(CLOCK_MONOTONIC) % 1000 != 0;-	CHECK(sub_us, "CLOCK_MONOTONIC only has microseconds");-	/* The wall clock is about now (after 2020), and REALTIME = MONOTONIC + boot. */-	CHECK(vdso_ns(CLOCK_REALTIME) / 1000000000 > 1577836800LL, "CLOCK_REALTIME is before 2020");-	/* Other clocks take the syscall path inside the vDSO, and still work. */-	struct timespec t;-	CHECK(clock_gettime(CLOCK_BOOTTIME, &t) == 0 && ns(t) > 0, "CLOCK_BOOTTIME");-	CHECK(clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &t) == 0, "CLOCK_PROCESS_CPUTIME_ID");-	CHECK(clock_gettime(CLOCK_THREAD_CPUTIME_ID, &t) == 0, "CLOCK_THREAD_CPUTIME_ID");-	errno = 0;-	CHECK(clock_gettime(12345, &t) == -1 && errno == EINVAL, "bad clock: errno %d", errno);--	/* gettimeofday and time: also from the vDSO, also agreeing. */-	struct timeval tv1, tv2, tvs;-	struct timezone tz = { 99, 99 };-	gettimeofday(&tv1, NULL);-	CHECK(syscall(SYS_gettimeofday, &tvs, &tz) == 0, "gettimeofday syscall");-	gettimeofday(&tv2, NULL);-	CHECK(us(tv1) <= us(tvs) && us(tvs) <= us(tv2), "gettimeofday out of order: %lld %lld %lld", us(tv1), us(tvs),-	      us(tv2));-	CHECK(tz.tz_minuteswest == 0 && tz.tz_dsttime == 0, "timezone not UTC");-	CHECK(tv1.tv_usec >= 0 && tv1.tv_usec < 1000000, "tv_usec %ld", (long)tv1.tv_usec);-	time_t t1 = time(NULL), ts = syscall(SYS_time, NULL), t2;-	time(&t2);-	CHECK(t1 <= ts && ts <= t2 && t2 - t1 < 2, "time out of order: %ld %ld %ld", (long)t1, (long)ts, (long)t2);-	CHECK(t1 == tv1.tv_sec || t1 == tv1.tv_sec + 1, "time %ld vs gettimeofday %ld", (long)t1, (long)tv1.tv_sec);--	/* sched_getcpu (getcpu in the vDSO): one of our 4 CPUs. */-	int cpu = sched_getcpu();-	CHECK(cpu >= 0 && cpu < 64, "sched_getcpu %d", cpu);-	unsigned scpu = 999, node = 999;-	CHECK(syscall(SYS_getcpu, &scpu, &node, NULL) == 0 && scpu < 64 && node == 0, "getcpu syscall %u %u", scpu, node);-	/* Pin ourselves? No sched_setaffinity here: just check both agree-	 * at least once in a few tries (the scheduler may move us between). */-	int same = 0;-	for (int i = 0; i < 50 && !same; i++) {-		cpu = sched_getcpu();-		syscall(SYS_getcpu, &scpu, NULL, NULL);-		same = (unsigned)cpu == scpu;-	}-	CHECK(same, "sched_getcpu %d never matched getcpu %u", cpu, scpu);-}--/* Writing to the vvar page is a SIGSEGV (it's mapped read-only). */-static void vvar_is_read_only(void)-{-	unsigned long base = getauxval(AT_SYSINFO_EHDR);-	if (!base)-		return;-	pid_t pid = fork();-	if (pid == 0) {-		*(volatile unsigned *)(base - 4096) = 1;-		_exit(0);-	}-	int status = 0;-	waitpid(pid, &status, 0);-	CHECK(WIFSIGNALED(status) && WTERMSIG(status) == SIGSEGV, "write to vvar: status %#x", status);-}--/* A forked child has the vDSO too (same pages, same answers). */-static void after_fork(void)-{-	long long before = vdso_ns(CLOCK_MONOTONIC);-	pid_t pid = fork();-	if (pid == 0) {-		long long a = vdso_ns(CLOCK_MONOTONIC), b = sys_ns(CLOCK_MONOTONIC), c = vdso_ns(CLOCK_MONOTONIC);-		_exit(getauxval(AT_SYSINFO_EHDR) && a >= before && a <= b && b <= c ? 0 : 1);-	}-	int status = 0;-	waitpid(pid, &status, 0);-	CHECK(WIFEXITED(status) && WEXITSTATUS(status) == 0, "vDSO after fork: status %#x", status);-}--/* And a program started by execve gets a fresh one. */-static void after_exec(const char *self)-{-	pid_t pid = fork();-	if (pid == 0) {-		char *argv[] = { (char *)self, "exec", NULL };-		execv(self, argv);-		_exit(99);-	}-	int status = 0;-	waitpid(pid, &status, 0);-	CHECK(WIFEXITED(status) && WEXITSTATUS(status) == 0, "vdsotest after execve: status %#x", status);-}--/* A dynamically linked program (PT_INTERP) can't run here: ENOEXEC. */-static void dynamic_is_enoexec(void)-{-	char *argv[] = { "dynhello", NULL };-	errno = 0;-	execv("/mnt/disk0/bin/dynhello", argv);-	CHECK(errno == ENOEXEC, "execve of a dynamic program: errno %d", errno);-}--int main(int argc, char **argv)-{-	if (argc > 1 && strcmp(argv[1], "calls") == 0) {-		/* Lots of clock reads; tests/vdso.rs checks none is a syscall. */-		struct timespec t;-		struct timeval tv;-		long long sum = 0;-		for (int i = 0; i < 1000; i++) {-			clock_gettime(CLOCK_MONOTONIC, &t);-			sum += t.tv_nsec;-			clock_gettime(CLOCK_REALTIME, &t);-			gettimeofday(&tv, NULL);-			sum += time(NULL) + sched_getcpu();-		}-		return sum == 42; /* use the result; 0 */-	}-	if (argc > 1 && strcmp(argv[1], "exec") == 0) {-		image();-		clocks();-		return fails;-	}-	image();-	clocks();-	vvar_is_read_only();-	after_fork();-	after_exec((const char *)getauxval(AT_EXECFN));-	dynamic_is_enoexec();-	printf("vdsotest: %d failed\n", fails);-	return fails;-}