#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/time.h>

#if __APPLE__
#  define st_atim st_atimespec
#  define st_ctim st_ctimespec
#  define st_mtim st_mtimespec
#  define DELAY_US  1100000  // MacOS has 1 second granularity
#else
#  define DELAY_US  11000    // Linux has ~10ms granularity
#endif


void die(const char *prefix) {
    perror(prefix);
    exit(99);
}


struct timeval *globaltime() {
    static struct timeval tv;
    if (tv.tv_sec == 0) {
        gettimeofday(&tv, NULL);
    }
    return &tv;
}


// returns a - b
long long nsdiff(struct timespec *a, struct timeval *b) {
    return (a->tv_sec - b->tv_sec) * 1000000000LL +
           (a->tv_nsec - (b->tv_usec * 1000));
}


long long mtime() {
    struct stat st;
    if (stat("testfile", &st) != 0) die("stat");
    return nsdiff(&st.st_mtim, globaltime());
}


void print(const char *s) {
    static long long lastv;
    long long v = mtime();
    printf("%15.9f %s after %s\n",
        1e-9 * (double)v,
        v != lastv ? "*" : " ",
        s);
    lastv = v;
    fflush(stdout);
    usleep(DELAY_US);
}


unsigned char zbuf[4096];

int main() {
    unlink("testfile");
    int fd = open("testfile", O_RDWR | O_CREAT | O_EXCL);
    if (fd < 0) die("open");
    print("start");
    if (write(fd, zbuf, sizeof(zbuf)) != sizeof(zbuf)) die("write");
    print("write");
    void *mbuf = mmap(NULL, sizeof(zbuf),
        PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    unsigned char volatile *buf = mbuf;
    if (!buf || buf == MAP_FAILED) die("mmap");
    print("mmap (should be unchanged)");
    if (close(fd)) die("close");
    print("close (should be unchanged)");
    buf[0]++;
    print("dirty (changes on Linux, not on FreeBSD)");
    buf[0]++;
    buf[1]++;
    print("continue to dirty (probably does not change)");
    if (msync(mbuf, sizeof(zbuf), MS_SYNC) != 0) die("msync");
    print("msync (should change if previous ones didn't)");
    buf[0]++;
    print("redirty (changes on Linux, not on FreeBSD)");
    buf[1]++;
    if (msync(mbuf, sizeof(zbuf), MS_SYNC) != 0) die("msync");
    print("msync (should change if previous ones didn't)");
    buf[2]++;
    print("redirty (changes on Linux, not on FreeBSD)");
    if (munmap(mbuf, sizeof(zbuf)) != 0) die("munmap");
    print("munmap (should change if previous one didn't)");
    return 0;
}
