SchOS/kernel/sys/sys_compat.c
Nexoniarz a1c965da1e include: one folder per subsystem, and includes that name it
Sixty-five headers sat side by side in include/, so `#include "irq.h"`
said nothing about where irq.h was or what it belonged to, and errno.h,
stat.h and fcntl.h at the top shadowed the system's for any host tool
built with -I./include. They are in folders now -- arch/ core/ fs/
dev/ gfx/ net/ uapi/ (and ldls/ for the Linux driver layer) -- and
every include names the folder, as Linux's do: "core/proc.h".

Done mechanically and checked the hard way: the kernel built from this
tree disassembles identically to the one built from the tree before
it. The full image boots, and userland is the same set of programs.
Paths in docs and comments follow; docs/guides/layout.md has a table
of what is where, and tools/fix-includes.py rewrites old-style
includes from the folders in the tree, for code written before this.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
2026-10-23 15:05:12 +02:00

239 lines
6.7 KiB
C

// POSIX / Linux-ABI compatibility syscalls.
// These are stubs or thin wrappers that make musl and ksh happy
// without implementing full semantics.
#include "core/types.h"
#include "core/defs.h"
#include "core/param.h"
#include "arch/memlayout.h"
#include "arch/mmu.h"
#include "arch/traps.h"
#include "core/proc.h"
#include "arch/x86.h"
#include "core/syscall.h"
#include "uapi/posix.h"
#include "core/uaccess.h"
#include "uapi/errno.h"
#include "core/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; }