// libcGno.c — GNO/ME backend for our libc.
//
// Two roles:
//   1. The GNO half of stdio: provides the GS/OS class-1 call wrappers
//      (gsosOpen/Read/Write/Close/...) that libc.c's FILE* layer calls
//      via weak externs, using GNO's INLINE GS/OS dispatch — so fopen /
//      fread / fwrite / fprintf / fgets to real GS/OS files Just Work.
//      Also provides the __putByte / __getByte console hooks libc.c
//      uses for putchar / getchar, routing them to the GNO console.
//   2. POSIX process glue: __gnoStartup (argv parse + main), _exit, and
//      the fork/exec/wait/etc. wrappers over the K* kernel primitives.

#include <stdint.h>
#include <gno/kernel.h>
#include <string.h>         // strchr for the exec-family path/delimiter scans
#include <errno.h>          // EINVAL / EINTR for the POSIX wrappers below
#include <signal.h>         // SIGALRM for the sleep/usleep alarm dance
#include <sys/times.h>      // struct tms for times()
#include <sys/stat.h>       // struct stat + S_IF*/S_IS* for stat/fstat/lstat/isatty
#include <unistd.h>         // pid_t/uid_t/gid_t + POSIX process/cred/jobctl prototypes
#include <fcntl.h>          // O_* / SEEK_* + open()/creat() for the fd layer

extern int main(int argc, char **argv);

// GS/OS class-1 paramblock shapes (layout-matched to libc.c's).
typedef struct {
    uint16_t       pCount;
    uint16_t       refNum;
    void          *dataBuffer;
    unsigned long  requestCount;
    unsigned long  transferCount;
} GnoIORec;
typedef struct { uint16_t pCount; uint16_t refNum; void *pathname; uint16_t requestAccess; } GnoOpenParm;
typedef struct { uint16_t pCount; uint16_t refNum; } GnoRefNumRec;
typedef struct { uint16_t pCount; uint16_t refNum; unsigned long val; } GnoEOFRec;
typedef struct { uint16_t pCount; uint16_t refNum; unsigned long val; } GnoMarkRec;
typedef struct { uint16_t pCount; void *pathname; uint16_t access; uint16_t fileType; unsigned long auxType; uint16_t storageType; } GnoCreateParm;
typedef struct { uint16_t pCount; void *pathname; } GnoDestroyParm;
typedef struct { uint16_t pCount; void *oldPathname; void *newPathname; } GnoChangePathParm;

// GS/OS class-1 call numbers.
#define GSOS_CREATE      0x2001
#define GSOS_DESTROY     0x2002
#define GSOS_CHANGEPATH  0x2004
#define GSOS_OPEN        0x2010
#define GSOS_READ        0x2012
#define GSOS_WRITE       0x2013
#define GSOS_CLOSE       0x2014
#define GSOS_SETMARK     0x2016
#define GSOS_GETMARK     0x2017
#define GSOS_SETEOF      0x2018
#define GSOS_GETEOF      0x2019

// Generic inline-form GS/OS dispatch (asm helper, runtime/src/gnoGsos.s).
// GNO's $E100A8 interceptor reads callNum + pBlock from the inline bytes
// after the JSL; the helper self-modifies them and restores GNO's
// process DP around the call.  Returns the GS/OS error (0 = OK).
extern uint16_t __gnoGsosCall(void *pBlock, unsigned short callNum);


// ---- GS/OS file-call wrappers (override libc.c's weak externs) -------
// libc.c's FILE* layer (fopen with FILE_KIND_GSOS, fread/fwrite/fgetc/
// fputc/fclose) calls these.  Routing them through GNO's inline dispatch
// makes the whole buffered-stdio surface work for real GS/OS files.
//
// `retain` + `used` is the LTO survival policy (Phase 1.11): libc.c
// references these via undefined-weak extern, so without retain the
// LTO inliner can prove they're unreferenced from any non-weak root
// in libcGno.o itself and DCE the bodies — leaving the weak refs in
// libc.o to resolve to NULL.  `retain` keeps the symbol past linker
// GC; `used` keeps it past compiler DCE.  No-op in non-LTO builds.
#define KEEP __attribute__((retain, used))
KEEP uint16_t gsosCreate(GnoCreateParm *p){ return __gnoGsosCall(p, GSOS_CREATE); }
KEEP uint16_t gsosOpen(GnoOpenParm *p)    { return __gnoGsosCall(p, GSOS_OPEN); }
KEEP uint16_t gsosRead(GnoIORec *p)       { return __gnoGsosCall(p, GSOS_READ); }
KEEP uint16_t gsosWrite(GnoIORec *p)      { return __gnoGsosCall(p, GSOS_WRITE); }
KEEP uint16_t gsosClose(GnoRefNumRec *p)  { return __gnoGsosCall(p, GSOS_CLOSE); }
KEEP uint16_t gsosGetEOF(GnoEOFRec *p)    { return __gnoGsosCall(p, GSOS_GETEOF); }
KEEP uint16_t gsosSetEOF(GnoEOFRec *p)    { return __gnoGsosCall(p, GSOS_SETEOF); }
KEEP uint16_t gsosSetMark(GnoMarkRec *p)  { return __gnoGsosCall(p, GSOS_SETMARK); }
KEEP uint16_t gsosGetMark(GnoMarkRec *p)  { return __gnoGsosCall(p, GSOS_GETMARK); }
KEEP uint16_t gsosDestroy(GnoDestroyParm *p)      { return __gnoGsosCall(p, GSOS_DESTROY); }
KEEP uint16_t gsosChangePath(GnoChangePathParm *p){ return __gnoGsosCall(p, GSOS_CHANGEPATH); }

// Stub-mode sentinel.  GNO's gsosCreate/Open/Read/... above are real
// GS/OS dispatchers (through __gnoGsosCall), so when libcGno.o is in
// the link the dispatch surface is real and __gsosAvailable() must
// return 1.  Mirrors the value in iigsGsos.s for the bare-metal path.
KEEP int __gsosIsRealImpl = 1;


// ---- console hooks (override libc.c's weak __putByte/__getByte) ------
// GNO fds are 1-based GS/OS refNums mapped through the per-process
// open-files table by the kernel's GS/OS interceptor (GNORdWr,
// kern/gno/gsos.asm:1794): stdin = 1, stdout = 2, stderr = 3
// (include/unistd.h:51-53; main.c:388-390).  A GS/OS Read/Write with
// that refNum is the exact mechanism GNO's own libc read()/write() use
// (lib/libc/sys/syscall.c:756/1271) -- the interceptor routes it to the
// file ('<'), pipe ('|'), or TTY behind the fd, so redirection is
// honored transparently.  putchar/getchar in libc.c route here; the
// rest of stdio (puts/printf/fgets/...) builds on them.
//
// GNO 1-based fd indices for the inherited standard streams.
#define GNO_FD_STDIN   1
#define GNO_FD_STDOUT  2
#define GNO_FD_STDERR  3

// GS/OS error returned at end-of-file ($4C).  GNO's read() treats this
// as a successful (possibly short) transfer, not a hard error
// (syscall.c:765; texttool.asm:2250).
#define GSOS_ERR_EOF  0x4C

KEEP void __putByte(char c) {
    if (c == '\n') c = '\r';   // GNO console (Apple II TTY) wants CR
    GnoIORec r = { 4, GNO_FD_STDOUT, &c, 1, 0 };
    __gnoGsosCall(&r, GSOS_WRITE);
}

// Strong override for stderr (libc.c routes FILE_KIND_STDERR here).
// stderr is fd 3 -- distinct from stdout so '2>file' redirection works.
KEEP void __putByteErr(char c) {
    if (c == '\n') c = '\r';
    GnoIORec r = { 4, GNO_FD_STDERR, &c, 1, 0 };
    __gnoGsosCall(&r, GSOS_WRITE);
}

KEEP int __getByte(void) {
    unsigned char c;
    GnoIORec r = { 4, GNO_FD_STDIN, &c, 1, 0 };
    uint16_t err = __gnoGsosCall(&r, GSOS_READ);
    // Match GNO's read(): err 0 OR $4C is a valid transfer; only a
    // zero-byte transfer is genuine EOF.  Reading refNum 1 reaches the
    // live inherited stdin -- the file behind '<', the pipe behind '|',
    // or the console TTY -- via the kernel's GNORdWr interceptor.
    if ((err != 0 && err != GSOS_ERR_EOF) || r.transferCount != 1)
        return -1;            // EOF / error
    return (int)c;
}


