SchOS/include/user.h
Nexoniarz a053a96add signal: the return trampoline was Linux-only, and a test that proves the rest
Went looking for why `X=$(echo hi)` hangs every shell. Did not find it. Found
two other things, and built the instrument that rules out the largest suspect.

**The signal return trampoline was ABI-blind.** deliver_signals() manufactures
a return address for the handler -- seven bytes on the user stack reading
`mov eax, 15 ; syscall`. Fifteen is *Linux's* rt_sigreturn, which is right for
anything musl-linked, and that is every port and every shell. For a first-party
binary it is native syscall 15, which is `open`. So a handler in any cmd/
program ran, returned into the trampoline, called open() with whatever the
handler had left in its registers, got -ENOENT, and carried on executing the
stack bytes after the trampoline.

    pid 14 sigtest: trap 14 err 4 rip 0x46ca7 addr 0xfffffffffffffffe
      rax=fffffffffffffffe ... rsp=46ca0
      ustack: 46e30 46ca0 50f0000000fb8 0 0 0

rip is rsp+7 -- one past a seven-byte trampoline -- and the third word of the
stack is the trampoline's own bytes. It emits the number for the process's ABI
now; syscall_map is the thing that says which numbering this process speaks.

**SIG_BLOCK, SIG_UNBLOCK and SIG_SETMASK were not defined anywhere.**
sys_sigprocmask branched on bare 0, 1 and 2 with the names in comments, so
anything outside the kernel had to spell the number and hope it had the right
one. Defined in posix.h, used in the kernel.

**cmd/sigtest** asserts the SIGCHLD/sigsuspend contract every shell is built
on, and it is written so a permissive stub cannot pass: a blocked SIGCHLD stays
pending, sigsuspend delivers an *already pending* signal without spinning, the
handler runs exactly once, the mask is restored, the child is still reapable,
and a signal arriving mid-wait also wakes it. Eight checks, all green. The
kernel's half of that contract is now proven rather than assumed, which is what
lets the remaining bug be pursued somewhere else.

Along with it, `rt_sigsuspend` in lib/usys.S -- the syscall existed and the
first-party library simply never exposed it.

On the hang itself: it is not job control, not SMP, not zsh (mksh does it too),
and not any value the kernel returns -- a full syscall trace is in
docs/history/kernel.md section 7, which also records what the contradiction
narrows to. That section had said "root cause not yet isolated" with three
suspects; all three are now eliminated, and the note says what to do next
instead of what to guess.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 23:56:34 +02:00

164 lines
No EOL
5.6 KiB
C

