feat: extend ulib

This commit is contained in:
dualfroz committed 2026-09-09 20:01:46 +02:00
1 parent 2ee63600a4
commit b3b01cc38f
1 file changed
+202
+202
View File
@@ -3,6 +3,19 @@
#include "syscall.h" #include "syscall.h"
#include "fs.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 userWrite(int fd, const char* buffer, unsigned long length);
long userRead(int fd, char* buffer, unsigned long length); long userRead(int fd, char* buffer, unsigned long length);
@@ -12,6 +25,22 @@ long userGetpid(void);
void* userSbrk(long increment); void* userSbrk(long increment);
void userSleep(unsigned long milliseconds); 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 userOpen(const char* path, unsigned int flags);
long userClose(int fd); long userClose(int fd);
long userLseek(int fd, long offset, int whence); long userLseek(int fd, long offset, int whence);
@@ -20,6 +49,179 @@ long userGetdents(int fd, FbDirent* out, unsigned long length);
long userMkdir(const char* path); long userMkdir(const char* path);
long userUnlink(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 print(const char* text);
void printDec(long value); void printDec(long value);
unsigned long stringLength(const char* text); unsigned long stringLength(const char* text);