// ---- raw POSIX read/write (used by demos that want fd-level I/O) -----
// POSIX callers pass Unix 0/1/2; GNO's fd table is 1-based (stdin = 1),
// so translate 0/1/2 -> 1/2/3 in one place.  Any other fd is assumed to
// already be a GNO 1-based fd (e.g. from pipe()/dup()) and passed
// straight through.
static uint16_t gnoFd(int fd) {
    if (fd >= 0 && fd <= 2)
        return (uint16_t)(fd + 1);
    return (uint16_t)fd;
}


long write(int fd, const void *buf, unsigned long n) {
    GnoIORec r = { 4, gnoFd(fd), (void *)buf, n, 0 };
    if (__gnoGsosCall(&r, GSOS_WRITE) != 0) return -1;
    return (long)r.transferCount;
}

long read(int fd, void *buf, unsigned long n) {
    GnoIORec r = { 4, gnoFd(fd), buf, n, 0 };
    uint16_t err = __gnoGsosCall(&r, GSOS_READ);
    if (err != 0 && err != GSOS_ERR_EOF) return -1;
    return (long)r.transferCount;
}


// Tokenize cmdline in-place using simple whitespace rules.  GNO's
// shell does proper quote handling — we'll match that once we have a
// test harness to drive it.  For the initial hello-world the cmdline
// is just the program name (no args), so a degenerate parser suffices.
//
// Writes into the static argv[] and writable copy buf[].  Caps argc
// at 31; longer command lines have their tail args silently dropped.
#define ARG_MAX  32
#define CMD_MAX  256

static char argBuf[CMD_MAX];
static char *argVec[ARG_MAX];


static int parseCmdline(const char *cmd, char ***argvOut) {
    int n = 0;
    int i = 0;
    // Copy cmdline into our writable buffer up to CMD_MAX-1.
    while (i < CMD_MAX - 1 && cmd[i]) {
        argBuf[i] = cmd[i];
        i++;
    }
    argBuf[i] = 0;

    // Tokenize on whitespace.  Replace separator runs with NULs,
    // record argv entries pointing at the first non-NUL char of each
    // run.
    char *p = argBuf;
    while (n < ARG_MAX - 1 && *p) {
        while (*p == ' ' || *p == '\t')
            p++;
        if (!*p)
            break;
        argVec[n++] = p;
        while (*p && *p != ' ' && *p != '\t')
            p++;
        if (*p)
            *p++ = 0;
    }
    argVec[n] = 0;
    *argvOut = argVec;
    return n;
}


// crt0Gno.s entry point.  Receives the command-line pointer the kernel
// handed us (Y:X on entry; crt0 forwards it as a ptr32 arg here).
// GNO prepends an 8-byte "BYTEWRKS" signature to the command line
// (kern/gno/sys.c builds child->args = "BYTEWRKS" + name; ~GNO_PARSEARG
// skips it with `add4 commandline,#8`).  Skip those 8 bytes, parse into
// argc/argv, invoke main(), and return to crt0 for the QUIT call.
int __gnoStartup(const char *cmdline) {
    char **argv;
    int argc;
    if (cmdline) cmdline += 8;        // skip "BYTEWRKS" prefix
    argc = parseCmdline(cmdline, &argv);
    return main(argc, argv);
}


// GS/OS QUIT ($2029) paramblock.  pCount=0 => plain QUIT: GNO's
// CommonQuit reaps the process and returns control to the launcher (gsh).
typedef struct { uint16_t pCount; } GnoQuitParm;

// _exit: GNO/ME program termination, carrying the exit STATUS to the parent.
// Routed through __gnoQuit (runtime/src/gnoGsos.s), not __gnoGsosCall, for two
// reasons:
//   1. Inline dispatch (not the stack form).  GNO patches $E100A8 to OurGSOS,
//      which reads the call number + pblock from the 6 inline bytes AFTER the
//      jsl.  The stack form leaves those bytes as whatever opcode follows, so
//      OurGSOS mis-dispatches QUIT and the process stays restartable -- GNO then
//      RE-RUNS main() (observed: main runs twice, silently draining redirected
//      stdin on the first pass).
//   2. Status propagation.  GNO's StackGSOS records the accumulator at the trap
//      as the process exit code (ldx curProcInd; sta exitCode,x).  __gnoGsosCall
//      loads A = callNum right before dispatching, so every _exit recorded
//      0x2029 -> the parent's wait() decoded WEXITSTATUS = 0x29 (41).  __gnoQuit
//      leaves A = status (the call number is an inline constant), so wait() sees
//      the real code.
extern __attribute__((noreturn)) void __gnoQuit(void *pBlock, int status);

__attribute__((noreturn))
void _exit(int status) {
    GnoQuitParm q = { 0 };          // pCount=0 -- plain QUIT to launcher
    __gnoQuit(&q, status);          // inline dispatch; records status; no return
    for (;;) { }                    // unreachable
}


// Stubs for the K*-wrapped POSIX surface.  These mirror what
// runtime/src/gnoKernel.s already provides; we just rename them.
// Errno handling: each K* takes a `int *errno` arg; on failure the
// kernel writes the errno value there and returns -1.  Our wrappers
// stash it into the global `errno` variable.
//
// Only the minimum needed for the demo is non-trivial — the rest
// are scaffold-only and will be filled in as we add tests.

extern int errno;   // defined in libc.c


// gnoToPosixErrno: translate a GNO kernel errno into this runtime's <errno.h>
// (glibc) numbering.  The K* process/stat/exec wrappers surface the kernel's RAW
// errno, and GNO numbers its errno space differently from us (tools/gno/src-repo/
// include/sys/errno.h): GNO ENOENT is 4, EINVAL 6, EACCES 9, etc. -- versus our
// 2 / 22 / 13.  Passing the raw value straight through makes a program's
// `errno == ENOENT` test misfire after stat()/access().  (The fd layer instead
// maps GS/OS TOOL errors via _mapErr; this maps the kernel's already-POSIX-shaped
// but differently-numbered errno.)  Codes outside the shared table pass through.
static int gnoToPosixErrno(int e) {
    switch (e) {
        case 1:  return EDOM;
        case 2:  return ERANGE;
        case 3:  return ENOMEM;
        case 4:  return ENOENT;
        case 5:  return EIO;
        case 6:  return EINVAL;
        case 7:  return EBADF;
        case 8:  return EMFILE;
        case 9:  return EACCES;
        case 10: return EEXIST;
        case 11: return ENOSPC;
        case 12: return EPERM;
        case 13: return ESRCH;
        case 14: return EINTR;
        case 15: return E2BIG;
        case 16: return ENOEXEC;
        case 17: return ECHILD;
        case 18: return EAGAIN;
        case 19: return ENOTDIR;
        case 20: return ENOTTY;
        case 21: return EPIPE;
        case 22: return ESPIPE;
        case 24: return EISDIR;
        case 52: return EFAULT;
        case 53: return ENODEV;
        default: return e;
    }
}


// fork: GNO thread-spawn -- NOT Unix copy-fork.  `subr` is the 24-bit address of
// a function that runs in the CHILD as a concurrent thread on a fresh stack +
// direct page; the parent gets the child pid.  GNO's createProc (kern/gno/sys.c)
// starts the child with DBR=PBR=program bank but a FRESH direct page whose codegen
// scratch is uninitialized.  We spawn __gnoForkThunk (gnoGsos.s) instead of subr
// directly: it seeds the child's per-thread $BE (the &global bank byte crt0 sets
// for the main thread) and then dispatches to subr.  Together with the reentrant
// __jsl_indir (per-thread indirect-call dispatch, no shared self-modified
// operand), a forked child is now a FULL-CAPABILITY thread -- function calls,
// i32/i64 libcalls, &global pointers, function pointers, and syscalls all work,
// concurrently with the parent.  ($E0-$FF libcall scratch and $B8/$BA per-call
// indirect target are written-before-read, so a fresh page needs no init for
// them.)  The child may return (the thunk _exit(0)s it) or _exit/exec itself.
//
// Not yet concurrency-safe: the malloc heap is shared with the parent and its
// allocator is not serialized, so a child doing heavy allocation must not race
// the parent's; and __gnoPendingChildFn is a single global, so forking a second
// child before the first is scheduled would race (GNO programs fork one at a time).
//
// `subr` is typed void* (matching GNO's own `int fork(void *subr)`), NOT a
// function-pointer type: the callee-side lowering of a function-pointer PARAMETER
// currently drops the value (saves only the low word, never reloads it), whereas
// the ptr32 data path carries the full 24-bit address.  Callers pass (void *)&fn.
void *__gnoPendingChildFn;             // subr for the current child; read by __gnoChildStart
extern void __gnoForkThunk(void);

