foundry
Foundry OS: the operating system built on the Rustbelt kernel
| author | Edward <edward@localhost> | 2026-10-31 16:49:32 -0700 |
|---|---|---|
| commit | e6eb9917e47dda5cf57b32256ee0c85032c1c600 | |
| parent | a2549ec94f2e8038864bad4255e7a965950fc947 | |
| tree | browse files at this commit | |
Stage 17: coreutils batch 2 (0.17.0) The ENOSYS calls from Clarke's coreutils/busybox (tier 3) traces, in his priority order. New calls live in src/syscall_ext.rs; timers in src/timer.rs. - link/linkat, symlink/symlinkat (ramfs, ext2; EPERM on FAT/exFAT, EROFS on btrfs, EXDEV across mounts, EMLINK); native rename of full directories, renameat2 RENAME_NOREPLACE; real st_ino/st_nlink/st_dev/owner in stat - chown/fchown/lchown/fchownat (stored on ramfs/ext2; vfat-like on FAT) - statfs/fstatfs with Linux magic numbers, statx - Linux sysinfo (99); ours stays 502, traced as rb_sysinfo - times, getrusage, CPU-time clocks (10 ms sampling), clock_getres - setsockopt options accepted/remembered, getsockopt, getsockname, getpeername, shutdown; socketpair -> EAFNOSUPPORT (no AF_UNIX) - alarm, getitimer/setitimer (ITIMER_REAL), POSIX timers (timerd thread) - real (unenforced) uid/gid sets, groups, fs ids, nice values, sethostname/setdomainname - flock, preadv/pwritev, sendfile - fixes: getdents64 skipped entries while deleting (rm -r); --trace shows the user-visible result of an interrupted call; sigsetsize checks for ppoll/pselect6/epoll_pwait; the headless frame report waits for all tasks - user/coretest.rs + tests/coreutils_batch2.rs, README stage 17 chapter Deferred: socketpair/AF_UNIX, timerfd, mremap, ITIMER_VIRTUAL/PROF.
| A | coretest.rs | +885 -0 |
| M | nbtest.rs | +1 -1 |
| M | ulib.rs | +2 -2 |
3 files changed, 888 insertions, 3 deletions
@@ -0,0 +1,885 @@+//! 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");+ // 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, 1000], "clock_getres(MONOTONIC): 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) == -EAFNOSUPPORT, "socketpair(AF_UNIX): EAFNOSUPPORT (no AF_UNIX yet)");+ 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);++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);+ }+ 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");+ // 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));+ }+}
@@ -137,7 +137,7 @@ fn main() -> i64 { 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 16"), "uname: Rustbelt, stage 16");+ c.check(u[0] == "Rustbelt" && u[3].starts_with("stage 17"), "uname: Rustbelt, stage 17"); let (secs, _) = clock_gettime(0); c.check(secs > 1_600_000_000, "CLOCK_REALTIME is a real date (after 2020)");
@@ -83,7 +83,7 @@ pub const AT_EMPTY_PATH: u64 = 0x1000; /// Longest path the kernel takes, counting the NUL. pub const PATH_MAX: usize = 4096; pub const SYS_MOUNTS: u64 = 501;-pub const SYS_SYSINFO: u64 = 502;+pub const SYS_RB_SYSINFO: u64 = 502; // ours (Linux's sysinfo is 99) pub const SYS_SOCKET: u64 = 41; pub const SYS_CONNECT: u64 = 42; pub const SYS_ACCEPT: u64 = 43;@@ -686,7 +686,7 @@ pub struct SysInfo { /// Ask the kernel for CPU, thread, process and memory counts. pub fn sysinfo() -> SysInfo { let mut info = SysInfo::default();- syscall3(SYS_SYSINFO, &mut info as *mut SysInfo as u64, 0, 0);+ syscall3(SYS_RB_SYSINFO, &mut info as *mut SysInfo as u64, 0, 0); info } /// Current directory into `buf`; returns its length (or -errno). The