/*
 * DF-2628 PoC: hammer2_vop_nlink() increments ip->meta.nlinks even when
 * hammer2_dirent_create() fails, permanently desynchronizing the
 * persistent link count from the number of real directory entries.
 *
 * Deterministic trigger (no ENOSPC race needed):
 *   hammer2_dirhash() maps delimiter-free names to
 *     key = 0x8.. | (c<<32) | ((c^(c<<16))&0xFFFF0000) | 0x8000,
 *   c = CRC32C(name).  32768 names sharing one c fill the whole 64K
 *   collision window (slots X8000..XFFFF); hammer2_dirent_create() then
 *   returns ENOSPC for any further same-c name (hammer2_inode.c:1324)
 *   while hammer2_vop_nlink() still does ++nlinks (hammer2_vnops.c:1602).
 *
 * Phases:
 *   fill <dir> <namesfile> <count>       create count files (packs the window)
 *   trigger <basefile> <target> <times>  stat nlink, loop link(), stat nlink
 *   ctrl <basefile> <newname>            successful link() control (other window)
 *   probe <file>                         print st_nlink
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>

static long
nlink_of(const char *p)
{
	struct stat st;
	if (stat(p, &st) < 0) {
		fprintf(stderr, "stat %s: %s\n", p, strerror(errno));
		return -1;
	}
	return (long)st.st_nlink;
}

static int
do_fill(const char *dir, const char *namesfile, int count)
{
	char line[64], path[4096];
	FILE *f = fopen(namesfile, "r");
	int made = 0, n = 0;
	time_t t0 = time(NULL);

	if (!f) { perror(namesfile); return 1; }
	if (chdir(dir) < 0) { perror(dir); return 1; }
	while (made < count && fgets(line, sizeof(line), f)) {
		size_t len = strlen(line);
		int fd;
		while (len && (line[len-1] == '\n' || line[len-1] == '\r'))
			line[--len] = 0;
		if (len == 0)
			continue;
		++n;
		fd = open(line, O_CREAT | O_EXCL | O_WRONLY, 0644);
		if (fd < 0) {
			if (errno == EEXIST)
				continue;
			fprintf(stderr, "fill: open(%s): %s\n",
			    line, strerror(errno));
			break;
		}
		close(fd);
		++made;
		if ((made % 8192) == 0)
			fprintf(stderr, "  %d files (%lds)\n",
			    made, (long)(time(NULL) - t0));
	}
	fclose(f);
	printf("FILL done: %d created of %d names, %lds\n",
	    made, n, (long)(time(NULL) - t0));
	return (made == count) ? 0 : 2;
}

static int
do_trigger(const char *base, const char *target, int times)
{
	long before, after;
	int i, fails = 0, first_errno = 0;
	char tname[4096];

	before = nlink_of(base);
	printf("BASE %s nlink_before=%ld\n", base, before);
	for (i = 0; i < times; ++i) {
		errno = 0;
		/* distinct name per attempt: a successful link consumes
		 * its name, a failed one must NOT create anything */
		snprintf(tname, sizeof(tname), "%s.%03d", target, i);
		if (link(base, tname) < 0) {
			if (!first_errno)
				first_errno = errno;
			++fails;
		}
	}
	after = nlink_of(base);
	printf("TRIGGER: %d link() attempts, %d failed (errno=%d/%s)\n",
	    times, fails, first_errno, strerror(first_errno ? first_errno : 0));
	printf("BASE %s nlink_after=%ld  delta=%ld\n", base, after,
	    after - before);
	/* how many of the target names actually exist? */
	{
		int exist = 0;
		for (i = 0; i < times; ++i) {
			snprintf(tname, sizeof(tname), "%s.%03d", target, i);
			if (access(tname, F_OK) == 0)
				++exist;
		}
		printf("TARGET names created: %d of %d\n", exist, times);
	}
	if (fails > 0 && after - before > 0) {
		printf("RESULT: BUG REPRODUCED - %d failed link() calls but "
		    "nlink rose %ld->%ld\n", fails, before, after);
		return 0;
	}
	if (fails == times && after == before) {
		printf("RESULT: all link() failed, nlink UNCHANGED (fixed "
		    "kernel?)\n");
		return 1;
	}
	if (fails == 0 && after - before == times) {
		printf("RESULT: all link() succeeded normally (no failure "
		    "engaged - scenario not set up)\n");
		return 4;
	}
	printf("RESULT: mixed/unexpected (fails=%d delta=%ld)\n", fails,
	    after - before);
	return 3;
}

static int
do_ctrl(const char *base, const char *newname)
{
	long before = nlink_of(base);
	int r = link(base, newname);
	printf("CTRL link(%s, %s) = %d errno=%d/%s  nlink %ld -> %ld\n",
	    base, newname, r, errno, strerror(errno), before, nlink_of(base));
	return (r == 0) ? 0 : 1;
}

int
main(int argc, char **argv)
{
	if (argc < 2)
		goto usage;
	if (!strcmp(argv[1], "fill") && argc == 5)
		return do_fill(argv[2], argv[3], atoi(argv[4]));
	if (!strcmp(argv[1], "trigger") && argc == 5)
		return do_trigger(argv[2], argv[3], atoi(argv[4]));
	if (!strcmp(argv[1], "ctrl") && argc == 4)
		return do_ctrl(argv[2], argv[3]);
	if (!strcmp(argv[1], "probe") && argc == 3) {
		printf("%s nlink=%ld\n", argv[2], nlink_of(argv[2]));
		return 0;
	}
usage:
	fprintf(stderr,
	    "usage: %s fill <dir> <namesfile> <count>\n"
	    "       %s trigger <base> <target> <times>\n"
	    "       %s ctrl <base> <newname>\n"
	    "       %s probe <file>\n", argv[0], argv[0], argv[0], argv[0]);
	return 64;
}