// Fork serialization + stash handoff.  __forkMutex serializes concurrent fork()
// calls; __forkDone lets the child signal that it has read __gnoPendingChildFn, so
// the parent can release the mutex and let the next fork proceed.  Without it a
// rapid second fork could overwrite the stash before the first child read it.
// Both are GNO binary semaphores, lazily created on the first fork (the program
// is single-threaded until the first child spawns).
// Concurrency state, all lazily set up by the FIRST fork() (which runs while the
// program is still single-threaded).  __gnoThreaded gates both the fork
// serialization AND the heap lock: before any fork there is only one thread, so
// malloc/free need no locking and pay nothing -- no semaphore syscall.
static int __forkMutex   = 0;   // serialize concurrent fork() calls
static int __forkDone    = 0;   // child -> parent "stash consumed" handshake
static int __heapSem     = 0;   // malloc/free free-list mutex (see __heapLock)
static int __gnoThreaded = 0;   // 0 until the first fork(); gates all of the above

int fork(void *subr) {
    int err = 0;
    int r;
    if (__gnoThreaded == 0) {
        // First fork(): lazily create the concurrency semaphores while the
        // program is still single-threaded.  All three gate concurrency
        // safety (fork serialization + heap locking), so commit to threaded
        // mode ONLY if every create succeeds -- latching __gnoThreaded on a
        // failed create would silently disable every guard.  On failure (e.g.
        // the system semaphore table is exhausted) K* returns -1 with err set;
        // fail the fork with errno and stay single-threaded so malloc/free
        // keep their safe single-threaded early-out.
        __forkMutex = Kscreate(1, &err);      // serialize forks
        __forkDone  = Kscreate(0, &err);      // fork handshake
        __heapSem   = Kscreate(1, &err);      // heap mutex
        if (__forkMutex < 0 || __forkDone < 0 || __heapSem < 0) {
            errno = gnoToPosixErrno(err);
            return -1;
        }
        __gnoThreaded = 1;                     // enable locking from here on
    }
    Kswait(__forkMutex, &err);                // serialize against other fork()s
    __gnoPendingChildFn = subr;
    r = Kfork((void *)__gnoForkThunk, &err);
    if (r < 0) {
        Kssignal(__forkMutex, &err);
        errno = gnoToPosixErrno(err);
        return r;
    }
    Kswait(__forkDone, &err);                 // block until the child consumes the stash
    Kssignal(__forkMutex, &err);              // release; the next fork may proceed
    return r;
}


// __gnoChildStart: runs on the CHILD thread, called by __gnoForkThunk (gnoGsos.s)
// after it seeds the child's per-thread $BE.  Reads the stashed child entry,
// signals the parent's fork() that the stash is consumed (so the next fork may
// proceed), then runs the child.  The indirect call goes through the reentrant
// __jsl_indir on this child's OWN direct page, so the child is a full-capability
// concurrent thread.  If the child returns, _exit(0).
void __gnoChildStart(void) {
    void (*fn)(void);
    int   err = 0;
    fn = (void (*)(void))__gnoPendingChildFn;
    Kssignal(__forkDone, &err);               // stash consumed -> unblock parent fork()
    fn();                                      // run the real child concurrently
    _exit(0);
}


// __heapLock / __heapUnlock: GNO override of the weak no-ops in libgcc.s.  The
// binary-semaphore mutex __heapSem (created by the first fork) serializes
// malloc/free's free-list critical section across preemptive threads.  Until the
// first fork the program is single-threaded, so both are a cheap early-out -- a
// single-threaded GNO program pays no semaphore syscall per malloc/free.
void __heapLock(void) {
    int err = 0;
    if (__gnoThreaded == 0) {
        return;
    }
    Kswait(__heapSem, &err);
}


void __heapUnlock(void) {
    int err = 0;
    if (__gnoThreaded == 0) {
        return;
    }
    Kssignal(__heapSem, &err);
}


// wait: block until a child terminates; *status receives the 16-bit union-wait
// word (decode with WIFEXITED/WEXITSTATUS from <sys/wait.h>).  Returns the child
// pid, or -1 with errno set.  Kwait(*status, *errno) -- the kernel writes the
// status through the pointer.
int wait(int *status) {
    int err = 0;
    int pid = Kwait((void *)status, &err);
    if (pid < 0) errno = gnoToPosixErrno(err);
    return pid;
}


int kill(int pid, int sig) {
    int err = 0;
    int r = Kkill(pid, sig, &err);
    if (r < 0) errno = gnoToPosixErrno(err);
    return r;
}


int getpid(void) {
    return Kgetpid();
}


int getppid(void) {
    int err = 0;
    int r = Kgetppid(&err);
    if (r < 0) errno = gnoToPosixErrno(err);
    return r;
}


int dup(int oldfd) {
    int err = 0;
    int r = Kdup(oldfd, &err);
    if (r < 0) errno = gnoToPosixErrno(err);
    return r;
}


int dup2(int oldfd, int newfd) {
    int err = 0;
    int r = Kdup2(oldfd, newfd, &err);
    if (r < 0) errno = gnoToPosixErrno(err);
    return r;
}


int pipe(int fds[2]) {
    int err = 0;
    int r = Kpipe(fds, &err);
    if (r < 0) errno = gnoToPosixErrno(err);
    return r;
}


unsigned long alarm(unsigned long seconds) {
    int err = 0;
    return Kalarm((void *)seconds, &err);
}


// waitpid: block until the specific child `pid` (or any child when pid == -1)
// is reaped, decoding its status into *status.  GNO has no Kwaitpid, so this
// is a pure-C loop over wait() -- exactly GNO's own syscall.c waitpid.  Two
// deviations from that source: WNOHANG cannot be honored (Kwait always
// blocks), so `options` is ignored rather than pretended-supported; and the
// process-group forms (pid < -1 or pid == 0) -- which GNO's libc abort()s on
// because getpgrp() is unimplemented -- are rejected with errno = EINVAL
// instead of aborting the process.
int waitpid(int pid, int *status, int options) {
    int result;
    (void)options;                  // WNOHANG unsupported: Kwait always blocks
    if (pid < -1 || pid == 0) {
        errno = EINVAL;             // process-group waits not implemented
        return -1;
    }
    for (;;) {
        result = wait(status);
        if (result == -1 || pid == -1 || pid == result) {
            return result;          // error, "any child", or the awaited pid
        }
    }
}


// SIGALRM catch-handler for sleep()/usleep().  The GNO kernel delivers the
// signal by building a context and JSLing here; we only flip a flag so the
// Ksigpause loop wakes, then return (RTL) to the kernel's context-restore.
// The handler's args are ignored, so the kernel's toolbox-style arg push vs.
// our C arg ABI does not matter -- only clean entry/return does.
static volatile int alarmRang;


static void alarmCatch(int sig, int code) {
    alarmRang = 1;
}


// Shared body for sleep()/usleep(): install a SIGALRM catch, arm the kernel
// alarm (whole seconds via Kalarm, or tenths of a second via Kalarm10), then
// block in Ksigpause until it fires.  Mirrors BSD/GNO sleep(3): SIGALRM is
// blocked around the arming so the alarm cannot fire before the pause commits,
// and Ksigpause atomically drops that block while it waits (race-free).  The
// previous SIGALRM handler and signal mask are restored on the way out.
static void alarmSleep(unsigned long count, int tenths) {
    int           err = 0;
    unsigned long alrmBit = 1UL << (SIGALRM - 1);   // sigmask(SIGALRM)
    unsigned long oldHandler;
    unsigned long oldMask;

    oldHandler = Ksignal(SIGALRM, (void *)&alarmCatch, &err);
    oldMask    = Ksigblock((void *)alrmBit, &err);
    alarmRang  = 0;
    if (tenths) {
        Kalarm10((void *)count, &err);
    } else {
        Kalarm((void *)count, &err);
    }
    while (!alarmRang) {
        Ksigpause((void *)(oldMask & ~alrmBit), &err);
    }
    Ksignal(SIGALRM, (void *)oldHandler, &err);
    Ksigsetmask((void *)oldMask, &err);
}


