Porting zsh found four defects in the kernel and one in the filesystem. None of them were about zsh; all of them had been there the whole time. **exec threw the environment away.** kernel/fs/exec.c wrote a single zero for envp with the comment "(no environment)" -- not yet, but ever, for every process. musl has been passing a perfectly good envp to execve (Linux 59, which SchOS answers) and it was never read. So $TERM was unset, $PATH was whatever each program invented, and a variable exported in a shell did not survive the exec meant to carry it. exec() takes an envp, sys_exec reads argument three, and schinit manifests grew an Environment key. The userland two-argument exec(path, argv) stays as a wrapper that passes null explicitly -- the raw stub is execve now, so a short caller cannot leave %rdx holding a stray pointer. **Which overflowed the kernel stack.** Three pointer arrays, ~1.8 KB, on a 4096-byte stack, under exec -> loaduvm -> readi -> SchFS -> btree. Pid 1 went down on the first exec. The canary caught it and named the process, which is what it is for. They live in one kalloc'd page now. **waitpid returned a bare -1** for both "no children" and "interrupted". musl reads [-4095,-1] as -errno, so userspace saw EPERM; zsh printed "wait failed: operation not permitted" and abandoned the rest of /etc/zsh/zshrc. -ECHILD and -EINTR now. Third time a bare -1 has cost real time -- on a system with no permission model, EPERM should be read as "a syscall returned -1". **NVMA was 64.** musl's allocator takes a mapping per size class and does not release them eagerly, so compinit ran out of slots and got -1, which is EPERM, which malloc turns into NULL, which zsh reports as "fatal error: out of memory" -- on a machine with 1000 MB free. 256 slots, and -ENOMEM. **And a small write to SchFS costs 58 ms.** 200 writes to one open file take 11.75 s; the same to /dev/null take 0.14 s. That is the design stated at the top of kmount.c -- one transaction group per operation, and a commit is two flushes and an uberblock -- not a bug. But compdump writes its digest with thousands of separate prints, so it takes minutes and looks like a hang. compinit -D, which skips the dump, finishes in 3.6 s. This one is a decision rather than a fix, so it is written up and left: /etc/zsh/zshrc keeps compinit switched off and explains exactly why, with the measurements. zsh runs as /bin/zsh and is the console shell. ZLE works -- arrow keys recall history through terminfo, $COLUMNS reads the real 160 because TIOCGWINSZ was fixed, colour and prompt expansion work, startup is 0.14 s. mksh keeps /bin/sh, so schinit's no-jobs fallback is a different program from the one that could have failed. Also fixed while here: AT_CLKTCK was the literal 100 and is USER_HZ; the extract rule in four ports could move a source tree inside itself when make re-ran it, which is how zsh's configure came to write modfile=zsh-5.9/... into config.modules and fail three steps later. schfs-check passes. Full writeup in docs/history/kernel.md section 14. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
151 lines
No EOL
4.9 KiB
C
151 lines
No EOL
4.9 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*);
|
|
|
|
// 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);
|
|
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);
|
|
|
|
// --- ulib.c ---
|
|
int stat(const char*, struct stat*);
|
|
char* gets(char*, int max);
|
|
|
|
#endif // USER_H
|