/* API observations on disposable files, not a power-loss test. */
#define _POSIX_C_SOURCE 200809L
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

static void require(int ok, const char *label) {
    if (!ok) {
        fprintf(stderr, "Check failed: %s (errno=%d)\n", label, errno);
        exit(1);
    }
}

static int fresh(size_t bytes) {
    char name[] = "mmap-boundaries-XXXXXX";
    int fd = mkstemp(name);
    require(fd >= 0, "mkstemp");
    require(unlink(name) == 0, "unlink temporary file");
    require(ftruncate(fd, (off_t)bytes) == 0, "initial file size");
    require(pwrite(fd, "A", 1, 0) == 1, "initial byte");
    return fd;
}

static unsigned char *mapping(int fd, size_t bytes, int flags) {
    void *p = mmap(NULL, bytes, PROT_READ | PROT_WRITE, flags, fd, 0);
    require(p != MAP_FAILED, "mmap");
    return p;
}

static char first_byte(int fd) {
    char byte;
    require(pread(fd, &byte, 1, 0) == 1, "read first byte");
    return byte;
}

static off_t length(int fd) {
    struct stat st;
    require(fstat(fd, &st) == 0, "fstat");
    return st.st_size;
}

int main(void) {
    long raw_page = sysconf(_SC_PAGESIZE);
    require(raw_page > 0 && raw_page <= 1048576, "supported page size");
    size_t page = (size_t)raw_page;

    int fd = fresh(page);
    unsigned char *p = mapping(fd, page, MAP_PRIVATE);
    p[0] = 'P';
    require(msync(p, page, MS_SYNC) == 0, "private msync");
    char private_read = first_byte(fd);
    require(p[0] == 'P' && private_read == 'A', "private isolation");
    require(munmap(p, page) == 0 && close(fd) == 0, "private cleanup");

    fd = fresh(page);
    p = mapping(fd, page, MAP_SHARED);
    p[0] = 'S';
    char shared_before = first_byte(fd);
    require(msync(p, page, MS_SYNC) == 0, "shared msync");
    char shared_after = first_byte(fd);
    require(shared_before == 'S' && shared_after == 'S', "shared cached reads");
    require(munmap(p, page) == 0 && close(fd) == 0, "shared cleanup");

    fd = fresh(page);
    p = mapping(fd, page, MAP_SHARED);
    require(close(fd) == 0, "close mapped descriptor");
    char after_close = (char)((volatile unsigned char *)p)[0];
    require(after_close == 'A', "mapping survives descriptor close");
    require(munmap(p, page) == 0, "closed descriptor cleanup");

    fd = fresh(page);
    p = mapping(fd, page * 2, MAP_SHARED);
    off_t unchanged = length(fd);
    require(unchanged == (off_t)page, "mapping does not extend file");
    require(munmap(p, page * 2) == 0 && close(fd) == 0, "oversize cleanup");

    fd = fresh(page - 1);
    p = mapping(fd, page, MAP_SHARED);
    unsigned int tail_initial = ((volatile unsigned char *)p)[page - 1];
    ((volatile unsigned char *)p)[page - 1] = 'T';
    unsigned int tail_stored = ((volatile unsigned char *)p)[page - 1];
    require(tail_initial == 0 && tail_stored == 'T', "partial-page tail access");
    require(msync(p, page, MS_SYNC) == 0, "partial-page msync");
    off_t tail_size = length(fd);
    char eof_byte;
    ssize_t tail_read = pread(fd, &eof_byte, 1, (off_t)(page - 1));
    require(tail_size == (off_t)(page - 1) && tail_read == 0,
            "tail store does not extend the file");
    require(munmap(p, page) == 0 && close(fd) == 0, "partial-page cleanup");

    fd = fresh(page);
    p = mapping(fd, page, MAP_SHARED);
    errno = 0;
    int unaligned_result = msync(p + 1, page - 1, MS_SYNC);
    int unaligned_errno = errno;
    require(unaligned_result == -1 && unaligned_errno == EINVAL,
            "unaligned msync reports EINVAL");
    require(munmap(p, page) == 0 && close(fd) == 0, "unaligned cleanup");

    fd = fresh(page * 2);
    p = mapping(fd, page * 2, MAP_SHARED);
    require(ftruncate(fd, (off_t)page) == 0, "shrink to one whole page");
    require(length(fd) == (off_t)page, "measured size after shrink");
    pid_t child = fork();
    require(child >= 0, "fork");
    if (child == 0) {
        struct rlimit no_core = {0, 0};
        if (setrlimit(RLIMIT_CORE, &no_core) != 0) _exit(120);
        /* RLIMIT_CORE alone does not suppress a piped core-dump handler. */
        if (prctl(PR_SET_DUMPABLE, 0L) != 0) _exit(123);
        struct sigaction action = {0};
        action.sa_handler = SIG_DFL;
        if (sigemptyset(&action.sa_mask) != 0 ||
            sigaction(SIGBUS, &action, NULL) != 0) _exit(121);
        sigset_t unblocked;
        if (sigemptyset(&unblocked) != 0 ||
            sigaddset(&unblocked, SIGBUS) != 0 ||
            sigprocmask(SIG_UNBLOCK, &unblocked, NULL) != 0) _exit(122);
        volatile unsigned char byte = ((volatile unsigned char *)p)[page];
        (void)byte;
        _exit(0);
    }
    int status;
    pid_t waited;
    do { waited = waitpid(child, &status, 0); } while (waited < 0 && errno == EINTR);
    require(waited == child, "waitpid");
    require(WIFSIGNALED(status) && WTERMSIG(status) == SIGBUS,
            "discarded whole page raises SIGBUS");
    require(munmap(p, page * 2) == 0 && close(fd) == 0, "shrink cleanup");

    printf("{\"page_size_bytes\":%zu,\"private\":{\"mapped_byte\":\"P\","
           "\"file_byte_after_sync\":\"%c\",\"msync_return\":0},"
           "\"shared\":{\"file_byte_before_sync\":\"%c\","
           "\"file_byte_after_sync\":\"%c\",\"msync_return\":0},"
           "\"closed_descriptor\":{\"mapped_byte\":\"%c\"},"
           "\"oversize_mapping\":{\"mapping_bytes\":%zu,\"file_bytes\":%lld},"
           "\"partial_tail\":{\"initial_byte\":%u,\"stored_byte_before_sync\":%u,"
           "\"file_bytes_after_sync\":%lld,\"pread_at_eof_bytes\":%zd,\"msync_return\":0},"
           "\"unaligned_sync\":{\"address_offset_bytes\":1,\"return_value\":%d,"
           "\"errno_name\":\"EINVAL\"},"
           "\"shrink\":{\"initial_bytes\":%zu,\"final_bytes\":%zu,"
           "\"access_offset_bytes\":%zu,\"child_signal\":\"SIGBUS\"}}\n",
           page, private_read, shared_before, shared_after, after_close,
           page * 2, (long long)unchanged, tail_initial, tail_stored,
           (long long)tail_size, tail_read, unaligned_result, page * 2, page, page);
    return 0;
}