// sleep: suspend the process for `seconds` whole seconds using the kernel's
// second-granularity alarm.  Returns 0 (we always sleep the full interval;
// there is no early-wake path since only our own SIGALRM is unblocked).
unsigned int sleep(unsigned int seconds) {
    if (seconds == 0) {
        return 0;
    }
    alarmSleep((unsigned long)seconds, 0);
    return 0;
}


// usleep: suspend for `useconds` microseconds, floored at the kernel's alarm
// granularity of one tenth of a second (Kalarm10).  Any nonzero request thus
// sleeps at least 0.1 s.  Returns 0.
int usleep(unsigned long useconds) {
    unsigned long tenths;
    if (useconds == 0) {
        return 0;
    }
    tenths = (useconds + 99999UL) / 100000UL;   // 100000 us == 1 tenth
    if (tenths == 0) {
        tenths = 1;                             // floor: never round down to 0
    }
    alarmSleep(tenths, 1);
    return 0;
}


// pause: block until any signal is delivered.  Ksigpause(0) unblocks all
// signals and waits; on wake POSIX pause always reports failure with EINTR.
int pause(void) {
    int err = 0;
    Ksigpause((void *)0, &err);
    errno = EINTR;
    return -1;
}


// times: fill *buf with this process' CPU-time accounting.  Thin wrapper over
// the GNO kernel Ktimes primitive, which writes tms_utime/tms_cutime (60 Hz
// ticks) and zeroes tms_stime/tms_cstime, returning 0.  Ktimes has retSize=4
// but the kernel only returns a 16-bit 0; the wrapper zero-fills the hi result
// word (pea 0), so the clock_t result is a clean 0.
clock_t times(struct tms *buf) {
    int err = 0;
    return (clock_t)Ktimes(buf, &err);
}


// ---- file status: stat/fstat/lstat + isatty (punch-list #8) ---------
// Kstat/Kfstat/Klstat (toolset $03 funcs 0x2703/0x2803/0x2903) fill a struct
// stat directly and return an ALREADY-POSIX errno through their pointer arg
// (the kernel owns the tool-error->errno map), so these are thin wrappers.
// struct stat MUST byte-match kern/gno/stat.c -- pinned by the _Static_assert
// in <sys/stat.h>.  (NOTE: like every other K* wrapper here, errno gets the
// kernel's errno-space value, which differs from runtime/include/errno.h --
// a pre-existing project-wide inconsistency, not reconciled per-call.)


// fstat: status of the open descriptor fd.  POSIX 0/1/2 are remapped to GNO's
// 1-based table via gnoFd (same as read/write) -- the kernel rejects the
// unmapped fd 0 as EBADF, so the remap is load-bearing, not cosmetic.
int fstat(int fd, struct stat *sb) {
    int err = 0;
    int r   = Kfstat((int)gnoFd(fd), sb, &err);
    if (r < 0) {
        errno = gnoToPosixErrno(err);
    }
    return r;
}


// isatty: true when fd is a terminal.  POSIX does not require ioctl -- a GNO
// TTY descriptor stats as a character special, so the predicate is S_ISCHR.
int isatty(int fd) {
    struct stat sb;
    if (fstat(fd, &sb) != 0) {
        return 0;                       // errno set by fstat (EBADF)
    }
    if (S_ISCHR(sb.st_mode)) {
        return 1;
    }
    errno = ENOTTY;
    return 0;
}


// lstat: like stat but does not follow a final symlink.  GNO has no real
// symlinks, so Klstat behaves identically to Kstat; it takes a plain C string
// (the kernel does the C->GS/OS conversion + path expansion).
int lstat(const char *path, struct stat *sb) {
    int err = 0;
    int r   = Klstat((void *)path, sb, &err);
    if (r < 0) {
        errno = gnoToPosixErrno(err);
    }
    return r;
}


// stat: status of the file named by path.  Kstat takes a plain C string.
int stat(const char *path, struct stat *sb) {
    int err = 0;
    int r   = Kstat((void *)path, sb, &err);
    if (r < 0) {
        errno = gnoToPosixErrno(err);
    }
    return r;
}


// ---- uid/gid + job control (punch-list #11) -------------------------
// Thin wrappers over the toolset-$03 credential / process-group primitives
// (K* in gno/kernel.h), mirroring GNO's own libc trap.asm / syscall.c.
//
// The getid family (getuid/geteuid/getgid/getegid) NEVER fails in the kernel:
// the trailing *errno arg is written only on an (unreachable) error path, and
// GNO's own trap.asm stubs return the raw result without inspecting it.  So
// these do NOT gate on r < 0.  The setters and the pgrp calls DO carry a real
// error path and stash *errno into the global errno on -1.


// getegid: effective group id of the calling process.  Cannot fail.
gid_t getegid(void) {
    int err = 0;
    return (gid_t)Kgetegid(&err);
}


// geteuid: effective user id of the calling process.  Cannot fail.
uid_t geteuid(void) {
    int err = 0;
    return (uid_t)Kgeteuid(&err);
}


// getgid: real group id of the calling process.  Cannot fail.
gid_t getgid(void) {
    int err = 0;
    return (gid_t)Kgetgid(&err);
}


// getpgrp: process group of the calling process.  GNO has no zero-arg
// primitive -- it is K_getpgrp(getpid()), exactly like GNO's own getpgrp()
// (syscall.c: _getpgrp(getpid())).  Returns the pgrp, or -1 with errno set.
pid_t getpgrp(void) {
    int err = 0;
    int r   = K_getpgrp(getpid(), &err);
    if (r < 0) {
        errno = gnoToPosixErrno(err);
    }
    return r;
}


// getuid: real user id of the calling process.  Cannot fail.
uid_t getuid(void) {
    int err = 0;
    return (uid_t)Kgetuid(&err);
}


// setgid: set the real (and, for the superuser, effective) group id.
int setgid(gid_t gid) {
    int err = 0;
    int r   = Ksetgid((int)gid, &err);
    if (r < 0) {
        errno = gnoToPosixErrno(err);
    }
    return r;
}


// setpgid: place process pid into process group pgrp.  Maps to Ksetpgrp(pid,
// pgrp) -- note the (pid, pgrp) order, matching trap.asm's 0x3403 entry.
int setpgid(pid_t pid, pid_t pgrp) {
    int err = 0;
    int r   = Ksetpgrp(pid, pgrp, &err);
    if (r < 0) {
        errno = gnoToPosixErrno(err);
    }
    return r;
}


// setpgrp: BSD spelling of setpgid; shares the same kernel entry (0x3403).
int setpgrp(pid_t pid, pid_t pgrp) {
    return setpgid(pid, pgrp);
}


// setsid: create a new session and become its leader.  GNO exposes no session
// primitive (tcnewpgrp/0x1803 only reassigns a tty foreground group), so this
// stubs ENOSYS.
pid_t setsid(void) {
    errno = ENOSYS;
    return -1;
}


// setuid: set the real (and, for the superuser, effective) user id.
int setuid(uid_t uid) {
    int err = 0;
    int r   = Ksetuid((int)uid, &err);
    if (r < 0) {
        errno = gnoToPosixErrno(err);
    }
    return r;
}


// tcgetpgrp: foreground process group of the terminal behind fd.  GNO's tty
// driver routes both TIOCGPGRP and TIOCSPGRP to its `invalid` handler
// (kern/gno/tty.asm), so the query can never succeed -- stub ENOSYS rather
// than issue an ioctl guaranteed to fail with a misleading errno.
pid_t tcgetpgrp(int fd) {
    (void)fd;
    errno = ENOSYS;
    return -1;
}


// tcsetpgrp: make pgrp the foreground process group of the terminal behind fd.
// Maps to Ktctpgrp(gnoFd(fd), pgrp) (0x1A03); fd is a POSIX 0/1/2 std-stream
// descriptor, so it goes through gnoFd like every other tty-fd call here.
int tcsetpgrp(int fd, pid_t pgrp) {
    int err = 0;
    int r   = Ktctpgrp((int)gnoFd(fd), pgrp, &err);
    if (r < 0) {
        errno = gnoToPosixErrno(err);
    }
    return r;
}


