/*
 * craft_3033.c - DF-3033 image patcher: finds the btree leaf node holding a
 * HAMMER_RECTYPE_DATA record for obj_id <oid> (or the FIRST DATA record with
 * data_len==16384), rewrites leaf->data_len = 0x20000 (128K > MAXBSIZE),
 * recomputes leaf->data_crc (crc32/crc32c over 128K at the record's data
 * offset) and the btree node crc.
 *
 * Steps around it: newfs_hammer; mount; dd 16K file; sync; umount;
 * craft_3033 <img> <oid>;  mount -u rw; read file (cache); hammer reblock.
 *
 * usage: craft_3033 <img> [oid]
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <inttypes.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <stddef.h>
#include <sys/stat.h>
#include <hammer_disk.h>
#include <hammer_btree.h>

/* ---- crc32 (IEEE) and crc32c (Castagnoli) ---- */
static uint32_t crc_tab[16][256], tabs_ready;
static void init_tabs(void)
{
	static const uint32_t poly_n[] = {0xEDB88320u,0x82F63B78u};
	if (tabs_ready) return;
	for (int k = 0; k < 2; k++) {
		for (uint32_t i = 0; i < 256; i++) {
			uint32_t c = i;
			for (int j = 0; j < 8; j++)
				c = (c & 1) ? (poly_n[k] ^ (c >> 1)) : (c >> 1);
			crc_tab[k*8+0][i] = c;
		}
	}
	for (int k = 0; k < 2; k++)
		for (int s = 1; s < 8; s++)
			for (uint32_t i = 0; i < 256; i++)
				crc_tab[k*8+s][i] = (crc_tab[k*8+s-1][i] >> 8)
					^ crc_tab[k*8+0][crc_tab[k*8+s-1][i] & 0xff];
	tabs_ready = 1;
}
static uint32_t crc_gen(int c, const void *buf, size_t len, uint32_t seed)
{
	const uint8_t *p = buf;
	uint32_t crc = seed;
	while (len >= 8) {
		crc ^= (uint32_t)p[0] | ((uint32_t)p[1]<<8) | ((uint32_t)p[2]<<16) | ((uint32_t)p[3]<<24);
		crc = crc_tab[c*8+7][crc & 0xff] ^ crc_tab[c*8+6][(crc>>8)&0xff]
		    ^ crc_tab[c*8+5][(crc>>16)&0xff] ^ crc_tab[c*8+4][crc>>24]
		    ^ crc_tab[c*8+3][p[4]] ^ crc_tab[c*8+2][p[5]]
		    ^ crc_tab[c*8+1][p[6]] ^ crc_tab[c*8+0][p[7]];
		p += 8; len -= 8;
	}
	while (len--) crc = crc_tab[c*8+0][ (crc ^ *p++) & 0xff ] ^ (crc >> 8);
	return crc;
}
static uint32_t crc32_ieee(const void *b, size_t n){ return crc_gen(0,b,n,0xFFFFFFFFu)^0xFFFFFFFFu; }
static uint32_t crc32_cast(const void *b, size_t n){ return crc_gen(1,b,n,0xFFFFFFFFu)^0xFFFFFFFFu; }
static uint32_t datacrc(uint32_t ver, const void *b, size_t n)
{ return (ver >= HAMMER_VOL_VERSION_SEVEN) ? crc32_cast(b,n) : crc32_ieee(b,n); }

#define OOF(x)  offsetof(struct hammer_volume_ondisk, x)

static uint64_t zoff_to_phys(struct hammer_volume_ondisk *hdr, int nvol,
			     hammer_off_t off, uint64_t *volsize)
{
	/* only zone 2/3-ish raw buffer translation we need: use the blockmap
	 * phys_offset of the owning zone if set, else linear map over volumes.
	 * For simplicity assume a single-volume image: phys = short offset. */
	uint64_t zone = off & 0xF000000000000000ULL;
	hammer_off_t short_off = off & ~0xF000000000000000ULL;
	if (zone == HAMMER_ZONE_RAW_BUFFER) {
		return short_off;  /* zone2 phys == short offset */
	}
	/* btree/data zones: physical = blockmap.phys_offset + short % ??
	 * For freshly newfs'd single-volume images the freemap assigns
	 * sequentially; instead scan each blockmap for phys base. */
	for (int i = 0; i < HAMMER_MAX_ZONES; i++) {
		struct hammer_blockmap *bm = (struct hammer_blockmap *)
			((char *)hdr + OOF(vol0_blockmap) +
			 i * sizeof(struct hammer_blockmap));
		if ((bm->phys_offset & 0xF000000000000000ULL) == HAMMER_ZONE_RAW_BUFFER &&
		    short_off >= (bm->phys_offset & HAMMER_OFF_SHORT_MASK) &&
		    short_off < (bm->phys_offset & HAMMER_OFF_SHORT_MASK) + (1ULL<<40)) {
			/* linear within this zone allocation area (fresh fs) */
			return (bm->phys_offset & HAMMER_OFF_SHORT_MASK) +
			       (short_off - (bm->phys_offset & HAMMER_OFF_SHORT_MASK));
		}
	}
	return short_off;
}

