The inode table was NINODE (200) entries, searched end to end on every
iget(), and a panic -- "iget: no inodes" -- when all of them were held.
Held means open, a cwd, a running binary or a directory mid-walk, so a
few busy processes reached it. iget() now finds an inode through a hash
on (dev, inum), takes a free one from a list, and makes more from a
slab cache once the NINODE in the image are all in use.
The open-file table had the same shape, NFILE (512) and then ENFILE,
and gets the same treatment: a free list, and the slab past NFILE.
/proc/fsstat's peaks now read against how many exist.
getrlimit(RLIMIT_NOFILE) said unlimited, which musl turns into
sysconf(_SC_OPEN_MAX) == -1, after which zsh believed it had no
descriptors and `exec {fd}<file` failed on the first one. It reports
NOFILE now. And a comment in sys_fs.c held a "*/" that ended it early.
Checked under QEMU: six processes holding 115 files each, 713 open
descriptions and 341 inodes at once, all back to the baseline after.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
239 lines
6.6 KiB
C
239 lines
6.6 KiB
C
// POSIX / Linux-ABI compatibility syscalls.
|
|
// These are stubs or thin wrappers that make musl and ksh happy
|
|
// without implementing full semantics.
|
|
|
|
#include "types.h"
|
|
#include "defs.h"
|
|
#include "param.h"
|
|
#include "memlayout.h"
|
|
#include "mmu.h"
|
|
#include "traps.h"
|
|
#include "proc.h"
|
|
#include "x86.h"
|
|
#include "syscall.h"
|
|
#include "posix.h"
|
|
#include "uaccess.h"
|
|
#include "errno.h"
|
|
#include "klog.h" /* KLOG_BYTES, for syslog(2) action 10 */
|
|
|
|
extern int linux_syscall_map[];
|
|
extern int linux_syscall_map_size;
|
|
|
|
// enable_linux_abi: switch this process to Linux syscall numbering.
|
|
addr_t
|
|
sys_enable_linux_abi(void)
|
|
{
|
|
myproc()->syscall_map = linux_syscall_map;
|
|
myproc()->map_size = linux_syscall_map_size;
|
|
return 0;
|
|
}
|
|
|
|
// set_tid_address: musl calls this at startup to register its TID location.
|
|
addr_t
|
|
sys_set_tid_address(void)
|
|
{
|
|
addr_t tidp;
|
|
argaddr(0, &tidp);
|
|
if (tidp)
|
|
put_user(myproc()->pid, tidp);
|
|
return myproc()->pid;
|
|
}
|
|
|
|
// set_robust_list: robust mutex cleanup on exit — stub.
|
|
addr_t sys_set_robust_list(void) { return 0; }
|
|
|
|
// futex: used by musl for single-threaded lock primitives.
|
|
addr_t
|
|
sys_futex(void)
|
|
{
|
|
int op;
|
|
argint(1, &op);
|
|
op &= ~(FUTEX_WAIT_PRIVATE - FUTEX_WAIT);
|
|
if (op == FUTEX_WAKE || op == FUTEX_WAKE_PRIVATE)
|
|
return 0;
|
|
return -EAGAIN; // no blocking, musl retries
|
|
}
|
|
|
|
// arch_prctl: save FS_BASE for TLS.
|
|
addr_t
|
|
sys_arch_prctl(void)
|
|
{
|
|
int code;
|
|
addr_t addr;
|
|
argint(0, &code);
|
|
argaddr(1, &addr);
|
|
if (code == ARCH_SET_FS) {
|
|
myproc()->fs_base = addr;
|
|
return 0;
|
|
}
|
|
if (code == ARCH_GET_FS) {
|
|
if (put_user(myproc()->fs_base, addr) < 0)
|
|
return -EFAULT;
|
|
return 0;
|
|
}
|
|
return -EINVAL;
|
|
}
|
|
|
|
// Limits are reported, not enforced: everything is unlimited except what the
|
|
// kernel actually has a ceiling for. RLIMIT_NOFILE was "unlimited" too, and
|
|
// musl turns that into sysconf(_SC_OPEN_MAX) == -1 -- after which zsh decided
|
|
// it had no descriptors at all, and `exec {fd}<file` failed on the first try.
|
|
#define RLIMIT_NOFILE_ 7
|
|
|
|
static void
|
|
rlimit_of(int resource, struct rlimit *r)
|
|
{
|
|
r->rlim_cur = r->rlim_max = (uint64)-1;
|
|
if (resource == RLIMIT_NOFILE_)
|
|
r->rlim_cur = r->rlim_max = NOFILE;
|
|
}
|
|
|
|
addr_t
|
|
sys_getrlimit(void)
|
|
{
|
|
int resource;
|
|
struct rlimit *rlp;
|
|
argint(0, &resource);
|
|
if (argptr(1, (char**)&rlp, sizeof(*rlp)) < 0)
|
|
return -1;
|
|
rlimit_of(resource, rlp);
|
|
return 0;
|
|
}
|
|
|
|
addr_t
|
|
sys_prlimit(void)
|
|
{
|
|
int resource;
|
|
struct rlimit *oldrlp;
|
|
argint(1, &resource);
|
|
if (argptr(3, (char**)&oldrlp, sizeof(*oldrlp)) == 0 && oldrlp)
|
|
rlimit_of(resource, oldrlp);
|
|
return 0;
|
|
}
|
|
|
|
// uname: return fake but plausible kernel/host info
|
|
addr_t
|
|
sys_uname(void)
|
|
{
|
|
struct utsname {
|
|
char sysname[65];
|
|
char nodename[65];
|
|
char release[65];
|
|
char version[65];
|
|
char machine[65];
|
|
char domainname[65];
|
|
};
|
|
addr_t ubuf;
|
|
argaddr(0, &ubuf);
|
|
struct utsname *u = (struct utsname*)user_ptr(ubuf, sizeof(*u));
|
|
if (!u) return -EFAULT;
|
|
memset(u, 0, sizeof(*u));
|
|
memmove(u->sysname, "SchIFT", 7);
|
|
memmove(u->nodename, "schos", 6);
|
|
memmove(u->release, "0.1.0", 6);
|
|
memmove(u->version, "#1", 3);
|
|
memmove(u->machine, "x86_64", 7);
|
|
memmove(u->domainname, "(none)", 7);
|
|
return 0;
|
|
}
|
|
|
|
// socket/bind/listen/accept/connect live in sys_net.c now that they do
|
|
// something.
|
|
|
|
// syslog(2) -- what `dmesg` actually calls.
|
|
//
|
|
// This returned 0 for every action, and 0 to action 10 means "the log buffer
|
|
// is zero bytes". So `dmesg` printed nothing, on a system whose boot banner
|
|
// says *"or dmesg once you are in"* and whose entire quiet-boot design is
|
|
// "put the detail in the kernel log and tell them how to read it". The detail
|
|
// was there the whole time -- `cat /proc/kmsg` has always worked -- but the
|
|
// one command anybody would reach for could not see it.
|
|
//
|
|
// dmesg does not read /proc/kmsg. It calls klogctl(10) for the buffer size and
|
|
// klogctl(3) to read, which is this syscall, and a stub that answers 0 is
|
|
// indistinguishable from a machine that logged nothing.
|
|
//
|
|
// Only the actions that mean something here are implemented. The rest say so
|
|
// rather than returning 0: a console log level this kernel does not have is
|
|
// not a request that succeeded.
|
|
#define SYSLOG_CLOSE 0
|
|
#define SYSLOG_OPEN 1
|
|
#define SYSLOG_READ 2
|
|
#define SYSLOG_READ_ALL 3
|
|
#define SYSLOG_CLEAR 5
|
|
#define SYSLOG_SIZE_UNREAD 9
|
|
#define SYSLOG_SIZE_BUFFER 10
|
|
|
|
addr_t sys_syslog(void)
|
|
{
|
|
int type, len;
|
|
addr_t bufaddr;
|
|
|
|
argint(0, &type);
|
|
argaddr(1, &bufaddr);
|
|
argint(2, &len);
|
|
|
|
switch (type) {
|
|
case SYSLOG_CLOSE:
|
|
case SYSLOG_OPEN:
|
|
return 0; // genuinely nothing to do, as on Linux
|
|
|
|
case SYSLOG_SIZE_BUFFER:
|
|
return KLOG_BYTES;
|
|
|
|
case SYSLOG_SIZE_UNREAD:
|
|
return (addr_t)klog_len();
|
|
|
|
case SYSLOG_READ_ALL: {
|
|
char *dst;
|
|
|
|
if (len < 0)
|
|
return -EINVAL;
|
|
if (len == 0 || klog_len() == 0)
|
|
return 0;
|
|
if (argptr(1, &dst, len) < 0)
|
|
return -EFAULT;
|
|
return (addr_t)klog_render_syslog(dst, len);
|
|
}
|
|
|
|
// Reading destructively needs a per-reader cursor the ring does not carry,
|
|
// and clearing needs the ring to forget -- which is the one thing it exists
|
|
// not to do, since its whole purpose is surviving the kernel that wrote it.
|
|
case SYSLOG_READ:
|
|
case SYSLOG_CLEAR:
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
// getrandom: fill buffer with pseudo-random bytes.
|
|
addr_t sys_getrandom(void)
|
|
{
|
|
addr_t bufaddr;
|
|
int len, flags;
|
|
argaddr(0, &bufaddr); argint(1, &len); argint(2, &flags);
|
|
if (len <= 0) return 0;
|
|
uint8 *buf = (uint8*)user_ptr(bufaddr, (uint64)len);
|
|
if (!buf) return -EFAULT;
|
|
// LCG seeded from proc pointer + pid; no lock needed (read-only fields).
|
|
uint rseed = (uint)(uintptr_t)myproc() ^ (uint)myproc()->pid;
|
|
for (int i = 0; i < len; i++) {
|
|
rseed = rseed * 1664525u + 1013904223u;
|
|
buf[i] = (uint8)(rseed >> 16);
|
|
}
|
|
return len;
|
|
}
|
|
|
|
// pselect6 / ppoll live in sys_poll.c alongside select/poll.
|
|
|
|
// epoll: not implemented.
|
|
addr_t sys_epoll_create1(void) { return -ENOSYS; }
|
|
addr_t sys_epoll_ctl(void) { return -ENOSYS; }
|
|
addr_t sys_epoll_wait(void) { return -ENOSYS; }
|
|
|
|
// signalfd / eventfd / timerfd: not implemented.
|
|
addr_t sys_signalfd4(void) { return -ENOSYS; }
|
|
addr_t sys_eventfd2(void) { return -ENOSYS; }
|
|
addr_t sys_timerfd_create(void) { return -ENOSYS; }
|
|
addr_t sys_timerfd_settime(void){ return -ENOSYS; }
|
|
addr_t sys_timerfd_gettime(void){ return -ENOSYS; }
|