// ---- environment: GNO shell-variable table (punch-list #6) ----------
// GNO has no Unix environ[]; the "environment" is the kernel SHELL-VARIABLE
// table, reached through GS/OS Shell calls dispatched by the SAME inline
// $E100A8 mechanism as the file calls (__gnoGsosCall) -- zero new asm.
// Layouts + call numbers mirror GNO libc's lib/libc/stdlib/environ.c.

// GS/OS class-0 shell-call numbers (from GNO environ.c usage).
#define SHELL_SETGS            0x0146   // SetGS: set (and export) a variable
#define SHELL_READVARIABLEGS   0x014B   // ReadVariableGS: fetch a value
#define SHELL_UNSETVARIABLEGS  0x0155   // UnsetVariableGS: delete a variable

// GS/OS length-prefixed strings (Word length; char text[]).  The shell's
// GSString255 text is 255 bytes; our copy carries one extra byte so getenv
// can NUL-terminate the value in place without overflowing the shell's region.
#define GSSTR255_TEXT   255
#define GSSTR255_SIZE   (2 + GSSTR255_TEXT)      // 257 = shell sizeof(GSString255)
typedef struct { uint16_t length; char text[GSSTR255_TEXT + 1]; } GnoGSString255;
typedef struct { uint16_t bufSize; GnoGSString255 bufString; } GnoResultBuf255;

// Shell param blocks (field order per GNO's shell interface; confirmed by
// environ.c's field accesses: pCount, name, value, export).
typedef struct { uint16_t pCount; GnoGSString255 *name; GnoGSString255 *value; uint16_t export; } GnoSetParm;
typedef struct { uint16_t pCount; GnoGSString255 *name; GnoResultBuf255 *value; uint16_t export; } GnoReadVarParm;
typedef struct { uint16_t pCount; GnoGSString255 *name; } GnoUnsetVarParm;

_Static_assert(sizeof(void *) == 4, "GS/OS pblock pointers must be 4 bytes");
_Static_assert(__builtin_offsetof(GnoResultBuf255, bufString.length) == 2, "GSString length must sit at ResultBuf+2");
_Static_assert(__builtin_offsetof(GnoResultBuf255, bufString.text) == 4, "GSString text must sit at ResultBuf+4");

// POSIX environ[] mirror.  This thin port keeps only the internal (shell)
// table, so environ stays NULL/lazy: use the getenv/setenv accessors.
char **environ = 0;

// Shared GS/OS-string scratch for the env accessors -- single-threaded, so one
// name + one value buffer serve getenv/setenv/unsetenv.  getenv's result must
// persist across the call (the returned pointer aims into it), so it is its own
// buffer.  setenv fills envName only AFTER its internal getenv overwrite-check,
// so sharing envName between them is safe.  (Consolidated from five per-function
// static buffers to keep the GNO libc's bank-0 BSS footprint small.)
static GnoGSString255  envName;       // name arg (getenv/setenv/unsetenv)
static GnoGSString255  envSetValue;   // setenv value arg
static GnoResultBuf255 envResult;     // getenv result (returned-pointer target)

int setenv(const char *name, const char *value, int overwrite);   // putenv calls it


// Copy up to n bytes of str into a GS/OS length-prefixed string, clamping to
// the shell's 255-byte text capacity.
static void gnoFillGSStringN(GnoGSString255 *s, const char *str, uint16_t n) {
    uint16_t i;
    if (n > GSSTR255_TEXT) {
        n = GSSTR255_TEXT;
    }
    i = 0;
    while (i < n) {
        s->text[i] = str[i];
        i++;
    }
    s->length = n;
}


// strlen returning a 16-bit count (shell names/values never exceed 255).
static uint16_t gnoEnvLen(const char *s) {
    uint16_t n;
    n = 0;
    while (s[n] != 0) {
        n++;
    }
    return n;
}


// getenv: read shell variable name via ReadVariableGS ($014B).  Returns a
// pointer into a STATIC result buffer (NUL-terminated in place), or NULL if
// the variable is unset, not exported, or the call errors.  A trailing '=' on
// name is stripped.  The static buffer is overwritten by the next getenv call.
char *getenv(const char *name) {
    GnoReadVarParm pb;
    uint16_t       nameLen;
    uint16_t       length;

    if (name == 0) {
        return 0;
    }
    nameLen = gnoEnvLen(name);
    if (nameLen != 0 && name[nameLen - 1] == '=') {
        nameLen--;
    }
    if (nameLen == 0) {
        return 0;
    }
    gnoFillGSStringN(&envName, name, nameLen);

    pb.pCount         = 3;
    pb.name           = &envName;
    envResult.bufSize = GSSTR255_SIZE;
    pb.value          = &envResult;
    pb.export         = 0;
    if (__gnoGsosCall(&pb, SHELL_READVARIABLEGS) != 0) {
        return 0;
    }
    if (pb.export == 0) {
        return 0;                        // not defined / not exported
    }
    length = envResult.bufString.length;
    if (length == 0) {
        return 0;
    }
    if (length > GSSTR255_TEXT) {
        length = GSSTR255_TEXT;
    }
    envResult.bufString.text[length] = 0;
    return envResult.bufString.text;
}


// putenv: split "NAME=value" and forward to setenv(name, value, 1).  The
// caller's string is not retained (GNO keeps only the shell table).
int putenv(const char *str) {
    char     nameBuf[GSSTR255_TEXT + 1];
    uint16_t i;

    if (str == 0) {
        return -1;
    }
    i = 0;
    while (str[i] != 0 && str[i] != '=' && i < GSSTR255_TEXT) {
        nameBuf[i] = str[i];
        i++;
    }
    if (str[i] != '=') {
        return -1;                       // no '=' -> not NAME=value
    }
    nameBuf[i] = 0;
    return setenv(nameBuf, str + i + 1, 1);
}


// setenv: set shell variable name to value via SetGS ($0146) with export=1.
// Honors overwrite: when zero and the variable exists, leaves it unchanged.
int setenv(const char *name, const char *value, int overwrite) {
    GnoSetParm pb;
    uint16_t   nameLen;

    if (name == 0 || value == 0) {
        errno = EINVAL;
        return -1;
    }
    if (*value == '=') {
        value++;
    }
    nameLen = gnoEnvLen(name);
    if (nameLen != 0 && name[nameLen - 1] == '=') {
        nameLen--;
    }
    if (nameLen == 0 || *value == 0) {
        errno = EINVAL;
        return -1;
    }
    if (!overwrite && getenv(name) != 0) {
        return 0;                        // exists and not overwriting (uses envName)
    }
    gnoFillGSStringN(&envName, name, nameLen);        // re-filled AFTER the getenv check
    gnoFillGSStringN(&envSetValue, value, gnoEnvLen(value));

    pb.pCount = 3;
    pb.name   = &envName;
    pb.value  = &envSetValue;
    pb.export = 1;
    if (__gnoGsosCall(&pb, SHELL_SETGS) != 0) {
        errno = ENOMEM;
        return -1;
    }
    return 0;
}


// unsetenv: delete shell variable name via UnsetVariableGS ($0155).
int unsetenv(const char *name) {
    GnoUnsetVarParm pb;
    uint16_t        nameLen;

    if (name == 0) {
        errno = EINVAL;
        return -1;
    }
    nameLen = gnoEnvLen(name);
    if (nameLen != 0 && name[nameLen - 1] == '=') {
        nameLen--;
    }
    gnoFillGSStringN(&envName, name, nameLen);

    pb.pCount = 1;
    pb.name   = &envName;
    __gnoGsosCall(&pb, SHELL_UNSETVARIABLEGS);
    return 0;
}


// ---- POSIX fd layer: open/close/lseek/creat/unlink (punch-list #9) ---
// GS/OS-toolbox emulation over the gsos* wrappers above -- NOT a K* job.
// (rename() already lives in libc.c as a strong symbol; do NOT add one here.)

// GS/OS Open requestAccess values (GS/OS Reference, requestAccess field).
#define GSOS_READ_ENABLE    0x0001
#define GSOS_WRITE_ENABLE   0x0002
#define GSOS_RW_ENABLE      0x0003
// Create access for a fresh data file: destroy|rename|backup|write|read enabled.
#define GSOS_CREATE_ACCESS  0x00C3
#define GSOS_FILETYPE_TXT   0x0004
#define GSOS_FILETYPE_BIN   0x0006
#define GSOS_STORAGE_STD    0x0001   // standard (seedling/sapling/tree) file
#define GSOS_PATH_MAX       256

