/*
 * DF-0770 — Record sync error silently dropped in hammer_sync_inode.
 * Variant C: metadata-heavy burst (file create + rename + delete) to trigger
 * flush-time B-tree allocation failure.
 *
 * The bug (sys/vfs/hammer/hammer_inode.c:3066-3073):
 *   if (error == 0) {
 *       tmp_error = RB_SCAN(... hammer_sync_record_callback, &cursor);
 *       if (tmp_error < 0)
 *           tmp_error = -error;     // BUG: should be -tmp_error
 *       if (tmp_error)
 *           error = tmp_error;
 *   }
 *
 * hammer_sync_record_callback returns -errno on failure (line 2876-2877
 * `error = -error`).  Because we are inside `if (error == 0)`, the buggy
 * line `tmp_error = -error` always yields 0, silently dropping the error.
 * hammer_sync_inode returns 0 -> hammer_sync_inode_done sets ip->error = 0
 * -> hammer_vop_fsync (hammer_vnops.c:293) returns 0 to userspace.
 *
 * This variant creates a flood of new files, renames them, deletes them,
 * forcing many record additions and deletions in the B-tree.  Combined
 * with filesystem pressure, this can trigger a flush-time B-tree alloc
 * failure where the bug's signal (fsync returning 0) is observable.
 *
 * Definitive proof of the bug is the code-level trace in VERDICT.md;
 * this runtime PoC is corroborating evidence.
 */

#include <sys/fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#define TARGET_DIR "/mnt/hammer/df0770"
#define FILL_DIR   "/mnt/hammer/fill"
#define CHUNK_SZ   (4 * 1024 * 1024)
#define N_OPS      4000

static char buf[CHUNK_SZ];

int main(void) {
    char path[256], path2[256];
    int fd, idx;
    int total_fsync = 0, fsync_success = 0, fsync_fail = 0;
    long total_written = 0;

    mkdir(TARGET_DIR, 0777);
    mkdir(FILL_DIR, 0777);
    memset(buf, 0xA5, sizeof(buf));

    /* Stage 1: fill until 90% full (leave ~1GB free for B-tree ops). */
    printf("[*] Stage 1: prefilling filesystem to ~90%%\n");
    int fill_idx = 0;
    for (;;) {
        snprintf(path, sizeof(path), "%s/bigfill_%06d", FILL_DIR, fill_idx);
        fd = open(path, O_WRONLY|O_CREAT|O_EXCL, 0644);
        if (fd < 0) break;
        ssize_t n;
        while ((n = write(fd, buf, sizeof(buf))) > 0) total_written += n;
        close(fd);
        if (total_written > 10L * 1024 * 1024 * 1024) break;   /* 10GB */
        fill_idx++;
    }
    printf("    prefill done, total=%ld MB\n", total_written / (1024*1024));
    sync();
    sleep(1);

    /* Stage 2: hammer metadata ops + fsync to trigger flush-time errors. */
    printf("\n[*] Stage 2: %d create+rename+fsync+delete ops\n", N_OPS);
    for (idx = 0; idx < N_OPS; idx++) {
        snprintf(path, sizeof(path), "%s/op_%06d", TARGET_DIR, idx);
        fd = open(path, O_WRONLY|O_CREAT|O_EXCL, 0644);
        if (fd < 0) {
            if (errno == ENOSPC || errno == EROFS) { /* FS forced ronly */ }
            continue;
        }
        write(fd, buf, 4096);
        /* rename creates a delete + add record */
        snprintf(path2, sizeof(path2), "%s/op_%06d.rn", TARGET_DIR, idx);
        rename(path, path2);
        int rc = fsync(fd);
        total_fsync++;
        if (rc == 0) fsync_success++;
        else         fsync_fail++;
        if (rc == 0 && (idx % 200) == 0)
            printf("    fsync #%d: rc=0\n", idx);
        else if (rc < 0)
            printf("    fsync #%d: errno=%d (%s)\n", idx, errno, strerror(errno));
        close(fd);
    }

    printf("\n=== DF-0770 SUMMARY ===\n");
    printf("total fsync calls: %d\n", total_fsync);
    printf("  rc=0 (success)  : %d\n", fsync_success);
    printf("  rc<0 (error)    : %d\n", fsync_fail);
    printf("\nDefinitive proof: code-level trace in VERDICT.md (lines 3066-3073).\n");
    printf("Runtime signal: if dmesg shows 'Critical error'/'Forcing read-only'\n");
    printf("  while fsync still returned 0, the dropped error reached userspace.\n");
    return 0;
}
