rust-kernel
Rustbelt: a hobby x86_64 kernel written in Rust
| author | Edward <edward@localhost> | 2026-10-31 16:49:32 -0700 |
|---|---|---|
| commit | fc5e78aac8e1fcad62e645581d205f6b5b391b7a | |
| parent | 63fcb8621f453f1445fea724b37bf39479a23d9f | |
| 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.
| M | Cargo.lock | +1 -1 |
| M | Cargo.toml | +1 -1 |
| M | README.md | +361 -19 |
| M | build.rs | +1 -0 |
| M | scripts/run-headless.sh | +6 -6 |
| M | scripts/screenshot.sh | +1 -1 |
| M | src/dev.rs | +2 -2 |
| M | src/errno.rs | +5 -2 |
| M | src/fs/btrfs.rs | +60 -1 |
| M | src/fs/exfat.rs | +17 -0 |
| M | src/fs/ext2.rs | +230 -4 |
| M | src/fs/fat.rs | +21 -1 |
| M | src/fs/file.rs | +56 -5 |
| M | src/fs/lock.rs | +70 -2 |
| M | src/fs/mod.rs | +180 -23 |
| M | src/fs/ramfs.rs | +183 -8 |
| M | src/interrupts.rs | +8 -0 |
| M | src/lib.rs | +2 -0 |
| M | src/main.rs | +14 -1 |
| M | src/memory.rs | +15 -0 |
| M | src/net/socket.rs | +105 -3 |
| M | src/poll.rs | +18 -3 |
| M | src/process.rs | +71 -0 |
| M | src/signal.rs | +21 -5 |
| M | src/syscall.rs | +77 -62 |
| A | src/syscall_ext.rs | +1134 -0 |
| A | src/timer.rs | +278 -0 |
| A | tests/coreutils_batch2.rs | +129 -0 |
| M | tests/shell.rs | +1 -1 |
| A | user/coretest.rs | +885 -0 |
| M | user/nbtest.rs | +1 -1 |
| M | user/ulib.rs | +2 -2 |
32 files changed, 3956 insertions, 154 deletions
@@ -261,7 +261,7 @@ dependencies = [ [[package]] name = "rustbelt"-version = "0.16.0"+version = "0.17.0" dependencies = [ "bootloader", "conquer-once",
@@ -1,6 +1,6 @@ [package] name = "rustbelt"-version = "0.16.0"+version = "0.17.0" edition = "2024" [dependencies]
@@ -997,6 +997,7 @@ heap_allocation::simple_allocation... [ok] | `tests/libc.rs` | **libc plumbing** (stage 15), by running four user test programs twice each (a warm-up, then a run that must leave the thread, process, futex-waiter, pty, socket and frame counts exactly as they were): `threads` (4 threads sum 1..=40,000,000 on several CPUs; a futex mutex keeps a shared counter exact while an unlocked one loses updates; each thread sees its own TLS value through `fs:`; `gettid`; `FUTEX_WAIT` with a stale value is `EAGAIN`, times out, `FUTEX_WAKE` with no waiters; `CLONE_CHILD_CLEARTID` + futex wake for join; `exit` ends one thread, `exit_group` all; `tgkill` and `ESRCH`); `echoserver` (an epoll server and a poll server on 127.0.0.1, three forked clients each, plus UDP over loopback); `ptytest` (canonical mode holds a half line back, backspace editing, CR -> NL, echo, ONLCR; raw mode passes bytes through without echo; `TCGETS`/`TCSETS`; ^C and ^\ kill the child that made the pty its controlling terminal with SIGINT/SIGQUIT and echo "^C"; `/dev/tty` with and without a controlling terminal; closing the master gives the slave EOF); `nbtest` (`EAGAIN` from pipes, sockets and ptys; 64 KiB pipe capacity; poll bits; `fcntl(F_SETFL, O_NONBLOCK)` and `FIONBIO`; nonblocking `connect` is `EINPROGRESS` then `POLLOUT`; `select`, `ppoll`, `readv`/`writev`, `pread`/`pwrite`, `getcwd`, `uname`, `CLOCK_REALTIME`, `nanosleep`, `readlink /proc/self/exe`) | | `tests/musl_prereq.rs` | **musl prerequisites** (stage 15.1), each program twice with no leaks: the FPU is set up (XSAVE with x87 + SSE (+ AVX) under `-cpu max`); `fputest` (16 xmm/ymm registers with a per-thread pattern in 4 threads x 3 processes at once, fork copies them, exec clears them, a signal handler gets clean registers and Linux's fpstate in its frame and may edit it, 40 async signals mid-check, SIGFPE when TCG raises it); `pathtest` (`EFAULT` for NULL/kernel/unmapped/straddling path pointers, `ENAMETOOLONG`, `struct stat` fields and types, `lstat`, `AT_EMPTY_PATH`, every `*at()` call, `O_EXCL`/`O_DIRECTORY`/`O_NOFOLLOW`, `fchdir`, `rename`); `rawabi` (a gcc-built no-libc C program, unmodified) | | `tests/libc_batch1.rs` | **libc batch 1** (stage 16): `libctest` runs one group per test, twice, and nothing (threads, processes, frames, file locks) may be left over: `wait` (dash's `wait`: SIGCHLD blocked, fork, `rt_sigsuspend` returns `EINTR` once the handler ran and puts the old mask back; a SIGCHLD already pending ends it at once); `signals` (`rt_sigpending`, `rt_sigtimedwait` with and without a timeout and from another process, real-time signals queued 3 times with their values, a standard signal only once, `EPERM`/`ESRCH` for `rt_sigqueueinfo`, `sigaltstack` + `SA_ONSTACK`, `EPERM` on the stack, unknown `sa_flags` dropped); `locks` (`F_GETFL` access modes, `F_SETFL`, `F_GETLK`/`F_SETLK`/`F_SETLKW` between two processes, `EDEADLK`, locks dropped on close and on exit, `RLIMIT_NOFILE` vs `dup` and `F_DUPFD`); `files` (`truncate`/`ftruncate`/`fsync` on ramfs, ext2 and FAT, `umask` on `open` and `mkdir` and across fork, `chmod`/`fchmod`/`fchmodat`, `utimensat` with `UTIME_NOW`/`UTIME_OMIT`, `EROFS` on btrfs); `misc` (rlimits, `prctl`, `sched_getaffinity`, `madvise(MADV_DONTNEED)` reads zeros, a robust futex gets `FUTEX_OWNER_DIED`); `orphans` (running and zombie orphans are freed, a `PR_SET_CHILD_SUBREAPER` process adopts and reaps a grandchild); `--trace` names unknown calls `syscall_N` |+| `tests/coreutils_batch2.rs` | **coreutils batch 2** (stage 17): `coretest` runs one group per test, twice, and nothing (threads, processes, frames, file locks, timers) may be left over: `links` (`link`/`linkat`/`symlink`/`symlinkat` on ramfs and ext2 with `st_nlink` and `st_ino`, a dangling symlink, renaming a full directory and over a second name, `EPERM` on FAT/exFAT, `EROFS` on btrfs, `EXDEV`); `owners` (the `chown` family on ramfs and ext2, `-1`, `lchown` vs `chown` on a symlink, FAT's `EPERM` unless nothing changes); `statfs` (`statfs`/`fstatfs` magic numbers and sizes per mount, `ST_RDONLY`, pipes, `statx` vs `stat`, `st_dev` per mount); `info` (`sysinfo` 99 and 502, `times`, `getrusage` for self and children, the CPU-time clocks, `clock_getres`); `sockets` (`getsockname`/`getpeername`, `SO_REUSEADDR` & co., `SO_TYPE`, `SO_ERROR` after a refused connect, `SHUT_WR` = end of file for the peer, `EPIPE` after it, `socketpair` refused); `timers` (`alarm`, one-shot and periodic `setitimer`, `pause`, POSIX timers with `SI_TIMER` + `si_value`, `TIMER_ABSTIME`, `SIGEV_NONE`, not inherited by fork); `ids` (`setuid` & co. with the saved id, groups, new files owned by their creator, nice values, `sethostname` -> `uname`); `misc` (`flock` between open files and processes, `preadv`/`pwritev`, `sendfile`, mask sizes for `ppoll`/`pselect6`, a `ppoll` mask letting a signal in, `getdents64` while deleting on ramfs and ext2); `--trace` names (`link`, `statx`, `rb_sysinfo`) | How it works: `#![feature(custom_test_frameworks)]` collects every `#[test_case]` function and passes them to `rustbelt::test_runner` (in `src/lib.rs`), which@@ -1119,6 +1120,9 @@ user/{fputest,pathtest}.rs stage 15.1: per-thread FPU/SSE/AVX state + signal fp C-string path calls (tests/musl_prereq.rs runs them, and rawabi) user/rawabi.c stage 15.1: a no-libc Linux C program (gcc -nostdlib): raw syscalls, C paths, SSE user/libctest.rs stage 16: libc batch 1 checks, one group per argument (tests/libc_batch1.rs)+user/coretest.rs stage 17: coreutils batch 2 checks, one group per argument (tests/coreutils_batch2.rs)+src/syscall_ext.rs stage 17: links, chown, statfs/statx, CPU times, socket names/options, timers, ids, flock... (table at the top)+src/timer.rs stage 17: alarm, setitimer and POSIX timers; the timerd thread that sends their signals src/fs/lock.rs stage 16: fcntl record locks (F_GETLK/F_SETLK/F_SETLKW), deadlock detection src/fpu.rs stage 15.1: FPU/SSE/AVX on per CPU (CR0/CR4/XCR0), XSAVE or FXSAVE save/restore scripts/hvc0-session.txt the commands run-headless.sh feeds into hvc0 (for `getty -1 /dev/hvc0`)@@ -1570,15 +1574,24 @@ Numbers are Linux's wherever Linux has the same call, so man pages and | 33 | `dup2` | old, new | closes `new` first if open | | 34 | `pause` | | sleeps until a signal; returns EINTR | | 35 | `nanosleep` | *timespec, *remaining | **Linux's `nanosleep` since stage 15** (stages 2-14 had our own `sleep_ms(ms)` here). Sleeps whole PIT ticks, then yields until the TSC clock says the time is up; a signal ends it early with `EINTR` and the time left in `*remaining` |+| 36 | `getitimer` | which, *itimerval | **stage 17**: `ITIMER_REAL` only (others `EINVAL`) |+| 37 | `alarm` | seconds | **stage 17**: SIGALRM after N seconds; returns the seconds left on the old one; 0 cancels |+| 38 | `setitimer` | which, *new, *old | **stage 17**: `ITIMER_REAL`, one-shot or periodic, in microseconds; fires up to ~110 ms late, never early | | 39 | `getpid` | | |+| 40 | `sendfile` | out_fd, in_fd, *offset, count | **stage 17**: in-kernel copy; the input must be seekable (`EINVAL` for a pipe); with `*offset` the input position doesn't move | | 41 | `socket` | domain, type, protocol | `AF_INET` (2) only; `SOCK_STREAM` (1) = TCP, `SOCK_DGRAM` (2) = UDP, or `SOCK_DGRAM` + `IPPROTO_ICMP` (1) = ping socket (like Linux's unprivileged ping sockets). Returns an fd; `close` closes it | | 42 | `connect` | fd, *sockaddr_in, len | TCP: blocks for the handshake (10 s, `ETIMEDOUT`; `ECONNREFUSED` on a reset). UDP: sets the default peer | | 43 | `accept` | fd, *sockaddr_in, *len | blocks for a connection on a listening socket; returns a new fd. Since 15.1 `*len` is read and updated like Linux | | 44 | `sendto` | fd, buf, len, flags, *sockaddr_in, len | address 0 = the connected peer. ICMP: you write the echo header, the kernel fills in the id and checksum. `flags` (so `MSG_NOSIGNAL`) and `len` are ignored | | 45 | `recvfrom` | fd, buf, len, flags, *sockaddr_in, *len | blocks (wait queue) until data; 0 = the peer closed (TCP); `EAGAIN` after `SO_RCVTIMEO`. Since 15.1 the address honours `*len` |+| 48 | `shutdown` | fd, how | **stage 17**: `SHUT_RD`/`SHUT_WR`/`SHUT_RDWR`; `SHUT_WR` sends a FIN (the peer reads 0); `ENOTCONN` if not connected | | 49 | `bind` | fd, *sockaddr_in, len | local port (address must be 0 or ours) | | 50 | `listen` | fd, backlog | TCP; up to 4 connections can be waiting |-| 54 | `setsockopt` | fd, level, name, *val, len | only `SOL_SOCKET`/`SO_RCVTIMEO` (a `timeval`); anything else `ENOPROTOOPT`, like Linux |+| 51 | `getsockname` | fd, *sockaddr_in, *len | **stage 17**: my address and port (after `bind`, `connect` or `accept`) |+| 52 | `getpeername` | fd, *sockaddr_in, *len | **stage 17**: the other end; `ENOTCONN` if none |+| 53 | `socketpair` | domain, type, protocol, *[u32; 2] | **stage 17**: needs `AF_UNIX`, which we don't have: `EAFNOSUPPORT` (`EOPNOTSUPP` for `AF_INET`) |+| 54 | `setsockopt` | fd, level, name, *val, len | `SO_RCVTIMEO` (a `timeval`) is real; **stage 17**: `SO_REUSEADDR`/`SO_REUSEPORT`/`SO_KEEPALIVE`, `SO_SNDTIMEO`, buffer sizes and `TCP_NODELAY` are accepted and remembered; anything else `ENOPROTOOPT`, like Linux |+| 55 | `getsockopt` | fd, level, name, *val, *len | **stage 17**: `SO_TYPE`, `SO_ERROR` (once), `SO_ACCEPTCONN`, `SO_DOMAIN`, `SO_PROTOCOL`, the timeouts, buffer sizes and remembered flags | | 56 | `clone` | flags, stack, *ptid, *ctid, tls | fork-like (SIGCHLD, optional CLONE_VFORK), or since **stage 15** a thread: `CLONE_VM|FS|FILES|SIGHAND|THREAD|SYSVSEM` plus optional `SETTLS`, `PARENT_SETTID`, `CHILD_SETTID`, `CHILD_CLEARTID`. The child starts on `stack` with rax = 0 | | 57 | `fork` | | copy-on-write; parent gets the pid, child 0 | | 58 | `vfork` | | fork + the parent waits until the child execs or exits |@@ -1588,36 +1601,53 @@ Numbers are Linux's wherever Linux has the same call, so man pages and | 62 | `kill` | pid, sig | pid 0 = my group, -1 = all, < -1 = group -pid | | 63 | `uname` | *utsname | Linux's struct: 6 NUL-padded 65-byte fields: "Rustbelt", "qemu", "0.16.0", "stage 16: libc batch 1", "x86_64", "(none)" | | 72 | `fcntl` | fd, cmd, arg | F_DUPFD 0 (below `RLIMIT_NOFILE`, else `EINVAL`), F_GETFD 1, F_SETFD 2, F_GETFL 3 (since stage 16 the real access mode, `O_APPEND`, `O_NONBLOCK`), F_SETFL 4 (`O_NONBLOCK`; `O_APPEND` since 16), F_DUPFD_CLOEXEC 1030. **Stage 16**: record locks F_GETLK 5, F_SETLK 6 (`EAGAIN` on a conflict), F_SETLKW 7 (sleeps; `EDEADLK`, `EINTR`), Linux's `struct flock`; dropped when the process closes *any* fd of the file, or exits |+| 73 | `flock` | fd, op | **stage 17**: whole-file `LOCK_SH`/`LOCK_EX`, `LOCK_NB`, `LOCK_UN`; owned by the open file, dropped when its last fd closes | | 74 | `fsync` | fd | **stage 16**: 0 for files and directories (writes already go straight to the disk); `fdatasync` 75 the same; `EINVAL` for pipes, sockets, ttys | | 76 | `truncate` | path, length | **stage 16**: grow (zeros) or shrink a file; `EISDIR`, `EINVAL` (negative), `EFBIG` (over 4 GiB), `EROFS` on btrfs | | 77 | `ftruncate` | fd, length | **stage 16**: the same through an fd opened for writing (else `EINVAL`) | | 79 | `getcwd` | buf, len | like Linux since stage 15: writes a NUL and returns the length including it; `ERANGE` if too small | | 80 | `chdir` | path | per-process cwd, inherited by forked and spawned children | | 81 | `fchdir` | fd | **stage 15.1**: an open directory |-| 82 | `rename` | old, new | **stage 15.1**: files (also over an existing file) and empty directories, within one file system (else `EXDEV`). Not atomic: see "Linux paths" |+| 82 | `rename` | old, new | **stage 15.1**: files (also over an existing file) and empty directories, within one file system (else `EXDEV`). Not atomic: see "Linux paths". **Stage 17**: ramfs and ext2 move the entry itself (full directories, files with several names); `renameat2` 316 adds `RENAME_NOREPLACE` | | 83 | `mkdir` | path, mode | since stage 16 `mode & ~umask` (ramfs, ext2) | | 84 | `rmdir` | path | empty directories only (`ENOTEMPTY`); `EBUSY` on a mount point |+| 86 | `link` | old, new | **stage 17**: a hard link on ramfs and ext2 (`EMLINK` past 32000); `EPERM` on FAT/exFAT, `EROFS` on btrfs, `EXDEV` across file systems | | 87 | `unlink` | path | files and symlinks (the link, not its target); directories give `EISDIR` |+| 88 | `symlink` | target, path | **stage 17**: ramfs and ext2; the target needn't exist | | 89 | `readlink` | path, buf, len | a symlink's target (no NUL); returns its length, cut to `len` like Linux; `EINVAL` if not a symlink. Stage 15: `/proc/self/exe` is the running program's path | | 90 | `chmod` | path, mode | **stage 16**: the 12 permission bits, stored on ramfs and ext2; `EPERM` on FAT/exFAT (nowhere to keep them), `EROFS` on btrfs. Never checked: everyone is root | | 91 | `fchmod` | fd, mode | **stage 16** |+| 92 | `chown` | path, uid, gid | **stage 17**: -1 = unchanged; stored on ramfs and ext2 (FAT/exFAT: `EPERM` unless nothing changes); not enforced; `fchown` 93, `lchown` 94 | | 95 | `umask` | mask | **stage 16**: per process (default 022), inherited by fork; returns the old one | | 97 | `getrlimit` | resource, *[cur, max] | **stage 16**: = `prlimit64(0, ...)`; `setrlimit` 160 too |-| 102 | `getuid` | | **stage 15.1**: always 0, and so are `getgid` 104, `geteuid` 107, `getegid` 108 (no users yet) |+| 98 | `getrusage` | who, *rusage | **stage 17**: user/system CPU time (10 ms samples) for `RUSAGE_SELF`, `RUSAGE_CHILDREN`, `RUSAGE_THREAD`; `ru_maxrss` |+| 99 | `sysinfo` | *sysinfo | **stage 17**: Linux's 112-byte struct: uptime, memory, process count, "load" = busy fraction since boot |+| 100 | `times` | *tms | **stage 17**: CPU times of me and my waited-for children in 1/100 s; returns ticks since boot |+| 102 | `getuid` | | **stage 17**: the real user id (0 = root unless changed); `getgid` 104, `geteuid` 107, `getegid` 108 likewise |+| 105 | `setuid` | uid | **stage 17**: root sets all three ids; others only the effective one, to their real or saved id (`EPERM`); `setgid` 106 likewise | | 109 | `setpgid` | pid, pgid | 0 = myself / a new group | | 110 | `getppid` | | 0 if started by the kernel or orphaned | | 111 | `getpgrp` | | | | 112 | `setsid` | | **stage 15**: new session + group, no controlling tty |+| 113 | `setreuid` | ruid, euid | **stage 17**: Linux's rules for the saved id; `setregid` 114 |+| 115 | `getgroups` | size, *list | **stage 17**: supplementary groups; `setgroups` 116 (root only) |+| 117 | `setresuid` | ruid, euid, suid | **stage 17**: not root: each new value must be one of the current three; `getresuid` 118, `setresgid`/`getresgid` 119/120 | | 121 | `getpgid` | pid | |+| 122 | `setfsuid` | uid | **stage 17**: the id new files get; returns the old one; `setfsgid` 123 | | 124 | `getsid` | pid | **stage 15** | | 127 | `rt_sigpending` | *set, size | **stage 16**: signals waiting because they're blocked | | 128 | `rt_sigtimedwait` | *set, *siginfo, *timespec, 8 | **stage 16**: take one of `set` without running a handler (sleeps; `EAGAIN` after the timeout, `{0, 0}` = just look) | | 129 | `rt_sigqueueinfo` | pid, sig, *siginfo | **stage 16**: send with your own siginfo (`sigqueue`); `si_code` must be negative for another process (`EPERM`). Real-time signals (32-64) queue up, each with its value; `rt_tgsigqueueinfo` 297 for one thread | | 130 | `rt_sigsuspend` | *mask, 8 | **stage 16**: swap in `mask`, sleep until a handler runs, put the old mask back, `EINTR` (dash's `wait`) | | 131 | `sigaltstack` | *new, *old | **stage 16**: a separate stack for `SA_ONSTACK` handlers; `SS_DISABLE` 2, `SS_ONSTACK` 1 (read only), `SS_AUTODISARM`; `ENOMEM` below 2 KiB, `EPERM` while on it |+| 137 | `statfs` | path, *statfs | **stage 17**: Linux's magic numbers (ramfs, msdos, ext2, exfat, btrfs), sizes, free blocks and inodes, `ST_RDONLY` |+| 138 | `fstatfs` | fd, *statfs | **stage 17**; pipes and sockets get `PIPEFS`/`SOCKFS` |+| 140 | `getpriority` | which, who | **stage 17**: returns 20 - nice (the raw call; libc subtracts) |+| 141 | `setpriority` | which, who, nice | **stage 17**: clamped to -20..19; lowering it needs root (`EACCES`); the scheduler ignores it | | 157 | `prctl` | option, arg2... | **stage 16**: `PR_SET_NAME`/`PR_GET_NAME` 15/16 (thread name, 15 bytes), `PR_SET_PDEATHSIG`/`GET` 1/2, `PR_SET_DUMPABLE`/`GET` 4/3, `PR_SET_CHILD_SUBREAPER`/`GET` 36/37, `PR_SET_NO_NEW_PRIVS`/`GET` 38/39; others `EINVAL` | | 158 | `arch_prctl` | code, addr | **stage 15**: ARCH_SET_FS 0x1002 / ARCH_GET_FS 0x1003: the TLS pointer | | 160 | `setrlimit` | resource, *[cur, max] | **stage 16**: see `prlimit64` |+| 170 | `sethostname` | name, len | **stage 17**: what `uname` reports; root only, at most 64 bytes; `setdomainname` 171 | | 186 | `gettid` | | **stage 15**: thread id (= pid for the first thread) | | 200 | `tkill` | tid, sig | **stage 15** | | 202 | `futex` | *u32, op, val, *timespec, *u32, val3 | **stage 15**: FUTEX_WAIT 0, WAKE 1, WAIT_BITSET 9, WAKE_BITSET 10 (+ PRIVATE 128) |@@ -1625,7 +1655,10 @@ Numbers are Linux's wherever Linux has the same call, so man pages and | 213 | `epoll_create` | size | **stage 15**: = epoll_create1(0) | | 217 | `getdents64` | fd, buf, len | real Linux `struct linux_dirent64` records (ino, off, reclen, type, name\0) | | 218 | `set_tid_address` | *u32 | **stage 15**: cleared + futex-woken when this thread exits; returns the tid |-| 228 | `clock_gettime` | clock, *timespec | microsecond resolution (TSC calibrated against the PIT). `CLOCK_MONOTONIC` (1) counts from boot; since stage 15 `CLOCK_REALTIME` (0) is real Unix time (the CMOS clock read once at boot, plus uptime) |+| 222 | `timer_create` | clock, *sigevent, *id | **stage 17**: `CLOCK_REALTIME`/`MONOTONIC`/`BOOTTIME`; `SIGEV_SIGNAL`, `SIGEV_NONE`, `SIGEV_THREAD_ID`; signals carry `SI_TIMER` and `si_value` |+| 223 | `timer_settime` | id, flags, *new, *old | **stage 17**: `TIMER_ABSTIME`; periodic; `timer_gettime` 224, `timer_getoverrun` 225, `timer_delete` 226 |+| 228 | `clock_gettime` | clock, *timespec | microsecond resolution (TSC calibrated against the PIT). `CLOCK_MONOTONIC` (1) counts from boot; since stage 15 `CLOCK_REALTIME` (0) is real Unix time (the CMOS clock read once at boot, plus uptime); **stage 17**: `CLOCK_PROCESS_CPUTIME_ID` (2) and `CLOCK_THREAD_CPUTIME_ID` (3), in 10 ms steps |+| 229 | `clock_getres` | clock, *timespec | **stage 17**: 1 µs for the wall clocks, 10 ms for the CPU-time ones | | 230 | `clock_nanosleep` | clock, flags, *timespec, *remaining | **stage 15.1**: `CLOCK_REALTIME`, `MONOTONIC` or `BOOTTIME`; flag `TIMER_ABSTIME` (1) sleeps until that time. musl's `nanosleep` and `sleep` use this | | 231 | `exit_group` | code | ends the process: every thread (stage 15; `exit`, 60, ends one thread) | | 232 | `epoll_wait` | epfd, *epoll_event, max, timeout_ms | **stage 15**: level-triggered; EPOLLONESHOT supported |@@ -1633,9 +1666,12 @@ Numbers are Linux's wherever Linux has the same call, so man pages and | 234 | `tgkill` | tgid, tid, sig | **stage 15**: a signal for one thread | | 257 | `openat` | dirfd, path, flags, mode | **stage 15.1**: like `open`, relative to `dirfd` or `AT_FDCWD` (-100); musl's `open` calls this | | 258 | `mkdirat` | dirfd, path, mode | **stage 15.1** |+| 260 | `fchownat` | dirfd, path, uid, gid, flags | **stage 17**: `AT_SYMLINK_NOFOLLOW`, `AT_EMPTY_PATH` | | 262 | `newfstatat` | dirfd, path, *stat, flags | **stage 15.1**: `AT_SYMLINK_NOFOLLOW` 0x100 (= lstat), `AT_EMPTY_PATH` 0x1000 (= `fstat(dirfd)`); musl's `stat`/`fstat`/`lstat` all end up here | | 263 | `unlinkat` | dirfd, path, flags | **stage 15.1**: `AT_REMOVEDIR` 0x200 = rmdir |-| 264 | `renameat` | olddirfd, old, newdirfd, new | **stage 15.1**; `renameat2` (316) too, with flags 0 |+| 264 | `renameat` | olddirfd, old, newdirfd, new | **stage 15.1**; `renameat2` (316) too, with flags 0 (stage 17: or `RENAME_NOREPLACE`) |+| 265 | `linkat` | olddirfd, old, newdirfd, new, flags | **stage 17**: `AT_SYMLINK_FOLLOW`, `AT_EMPTY_PATH` |+| 266 | `symlinkat` | target, dirfd, path | **stage 17** | | 267 | `readlinkat` | dirfd, path, buf, len | **stage 15.1** | | 268 | `fchmodat` | dirfd, path, mode | **stage 16**; `fchmodat2` (452) also takes `AT_SYMLINK_NOFOLLOW` (`EOPNOTSUPP` on a symlink, like Linux) and `AT_EMPTY_PATH` | | 269 | `faccessat` | dirfd, path, mode | **stage 15.1**; `faccessat2` (439) too, its flags ignored |@@ -1648,11 +1684,13 @@ Numbers are Linux's wherever Linux has the same call, so man pages and | 291 | `epoll_create1` | flags | **stage 15**: EPOLL_CLOEXEC | | 292 | `dup3` | old, new, flags | flags: O_CLOEXEC | | 293 | `pipe2` | *[u32; 2], flags | flags: O_CLOEXEC |+| 295 | `preadv` | fd, *iovec, n, offset | **stage 17**: the file position doesn't move; `pwritev` 296 | | 302 | `prlimit64` | pid, resource, *new, *old | **stage 16**: limits are per process and inherited. Real numbers where the kernel has a real limit: `RLIMIT_NOFILE` 64 (the fd table), `RLIMIT_STACK` (the user stack), `RLIMIT_NPROC`; the rest unlimited. Lowering works; raising a hard limit past the real one is `EPERM` | | 318 | `getrandom` | buf, len, flags | random bytes from virtio-rng (the host's `/dev/urandom`), up to 64 KiB per call; never blocks, flags ignored. Without virtio-rng: a TSC-seeded xorshift (not secure) |+| 332 | `statx` | dirfd, path, flags, mask, *statx | **stage 17**: the 256-byte `struct statx`, from the same data as `stat` | | 500 | `spawn` | path, argv, argv_len, *SpawnOptions | **ours**: start a program, returns its pid (fork + exec in one step, like `posix_spawn`; the kernel's first shell and `getty` use it, the shell uses fork + exec) | | 501 | `mounts` | buf, len | **ours** (Linux reads `/proc/mounts`): the mount table as text, one `path fstype` line per mount, in mount order; returns the length, `ERANGE` if too small. The shell assigns drive letters from it |-| 502 | `sysinfo` | *SysInfo | **ours**: CPU count, the CPU you're on, thread/process/frame counts, uptime ticks, and per-CPU timer ticks and busy ticks (sample twice for the load) |+| 502 | `rb_sysinfo` | *SysInfo | **ours** (called `sysinfo` until stage 17; Linux's `sysinfo` is 99): CPU count, the CPU you're on, thread/process/frame counts, uptime ticks, and per-CPU timer ticks and busy ticks (sample twice for the load) | | 503 | `resolve` | name, name_len, *addrs, max | **ours** (Linux does DNS in libc, in user space): IPv4 addresses for a name via the DHCP-given DNS server; returns how many; `ENOENT` / `ETIMEDOUT` | | 504 | `netinfo` | *NetInfo | **ours** (Linux has `ioctl`s and netlink): MAC, IRQ, address/prefix, gateway, DNS, packet and byte counters | | 505 | `trace_me` | on | ours: print my syscalls from now on (survives execve; `run --trace`) |@@ -2014,6 +2052,7 @@ Programs in `B:/bin` (`user/*.rs`, each 30-150 lines): | `threads [n]`, `echoserver [port]`, `ptytest`, `nbtest` | the stage 15 checks: a parallel sum on `n` threads with a futex mutex; an epoll + poll echo server over 127.0.0.1 (`echoserver 7070` just serves forever); ptys and the line discipline; nonblocking I/O and the libc extras. Each ends with "all checks passed" | | `fputest`, `pathtest`, `rawabi [dir]` | the stage 15.1 checks: per-thread FPU/SSE/AVX state and signal frames; the Linux path calls; a C program with no libc doing file calls in `dir` (default `/tmp`), SSE maths, a signal and fork + wait4. Each ends with "all checks passed" | | `libctest wait\|signals\|locks\|files\|misc\|orphans` | the stage 16 checks (libc batch 1), one group at a time; each ends with "all checks passed" |+| `coretest links\|owners\|statfs\|info\|sockets\|timers\|ids\|misc` | the stage 17 checks (coreutils batch 2), one group at a time; each ends with "all checks passed" | Built-in aliases are the DOS names: `dir` = `ls`, `type` = `cat`, `del` = `rm`, `md` = `mkdir`, `copy` = `cp`; and for networking,@@ -3869,22 +3908,17 @@ Doesn't (yet): (Stage 16, below, crossed off the rows for signals, rlimits, `umask` and `chmod`, `truncate`, `fsync`, `utimensat`, record locks, `prctl`, robust lists, `sched_getaffinity` and `madvise`.)+(Stage 17 crossed off `chown`, `link`/`symlink`, `statfs`/`flock`,+`sysinfo`, `getrusage`/`times`/`clock()`, `clock_getres`, the timers, the+`setuid` family, `nice`, `preadv`/`pwritev`/`sendfile`, and most of the+socket row.) | musl function(s) | missing syscall (x86_64 number) | |---|---| | `malloc` (mallocng) | `mremap` 25 (failure is tolerated) |-| `chown` family | `fchownat` 260 (and `chown` 92, `fchown` 93, `lchown` 94) |-| `link`, `symlink` (`ln`) | `link` 86 / `linkat` 265, `symlink` 88 / `symlinkat` 266 |-| `statvfs`, `flock` | `statfs` 137 / `fstatfs` 138, `flock` 73 |-| `sysinfo` | `sysinfo` 99 (ours is 502, with a different struct) |-| `getrusage`, `times`, `clock()` | `getrusage` 98, `times` 100; `clock_gettime(CLOCK_PROCESS_CPUTIME_ID)` is `EINVAL` |-| `clock_getres` | `clock_getres` 229 |-| `alarm`, `setitimer`, `timer_create` | `alarm` 37, `setitimer` 38, `timer_create` 222 ... | | `dlclose` barriers | `membarrier` 324 |-| sockets | `shutdown` 48, `getsockname` 51, `getpeername` 52, `socketpair` 53, `getsockopt` 55, `sendmsg` 46, `recvmsg` 47 (the DNS resolver's TCP fallback) |-| `setuid` family, `getgroups` | 105, 106, 113-120 (`getresuid` 118 ...), `getgroups` 115 |-| `nice`, `getpriority` | `getpriority` 140, `setpriority` 141 |-| `preadv`, `pwritev`, `sendfile` | 295, 296, `sendfile` 40 |+| sockets | `socketpair` 53 (needs `AF_UNIX`; `EAFNOSUPPORT` now), `sendmsg` 46, `recvmsg` 47 (the DNS resolver's TCP fallback) |+| `timerfd_create` | `timerfd_create` 283, `timerfd_settime` 286, `timerfd_gettime` 287 | Not syscalls, but missing just the same: @@ -4141,6 +4175,290 @@ Doesn't (yet): - **The rest of Clarke's list** (static-PIE loading, a hook for running disk images) are separate stages. +## coreutils batch 2 (stage 17)++Stage 16 was what a C library needs to *run*. Stage 17 is what the+everyday tools need: `ln`, `chown`, `df`, `uptime`, `time`, `sleep`+with an alarm, `id`, `nice`, `hostname`, `rm -r`, `cp`, and the socket+calls a small server makes. Clarke ran coreutils and busybox (tier 3)+with `run --trace`, wrote down every `ENOSYS`, and put them in order:++1. `link` and `symlink` (and their `*at` forms);+2. `setsockopt` options that may be ignored, `times()`, CPU-time clocks;+3. the `chown` family;+4. `statfs` / `fstatfs` (`df`);+5. Linux's `sysinfo` (99), `getrusage`, `clock_getres`;+6. `getsockopt`, `getsockname`, `shutdown`, `socketpair`;+7. `alarm`, interval timers and POSIX timers;+8. user and group ids, priority, `sethostname`;+9. then, if there was room: `flock`, `preadv`/`pwritev`, `sendfile`+ (and `mremap`, which didn't make it).++As before, every call uses Linux's number, arguments and error codes, so+`man 2 <name>` describes it. The new calls live in their own file,+`src/syscall_ext.rs`, with a table at the top, because `syscall.rs` had+grown past 2000 lines. The dispatcher in `syscall.rs` tries its own+table first and then asks `syscall_ext::dispatch`.++### Hard links and symbolic links++A file's *name* and the file itself are different things. On ext2 (and+on Linux's ramfs) the file is an **inode**, with a number, and a+directory is just a list of `name -> inode number`. So one file can have+two names:++```+ln a.txt b.txt # link(86): a second name for the same inode+ls -i a.txt b.txt # the same inode number twice+stat a.txt # "Links: 2"+```++Change the file through one name and the other sees it; delete one name+and the file lives on until the last name (and the last open fd) is+gone. That count is `st_nlink`, and `stat` now reports the real one.++A **symbolic link** (`symlink` 88, `ln -s`) is different: a tiny file+whose content is a path. Opening it follows the path; the target doesn't+even have to exist (a *dangling* link). `readlink` (stage 15.1) reads it.++Where it works and where it says no:++| file system | `link` | `symlink` |+|---|---|---|+| ramfs (`/`, `/tmp`) | yes | yes |+| ext2 (`/mnt/disk1`) | yes, up to 32000 links (`EMLINK`) | yes |+| FAT, exFAT | `EPERM` (no inodes: a name *is* the file) | `EPERM` |+| btrfs (read-only) | `EROFS` | `EROFS` |+| two different file systems | `EXDEV` (an inode number only means something on its own disk) | works (it's just a path) |++`linkat` (265) adds `AT_SYMLINK_FOLLOW` (link to what a symlink points+at, not the symlink) and `AT_EMPTY_PATH` (link an open fd). `symlinkat`+(266) is `symlink` relative to a directory fd.++**rename learned to move things.** Before, `rename` copied and deleted,+which can't work for a file with two names (the copy would be a third,+separate file) or for a full directory. ramfs and ext2 now move the+directory entry itself (`Inode::rename_entry`), so `mv dir1 dir2/`+works, and so does renaming over a second name of the same file.+`renameat2`'s `RENAME_NOREPLACE` ("fail with `EEXIST` rather than+overwrite") is supported too.++### Owners: chown++Every file has an owner (a user id) and a group (a group id).+`chown` (92), `fchown` (93), `lchown` (94, the symlink itself) and+`fchownat` (260) change them; `-1` means "leave this one alone".+`stat` reports them. ramfs and ext2 store them; FAT and exFAT have+nowhere to put them, so a real change is `EPERM`, but "change it to+what it already is" (root:root) succeeds, as Linux's vfat does. That+matters: `cp -p` and `tar` call `chown` on every file they copy.++New files belong to their creator (the process's *fs* uid and gid, see+below).++### How big is the disk? statfs++`df` asks `statfs` (137) or `fstatfs` (138) for each mount: the block+size, how many blocks there are and how many are free, how many inodes,+the longest name, and a **magic number** saying which kind of file+system it is. Linux uses the same magic numbers, so `stat -f` prints the+right name:++| mount | magic | type |+|---|---|---|+| `/`, `/tmp` | `0x858458f6` | ramfs |+| `/mnt/disk0` | `0x4d44` | msdos (FAT) |+| `/mnt/disk1` | `0xEF53` | ext2 |+| `/mnt/disk2` | `0x2011BAB0` | exfat |+| `/mnt/disk3` | `0x9123683E` | btrfs (flagged read-only, `ST_RDONLY`) |++A pipe or socket fd gets `PIPEFS`/`SOCKFS`, like Linux. Each mount also+has its own `st_dev` now (it used to be 0 everywhere), which is how `cp+-r` and `find -xdev` tell "same file system" from "crossed a mount".++**statx** (332) is the newer stat: a 256-byte struct with a mask saying+which fields are filled in. coreutils' `ls` and `stat` try it first, so+it was cheaper to implement it (from the same data as `stat`) than to+send them down a fallback path.++### What time is it for *me*? CPU time++`time ls` prints three numbers: *real* (wall clock), *user* (CPU time+running the program's own code) and *sys* (CPU time in the kernel on its+behalf). The kernel learns those by looking at what each CPU was doing+at every timer interrupt (every 10 ms): if a thread was running, it gets+10 ms of user or system time, depending on whether the interrupt arrived+in ring 3 or ring 0. It's sampling, not exact, but it's what Linux did+for decades.++From those counters:++- **`times`** (100) gives user and system time of the process and of its+ waited-for children, in "clock ticks" of 1/100 s.+- **`getrusage`** (98) gives the same as `timeval`s, for `RUSAGE_SELF`,+ `RUSAGE_CHILDREN` or `RUSAGE_THREAD`, plus the memory high-water mark+ (here: the resident memory now).+- **`clock_gettime(CLOCK_PROCESS_CPUTIME_ID)`** (clock 2) and+ `CLOCK_THREAD_CPUTIME_ID` (3) used to be `EINVAL`. C's `clock()` uses+ clock 2.+- **`clock_getres`** (229) says how fine each clock is: 1 µs for the+ wall clocks, 10 ms for the CPU-time ones. Honest numbers, not 1 ns.++**sysinfo, the Linux one.** Rustbelt has had its own `sysinfo` since+stage 11, but it took number 502 and a different struct (per-CPU load+bars for `user/sysinfo.rs`). Linux's is 99, and musl's `sysinfo()`,+`uptime` and `free` use it. Both exist now: 99 fills Linux's `struct+sysinfo` (uptime, total/free memory, process count, load averages), and+ours stays at 502, renamed `rb_sysinfo` in `--trace` so the two can't be+confused. The "load average" is the busy fraction of the CPUs since boot+(Rustbelt doesn't keep a 1/5/15-minute history yet).++### Sockets: names, options, half-closing++- **`getsockname`** (51) and **`getpeername`** (52): "which address and+ port am I?" and "who am I talking to?". A server that binds to port 0+ uses the first to learn which port it got.+- **`setsockopt`** (54) used to know only `SO_RCVTIMEO`. Servers set+ `SO_REUSEADDR` (and `SO_KEEPALIVE`, `TCP_NODELAY`, buffer sizes)+ before `bind` and treat an error as fatal, so these are now accepted+ and remembered (smoltcp doesn't need them). Unknown options are still+ `ENOPROTOOPT`, not a fake success.+- **`getsockopt`** (55) reads them back, plus `SO_TYPE`, `SO_ERROR`+ (a failed nonblocking `connect` reports `ECONNREFUSED` here, once) and+ `SO_ACCEPTCONN`.+- **`shutdown`** (48) closes one direction: `SHUT_WR` sends the "no more+ data from me" (a TCP FIN) while you can still read the answer; the+ other side's `read` returns 0. Writing after it is `EPIPE` (and a+ SIGPIPE), reading after `SHUT_RD` returns 0.+- **`socketpair`** (53) makes two connected sockets, which needs+ `AF_UNIX` sockets. Rustbelt doesn't have those yet, so it says so:+ `EAFNOSUPPORT` (`EOPNOTSUPP` for `AF_INET`, as on Linux).++### Alarms and timers++Three generations of "send me a signal later":++- **`alarm(seconds)`** (37): SIGALRM once, after N seconds. Returns how+ many seconds were left on the previous alarm; `alarm(0)` cancels.+- **`setitimer`/`getitimer`** (38/36): the same timer (`ITIMER_REAL`) in+ microseconds, optionally repeating every *interval*.+- **POSIX timers** (`timer_create` 222 to `timer_delete` 226): as many as+ you like, on `CLOCK_REALTIME`, `CLOCK_MONOTONIC` or `CLOCK_BOOTTIME`,+ any signal, an absolute or relative start, and a value (`si_value`)+ that arrives in the handler's `siginfo` with `si_code = SI_TIMER`.+ `SIGEV_NONE` makes a timer you only read with `timer_gettime`.+ If a periodic timer fires while its last signal is still pending, the+ missed periods are counted as *overruns* (`timer_getoverrun`).++They all live in `src/timer.rs`: one table, and one kernel thread+(`timerd`) that sleeps until the nearest deadline and sends what's due.+Why not send signals from the timer interrupt? Sending one takes locks+and may wake threads; doing that in a normal thread keeps the interrupt+handler tiny.++As on Linux: `fork` doesn't copy timers, `execve` keeps the alarm but+deletes POSIX timers, and exiting deletes them all.++### Who am I? User ids++A process has three user ids, and the same three for groups:++- **real**: who started it;+- **effective**: whose permissions it has right now (0 = root);+- **saved**: a spare, so a program that dropped privileges can get them+ back.++Plus an *fs* id (what new files get) and a list of supplementary groups.+`setuid`, `setreuid`, `setresuid`, `setfsuid`, the group versions,+`getgroups`/`setgroups` and the getters (102-123) follow Linux's rules:+root may set anything; anyone else may only switch between the ids they+already have. So a root daemon that does `setuid(1000)` has given up root+for good, while `setresuid(1000, 1000, 0)` keeps 0 in the saved id and+can come back with `setuid(0)`. Ids are inherited by `fork` and kept by+`execve`.++They are **stored, not enforced**: nothing checks permissions yet+(stage 16 said the same about modes). `id` prints the truth, `chown`+uses them, and new files get the fs ids, but uid 1000 can still read+everything.++**Priority.** `getpriority`/`setpriority` (140/141) and `nice` keep a+nice value per process, -20 (most important) to 19. Only root may lower+it (`EACCES`). The scheduler doesn't look at it yet: every thread gets+the same share.++**Host name.** `sethostname`/`setdomainname` (170/171) change what+`uname` reports (the node name was always "qemu"); root only, at most 64+bytes.++### Whole-file locks, vectors and sendfile++- **`flock`** (73) is the other kind of file lock (stage 16 did+ `fcntl`'s). It locks the whole file, shared (`LOCK_SH`) or exclusive+ (`LOCK_EX`), with `LOCK_NB` for "don't wait" and `LOCK_UN` to let go.+ Its big difference from `fcntl` locks: it belongs to the **open file**+ (what one `open()` made, shared by `dup` and `fork`), not to the+ process, and it goes away when the *last* fd of that open file is+ closed. Two `open()`s of the same file by the same process conflict.+ This is what `flock(1)` and many package managers use.+- **`preadv`/`pwritev`** (295/296): `readv`/`writev` at an offset,+ without moving the file position.+- **`sendfile`** (40) copies from one fd to another inside the kernel,+ no user buffer in between. `cp` and web servers use it. The input must+ be something with a position (a file); the output can be anything.++### Small fixes Clarke's traces found++- **`rm -r` left files behind.** `getdents64` re-listed the directory on+ every call and skipped the entries it had already returned *by count*.+ `rm -r` deletes entries between calls, so the count was off and every+ other file was skipped. Now an open directory takes its listing once+ (until it's rewound) and `rm` sees every name. `getdents64` also+ reports the same inode number `stat` does.+- **`--trace` and interrupted calls.** A call cut short by a signal used+ to be traced as `= -512` or `-514` (`ERESTARTSYS`, `ERESTARTNOHAND`),+ kernel-internal codes no program ever sees.+ It's now traced after the signal is delivered, with what the program+ really gets: `= -4 EINTR`, or `= ? (interrupted, restarted)` when+ `SA_RESTART` makes the kernel run it again.+- **`ppoll`, `pselect6`, `epoll_pwait`** now check the signal-mask size+ (must be 8, else `EINVAL`), as Linux does.+- **The headless frame report** (`run-headless.sh`) is printed after+ every process and thread has finished, not while the last ones are+ still exiting, so the numbers no longer jump around.++`unlinkat`, `mkdirat` and `readlinkat` were audited and were already+right.++### Testing it++`user/coretest.rs` has one group of checks per argument: `links`,+`owners`, `statfs`, `info`, `sockets`, `timers`, `ids` and `misc`.+`tests/coreutils_batch2.rs` runs each group twice; the second run must+leave threads, processes, memory frames, file locks (`flock` ones too)+and timers exactly where they were. From the shell: `coretest timers`.++### What stage 17 does and doesn't do++Does: everything above, with Linux's numbers and error codes.++Doesn't (yet):++- **Ids, modes and priorities are stored, not enforced.** No permission+ checks, and the scheduler ignores nice values.+- **Timers are coarse.** `timerd` sleeps in PIT ticks (~55 ms), so a+ timer fires up to ~110 ms late (never early). A 10 ms periodic timer+ will mostly count overruns.+- **Only `ITIMER_REAL`.** `ITIMER_VIRTUAL` and `ITIMER_PROF` (CPU-time+ interval timers) are `EINVAL`, and so is `timer_create` on a CPU-time+ clock.+- **`socketpair`**, which needs `AF_UNIX` sockets; **`timerfd`**; and+ **`mremap`** (musl's `malloc` copes with `ENOSYS`) are left for later.+- **`link`/`symlink` on FAT and exFAT** are `EPERM`: those formats have+ nowhere to keep them.+- **Rustbelt's own `sysinfo` stays at 502** (as `rb_sysinfo`), next to+ Linux's 99.+ ## Roadmap Done: all 12 chapters of the 2nd edition, plus preemptive kernel threads,@@ -4153,8 +4471,10 @@ virtual drivers (virtio-rng, virtio-console, virtio-gpu 2D with a framebuffer console), the process model (fork/exec/mmap/signals), and the kernel side of a libc (poll/epoll, nonblocking I/O, loopback, threads and futexes, ttys and ptys), musl's prerequisites (per-thread-FPU/SSE/AVX state, Linux C-string path calls and `struct stat`), and the-first batch of the calls musl and dash use at run time.+FPU/SSE/AVX state, Linux C-string path calls and `struct stat`), the+first batch of the calls musl and dash use at run time, and a second+batch for coreutils and busybox (links, owners, `statfs`, CPU times,+timers, user ids). - [x] Freestanding binary — [phil-opp](https://os.phil-opp.com/freestanding-rust-binary/) - [x] Minimal kernel booting in QEMU — [phil-opp](https://os.phil-opp.com/minimal-rust-kernel/), [OSDev: Bare Bones](https://wiki.osdev.org/Bare_Bones)@@ -4192,6 +4512,7 @@ first batch of the calls musl and dash use at run time. - [x] Libc plumbing (stage 15): `poll`/`ppoll`/`select`/`pselect6`/`epoll`, `O_NONBLOCK` + `EAGAIN`, a loopback interface, `futex`, `clone` threads with TLS (`arch_prctl`), `set_tid_address`, `gettid`/`tgkill`, `exit` vs `exit_group`, ttys and ptys with termios, `/dev/tty`, `/dev/ptmx` + `/dev/pts/N`, a fork+exec `getty`, `nanosleep`, `CLOCK_REALTIME`, `readv`/`writev`, `pread`/`pwrite` — [man 7 epoll](https://man7.org/linux/man-pages/man7/epoll.7.html), [man 2 futex](https://man7.org/linux/man-pages/man2/futex.2.html), [Drepper: Futexes Are Tricky](https://www.akkadia.org/drepper/futex.pdf), [The TTY demystified](https://www.linusakesson.net/programming/tty/), [man 7 pty](https://man7.org/linux/man-pages/man7/pty.7.html) - [x] musl prerequisites (stage 15.1): per-thread FPU/SSE/AVX state with XSAVE/FXSAVE (eager switching, cleared on exec), Linux's fpstate in signal frames, #MF/#XM -> SIGFPE, C-string paths with `PATH_MAX` and `EFAULT`, Linux `struct stat`, the `*at()` calls, `rename`, a syscall-number audit, and `rawabi` (a no-libc gcc binary) — [Intel SDM vol. 1 ch. 13: XSAVE](https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html), [OSDev: SSE](https://wiki.osdev.org/SSE), [man 2 openat](https://man7.org/linux/man-pages/man2/openat.2.html), [man 2 stat](https://man7.org/linux/man-pages/man2/stat.2.html), [LWN: LazyFP](https://lwn.net/Articles/757246/) - [x] libc batch 1 (stage 16): `rt_sigsuspend`/`rt_sigpending`/`rt_sigtimedwait`/`rt_sigqueueinfo`, queued real-time signals, `sigaltstack`, per-process `umask` and rlimits (`prlimit64`), `fcntl` record locks with deadlock detection and honest `F_GETFL`, `truncate`/`ftruncate`/`fsync`/`chmod`/`utimensat` with real file times, `prctl`, `sched_getaffinity`, `madvise`, robust futex lists, subreapers and self-reaping orphans, `--trace` naming unknown calls by number — [man 2 sigsuspend](https://man7.org/linux/man-pages/man2/sigsuspend.2.html), [man 2 fcntl](https://man7.org/linux/man-pages/man2/fcntl.2.html), [man 2 sigaltstack](https://man7.org/linux/man-pages/man2/sigaltstack.2.html), [man 2 prlimit](https://man7.org/linux/man-pages/man2/prlimit.2.html), [man 2 get_robust_list](https://man7.org/linux/man-pages/man2/get_robust_list.2.html)+- [x] coreutils batch 2 (stage 17): `link`/`symlink` and their `*at` forms, native `rename` of full directories and `RENAME_NOREPLACE`, the `chown` family with owners on ramfs and ext2, `statfs`/`fstatfs`/`statx` with Linux's magic numbers and a real `st_dev`/`st_nlink`, Linux's `sysinfo` (99), `times`/`getrusage`/CPU-time clocks/`clock_getres`, `getsockname`/`getpeername`/`getsockopt`/`shutdown` and remembered socket options, `alarm`/`setitimer`/POSIX timers, real (unenforced) user and group ids, nice values, `sethostname`, `flock`, `preadv`/`pwritev`, `sendfile`, and a `getdents64` that `rm -r` can trust — [man 2 link](https://man7.org/linux/man-pages/man2/link.2.html), [man 2 statfs](https://man7.org/linux/man-pages/man2/statfs.2.html), [man 2 timer_create](https://man7.org/linux/man-pages/man2/timer_create.2.html), [man 7 credentials](https://man7.org/linux/man-pages/man7/credentials.7.html), [man 2 flock](https://man7.org/linux/man-pages/man2/flock.2.html) - [ ] The kernel/OS split and a musl port (next; waits for Tyler's go; see the missing-syscall list in "musl prerequisites") - [ ] A Hyprland-style tiling compositor on the framebuffer (later) @@ -4412,6 +4733,27 @@ and for a UEFI/framebuffer setup later, the newer [`bootloader` 0.11](https://gi - QEMU's default network card is an e1000. Adding `-netdev`/`-device virtio-net-pci` replaces it, so the PCI device count stayed 10 (stage 13's GPU, rng and console make it 12).+- **Pitfalls hit in stage 17:**+ - *A number with two names.* Rustbelt's own `sysinfo` had taken 502+ while Linux's `sysinfo` is 99 with a different struct. Both exist now,+ and 502 is traced as `rb_sysinfo` so a trace never says "sysinfo" for+ two different calls.+ - *Listing a directory while deleting from it.* `getdents64` skipped+ entries by count, so `rm -r` silently missed every other file. Tests+ that only list (never delete while listing) can't see this bug.+ - *`nanosleep` ends early when a signal arrives.* A test that sleeps+ 500 ms to count a periodic timer's signals woke after the first one+ and counted 1. Sleep in a loop until the time is really up.+ - *A forked child keeps the parent's open files.* With `flock`, the+ lock belongs to the open file, so the child's inherited copy kept it+ alive and a waiting child blocked forever (Linux does the same). The+ child has to close its copies first.+ - *Linux's saved-id rule is subtle.* `setreuid(-1, x)` only copies `x`+ into the saved id if `x` isn't the old real id. Read `man 2 setreuid`+ before writing the test, not after.+ - *Version and count pins again*: `nbtest` wanted "stage 16", the shell+ test "0.16.0", `run-headless.sh` 37 programs in `bin` (now 38, with+ `coretest`). - **Pitfalls hit in stage 16:** - *Tests that pin a version or a count.* `nbtest` checked that `uname` said "stage 15", the shell test looked for "Rustbelt 0.15.1", and
@@ -79,6 +79,7 @@ const PROGRAMS: &[&str] = &[ "threads", "echoserver", "ptytest", "nbtest", // stage 15: libc plumbing "fputest", "pathtest", // stage 15.1: musl prerequisites "libctest", // stage 16: libc batch 1+ "coretest", // stage 17: coreutils batch 2 ]; /// C programs in /bin, built with the host's C compiler and no libc at all:
@@ -88,7 +88,7 @@ set -e # Lines the shell self-test must have printed (only for a plain boot). if [ -z "${1:-}" ] && [ "$code" = 33 ]; then expect=(- "Rustbelt 0.16.0 (stage 16: libc batch 1) x86_64" # ver+ "Rustbelt 0.17.0 (stage 17: coreutils batch 2) x86_64" # ver "[smp] 4 CPUs online" # smp.rs started 3 more cores "[fpu] " # XSAVE (-cpu max) or FXSAVE (qemu64) "B: /mnt/disk0 fat32" # drives@@ -159,7 +159,7 @@ if [ -z "${1:-}" ] && [ "$code" = 33 ]; then "[nbtest] all checks passed" "[fputest] all checks passed" # stage 15.1: musl prerequisites "[pathtest] all checks passed"- "rawabi: uname: Rustbelt 0.16.0" # a no-libc gcc binary+ "rawabi: uname: Rustbelt 0.17.0" # a no-libc gcc binary "rawabi: all checks passed" "sh: nosuchprogram: command not found" "cd: there is no drive Q:"@@ -169,11 +169,11 @@ if [ -z "${1:-}" ] && [ "$code" = 33 ]; then for line in "${expect[@]}"; do grep -qF -- "$line" "$LOG" || { echo "[run-headless] MISSING: $line"; missing=1; } done- # Pipe results: `... | grep -i EXT | wc -l` -> 1, `dir bin | wc -l` -> 37 (the programs),+ # Pipe results: `... | grep -i EXT | wc -l` -> 1, `dir bin | wc -l` -> 38 (the programs), # `cat C:/chunks.txt | wc -l` (copied from E:) -> 3, # `type D:/COW.TXT | wc -l` (copied from E:) -> 4, and # `cat D:/seq.txt | wc -l` (downloaded by wget) -> 20000.- for n in 1 37 3 4 20000; do+ for n in 1 38 3 4 20000; do grep -qx -- "$n" "$LOG" || { echo "[run-headless] MISSING pipe result: $n"; missing=1; } done # No memory leaks: once the shell and all ~60 programs it ran have@@ -184,7 +184,7 @@ if [ -z "${1:-}" ] && [ "$code" = 33 ]; then echo "[run-headless] MISSING: frames back at the baseline after the shell"; missing=1 fi # The other direction: the host fetched a page from httpd in the guest.- if grep -qF "Hello from Rustbelt! Rustbelt 0.16.0" "$CURL_OUT" 2>/dev/null; then+ if grep -qF "Hello from Rustbelt! Rustbelt 0.17.0" "$CURL_OUT" 2>/dev/null; then echo "[run-headless] host curl got httpd's page: $(head -1 "$CURL_OUT")" else echo "[run-headless] MISSING: host curl through hostfwd to httpd"; missing=1@@ -196,7 +196,7 @@ if [ -z "${1:-}" ] && [ "$code" = 33 ]; then # drop the '\r's before comparing whole lines. tr -d '\r' < "$HVC_OUT" > "$HVC_OUT.txt" 2>/dev/null && mv "$HVC_OUT.txt" "$HVC_OUT" # The shell on hvc0 ran the host's commands and answered over hvc0.- for line in "Rustbelt getty: shell on this terminal" "Rustbelt 0.16.0 (stage 16" \+ for line in "Rustbelt getty: shell on this terminal" "Rustbelt 0.17.0 (stage 17" \ "$ echo hi over hvc0" "hi over hvc0" "fb0" "hvc0"; do grep -qF -- "$line" "$HVC_OUT" 2>/dev/null || { echo "[run-headless] MISSING on hvc0: $line"; missing=1; } done
@@ -45,7 +45,7 @@ fputest | grep passed pathtest | grep passed rawabi | grep -v "ok " TXT- set -- "Rustbelt 0.16.0 (stage 16: libc batch 1) x86_64" "tile: 4 windows on 1024x768, console 61x46" \+ set -- "Rustbelt 0.17.0 (stage 17: coreutils batch 2) x86_64" "tile: 4 windows on 1024x768, console 61x46" \ "^C" "(interrupted: SIGINT)" "threads: 4 threads summed 1..=40000000 = 800000020000000" \ "echoserver: epoll: 3 clients, 9 echoes" "[threads] all checks passed" \ "[fputest] all checks passed" "[pathtest] all checks passed" \
@@ -68,7 +68,7 @@ impl Inode for DevDir { } fn list(&self) -> Result<Vec<DirEntry>, Errno> { let kind = |n| if n == "pts" { InodeKind::Directory } else { InodeKind::Device };- Ok(names().into_iter().map(|n| DirEntry { name: String::from(n), kind: kind(n) }).collect())+ Ok(names().into_iter().map(|n| DirEntry { name: String::from(n), kind: kind(n), ino: 0 }).collect()) } } @@ -90,7 +90,7 @@ impl Inode for PtsDir { Ok(Arc::new(PtsNode(n))) } fn list(&self) -> Result<Vec<DirEntry>, Errno> {- Ok(tty::pty_numbers().into_iter().map(|n| DirEntry { name: alloc::format!("{}", n), kind: InodeKind::Device }).collect())+ Ok(tty::pty_numbers().into_iter().map(|n| DirEntry { name: alloc::format!("{}", n), kind: InodeKind::Device, ino: 0 }).collect()) } }
@@ -30,6 +30,8 @@ pub enum Errno { EFBIG = 27, // file too large ESPIPE = 29, // can't seek (pipe, console) EROFS = 30, // read-only file system+ EACCES = 13, // permission denied (setpriority: only root may raise priority)+ EMLINK = 31, // too many hard links to one file EPIPE = 32, // writing to a pipe nobody reads ERANGE = 34, // result doesn't fit (getcwd buffer too small) EDEADLK = 35, // waiting for this lock would deadlock (fcntl F_SETLKW)@@ -66,12 +68,13 @@ impl Errno { /// The errno for a negative syscall result like -2, if we know it. pub fn from_ret(ret: i64) -> Option<Errno> { use Errno::*;- const ALL: [Errno; 52] = [+ const ALL: [Errno; 54] = [ EPERM, ESRCH, EINTR, ERESTARTSYS, ERESTARTNOHAND, ENOENT, EIO, E2BIG, ENOEXEC, EBADF, ECHILD, EAGAIN, ENOMEM, EFAULT, EBUSY, EEXIST, ENODEV, ENOTDIR, EISDIR, EINVAL, EMFILE, ENOTTY, EFBIG, ENOSPC, ESPIPE, EROFS, EPIPE, ERANGE, ENAMETOOLONG, ENOSYS, ENOTEMPTY, ELOOP, EOPNOTSUPP, ENOTSOCK, EDESTADDRREQ, EMSGSIZE, EPROTONOSUPPORT, EAFNOSUPPORT, EADDRINUSE, ENETDOWN, ENETUNREACH, ECONNRESET, EISCONN,- ENOTCONN, ETIMEDOUT, ECONNREFUSED, ENXIO, EINPROGRESS, EXDEV, ENOPROTOOPT, EDEADLK, ENOLCK,+ ENOTCONN, ETIMEDOUT, ECONNREFUSED, ENXIO, EINPROGRESS, EXDEV, ENOPROTOOPT, EDEADLK, ENOLCK, EMLINK,+ EACCES, ]; ALL.into_iter().find(|&e| -(e as i64) == ret) }
@@ -332,6 +332,10 @@ struct Loc { struct InodeItem { size: u64, mode: u32,+ /// Hard links, owner and group (stage 17: stat shows them).+ nlink: u32,+ uid: u32,+ gid: u32, flags: u64, /// atime, mtime, ctime as [seconds, nanoseconds] (stage 16: for stat). times: [[i64; 2]; 3],@@ -752,7 +756,15 @@ impl Volume { // end: atime 112, ctime 124, mtime 136, otime 148. let ts = |at: usize| [get64(&d, at) as i64, get32(&d, at + 8) as i64]; let times = [ts(112), ts(136), ts(124)];- Ok(InodeItem { size: get64(&d, INODE_SIZE), mode: get32(&d, INODE_MODE), flags: get64(&d, INODE_FLAGS), times })+ Ok(InodeItem {+ size: get64(&d, INODE_SIZE),+ mode: get32(&d, INODE_MODE),+ nlink: get32(&d, 40),+ uid: get32(&d, 44),+ gid: get32(&d, 48),+ flags: get64(&d, INODE_FLAGS),+ times,+ }) } /// Where a directory entry points: an inode in the same tree, or the@@ -1072,6 +1084,30 @@ impl FileSystem for BtrfsFs { fn root(&self) -> Arc<dyn Inode> { Arc::new(BtrfsInode { vol: self.vol.clone(), loc: self.root, kind: InodeKind::Directory }) }+ /// From the superblock's totals (in sectors). btrfs doesn't count+ /// inodes (it makes them as needed), so files = 0, like Linux.+ fn statfs(&self) -> super::StatFs {+ let v = self.vol.lock();+ let bs = v.sectorsize.max(1) as u64;+ let free = v.total_bytes.saturating_sub(v.bytes_used) / bs;+ super::StatFs {+ magic: super::BTRFS_MAGIC,+ block_size: bs,+ blocks: v.total_bytes / bs,+ free_blocks: free,+ avail_blocks: free,+ files: 0,+ free_files: 0,+ name_max: 255,+ read_only: true, // our driver only reads+ }+ }+}++/// An inode number unique across subvolumes: each subvolume numbers its+/// inodes from 256, so other subvolumes' get their tree id on top.+fn ino_of(loc: Loc) -> u64 {+ if loc.root == FS_TREE { loc.ino } else { loc.ino | loc.root << 40 } } /// A file, directory or symlink: (subvolume, inode number).@@ -1140,6 +1176,7 @@ impl Inode for BtrfsInode { Ok(entries .into_iter() .map(|e| DirEntry {+ ino: if e.location.kind == ROOT_ITEM { 256 | e.location.objectid << 40 } else { ino_of(Loc { root: self.loc.root, ino: e.location.objectid }) }, name: String::from_utf8_lossy(&e.name).into_owned(), kind: match e.file_type { 2 => InodeKind::Directory,@@ -1172,6 +1209,28 @@ impl Inode for BtrfsInode { fn remove(&self, _name: &str) -> Result<(), Errno> { Err(Errno::EROFS) }+ fn set_owner(&self, _uid: Option<u32>, _gid: Option<u32>) -> Result<(), Errno> {+ Err(Errno::EROFS)+ }+ fn symlink(&self, _name: &str, _target: &str) -> Result<Arc<dyn Inode>, Errno> {+ Err(Errno::EROFS)+ }+ fn link(&self, _name: &str, _target: &Arc<dyn Inode>) -> Result<(), Errno> {+ Err(Errno::EROFS)+ }+ fn rename_entry(&self, _old: &str, _new_dir: &Arc<dyn Inode>, _new: &str) -> Result<(), Errno> {+ Err(Errno::EROFS)+ }++ fn ino(&self) -> Option<u64> {+ Some(ino_of(self.loc))+ }+ fn nlink(&self) -> Option<u64> {+ self.vol.lock().inode(self.loc).ok().map(|i| i.nlink as u64)+ }+ fn owner(&self) -> (u32, u32) {+ self.vol.lock().inode(self.loc).map(|i| (i.uid, i.gid)).unwrap_or((0, 0))+ } } #[cfg(test)]
@@ -902,6 +902,22 @@ impl FileSystem for ExfatFs { fn root(&self) -> Arc<dyn Inode> { Arc::new(ExfatInode { vol: self.vol.clone(), path: Vec::new(), kind: InodeKind::Directory }) }+ /// statfs (stage 17): blocks are clusters, counted by the bitmap.+ fn statfs(&self) -> super::StatFs {+ let v = self.vol.lock();+ let free = v.cluster_count.saturating_sub(v.used) as u64;+ super::StatFs {+ magic: super::EXFAT_MAGIC,+ block_size: v.cluster_size as u64,+ blocks: v.cluster_count as u64,+ free_blocks: free,+ avail_blocks: free,+ files: 0,+ free_files: 0,+ name_max: 255,+ read_only: false,+ }+ } } /// A file or directory on an exFAT volume. exFAT has no inode numbers (a@@ -980,6 +996,7 @@ impl Inode for ExfatInode { Ok(sets .into_iter() .map(|s| DirEntry {+ ino: 0, name: String::from_utf16_lossy(&s.name), kind: if s.attrs & ATTR_DIRECTORY != 0 { InodeKind::Directory } else { InodeKind::File }, })
@@ -97,6 +97,7 @@ const ROOT_INO: u32 = 2; // Superblock fields (byte offsets inside the 1024-byte superblock). const SB_INODES_COUNT: usize = 0; const SB_BLOCKS_COUNT: usize = 4;+const SB_R_BLOCKS: usize = 8; // blocks reserved for root const SB_FREE_BLOCKS: usize = 12; const SB_FREE_INODES: usize = 16; const SB_FIRST_DATA_BLOCK: usize = 20; // block holding the superblock: 1 with 1 KiB blocks, else 0@@ -129,6 +130,10 @@ const GD_USED_DIRS: usize = 16; // u16 // Inode fields (the classic first 128 bytes). const I_MODE: usize = 0; // u16+const I_UID: usize = 2; // u16 (low half; the high half is at I_UID_HIGH)+const I_GID: usize = 24; // u16+const I_UID_HIGH: usize = 120; // in the Linux part of "osd2"+const I_GID_HIGH: usize = 122; const I_SIZE: usize = 4; const I_ATIME: usize = 8; const I_CTIME: usize = 12;@@ -150,6 +155,7 @@ const S_IFLNK: u16 = 0o120000; // Directory entry file types (with INCOMPAT_FILETYPE). const FT_REG: u8 = 1; const FT_DIR: u8 = 2;+const FT_SYMLINK: u8 = 7; const INDEX_FL: u32 = 0x1000; // directory has an htree index @@ -237,6 +243,29 @@ impl DiskInode { fn set_times(&mut self, which: &[usize], t: u32) { which.iter().for_each(|&at| put32(&mut self.raw, at, t)); }+ fn owner(&self) -> (u32, u32) {+ let uid = get16(&self.raw, I_UID) as u32 | (get16(&self.raw, I_UID_HIGH) as u32) << 16;+ let gid = get16(&self.raw, I_GID) as u32 | (get16(&self.raw, I_GID_HIGH) as u32) << 16;+ (uid, gid)+ }+ fn set_owner(&mut self, uid: Option<u32>, gid: Option<u32>) {+ if let Some(uid) = uid {+ put16(&mut self.raw, I_UID, uid as u16);+ put16(&mut self.raw, I_UID_HIGH, (uid >> 16) as u16);+ }+ if let Some(gid) = gid {+ put16(&mut self.raw, I_GID, gid as u16);+ put16(&mut self.raw, I_GID_HIGH, (gid >> 16) as u16);+ }+ }+ /// The directory-entry type byte for this inode.+ fn file_type(&self) -> u8 {+ match self.kind() {+ InodeKind::Directory => FT_DIR,+ InodeKind::Symlink => FT_SYMLINK,+ _ => FT_REG,+ }+ } } /// One directory entry, as read from disk.@@ -334,10 +363,10 @@ impl Volume { get32(&self.sb, at) } - /// Timestamp for new and changed inodes. We have no clock driver yet,- /// so we reuse the superblock's "last written" time (set by mke2fs).+ /// Timestamp for new and changed inodes: the wall clock (stage 17;+ /// before, with no clock driver, the superblock's "last written" time). fn now(&self) -> u32 {- self.sb32(SB_WTIME).max(1)+ (super::now_timespec()[0].clamp(0, u32::MAX as i64) as u32).max(self.sb32(SB_WTIME)).max(1) } // ---- blocks ----------------------------------------------------------@@ -899,6 +928,141 @@ impl Volume { self.free_inode(ino, is_dir) } + /// Checks every new name goes through: writable, a valid name, the+ /// parent a directory without that name yet. Returns the parent.+ fn check_new_entry(&mut self, parent_ino: u32, name: &str) -> Result<DiskInode, Errno> {+ if !self.writable {+ return Err(Errno::EROFS);+ }+ if name.is_empty() || name == "." || name == ".." || name.contains('/') {+ return Err(Errno::EINVAL);+ }+ if name.len() > 255 {+ return Err(Errno::ENAMETOOLONG);+ }+ let parent = self.read_inode(parent_ino)?;+ if parent.kind() != InodeKind::Directory {+ return Err(Errno::ENOTDIR);+ }+ if self.find_entry(&parent, name)?.is_some() {+ return Err(Errno::EEXIST);+ }+ Ok(parent)+ }++ /// symlink (stage 17). Like Linux, a target of up to 60 bytes is a+ /// "fast" symlink: stored in the inode's block pointers themselves, no+ /// data block. Longer targets (up to one block) get a data block.+ fn symlink(&mut self, parent_ino: u32, name: &str, target: &str) -> Result<u32, Errno> {+ let mut parent = self.check_new_entry(parent_ino, name)?;+ if target.len() > self.block_size.min(4095) {+ return Err(Errno::ENAMETOOLONG);+ }+ let mut inode = self.alloc_inode(self.group_of(parent_ino), false)?;+ let now = self.now();+ inode.set_times(&[I_ATIME, I_CTIME, I_MTIME], now);+ put16(&mut inode.raw, I_MODE, S_IFLNK | 0o777);+ inode.set_links(1);+ let result = (|| {+ if target.len() <= 60 {+ inode.raw[I_BLOCK..I_BLOCK + target.len()].copy_from_slice(target.as_bytes());+ inode.set_size(target.len() as u64);+ self.write_inode(&inode)?;+ } else {+ self.write_file(&mut inode, 0, target.as_bytes())?; // (writes the inode)+ }+ self.add_entry(&mut parent, name, inode.ino, FT_SYMLINK)+ })();+ if let Err(e) = result {+ if target.len() > 60 {+ let _ = self.free_data(&mut inode);+ }+ let _ = self.free_inode(inode.ino, false);+ return Err(e);+ }+ Ok(inode.ino)+ }++ /// Hard link (stage 17): one more entry for inode `ino`, one more in+ /// its link count.+ fn link(&mut self, parent_ino: u32, name: &str, ino: u32) -> Result<(), Errno> {+ let mut parent = self.check_new_entry(parent_ino, name)?;+ let mut child = self.read_inode(ino)?;+ if child.kind() == InodeKind::Directory {+ return Err(Errno::EPERM);+ }+ if child.links() >= 32000 {+ return Err(Errno::EMLINK); // Linux's EXT2_LINK_MAX+ }+ self.add_entry(&mut parent, name, ino, child.file_type())?;+ child.set_links(child.links() + 1);+ let now = self.now();+ child.set_times(&[I_CTIME], now);+ self.write_inode(&child)+ }++ /// rename (stage 17): add the new entry, drop the old one. A replaced+ /// file loses a link (and is freed if that was its last); a moved+ /// directory gets its ".." pointed at the new parent. The VFS checked+ /// the kinds and that a replaced directory is empty.+ fn rename(&mut self, old_parent: u32, old: &str, new_parent: u32, new: &str) -> Result<(), Errno> {+ if !self.writable {+ return Err(Errno::EROFS);+ }+ if new.is_empty() || new == "." || new == ".." || new.contains('/') {+ return Err(Errno::EINVAL);+ }+ if new.len() > 255 {+ return Err(Errno::ENAMETOOLONG);+ }+ let dir = self.read_inode(old_parent)?;+ let ino = self.find_entry(&dir, old)?.ok_or(Errno::ENOENT)?;+ let mut child = self.read_inode(ino)?;+ let is_dir = child.kind() == InodeKind::Directory;+ let target_dir = self.read_inode(new_parent)?;+ if let Some(existing) = self.find_entry(&target_dir, new)? {+ if existing == ino {+ return Ok(());+ }+ self.remove(new_parent, new)?;+ }+ let mut target_dir = self.read_inode(new_parent)?;+ self.add_entry(&mut target_dir, new, ino, child.file_type())?;+ let mut dir = self.read_inode(old_parent)?;+ self.remove_entry(&mut dir, old)?;+ if is_dir && old_parent != new_parent {+ self.set_dotdot(&mut child, new_parent)?;+ let mut from = self.read_inode(old_parent)?;+ from.set_links(from.links() - 1);+ self.write_inode(&from)?;+ let mut to = self.read_inode(new_parent)?;+ to.set_links(to.links() + 1);+ self.write_inode(&to)?;+ }+ let now = self.now();+ child.set_times(&[I_CTIME], now);+ self.write_inode(&child)+ }++ /// Point a directory's ".." entry at `parent`.+ fn set_dotdot(&mut self, dir: &mut DiskInode, parent: u32) -> Result<(), Errno> {+ let block = self.bmap(dir, 0, false)?;+ if block == 0 {+ return Err(Errno::EIO);+ }+ let mut data = self.read_block(block)?;+ let mut at = 0;+ while at < data.len() {+ let (e_ino, rec_len, name_len, _) = self.parse_entry(&data, at)?;+ if e_ino != 0 && &data[at + 8..at + 8 + name_len] == b".." {+ put32(&mut data, at, parent);+ return self.write_block(block, &data);+ }+ at += rec_len;+ }+ Err(Errno::EIO) // a directory without ".."+ }+ fn truncate(&mut self, ino: u32) -> Result<(), Errno> { if !self.writable { return Err(Errno::EROFS);@@ -976,6 +1140,21 @@ impl FileSystem for Ext2Fs { fn root(&self) -> Arc<dyn Inode> { Arc::new(Ext2Inode { vol: self.vol.clone(), ino: ROOT_INO, kind: InodeKind::Directory }) }+ fn statfs(&self) -> super::StatFs {+ let v = self.vol.lock();+ let free = v.sb32(SB_FREE_BLOCKS) as u64;+ super::StatFs {+ magic: super::EXT2_MAGIC,+ block_size: v.block_size as u64,+ blocks: v.sb32(SB_BLOCKS_COUNT) as u64,+ free_blocks: free,+ avail_blocks: free.saturating_sub(v.sb32(SB_R_BLOCKS) as u64),+ files: v.sb32(SB_INODES_COUNT) as u64,+ free_files: v.sb32(SB_FREE_INODES) as u64,+ name_max: 255,+ read_only: !v.writable,+ }+ } } /// A file, directory or symlink on an ext2 volume: just its inode number.@@ -1119,7 +1298,7 @@ impl Inode for Ext2Inode { 7 => InodeKind::Symlink, _ => vol.read_inode(e.ino)?.kind(), // revision 0: no type in the entry };- out.push(DirEntry { name: e.name, kind });+ out.push(DirEntry { name: e.name, kind, ino: e.ino as u64 }); } Ok(out) }@@ -1128,4 +1307,51 @@ impl Inode for Ext2Inode { self.dir_only()?; self.vol.lock().remove(self.ino, name) }++ // --- stage 17 ----------------------------------------------------------++ fn ino(&self) -> Option<u64> {+ Some(self.ino as u64)+ }++ fn nlink(&self) -> Option<u64> {+ self.vol.lock().read_inode(self.ino).ok().map(|i| i.links() as u64)+ }++ fn owner(&self) -> (u32, u32) {+ self.vol.lock().read_inode(self.ino).map(|i| i.owner()).unwrap_or((0, 0))+ }++ fn set_owner(&self, uid: Option<u32>, gid: Option<u32>) -> Result<(), Errno> {+ let vol = self.vol.lock();+ if !vol.writable {+ return Err(Errno::EROFS);+ }+ let mut inode = vol.read_inode(self.ino)?;+ inode.set_owner(uid, gid);+ inode.set_times(&[I_CTIME], vol.now());+ vol.write_inode(&inode)+ }++ fn symlink(&self, name: &str, target: &str) -> Result<Arc<dyn Inode>, Errno> {+ self.dir_only()?;+ let ino = self.vol.lock().symlink(self.ino, name, target)?;+ Ok(self.child(ino, InodeKind::Symlink))+ }++ fn link(&self, name: &str, target: &Arc<dyn Inode>) -> Result<(), Errno> {+ self.dir_only()?;+ let other = super::downcast::<Ext2Inode>(target).filter(|o| Arc::ptr_eq(&o.vol, &self.vol)).ok_or(Errno::EXDEV)?;+ self.vol.lock().link(self.ino, name, other.ino)+ }++ fn rename_entry(&self, old: &str, new_dir: &Arc<dyn Inode>, new: &str) -> Result<(), Errno> {+ self.dir_only()?;+ let other = super::downcast::<Ext2Inode>(new_dir).filter(|o| Arc::ptr_eq(&o.vol, &self.vol)).ok_or(Errno::EXDEV)?;+ self.vol.lock().rename(self.ino, old, other.ino, new)+ }++ fn as_any(self: Arc<Self>) -> Option<Arc<dyn core::any::Any + Send + Sync>> {+ Some(self)+ } }
@@ -207,6 +207,26 @@ impl FileSystem for FatFs { fn root(&self) -> Arc<dyn Inode> { Arc::new(FatInode { fs: self.fs.clone(), path: String::new(), kind: InodeKind::Directory }) }+ /// statfs (stage 17): FAT counts clusters, so a "block" is a cluster.+ /// There's no inode table, so files = 0 (Linux's vfat does the same).+ fn statfs(&self) -> super::StatFs {+ let fs = self.fs.lock();+ let (bs, total, free) = match fs.stats() {+ Ok(st) => (st.cluster_size() as u64, st.total_clusters() as u64, st.free_clusters() as u64),+ Err(_) => (fs.cluster_size() as u64, 0, 0),+ };+ super::StatFs {+ magic: super::MSDOS_MAGIC,+ block_size: bs,+ blocks: total,+ free_blocks: free,+ avail_blocks: free,+ files: 0,+ free_files: 0,+ name_max: 255,+ read_only: false,+ }+ } } /// A file or directory on a FAT volume, identified by its path from the@@ -366,7 +386,7 @@ impl Inode for FatInode { continue; // the VFS doesn't list these (like our ramfs) } let kind = if entry.is_dir() { InodeKind::Directory } else { InodeKind::File };- out.push(DirEntry { name, kind });+ out.push(DirEntry { name, kind, ino: 0 }); } Ok(out) })
@@ -112,6 +112,30 @@ impl LinuxStat { } } +/// `struct stat` for an inode reached by `path` (stage 17): the real+/// inode number, link count and owner where the file system has them+/// (ramfs, ext2, btrfs), the path hash and made-up values where it doesn't+/// (FAT, exFAT, /dev), and which mounted file system it's on (st_dev).+pub fn linux_stat(inode: &dyn Inode, path: &str) -> LinuxStat {+ let mut st = LinuxStat::new(&Stat::of(inode), inode.ino().unwrap_or_else(|| ino_hash(path.as_bytes()))).with_times(inode.times());+ if let Some(n) = inode.nlink() {+ st.st_nlink = n.max(1);+ }+ (st.st_uid, st.st_gid) = inode.owner();+ st.st_dev = super::dev_of(path);+ st+}++/// The inode number getdents reports for entry `name` of the directory at+/// `dir`: the file system's own, or the same path hash `linux_stat` uses.+fn entry_ino(dir: &str, entry: &super::DirEntry) -> u64 {+ if entry.ino != 0 {+ return entry.ino;+ }+ let path = if dir == "/" { alloc::format!("/{}", entry.name) } else { alloc::format!("{}/{}", dir, entry.name) };+ ino_hash(path.as_bytes()).max(1)+}+ /// A 64-bit FNV-1a hash: our stand-in for inode numbers (from the path). pub fn ino_hash(bytes: &[u8]) -> u64 { bytes.iter().fold(0xcbf2_9ce4_8422_2325, |h, &b| (h ^ b as u64).wrapping_mul(0x100_0000_01b3))@@ -226,6 +250,17 @@ pub struct InodeFile { accmode: u64, /// The absolute path it was opened by (see `File::path`). path: String,+ /// Directories (stage 17): the listing, taken at the first getdents+ /// and kept until a rewind, so deleting entries while reading the+ /// directory (`rm -r`) doesn't make us skip any.+ listing: Mutex<Option<alloc::vec::Vec<super::DirEntry>>>,+}++impl Drop for InodeFile {+ /// The last fd for this open file is gone: drop its flock (stage 17).+ fn drop(&mut self) {+ super::lock::flock_release(self as *const InodeFile as usize);+ } } /// open() flag bits InodeFile looks at (Linux's values).@@ -259,6 +294,7 @@ impl InodeFile { nonblock: AtomicBool::new(flags & O_NONBLOCK != 0), accmode, path,+ listing: Mutex::new(None), }) } }@@ -283,7 +319,17 @@ impl File for InodeFile { fn seek(&self, offset: i64, whence: u64) -> Result<u64, Errno> { if self.inode.kind() == InodeKind::Directory {- return if offset == 0 && whence == SEEK_SET { *self.offset.lock() = 0; Ok(0) } else { Err(Errno::EINVAL) };+ // The offset is an entry index (what getdents puts in d_off),+ // so seekdir/telldir work. Back to 0 (rewinddir) re-reads it.+ if whence != SEEK_SET || offset < 0 {+ return if whence == SEEK_CUR && offset == 0 { Ok(*self.offset.lock() as u64) } else { Err(Errno::EINVAL) };+ }+ let mut listing = self.listing.lock();+ if offset == 0 {+ *listing = None;+ }+ *self.offset.lock() = offset as usize;+ return Ok(offset as u64); } let mut current = self.offset.lock(); let base = match whence {@@ -348,9 +394,14 @@ impl File for InodeFile { } fn getdents(&self, buf: &mut [u8]) -> Result<usize, Errno> {- // Re-list the directory each call and skip what we've returned- // already (simple; a real fs would keep a cursor into the directory).- let entries = self.inode.list()?;+ // List the directory once (stage 17; before, each call re-listed+ // it and skipped by index, which skipped entries when files were+ // deleted in between) and hand it out from that copy.+ let mut listing = self.listing.lock();+ if listing.is_none() {+ *listing = Some(self.inode.list()?);+ }+ let entries = listing.as_ref().unwrap(); let mut index = self.offset.lock(); let mut used = 0; for entry in entries.iter().skip(*index) {@@ -365,7 +416,7 @@ impl File for InodeFile { } let rec = &mut buf[used..used + reclen]; rec.fill(0);- rec[0..8].copy_from_slice(&(*index as u64 + 1).to_le_bytes()); // fake inode number+ rec[0..8].copy_from_slice(&entry_ino(&self.path, entry).to_le_bytes()); // the inode number stat gives rec[8..16].copy_from_slice(&(*index as i64 + 1).to_le_bytes()); // offset of next rec[16..18].copy_from_slice(&(reclen as u16).to_le_bytes()); rec[18] = match entry.kind {
@@ -188,7 +188,75 @@ pub fn process_exited(pid: u64) { UNLOCKED.wake_all(); } -/// How many locks exist (tests: nothing left behind).+/// How many locks exist, record locks and flocks (tests: nothing left behind). pub fn count() -> usize {- without_interrupts(|| TABLE.lock().files.values().map(|v| v.len()).sum())+ without_interrupts(|| TABLE.lock().files.values().map(|v| v.len()).sum::<usize>()) + flock_count()+}++// --- flock (stage 17) ------------------------------------------------------+//+// flock(fd, LOCK_SH / LOCK_EX / LOCK_UN, maybe | LOCK_NB) locks a *whole*+// file, BSD style. Unlike the record locks above, a flock belongs to the+// *open file* (what open() made; dup() and fork() share it), not to a+// process: two open()s of the same file in one process do conflict, and the+// lock goes away when the last fd sharing that open file is closed+// (`Drop for InodeFile` calls `flock_release`).+//+// Many holders may share LOCK_SH; LOCK_EX allows nobody else. Asking again+// on an open file that already holds one *converts* it (SH <-> EX).++/// file key -> the open files holding a flock on it: (owner, exclusive).+static FLOCKS: Mutex<BTreeMap<u64, Vec<(usize, bool)>>> = Mutex::new(BTreeMap::new());++fn flock_conflict(t: &BTreeMap<u64, Vec<(usize, bool)>>, key: u64, owner: usize, exclusive: bool) -> bool {+ t.get(&key).is_some_and(|v| v.iter().any(|&(o, ex)| o != owner && (ex || exclusive)))+}++/// Remove `owner`'s flock (any file). True if it had one.+fn flock_drop(t: &mut BTreeMap<u64, Vec<(usize, bool)>>, owner: usize) -> bool {+ let mut found = false;+ for v in t.values_mut() {+ let before = v.len();+ v.retain(|&(o, _)| o != owner);+ found |= v.len() != before;+ }+ t.retain(|_, v| !v.is_empty());+ found+}++/// Take (or convert to) a shared / exclusive flock on file `key` for open+/// file `owner`. `wait`: sleep until it's free (a signal interrupts:+/// ERESTARTSYS); else EAGAIN (= EWOULDBLOCK) at once.+pub fn flock(key: u64, owner: usize, exclusive: bool, wait: bool) -> Result<(), Errno> {+ let try_take = || {+ let mut t = FLOCKS.lock();+ if flock_conflict(&t, key, owner, exclusive) {+ return None;+ }+ let v = t.entry(key).or_default();+ v.retain(|&(o, _)| o != owner);+ v.push((owner, exclusive));+ Some(())+ };+ if !wait {+ return without_interrupts(try_take).ok_or(Errno::EAGAIN);+ }+ // Linux drops the old lock before waiting for a conversion (so two+ // holders upgrading at once can't deadlock); so do we.+ if without_interrupts(|| flock_drop(&mut FLOCKS.lock(), owner)) {+ UNLOCKED.wake_all();+ }+ UNLOCKED.wait_until_intr(try_take)+}++/// LOCK_UN, or the open file went away: drop its flock, if any.+pub fn flock_release(owner: usize) {+ if without_interrupts(|| flock_drop(&mut FLOCKS.lock(), owner)) {+ UNLOCKED.wake_all();+ }+}++/// How many flocks are held (tests: nothing left behind).+pub fn flock_count() -> usize {+ without_interrupts(|| FLOCKS.lock().values().map(|v| v.len()).sum()) }
@@ -57,6 +57,9 @@ pub enum InodeKind { pub struct DirEntry { pub name: String, pub kind: InodeKind,+ /// The child's inode number (stage 17: what getdents64 reports as+ /// d_ino), or 0 if the file system has none (FAT, exFAT, /dev).+ pub ino: u64, } /// A file or directory inside a file system.@@ -165,8 +168,83 @@ pub trait Inode: Send + Sync { fn set_times(&self, _atime: Option<[i64; 2]>, _mtime: Option<[i64; 2]>) -> Result<(), Errno> { Ok(()) }++ // --- Stage 17: links, owners, renames (coreutils / busybox) ----------++ /// A real inode number (ext2's, btrfs's, a ramfs node's address), or+ /// None: stat then hashes the path instead (FAT, exFAT, /dev).+ fn ino(&self) -> Option<u64> {+ None+ }+ /// How many names (hard links) the inode has; None = "1, or 2 for a+ /// directory" (file systems without hard links).+ fn nlink(&self) -> Option<u64> {+ None+ }+ /// Owner (uid, gid). File systems without owners say root (0, 0).+ fn owner(&self) -> (u32, u32) {+ (0, 0)+ }+ /// chown: set the owner and/or group (None = leave it). EPERM where+ /// the file system can't store them (FAT, exFAT, /dev, like Linux's+ /// vfat), EROFS on read-only btrfs.+ fn set_owner(&self, _uid: Option<u32>, _gid: Option<u32>) -> Result<(), Errno> {+ Err(Errno::EPERM)+ }+ /// Create a symlink `name` -> `target` in this directory. EPERM where+ /// the file system has no symlinks (FAT and exFAT: Linux says EPERM too).+ fn symlink(&self, _name: &str, _target: &str) -> Result<Arc<dyn Inode>, Errno> {+ Err(Errno::EPERM)+ }+ /// Hard link: add `name` in this directory for the existing `target`+ /// (same file system, checked by the caller). EPERM where there are no+ /// hard links (FAT, exFAT).+ fn link(&self, _name: &str, _target: &Arc<dyn Inode>) -> Result<(), Errno> {+ Err(Errno::EPERM)+ }+ /// Move entry `old` of this directory to `new` in `new_dir` (same file+ /// system), replacing what's there (the caller checked that's allowed).+ /// ENOSYS = "can't do it directly": `rename` then copies and deletes.+ fn rename_entry(&self, _old: &str, _new_dir: &Arc<dyn Inode>, _new: &str) -> Result<(), Errno> {+ Err(Errno::ENOSYS)+ }+ /// The concrete type behind the trait object, so a file system can+ /// recognise its own inodes (for link and rename). None = don't care.+ fn as_any(self: Arc<Self>) -> Option<Arc<dyn core::any::Any + Send + Sync>> {+ None+ }+}++/// Downcast an inode to a file system's own type (stage 17).+pub fn downcast<T: Send + Sync + 'static>(inode: &Arc<dyn Inode>) -> Option<Arc<T>> {+ inode.clone().as_any()?.downcast::<T>().ok()+}++/// What statfs/fstatfs report about a file system (stage 17).+#[derive(Debug, Clone, Copy, Default)]+pub struct StatFs {+ /// The file system's magic number (f_type), as in <linux/magic.h>.+ pub magic: u64,+ /// Block size in bytes, and the counts in those blocks.+ pub block_size: u64,+ pub blocks: u64,+ pub free_blocks: u64,+ /// Free blocks an ordinary user may use (ext2 keeps some for root).+ pub avail_blocks: u64,+ /// Inodes (0 = the file system doesn't count them).+ pub files: u64,+ pub free_files: u64,+ pub name_max: u64,+ pub read_only: bool, } +pub const RAMFS_MAGIC: u64 = 0x8584_58f6;+pub const TMPFS_MAGIC: u64 = 0x0102_1994;+pub const EXT2_MAGIC: u64 = 0xEF53;+pub const MSDOS_MAGIC: u64 = 0x4d44;+pub const EXFAT_MAGIC: u64 = 0x2011_BAB0;+pub const BTRFS_MAGIC: u64 = 0x9123_683E;+ /// The wall-clock time as [seconds, nanoseconds] since 1970 (file times). pub fn now_timespec() -> [i64; 2] { let us = crate::time::realtime_us();@@ -182,6 +260,11 @@ pub trait FileSystem: Send + Sync { /// Type name, like the `-t` argument of `mount` ("ramfs", "fat32", ...). fn name(&self) -> &'static str; fn root(&self) -> Arc<dyn Inode>;+ /// Sizes and counts for statfs (stage 17). The default suits a+ /// memory-only file system that keeps no counts (/dev: devtmpfs).+ fn statfs(&self) -> StatFs {+ StatFs { magic: TMPFS_MAGIC, block_size: 4096, name_max: 255, ..Default::default() }+ } } struct Mount {@@ -347,38 +430,117 @@ fn mount_of(path: &str) -> Option<String> { mounts.iter().filter(|m| mount_contains(&m.path, path)).max_by_key(|m| m.path.len()).map(|m| m.path.clone()) } -/// rename(old, new). Our `Inode` trait has no "move this entry" operation-/// yet, so we do it the long way, which is enough for what programs use-/// rename for (save to a temp file, then rename it over the real one):+/// The file system `path` lives on and its place in the mount table+/// (stage 17: statfs, and st_dev = place + 1 so that inode numbers of+/// different file systems never look like the same file).+pub fn fs_of(path: &str) -> Option<(usize, Arc<dyn FileSystem>)> {+ let path = normalize("/", path).ok()?;+ let mounts = MOUNTS.lock();+ mounts+ .iter()+ .enumerate()+ .filter(|(_, m)| mount_contains(&m.path, &path))+ .max_by_key(|(_, m)| m.path.len())+ .map(|(i, m)| (i, m.fs.clone()))+}++/// st_dev for a path: which mounted file system (see `fs_of`).+pub fn dev_of(path: &str) -> u64 {+ fs_of(path).map(|(i, _)| i as u64 + 1).unwrap_or(0)+}++/// symlink(target, path): a new symlink at absolute `path` holding+/// `target` exactly as given (it's resolved when the link is followed).+pub fn symlink(target: &str, path: &str) -> Result<Arc<dyn Inode>, Errno> {+ if target.is_empty() {+ return Err(Errno::ENOENT);+ }+ if lookup_nofollow(path).is_ok() {+ return Err(Errno::EEXIST);+ }+ let (parent, name) = parent_of(path)?;+ parent.symlink(&name, target)+}++/// link(old, new): a second name for the file at `old` (`follow`: if `old`+/// is a symlink, link what it points to, like linkat's AT_SYMLINK_FOLLOW).+pub fn link(old: &str, new: &str, follow: bool) -> Result<(), Errno> {+ let (old, new) = (normalize("/", old)?, normalize("/", new)?);+ let src = if follow { lookup(&old)? } else { lookup_nofollow(&old)? };+ link_inode(&src, &old, &new)+}++/// Hard-link the inode `src` (found at `src_path`) as `new`.+pub fn link_inode(src: &Arc<dyn Inode>, src_path: &str, new: &str) -> Result<(), Errno> {+ if src.kind() == InodeKind::Directory {+ return Err(Errno::EPERM); // no hard links to directories, ever+ }+ let new = normalize("/", new)?;+ if lookup_nofollow(&new).is_ok() {+ return Err(Errno::EEXIST);+ }+ if mount_of(src_path) != mount_of(&new) {+ return Err(Errno::EXDEV);+ }+ let (parent, name) = parent_of(&new)?;+ parent.link(&name, src)+}++/// rename(old, new), and renameat2's RENAME_NOREPLACE (`noreplace`: fail+/// with EEXIST instead of replacing `new`).+///+/// ramfs, ext2 and FAT move the directory entry itself (stage 17,+/// `Inode::rename_entry`): any file, symlink or directory, full or not,+/// and hard links stay links. Other file systems (exFAT) get the long way+/// round, enough for "save to a temp file, rename it over the real one": /// - a file: copy its bytes to `new` (replacing a file there), then /// delete `old`; /// - an empty directory: create `new` (or reuse an empty one), delete `old`;-/// - anything else (a non-empty directory, a symlink, a device), or-/// across file systems: EXDEV, which tells `mv` to copy and delete-/// itself, like Linux does across file systems.-/// Not atomic, unlike Linux's: a crash in the middle can leave both.-pub fn rename(old: &str, new: &str) -> Result<(), Errno> {+/// - anything else (a non-empty directory, a symlink), or across file+/// systems: EXDEV, which tells `mv` to copy and delete itself, like+/// Linux does across file systems.+/// Neither is atomic like Linux's: a crash in the middle can leave both.+pub fn rename(old: &str, new: &str, noreplace: bool) -> Result<(), Errno> { let (old, new) = (normalize("/", old)?, normalize("/", new)?); let src = lookup_nofollow(&old)?;- if old == new {- return Ok(());- } if new.starts_with(&old) && new.as_bytes().get(old.len()) == Some(&b'/') { return Err(Errno::EINVAL); // a directory into itself }+ let existing = lookup_nofollow(&new).ok();+ if noreplace && existing.is_some() {+ return Err(Errno::EEXIST); // (even renaming a file onto itself)+ }+ if old == new {+ return Ok(());+ } if MOUNTS.lock().iter().any(|m| m.path == old || m.path == new) { return Err(Errno::EBUSY); } if mount_of(&old) != mount_of(&new) { return Err(Errno::EXDEV); }- let existing = lookup_nofollow(&new).ok();+ // What may be replaced: a directory only by a directory, and only an+ // empty one; anything else only by a non-directory.+ if let Some(dst) = &existing {+ if src.ino().is_some() && src.ino() == dst.ino() {+ return Ok(()); // two hard links of one file: nothing to do (POSIX)+ }+ match (src.kind() == InodeKind::Directory, dst.kind() == InodeKind::Directory) {+ (true, false) => return Err(Errno::ENOTDIR),+ (false, true) => return Err(Errno::EISDIR),+ (true, true) if !dst.list()?.is_empty() => return Err(Errno::ENOTEMPTY),+ _ => {}+ }+ }+ let (old_parent, old_name) = parent_of(&old)?;+ let (new_parent, new_name) = parent_of(&new)?;+ match old_parent.rename_entry(&old_name, &new_parent, &new_name) {+ Err(Errno::ENOSYS) => {} // not supported here: the long way below+ other => return other,+ } match src.kind() { InodeKind::File => {- if let Some(dst) = &existing {- if dst.kind() == InodeKind::Directory {- return Err(Errno::EISDIR);- }+ if existing.is_some() { remove(&new, false)?; } let data = read_all(&old)?;@@ -393,13 +555,8 @@ pub fn rename(old: &str, new: &str) -> Result<(), Errno> { if !src.list()?.is_empty() { return Err(Errno::EXDEV); }- match &existing {- Some(dst) if dst.kind() != InodeKind::Directory => return Err(Errno::ENOTDIR),- Some(dst) if !dst.list()?.is_empty() => return Err(Errno::ENOTEMPTY),- Some(_) => {}- None => {- create(&new, InodeKind::Directory)?;- }+ if existing.is_none() {+ create(&new, InodeKind::Directory)?; } remove(&old, true) }
@@ -3,8 +3,10 @@ //! of nodes; files are a `Vec<u8>`, directories a sorted map name -> node. //! Since stage 16 every node also remembers its permission bits (chmod) //! and its three times (utimensat, and writes update the modification time).+//! Stage 17 adds symlinks, hard links (two directory entries holding the+//! same `Arc`), owners (chown) and renames that just move the `Arc`. -use super::{now_timespec, DirEntry, FileSystem, Inode, InodeKind, MODE_SET};+use super::{now_timespec, DirEntry, FileSystem, Inode, InodeKind, StatFs, MODE_SET, RAMFS_MAGIC}; use crate::errno::Errno; use alloc::{collections::BTreeMap, string::String, sync::Arc, vec::Vec}; use spin::Mutex;@@ -26,11 +28,29 @@ impl FileSystem for RamFs { fn root(&self) -> Arc<dyn Inode> { self.root.clone() }+ /// A ramfs can grow until memory runs out, so that's its "size" (Linux's+ /// ramfs says 0 for everything; numbers are friendlier to `df`).+ fn statfs(&self) -> StatFs {+ let (total, free) = crate::memory::frame_totals();+ StatFs {+ magic: RAMFS_MAGIC,+ block_size: 4096,+ blocks: total,+ free_blocks: free,+ avail_blocks: free,+ files: total,+ free_files: free,+ name_max: 255,+ read_only: false,+ }+ } } pub enum Node { File(Mutex<Vec<u8>>), Dir(Mutex<BTreeMap<String, Arc<RamInode>>>),+ /// A symlink's target (stage 17).+ Symlink(String), } /// What `stat` shows besides the contents (stage 16).@@ -40,6 +60,12 @@ struct Meta { mode: u32, /// atime, mtime, ctime as [seconds, nanoseconds]. times: [[i64; 2]; 3],+ /// Owner and group (stage 17: chown; new files get the creator's).+ uid: u32,+ gid: u32,+ /// Directory entries naming this node (hard links; directories count+ /// their subdirectories' ".." on top, see `nlink`).+ links: u32, } pub struct RamInode {@@ -50,7 +76,26 @@ pub struct RamInode { impl RamInode { fn new(node: Node) -> RamInode { let now = now_timespec();- RamInode { node, meta: Mutex::new(Meta { mode: 0, times: [now; 3] }) }+ RamInode { node, meta: Mutex::new(Meta { mode: 0, times: [now; 3], uid: 0, gid: 0, links: 1 }) }+ }++ fn entries(&self) -> Result<&Mutex<BTreeMap<String, Arc<RamInode>>>, Errno> {+ match &self.node {+ Node::Dir(entries) => Ok(entries),+ _ => Err(Errno::ENOTDIR),+ }+ }++ /// A directory entry pointing at `node` went away.+ fn unlinked(node: &RamInode) {+ let mut meta = node.meta.lock();+ meta.links = meta.links.saturating_sub(1);+ meta.times[2] = now_timespec();+ }++ /// The status-change time (ctime) is now.+ fn changed(&self) {+ self.meta.lock().times[2] = now_timespec(); } /// The contents changed: new modification and status-change time.@@ -67,6 +112,7 @@ impl Inode for RamInode { match &self.node { Node::File(_) => InodeKind::File, Node::Dir(_) => InodeKind::Directory,+ Node::Symlink(_) => InodeKind::Symlink, } } @@ -74,6 +120,14 @@ impl Inode for RamInode { match &self.node { Node::File(data) => data.lock().len(), Node::Dir(entries) => entries.lock().len(),+ Node::Symlink(target) => target.len(),+ }+ }++ fn readlink(&self) -> Result<String, Errno> {+ match &self.node {+ Node::Symlink(target) => Ok(target.clone()),+ _ => Err(Errno::EINVAL), } } @@ -109,9 +163,7 @@ impl Inode for RamInode { fn create(&self, name: &str, kind: InodeKind) -> Result<Arc<dyn Inode>, Errno> { let Node::Dir(entries) = &self.node else { return Err(Errno::ENOTDIR) };- if name.is_empty() || name.contains('/') || name.len() > 255 {- return Err(Errno::EINVAL);- }+ check_name(name)?; let mut entries = entries.lock(); if entries.contains_key(name) { return Err(Errno::EEXIST);@@ -119,16 +171,18 @@ impl Inode for RamInode { let node = Arc::new(RamInode::new(match kind { InodeKind::File => Node::File(Mutex::new(Vec::new())), InodeKind::Directory => Node::Dir(Mutex::new(BTreeMap::new())),- InodeKind::Symlink | InodeKind::Device => return Err(Errno::EOPNOTSUPP), // ramfs has neither+ InodeKind::Symlink | InodeKind::Device => return Err(Errno::EOPNOTSUPP), // symlinks: `symlink` })); entries.insert(String::from(name), node.clone());+ drop(entries);+ self.touch(); Ok(node) } fn list(&self) -> Result<Vec<DirEntry>, Errno> { let Node::Dir(entries) = &self.node else { return Err(Errno::ENOTDIR) }; let entries = entries.lock();- Ok(entries.iter().map(|(name, node)| DirEntry { name: name.clone(), kind: node.kind() }).collect())+ Ok(entries.iter().map(|(name, node)| DirEntry { name: name.clone(), kind: node.kind(), ino: node.ino_number() }).collect()) } fn truncate(&self) -> Result<(), Errno> {@@ -181,7 +235,128 @@ impl Inode for RamInode { return Err(Errno::ENOTEMPTY); } }- entries.remove(name);+ if let Some(node) = entries.remove(name) {+ Self::unlinked(&node);+ }+ drop(entries);+ self.touch();+ Ok(())+ }++ // --- stage 17 ----------------------------------------------------------++ fn ino(&self) -> Option<u64> {+ Some(self.ino_number())+ }++ fn nlink(&self) -> Option<u64> {+ let links = self.meta.lock().links as u64;+ Some(match &self.node {+ // "." plus each subdirectory's ".." (like a disk file system)+ Node::Dir(entries) => links + 1 + entries.lock().values().filter(|n| matches!(n.node, Node::Dir(_))).count() as u64,+ _ => links,+ })+ }++ fn owner(&self) -> (u32, u32) {+ let meta = self.meta.lock();+ (meta.uid, meta.gid)+ }++ fn set_owner(&self, uid: Option<u32>, gid: Option<u32>) -> Result<(), Errno> {+ let mut meta = self.meta.lock();+ if let Some(uid) = uid {+ meta.uid = uid;+ }+ if let Some(gid) = gid {+ meta.gid = gid;+ }+ meta.times[2] = now_timespec();+ Ok(())+ }++ fn symlink(&self, name: &str, target: &str) -> Result<Arc<dyn Inode>, Errno> {+ check_name(name)?;+ let mut entries = self.entries()?.lock();+ if entries.contains_key(name) {+ return Err(Errno::EEXIST);+ }+ let node = Arc::new(RamInode::new(Node::Symlink(String::from(target))));+ entries.insert(String::from(name), node.clone());+ drop(entries);+ self.touch();+ Ok(node)+ }++ fn link(&self, name: &str, target: &Arc<dyn Inode>) -> Result<(), Errno> {+ check_name(name)?;+ let node = super::downcast::<RamInode>(target).ok_or(Errno::EXDEV)?;+ let mut entries = self.entries()?.lock();+ if entries.contains_key(name) {+ return Err(Errno::EEXIST);+ }+ {+ let mut meta = node.meta.lock();+ meta.links += 1;+ meta.times[2] = now_timespec();+ }+ entries.insert(String::from(name), node);+ drop(entries);+ self.touch();+ Ok(())+ }++ /// Move the `Arc` from one map to the other. With two directories, the+ /// two locks are taken in address order, so two renames going opposite+ /// ways can't each hold one lock and wait for the other.+ fn rename_entry(&self, old: &str, new_dir: &Arc<dyn Inode>, new: &str) -> Result<(), Errno> {+ check_name(new)?;+ let target = super::downcast::<RamInode>(new_dir).ok_or(Errno::EXDEV)?;+ let same = core::ptr::eq(self, &*target);+ let (src, dst) = (self.entries()?, target.entries()?);+ let replaced;+ if same {+ let mut map = src.lock();+ let node = map.remove(old).ok_or(Errno::ENOENT)?;+ replaced = map.insert(String::from(new), node);+ } else {+ let self_first = (self as *const RamInode) < Arc::as_ptr(&target);+ let (mut a, mut b) = if self_first {+ let a = src.lock();+ (a, dst.lock())+ } else {+ let b = dst.lock();+ (src.lock(), b)+ };+ let node = a.remove(old).ok_or(Errno::ENOENT)?;+ node.changed();+ replaced = b.insert(String::from(new), node);+ }+ if let Some(gone) = replaced {+ Self::unlinked(&gone);+ }+ self.touch();+ target.touch(); Ok(()) }++ fn as_any(self: Arc<Self>) -> Option<Arc<dyn core::any::Any + Send + Sync>> {+ Some(self)+ }+}++impl RamInode {+ /// Inode number: the node's address (unique while it exists).+ fn ino_number(&self) -> u64 {+ self as *const RamInode as u64+ }+}++fn check_name(name: &str) -> Result<(), Errno> {+ match name {+ "" | "." | ".." => Err(Errno::EINVAL),+ _ if name.contains('/') => Err(Errno::EINVAL),+ _ if name.len() > 255 => Err(Errno::ENAMETOOLONG),+ _ => Ok(()),+ } }
@@ -123,6 +123,14 @@ pub extern "C" fn trap_handler(frame: &mut TrapFrame) { // Every CPU's APIC timer, ~100 times a second: time's up for the // running thread, let the next one have a go (see thread.rs). crate::apic::eoi(); // before switching: the next thread may be new+ // CPU-time accounting (stage 17): charge this tick to the+ // thread it interrupted, as user or system time.+ if let Some(task) = crate::thread::current_task() {+ let user = frame.cs & 3 == 3;+ task.cpu_ticks.fetch_add(1, core::sync::atomic::Ordering::Relaxed);+ let cpu = &task.process.cpu;+ (if user { &cpu.user } else { &cpu.system }).fetch_add(1, core::sync::atomic::Ordering::Relaxed);+ } crate::thread::timer_tick(); } 14 => page_fault(frame),
@@ -46,9 +46,11 @@ pub mod signal; // signals: kill, handlers, signal frames, default actions pub mod smp; // multi-core: start the other CPUs, per-CPU data pub mod stack; // kernel stacks with guard pages pub mod syscall; // syscall/sysret entry + system call implementations+pub mod syscall_ext; // stage 17's syscalls for coreutils/busybox: links, owners, statfs, ids, timers... pub mod task; // cooperative multitasking: async tasks + executor pub mod trap; // TrapFrame: saved user registers, the common return to ring 3 pub mod time; // microsecond clock (TSC calibrated against the PIT)+pub mod timer; // alarm, setitimer, POSIX timers: signals at a deadline (stage 17) pub mod thread; // threads + the multi-core scheduler (sleep/wake, wait queues) pub mod tty; // terminals: line discipline, termios, console/hvc0/ptys pub mod vga_buffer; // the console: VGA text mode, or fbcon on a GPU
@@ -147,7 +147,7 @@ fn shell_supervisor() { } loop { #[cfg(feature = "headless")]- let frames_before = rustbelt::memory::frames_in_use();+ let (frames_before, threads_before) = (rustbelt::memory::frames_in_use(), rustbelt::thread::thread_count()); #[allow(unused_mut)] let mut args = process::SpawnArgs::kernel("sh"); #[cfg(feature = "headless")]@@ -165,6 +165,19 @@ fn shell_supervisor() { { let ok = status == process::ExitStatus::Exited(0); serial_println!("[{}] shell self-test finished: {:?}", if ok { "ok" } else { "failed" }, status);+ // The shell is gone, but programs it left running in the+ // background (or orphans still exiting) may not be yet: wait+ // until every process and thread has finished -- up to 5+ // seconds -- before counting frames (stage 17).+ let deadline = rustbelt::time::now_us() + 5_000_000;+ while (process::count() > 0 || rustbelt::thread::thread_count() > threads_before)+ && rustbelt::time::now_us() < deadline+ {+ rustbelt::thread::sleep_ticks(1);+ }+ if process::count() > 0 {+ serial_println!("[memory] {} process(es) still running after 5 s", process::count());+ } // Every process the self-test ran is gone: all their memory // (page tables, user pages, kernel stacks) must be back. serial_println!(
@@ -142,6 +142,10 @@ pub fn usable_memory_bytes(memory_map: &MemoryMap) -> u64 { /// Hand the frame allocator over to the kernel (after the heap is set up), /// so processes can get page tables and pages later. pub fn init_frame_allocator(frame_allocator: BootInfoFrameAllocator) {+ // For sysinfo/statfs (stage 17): how much RAM there is, and how much of+ // it was gone (kernel heap, early page tables) before the hand-over.+ TOTAL_FRAMES.store(usable_memory_bytes(frame_allocator.memory_map) / 4096, Ordering::Relaxed);+ EARLY_FRAMES.store(frame_allocator.next as u64, Ordering::Relaxed); without_interrupts(|| *FRAME_ALLOCATOR.lock() = Some(frame_allocator)); init_kernel_stack_area(); // must happen before any process exists }@@ -195,6 +199,17 @@ pub fn free_frame(frame: PhysFrame) { }) } +/// Usable RAM in 4 KiB frames, and frames used before `init_frame_allocator`.+static TOTAL_FRAMES: AtomicU64 = AtomicU64::new(0);+static EARLY_FRAMES: AtomicU64 = AtomicU64::new(0);++/// (total, free) RAM in 4 KiB frames, for sysinfo and the ramfs's statfs.+pub fn frame_totals() -> (u64, u64) {+ let total = TOTAL_FRAMES.load(Ordering::Relaxed);+ let used = EARLY_FRAMES.load(Ordering::Relaxed) + frames_in_use() as u64;+ (total, total.saturating_sub(used))+}+ /// How many frames are in use right now (handed out and not freed). pub fn frames_in_use() -> usize { without_interrupts(|| BOOT_FRAMES_USED.load(Ordering::Relaxed) as usize - FREE_FRAMES.lock().len)
@@ -58,6 +58,20 @@ struct State { remote: Option<IpEndpoint>, /// SO_RCVTIMEO: how long recv/accept may block (None = forever). timeout_us: Option<u64>,+ /// Stage 17: shutdown(SHUT_RD) / shutdown(SHUT_WR) done.+ rd_shut: bool,+ wr_shut: bool,+ /// SO_ERROR already reported a refused connection (it's said once).+ error_taken: bool,+ /// Boolean options set with setsockopt (bit n = option n, e.g.+ /// SO_REUSEADDR = 2): remembered so getsockopt reads them back.+ flags: u32,+}++impl State {+ fn new(handle: Option<SocketHandle>, local_port: u16) -> State {+ State { handle, listeners: Vec::new(), local_port, remote: None, timeout_us: None, rd_shut: false, wr_shut: false, error_taken: false, flags: 0 }+ } } pub struct Socket {@@ -97,7 +111,7 @@ impl Socket { if with_net(|_| ()).is_none() { return Err(Errno::ENETDOWN); // net::init not run }- let mut state = State { handle: None, listeners: Vec::new(), local_port: 0, remote: None, timeout_us: None };+ let mut state = State::new(None, 0); match kind { Kind::Tcp => {} // created by connect/listen Kind::Udp => {@@ -262,8 +276,7 @@ impl Socket { st.listeners.remove(i); } let peer = n.sockets.get::<tcp::Socket>(handle).remote_endpoint().map(addr).unwrap_or(([0; 4], 0));- let state =- State { handle: Some(handle), listeners: Vec::new(), local_port: st.local_port, remote: None, timeout_us: None };+ let state = State::new(Some(handle), st.local_port); Some(Ok((Socket::with_state(Kind::Tcp, state), peer))) }) }@@ -277,6 +290,9 @@ impl Socket { self.bind(0)?; // first send picks a local port, like Linux } let sent = self.wait(None, |st, n| {+ if st.wr_shut {+ return Some(Err(Errno::EPIPE)); // shutdown(SHUT_WR)+ } let Some(h) = st.handle else { return Some(Err(Errno::ENOTCONN)) }; match self.kind { Kind::Tcp => {@@ -319,6 +335,9 @@ impl Socket { /// closed). A datagram bigger than `buf` is cut short (like Linux). pub fn recv(&self, buf: &mut [u8]) -> Result<(usize, Option<Addr>), Errno> { let result = self.wait(self.recv_timeout(), |st, n| {+ if st.rd_shut {+ return Some(Ok((0, None))); // shutdown(SHUT_RD): end of stream+ } let Some(h) = st.handle else { return Some(Err(if self.kind == Kind::Tcp { Errno::ENOTCONN } else { Errno::EINVAL })); };@@ -357,6 +376,89 @@ impl Socket { Ok(result) } + /// getsockname (stage 17): our address and port. Connected TCP knows+ /// its local IP; otherwise it's 0.0.0.0 ("any of ours"), like an+ /// unconnected socket on Linux.+ pub fn local(&self) -> Addr {+ self.locked(|st, n| match (self.kind, st.handle) {+ (Kind::Tcp, Some(h)) => n.sockets.get::<tcp::Socket>(h).local_endpoint().map(addr).unwrap_or(([0; 4], st.local_port)),+ _ => ([0; 4], st.local_port),+ })+ .unwrap_or(([0; 4], 0))+ }++ /// shutdown (stage 17): 0 = SHUT_RD (reads say end-of-file), 1 =+ /// SHUT_WR (TCP sends its FIN; writes get EPIPE), 2 = both.+ pub fn shutdown(&self, how: u64) -> Result<(), Errno> {+ if how > 2 {+ return Err(Errno::EINVAL);+ }+ let kind = self.kind;+ self.locked(|st, n| {+ let connected = match kind {+ Kind::Tcp => st.handle.is_some(),+ _ => st.remote.is_some(),+ };+ if !connected {+ return Err(Errno::ENOTCONN);+ }+ if how != 1 {+ st.rd_shut = true;+ }+ if how != 0 {+ st.wr_shut = true;+ if let (Kind::Tcp, Some(h)) = (kind, st.handle) {+ n.sockets.get_mut::<tcp::Socket>(h).close(); // FIN; we can still receive+ }+ }+ Ok(())+ })??;+ poll();+ Ok(())+ }++ /// SO_ERROR (stage 17): a pending error, reported once. That's how a+ /// program learns that a nonblocking connect failed.+ pub fn take_error(&self) -> i32 {+ let kind = self.kind;+ self.locked(|st, n| match (kind, st.handle) {+ (Kind::Tcp, Some(h)) if !st.error_taken && !st.wr_shut && n.sockets.get::<tcp::Socket>(h).state() == tcp::State::Closed => {+ st.error_taken = true;+ Errno::ECONNREFUSED as i32+ }+ _ => 0,+ })+ .unwrap_or(0)+ }++ /// Is it listening (SO_ACCEPTCONN)?+ pub fn listening(&self) -> bool {+ without_interrupts(|| !self.state.lock().listeners.is_empty())+ }++ /// The buffer sizes (SO_RCVBUF / SO_SNDBUF): fixed here.+ pub fn buffer_size(&self) -> usize {+ if self.kind == Kind::Tcp { TCP_BUFFER } else { DGRAM_BUFFER }+ }++ /// Remember / read back a boolean option (SO_REUSEADDR & co.). They+ /// change nothing: smoltcp lets a port be reused at once anyway, and+ /// there are no keepalives to send.+ pub fn set_flag(&self, opt: u32, on: bool) {+ without_interrupts(|| {+ let mut st = self.state.lock();+ if on { st.flags |= 1 << opt } else { st.flags &= !(1 << opt) }+ });+ }+ pub fn flag(&self, opt: u32) -> bool {+ without_interrupts(|| self.state.lock().flags & 1 << opt != 0)+ }++ /// The SO_RCVTIMEO set, if any (µs).+ pub fn recv_timeout_us(&self) -> Option<u64> {+ self.recv_timeout()+ }+ /// Local and remote address (for `accept` callers and tests). pub fn peer(&self) -> Option<Addr> { self.locked(|st, n| match (self.kind, st.handle) {
@@ -179,7 +179,10 @@ pub fn sys_poll_ms(fds_ptr: u64, nfds: u64, timeout_ms: i64) -> Result<u64, Errn sys_poll(fds_ptr, nfds, ms_deadline(timeout_ms)) } -pub fn sys_ppoll(fds_ptr: u64, nfds: u64, ts_ptr: u64, mask_ptr: u64) -> Result<u64, Errno> {+/// ppoll(fds, nfds, *timespec, *sigmask, sigsetsize). Stage 17: like+/// Linux, a mask whose size isn't 8 bytes (64 signals) is EINVAL.+pub fn sys_ppoll(fds_ptr: u64, nfds: u64, ts_ptr: u64, mask_ptr: u64, setsize: u64) -> Result<u64, Errno> {+ check_setsize(mask_ptr, setsize)?; let deadline = timespec_deadline(ts_ptr)?; with_sigmask(mask_ptr, || sys_poll(fds_ptr, nfds, deadline)) }@@ -264,7 +267,13 @@ pub fn sys_select_tv(nfds: u64, r: u64, w: u64, e: u64, tv_ptr: u64) -> Result<u /// pselect6(nfds, r, w, e, *timespec, *{sigmask ptr, size}) (270). pub fn sys_pselect6(nfds: u64, r: u64, w: u64, e: u64, ts_ptr: u64, mask_arg: u64) -> Result<u64, Errno> { let deadline = timespec_deadline(ts_ptr)?;- let mask_ptr = if mask_arg == 0 { 0 } else { u64::from_le_bytes(user_slice(mask_arg, 8)?.try_into().unwrap()) };+ let (mask_ptr, setsize) = if mask_arg == 0 {+ (0, 8)+ } else {+ let raw = user_slice(mask_arg, 16)?;+ (u64::from_le_bytes(raw[..8].try_into().unwrap()), u64::from_le_bytes(raw[8..].try_into().unwrap()))+ };+ check_setsize(mask_ptr, setsize)?; with_sigmask(mask_ptr, || sys_select(nfds, [r, w, e], deadline)) } @@ -396,7 +405,13 @@ pub fn sys_epoll_ctl(epfd: u64, op: u64, fd: u64, event_ptr: u64) -> Result<u64, /// epoll_wait(epfd, *events, max, timeout_ms) (232) and epoll_pwait(..., /// *sigmask, 8) (281).-pub fn sys_epoll_pwait(epfd: u64, events_ptr: u64, max: i64, timeout_ms: i64, mask_ptr: u64) -> Result<u64, Errno> {+/// The sigset size check of ppoll / pselect6 / epoll_pwait (stage 17).+fn check_setsize(mask_ptr: u64, setsize: u64) -> Result<(), Errno> {+ if mask_ptr != 0 && setsize != 8 { Err(Errno::EINVAL) } else { Ok(()) }+}++pub fn sys_epoll_pwait(epfd: u64, events_ptr: u64, max: i64, timeout_ms: i64, mask_ptr: u64, setsize: u64) -> Result<u64, Errno> {+ check_setsize(mask_ptr, setsize)?; if max <= 0 || max > 1024 { return Err(Errno::EINVAL); }
@@ -200,6 +200,54 @@ pub struct Task { /// robust_list_head` (0 = none). When the thread dies, the kernel walks /// that list and marks every futex lock it still held "owner died". pub robust_list: AtomicU64,+ /// CPU time it used (stage 17), in timer ticks (`CPU_TICK_US` each):+ /// for CLOCK_THREAD_CPUTIME_ID.+ pub cpu_ticks: AtomicU64,+}++/// One APIC timer tick (apic.rs programs ~100 a second), in microseconds:+/// the unit of CPU-time accounting (stage 17).+pub const CPU_TICK_US: u64 = 10_000;++/// CPU time, Linux style (stage 17): every timer tick is charged to+/// whatever thread it interrupted, as "user" time if the CPU was running+/// the program and "system" time if it was in the kernel for it. That's+/// what Linux does without fine-grained accounting too: a sample, not a+/// stopwatch. Children's times are added to `child_*` when they're reaped+/// with wait4, which is what times() and getrusage(RUSAGE_CHILDREN) show.+#[derive(Default)]+pub struct CpuTimes {+ pub user: AtomicU64,+ pub system: AtomicU64,+ pub child_user: AtomicU64,+ pub child_system: AtomicU64,+}++/// User and group ids (stage 17). Linux keeps three of each: real (who+/// you are), effective (what permission checks use) and saved (what a+/// setuid program may switch back to), plus "fs" ids and the+/// supplementary groups. We store them all and report them truthfully,+/// but nothing checks them yet: permissions aren't enforced.+#[derive(Clone, Copy, Debug, PartialEq, Eq)]+pub struct Creds {+ pub ruid: u32,+ pub euid: u32,+ pub suid: u32,+ pub fsuid: u32,+ pub rgid: u32,+ pub egid: u32,+ pub sgid: u32,+ pub fsgid: u32,+ pub groups: [u32; NGROUPS_MAX],+ pub ngroups: usize,+}++/// How many supplementary groups a process can have (Linux allows 65536;+/// 32 is plenty here).+pub const NGROUPS_MAX: usize = 32;++impl Creds {+ pub const ROOT: Creds = Creds { ruid: 0, euid: 0, suid: 0, fsuid: 0, rgid: 0, egid: 0, sgid: 0, fsgid: 0, groups: [0; NGROUPS_MAX], ngroups: 0 }; } /// A thread name as prctl(PR_GET_NAME) returns it: at most 15 bytes, then NULs.@@ -286,6 +334,13 @@ pub struct Process { /// descendants' orphans and promises to reap them (see `adopter`). /// Not inherited. pub child_subreaper: AtomicBool,+ /// CPU time used (stage 17), see `CpuTimes`.+ pub cpu: CpuTimes,+ /// User and group ids (stage 17): inherited by fork, kept by exec.+ pub creds: Mutex<Creds>,+ /// The nice value (stage 17), -20..19: stored for get/setpriority,+ /// inherited; the scheduler doesn't use it (every thread is equal).+ pub nice: core::sync::atomic::AtomicI32, } // --- Resource limits (stage 16) ------------------------------------------@@ -439,6 +494,9 @@ pub fn spawn_image(path: &str, elf_image: &[u8], spawn: SpawnArgs) -> Result<Arc dumpable: AtomicU32::new(1), no_new_privs: AtomicBool::new(false), child_subreaper: AtomicBool::new(false),+ cpu: CpuTimes::default(),+ creds: Mutex::new(Creds::ROOT),+ nice: core::sync::atomic::AtomicI32::new(0), }; serial_println!("[process] started pid {} ({}), entry {:#x}", pid, name, start.rip); Ok(start_process(process, start, 0, 0, crate::fpu::initial(), signal::AltStack::DISABLED, comm_from(name)))@@ -469,6 +527,7 @@ fn start_task(process: &Arc<Process>, tid: u64, start: TrapFrame, fs_base: u64, fpu: Mutex::new(fpu), comm: Mutex::new(comm), robust_list: AtomicU64::new(0),+ cpu_ticks: AtomicU64::new(0), }); without_interrupts(|| process.tasks.lock().push(task.clone())); let l4 = process.mm.lock().l4;@@ -916,6 +975,7 @@ fn process_exit(process: Arc<Process>, status: ExitStatus) { let files = core::mem::take(&mut process.fds.lock().files); drop(files); fs::lock::process_exited(process.pid); // its fcntl record locks+ crate::timer::process_exited(process.pid); // its alarm and POSIX timers (stage 17) // prctl(PR_SET_PDEATHSIG): children that asked to hear of our death. for child in all().into_iter().filter(|c| c.parent() == process.pid && c.status().is_none()) { let sig = child.pdeathsig.load(Ordering::Relaxed);@@ -1106,6 +1166,13 @@ pub fn wait4(me: &Process, pid: i64, options: u64) -> Result<(u64, i32), Errno> Found::Event(child, status, exited) => { if exited { reap(&child);+ // Its CPU time (and its reaped children's) now counts as+ // our children's time (times(), getrusage).+ let c = &child.cpu;+ let user = c.user.load(Ordering::Relaxed) + c.child_user.load(Ordering::Relaxed);+ let system = c.system.load(Ordering::Relaxed) + c.child_system.load(Ordering::Relaxed);+ me.cpu.child_user.fetch_add(user, Ordering::Relaxed);+ me.cpu.child_system.fetch_add(system, Ordering::Relaxed); } Ok((child.pid, status)) }@@ -1165,6 +1232,9 @@ pub fn fork(parent: &Arc<Process>, frame: &TrapFrame, stack: u64, vfork: bool) - dumpable: AtomicU32::new(parent.dumpable.load(Ordering::Relaxed)), no_new_privs: AtomicBool::new(parent.no_new_privs.load(Ordering::Relaxed)), child_subreaper: AtomicBool::new(false),+ cpu: CpuTimes::default(),+ creds: Mutex::new(*parent.creds.lock()),+ nice: core::sync::atomic::AtomicI32::new(parent.nice.load(Ordering::Relaxed)), }; serial_println!("[process] pid {} forked pid {}", parent.pid, pid); // The child's FPU/SSE registers start as a copy of ours (we're running,@@ -1256,6 +1326,7 @@ pub fn execve(p: &Arc<Process>, frame: &mut TrapFrame, path: &str, argv: &[Vec<u *frame = start; *p.name.lock() = String::from(name); *p.exe.lock() = String::from(path);+ crate::timer::exec(p.pid); // POSIX timers don't survive exec (the alarm does) if let Some(me) = thread::current_task() { me.fs_base.store(0, Ordering::Relaxed); // no TLS until the new libc sets one crate::fpu::reset_live(); // clean FPU/SSE/AVX registers for the new program
@@ -533,10 +533,18 @@ fn restart(frame: &mut TrapFrame) { /// (by rewriting `frame`), applies default actions (which may end the /// process right here), and sorts out interrupted syscalls. pub fn deliver(frame: &mut TrapFrame) {+ deliver_outcome(frame);+}++/// `deliver`, also saying what became of an interrupted syscall (for the+/// `--trace` line, stage 17): `Some(true)` = it will run again,+/// `Some(false)` = the program sees EINTR, `None` = nothing was interrupted.+pub fn deliver_outcome(frame: &mut TrapFrame) -> Option<bool> { if !frame.from_user() {- return;+ return None; }- let Some(t) = thread::current_task() else { return };+ let Some(t) = thread::current_task() else { return None };+ let mut outcome = None; let p = t.process.clone(); // Writing the signal frame may fault user pages in: interrupts on. x86_64::instructions::interrupts::enable();@@ -576,8 +584,14 @@ pub fn deliver(frame: &mut TrapFrame) { // EINTR, or restart it after the handler (SA_RESTART). if frame.is_syscall() { match restart_code(frame.rax) {- Some(Errno::ERESTARTSYS) if action.flags & SA_RESTART != 0 => restart(frame),- Some(_) => frame.rax = -(Errno::EINTR as i64) as u64,+ Some(Errno::ERESTARTSYS) if action.flags & SA_RESTART != 0 => {+ restart(frame);+ outcome = Some(true);+ }+ Some(_) => {+ frame.rax = -(Errno::EINTR as i64) as u64;+ outcome = Some(false);+ } None => {} } frame.vector = 0; // dealt with: not a syscall return any more@@ -600,7 +614,7 @@ pub fn deliver(frame: &mut TrapFrame) { s.actions[sig as usize - 1] = SigAction::default(); } });- return; // one handler at a time; the next on its return+ return outcome; // one handler at a time; the next on its return } } }@@ -609,7 +623,9 @@ pub fn deliver(frame: &mut TrapFrame) { restore_temporary_mask(); if frame.is_syscall() && restart_code(frame.rax).is_some() { restart(frame);+ outcome = Some(true); }+ outcome } /// ppoll / pselect6 / epoll_pwait / (sigsuspend-like) waits: block exactly
@@ -58,7 +58,7 @@ //! | 45 | recvfrom | fd, buf, len, flags, *sockaddr_in or 0, *addrlen | blocks (SO_RCVTIMEO -> EAGAIN); 0 = TCP peer closed. The address is cut to *addrlen | //! | 49 | bind | fd, *sockaddr_in, len | sets the local port (address ignored: we have one) | //! | 50 | listen | fd, backlog | TCP; backlog 1..4 |-//! | 54 | setsockopt | fd, level, name, *value, len | only SOL_SOCKET (1) / SO_RCVTIMEO (20) with a `struct timeval`; other options: ENOPROTOOPT |+//! | 54 | setsockopt | fd, level, name, *value, len | stage 17 (src/syscall_ext.rs): SO_RCVTIMEO is real; SO_REUSEADDR & co. stored; others ENOPROTOOPT | //! | 56 | clone | flags, stack, *ptid, *ctid, tls | fork-like (SIGCHLD [+ CLONE_VFORK]) or a thread: CLONE_VM|FS|FILES|SIGHAND|THREAD|SYSVSEM [+ SETTLS, PARENT_SETTID, CHILD_CLEARTID] | //! | 57 | fork | | copy-on-write; parent gets the pid, child 0 | //! | 58 | vfork | | fork + the parent waits until the child execs or exits |@@ -81,7 +81,7 @@ //! | 90 | chmod | path, mode | ramfs and ext2 keep it; FAT: EPERM; btrfs: EROFS. fchmod 91 = on an fd | //! | 95 | umask | mask | returns the old one; open(O_CREAT)/mkdir take these bits away from `mode` | //! | 97 | getrlimit | resource, *rlimit | {soft, hard}; setrlimit 160 too. RLIMIT_NOFILE is enforced (<= 64) |-//! | 102 | getuid | | always 0 (root), like getgid 104, geteuid 107, getegid 108 |+//! | 102 | getuid | | stage 17 (src/syscall_ext.rs): real per-process ids, root (0) by default | //! | 109 | setpgid | pid, pgid | 0 = myself / a new group | //! | 110 | getppid | | 0 if started by the kernel or orphaned | //! | 111 | getpgrp | | |@@ -102,7 +102,7 @@ //! | 213 | epoll_create | size | = epoll_create1(0) | //! | 217 | getdents64 | fd, buf, len | Linux `struct linux_dirent64` records | //! | 218 | set_tid_address | *u32 | cleared + futex-woken when this thread exits; returns the tid |-//! | 228 | clock_gettime | clock, *timespec | REALTIME 0 (CMOS clock at boot + uptime), MONOTONIC 1 (since boot) |+//! | 228 | clock_gettime | clock, *timespec | REALTIME 0 (CMOS clock at boot + uptime), MONOTONIC 1 (since boot); stage 17: CPU time 2/3 | //! | 230 | clock_nanosleep | clock, flags, *timespec, *remaining | REALTIME, MONOTONIC or BOOTTIME 7; TIMER_ABSTIME 1 = sleep until that time | //! | 231 | exit_group | code | ends every thread of the process | //! | 232 | epoll_wait | epfd, *epoll_event, max, timeout_ms | level-triggered; EPOLLONESHOT supported |@@ -112,7 +112,7 @@ //! | 258 | mkdirat | dirfd, path, mode | | //! | 262 | newfstatat | dirfd, path, *stat, flags | AT_SYMLINK_NOFOLLOW 0x100, AT_EMPTY_PATH 0x1000 (= fstat(dirfd)) | //! | 263 | unlinkat | dirfd, path, flags | AT_REMOVEDIR 0x200 = rmdir |-//! | 264 | renameat | olddirfd, old, newdirfd, new | renameat2 316 too: flags 0 only (else EINVAL) |+//! | 264 | renameat | olddirfd, old, newdirfd, new | renameat2 316 too: flags 0 or RENAME_NOREPLACE (else EINVAL) | //! | 267 | readlinkat | dirfd, path, buf, len | | //! | 268 | fchmodat | dirfd, path, mode | fchmodat2 452: + flags (AT_SYMLINK_NOFOLLOW on a symlink: EOPNOTSUPP) | //! | 269 | faccessat | dirfd, path, mode | faccessat2 439 too (its flags are ignored) |@@ -129,11 +129,16 @@ //! | 318 | getrandom | buf, len, flags | random bytes (virtio-rng); up to 64 KiB per call; flags ignored | //! | 500 | spawn | path, argv, argv_len, *SpawnOptions | ours: new process running a program (fork+exec in one step, like posix_spawn) | //! | 501 | mounts | buf, len | ours: "path fstype\n" per mount (Linux: read /proc/mounts) |-//! | 502 | sysinfo | *SysInfo | ours: CPUs, threads, memory, per-CPU busy time (Linux: /proc/stat etc.) |+//! | 502 | rb_sysinfo | *SysInfo | ours: CPUs, threads, memory, per-CPU busy time (Linux: /proc/stat etc.) | //! | 503 | resolve | name, name_len, *[[u8; 4]], max | ours: DNS lookup (A records), returns the count. Linux does DNS in libc | //! | 504 | netinfo | *NetInfo | ours: MAC, IP, gateway, DNS, packet counters (Linux: netlink/ioctls) | //! | 505 | trace_me | on | ours: print my syscalls from now on (survives execve; `run --trace`) | //!+//! Stage 17's calls (link/symlink, chown, statfs/statx, sysinfo 99, times,+//! getrusage, clock_getres, socket names/options, timers, ids, priority,+//! host names, flock, preadv/pwritev, sendfile) live in `syscall_ext.rs`,+//! which has its own table; unknown numbers fall through to it.+//! //! spawn's `argv` is all arguments separated by NUL bytes ("ls\0-l\0/bin"); //! empty means just [program name]. `SpawnOptions` (or a null pointer for //! the defaults) picks the child's stdin/stdout/stderr from the caller's fds@@ -175,7 +180,7 @@ //! OS use, and it's faster. use crate::errno::Errno;-use crate::fs::file::{File, InodeFile, LinuxStat, Stat};+use crate::fs::file::{File, InodeFile, LinuxStat}; use crate::fs::{self, InodeKind}; use crate::trap::TrapFrame; use crate::{memory, process, signal, thread};@@ -217,7 +222,7 @@ pub const SYS_GETRANDOM: u64 = 318; pub const SYS_CLOCK_GETTIME: u64 = 228; pub const SYS_SPAWN: u64 = 500; pub const SYS_MOUNTS: u64 = 501;-pub const SYS_SYSINFO: u64 = 502;+pub const SYS_RB_SYSINFO: u64 = 502; pub const SYS_RESOLVE: u64 = 503; pub const SYS_NETINFO: u64 = 504; pub const SYS_MMAP: u64 = 9;@@ -516,7 +521,7 @@ pub fn name(nr: u64) -> &'static str { SYS_GETRANDOM => "getrandom", SYS_SPAWN => "spawn", SYS_MOUNTS => "mounts",- SYS_SYSINFO => "sysinfo",+ SYS_RB_SYSINFO => "rb_sysinfo", SYS_SOCKET => "socket", SYS_CONNECT => "connect", SYS_ACCEPT => "accept",@@ -529,7 +534,7 @@ pub fn name(nr: u64) -> &'static str { SYS_CLOCK_GETTIME => "clock_gettime", SYS_RESOLVE => "resolve", SYS_NETINFO => "netinfo",- _ => "?",+ _ => crate::syscall_ext::name(nr).unwrap_or("?"), } } @@ -710,7 +715,7 @@ extern "C" fn syscall_dispatch(frame: &mut TrapFrame) { SYS_GETDENTS64 => sys_getdents64(a1, a2, a3), SYS_SPAWN => sys_spawn(a1, a2, a3, a4), SYS_MOUNTS => sys_mounts(a1, a2),- SYS_SYSINFO => sys_sysinfo(a1),+ SYS_RB_SYSINFO => sys_sysinfo(a1), SYS_SOCKET => sys_socket(a1, a2, a3), SYS_CONNECT => with_socket(a1, |s| s.connect(read_sockaddr(a2)?)).map(|_| 0), SYS_ACCEPT => sys_accept(a1, a2, a3, 0),@@ -719,11 +724,8 @@ extern "C" fn syscall_dispatch(frame: &mut TrapFrame) { SYS_RECVFROM => sys_recvfrom(a1, a2, a3, a5, frame.r9), SYS_BIND => with_socket(a1, |s| s.bind(read_sockaddr(a2)?.1)).map(|_| 0), SYS_LISTEN => with_socket(a1, |s| s.listen(a2 as usize)).map(|_| 0),- SYS_SETSOCKOPT => sys_setsockopt(a1, a2, a3, a4, a5), SYS_CLOCK_GETTIME => sys_clock_gettime(a1, a2), SYS_CLOCK_NANOSLEEP => sys_clock_nanosleep(a1, a2, a3, a4),- // One user, root: musl asks at startup (and `id`, `ls -l` ask).- SYS_GETUID | SYS_GETGID | SYS_GETEUID | SYS_GETEGID => Ok(0), SYS_RESOLVE => sys_resolve(a1, a2, a3, a4), SYS_NETINFO => copy_to_user(a1, &crate::net::info()).map(|_| 0), SYS_GETRANDOM => sys_getrandom(a1, a2),@@ -734,14 +736,14 @@ extern "C" fn syscall_dispatch(frame: &mut TrapFrame) { SYS_READV => sys_readv(a1, a2, a3), SYS_WRITEV => sys_writev(a1, a2, a3), SYS_POLL => crate::poll::sys_poll_ms(a1, a2, a3 as i32 as i64),- SYS_PPOLL => crate::poll::sys_ppoll(a1, a2, a3, a4),+ SYS_PPOLL => crate::poll::sys_ppoll(a1, a2, a3, a4, a5), SYS_SELECT => crate::poll::sys_select_tv(a1, a2, a3, a4, a5), SYS_PSELECT6 => crate::poll::sys_pselect6(a1, a2, a3, a4, a5, frame.r9), SYS_EPOLL_CREATE => if (a1 as i32) <= 0 { Err(Errno::EINVAL) } else { crate::poll::sys_epoll_create1(0) }, SYS_EPOLL_CREATE1 => crate::poll::sys_epoll_create1(a1), SYS_EPOLL_CTL => crate::poll::sys_epoll_ctl(a1, a2, a3, a4),- SYS_EPOLL_WAIT => crate::poll::sys_epoll_pwait(a1, a2, a3 as i32 as i64, a4 as i32 as i64, 0),- SYS_EPOLL_PWAIT => crate::poll::sys_epoll_pwait(a1, a2, a3 as i32 as i64, a4 as i32 as i64, a5),+ SYS_EPOLL_WAIT => crate::poll::sys_epoll_pwait(a1, a2, a3 as i32 as i64, a4 as i32 as i64, 0, 8),+ SYS_EPOLL_PWAIT => crate::poll::sys_epoll_pwait(a1, a2, a3 as i32 as i64, a4 as i32 as i64, a5, frame.r9), SYS_FUTEX => current().and_then(|p| crate::futex::sys_futex(&p, a1, a2, a3, a4, a5, frame.r9)), SYS_GETTID => task().map(|t| t.tid()), SYS_SET_TID_ADDRESS => task().map(|t| {@@ -778,7 +780,7 @@ extern "C" fn syscall_dispatch(frame: &mut TrapFrame) { SYS_RT_TGSIGQUEUEINFO => sys_rt_sigqueueinfo(a1, Some(a2), a3, a4), SYS_SCHED_GETAFFINITY => sys_sched_affinity(a1, a2, a3, false), SYS_SCHED_SETAFFINITY => sys_sched_affinity(a1, a2, a3, true),- _ => Err(Errno::ENOSYS),+ _ => crate::syscall_ext::dispatch(nr, [a1, a2, a3, a4, a5, frame.r9]).unwrap_or(Err(Errno::ENOSYS)), }; let ret = match result { Ok(value) => value as i64,@@ -789,16 +791,27 @@ extern "C" fn syscall_dispatch(frame: &mut TrapFrame) { if !(nr == SYS_EXECVE && ret == 0) { frame.rax = ret as u64; }- if trace {+ let interrupted = matches!(Errno::from_ret(ret), Some(Errno::ERESTARTSYS | Errno::ERESTARTNOHAND)) && ret < 0;+ if trace && !interrupted { let pid = current().map(|p| p.pid).unwrap_or(0); match exec_line { Some(line) if ret == 0 => crate::kprintln!("[trace {}] {}", pid, line), _ => crate::kprintln!("[trace {}] {}", pid, trace_line(nr, [a1, a2, a3, a4], ret)), } }+ // An interrupted call (-512/-514) is traced *after* signal delivery,+ // with what the program really gets (stage 17): EINTR, or "restarted".+ let line = (trace && interrupted).then(|| trace_line(nr, [a1, a2, a3, a4], -(Errno::EINTR as i64))); // A signal may have arrived meanwhile (or the call was interrupted by // one): act on it before returning to the program.- signal::deliver(frame);+ let outcome = signal::deliver_outcome(frame);+ if let Some(line) = line {+ let pid = current().map(|p| p.pid).unwrap_or(0);+ match outcome {+ Some(true) => crate::kprintln!("[trace {}] {} = ? (interrupted, restarted)", pid, line.rsplit_once(" = ").map(|l| l.0).unwrap_or(&line)),+ _ => crate::kprintln!("[trace {}] {}", pid, line),+ }+ } } /// One syscall in strace style: `open("/mnt/disk0/readme.txt", 0) = 3`.@@ -869,12 +882,12 @@ pub fn trace_name(nr: u64) -> String { } } -fn current() -> Result<Arc<process::Process>, Errno> {+pub(crate) fn current() -> Result<Arc<process::Process>, Errno> { thread::current_process().ok_or(Errno::EINVAL) } /// The calling thread.-fn task() -> Result<Arc<process::Task>, Errno> {+pub(crate) fn task() -> Result<Arc<process::Task>, Errno> { thread::current_task().ok_or(Errno::EINVAL) } @@ -946,7 +959,7 @@ fn sys_write(fd: u64, buf: u64, len: u64) -> Result<u64, Errno> { // O_DIRECTORY, and so on. /// The directory a relative path in an *at() call starts from.-fn at_base(dirfd: u64) -> Result<String, Errno> {+pub(crate) fn at_base(dirfd: u64) -> Result<String, Errno> { let p = current()?; if dirfd as i32 as i64 == AT_FDCWD { return Ok(p.cwd.lock().clone());@@ -959,7 +972,7 @@ fn at_base(dirfd: u64) -> Result<String, Errno> { } /// A path string (already copied in) made absolute, relative to `dirfd`.-fn path_at(dirfd: u64, raw: &[u8]) -> Result<String, Errno> {+pub(crate) fn path_at(dirfd: u64, raw: &[u8]) -> Result<String, Errno> { // Our file systems use UTF-8 names (Linux allows any bytes but '/' and NUL). let path = core::str::from_utf8(raw).map_err(|_| Errno::EINVAL)?; if path.is_empty() {@@ -972,12 +985,12 @@ fn path_at(dirfd: u64, raw: &[u8]) -> Result<String, Errno> { } /// Copy in a path argument and make it absolute, relative to `dirfd`.-fn user_path_at(dirfd: u64, ptr: u64) -> Result<String, Errno> {+pub(crate) fn user_path_at(dirfd: u64, ptr: u64) -> Result<String, Errno> { path_at(dirfd, &user_cstr(ptr, PATH_MAX - 1)?) } /// Copy in a path argument, relative to the cwd.-fn user_path(ptr: u64) -> Result<String, Errno> {+pub(crate) fn user_path(ptr: u64) -> Result<String, Errno> { user_path_at(AT_FDCWD as u64, ptr) } @@ -1027,27 +1040,39 @@ fn sys_openat(dirfd: u64, path_ptr: u64, flags: u64, mode: u64) -> Result<u64, E /// a symlink itself, not what it points to) and, with AT_EMPTY_PATH and /// path "", fstat(dirfd). Writes Linux's `struct stat`. fn sys_newfstatat(dirfd: u64, path_ptr: u64, stat_ptr: u64, flags: u64) -> Result<u64, Errno> {+ copy_to_user(stat_ptr, &stat_at(dirfd, path_ptr, flags)?)?;+ Ok(0)+}++/// The `struct stat` newfstatat (and statx, stage 17) report.+pub(crate) fn stat_at(dirfd: u64, path_ptr: u64, flags: u64) -> Result<LinuxStat, Errno> { let raw = user_cstr(path_ptr, PATH_MAX - 1)?; if raw.is_empty() && flags & AT_EMPTY_PATH != 0 && dirfd as i32 as i64 != AT_FDCWD {- return sys_fstat(dirfd, stat_ptr);+ return fd_stat(dirfd); } let path = if raw.is_empty() && flags & AT_EMPTY_PATH != 0 { at_base(dirfd)? } else { path_at(dirfd, &raw)? }; let inode = if flags & AT_SYMLINK_NOFOLLOW != 0 { fs::lookup_nofollow(&path)? } else { fs::lookup(&path)? };- let stat = LinuxStat::new(&Stat::of(&*inode), fs::file::ino_hash(path.as_bytes())).with_times(inode.times());- copy_to_user(stat_ptr, &stat)?;- Ok(0)+ Ok(fs::file::linux_stat(&*inode, &path)) } fn sys_fstat(fd: u64, stat_ptr: u64) -> Result<u64, Errno> {+ copy_to_user(stat_ptr, &fd_stat(fd)?)?;+ Ok(0)+}++/// fstat's `struct stat`: files opened by path report what stat() of the+/// path does. Others (pipes, sockets, consoles): the open file's address+/// as the inode number, unique while it's open.+pub(crate) fn fd_stat(fd: u64) -> Result<LinuxStat, Errno> { let file = current()?.file(fd)?;- // Files opened by path: the same inode number stat() gives. Others- // (pipes, sockets, consoles): the open file's address, unique while open.+ if let (Some(inode), Some(path)) = (file.vfs_inode(), file.path()) {+ return Ok(fs::file::linux_stat(&*inode, &path));+ } let ino = match file.path() { Some(path) => fs::file::ino_hash(path.as_bytes()), None => Arc::as_ptr(&file) as *const u8 as u64, };- copy_to_user(stat_ptr, &LinuxStat::new(&file.stat()?, ino).with_times(file.times()))?;- Ok(0)+ Ok(LinuxStat::new(&file.stat()?, ino).with_times(file.times())) } /// access / faccessat(dirfd, path, mode): "does it exist?" There are no@@ -1068,9 +1093,10 @@ fn sys_mkdirat(dirfd: u64, path_ptr: u64, mode: u64) -> Result<u64, Errno> { /// umask bits, where the filesystem keeps modes (ramfs, ext2). Elsewhere /// (FAT, read-only btrfs) it's quietly skipped -- the file exists, which /// is what the caller asked for.-fn apply_create_mode(inode: &dyn fs::Inode, mode: u64) {+pub(crate) fn apply_create_mode(inode: &dyn fs::Inode, mode: u64) { let umask = current().map(|p| p.umask.load(Ordering::Relaxed)).unwrap_or(0o022); let _ = inode.set_mode(mode as u32 & 0o7777 & !umask);+ crate::syscall_ext::set_creator(inode); } /// unlink / rmdir / unlinkat(dirfd, path, flags): AT_REMOVEDIR = rmdir.@@ -1082,15 +1108,18 @@ fn sys_unlinkat(dirfd: u64, path_ptr: u64, flags: u64) -> Result<u64, Errno> { Ok(0) } -/// rename / renameat / renameat2 (no flags: RENAME_NOREPLACE & co. are-/// EINVAL). See `fs::rename` for what we can and can't move.+/// rename / renameat / renameat2. Stage 17: RENAME_NOREPLACE (1) works+/// (EEXIST if the new name is taken); RENAME_EXCHANGE (2) and+/// RENAME_WHITEOUT (4) are EINVAL, as on file systems that lack them.+/// See `fs::rename` for what we can and can't move. fn sys_renameat(olddir: u64, old_ptr: u64, newdir: u64, new_ptr: u64, flags: u64) -> Result<u64, Errno> {+ const RENAME_NOREPLACE: u64 = 1; let old = user_path_at(olddir, old_ptr)?; let new = user_path_at(newdir, new_ptr)?;- if flags != 0 {+ if flags & !RENAME_NOREPLACE != 0 { return Err(Errno::EINVAL); }- fs::rename(&old, &new)?;+ fs::rename(&old, &new, flags & RENAME_NOREPLACE != 0)?; Ok(0) } @@ -1307,13 +1336,14 @@ fn sys_getrandom(buf: u64, len: u64) -> Result<u64, Errno> { /// Linux's `struct utsname`: six NUL-terminated strings of 65 bytes each. fn sys_uname(buf: u64) -> Result<u64, Errno> {+ let (host, domain) = (crate::syscall_ext::hostname(), crate::syscall_ext::domainname()); let fields = [ "Rustbelt", // sysname (`uname -s`)- "qemu", // nodename (hostname)+ host.as_str(), // nodename (hostname; sethostname changes it) env!("CARGO_PKG_VERSION"), // release (`uname -r`)- "stage 16: libc batch 1", // version+ "stage 17: coreutils batch 2", // version "x86_64", // machine- "(none)", // domainname+ domain.as_str(), // domainname ]; let out = user_slice_mut(buf, 6 * 65)?; out.fill(0);@@ -1389,7 +1419,7 @@ pub struct SockAddrIn { pub zero: [u8; 8], } -fn read_sockaddr(ptr: u64) -> Result<crate::net::socket::Addr, Errno> {+pub(crate) fn read_sockaddr(ptr: u64) -> Result<crate::net::socket::Addr, Errno> { let bytes = user_slice(ptr, 16)?; let family = u16::from_ne_bytes([bytes[0], bytes[1]]); if family as u64 != AF_INET {@@ -1401,7 +1431,7 @@ fn read_sockaddr(ptr: u64) -> Result<crate::net::socket::Addr, Errno> { /// Write a peer's address like Linux: `*len_ptr` (a u32) says how much /// room there is; we write at most that much and set it to the real size /// (16). No `len_ptr` (only our own older programs do that): all 16 bytes.-fn write_sockaddr(ptr: u64, len_ptr: u64, (addr, port): crate::net::socket::Addr) -> Result<(), Errno> {+pub(crate) fn write_sockaddr(ptr: u64, len_ptr: u64, (addr, port): crate::net::socket::Addr) -> Result<(), Errno> { if ptr == 0 { return Ok(()); // caller doesn't want it }@@ -1421,7 +1451,7 @@ fn write_sockaddr(ptr: u64, len_ptr: u64, (addr, port): crate::net::socket::Addr /// Run `f` on the socket behind `fd` (ENOTSOCK if it's another kind of /// file). We hold our own Arc to it, so the fd may be closed meanwhile.-fn with_socket<R>(fd: u64, f: impl FnOnce(&crate::net::socket::Socket) -> Result<R, Errno>) -> Result<R, Errno> {+pub(crate) fn with_socket<R>(fd: u64, f: impl FnOnce(&crate::net::socket::Socket) -> Result<R, Errno>) -> Result<R, Errno> { let file = current()?.file(fd)?; f(file.as_socket().ok_or(Errno::ENOTSOCK)?) }@@ -1465,23 +1495,6 @@ fn sys_recvfrom(fd: u64, buf: u64, len: u64, addr_ptr: u64, len_ptr: u64) -> Res Ok(n as u64) } -/// Only SO_RCVTIMEO: `struct timeval {i64 seconds, i64 microseconds}`;-/// zero means "block forever", like Linux.-fn sys_setsockopt(fd: u64, level: u64, name: u64, value: u64, len: u64) -> Result<u64, Errno> {- if level != SOL_SOCKET || name != SO_RCVTIMEO {- return Err(Errno::ENOPROTOOPT); // what Linux says for an unknown option- }- if len < 16 {- return Err(Errno::EINVAL);- }- let tv = user_slice(value, 16)?;- let secs = u64::from_ne_bytes(tv[..8].try_into().unwrap());- let usecs = u64::from_ne_bytes(tv[8..16].try_into().unwrap());- let total = secs.saturating_mul(1_000_000).saturating_add(usecs);- with_socket(fd, |s| Ok(s.set_recv_timeout(if total == 0 { None } else { Some(total) })))?;- Ok(0)-}- /// clock_gettime(clock, *timespec): CLOCK_REALTIME (0, wall-clock time /// since 1970, from the CMOS clock at boot) or the MONOTONIC family (1, /// 4 RAW, 6 COARSE, 7 BOOTTIME: time since boot). CPU-time clocks: EINVAL.@@ -1489,6 +1502,8 @@ fn sys_clock_gettime(clock: u64, ts_ptr: u64) -> Result<u64, Errno> { let us = match clock { 0 | 5 => crate::time::realtime_us(), // REALTIME, REALTIME_COARSE 1 | 4 | 6 | 7 => crate::time::now_us(),+ // Stage 17: CPU time used so far (process / this thread).+ 2 | 3 => crate::syscall_ext::cpu_clock_us(clock)?, _ => return Err(Errno::EINVAL), }; copy_to_user(ts_ptr, &[us / 1_000_000, (us % 1_000_000) * 1000])?;@@ -1711,7 +1726,7 @@ fn sys_pwrite(fd: u64, buf: u64, len: u64, offset: u64) -> Result<u64, Errno> { } /// The `struct iovec {void *base; size_t len;}` array of readv/writev.-fn read_iovecs(ptr: u64, count: u64) -> Result<Vec<(u64, u64)>, Errno> {+pub(crate) fn read_iovecs(ptr: u64, count: u64) -> Result<Vec<(u64, u64)>, Errno> { if count > 1024 { return Err(Errno::EINVAL); }
@@ -0,0 +1,1134 @@+//! Stage 17: the "coreutils / busybox" batch of Linux system calls.+//!+//! syscall.rs grew big, so the calls added in stage 17 live here. The+//! dispatcher there tries its own table first and then asks `dispatch`+//! below; `name` gives `--trace` their names.+//!+//! | nr | call | what it does here |+//! |-----|----------------------|----------------------------------------------------|+//! | 86 | link | a second name for a file (ramfs, ext2) |+//! | 265 | linkat | same, AT_SYMLINK_FOLLOW / AT_EMPTY_PATH |+//! | 88 | symlink | a symbolic link (ramfs, ext2) |+//! | 266 | symlinkat | same, relative to a directory fd |+//! | 92 | chown | change owner/group (stored, not enforced) |+//! | 93 | fchown | same, by fd |+//! | 94 | lchown | same, the symlink itself |+//! | 260 | fchownat | same, relative to a directory fd |+//! | 137 | statfs | sizes and free space of a file system |+//! | 138 | fstatfs | same, by fd |+//! | 332 | statx | the newer, extensible stat |+//! | 99 | sysinfo | Linux's: uptime, memory, process count |+//! | 98 | getrusage | CPU time used (self, children, thread) |+//! | 100 | times | CPU time in clock ticks (100 per second) |+//! | 229 | clock_getres | how fine each clock is |+//! | 54 | setsockopt | SO_RCVTIMEO; SO_REUSEADDR & co. accepted |+//! | 55 | getsockopt | SO_ERROR, SO_TYPE, SO_RCVBUF/SNDBUF, ... |+//! | 51 | getsockname | our own address and port |+//! | 52 | getpeername | the other end's address and port |+//! | 48 | shutdown | close one direction of a connection |+//! | 53 | socketpair | needs AF_UNIX, which we don't have: EAFNOSUPPORT |+//! | 37 | alarm | SIGALRM in N seconds |+//! | 36 | getitimer | the ITIMER_REAL timer |+//! | 38 | setitimer | same, set (periodic too) |+//! | 222 | timer_create | POSIX timers (timer.rs) |+//! | 223-226 | timer_settime/gettime/getoverrun/delete | |+//! | 102/104/107/108 | getuid/getgid/geteuid/getegid | the stored ids |+//! | 105/106 | setuid/setgid | change ids (root: any; others: their own) |+//! | 113/114 | setreuid/setregid | |+//! | 117/119 | setresuid/setresgid | |+//! | 118/120 | getresuid/getresgid | |+//! | 122/123 | setfsuid/setfsgid | |+//! | 115/116 | getgroups/setgroups | supplementary groups |+//! | 140/141 | getpriority/setpriority | the nice value (stored only) |+//! | 170/171 | sethostname/setdomainname | what uname reports |+//! | 73 | flock | whole-file advisory locks |+//! | 295/296 | preadv/pwritev | readv/writev at an offset |+//! | 40 | sendfile | copy from one fd to another inside the kernel |++use crate::errno::Errno;+use crate::fs::{self, Inode};+use crate::process::{self, Creds};+use crate::syscall::{+ at_base, copy_to_user, current, path_at, read_iovecs, task, user_cstr, user_path, user_path_at, user_slice,+ user_slice_mut, with_socket, write_sockaddr, AT_EMPTY_PATH, AT_FDCWD, AT_SYMLINK_NOFOLLOW, PATH_MAX,+ SOL_SOCKET, SO_RCVTIMEO,+};+use alloc::string::String;+use alloc::sync::Arc;+use alloc::vec::Vec;+use core::sync::atomic::Ordering;+use spin::Mutex;++pub const SYS_SHUTDOWN: u64 = 48;+pub const SYS_GETSOCKNAME: u64 = 51;+pub const SYS_GETPEERNAME: u64 = 52;+pub const SYS_SOCKETPAIR: u64 = 53;+pub const SYS_SETSOCKOPT: u64 = 54;+pub const SYS_GETSOCKOPT: u64 = 55;+pub const SYS_GETITIMER: u64 = 36;+pub const SYS_ALARM: u64 = 37;+pub const SYS_SETITIMER: u64 = 38;+pub const SYS_SENDFILE: u64 = 40;+pub const SYS_FLOCK: u64 = 73;+pub const SYS_LINK: u64 = 86;+pub const SYS_SYMLINK: u64 = 88;+pub const SYS_CHOWN: u64 = 92;+pub const SYS_FCHOWN: u64 = 93;+pub const SYS_LCHOWN: u64 = 94;+pub const SYS_GETRUSAGE: u64 = 98;+pub const SYS_SYSINFO: u64 = 99;+pub const SYS_TIMES: u64 = 100;+pub const SYS_GETUID: u64 = 102;+pub const SYS_GETGID: u64 = 104;+pub const SYS_SETUID: u64 = 105;+pub const SYS_SETGID: u64 = 106;+pub const SYS_GETEUID: u64 = 107;+pub const SYS_GETEGID: u64 = 108;+pub const SYS_SETREUID: u64 = 113;+pub const SYS_SETREGID: u64 = 114;+pub const SYS_GETGROUPS: u64 = 115;+pub const SYS_SETGROUPS: u64 = 116;+pub const SYS_SETRESUID: u64 = 117;+pub const SYS_GETRESUID: u64 = 118;+pub const SYS_SETRESGID: u64 = 119;+pub const SYS_GETRESGID: u64 = 120;+pub const SYS_SETFSUID: u64 = 122;+pub const SYS_SETFSGID: u64 = 123;+pub const SYS_STATFS: u64 = 137;+pub const SYS_FSTATFS: u64 = 138;+pub const SYS_GETPRIORITY: u64 = 140;+pub const SYS_SETPRIORITY: u64 = 141;+pub const SYS_SETHOSTNAME: u64 = 170;+pub const SYS_SETDOMAINNAME: u64 = 171;+pub const SYS_TIMER_CREATE: u64 = 222;+pub const SYS_TIMER_SETTIME: u64 = 223;+pub const SYS_TIMER_GETTIME: u64 = 224;+pub const SYS_TIMER_GETOVERRUN: u64 = 225;+pub const SYS_TIMER_DELETE: u64 = 226;+pub const SYS_CLOCK_GETRES: u64 = 229;+pub const SYS_FCHOWNAT: u64 = 260;+pub const SYS_LINKAT: u64 = 265;+pub const SYS_SYMLINKAT: u64 = 266;+pub const SYS_PREADV: u64 = 295;+pub const SYS_PWRITEV: u64 = 296;+pub const SYS_STATX: u64 = 332;++/// linkat: link what a symlink points to, not the symlink.+pub const AT_SYMLINK_FOLLOW: u64 = 0x400;+const AT_NO_AUTOMOUNT: u64 = 0x800;+const AT_STATX_SYNC_TYPE: u64 = 0x6000;++/// The stage 17 calls; None = not one of ours (the caller says ENOSYS).+pub fn dispatch(nr: u64, a: [u64; 6]) -> Option<Result<u64, Errno>> {+ let fdcwd = AT_FDCWD as u64;+ Some(match nr {+ // 1. Links.+ SYS_LINK => sys_linkat(fdcwd, a[0], fdcwd, a[1], 0),+ SYS_LINKAT => sys_linkat(a[0], a[1], a[2], a[3], a[4]),+ SYS_SYMLINK => sys_symlinkat(a[0], fdcwd, a[1]),+ SYS_SYMLINKAT => sys_symlinkat(a[0], a[1], a[2]),+ // 2. Sockets options, CPU times.+ SYS_SETSOCKOPT => sys_setsockopt(a[0], a[1], a[2], a[3], a[4]),+ SYS_TIMES => sys_times(a[0]),+ // 3. Owners.+ SYS_CHOWN => sys_fchownat(fdcwd, a[0], a[1], a[2], 0),+ SYS_LCHOWN => sys_fchownat(fdcwd, a[0], a[1], a[2], AT_SYMLINK_NOFOLLOW),+ SYS_FCHOWN => sys_fchown(a[0], a[1], a[2]),+ SYS_FCHOWNAT => sys_fchownat(a[0], a[1], a[2], a[3], a[4]),+ // 4. File systems, statx.+ SYS_STATFS => sys_statfs(a[0], a[1]),+ SYS_FSTATFS => sys_fstatfs(a[0], a[1]),+ SYS_STATX => sys_statx(a[0], a[1], a[2], a[3], a[4]),+ // 5. System information, usage, clocks.+ SYS_SYSINFO => sys_sysinfo(a[0]),+ SYS_GETRUSAGE => sys_getrusage(a[0] as i64, a[1]),+ SYS_CLOCK_GETRES => sys_clock_getres(a[0], a[1]),+ // 6. Sockets.+ SYS_GETSOCKOPT => sys_getsockopt(a[0], a[1], a[2], a[3], a[4]),+ SYS_GETSOCKNAME => with_socket(a[0], |s| Ok(s.local())).and_then(|addr| write_sockaddr(a[1], a[2], addr).map(|_| 0)),+ SYS_GETPEERNAME => with_socket(a[0], |s| s.peer().ok_or(Errno::ENOTCONN)).and_then(|addr| write_sockaddr(a[1], a[2], addr).map(|_| 0)),+ SYS_SHUTDOWN => with_socket(a[0], |s| s.shutdown(a[1])).map(|_| 0),+ SYS_SOCKETPAIR => sys_socketpair(a[0]),+ // 7. Timers.+ SYS_ALARM => sys_alarm(a[0]),+ SYS_GETITIMER => sys_getitimer(a[0], a[1]),+ SYS_SETITIMER => sys_setitimer(a[0], a[1], a[2]),+ SYS_TIMER_CREATE => sys_timer_create(a[0], a[1], a[2]),+ SYS_TIMER_SETTIME => sys_timer_settime(a[0], a[1], a[2], a[3]),+ SYS_TIMER_GETTIME => sys_timer_gettime(a[0], a[1]),+ SYS_TIMER_GETOVERRUN => current().and_then(|p| crate::timer::getoverrun(p.pid, a[0] as i32)),+ SYS_TIMER_DELETE => current().and_then(|p| crate::timer::delete(p.pid, a[0] as i32)).map(|_| 0),+ // 8. Ids, priority, host name.+ SYS_GETUID => creds().map(|c| c.ruid as u64),+ SYS_GETEUID => creds().map(|c| c.euid as u64),+ SYS_GETGID => creds().map(|c| c.rgid as u64),+ SYS_GETEGID => creds().map(|c| c.egid as u64),+ SYS_SETUID => set_id(false, a[0] as u32),+ SYS_SETGID => set_id(true, a[0] as u32),+ SYS_SETREUID => set_re(false, a[0] as u32, a[1] as u32),+ SYS_SETREGID => set_re(true, a[0] as u32, a[1] as u32),+ SYS_SETRESUID => set_res(false, [a[0] as u32, a[1] as u32, a[2] as u32]),+ SYS_SETRESGID => set_res(true, [a[0] as u32, a[1] as u32, a[2] as u32]),+ SYS_GETRESUID => get_res(false, [a[0], a[1], a[2]]),+ SYS_GETRESGID => get_res(true, [a[0], a[1], a[2]]),+ SYS_SETFSUID => set_fs(false, a[0] as u32),+ SYS_SETFSGID => set_fs(true, a[0] as u32),+ SYS_GETGROUPS => sys_getgroups(a[0] as i32 as i64, a[1]),+ SYS_SETGROUPS => sys_setgroups(a[0], a[1]),+ SYS_GETPRIORITY => sys_getpriority(a[0], a[1]),+ SYS_SETPRIORITY => sys_setpriority(a[0], a[1], a[2] as i32),+ SYS_SETHOSTNAME => set_name(&HOSTNAME, a[0], a[1]),+ SYS_SETDOMAINNAME => set_name(&DOMAINNAME, a[0], a[1]),+ // 9. The rest.+ SYS_FLOCK => sys_flock(a[0], a[1]),+ SYS_PREADV => sys_preadv(a[0], a[1], a[2], a[3], false),+ SYS_PWRITEV => sys_preadv(a[0], a[1], a[2], a[3], true),+ SYS_SENDFILE => sys_sendfile(a[0], a[1], a[2], a[3]),+ _ => return None,+ })+}++/// Names for `--trace`.+pub fn name(nr: u64) -> Option<&'static str> {+ Some(match nr {+ SYS_SHUTDOWN => "shutdown",+ SYS_GETSOCKNAME => "getsockname",+ SYS_GETPEERNAME => "getpeername",+ SYS_SOCKETPAIR => "socketpair",+ SYS_GETSOCKOPT => "getsockopt",+ SYS_GETITIMER => "getitimer",+ SYS_ALARM => "alarm",+ SYS_SETITIMER => "setitimer",+ SYS_SENDFILE => "sendfile",+ SYS_FLOCK => "flock",+ SYS_LINK => "link",+ SYS_SYMLINK => "symlink",+ SYS_CHOWN => "chown",+ SYS_FCHOWN => "fchown",+ SYS_LCHOWN => "lchown",+ SYS_GETRUSAGE => "getrusage",+ SYS_SYSINFO => "sysinfo",+ SYS_TIMES => "times",+ SYS_SETUID => "setuid",+ SYS_SETGID => "setgid",+ SYS_SETREUID => "setreuid",+ SYS_SETREGID => "setregid",+ SYS_GETGROUPS => "getgroups",+ SYS_SETGROUPS => "setgroups",+ SYS_SETRESUID => "setresuid",+ SYS_GETRESUID => "getresuid",+ SYS_SETRESGID => "setresgid",+ SYS_GETRESGID => "getresgid",+ SYS_SETFSUID => "setfsuid",+ SYS_SETFSGID => "setfsgid",+ SYS_STATFS => "statfs",+ SYS_FSTATFS => "fstatfs",+ SYS_GETPRIORITY => "getpriority",+ SYS_SETPRIORITY => "setpriority",+ SYS_SETHOSTNAME => "sethostname",+ SYS_SETDOMAINNAME => "setdomainname",+ SYS_TIMER_CREATE => "timer_create",+ SYS_TIMER_SETTIME => "timer_settime",+ SYS_TIMER_GETTIME => "timer_gettime",+ SYS_TIMER_GETOVERRUN => "timer_getoverrun",+ SYS_TIMER_DELETE => "timer_delete",+ SYS_CLOCK_GETRES => "clock_getres",+ SYS_FCHOWNAT => "fchownat",+ SYS_LINKAT => "linkat",+ SYS_SYMLINKAT => "symlinkat",+ SYS_PREADV => "preadv",+ SYS_PWRITEV => "pwritev",+ SYS_STATX => "statx",+ _ => return None,+ })+}++/// Write `bytes` (a C struct we built) to user memory.+fn put(ptr: u64, bytes: &[u8]) -> Result<(), Errno> {+ user_slice_mut(ptr, bytes.len() as u64)?.copy_from_slice(bytes);+ Ok(())+}++/// A little-endian struct builder: `at(offset, value)`.+struct Buf(Vec<u8>);+impl Buf {+ fn new(len: usize) -> Buf {+ Buf(alloc::vec![0; len])+ }+ fn u64(&mut self, off: usize, v: u64) -> &mut Buf {+ self.0[off..off + 8].copy_from_slice(&v.to_le_bytes());+ self+ }+ fn u32(&mut self, off: usize, v: u32) -> &mut Buf {+ self.0[off..off + 4].copy_from_slice(&v.to_le_bytes());+ self+ }+ fn u16(&mut self, off: usize, v: u16) -> &mut Buf {+ self.0[off..off + 2].copy_from_slice(&v.to_le_bytes());+ self+ }+}++fn read_u64(ptr: u64, i: u64) -> Result<u64, Errno> {+ Ok(u64::from_le_bytes(user_slice(ptr + 8 * i, 8)?.try_into().unwrap()))+}++// --- 1. link / symlink ------------------------------------------------------++/// link / linkat(olddirfd, old, newdirfd, new, flags). Like Linux, a+/// symlink is linked itself unless AT_SYMLINK_FOLLOW; AT_EMPTY_PATH with+/// "" links the file open on `olddirfd`. Directories: EPERM. FAT, exFAT+/// (no hard links on disk): EPERM; btrfs (read-only): EROFS.+fn sys_linkat(olddirfd: u64, old_ptr: u64, newdirfd: u64, new_ptr: u64, flags: u64) -> Result<u64, Errno> {+ if flags & !(AT_SYMLINK_FOLLOW | AT_EMPTY_PATH) != 0 {+ return Err(Errno::EINVAL);+ }+ let raw = user_cstr(old_ptr, PATH_MAX - 1)?;+ let new = user_path_at(newdirfd, new_ptr)?;+ if raw.is_empty() && flags & AT_EMPTY_PATH != 0 {+ let file = current()?.file(olddirfd)?;+ let (inode, path) = file.vfs_inode().zip(file.path()).ok_or(Errno::ENOENT)?;+ fs::link_inode(&inode, &path, &new)?;+ return Ok(0);+ }+ let old = path_at(olddirfd, &raw)?;+ fs::link(&old, &new, flags & AT_SYMLINK_FOLLOW != 0)?;+ Ok(0)+}++/// symlink / symlinkat(target, newdirfd, linkpath). The target is stored+/// exactly as given -- it may be relative, or point nowhere yet.+fn sys_symlinkat(target_ptr: u64, newdirfd: u64, path_ptr: u64) -> Result<u64, Errno> {+ let raw = user_cstr(target_ptr, PATH_MAX - 1)?;+ let target = core::str::from_utf8(&raw).map_err(|_| Errno::EINVAL)?;+ let path = user_path_at(newdirfd, path_ptr)?;+ let link = fs::symlink(target, &path)?;+ set_creator(&*link);+ Ok(0)+}++/// A new file, directory or symlink belongs to whoever made it (its+/// fsuid/fsgid). Root's files are owned by 0:0 already, so only other+/// users cost a write; where owners can't be stored (FAT), nothing happens.+pub fn set_creator(inode: &dyn Inode) {+ if let Ok(c) = creds() {+ if c.fsuid != 0 || c.fsgid != 0 {+ let _ = inode.set_owner(Some(c.fsuid), Some(c.fsgid));+ }+ }+}++// --- 2. setsockopt, times(), CPU clocks -----------------------------------++const SO_REUSEADDR: u64 = 2;+const SO_TYPE: u64 = 3;+const SO_ERROR: u64 = 4;+const SO_SNDBUF: u64 = 7;+const SO_RCVBUF: u64 = 8;+const SO_KEEPALIVE: u64 = 9;+const SO_REUSEPORT: u64 = 15;+const SO_SNDTIMEO: u64 = 21;+const SO_ACCEPTCONN: u64 = 30;+const SO_PROTOCOL: u64 = 38;+const SO_DOMAIN: u64 = 39;+const IPPROTO_TCP: u64 = 6;+const TCP_NODELAY: u64 = 1;+/// Where TCP_NODELAY is remembered among the socket's flag bits.+const NODELAY_BIT: u32 = 31;++/// setsockopt. SO_RCVTIMEO really works (recv/accept time out). The rest+/// that servers routinely set are accepted and remembered, but change+/// nothing: SO_REUSEADDR/SO_REUSEPORT (smoltcp lets a port be reused at+/// once anyway), SO_KEEPALIVE (no keepalive probes), SO_SNDBUF/SO_RCVBUF+/// (fixed buffers), SO_SNDTIMEO (sends don't block for long), TCP_NODELAY.+/// Anything else: ENOPROTOOPT, like Linux for an unknown option.+fn sys_setsockopt(fd: u64, level: u64, opt: u64, value: u64, len: u64) -> Result<u64, Errno> {+ let int = || -> Result<u32, Errno> {+ if len < 4 {+ return Err(Errno::EINVAL);+ }+ Ok(u32::from_le_bytes(user_slice(value, 4)?.try_into().unwrap()))+ };+ match (level, opt) {+ (SOL_SOCKET, SO_RCVTIMEO) => {+ if len < 16 {+ return Err(Errno::EINVAL);+ }+ let (secs, usecs) = (read_u64(value, 0)?, read_u64(value, 1)?);+ let total = secs.saturating_mul(1_000_000).saturating_add(usecs);+ with_socket(fd, |s| Ok(s.set_recv_timeout(if total == 0 { None } else { Some(total) })))?;+ }+ (SOL_SOCKET, SO_SNDTIMEO) => {+ if len < 16 {+ return Err(Errno::EINVAL);+ }+ with_socket(fd, |_| Ok(()))?;+ }+ (SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT | SO_KEEPALIVE) => {+ let on = int()? != 0;+ with_socket(fd, |s| Ok(s.set_flag(opt as u32, on)))?;+ }+ (SOL_SOCKET, SO_SNDBUF | SO_RCVBUF) => {+ int()?;+ with_socket(fd, |_| Ok(()))?;+ }+ (IPPROTO_TCP, TCP_NODELAY) => {+ let on = int()? != 0;+ with_socket(fd, |s| {+ if s.kind != crate::net::socket::Kind::Tcp {+ return Err(Errno::EOPNOTSUPP);+ }+ Ok(s.set_flag(NODELAY_BIT, on))+ })?;+ }+ _ => {+ with_socket(fd, |_| Ok(()))?; // ENOTSOCK / EBADF first, like Linux+ return Err(Errno::ENOPROTOOPT);+ }+ }+ Ok(0)+}++/// getsockopt(fd, level, opt, *value, *len).+fn sys_getsockopt(fd: u64, level: u64, opt: u64, value: u64, len_ptr: u64) -> Result<u64, Errno> {+ use crate::net::socket::Kind;+ let room = u32::from_le_bytes(user_slice(len_ptr, 4)?.try_into().unwrap()) as i32;+ if room < 0 {+ return Err(Errno::EINVAL);+ }+ let answer: Vec<u8> = with_socket(fd, |s| {+ let int = |v: u32| v.to_le_bytes().to_vec();+ Ok(match (level, opt) {+ (SOL_SOCKET, SO_TYPE) => int(if s.kind == Kind::Tcp { 1 } else { 2 }), // SOCK_STREAM / SOCK_DGRAM+ (SOL_SOCKET, SO_ERROR) => int(s.take_error() as u32),+ (SOL_SOCKET, SO_SNDBUF | SO_RCVBUF) => int(s.buffer_size() as u32),+ (SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT | SO_KEEPALIVE) => int(s.flag(opt as u32) as u32),+ (SOL_SOCKET, SO_ACCEPTCONN) => int(s.listening() as u32),+ (SOL_SOCKET, SO_DOMAIN) => int(2), // AF_INET+ (SOL_SOCKET, SO_PROTOCOL) => int(match s.kind {+ Kind::Tcp => 6,+ Kind::Udp => 17,+ Kind::Icmp => 1,+ }),+ (SOL_SOCKET, SO_RCVTIMEO | SO_SNDTIMEO) => {+ let us = if opt == SO_RCVTIMEO { s.recv_timeout_us().unwrap_or(0) } else { 0 };+ let mut b = (us / 1_000_000).to_le_bytes().to_vec();+ b.extend_from_slice(&(us % 1_000_000).to_le_bytes());+ b+ }+ (IPPROTO_TCP, TCP_NODELAY) if s.kind == Kind::Tcp => int(s.flag(NODELAY_BIT) as u32),+ _ => return Err(Errno::ENOPROTOOPT),+ })+ })?;+ let n = answer.len().min(room as usize);+ put(value, &answer[..n])?;+ copy_to_user(len_ptr, &(n as u32))?;+ Ok(0)+}++/// socketpair: Linux makes these with AF_UNIX sockets, which Rustbelt+/// doesn't have yet (deferred). We say so honestly: EAFNOSUPPORT for+/// AF_UNIX, and EOPNOTSUPP for AF_INET -- as Linux does.+fn sys_socketpair(domain: u64) -> Result<u64, Errno> {+ match domain {+ 2 => Err(Errno::EOPNOTSUPP),+ _ => Err(Errno::EAFNOSUPPORT),+ }+}++/// The CPU clocks of clock_gettime (2 = this process, 3 = this thread),+/// in microseconds. Counted in 10 ms timer ticks (see process::CpuTimes),+/// so a program that ran for less than a tick reads 0.+pub fn cpu_clock_us(clock: u64) -> Result<u64, Errno> {+ let ticks = match clock {+ 2 => {+ let p = current()?;+ p.cpu.user.load(Ordering::Relaxed) + p.cpu.system.load(Ordering::Relaxed)+ }+ 3 => task()?.cpu_ticks.load(Ordering::Relaxed),+ _ => return Err(Errno::EINVAL),+ };+ Ok(ticks * process::CPU_TICK_US)+}++/// times(*tms): CPU time in clock ticks (sysconf(_SC_CLK_TCK) = 100 per+/// second, which is exactly our 10 ms timer tick): user, system, and the+/// same for reaped children. Returns ticks since boot.+fn sys_times(buf: u64) -> Result<u64, Errno> {+ let p = current()?;+ if buf != 0 {+ let c = &p.cpu;+ let t = [c.user.load(Ordering::Relaxed), c.system.load(Ordering::Relaxed), c.child_user.load(Ordering::Relaxed), c.child_system.load(Ordering::Relaxed)];+ copy_to_user(buf, &t)?;+ }+ Ok(crate::time::now_us() / process::CPU_TICK_US)+}++/// clock_getres(clock, *res): our clocks count microseconds; the CPU+/// clocks move in 10 ms ticks.+fn sys_clock_getres(clock: u64, res: u64) -> Result<u64, Errno> {+ let ns = match clock {+ 0 | 1 | 4 | 5 | 6 | 7 => 1000,+ 2 | 3 => process::CPU_TICK_US * 1000,+ _ => return Err(Errno::EINVAL),+ };+ if res != 0 {+ copy_to_user(res, &[0u64, ns])?;+ }+ Ok(0)+}++/// getrusage(who, *rusage): 0 = RUSAGE_SELF, -1 = RUSAGE_CHILDREN (reaped+/// ones), 1 = RUSAGE_THREAD. Fills the two CPU times and ru_maxrss (our+/// resident size now, in KiB); the other 14 counters stay 0.+fn sys_getrusage(who: i64, buf: u64) -> Result<u64, Errno> {+ let p = current()?;+ let c = &p.cpu;+ let (user, system) = match who {+ 0 => (c.user.load(Ordering::Relaxed), c.system.load(Ordering::Relaxed)),+ -1 => (c.child_user.load(Ordering::Relaxed), c.child_system.load(Ordering::Relaxed)),+ 1 => {+ // Per thread we only count ticks, not user/system: say "user".+ (task()?.cpu_ticks.load(Ordering::Relaxed), 0)+ }+ _ => return Err(Errno::EINVAL),+ };+ let tv = |ticks: u64| {+ let us = ticks * process::CPU_TICK_US;+ (us / 1_000_000, us % 1_000_000)+ };+ let mut b = Buf::new(144);+ let (us, uu) = tv(user);+ let (ss, su) = tv(system);+ b.u64(0, us).u64(8, uu).u64(16, ss).u64(24, su);+ if who != -1 {+ let resident = p.mm.lock().usage().0;+ b.u64(32, resident * 4);+ }+ put(buf, &b.0)?;+ Ok(0)+}++// --- 3. chown family -------------------------------------------------------++/// -1 (as a 32-bit id) = "leave this one alone".+fn id_arg(v: u64) -> Option<u32> {+ let v = v as u32;+ (v != u32::MAX).then_some(v)+}++/// Change an inode's owner. Rustbelt stores owners (ramfs, ext2) but+/// doesn't check permissions, so anyone may chown anything. FAT and exFAT+/// can't store an owner: asking for the owner they already show (root) is+/// fine, anything else is EPERM, like Linux's vfat. btrfs (read-only): EROFS.+fn chown_inode(inode: &dyn Inode, uid: u64, gid: u64) -> Result<u64, Errno> {+ let (uid, gid) = (id_arg(uid), id_arg(gid));+ match inode.set_owner(uid, gid) {+ Err(Errno::EPERM) => {+ let (u, g) = inode.owner();+ if uid.unwrap_or(u) == u && gid.unwrap_or(g) == g {+ Ok(0)+ } else {+ Err(Errno::EPERM)+ }+ }+ other => other.map(|_| 0),+ }+}++fn sys_fchownat(dirfd: u64, path_ptr: u64, uid: u64, gid: u64, flags: u64) -> Result<u64, Errno> {+ if flags & !(AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH) != 0 {+ return Err(Errno::EINVAL);+ }+ let raw = user_cstr(path_ptr, PATH_MAX - 1)?;+ if raw.is_empty() && flags & AT_EMPTY_PATH != 0 {+ if dirfd as i32 as i64 == AT_FDCWD {+ let cwd = at_base(dirfd)?;+ return chown_inode(&*fs::lookup(&cwd)?, uid, gid);+ }+ return sys_fchown(dirfd, uid, gid);+ }+ let path = path_at(dirfd, &raw)?;+ let inode = if flags & AT_SYMLINK_NOFOLLOW != 0 { fs::lookup_nofollow(&path)? } else { fs::lookup(&path)? };+ chown_inode(&*inode, uid, gid)+}++fn sys_fchown(fd: u64, uid: u64, gid: u64) -> Result<u64, Errno> {+ let file = current()?.file(fd)?;+ match file.vfs_inode() {+ Some(inode) => chown_inode(&*inode, uid, gid),+ // Pipes, sockets, terminals: nothing on disk to own. Linux keeps+ // an owner in their in-memory inode; we accept and forget it.+ None => Ok(0),+ }+}++// --- 4. statfs / fstatfs / statx ----------------------------------------++const PIPEFS_MAGIC: u64 = 0x5049_5045;+const SOCKFS_MAGIC: u64 = 0x534F_434B;+const ST_RDONLY: u64 = 1;+const ST_VALID: u64 = 0x20;++/// Linux's `struct statfs` (120 bytes).+fn write_statfs(buf: u64, st: &fs::StatFs, dev: u64) -> Result<u64, Errno> {+ let mut b = Buf::new(120);+ b.u64(0, st.magic)+ .u64(8, st.block_size)+ .u64(16, st.blocks)+ .u64(24, st.free_blocks)+ .u64(32, st.avail_blocks)+ .u64(40, st.files)+ .u64(48, st.free_files)+ .u32(56, dev as u32) // f_fsid: which mount, like st_dev+ .u64(64, st.name_max)+ .u64(72, st.block_size) // f_frsize+ .u64(80, ST_VALID | if st.read_only { ST_RDONLY } else { 0 });+ put(buf, &b.0)?;+ Ok(0)+}++fn statfs_path(path: &str, buf: u64) -> Result<u64, Errno> {+ let (index, fs) = fs::fs_of(path).ok_or(Errno::ENOENT)?;+ write_statfs(buf, &fs.statfs(), index as u64 + 1)+}++fn sys_statfs(path_ptr: u64, buf: u64) -> Result<u64, Errno> {+ let path = user_path(path_ptr)?;+ fs::lookup(&path)?; // ENOENT for a missing path+ statfs_path(&path, buf)+}++fn sys_fstatfs(fd: u64, buf: u64) -> Result<u64, Errno> {+ let file = current()?.file(fd)?;+ if let Some(path) = file.path() {+ return statfs_path(&path, buf);+ }+ let magic = match file.stat()?.kind {+ fs::file::KIND_PIPE => PIPEFS_MAGIC,+ fs::file::KIND_SOCKET => SOCKFS_MAGIC,+ _ => fs::TMPFS_MAGIC, // terminals, epoll: in-memory things+ };+ let st = fs::StatFs { magic, block_size: 4096, name_max: 255, ..Default::default() };+ write_statfs(buf, &st, 0)+}++/// statx(dirfd, path, flags, mask, *statx): the same facts as stat, in+/// the newer layout (256 bytes). We fill all the basic fields (mask+/// STATX_BASIC_STATS = 0x7ff) whatever `mask` asks; no birth time.+fn sys_statx(dirfd: u64, path_ptr: u64, flags: u64, mask: u64, buf: u64) -> Result<u64, Errno> {+ if flags & !(AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH | AT_NO_AUTOMOUNT | AT_STATX_SYNC_TYPE) != 0+ || flags & AT_STATX_SYNC_TYPE == AT_STATX_SYNC_TYPE+ || mask & 0x8000_0000 != 0+ {+ return Err(Errno::EINVAL);+ }+ let st = crate::syscall::stat_at(dirfd, path_ptr, flags & (AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH))?;+ let mut b = Buf::new(256);+ b.u32(0, 0x7ff)+ .u32(4, st.st_blksize as u32)+ .u32(16, st.st_nlink as u32)+ .u32(20, st.st_uid)+ .u32(24, st.st_gid)+ .u16(28, st.st_mode as u16)+ .u64(32, st.st_ino)+ .u64(40, st.st_size as u64)+ .u64(48, st.st_blocks as u64);+ for (off, t) in [(64, st.st_atime), (96, st.st_ctime), (112, st.st_mtime)] {+ b.u64(off, t[0] as u64).u32(off + 8, t[1] as u32);+ }+ b.u32(128, (st.st_rdev >> 8) as u32 & 0xfff).u32(132, st.st_rdev as u32 & 0xff);+ b.u32(136, 0).u32(140, st.st_dev as u32); // device: major 0, minor = mount+ put(buf, &b.0)?;+ Ok(0)+}++// --- 5. sysinfo -------------------------------------------------------------++/// sysinfo (99): Linux's `struct sysinfo` (112 bytes), what `free`,+/// `uptime` and `top` read. (Rustbelt's own, richer sysinfo is 502.)+/// The load averages are how busy the CPUs have been since boot, the+/// same number three times: we don't keep 1/5/15-minute averages.+fn sys_sysinfo(buf: u64) -> Result<u64, Errno> {+ let (total, free) = crate::memory::frame_totals();+ let cpus = crate::smp::cpu_count();+ let (mut ticks, mut busy) = (0, 0);+ for cpu in 0..cpus {+ let (t, b) = crate::thread::cpu_ticks(cpu);+ ticks += t;+ busy += b;+ }+ // Load = busy CPUs on average, in 16.16 fixed point.+ let load = if ticks == 0 { 0 } else { busy * cpus as u64 * 65536 / ticks };+ let mut b = Buf::new(112);+ b.u64(0, crate::time::now_us() / 1_000_000)+ .u64(8, load)+ .u64(16, load)+ .u64(24, load)+ .u64(32, total as u64 * 4096)+ .u64(40, free as u64 * 4096)+ .u16(80, process::count().min(u16::MAX as usize) as u16)+ .u32(104, 1); // mem_unit: sizes are in bytes+ put(buf, &b.0)?;+ Ok(0)+}++// --- 7. Timers -------------------------------------------------------------++fn tv_us(ptr: u64) -> Result<(u64, u64), Errno> {+ // struct itimerval { timeval it_interval; timeval it_value; }+ let (is, iu, vs, vu) = (read_u64(ptr, 0)?, read_u64(ptr, 1)?, read_u64(ptr, 2)?, read_u64(ptr, 3)?);+ if iu >= 1_000_000 || vu >= 1_000_000 || (is as i64) < 0 || (vs as i64) < 0 {+ return Err(Errno::EINVAL);+ }+ Ok((vs.saturating_mul(1_000_000).saturating_add(vu), is.saturating_mul(1_000_000).saturating_add(iu)))+}++fn put_itimerval(ptr: u64, (value, interval): (u64, u64)) -> Result<(), Errno> {+ copy_to_user(ptr, &[interval / 1_000_000, interval % 1_000_000, value / 1_000_000, value % 1_000_000])+}++/// alarm(seconds): SIGALRM that many seconds from now (0 cancels).+/// Returns the seconds left on the previous alarm (rounded, never 0 if+/// one was pending).+fn sys_alarm(secs: u64) -> Result<u64, Errno> {+ let pid = current()?.pid;+ let (old, _) = crate::timer::set_itimer(pid, (secs as u32 as u64) * 1_000_000, 0);+ Ok(if old == 0 { 0 } else { ((old + 500_000) / 1_000_000).max(1) })+}++/// ITIMER_REAL (0) only: ITIMER_VIRTUAL and ITIMER_PROF (CPU-time timers)+/// are EINVAL here.+fn sys_getitimer(which: u64, ptr: u64) -> Result<u64, Errno> {+ if which != 0 {+ return Err(Errno::EINVAL);+ }+ put_itimerval(ptr, crate::timer::get_itimer(current()?.pid))?;+ Ok(0)+}++fn sys_setitimer(which: u64, new: u64, old: u64) -> Result<u64, Errno> {+ if which != 0 {+ return Err(Errno::EINVAL);+ }+ let (value, interval) = if new == 0 { (0, 0) } else { tv_us(new)? };+ let prev = crate::timer::set_itimer(current()?.pid, value, interval);+ if old != 0 {+ put_itimerval(old, prev)?;+ }+ Ok(0)+}++/// A timespec as microseconds, rounded up (a timer never fires early).+fn ts_us(ptr: u64, i: u64) -> Result<u64, Errno> {+ let (s, ns) = (read_u64(ptr, 2 * i)?, read_u64(ptr, 2 * i + 1)?);+ if ns >= 1_000_000_000 || (s as i64) < 0 {+ return Err(Errno::EINVAL);+ }+ Ok(s.saturating_mul(1_000_000).saturating_add(ns.div_ceil(1000)))+}++fn put_itimerspec(ptr: u64, (value, interval): (u64, u64)) -> Result<(), Errno> {+ copy_to_user(ptr, &[interval / 1_000_000, interval % 1_000_000 * 1000, value / 1_000_000, value % 1_000_000 * 1000])+}++/// timer_create(clock, *sigevent, *timerid). Clocks: REALTIME, MONOTONIC,+/// BOOTTIME. Notification: SIGEV_SIGNAL, SIGEV_NONE, SIGEV_THREAD_ID.+/// (SIGEV_THREAD is the C library's job: it asks us for SIGEV_THREAD_ID.)+fn sys_timer_create(clock: u64, sev_ptr: u64, id_ptr: u64) -> Result<u64, Errno> {+ use crate::timer::{SigEvent, CLOCK_BOOTTIME, CLOCK_MONOTONIC, CLOCK_REALTIME, SIGEV_NONE, SIGEV_SIGNAL, SIGEV_THREAD_ID};+ if ![CLOCK_REALTIME, CLOCK_MONOTONIC, CLOCK_BOOTTIME].contains(&clock) {+ return Err(Errno::EINVAL);+ }+ let p = current()?;+ let ev = if sev_ptr == 0 {+ SigEvent { notify: SIGEV_SIGNAL, signo: crate::signal::SIGALRM, tid: 0, sigval: None }+ } else {+ // struct sigevent { sigval; int signo; int notify; int tid; ... }+ let raw = user_slice(sev_ptr, 20)?;+ let sigval = u64::from_le_bytes(raw[..8].try_into().unwrap());+ let signo = u32::from_le_bytes(raw[8..12].try_into().unwrap());+ let notify = i32::from_le_bytes(raw[12..16].try_into().unwrap());+ let tid = u32::from_le_bytes(raw[16..20].try_into().unwrap()) as u64;+ match notify {+ SIGEV_NONE => {}+ SIGEV_SIGNAL | SIGEV_THREAD_ID if (1..=64).contains(&signo) => {}+ _ => return Err(Errno::EINVAL),+ }+ if notify == SIGEV_THREAD_ID && !p.tasks().iter().any(|t| t.tid() == tid) {+ return Err(Errno::EINVAL);+ }+ SigEvent { notify, signo, tid, sigval: Some(sigval) }+ };+ user_slice_mut(id_ptr, 4)?; // check before making it+ let id = crate::timer::create(p.pid, clock, ev)?;+ copy_to_user(id_ptr, &id)?;+ Ok(0)+}++/// timer_settime(id, flags, *new, *old). TIMER_ABSTIME (1): the value is a+/// time on the timer's clock.+fn sys_timer_settime(id: u64, flags: u64, new: u64, old: u64) -> Result<u64, Errno> {+ if flags & !1 != 0 {+ return Err(Errno::EINVAL);+ }+ let (interval, value) = (ts_us(new, 0)?, ts_us(new, 1)?);+ let prev = crate::timer::settime(current()?.pid, id as i32, flags & 1 != 0, value, interval)?;+ if old != 0 {+ put_itimerspec(old, prev)?;+ }+ Ok(0)+}++fn sys_timer_gettime(id: u64, ptr: u64) -> Result<u64, Errno> {+ let now = crate::timer::gettime(current()?.pid, id as i32)?;+ put_itimerspec(ptr, now)?;+ Ok(0)+}++// --- 8. Ids, priority, host name -----------------------------------------++fn creds() -> Result<Creds, Errno> {+ Ok(*current()?.creds.lock())+}++/// Change the calling process's creds with `f` (on a copy, kept if Ok).+fn change(f: impl FnOnce(&mut Creds) -> Result<u64, Errno>) -> Result<u64, Errno> {+ let p = current()?;+ let mut creds = p.creds.lock();+ let mut new = *creds;+ let r = f(&mut new)?;+ *creds = new;+ Ok(r)+}++/// The (real, effective, saved, fs) ids of `c`, users or groups.+fn ids(c: &mut Creds, group: bool) -> [&mut u32; 4] {+ if group {+ [&mut c.rgid, &mut c.egid, &mut c.sgid, &mut c.fsgid]+ } else {+ [&mut c.ruid, &mut c.euid, &mut c.suid, &mut c.fsuid]+ }+}++/// setuid / setgid: root (euid 0) sets all of them; anyone else may only+/// switch the effective id to their real or saved one.+fn set_id(group: bool, id: u32) -> Result<u64, Errno> {+ if id == u32::MAX {+ return Err(Errno::EINVAL);+ }+ change(|c| {+ let root = c.euid == 0;+ let [r, e, s, f] = ids(c, group);+ if root {+ (*r, *e, *s, *f) = (id, id, id, id);+ } else if id == *r || id == *s {+ (*e, *f) = (id, id);+ } else {+ return Err(Errno::EPERM);+ }+ Ok(0)+ })+}++/// setreuid / setregid(real, effective), -1 = unchanged.+fn set_re(group: bool, real: u32, eff: u32) -> Result<u64, Errno> {+ change(|c| {+ let root = c.euid == 0;+ let [r, e, s, f] = ids(c, group);+ let (old_r, old_e, old_s) = (*r, *e, *s);+ if real != u32::MAX && !root && real != old_r && real != old_e {+ return Err(Errno::EPERM);+ }+ if eff != u32::MAX && !root && eff != old_r && eff != old_e && eff != old_s {+ return Err(Errno::EPERM);+ }+ if real != u32::MAX {+ *r = real;+ }+ if eff != u32::MAX {+ *e = eff;+ }+ // Linux: the saved id follows the effective one when the real id+ // was set, or the effective one changed to something but the old real.+ if real != u32::MAX || (eff != u32::MAX && eff != old_r) {+ *s = *e;+ }+ *f = *e;+ Ok(0)+ })+}++/// setresuid / setresgid(real, effective, saved), -1 = unchanged. Not+/// root: each new value must be one of the current three.+fn set_res(group: bool, new: [u32; 3]) -> Result<u64, Errno> {+ change(|c| {+ let root = c.euid == 0;+ let [r, e, s, f] = ids(c, group);+ let old = [*r, *e, *s];+ if !root && new.iter().any(|&v| v != u32::MAX && !old.contains(&v)) {+ return Err(Errno::EPERM);+ }+ for (slot, v) in [&mut *r, &mut *e, &mut *s].into_iter().zip(new) {+ if v != u32::MAX {+ *slot = v;+ }+ }+ *f = *e;+ Ok(0)+ })+}++fn get_res(group: bool, ptrs: [u64; 3]) -> Result<u64, Errno> {+ let mut c = creds()?;+ let [r, e, s, _] = ids(&mut c, group);+ for (ptr, v) in ptrs.into_iter().zip([*r, *e, *s]) {+ copy_to_user(ptr, &v)?;+ }+ Ok(0)+}++/// setfsuid / setfsgid: returns the old fs id; changes it if allowed+/// (root, or one of the current ids). Never fails, like Linux.+fn set_fs(group: bool, id: u32) -> Result<u64, Errno> {+ let p = current()?;+ let mut creds = p.creds.lock();+ let root = creds.euid == 0;+ let [r, e, s, f] = ids(&mut creds, group);+ let old = *f;+ if id != u32::MAX && (root || [*r, *e, *s, *f].contains(&id)) {+ *f = id;+ }+ Ok(old as u64)+}++/// getgroups(size, *list): size 0 = just say how many.+fn sys_getgroups(size: i64, list: u64) -> Result<u64, Errno> {+ let c = creds()?;+ if size < 0 {+ return Err(Errno::EINVAL);+ }+ if size == 0 {+ return Ok(c.ngroups as u64);+ }+ if (size as usize) < c.ngroups {+ return Err(Errno::EINVAL);+ }+ for (i, g) in c.groups[..c.ngroups].iter().enumerate() {+ copy_to_user(list + 4 * i as u64, g)?;+ }+ Ok(c.ngroups as u64)+}++/// setgroups(size, *list): root only.+fn sys_setgroups(size: u64, list: u64) -> Result<u64, Errno> {+ if size > process::NGROUPS_MAX as u64 {+ return Err(Errno::EINVAL);+ }+ let raw = user_slice(list, 4 * size)?;+ change(|c| {+ if c.euid != 0 {+ return Err(Errno::EPERM);+ }+ for (i, g) in raw.chunks(4).enumerate() {+ c.groups[i] = u32::from_le_bytes(g.try_into().unwrap());+ }+ c.ngroups = size as usize;+ Ok(0)+ })+}++/// The processes get/setpriority mean: PRIO_PROCESS (0) one pid, PRIO_PGRP+/// (1) a process group, PRIO_USER (2) everything of one real uid. 0 = mine.+fn prio_targets(which: u64, who: u64) -> Result<Vec<Arc<process::Process>>, Errno> {+ let me = current()?;+ let who = who as u32 as u64;+ let list: Vec<_> = match which {+ 0 => {+ let pid = if who == 0 { me.pid } else { who };+ process::get(pid).into_iter().collect()+ }+ 1 => {+ let pgid = if who == 0 { me.pgid() } else { who };+ process::all().into_iter().filter(|p| p.pgid() == pgid).collect()+ }+ 2 => {+ let uid = if who == 0 { me.creds.lock().ruid as u64 } else { who };+ process::all().into_iter().filter(|p| p.creds.lock().ruid as u64 == uid).collect()+ }+ _ => return Err(Errno::EINVAL),+ };+ if list.is_empty() {+ return Err(Errno::ESRCH);+ }+ Ok(list)+}++/// getpriority(which, who). The raw syscall returns 20 - nice (1..40) so+/// that it's never negative; the C library turns it back into the nice value.+fn sys_getpriority(which: u64, who: u64) -> Result<u64, Errno> {+ let best = prio_targets(which, who)?.iter().map(|p| p.nice.load(Ordering::Relaxed)).min().unwrap();+ Ok((20 - best) as u64)+}++/// setpriority(which, who, nice): stored (and inherited), but the+/// scheduler doesn't use it yet. Only root may lower a nice value.+fn sys_setpriority(which: u64, who: u64, nice: i32) -> Result<u64, Errno> {+ let nice = nice.clamp(-20, 19);+ let root = creds()?.euid == 0;+ for p in prio_targets(which, who)? {+ if nice < p.nice.load(Ordering::Relaxed) && !root {+ return Err(Errno::EACCES);+ }+ p.nice.store(nice, Ordering::Relaxed);+ }+ Ok(0)+}++static HOSTNAME: Mutex<Option<String>> = Mutex::new(None);+static DOMAINNAME: Mutex<Option<String>> = Mutex::new(None);++/// The host name `uname` reports ("qemu" until sethostname).+pub fn hostname() -> String {+ HOSTNAME.lock().clone().unwrap_or_else(|| String::from("qemu"))+}++/// The NIS domain name `uname` reports ("(none)" until setdomainname).+pub fn domainname() -> String {+ DOMAINNAME.lock().clone().unwrap_or_else(|| String::from("(none)"))+}++/// sethostname / setdomainname(name, len): at most 64 bytes; root only.+fn set_name(slot: &Mutex<Option<String>>, ptr: u64, len: u64) -> Result<u64, Errno> {+ if len > 64 {+ return Err(Errno::EINVAL);+ }+ if creds()?.euid != 0 {+ return Err(Errno::EPERM);+ }+ let raw = user_slice(ptr, len)?;+ let name = core::str::from_utf8(raw).map_err(|_| Errno::EINVAL)?;+ if name.contains('\0') {+ return Err(Errno::EINVAL);+ }+ *slot.lock() = Some(String::from(name));+ Ok(0)+}++// --- 9. flock, preadv/pwritev, sendfile -------------------------------------++const LOCK_SH: u64 = 1;+const LOCK_EX: u64 = 2;+const LOCK_NB: u64 = 4;+const LOCK_UN: u64 = 8;++/// flock(fd, op): a lock on the whole file, owned by the *open file*+/// (shared by dup'ed and inherited fds; released when its last fd+/// closes). LOCK_SH (shared) / LOCK_EX (exclusive) / LOCK_UN, | LOCK_NB =+/// EAGAIN instead of waiting. Pipes and sockets: nobody else can name+/// them, so there's never a conflict and we just say yes.+fn sys_flock(fd: u64, op: u64) -> Result<u64, Errno> {+ let file = current()?.file(fd)?;+ let cmd = op & !LOCK_NB;+ if ![LOCK_SH, LOCK_EX, LOCK_UN].contains(&cmd) {+ return Err(Errno::EINVAL);+ }+ let (Some(path), Some(inode)) = (file.path(), file.vfs_inode()) else { return Ok(0) };+ // The open file (an InodeFile) is the owner: the Arc's data address.+ let owner = Arc::as_ptr(&file) as *const u8 as usize;+ if cmd == LOCK_UN {+ fs::lock::flock_release(owner);+ return Ok(0);+ }+ // Same file = same key, also through another hard link.+ let key = match inode.ino() {+ Some(ino) => fs::file::ino_hash(alloc::format!("{}:{}", fs::dev_of(&path), ino).as_bytes()),+ None => fs::file::ino_hash(path.as_bytes()),+ };+ drop(file); // don't keep the file open while we sleep+ fs::lock::flock(key, owner, cmd == LOCK_EX, op & LOCK_NB == 0)?;+ Ok(0)+}++/// preadv / pwritev(fd, iov, count, offset): readv/writev at an offset+/// (the file offset doesn't move). Buffer by buffer; stops at a short one.+fn sys_preadv(fd: u64, iov: u64, count: u64, offset: u64, write: bool) -> Result<u64, Errno> {+ if (offset as i64) < 0 {+ return Err(Errno::EINVAL);+ }+ let file = current()?.file(fd)?;+ let iovs = read_iovecs(iov, count)?;+ let mut done = 0u64;+ for (base, len) in iovs {+ let n = if write {+ file.write_at(user_slice(base, len)?, offset + done)+ } else {+ file.read_at(user_slice_mut(base, len)?, offset + done)+ };+ let n = match n {+ Ok(n) => n as u64,+ Err(e) if done == 0 => return Err(e),+ Err(_) => break,+ };+ done += n;+ if n < len {+ break;+ }+ }+ Ok(done)+}++/// sendfile(out_fd, in_fd, *offset, count): copy from a file to any fd+/// (a socket, a pipe, another file) without a trip through user memory.+/// With `offset`, read from there and update it (in_fd's offset stays);+/// without, from in_fd's offset, which moves.+fn sys_sendfile(out_fd: u64, in_fd: u64, off_ptr: u64, count: u64) -> Result<u64, Errno> {+ let p = current()?;+ let (out, input) = (p.file(out_fd)?, p.file(in_fd)?);+ drop(p);+ let mut pos = if off_ptr != 0 {+ let v = read_u64(off_ptr, 0)?;+ if (v as i64) < 0 {+ return Err(Errno::EINVAL);+ }+ v+ } else {+ input.seek(0, 1).map_err(|_| Errno::EINVAL)? // SEEK_CUR+ };+ let count = count.min(0x7fff_f000);+ let mut buf = alloc::vec![0u8; 64 * 1024];+ let mut done = 0u64;+ while done < count {+ let want = (count - done).min(buf.len() as u64) as usize;+ let n = match input.read_at(&mut buf[..want], pos) {+ Ok(n) => n,+ Err(Errno::ESPIPE) => return Err(Errno::EINVAL), // in_fd must be a file+ Err(e) if done == 0 => return Err(e),+ Err(_) => break,+ };+ if n == 0 {+ break; // end of file+ }+ let mut written = 0;+ while written < n {+ match out.write(&buf[written..n]) {+ Ok(0) => break,+ Ok(k) => written += k,+ Err(e) if done == 0 && written == 0 => return Err(e),+ Err(_) => break,+ }+ }+ done += written as u64;+ pos += written as u64;+ if written < n {+ break;+ }+ }+ if off_ptr != 0 {+ copy_to_user(off_ptr, &pos)?;+ } else {+ input.seek(pos as i64, 0)?; // SEEK_SET+ }+ Ok(done)+}+
@@ -0,0 +1,278 @@+//! Timers that send signals (stage 17): alarm, setitimer/getitimer and+//! the POSIX timers (timer_create, timer_settime, ...).+//!+//! A timer is a deadline, maybe with an interval to repeat at, and what to+//! do when it's reached: send a signal (SIGALRM for alarm and setitimer;+//! any signal, maybe to one thread, for timer_create).+//!+//! All timers live in one table. One kernel thread, "timerd", sleeps until+//! the nearest deadline, sends the signals that are due, and goes back to+//! sleep. Why a thread and not the timer interrupt? Sending a signal takes+//! locks and may wake threads; doing that from normal thread context keeps+//! the interrupt handler tiny and follows the locking rules in thread.rs.+//! timerd is started the first time anyone arms a timer.+//!+//! Resolution: timerd sleeps in PIT ticks (~55 ms), so a timer fires up+//! to ~110 ms late, never early. A periodic timer whose signals come late+//! counts the periods it missed as "overruns" (timer_getoverrun), like+//! Linux does when a signal is still pending.+//!+//! Lifetime, as on Linux: fork doesn't copy timers; execve deletes the+//! POSIX timers but keeps the alarm / ITIMER_REAL; exit deletes them all.++use crate::errno::Errno;+use crate::process;+use crate::signal::{self, SigInfo};+use crate::thread::WaitQueue;+use crate::time;+use alloc::vec::Vec;+use core::sync::atomic::{AtomicBool, Ordering};+use spin::Mutex;+use x86_64::instructions::interrupts::without_interrupts;++/// si_code for a POSIX timer's signal.+pub const SI_TIMER: i32 = -2;++// The clocks timer_create understands.+pub const CLOCK_REALTIME: u64 = 0;+pub const CLOCK_MONOTONIC: u64 = 1;+pub const CLOCK_BOOTTIME: u64 = 7;++// sigev_notify: what to do when it fires.+pub const SIGEV_SIGNAL: i32 = 0; // send sigev_signo to the process+pub const SIGEV_NONE: i32 = 1; // nothing (the program polls timer_gettime)+pub const SIGEV_THREAD_ID: i32 = 4; // send sigev_signo to thread sigev_tid++/// At most this many POSIX timers per process: EAGAIN after that.+const MAX_TIMERS: usize = 32;+/// The id we use for a process's ITIMER_REAL (POSIX ids are 0..MAX_TIMERS).+const ITIMER_ID: i32 = -1;++#[derive(Clone, Copy)]+struct Timer {+ pid: u64,+ /// timer_create's id, or ITIMER_ID.+ id: i32,+ clock: u64,+ /// Next expiry, in `time::now_us` microseconds (None = disarmed).+ next: Option<u64>,+ /// Repeat every this many microseconds (0 = once).+ interval: u64,+ signo: u32,+ notify: i32,+ /// SIGEV_THREAD_ID's thread.+ tid: u64,+ sigval: u64,+ /// Expirations missed at the last firing (timer_getoverrun).+ overrun: u64,+}++static TIMERS: Mutex<Vec<Timer>> = Mutex::new(Vec::new());+/// timerd sleeps here; poked when the table changes.+static CHANGED_QUEUE: WaitQueue = WaitQueue::new();+static CHANGED: AtomicBool = AtomicBool::new(false);+static STARTED: AtomicBool = AtomicBool::new(false);++fn with_table<R>(f: impl FnOnce(&mut Vec<Timer>) -> R) -> R {+ without_interrupts(|| f(&mut TIMERS.lock()))+}++/// Tell timerd to look again (starting it the first time).+fn poke() {+ if !STARTED.swap(true, Ordering::AcqRel) {+ crate::thread::spawn("timerd", timerd);+ }+ CHANGED.store(true, Ordering::Release);+ CHANGED_QUEUE.wake_all();+}++/// How far CLOCK_REALTIME is ahead of the monotonic clock (µs), to turn+/// absolute wall-clock deadlines into ours.+fn realtime_offset() -> u64 {+ time::realtime_us().saturating_sub(time::now_us())+}++/// The kernel thread that fires timers (see the top).+fn timerd() {+ loop {+ let now = time::now_us();+ let mut due = Vec::new();+ let next = with_table(|t| {+ for timer in t.iter_mut() {+ let Some(at) = timer.next else { continue };+ if at > now {+ continue;+ }+ if timer.interval > 0 {+ let missed = (now - at) / timer.interval;+ timer.overrun = missed;+ timer.next = Some(at + (missed + 1) * timer.interval);+ } else {+ timer.overrun = 0;+ timer.next = None;+ }+ due.push(*timer);+ }+ t.iter().filter_map(|t| t.next).min()+ });+ for timer in due {+ fire(&timer);+ }+ let changed = || CHANGED.swap(false, Ordering::AcqRel).then_some(());+ match next {+ Some(at) => {+ let deadline = time::deadline_after_us(at.saturating_sub(time::now_us()));+ let _ = CHANGED_QUEUE.wait_until_deadline(deadline, changed);+ }+ None => CHANGED_QUEUE.wait_until(changed),+ }+ }+}++/// Send a due timer's signal.+fn fire(timer: &Timer) {+ if timer.notify == SIGEV_NONE {+ return;+ }+ let Some(p) = process::get(timer.pid) else { return };+ let info = if timer.id == ITIMER_ID {+ SigInfo::from_pid(timer.signo, 0) // SI_KERNEL, like Linux's itimers+ } else {+ let mut info = SigInfo::new(timer.signo, SI_TIMER);+ info.fields[0] = timer.id as u32 as u64 | (timer.overrun.min(i32::MAX as u64)) << 32; // si_tid, si_overrun+ info.fields[1] = timer.sigval; // si_value+ info+ };+ if timer.notify == SIGEV_THREAD_ID {+ if let Some(t) = p.tasks().into_iter().find(|t| t.tid() == timer.tid) {+ signal::send_thread(&t, timer.signo, info);+ }+ return;+ }+ signal::send(&p, timer.signo, info);+}++/// (value, interval) of a timer in microseconds: time left, and the period.+fn read(timer: &Timer, now: u64) -> (u64, u64) {+ (timer.next.map_or(0, |at| at.saturating_sub(now).max(1)), timer.interval)+}++// --- setitimer / getitimer / alarm ---------------------------------------++/// getitimer(ITIMER_REAL): (time left, interval), in microseconds.+pub fn get_itimer(pid: u64) -> (u64, u64) {+ let now = time::now_us();+ with_table(|t| t.iter().find(|x| x.pid == pid && x.id == ITIMER_ID).map_or((0, 0), |x| read(x, now)))+}++/// setitimer(ITIMER_REAL, value, interval) (µs; value 0 disarms). Returns+/// the old (value, interval).+pub fn set_itimer(pid: u64, value: u64, interval: u64) -> (u64, u64) {+ let now = time::now_us();+ let old = with_table(|t| {+ let old = t.iter().position(|x| x.pid == pid && x.id == ITIMER_ID).map(|i| t.remove(i));+ if value > 0 {+ t.push(Timer {+ pid,+ id: ITIMER_ID,+ clock: CLOCK_MONOTONIC,+ next: Some(now + value),+ interval,+ signo: signal::SIGALRM,+ notify: SIGEV_SIGNAL,+ tid: 0,+ sigval: 0,+ overrun: 0,+ });+ }+ old.map_or((0, 0), |x| read(&x, now))+ });+ if value > 0 {+ poke();+ }+ old+}++// --- POSIX timers --------------------------------------------------------++/// What timer_create's `struct sigevent` asked for.+pub struct SigEvent {+ pub notify: i32,+ pub signo: u32,+ pub tid: u64,+ /// None: no sigevent was given, so the value is the timer's id.+ pub sigval: Option<u64>,+}++/// timer_create: a new, disarmed timer. Returns its id.+pub fn create(pid: u64, clock: u64, ev: SigEvent) -> Result<i32, Errno> {+ with_table(|t| {+ let ids: Vec<i32> = t.iter().filter(|x| x.pid == pid && x.id != ITIMER_ID).map(|x| x.id).collect();+ if ids.len() >= MAX_TIMERS {+ return Err(Errno::EAGAIN);+ }+ let id = (0..).find(|i| !ids.contains(i)).unwrap();+ // No sigevent: SIGALRM with the timer id as the value (Linux's default).+ let sigval = ev.sigval.unwrap_or(id as u64);+ t.push(Timer { pid, id, clock, next: None, interval: 0, signo: ev.signo, notify: ev.notify, tid: ev.tid, sigval, overrun: 0 });+ Ok(id)+ })+}++/// timer_settime: arm (value > 0) or disarm. `absolute`: value is a time on+/// the timer's clock, not a delay. Returns the old (value, interval).+pub fn settime(pid: u64, id: i32, absolute: bool, value: u64, interval: u64) -> Result<(u64, u64), Errno> {+ let now = time::now_us();+ let old = with_table(|t| {+ let timer = t.iter_mut().find(|x| x.pid == pid && x.id == id && id != ITIMER_ID).ok_or(Errno::EINVAL)?;+ let old = read(timer, now);+ timer.overrun = 0;+ timer.interval = interval;+ timer.next = match (value, absolute) {+ (0, _) => None,+ (v, false) => Some(now + v),+ (v, true) if timer.clock == CLOCK_REALTIME => Some(v.saturating_sub(realtime_offset()).max(1)),+ (v, true) => Some(v.max(1)), // MONOTONIC/BOOTTIME: our own clock+ };+ Ok(old)+ })?;+ if value > 0 {+ poke();+ }+ Ok(old)+}++/// timer_gettime: (time left, interval) in microseconds.+pub fn gettime(pid: u64, id: i32) -> Result<(u64, u64), Errno> {+ let now = time::now_us();+ with_table(|t| t.iter().find(|x| x.pid == pid && x.id == id && id != ITIMER_ID).map(|x| read(x, now)).ok_or(Errno::EINVAL))+}++/// timer_getoverrun: periods missed at the last expiry.+pub fn getoverrun(pid: u64, id: i32) -> Result<u64, Errno> {+ with_table(|t| t.iter().find(|x| x.pid == pid && x.id == id && id != ITIMER_ID).map(|x| x.overrun.min(i32::MAX as u64)).ok_or(Errno::EINVAL))+}++/// timer_delete.+pub fn delete(pid: u64, id: i32) -> Result<(), Errno> {+ with_table(|t| {+ let i = t.iter().position(|x| x.pid == pid && x.id == id && id != ITIMER_ID).ok_or(Errno::EINVAL)?;+ t.remove(i);+ Ok(())+ })+}++/// execve: POSIX timers go, the alarm stays.+pub fn exec(pid: u64) {+ with_table(|t| t.retain(|x| x.pid != pid || x.id == ITIMER_ID));+}++/// The process ended: all its timers go.+pub fn process_exited(pid: u64) {+ with_table(|t| t.retain(|x| x.pid != pid));+}++/// How many timers exist (tests: nothing left behind).+pub fn count() -> usize {+ with_table(|t| t.len())+}
@@ -0,0 +1,129 @@+//! Stage 17, "coreutils batch 2": links and symlinks, owners, statfs and+//! statx, sysinfo/times/getrusage and the CPU clocks, socket names and+//! options, alarm/setitimer/POSIX timers, user and group ids, priority,+//! the host name, flock, preadv/pwritev and sendfile. The detailed checks+//! live in user/coretest.rs (exit code = failed checks); this file runs+//! each group and checks nothing is left behind -- threads, processes,+//! memory frames, file locks (flock ones too) and timers.+#![no_std]+#![no_main]+#![feature(custom_test_frameworks)]+#![test_runner(rustbelt::test_runner)]+#![reexport_test_harness_main = "test_main"]++extern crate alloc;++use alloc::string::String;+use bootloader::{BootInfo, entry_point};+use core::panic::PanicInfo;+use rustbelt::process::{self, ExitStatus, SpawnArgs};+use rustbelt::{fs, memory, syscall, thread, timer};++entry_point!(main);++fn main(boot_info: &'static BootInfo) -> ! {+ rustbelt::init();+ rustbelt::init_memory(boot_info);+ rustbelt::init_smp(); // all 4 CPUs run threads+ fs::init();+ rustbelt::init_storage();+ rustbelt::init_net(); // 127.0.0.1 for the socket checks+ test_main();+ rustbelt::hlt_loop();+}++fn run(args: &[&str]) -> ExitStatus {+ let mut spawn = SpawnArgs::kernel("coretest");+ spawn.args.extend(args.iter().map(|a| String::from(*a)));+ let p = process::spawn_path("/mnt/disk0/bin/coretest", spawn).expect("spawn failed");+ process::wait(&p)+}++fn counts() -> (usize, usize, usize) {+ thread::sleep_ticks(3); // let exited threads' stacks be freed+ (thread::thread_count(), process::count(), memory::frames_in_use())+}++/// Run one group twice (the first warms caches), check it passes and+/// nothing is left behind.+fn run_without_leaks(group: &str) {+ assert_eq!(run(&[group]), ExitStatus::Exited(0), "coretest {} failed (warm-up)", group);+ let before = counts();+ assert_eq!(run(&[group]), ExitStatus::Exited(0), "coretest {} failed", group);+ assert_eq!(counts(), before, "coretest {} leaked something", group);+ assert_eq!(fs::lock::count(), 0, "coretest {} left file locks behind", group);+ assert_eq!(timer::count(), 0, "coretest {} left timers behind", group);+}++/// link/linkat, symlink/symlinkat on ramfs and ext2 (st_nlink, st_ino,+/// rename over a second link, directories moved); EPERM on FAT/exFAT,+/// EROFS on btrfs, EXDEV across file systems.+#[test_case]+fn links_and_symlinks() {+ run_without_leaks("links");+}++/// chown/fchown/lchown/fchownat: stored on ramfs and ext2; FAT says EPERM+/// unless nothing changes.+#[test_case]+fn owners() {+ run_without_leaks("owners");+}++/// statfs/fstatfs magic numbers and sizes on every file system, statx,+/// st_dev per mount.+#[test_case]+fn statfs_and_statx() {+ run_without_leaks("statfs");+}++/// sysinfo (99) next to our 502, times(), getrusage, CPU-time clocks,+/// clock_getres.+#[test_case]+fn sysinfo_times_and_cpu_clocks() {+ run_without_leaks("info");+}++/// getsockname/getpeername, SO_REUSEADDR & co., SO_TYPE/SO_ERROR,+/// shutdown(SHUT_WR) = end-of-file for the peer, socketpair says no.+#[test_case]+fn socket_names_options_and_shutdown() {+ run_without_leaks("sockets");+}++/// alarm, setitimer/getitimer (one-shot, periodic), POSIX timers+/// (SI_TIMER + si_value, TIMER_ABSTIME, SIGEV_NONE), gone with the process.+#[test_case]+fn alarms_and_timers() {+ run_without_leaks("timers");+}++/// setuid & co. (root drops privileges, the saved id), groups, files owned+/// by their creator, nice values, sethostname -> uname.+#[test_case]+fn ids_priority_and_hostname() {+ run_without_leaks("ids");+}++/// flock (per open file, blocking, released on the last close),+/// preadv/pwritev, sendfile, ppoll/pselect6 mask sizes and masks, and+/// getdents64 while deleting (rm -r).+#[test_case]+fn flock_vectors_sendfile_and_masks() {+ run_without_leaks("misc");+}++/// `--trace` knows the new calls by name.+#[test_case]+fn trace_names_new_syscalls() {+ assert_eq!(syscall::trace_name(86), "link");+ assert_eq!(syscall::trace_name(332), "statx");+ assert_eq!(syscall::trace_name(222), "timer_create");+ assert_eq!(syscall::trace_name(99), "sysinfo");+ assert_eq!(syscall::trace_name(502), "rb_sysinfo");+}++#[panic_handler]+fn panic(info: &PanicInfo) -> ! {+ rustbelt::test_panic_handler(info)+}
@@ -176,7 +176,7 @@ fn scripted_shell_session() { "piped text\n", "wc -l\n2\n", "saved\nmore\n",- "Rustbelt 0.16.0",+ "Rustbelt 0.17.0", "B:/ $ exit 3\n", ] { assert!(out.contains(expected), "missing {:?} in:\n{}", expected, out);
@@ -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