/*
 * DF pass-2 live negative-test harness for sys/platform/pc64/x86_64/tls.c
 * Unprivileged boundary matrix for set_tls_area(472) / get_tls_area(473).
 *
 * Discipline: libc's errno is %fs-relative on x86_64, so the parent must
 * never leave a wild %fs base installed across any libc call. Every
 * successful set_tls test is followed by restore(); wild-base probes are
 * done in forked children (cpu_fork copies the pcb, inheriting the base).
 */
#include <sys/syscall.h>
#include <sys/tls.h>
#include <sys/wait.h>
#include <errno.h>
#include <limits.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#ifndef SYS_set_tls_area
#define SYS_set_tls_area 472
#endif
#ifndef SYS_get_tls_area
#define SYS_get_tls_area 473
#endif

static struct tls_info orig_fs, orig_gs;
static int orig_saved;

static int set_tls(int which, void *base, long size, size_t infosz)
{
    struct tls_info info;
    info.base = base;
    info.size = size;
    return syscall(SYS_set_tls_area, which, &info, infosz);
}
static int raw_set_tls(int which, void *iptr, size_t infosz)
{
    return syscall(SYS_set_tls_area, which, iptr, infosz);
}
static int get_tls(int which, struct tls_info *out, size_t infosz)
{
    return syscall(SYS_get_tls_area, which, out, infosz);
}

static void save_orig(void)
{
    if (!orig_saved) {
        get_tls(0, &orig_fs, sizeof(orig_fs));
        get_tls(1, &orig_gs, sizeof(orig_gs));
        orig_saved = 1;
    }
}
static void restore(void)
{
    syscall(SYS_set_tls_area, 0, &orig_fs, sizeof(orig_fs));
    syscall(SYS_set_tls_area, 1, &orig_gs, sizeof(orig_gs));
}

/* Fork a child that inherits the CURRENT pcb (incl. wild fs base) and
 * touches %fs:0. Parent restores immediately after fork. */
static void probe_fs_child(void)
{
    pid_t p = fork();
    if (p == 0) {
        void *self;
        __asm __volatile("movq %%fs:0, %0" : "=r"(self) :: "memory");
        printf("    child %%fs:0 read OK -> 0x%lx (NO fault)\n",
               (unsigned long)self);
        _exit(0);
    }
    restore();
    int st = -1;
    waitpid(p, &st, 0);
    if (WIFSIGNALED(st))
        printf("    child %%fs access -> SIGNAL %d (user-mode fault only)\n",
               WTERMSIG(st));
    else
        printf("    child exited %d\n", WEXITSTATUS(st));
}

