/*
 * DF-3187 patcher: offline-patch a HAMMER1 image so a PFS record leaf
 * carries an out-of-range data_len (default -1).
 *
 * Scans the image at 512-byte granularity for valid B-Tree leaf nodes
 * (self-identified by their stored node CRC), locates elements with
 * rec_type == HAMMER_RECTYPE_PFS (0x15), rewrites leaf->data_len and
 * recomputes the node CRC (auto-detects vol_version<=6 crc32 vs >=7
 * iscsi_crc32 by checking which algorithm reproduces the stored CRC).
 *
 * usage: ./patch_dlen <image> [value]   (default value = -1)
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <fcntl.h>
#include <unistd.h>

#define	BUFSIZE		4096		/* hammer btree node size */
#define	BTREE_TYPE_L	(('L') & 0xff)
#define	RECTYPE_PFS	0x0015
#define	LOCALIZE_MISC	0x00000002

/* ------------------------------------------------------------------ */
/* CRC-32 (poly 0xEDB88320, init/xorout 0xFFFFFFFF)  - vol version <=6 */
static uint32_t crc32_tab[256];
static void
crc32_init(void)
{
	for (uint32_t i = 0; i < 256; i++) {
		uint32_t c = i;
		for (int k = 0; k < 8; k++)
			c = (c & 1) ? 0xEDB88320U ^ (c >> 1) : c >> 1;
		crc32_tab[i] = c;
	}
}
static uint32_t
crc32_buf(const void *p, size_t n)
{
	const unsigned char *b = p;
	uint32_t c = 0xFFFFFFFFU;
	for (size_t i = 0; i < n; i++)
		c = crc32_tab[(c ^ b[i]) & 0xff] ^ (c >> 8);
	return (c ^ 0xFFFFFFFFU);
}

/* CRC-32C Castagnoli (poly 0x82F63B78)             - vol version >=7 */
static uint32_t crc32c_tab[256];
static void
crc32c_init(void)
{
	for (uint32_t i = 0; i < 256; i++) {
		uint32_t c = i;
		for (int k = 0; k < 8; k++)
			c = (c & 1) ? 0x82F63B78U ^ (c >> 1) : c >> 1;
		crc32c_tab[i] = c;
	}
}
static uint32_t
crc32c_buf(const void *p, size_t n)
{
	const unsigned char *b = p;
	uint32_t c = 0xFFFFFFFFU;
	for (size_t i = 0; i < n; i++)
		c = crc32c_tab[(c ^ b[i]) & 0xff] ^ (c >> 8);
	return (c ^ 0xFFFFFFFFU);
}

int
main(int argc, char **argv)
{
	unsigned char node[BUFSIZE];
	uint32_t stored, calc_a, calc_b, newcrc;
	int32_t value = -1;
	off_t off;
	int fd, count, patched = 0, nodes = 0;

	if (argc < 2) {
		fprintf(stderr, "usage: %s <image> [value]\n", argv[0]);
		exit(2);
	}
	if (argc >= 3)
		value = (int32_t)strtol(argv[2], NULL, 0);

	crc32_init();
	crc32c_init();

	fd = open(argv[1], O_RDWR);
	if (fd < 0) { perror("open"); exit(2); }

	for (off = 0; ; off += 512) {
		if (lseek(fd, off, SEEK_SET) < 0) break;
		if (read(fd, node, BUFSIZE) != BUFSIZE) break;

		/* node header: crc(0) r01(4) parent(8) count(16) type(20) */
		memcpy(&stored, node, 4);
		memcpy(&count, node + 16, 4);
		if (node[20] != BTREE_TYPE_L)
			continue;
		if (count < 1 || count > 63)
			continue;
		/* CRC over node minus first 4 bytes */
		calc_a = crc32_buf(node + 4, BUFSIZE - 4);
		calc_b = crc32c_buf(node + 4, BUFSIZE - 4);
		if (stored != calc_a && stored != calc_b)
			continue;	/* not a valid node at this offset */
		nodes++;
		{
			int is_c = (stored == calc_b);
			int hit = 0;
			for (int i = 0; i < count; i++) {
				unsigned char *elm = node + 64 + 64 * i;
				uint16_t rt;
				uint32_t loc;
				int32_t dl;
				memcpy(&rt, elm + 32, 2);	/* base.rec_type */
				memcpy(&loc, elm + 36, 4);	/* base.localization */
				memcpy(&dl, elm + 56, 4);	/* leaf.data_len */
				if (rt == RECTYPE_PFS && loc == LOCALIZE_MISC) {
					int64_t key;
					memcpy(&key, elm + 8, 8);
					if (key == 0)
						continue;	/* PFS#0 read at mount: leave intact */
					printf("found PFS leaf @off=%jd: "
					       "key(pfs lo)=%08jx "
					       "data_len=%d -> %d\n",
					       (intmax_t)off,
					       (uintmax_t)key, dl, value);
					dl = value;
					memcpy(elm + 48, &dl, 4);
					hit = 1;
					patched++;
				}
			}
			if (hit) {
				newcrc = is_c ? crc32c_buf(node + 4, BUFSIZE - 4)
					      : crc32_buf(node + 4, BUFSIZE - 4);
				memcpy(node, &newcrc, 4);
				if (lseek(fd, off, SEEK_SET) < 0 ||
				    write(fd, node, BUFSIZE) != BUFSIZE) {
					perror("write"); exit(1);
				}
				printf("patched node @off=%jd (crc algo %s)\n",
				       (intmax_t)off, is_c ? "iscsi/v7" : "crc32/v6");
			}
		}
	}
	printf("done: %d valid leaf nodes scanned, %d PFS records patched "
	       "to data_len=%d\n", nodes, patched, value);
	close(fd);
	return (patched == 0);
}