int main(int ac, char **av)
{
	struct hammer_volume_ondisk hdr;
	struct stat st;
	uint8_t *img;
	int fd, nvol = 1;
	uint32_t ver;
	uint64_t oid_filter = 0;
	int have_oid = 0;

	if (ac != 2 && ac != 3) {
		fprintf(stderr, "usage: craft_3033 <img> [oid]\n"); return 2;
	}
	if (ac == 3) { oid_filter = strtoull(av[2], NULL, 0); have_oid = 1; }
	init_tabs();
	fd = open(av[1], O_RDWR);
	if (fd < 0) { perror("open"); return 1; }
	fstat(fd, &st);
	img = malloc(st.st_size);
	if (pread(fd, img, st.st_size, 0) != st.st_size) { perror("read"); return 1; }
	memcpy(&hdr, img, sizeof(hdr));
	if (hdr.vol_signature != HAMMER_FSBUF_VOLUME) {
		fprintf(stderr, "not hammer\n"); return 1; }
	ver = hdr.vol_version;
	printf("vol_version=%u imgsize=%jd\n", ver, (intmax_t)st.st_size);

	/* walk vol0_next (btree root) is complex; instead scan every 16K
	 * buffer for btree leaf nodes: leaf hdr flags&1? node_type in
	 * hammer_node_ondisk.base? node->base.btype == HAMMER_BTREE_TYPE_LEAF
	 * and signature bytes 'BTRE'? hammer stores crc first; validate by
	 * testing node crc. */
	int patched = 0;
	for (uint64_t off = 0; off + sizeof(struct hammer_node_ondisk) <= (uint64_t)st.st_size; off += 16384) {
		struct hammer_node_ondisk *node = (struct hammer_node_ondisk *)(img + off);
		if (node->type != HAMMER_BTREE_TYPE_LEAF)
			continue;
		/* validate crc */
		uint32_t expect = datacrc(ver, ((uint8_t *)&node->crc) + sizeof(node->crc),
					  HAMMER_BTREE_CRCSIZE);
		if (node->crc != expect) continue;
		if (node->count <= 0 || node->count > HAMMER_BTREE_LEAF_ELMS) continue;
		for (int e = 0; e < node->count; e++) {
			struct hammer_btree_leaf_elm *leaf = &node->elms[e].leaf;
			if (leaf->base.rec_type != HAMMER_RECTYPE_DATA)
				continue;
			if (have_oid && (uint64_t)leaf->base.obj_id != oid_filter)
				continue;
			if (leaf->data_len != 16384)
				continue;
			printf("leaf @phys %#jx elm %d: obj_id=%016jx key=%016jx data_off=%016jx data_len=%u\n",
				(uintmax_t)off, e, (uintmax_t)leaf->base.obj_id,
				(uintmax_t)leaf->base.key, (uintmax_t)leaf->data_offset,
				leaf->data_len);
			/* data phys offset */
			uint64_t dphys = zoff_to_phys(&hdr, nvol, leaf->data_offset, NULL);
			printf("  data phys=%#jx\n", (uintmax_t)dphys);
			if (dphys + 0x20000 >= (uint64_t)st.st_size) {
				fprintf(stderr, "  data phys +128K beyond image; extend image first\n");
				continue;
			}
			leaf->data_len = 0x20000;
			leaf->data_crc = datacrc(ver, img + dphys, 0x20000);
			/* recompute node crc */
			node->crc = datacrc(ver, ((uint8_t *)&node->crc) + sizeof(node->crc),
					    HAMMER_BTREE_CRCSIZE);
			printf("  PATCHED: data_len=0x20000 data_crc=%08x node crc=%08x\n",
			       leaf->data_crc, node->crc);
			patched++;
		}
	}
	if (!patched) { fprintf(stderr, "no DATA leaf patched\n"); return 1; }
	if (pwrite(fd, img, st.st_size, 0) != st.st_size) { perror("pwrite"); return 1; }
	close(fd);
	printf("OK patched %d leaf(s)\n", patched);
	return 0;
}