// Class-1 SetMark/SetEOF position block: base + displacement.  GetMark/GetEOF
// return only a bare position, so the Get side and truncate-to-0 keep using the
// 3-field GnoMarkRec/GnoEOFRec; positioned seeks need this 4-field form (matches
// GNO's own lib/libc/sys/syscall.c lseek, which uses pCount=3 base=0).
typedef struct {
    uint16_t       pCount;
    uint16_t       refNum;
    uint16_t       base;
    unsigned long  displacement;
} GnoSetPosParm;

// Class-1 GSString (length-prefixed pathname).  libc.c's __fillGSString is
// static, so the fd layer carries its own builder + buffer.
typedef struct {
    uint16_t length;
    char     text[GSOS_PATH_MAX];
} GsStringT;
static GsStringT gsPath;


// _mapErr: translate a GS/OS tool error into a POSIX errno.  Ported from GNO's
// kernel _mapErr (kern/gno/stat.c): errors GNO already folded into the 0x43xx
// space carry the POSIX code in their low byte; the rest map by GS/OS error
// number.  0 stays 0.
static int _mapErr(uint16_t gsErr) {
    if (gsErr == 0) {
        return 0;
    }
    if ((gsErr & 0xFF00) == 0x4300) {
        return (int)(gsErr & 0x00FF);
    }
    switch (gsErr) {
        case 0x43:
            return EBADF;
        case 0x44:
        case 0x45:
        case 0x46:
            return ENOENT;
        case 0x47:
        case 0x50:
            return EEXIST;
        case 0x48:
        case 0x49:
            return ENOSPC;
        case 0x4A:
            return ENOTDIR;
        case 0x4B:
        case 0x4F:
        case 0x53:
            return EINVAL;
        case 0x4E:
            return EACCES;
        case 0x54:
            return ENOMEM;
        default:
            return EIO;
    }
}


// buildGsString: fill a class-1 GSString (uint16 length + text) from a NUL-
// terminated C path.  Returns -1 if the path is longer than GSOS_PATH_MAX.
static int buildGsString(GsStringT *dst, const char *path) {
    uint16_t n;

    n = 0;
    while (path[n] != 0 && n < GSOS_PATH_MAX) {
        dst->text[n] = path[n];
        n++;
    }
    if (path[n] != 0) {
        return -1;
    }
    dst->length = n;
    return 0;
}


// close: POSIX fd close over GS/OS Close.  fd is fed through gnoFd() so the
// inherited console streams (0/1/2 -> GNO refNums 1/2/3) still close cleanly;
// an open()-returned refNum (>= 4) passes through unchanged.
int close(int fd) {
    GnoRefNumRec rec;
    int          perr;

    rec.pCount = 1;
    rec.refNum = gnoFd(fd);
    perr = _mapErr(gsosClose(&rec));
    if (perr != 0) {
        errno = perr;
        return -1;
    }
    return 0;
}


// creat: create-or-truncate a file for writing.  POSIX defines it as this
// open() call; mode is accepted for source compatibility but ignored (GS/OS
// create access is fixed full-access).
int creat(const char *path, mode_t mode) {
    (void)mode;
    return open(path, O_CREAT | O_TRUNC | O_WRONLY, 0);
}


// lseek: reposition an open fd's mark over GS/OS GetEOF/GetMark/SetMark.
// SEEK_SET/CUR/END resolve to an absolute target; seeking past EOF extends the
// file first (GS/OS cannot park the mark beyond EOF).  fd is fed through
// gnoFd().  Returns the new absolute offset, or -1 with errno set.
off_t lseek(int fd, off_t offset, int whence) {
    GnoEOFRec     eof;
    GnoMarkRec    mark;
    GnoSetPosParm sp;
    uint16_t      rn;
    off_t         target;
    int           perr;

    rn = gnoFd(fd);
    eof.pCount = 2;
    eof.refNum = rn;
    eof.val    = 0;
    perr = _mapErr(gsosGetEOF(&eof));
    if (perr != 0) {
        errno = perr;
        return -1;
    }
    switch (whence) {
        case SEEK_SET:
            target = offset;
            break;
        case SEEK_CUR:
            mark.pCount = 2;
            mark.refNum = rn;
            mark.val    = 0;
            perr = _mapErr(gsosGetMark(&mark));
            if (perr != 0) {
                errno = perr;
                return -1;
            }
            target = (off_t)mark.val + offset;
            break;
        case SEEK_END:
            target = (off_t)eof.val + offset;
            break;
        default:
            errno = EINVAL;
            return -1;
    }
    if (target < 0) {
        errno = EINVAL;
        return -1;
    }
    sp.pCount       = 3;
    sp.refNum       = rn;
    sp.base         = 0;   // 0 = displacement measured from start of file
    sp.displacement = (unsigned long)target;
    if ((unsigned long)target > eof.val) {
        perr = _mapErr(__gnoGsosCall(&sp, GSOS_SETEOF));
        if (perr != 0) {
            errno = perr;
            return -1;
        }
    }
    perr = _mapErr(__gnoGsosCall(&sp, GSOS_SETMARK));
    if (perr != 0) {
        errno = perr;
        return -1;
    }
    return target;
}


// open: POSIX open(2) over GS/OS Open (+ Create for O_CREAT).  The access mode
// selects the GS/OS requestAccess; O_CREAT makes a missing file; O_EXCL fails
// an existing file; O_TRUNC resets EOF to 0; O_APPEND parks the mark at EOF.
// Returns the RAW GS/OS refNum (always >= 4, never colliding with the 1/2/3
// console fds), or -1 with errno set.  mode is variadic + unused.
int open(const char *path, int flags, ...) {
    GnoOpenParm   op;
    GnoCreateParm cp;
    GnoEOFRec     eof;
    GnoRefNumRec  rec;
    uint16_t      gsErr;
    int           perr;
    int           accMode;

    if (buildGsString(&gsPath, path) != 0) {
        errno = ENAMETOOLONG;
        return -1;
    }
    accMode     = flags & O_ACCMODE;
    op.pCount   = 3;
    op.refNum   = 0;
    op.pathname = &gsPath;
    if (accMode == O_WRONLY) {
        op.requestAccess = GSOS_WRITE_ENABLE;
    } else if (accMode == O_RDWR) {
        op.requestAccess = GSOS_RW_ENABLE;
    } else {
        op.requestAccess = GSOS_READ_ENABLE;
    }

    gsErr = gsosOpen(&op);
    perr  = _mapErr(gsErr);

    if (gsErr == 0 && (flags & O_CREAT) && (flags & O_EXCL)) {
        // O_CREAT|O_EXCL and the file already exists -- fail after closing it.
        rec.pCount = 1;
        rec.refNum = op.refNum;
        gsosClose(&rec);
        errno = EEXIST;
        return -1;
    }
    if (perr == ENOENT && (flags & O_CREAT)) {
        // Missing file -- create it, then reopen.
        cp.pCount      = 5;
        cp.pathname    = &gsPath;
        cp.access      = GSOS_CREATE_ACCESS;
        cp.fileType    = (flags & O_BINARY) ? GSOS_FILETYPE_BIN : GSOS_FILETYPE_TXT;
        cp.auxType     = 0;
        cp.storageType = GSOS_STORAGE_STD;
        perr = _mapErr(gsosCreate(&cp));
        if (perr != 0) {
            errno = perr;
            return -1;
        }
        perr = _mapErr(gsosOpen(&op));
        if (perr != 0) {
            errno = perr;
            return -1;
        }
    } else if (perr != 0) {
        errno = perr;
        return -1;
    }

    // File is open (refNum in op.refNum).  Apply size/position modifiers for
    // writable opens only.
    if (accMode != O_RDONLY) {
        if (flags & O_TRUNC) {
            eof.pCount = 2;
            eof.refNum = op.refNum;
            eof.val    = 0;
            gsosSetEOF(&eof);
        }
        if (flags & O_APPEND) {
            eof.pCount = 2;
            eof.refNum = op.refNum;
            eof.val    = 0;
            if (gsosGetEOF(&eof) == 0) {
                GnoSetPosParm sp;
                sp.pCount       = 3;
                sp.refNum       = op.refNum;
                sp.base         = 0;
                sp.displacement = eof.val;
                __gnoGsosCall(&sp, GSOS_SETMARK);
            }
        }
    }
    return (int)op.refNum;
}


