#ifndef FROSTBYTE_ULIB_H #define FROSTBYTE_ULIB_H #include "syscall.h" #include "fs.h" #include "signalnum.h" #include "poll.h" typedef void (*SigHandler)(int signum); /* Real sigaction()'s sa_sigaction shape: signum (same as SigHandler's own argument, kept for parity -- see signalnum.h's SigInfo.siSignum comment), a pointer to the real SigInfo describing THIS delivery, and a context pointer this kernel always hands back as NULL (see signalnum.h's sigFlagSigInfo comment for why). Only ever meaningful for a handler installed through userSigActionInfo below, WITH sigFlagSigInfo actually set in its flags -- see that function's own doc comment. */ typedef void (*SigInfoHandler)(int signum, SigInfo* info, void* context); long userWrite(int fd, const char* buffer, unsigned long length); long userRead(int fd, char* buffer, unsigned long length); void userExit(int code); long userYield(void); long userGetpid(void); void* userSbrk(long increment); void userSleep(unsigned long milliseconds); /* Real alarm(2): schedules sigAlrm against the calling process `seconds` real seconds from now, replacing any alarm already pending; 0 cancels instead of scheduling. Returns the whole seconds remaining on whatever alarm was pending before this call (rounded up), or 0 if none was. See sched.h's threadSetAlarm for the full contract. */ unsigned long userAlarm(unsigned long seconds); /* Real poll(2): waits for any of `nfds` PollFd entries (poll.h) to become ready, filling each one's revents in place. timeoutMs < 0 waits forever, 0 reports current readiness without ever blocking, > 0 blocks at most that many milliseconds (rounded up to this kernel's ~10ms PIT tick granularity -- see sched.c's threadSetAlarm for the same rounding on a different timer). Returns the number of entries with a nonzero revents (0 on timeout, never negative except -1 for a bad nfds/pointer). */ long userPoll(PollFd* fds, unsigned long nfds, long timeoutMs); long userOpen(const char* path, unsigned int flags); long userClose(int fd); long userLseek(int fd, long offset, int whence); long userStat(const char* path, FbStat* out); long userGetdents(int fd, FbDirent* out, unsigned long length); long userMkdir(const char* path); long userUnlink(const char* path); /* Real rmdir(2): the counterpart userUnlink above refuses a directory (EISDIR); this refuses anything that is NOT an empty directory (ENOTDIR/ENOTEMPTY, both collapsed to -1 here, matching userUnlink's own "just -1" error shape). */ long userRmdir(const char* path); /* Real chmod(2): overwrites `path`'s whole owner-rwx mode (fs.h's fsModeOwner*, e.g. 0644) with `mode` -- no symbolic +x-style merge, matching real chmod's own numeric-mode form. 0 on success, -1 for a missing path or (tmpfs only) a node under a non-writable mount, same restriction userUnlink/userRmdir already have on the ustar-loaded backing store. Takes effect immediately for any FUTURE userOpen/ userSpawn/userExec on this path -- an fd already open, or a process already running, is never affected (real Unix only checks permission at open/exec time). */ long userChmod(const char* path, unsigned int mode); /* Real chown(2) (vfs.h's vfsChown): reassigns `path`'s owner uid outright. Root-only (this process's own userGetuid() must be 0), full stop, unlike userChmod -- real Unix's _POSIX_CHOWN_RESTRICTED default means not even a file's own owner may give it away. -1 for a missing path, a node under a non-writable mount, or a non-root caller; the target's uid is left completely unchanged on any failure. */ long userChown(const char* path, unsigned int uid); /* Real getuid(2)/setuid(2) (sched.h's Thread.uid, currentThreadUid/ currentThreadSetUid): userGetuid always succeeds, returning THIS process's own current uid. userSetuid enforces the real privilege rule itself (no additional check happens at the syscall layer) -- 0 ("root") may become any uid; any other uid may only pass its own current value back in (a no-op that still returns 0, matching real setuid(getuid())), and any other target returns -1 with the credential unchanged. There is no way back to uid 0 once dropped short of already being uid 0 at the time of the call -- a real, permanent privilege drop for the lifetime of this process, exactly like a daemon that setuid()s away from root once at startup. */ long userGetuid(void); long userSetuid(unsigned int uid); /* Real getgid(2)/setgid(2) (sched.h's Thread.gid, currentThreadGid/ currentThreadSetGid): userGetgid always succeeds, returning THIS process's own current gid. userSetgid's privilege gate is THIS process's own userGetuid() (== 0), not its gid -- matching real setgid(2)'s CAP_SETGID-shaped rule -- any non-root caller may only pass its own current gid back in (a no-op that still returns 0), any other target returns -1 unchanged. */ long userGetgid(void); long userSetgid(unsigned int gid); /* Real chgrp(2) (vfs.h's vfsChgrp): reassigns `path`'s group gid. Unlike userChown (root-only, full stop), the file's own owner may also call this -- but only to set the group to THIS process's own current gid (userGetgid()), never an arbitrary one, since this kernel tracks exactly one gid per process (no supplementary-groups list). Root may set any gid. -1 for a missing path, a node under a non-writable mount, a non-owner non-root caller, or a non-root caller naming any gid other than its own. */ long userChgrp(const char* path, unsigned int gid); /* Real chdir(2)/getcwd(2) (sched.h's Thread.cwd, vfs.h's vfsChdir/ vfsGetcwd): userChdir resolves a relative `path` against THIS process's own current cwd, requires the result to already be a real directory, and makes it this process's new cwd -- 0 on success, -1 (nothing changed) if it does not name a directory. A child spawned by userSpawn/userExec AFTER this call inherits the new value; this process's parent, and any already-spawned sibling, are never affected (each process's cwd is an independent copy, not shared state). userGetcwd copies the current value into `buffer` (nul-terminated) and returns its length, or -1 (buffer untouched) if it does not fit `cap` -- real getcwd(2)'s own ERANGE case. */ long userChdir(const char* path); long userGetcwd(char* buffer, unsigned long cap); /* Real getenv(3)/setenv(3) (sched.h's Thread.env, syscall.c's sysGetEnv/ sysSetEnv): userGetEnv copies THIS process's own value for `name` into `buffer` (nul-terminated) and returns its length, or -1 (buffer untouched) if `name` has no entry or the value does not fit `cap`. userSetEnv overwrites an existing entry with a matching `name`, or creates a new one, in THIS process's own table only -- 0 on success, -1 if the combined "NAME=VALUE" would not fit the kernel's fixed entry size or the table is already full. A child spawned by userSpawn/userExec AFTER a userSetEnv call inherits the new value (a real, independent copy, same as userChdir above); this process's parent, and any already-spawned sibling, are never affected. */ long userGetEnv(const char* name, char* buffer, unsigned long cap); long userSetEnv(const char* name, const char* value); /* Real unsetenv(3): removes THIS process's own entry named `name` from its table, if any, so a following userGetEnv on the same name returns -1 again -- 0 whether or not an entry actually existed (matching real unsetenv(3), a no-op success either way), never touches the parent's or any sibling's own table (same independent-copy shape as userSetEnv). */ long userUnsetEnv(const char* name); long userPipe(int fds[2]); /* Shared pipes: unlike userPipe (both ends always land in the caller's own fd table), userPipeCreate returns a raw handle number that means nothing until an fd table actually attaches to it (userPipeAttach) -- meant to be handed to a different, independently spawned process (e.g. as an argv string) so the two can share a pipe without needing fork(). Once attached, userRead/userWrite on that fd really block (park the whole process, not busy-retry) instead of returning -1 for "try again" the way an unshared userPipe end does. */ long userPipeCreate(void); long userPipeAttach(long handle, int wantWrite); long userSymlink(const char* target, const char* path); long userReadlink(const char* path, char* buffer, unsigned long cap); long userLink(const char* existingPath, const char* newPath); /* Named pipe: creates a fifo at `path` (see vfsMkfifo, vfs.h). Unlike userPipeCreate/userPipeAttach, which need a raw handle number passed to both ends (e.g. as an argv string, see fifomaster.c), any two processes that simply agree on a path can rendezvous by both calling userOpen on it -- oWriteOnly for a blocking write end, oReadOnly for a blocking read end. 0 on success, -1 if the path already exists or its parent is not a writable directory. */ long userMkfifo(const char* path); /* argv is a NULL-terminated array of C strings, exactly like execv(3)'s -- pass nullptr for "no arguments" (argc 0 on the other side). */ long userExec(const char* path, char* const argv[]); long userSpawn(const char* path, char* const argv[]); long userWait(long pid); /* waitpid(-1)-style "wait for ANY child, not one specific pid": reaps whichever child of the caller happens to already be dead (or blocks, the same real blocking semantics as userWait(pid), until one dies) and returns its exit code. Writes the reaped child's own pid to *outPid (ignored if NULL). Returns -1 immediately, without blocking, if the caller currently has no children at all, dead or alive -- the real waitpid(-1)'s ECHILD case. See user/waitanytest.c for a live demo. */ long userWaitAny(long* outPid); /* Unconditionally terminates another process by pid -- the only way to recover a process genuinely stuck forever (e.g. blocked in userRead on a shared pipe whose writer never shows up, see userPipeAttach). 0 on success, -1 for an invalid pid, an already-dead one, or your own. The killed process gets no chance to run any cleanup -- its parent's userWait still reports its exit status, which will be exactly 9. Unlike userSigSend below, this can never be caught, blocked, or ignored -- internally it goes straight to threadKill (sched.c), the same as userSigSend(pid, sigKill) does (signal.h). Real kill(2)'s own pid<=0 convention (sched.h's Thread.pgid) also applies here: pid==0 kills every OTHER member of the caller's own process group (never the caller itself -- a Running thread is never one of threadKill's accepted states, the same reason plain self-targeting already does nothing special); pid<0 kills every member of group -pid. 0 is still returned for success as long as AT LEAST ONE target thread was actually killed (real kill(2)'s own ESRCH-if-nobody-matched semantics for the group form). */ long userKill(long pid); /* Real, catchable signal delivery (signal.h) -- see that header for the full design (a shared trampoline page, delivery on return-to-ring3, sysSigReturn to resume exactly where a handler interrupted). */ /* Raises signal `signum` (signal.h's sigHup..sigTstp) against `pid`. 0 on success, -1 for a bad signum or an invalid/dead/unused target. sigKill is delegated straight to the same unconditional path userKill uses -- it is never caught, blocked, or ignored, matching real kill(2). Any other signal is only actually delivered once the target next returns to user code on its own (this kernel does not wake a sleeping/blocked target early -- see signal.h's file comment). Same pid<=0 group convention as userKill above: pid==0 targets the caller's own process group (INCLUDING itself, unlike userKill's group form -- sigRaiseInfo has no "never self" exclusion the way threadKill does), pid<0 targets group -pid. See sched.h's Thread.pgid and signal.h's sigRaiseGroup for the full design, and user/pgroupfg.c/ user/pgroupbg.c for a live demo of the SAME scoping rule applied by keyboard.c's own real Ctrl-C. */ long userSigSend(long pid, int signum); void print(const char* text); void printDec(long value); unsigned long stringLength(const char* text); #endif