int main_matrix(void)
{
    struct tls_info info;
    int rc;

    setvbuf(stdout, NULL, _IONBF, 0);
    save_orig();
    printf("orig fs base=0x%lx size=0x%lx gs base=0x%lx\n",
           (unsigned long)orig_fs.base, (unsigned long)orig_fs.size,
           (unsigned long)orig_gs.base);

    printf("== which-index validation (tls.c:82-84, 132-134) ==\n");
    errno = 0; rc = set_tls(-1, (void *)0x1000, -1, sizeof(info));
    printf("set which=-1      rc=%d errno=%d(%s) EXPECT -1 ERANGE\n", rc,
           errno, strerror(errno));
    errno = 0; rc = set_tls(2, (void *)0x1000, -1, sizeof(info));
    printf("set which=2       rc=%d errno=%d(%s) EXPECT -1 ERANGE\n", rc,
           errno, strerror(errno));
    errno = 0; rc = set_tls(INT_MIN, (void *)0x1000, -1, sizeof(info));
    printf("set which=INT_MIN rc=%d errno=%d(%s) EXPECT -1 ERANGE\n", rc,
           errno, strerror(errno));
    errno = 0; rc = set_tls(INT_MAX, (void *)0x1000, -1, sizeof(info));
    printf("set which=INT_MAX rc=%d errno=%d(%s) EXPECT -1 ERANGE\n", rc,
           errno, strerror(errno));
    errno = 0; rc = get_tls(1, &info, sizeof(info));
    printf("get which=1       rc=%d errno=%d EXPECT 0\n", rc, errno);

    printf("== size validation, signed long (tls.c:100-101) ==\n");
    errno = 0; rc = set_tls(0, (void *)0x1000, -2, sizeof(info));
    restore();
    printf("set size=-2       rc=%d errno=%d(%s) EXPECT -1 EINVAL\n", rc,
           errno, strerror(errno));
    errno = 0; rc = set_tls(0, (void *)0x1000, INT64_MIN, sizeof(info));
    restore();
    printf("set size=INT64MIN rc=%d errno=%d(%s) EXPECT -1 EINVAL\n", rc,
           errno, strerror(errno));
    errno = 0; rc = set_tls(0, (void *)0x1000, -1, sizeof(info));
    restore();
    printf("set size=-1       rc=%d errno=%d EXPECT 0 (sentinel ok)\n", rc,
           errno);
    errno = 0; rc = set_tls(0, (void *)0x1000, INT64_MAX, sizeof(info));
    restore();
    printf("set size=INT64MAX rc=%d errno=%d EXPECT 0\n", rc, errno);

    printf("== infosize handling, size_t (tls.c:85-99) ==\n");
    errno = 0; rc = raw_set_tls(0, (void *)-1, 0);
    restore();
    printf("set infosize=0 ptr=-1 rc=%d errno=%d EXPECT 0 (len-0 copyin)\n",
           rc, errno);
    errno = 0; rc = raw_set_tls(0, (void *)-1, 1);
    printf("set infosize=1 ptr=-1 rc=%d errno=%d(%s) EXPECT -1 EFAULT\n", rc,
           errno, strerror(errno));
    errno = 0; rc = set_tls(0, (void *)0x2000, -1, (size_t)-1);
    restore();
    printf("set infosize=~0   rc=%d errno=%d EXPECT 0 (min clamps)\n", rc,
           errno);

    printf("== get_tls_area destination (tls.c:135-141) ==\n");
    errno = 0;
    rc = get_tls(0, (void *)0xffff800000000000UL, sizeof(info));
    printf("get to kern addr  rc=%d errno=%d(%s) EXPECT -1 EFAULT\n", rc,
           errno, strerror(errno));
    errno = 0; rc = get_tls(0, (void *)-1, 0);
    printf("get infosize=0    rc=%d errno=%d EXPECT 0 (len-0 copyout)\n", rc,
           errno);
    errno = 0; rc = get_tls(0, &info, (size_t)-1);
    printf("get infosize=~0   rc=%d errno=%d EXPECT 0 (clamps to 16)\n", rc,
           errno);
    errno = 0; rc = get_tls(1, (struct tls_info *)16, sizeof(info));
    printf("get to NULLpage+  rc=%d errno=%d(%s) EXPECT -1 EFAULT\n", rc,
           errno, strerror(errno));

    printf("== round trip + fork inheritance (vm_machdep.c:137,229) ==\n");
    rc = set_tls(1, (void *)0x12345000, 0x1000, sizeof(info));
    printf("set gs 0x12345000/0x1000 rc=%d EXPECT 0\n", rc);
    rc = get_tls(1, &info, sizeof(info));
    printf("get gs -> 0x%lx/0x%lx EXPECT 0x12345000/0x1000\n",
           (unsigned long)info.base, (unsigned long)info.size);
    pid_t p = fork();
    if (p == 0) {
        rc = get_tls(1, &info, sizeof(info));
        printf("child-of-fork get gs -> rc=%d 0x%lx EXPECT inherited\n", rc,
               (unsigned long)info.base);
        _exit(0);
    }
    waitpid(p, NULL, 0);

    printf("== wild bases accepted by design (tls.c:103-108) ==\n");
    errno = 0; rc = set_tls(0, (void *)0xffff800000000000UL, -1, sizeof(info));
    printf("set fs kernel-canon rc=%d errno=%d EXPECT 0 (accepted)\n", rc,
           errno);
    probe_fs_child();
    errno = 0; rc = set_tls(0, (void *)0x0000800000000000UL, -1, sizeof(info));
    printf("set fs noncanon    rc=%d errno=%d EXPECT 0 (accepted)\n", rc,
           errno);
    probe_fs_child();

    printf("== exec reset (exec_setregs machdep.c:1536-1543) ==\n");
    rc = set_tls(0, (void *)0x3000, -1, sizeof(info));
    rc = get_tls(0, &info, sizeof(info));
    printf("pre-exec get fs -> 0x%lx EXPECT 0x3000\n",
           (unsigned long)info.base);
    execl("/tmp/tls_edge", "/tmp/tls_edge", "postexec", NULL);
    printf("execl failed: %s\n", strerror(errno));
    return 1;
}

int main_postexec(void)
{
    struct tls_info info;
    int rc;

    setvbuf(stdout, NULL, _IONBF, 0);
    save_orig();
    printf("== post-exec image (fresh process image, base reset by kernel) ==\n");
    rc = get_tls(0, &info, sizeof(info));
    printf("post-exec get fs rc=%d -> base=0x%lx size=0x%lx\n", rc,
           (unsigned long)info.base, (unsigned long)info.size);
    /* actual %fs base is now 0 per exec_setregs; a %fs:0 access faults */
    pid_t p = fork();
    if (p == 0) {
        void *self;
        __asm __volatile("movq %%fs:0, %0" : "=r"(self) :: "memory");
        printf("    child %%fs:0 -> 0x%lx NO FAULT\n", (unsigned long)self);
        _exit(0);
    }
    int st = -1;
    waitpid(p, &st, 0);
    if (WIFSIGNALED(st))
        printf("    %%fs:0 post-exec -> SIGNAL %d (base really reset to 0)\n",
               WTERMSIG(st));
    printf("note: td_tls readback is stale iff base above != 0 "
           "(pcb zeroed, td_tls not)\n");
    return 0;
}

int main(int argc, char **argv)
{
    if (argc > 1 && strcmp(argv[1], "postexec") == 0)
        return main_postexec();
    return main_matrix();
}
