// Composite "real program" regression for GNO/ME: a SINGLE process that chains
// every verified POSIX subsystem in one run, in an order chosen to stress the
// interactions the isolated probes never exercise -- fork AFTER file I/O (does a
// thread-spawn disturb the parent's FST/heap state?), signals AFTER fork (does
// SIGALRM delivery survive a fork/wait cycle?), and exec AFTER all of it (does
// image-replacement still work once the process has opened files, installed a
// handler, and touched the heap?).
//
// A stage counter at 0x025002 is bumped at the START of each stage, so a hang or
// crash pinpoints the offending subsystem in a single MAME run (instrument
// before theorizing).  Run with GNO_PROG2=demos/gnoExeced.omf so the final
// execvp actually transfers control.
//
// Bank-2 absolute uint16 markers (DBR-independent), gnoHeapProbe convention:
//   0x025000  C0DE  reached end (set by gnoExeced after exec, else by fallback)
//   0x025002  0007  stage counter: last stage reached (1..7); 7 == pre-exec
//   0x025010  E0EC  exec transferred control to gnoExeced (set by the 2nd stage)
//   0x025020  0001  env: setenv/getenv("COMPOSITE") round-trip
//   0x025022  0001  file+stat: write/read/verify + stat st_size==16 + lseek
//   0x025024  0001  id/stat: fstat(1) char dev + isatty(1) + stat(".") dir
//   0x025026  0001  fork+waitpid: leaf child _exit(42), WEXITSTATUS==42, pid match
//   0x025028  0001  signals: SIGALRM via __sigTrampoline fired, pause() resumed
//   0x02502A  0001  times(): ret==0 && tms_stime==0 && tms_cstime==0
//   0x02502E  C41D  fork child was scheduled + ran (set by the child)
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/times.h>
#include <signal.h>

extern __attribute__((noreturn)) void _exit(int status);

#define M(a)  (*(volatile uint16_t *)(a))
#define OK    0x0001
#define BAD   0xDEAD

static const char    kPath[]   = "composite.tmp";
static const uint8_t kData[16] = {
    0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
    0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50
};

static volatile uint16_t rang;


// GNO fork child: a LEAF using only absolute-long stores + _exit (fresh DP).
static void child(void) {
    M(0x02502EUL) = 0xC41D;
    _exit(42);
}


static void onAlarm(int sig) {
    (void)sig;
    rang = 1;
    M(0x025028UL) = OK;                  // handler executed via the trampoline
}


int main(void) {
    struct stat sb;
    struct tms  tb;
    uint8_t     rbuf[16];
    char       *ev;
    char       *cargv[2];
    long        n;
    long        pos;
    char        one;
    int         fd;
    int         pid;
    int         st;
    int         w;
    uint8_t     i;
    uint16_t    ok;

    M(0x025000UL) = 0x0000;
    M(0x025010UL) = BAD;
    M(0x025020UL) = BAD;
    M(0x025022UL) = BAD;
    M(0x025024UL) = BAD;
    M(0x025026UL) = BAD;
    M(0x025028UL) = BAD;
    M(0x02502AUL) = BAD;
    M(0x02502EUL) = BAD;

    // Stage 1: environment round-trip (GS/OS shell-variable table).
    M(0x025002UL) = 1;
    if (setenv("COMPOSITE", "yes", 1) == 0) {
        ev = getenv("COMPOSITE");
        if (ev != 0 && ev[0] == 'y' && ev[1] == 'e' && ev[2] == 's' && ev[3] == 0) {
            M(0x025020UL) = OK;
        }
    }

    // Stage 2: file I/O + stat on the SAME file (compose fd layer + stat).
    M(0x025002UL) = 2;
    ok = OK;
    fd = creat(kPath, 0);
    if (fd >= 4) {
        n = write(fd, kData, 16);
        if (n != 16) {
            ok = BAD;
        }
        close(fd);
    } else {
        ok = BAD;
    }
    if (stat(kPath, &sb) != 0 || sb.st_size != 16) {
        ok = BAD;                        // stat on a real file we just wrote
    }
    fd = open(kPath, O_RDONLY);
    if (fd >= 4) {
        n = read(fd, rbuf, 16);
        if (n != 16) {
            ok = BAD;
        }
        for (i = 0; i < 16; i++) {
            if (rbuf[i] != kData[i]) {
                ok = BAD;
            }
        }
        pos = lseek(fd, 4, SEEK_SET);
        one = 0;
        read(fd, &one, 1);
        if (pos != 4 || (uint8_t)one != kData[4]) {
            ok = BAD;
        }
        close(fd);
    } else {
        ok = BAD;
    }
    if (unlink(kPath) != 0) {
        ok = BAD;
    }
    M(0x025022UL) = ok;

    // Stage 3: fstat/isatty on the console + stat(".") + credential calls.
    M(0x025002UL) = 3;
    ok = OK;
    if (fstat(1, &sb) != 0 || !S_ISCHR(sb.st_mode)) {
        ok = BAD;
    }
    if (isatty(1) != 1) {
        ok = BAD;
    }
    if (stat(".", &sb) != 0 || !S_ISDIR(sb.st_mode)) {
        ok = BAD;
    }
    (void)getuid();                      // must not crash mid-composition
    (void)getgid();
    M(0x025024UL) = ok;

    // Stage 4: times() sanity (deterministic zero-fill fields).
    M(0x025002UL) = 4;
    tb.tms_stime  = 0xFFFFFFFFUL;
    tb.tms_cstime = 0xFFFFFFFFUL;
    if (times(&tb) == 0 && tb.tms_stime == 0 && tb.tms_cstime == 0) {
        M(0x02502AUL) = OK;
    }

    // Stage 5: fork a leaf child + reap with waitpid -- AFTER file I/O, to catch
    // any FST/heap corruption a thread-spawn leaves in the parent.
    M(0x025002UL) = 5;
    pid = fork((void *)child);
    if (pid > 0) {
        st = 0;
        w  = waitpid(pid, &st, 0);
        if (WIFEXITED(st) && WEXITSTATUS(st) == 42 && w == pid) {
            M(0x025026UL) = OK;
        }
    }

    // Stage 6: signals -- AFTER fork, to prove SIGALRM delivery through the
    // trampoline survives a fork/wait cycle and pause() resumes cleanly.
    M(0x025002UL) = 6;
    rang = 0;
    signal(SIGALRM, onAlarm);
    alarm(1);
    pause();
    if (rang != 1) {
        M(0x025028UL) = BAD;             // handler did NOT run (overrides onAlarm)
    }

    // Stage 7: exec into the 2nd program -- AFTER all the above.  On success this
    // never returns; gnoExeced sets 0x025010=E0EC and 0x025000=C0DE.
    M(0x025002UL) = 7;
    cargv[0] = "gnoexeced";
    cargv[1] = 0;
    execvp("gnoexeced", cargv);

    // Reached only if the exec-into failed.
    M(0x025000UL) = 0xC0DE;
    for (volatile unsigned long j = 0; j < 300000UL; j++) {
    }
    return 0;
}
