//! 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 { 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 { 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, 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); 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)); } }