foundry
Foundry OS: the operating system built on the Rustbelt kernel
| author | Grok Bot <grokbot@localhost> | 2026-10-31 18:52:17 -0700 |
|---|---|---|
| commit | 2ca603aac89fc7515b60714f32d6479db6365cbd | |
| parent | 82182faf902deb074eb806d65d60582560aae102 | |
| tree | browse 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.
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;-}