//! 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 | AF_INET: EOPNOTSUPP (AF_UNIX: net/unix.rs, st. 18) | //! | 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> { 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); 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 { 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 { 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 { 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 { let int = || -> Result { 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 { 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 = 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 { 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 { 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 { 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 { 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 { 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().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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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, fs::file::dev_major(st.st_rdev)).u32(132, fs::file::dev_minor(st.st_rdev)); 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { let now = crate::timer::gettime(current()?.pid, id as i32)?; put_itimerspec(ptr, now)?; Ok(0) } // --- 8. Ids, priority, host name ----------------------------------------- fn creds() -> Result { 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) -> Result { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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>, 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 { 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 { 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> = Mutex::new(None); static DOMAINNAME: Mutex> = 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>, ptr: u64, len: u64) -> Result { 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 { 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 { 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 { 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) }