#pragma once
#ifndef USER_H
#define USER_H
#include "types.h"
#include "date.h"
struct stat;
struct rtcdate;
struct mouse_data;
// --- system calls ---
int fork(void);
void exit(void) __attribute__((noreturn));
int wait(void);
int pipe(int*);
int write(int, void*, int);
int read(int, void*, int);
int close(int);
int kill(int);
int exec(char*, char**); /* execve(path, argv, 0) */
int execve(char*, char**, char**);
int open(char*, int);
int mknod(char*, short, short);
int unlink(char*);
int fstat(int fd, struct stat*);
int link(char*, char*);
int mkdir(char*);
int chdir(char*);
int dup(int);
int dup2(int, int);
int fcntl(int, int, int);
long lseek(int, long, int);
int getpid(void);
char* sbrk(uint64);
int sleep(int);
int uptime(void);
int vga_sync(void*); // legacy no-op syscall; the name is ABI, not this layer
int getmouse(struct mouse_data*);
int reboot(void);
int shutdown(void);
int sigfillset(sigset_t *set);
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
int sigwait(const sigset_t *set, int *sig);
int alarm(int seconds);
int waitpid(int pid, int *status, int options);
int setsid(void);
void* mmap(void*, uint64, int, int, int, uint64);
// Shared memory. shmat returns the attach address, or a small negative value
// on failure — there is no errno in this runtime, so check for < 0.
int shmget(int key, uint64 size, int flags);
void* shmat(int id, void *addr, int flags);
int shmdt(void *addr);
int munmap(void*, uint64);
int delete(char*);
int show(char*);
int showstat(char*, struct stat*);
int ftruncate(int, int);
int truncate(char*, int);
int rename(const char*, const char*);
int symlink(char*, char*);
int readlink(char*, char*, int);
int getuid(void);
int geteuid(void);
int nanosleep(void*);
int mprotect(void*, uint64, int);
int ioctl(int, uint64, void*);
// Declared at file scope, not inside the prototype: struct pollfd lives in
// posix.h, and a tag first seen in a parameter list is scoped to that list --
// so the prototype would describe a different, incompatible type.
struct pollfd;
int poll(struct pollfd*, int, int);
// Attributes on a file, and the index over them. Only SchFS carries these;
// anything else answers EOPNOTSUPP.
//
// schfs_query returns how many object ids it wrote, or -1 if the attribute has
// no index -- it will not fall back to walking the filesystem, because a query
// that silently becomes a full scan is worse than one that says it cannot help.
int setxattr(const char *path, const char *name, const void *val, int size,
int flags);
int getxattr(const char *path, const char *name, void *val, int size);
int removexattr(const char *path, const char *name);
int schfs_index(const char *name);
int schfs_query(const char *name, const char *value,
unsigned long long *out, int cap);
char* getcwd(char*, int);
int arch_prctl(int, uint64);
int set_tid_address(int*);
int set_robust_list(void*, int);
int futex(int*, int, int, void*, int*, int);
int clone(uint64, void*, void*, void*, void*);
void exit_group(int) __attribute__((noreturn));
int wait4(int, int*, int, void*);
int getgid(void);
int getegid(void);
int umask(int);
int writev(int, void*, int);
int readv(int, void*, int);
void* brk(void*);
int prlimit(int, int, void*, void*);
int lstat(const char*, struct stat*);
int access(const char*, int);
int openat(int, const char*, int, ...);
int fchdir(int);
int enable_linux_abi(void);
int getrlimit(int, void*);
int setuid(int);
int setgid(int);
int getsid(int);
int dup3(int, int, int);
int pipe2(int*, int);
int clock_gettime(int, void*);
int getppid(void);
int rt_sigaction(int, void*, void*, uint64);
// The syscall, not glibc's wrapper: the argument is a pointer to one 64-bit
// sigset with signal N at bit N-1, which is Linux's layout and not the
// kernel's internal one. Returns -EINTR always, as sigsuspend must.
int rt_sigsuspend(const uint64 *mask);
int mount(const char *src, const char *tgt, const char *fstype, uint64 flags, void *data);
// Directory reading. The kernel has had getdents64 since the Linux ABI landed,
// but nothing declared it here, so first-party programs had no way to list a
// directory at all -- which is what schinit needs to find its job files.
//
// Records are packed: step by d_reclen, never by sizeof, because d_name is a
// flexible array and each record is only as long as its name needs.
struct dirent64 {
uint64 d_ino;
int64 d_off;
ushort d_reclen;
uchar d_type;
char d_name[];
};
#define DT_DIR 4
#define DT_REG 8
int getdents64(int fd, void *buf, int count);
// Sockets. `struct sockaddr` and the AF_*/SOCK_* constants are in socket.h;
// include it as well as this header to use them.
struct sockaddr;
int socket(int domain, int type, int protocol);
int bind(int fd, const struct sockaddr *addr, int addrlen);
int listen(int fd, int backlog);
int accept(int fd, struct sockaddr *addr, int *addrlen);
int accept4(int fd, struct sockaddr *addr, int *addrlen, int flags);
int connect(int fd, const struct sockaddr *addr, int addrlen);
int socketpair(int domain, int type, int protocol, int sv[2]);
int sendto(int fd, const void *buf, int len, int flags, const struct sockaddr*, int);
int recvfrom(int fd, void *buf, int len, int flags, struct sockaddr*, int*);
int sockshutdown(int fd, int how);
int setsockopt(int fd, int level, int opt, const void *val, int len);
int getsockopt(int fd, int level, int opt, void *val, int *len);
// The only calls that can move a descriptor between processes (SCM_RIGHTS).
struct msghdr;
int sendmsg(int fd, const struct msghdr *msg, int flags);
int recvmsg(int fd, struct msghdr *msg, int flags);
// --- ulib.c ---
int stat(const char*, struct stat*);
char* gets(char*, int max);
#endif // USER_H