// unlink: remove a file via GS/OS Destroy.  Returns 0, or -1 with errno set.
int unlink(const char *path) {
    GnoDestroyParm dp;
    int            perr;

    if (buildGsString(&gsPath, path) != 0) {
        errno = ENAMETOOLONG;
        return -1;
    }
    dp.pCount   = 1;
    dp.pathname = &gsPath;
    perr = _mapErr(gsosDestroy(&dp));
    if (perr != 0) {
        errno = perr;
        return -1;
    }
    return 0;
}


// ---- exec family + access (punch-list #5 + #7) ----------------------
// GNO's kernel exec (K_execve, $1D03) is NOT the Unix argv/envp exec: it takes
// a FILE path and ONE flattened command-line string, and the kernel prepends
// its own 8-byte "BYTEWRKS" tag (which __gnoStartup already skips), so we must
// NOT add it here.  These port lib/libc/sys/exec.c: gnoBuildCmd space-joins
// argv (stripping argv[0]'s GS/OS prefix), gnoBuildPath does the $PATH search
// for execvp/execlp, and K_execve carries file + cmd.  On success K_execve
// replaces the process image and never returns.  envp is ignored (GNO exec has
// no environment vector -- see the punch-list notes).

// va_list via compiler builtins (there is no <stdarg.h> in runtime/include;
// snprintf.c does the same).  execl/execlp/execle need it.
typedef __builtin_va_list execVaList;
#define EXEC_VA_START(ap, last) __builtin_va_start(ap, last)
#define EXEC_VA_ARG(ap, ty)     __builtin_va_arg(ap, ty)
#define EXEC_VA_END(ap)         __builtin_va_end(ap)

#define EXEC_CMD_MAX    256          // flattened cmd / candidate-path cap
#define EXEC_ARGV_MAX   32           // execl* argv gather cap (excess dropped)
#define GNO_DEFAULT_PATH "/bin /usr/bin"   // $PATH fallback (the set gsh searches)

// buildCmd/buildPath output buffers.  exec never returns on success and each
// result is consumed immediately by K_execve, so static single-use is safe.
static char execCmdBuf[EXEC_CMD_MAX];
static char execPathBuf[EXEC_CMD_MAX];
// Allowable first chars of a fully-specified (root) path name (from exec.c).
static const char *pfxBrkStr = "/:0123456789.*@";


// access: existence/permission test over stat().  Only F_OK (existence) is
// meaningfully supported -- GNO stat reports no faithful owner/perm bits, so
// R/W/X_OK degrade to an existence test, which is exactly what execvp's $PATH
// search needs.  Returns 0 if the path stats, else -1 with errno set.
int access(const char *path, int mode) {
    struct stat sb;
    (void)mode;
    if (path == 0 || *path == 0) {
        errno = ENOENT;
        return -1;
    }
    return (stat(path, &sb) == 0) ? 0 : -1;   // stat() set errno on failure
}


// isRootPath: TRUE if name is a fully-specified path from the file-system root
// (leading '/', ':', a digit, or a '.'/'*'/'@' prefix form) and thus needs no
// $PATH search.  Ported from exec.c isRootPath (with an empty-string guard).
static int isRootPath(const char *name) {
    const char *p;
    if (*name == 0) {
        return 0;
    }
    p = strchr(pfxBrkStr, *name);
    if (p == 0) {
        return 0;
    }
    switch (*p) {
        case '/':
        case ':':
            if (*(p + 1) == 0) {
                return 0;               // zero-length volume/prefix not allowed
            }
            return 1;
        case '.':
            switch (*(p + 1)) {
                case 0:
                case '/':
                case ':':
                    return 0;           // "." / "./" / ".:" are not root paths
                default:
                    return 1;
            }
        case '*':
        case '@':
            switch (*(p + 1)) {
                case 0:
                case '/':
                case ':':
                    return 1;
                default:
                    return 0;
            }
        default:
            return 1;                   // begins with a digit
    }
}


// gnoBuildCmd: flatten argv[] into ONE space-joined command line in execCmdBuf,
// stripping argv[0]'s GS/OS prefix to the leaf after the last '/' or ':'
// (matching exec.c buildCmd).  Returns execCmdBuf, or NULL with errno=E2BIG on
// overflow.  An empty argv yields a zero-length string.
static char *gnoBuildCmd(char *const *argv) {
    uint16_t    pos;
    int         i;
    const char *s;
    char        delim;

    pos = 0;
    execCmdBuf[0] = 0;
    if (argv == 0 || argv[0] == 0) {
        return execCmdBuf;              // zero-length command line
    }

    // Strip argv[0]'s prefix: keep only the leaf after the last delimiter.
    delim = (strchr(argv[0], ':') == 0) ? '/' : ':';
    s = argv[0];
    while (*s != 0) {
        s++;
    }
    while (s > argv[0] && *s != delim) {
        s--;
    }
    if (*s == delim) {
        s++;
    }

    // Append the leaf, then argv[1..] each preceded by a single space.
    i = 0;
    while (1) {
        while (*s != 0) {
            if (pos >= EXEC_CMD_MAX - 1) {
                errno = E2BIG;
                return 0;
            }
            execCmdBuf[pos++] = *s++;
        }
        i++;
        if (argv[i] == 0) {
            break;
        }
        if (pos >= EXEC_CMD_MAX - 1) {
            errno = E2BIG;
            return 0;
        }
        execCmdBuf[pos++] = ' ';
        s = argv[i];
    }
    execCmdBuf[pos] = 0;
    return execCmdBuf;
}


// gnoBuildPath: resolve a bare command name to a full path via $PATH (for
// execvp/execlp).  A rooted/prefixed name (isRootPath) is returned verbatim.
// Otherwise each space-separated $PATH directory (getenv("PATH"), or
// GNO_DEFAULT_PATH when unset) is tried as <dir><delim><file> and probed with
// access(F_OK); the first hit wins.  Returns execPathBuf, or NULL with
// errno=ENOENT.
static char *gnoBuildPath(const char *file) {
    const char *pathEnv;
    const char *p;
    const char *f;
    char        delim;
    uint16_t    pos;

    if (isRootPath(file)) {
        pos = 0;
        while (file[pos] != 0 && pos < EXEC_CMD_MAX - 1) {
            execPathBuf[pos] = file[pos];
            pos++;
        }
        execPathBuf[pos] = 0;
        return execPathBuf;
    }

    pathEnv = getenv("PATH");
    if (pathEnv == 0 || *pathEnv == 0) {
        pathEnv = GNO_DEFAULT_PATH;
    }
    delim = (strchr(pathEnv, '/') == 0) ? ':' : '/';

    p = pathEnv;
    while (*p != 0) {
        while (*p == ' ') {
            p++;
        }
        if (*p == 0) {
            break;
        }
        // Copy one directory token.
        pos = 0;
        while (*p != 0 && *p != ' ' && pos < EXEC_CMD_MAX - 1) {
            execPathBuf[pos++] = *p++;
        }
        // Resync past any tail of an over-long token.
        while (*p != 0 && *p != ' ') {
            p++;
        }
        // Join with the delimiter unless the token already ends in one.
        if (pos > 0 && execPathBuf[pos - 1] != delim && pos < EXEC_CMD_MAX - 1) {
            execPathBuf[pos++] = delim;
        }
        // Append the file name.
        f = file;
        while (*f != 0 && pos < EXEC_CMD_MAX - 1) {
            execPathBuf[pos++] = *f++;
        }
        execPathBuf[pos] = 0;
        if (access(execPathBuf, F_OK) == 0) {
            return execPathBuf;         // found it
        }
    }
    errno = ENOENT;
    return 0;
}


// execCore: shared tail for execv/execve/execvp.  K_execve only returns on
// failure (on success the process image is replaced), so this always ends by
// surfacing the kernel errno and returning -1.
static int execCore(const char *path, const char *cmd) {
    int err = 0;
    K_execve((void *)path, (void *)cmd, &err);
    errno = gnoToPosixErrno(err);                        // reached only if the exec failed
    return -1;
}


