feat: expand ulib

This commit is contained in:
dualfroz committed 2026-09-09 20:02:02 +02:00
1 parent b3b01cc38f
commit baece1a2b0
1 file changed
+203
+203
View File
@@ -222,8 +222,211 @@ long userKill(long pid);
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);
/* Real pidfd_open(2): opens a handle on process `pid` that stays correct
even if `pid`'s own numeric slot is later reaped and handed to a
completely different, unrelated process (pidfd.h/sched.h's
Thread.generation) -- unlike userKill/userSigSend/userWait above, which
all still take a raw pid the exact same way real kill(2)/waitpid(2) do
(and so share their exact same well-known pid-reuse race, deliberately
left unfixed for them, see sched.c's allocSlot doc comment). Returns a
small non-negative fd on success (closed with the ordinary userClose),
or -1 for pid 0, an out-of-range pid, an already-dead-and-reaped or
never-existed pid, or a full pidfd table. Opening a pidfd does NOT keep
the target alive in any way -- it can still exit and be reaped by
anyone else in the meantime, exactly like a raw pid could; the whole
point of this handle is only ever noticing that when it happens, instead
of silently acting on whoever now sits at that same number. */
long userPidfdOpen(long pid);
/* Sends signal `signum` to whichever process `fd` (from userPidfdOpen)
still safely refers to. 0 on success, -1 for a bad signum, an `fd` that
is not an open pidfd at all, or one whose original target has since
been reaped or recycled -- the failure userSigSend(cachedPid, signum)
can never distinguish from "the signal was delivered to a live but
completely unrelated process that happens to share that old pid now".
Otherwise identical semantics to userSigSend's own single-target path
(sigKill still unconditional and uncatchable, signum still delivered on
the target's own next return to user code). */
long userPidfdSend(long fd, int signum);
/* Real (partial) waitid(2)-over-a-pidfd: blocks until the process `fd`
still safely refers to actually exits, then reaps it and returns its
exit code -- the same real blocking semantics as userWait(pid), except
-1 also covers "the original target was reaped by someone else while
this call was waiting", which userWait(pid) has no way to report (it
would simply keep waiting on whichever unrelated thread now occupies
that pid instead). See user/pidfdtest.c for a live ring-3 demonstration
of a pidfd correctly reporting a just-reaped target as gone through the
real syscall path (not just the in-kernel unit tests), and
schedTest.c's pidfd tests for a deterministic, create/discard/recreate
reproduction of the full scenario -- a stale handle whose slot has
since been handed to a brand new, unrelated, live thread -- that a live
boot cannot reliably force on demand (allocSlot's own round-robin
headroom makes hitting one specific freed slot again take dozens-to-
hundreds of unrelated allocations first, see its doc comment, sched.c). */
long userPidfdWait(long fd);
/* Real mmap(2): addrHint of 0 asks the kernel to pick the address itself
(mmap.c's mmapReserve, first-fit inside this process's own mmap region);
a nonzero addrHint asks for that EXACT address, refused (not silently
relocated) if it does not fit -- there is no MAP_FIXED bit here, every
addrHint already behaves like real mmap's own weaker "hint" narrowed
down to "exact or refused". prot is fs.h's mmapProtRead/Write/Exec
(mmapProtExec IS hardware-enforced -- executing a mapping that never
asked for it takes a real SIGSEGV -- see fs.h's own doc comment for the
exact scope, including the CPU-support gate and the same enforcement
applied to a binary's own ELF image and its stack/brk); flags is fs.h's
mmapAnonymous or 0. When mmapAnonymous is NOT
set, `fd` must already be open on a real seekable file (fdFile/fdSfs/
fdBlockDev -- the same three kinds userLseek's own vfsLseek already
restricts to) and `offset` must be frameSize-aligned; both are ignored
for an anonymous mapping. No physical memory is actually touched until
the first real access to a given page -- see mmap.h's own file comment
for the full demand-paging contract, including why closing `fd` before
every page has been faulted in leaves the rest of this mapping unable
to fill (an honestly scoped limitation, not a hidden bug). Returns the
mapped base address, or (void*)-1 (compare against userMmapFailed) on
any failure: bad alignment, a range outside this process's own mmap
region, overlap with an existing mapping, a bad/wrong-kind fd for a
file-backed mapping, or no mapping slot/free region left. */
void* userMmap(void* addrHint, unsigned long length, int prot, int flags, int fd, long offset);
/* Sentinel userMmap returns on failure, real MAP_FAILED's own value --
never a real mapped address, since every real one falls inside this
process's own bounded 256 MiB elfSlotSize slot (elf.h), nowhere near
the top of the address space. */
#define userMmapFailed ((void*)-1)
/* Real munmap(2): see mmap.h's own mmapRelease for the full contract
(partial unmap shrinks the mapping in place, a range entirely inside
one mapping's interior splits it, already-unmapped memory is a no-op).
0 on success, -1 for a misaligned addr, a zero length, or (a real,
if unlikely, resource failure) no free mapping slot left to hold a
split's own second half. */
long userMunmap(void* addr, unsigned long length);
/* Installs `handler` as the calling process's own disposition for
`signum` (nullptr/sigHandlerDefault restores the default action,
sigHandlerIgnore silently discards it), `mask` as its sa_mask -- the
additional signals blocked for as long as THIS handler is running (on top
of whatever was already blocked and signum's own bit, always self-blocked
like real Unix's implicit default) -- and `flags` as its sa_flags
(signalnum.h's sigFlagRestart, or 0). A signal NOT covered by `mask` can
still interrupt this handler with a genuinely nested one of its own
(see signal.h's file comment) rather than waiting for it to return.
Without sigFlagRestart, a signal that interrupts THIS process blocked in
userRead/userWrite/userWait makes that call return errInterrupted once
the handler returns instead of transparently resuming it (real EINTR);
sigFlagRestart opts back into the transparent-resume behavior every
handler had before this flag existed. Returns the PREVIOUS disposition,
or -1 for a bad signum or an attempt to touch sigKill (uncatchable, like
real EINVAL on SIGKILL). `handler` is called with the signal number as
its only argument; it must return normally (not userExit, not
longjmp-style tricks this kernel does not support) for execution to
resume exactly where the signal interrupted. */
long userSigAction(int signum, SigHandler handler, unsigned long mask, unsigned long flags);
/* Identical wire format to userSigAction (same syscall, same signum/mask
validation, same PREVIOUS-disposition return value) -- the only
difference is `handler`'s real C type, SigInfoHandler instead of
SigHandler, so a caller does not need its own cast at the call site. The
kernel itself does not distinguish the two function-pointer shapes at
all (it only ever stores/calls a raw address, see signal.c's
sigConsumePending): what actually determines whether the handler is
CALLED with one argument or three is whether `flags` includes
sigFlagSigInfo (signalnum.h) -- the caller must include it here
deliberately, exactly like real sigaction()'s SA_SIGINFO. Passing a
SigInfoHandler through this function WITHOUT that flag set (or, equally,
passing a plain SigHandler through this function WITH it set) is a
caller bug this kernel does not protect against, mirroring real C's own
union-based sa_handler/sa_sigaction footgun -- the handler would run
with whatever raw rsi/rdx happened to hold, not a valid SigInfo
pointer. */
long userSigActionInfo(int signum, SigInfoHandler handler, unsigned long mask, unsigned long flags);
/* Blocks (sigMaskBlock), unblocks (sigMaskUnblock), or replaces
(sigMaskSetMask) the calling process's blocked-signal mask, returning
the mask as it was immediately before this call. A blocked signal still
gets raised (sigPending) but is not delivered until unblocked. */
unsigned long userSigProcMask(int how, unsigned long mask);
/* Real setpgid(2): moves process `pid` (0 means the caller itself) into
process group `pgid` (0 means "become a new group leader, using pid's
own resolved id as the group id" -- the same "0 means self/derive from
self" shape sysSetFgPgrp does NOT have, since a foreground group is
named directly, never derived). 0 on success, -1 for a pid that does not
name any currently live thread (sched.h's threadSetPgid; id 0, the idle
thread, is also always rejected -- see that function's own doc comment).
No session/permission check at all -- see threadSetPgid's own honestly-
documented omission. */
long userSetpgid(long pid, long pgid);
/* Real getpgid(2): returns process `pid`'s current process group (0 means
the caller itself), or -1 for a pid that does not name any currently
live thread. */
long userGetpgid(long pid);
/* Real setpriority(PRIO_PROCESS, pid, ...)/nice(2)'s underlying mechanism:
sets process `pid`'s (0 means the caller itself) CFS-lite scheduling
weight (sched.h's threadSetPriority) to `weight` directly -- higher runs
proportionally more often, exactly like kmain.c's own kernel-internal
workerA/workerB demo already proves for a kernel-created thread, now
reachable for any real userspace process including one the caller did
not create itself. Unlike real nice(), takes an absolute weight rather
than a relative increment, and has no privilege check at all (any
process may renice any other) -- this kernel's already-documented,
honest lack of a uid/permission model (see threadSetPgid's own comment
for the same omission). 0 on success, -1 for a `pid` that does not name
any currently live thread, the idle thread (pid 0's OWN id, not the
`pid == 0` self-convention above), or a `weight` of 0. */
long userNice(long pid, long weight);
/* Real getpriority(PRIO_PROCESS, pid)-equivalent: returns process `pid`'s
(0 means the caller itself) current CFS-lite scheduling weight, or -1
for a pid that does not name any currently live thread -- unambiguous
because a real weight is always > 0 (userNice above always rejects
weight 0, and every thread starts at schedDefaultWeight, sched.h). */
long userGetPriority(long pid);
/* Real time(NULL): seconds since the Unix epoch, off the kernel's real
CMOS RTC (rtc.h/syscall.c's sysTime) -- every other notion of time this
ulib exposes (userSleep, userAlarm) is relative to boot, not the
calendar. Never fails; a hardware read failure comes back as 0
(rtc.c's rtcNow contract), the same "epoch itself" sentinel real
time(2) would return for 1970-01-01T00:00:00Z. */
long userTime(void);
/* Real tcsetpgrp(3): names `pgid` as the process group keyboard.c's real
Ctrl-C/Ctrl-Z (SIGINT/SIGTSTP) is delivered to from now on -- see
keyboard.h's keyboardSetForegroundPgid for the full contract, including
why 0 (before anything ever calls this) safely means "nobody". Always
succeeds (returns 0): unlike a real terminal driver, this kernel has no
session/controlling-terminal ownership to check the caller against. */
long userSetForegroundPgid(long pgid);
/* Real tcgetpgrp(3): the pgid userSetForegroundPgid (by anyone) last set,
or 0 if nobody ever has since boot. */
long userGetForegroundPgid(void);
void print(const char* text);
void printDec(long value);
unsigned long stringLength(const char* text);
/* Writes value's decimal representation (with a leading '-' if negative)
into out, nul-terminated -- unlike printDec, which prints it directly,
this is for building an argv string to hand a value (e.g. a shared pipe
handle, see userPipeCreate/userSpawn) to a child process. out must have
room for at least 21 bytes (20 digits for INT64_MIN plus the sign, plus
the terminator). */
void decToString(long value, char* out);
/* Parses the decimal integer (optionally '-'-prefixed) at the start of
text, stopping at the first non-digit -- the inverse of decToString, for
a child process to recover a value (e.g. a shared pipe handle) passed to
it as an argv string. No error signaling: text that starts with no
digits at all (after an optional sign) parses as 0, matching how a
deliberately small, trusted set of internal callers uses it. */
long parseDec(const char* text);
#endif