/* DF-3042: flip one byte of the hammer volume header covered by vol_crc
 * (vol_reserved[7], harmless to mount logic) WITHOUT fixing vol_crc.
 * hammer's volume CRC is CRC32C (iscsi_crc32) of CRCSIZE1 ^ CRC32C of the
 * tail region; we compute it locally to prove the stored crc no longer
 * matches after the flip. */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <stddef.h>
#include <sys/types.h>
#include <hammer_disk.h>

static uint32_t crc32c_tab[8][256];
static void
crc32c_init(void)
{
	uint32_t i, j, c;
	for (i = 0; i < 256; i++) {
		c = i;
		for (j = 0; j < 8; j++)
			c = (c >> 1) ^ (0x82F63B78u & (0 - (c & 1)));
		crc32c_tab[0][i] = c;
	}
	for (i = 0; i < 256; i++) {
		c = crc32c_tab[0][i];
		for (j = 1; j < 8; j++) {
			c = crc32c_tab[0][c & 0xff] ^ (c >> 8);
			crc32c_tab[j][i] = c;
		}
	}
}
static uint32_t
crc32c(const void *buf, size_t len)
{
	const uint8_t *p = buf;
	uint32_t crc = 0xFFFFFFFFu;
	while (len >= 8) {
		crc ^= (uint32_t)p[0] | ((uint32_t)p[1]<<8) | ((uint32_t)p[2]<<16) | ((uint32_t)p[3]<<24);
		crc = crc32c_tab[7][crc & 0xff] ^ crc32c_tab[6][(crc>>8)&0xff] ^
		      crc32c_tab[5][(crc>>16)&0xff] ^ crc32c_tab[4][crc>>24] ^
		      crc32c_tab[3][p[4]] ^ crc32c_tab[2][p[5]] ^ crc32c_tab[1][p[6]] ^ crc32c_tab[0][p[7]];
		p += 8; len -= 8;
	}
	while (len--)
		crc = crc32c_tab[0][(crc ^ *p++) & 0xff] ^ (crc >> 8);
	return ~crc;
}

int main(int argc, char **argv)
{
	struct hammer_volume_ondisk hdr, hdr2;
	off_t roff = offsetof(struct hammer_volume_ondisk, vol_reserved);
	unsigned char *p;
	uint64_t before, after;
	int fd;

	crc32c_init();
	fd = open(argv[1], O_RDWR);
	if (fd < 0) { perror("open"); return 1; }
	if (pread(fd, &hdr, sizeof(hdr), 0) != sizeof(hdr)) { perror("pread"); return 1; }
	if (hdr.vol_signature != HAMMER_FSBUF_VOLUME) {
		fprintf(stderr, "not a hammer volume (sig %016jx)\n",
			(uintmax_t)hdr.vol_signature);
		return 1;
	}
	memcpy(&hdr2, &hdr, sizeof(hdr));
	p = (unsigned char *)&hdr2;
	if (argc > 2 && !strcmp(argv[2], "crc")) {
		/* forge a NONZERO but wrong stored vol_crc */
		unsigned char *crcp = (unsigned char *)&hdr2.vol_crc;
		crcp[0] ^= 0x5a;
		if (crcp[0] == 0)
			crcp[0] = 0x5a;
	} else {
		p[roff + 31] ^= 0x5a;
	}
#define VOLCRC(h) ((uint64_t)crc32c(&h, HAMMER_VOL_CRCSIZE1) << 32 | \
	crc32c((char *)&h + HAMMER_VOL_CRCSIZE1 + sizeof(hdr.vol_crc), HAMMER_VOL_CRCSIZE2))
	before = VOLCRC(hdr);
	after = VOLCRC(hdr2);
	printf("sizeof(hdr)=%zu vol_crc@%zu CRCSIZE1=%d(+4) CRCSIZE2=%d\n",
	       sizeof(hdr), (size_t)offsetof(struct hammer_volume_ondisk, vol_crc),
	       (int)HAMMER_VOL_CRCSIZE1, (int)HAMMER_VOL_CRCSIZE2);
	printf("stored vol_crc          = %016jx\n", (uintmax_t)hdr.vol_crc);
	printf("computed CRC (before)   = %016jx %s\n", (uintmax_t)before,
	       before == hdr.vol_crc ? "(MATCH - image was clean)" : "(MISMATCH)");
	printf("computed CRC (after)    = %016jx (differs from stored -> header invalid)\n",
	       (uintmax_t)after);
	if (pwrite(fd, &hdr2, sizeof(hdr2), 0) != sizeof(hdr2)) { perror("pwrite"); return 1; }
	printf("flipped vol_reserved[7] byte; vol_crc left stale\n");
	close(fd);
	return 0;
}