// execve: flatten argv into a command line and hand file+cmd to K_execve.
// envp is accepted for signature compatibility but ignored (GNO exec has no
// environment vector).  Never returns on success; -1/errno on failure.
int execve(const char *path, char *const *argv, char *const *envp) {
    char *cmd;
    (void)envp;
    cmd = gnoBuildCmd(argv);
    if (cmd == 0) {
        return -1;                      // errno = E2BIG from gnoBuildCmd
    }
    return execCore(path, cmd);
}


// execv: execve without an environment (GNO ignores envp regardless).
int execv(const char *path, char *const *argv) {
    return execve(path, argv, 0);
}


// execvp: like execv but resolve file through $PATH first (when not rooted).
int execvp(const char *file, char *const *argv) {
    char *path;
    char *cmd;
    path = gnoBuildPath(file);
    if (path == 0) {
        return -1;                      // errno = ENOENT from gnoBuildPath
    }
    cmd = gnoBuildCmd(argv);
    if (cmd == 0) {
        return -1;
    }
    return execCore(path, cmd);
}


// execl: variadic form of execv.  Gather arg, ... (NULL-terminated) into a
// local argv[] (capped at EXEC_ARGV_MAX) and defer to execv.
int execl(const char *path, const char *arg, ...) {
    char       *argv[EXEC_ARGV_MAX];
    execVaList  ap;
    int         i;

    argv[0] = (char *)arg;
    i = 1;
    if (arg != 0) {
        EXEC_VA_START(ap, arg);
        while (i < EXEC_ARGV_MAX - 1) {
            argv[i] = EXEC_VA_ARG(ap, char *);
            if (argv[i] == 0) {
                break;
            }
            i++;
        }
        EXEC_VA_END(ap);
    }
    argv[i] = 0;
    return execv(path, argv);
}


// execlp: variadic form of execvp (PATH-searched).
int execlp(const char *file, const char *arg, ...) {
    char       *argv[EXEC_ARGV_MAX];
    execVaList  ap;
    int         i;

    argv[0] = (char *)arg;
    i = 1;
    if (arg != 0) {
        EXEC_VA_START(ap, arg);
        while (i < EXEC_ARGV_MAX - 1) {
            argv[i] = EXEC_VA_ARG(ap, char *);
            if (argv[i] == 0) {
                break;
            }
            i++;
        }
        EXEC_VA_END(ap);
    }
    argv[i] = 0;
    return execvp(file, argv);
}


// execle: variadic form of execve.  The trailing envp (after the NULL argv
// terminator) is ignored -- GNO exec has no environment vector.
int execle(const char *path, const char *arg, ...) {
    char       *argv[EXEC_ARGV_MAX];
    execVaList  ap;
    int         i;

    argv[0] = (char *)arg;
    i = 1;
    if (arg != 0) {
        EXEC_VA_START(ap, arg);
        while (i < EXEC_ARGV_MAX - 1) {
            argv[i] = EXEC_VA_ARG(ap, char *);
            if (argv[i] == 0) {
                break;
            }
            i++;
        }
        EXEC_VA_END(ap);
    }
    argv[i] = 0;
    return execve(path, argv, 0);       // trailing envp intentionally ignored
}


// ---- signals (punch-list #10) ---------------------------------------
// The GNO kernel calls an installed handler in ORCA/C convention (args on the
// stack, callee-pops, RTL to the kernel's context-restore), which does not
// match our arg0-in-A / caller-pops ABI.  __sigTrampoline (gnoGsos.s) is
// registered as the kernel handler for every CAUGHT signal: it reads the signal
// number off the delivery frame, switches to this process's DP/DBR, dispatches
// the real C handler from userHandlers[] (signal number in A), then does the
// ORCA callee-pop return the kernel's ctxtRestore expects.  __gnoCaptureDP
// records this process's direct page for the trampoline.
extern void __sigTrampoline(void);   // asm: kernel-side signal-handler shim
extern void __gnoCaptureDP(void);    // asm: stash our DP -> __gnoProcDP

// Real C handlers, indexed by signal number (1..NSIG-1; slot 0 unused).  Read
// by name from __sigTrampoline, so it must be a non-static global; KEEP so LTO
// cannot DCE the table the asm references.
KEEP void (*userHandlers[NSIG])(int);

// Shadow of the user-visible disposition (incl SIG_DFL/SIG_IGN) so signal()/
// sigaction() can report the previous handler.  Zero-init == SIG_DFL.
static sig_t currentHandler[NSIG];


// abort: raise SIGABRT, then terminate with the conventional 128+signal status.
// libc.c's abort() is weak; this GNO one wins.
__attribute__((noreturn))
void abort(void) {
    raise(SIGABRT);
    _exit(128 + SIGABRT);           // 134; only reached if the handler returned
    for (;;) {
    }
}


// raise: send a signal to the calling process.  POSIX raise(sig) == kill(
// getpid(), sig); the self-signal path runs the handler synchronously inside
// the kernel, coming back through __sigTrampoline in the kernel's DP context.
int raise(int sig) {
    return kill(getpid(), sig);
}


// sigaction: install/query a handler.  GNO has no distinct sigaction primitive
// (only Ksignal), so this is a thin layer over signal(): sa_mask/sa_flags are
// accepted but not honored.  act==NULL only reports the current disposition.
int sigaction(int sig, const struct sigaction *act, struct sigaction *oact) {
    sig_t old;
    if (sig < 1 || sig >= NSIG) {
        errno = EINVAL;
        return -1;
    }
    old = currentHandler[sig];
    if (act != 0) {
        if (signal(sig, (sig_t)act->sa_handler) == SIG_ERR) {
            return -1;
        }
    }
    if (oact != 0) {
        oact->sa_handler = (void (*)(int))old;
        oact->sa_mask    = 0;
        oact->sa_flags   = 0;
    }
    return 0;
}


// sigblock: BSD -- add mask to the blocked set, return the previous mask.
unsigned long sigblock(unsigned long mask) {
    int err = 0;
    return Ksigblock((void *)mask, &err);
}


// signal: install func as the disposition for sig.  SIG_DFL/SIG_IGN are handed
// straight to the kernel (no callback, no ABI mismatch).  A real handler is
// stored in userHandlers[] and __sigTrampoline is what the kernel calls -- it
// adapts the kernel's ORCA/C callee-pops frame to our arg0-in-A handler.  We
// snapshot our process DP first (the trampoline needs it for the self-signal
// path).  Returns the PREVIOUS user-visible disposition.
sig_t signal(int sig, sig_t func) {
    int   err = 0;
    sig_t old;
    if (sig < 1 || sig >= NSIG) {
        errno = EINVAL;
        return SIG_ERR;
    }
    old = currentHandler[sig];
    if (func == SIG_DFL || func == SIG_IGN) {
        userHandlers[sig] = 0;
        Ksignal(sig, (void *)func, &err);
    } else {
        __gnoCaptureDP();
        userHandlers[sig] = (void (*)(int))func;
        Ksignal(sig, (void *)&__sigTrampoline, &err);
    }
    currentHandler[sig] = func;
    return old;
}


// sigprocmask: examine/change the blocked-signal mask, over Ksigblock (OR-in)
// and Ksigsetmask (replace).  Ksigblock(0) reads the current mask race-free.
int sigprocmask(int how, const sigset_t *set, sigset_t *oset) {
    int           err = 0;
    unsigned long old;
    unsigned long newSet;
    newSet = set ? (unsigned long)*set : 0;
    switch (how) {
        case SIG_BLOCK:
            old = Ksigblock((void *)newSet, &err);
            break;
        case SIG_UNBLOCK:
            old = Ksigblock((void *)0, &err);           // read current
            Ksigsetmask((void *)(old & ~newSet), &err); // clear the bits
            break;
        case SIG_SETMASK:
            old = Ksigsetmask((void *)newSet, &err);
            break;
        default:
            errno = EINVAL;
            return -1;
    }
    if (oset) {
        *oset = old;
    }
    return 0;
}


// sigsetmask: BSD -- replace the blocked set, return the old mask.
unsigned long sigsetmask(unsigned long mask) {
    int err = 0;
    return Ksigsetmask((void *)mask, &err);
}


// sigsuspend: atomically install mask and block until a signal is delivered.
// Ksigpause swaps the mask in, sleeps, and restores it on wake; POSIX
// sigsuspend always reports EINTR.
int sigsuspend(const sigset_t *mask) {
    int err = 0;
    Ksigpause((void *)(mask ? (unsigned long)*mask : 0), &err);
    errno = EINTR;
    return -1;
}
