/*
 * DF-0866 trigger: getdirentries() on a mounted cd9660 image,
 * inspect every returned dirent's d_unused1 (offset 11, 1B) and
 * d_unused2 (offset 12-15, 4B) and the d_name trailing padding
 * for leaked (non-zero) kernel heap bytes.
 *
 * Build: cc -O2 -D_BSD_VISIBLE -o leak_dirent leak_dirent.c
 * Run:   ./leak_dirent <mounted-iso-dir>
 *
 * Exit 0 if NO leak, exit 1 if leak detected (non-zero bytes in
 * d_unused1, d_unused2, or trailing d_name padding).
 */
#define _BSD_VISIBLE 1
#define _XOPEN_SOURCE 700

#include <sys/types.h>
#include <sys/dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

/* getdirentries is in <dirent.h> when _BSD_VISIBLE, but the kernel
 * syscall returns the raw dirent stream. Declare it explicitly to be safe. */
ssize_t getdirentries(int, char *, size_t, off_t *);

/*
 * DragonFly struct dirent layout (kernel == user, no d_reclen):
 *   ino_t       d_ino;     // off 0,  8 bytes
 *   __uint16_t  d_namlen;  // off 8,  2 bytes
 *   __uint8_t   d_type;    // off 10, 1 byte
 *   __uint8_t   d_unused1; // off 11, 1 byte  (UNINIT in cd9660)
 *   __uint32_t  d_unused2; // off 12, 4 bytes (UNINIT in cd9660)
 *   char        d_name[256]; // off 16
 *
 * Record length (size of bytes actually written for this entry) is
 * _DIRENT_DIRSIZ(dp) = round_up(offsetof(d_name) + d_namlen + 1, 8).
 */
#define OFF_UNUSED1 11
#define OFF_UNUSED2 12
#define OFF_DNAME   16

/* Same formula as the kernel's _DIRENT_DIRSIZ:
 *   (__offsetof(struct dirent, d_name) + d_namlen + 1 + 7) & ~7
 * which simplifies to (16 + d_namlen + 1 + 7) & ~7 = (d_namlen + 24) & ~7
 */
#define OUR_DIRSIZ(d)  (((OFF_DNAME + (d)->d_namlen + 1) + 7) & ~7)

static void hexdump(const unsigned char *p, size_t n) {
    for (size_t i = 0; i < n; i++)
        printf("%02x", p[i]);
}

int main(int argc, char **argv) {
    if (argc != 2) {
        fprintf(stderr, "usage: %s <mounted-iso-dir>\n", argv[0]);
        return 2;
    }
    int fd = open(argv[1], O_RDONLY);
    if (fd < 0) { perror("open"); return 2; }

    char buf[8192];
    long total_leak_bytes = 0;
    long total_entries = 0;
    int run = 0;
    off_t base = 0;

    for (;;) {
        ssize_t n = getdirentries(fd, buf, sizeof(buf), &base);
        if (n < 0) { perror("getdirentries"); return 2; }
        if (n == 0) break;

        run++;
        char *p = buf;
        while (p < buf + n) {
            struct dirent *d = (struct dirent *)p;
            /* Record length: name area rounded up to 8 bytes. */
            unsigned reclen = OUR_DIRSIZ(d);
            if (reclen < OFF_DNAME + 1) break;   /* safety */
            total_entries++;

            unsigned char *raw = (unsigned char *)d;
            int leak_unused1 = raw[OFF_UNUSED1] != 0 ? 1 : 0;
            int leak_unused2 = 0;
            for (int i = 0; i < 4; i++)
                if (raw[OFF_UNUSED2 + i] != 0) leak_unused2++;

            /* d_name trailing padding: [d_namlen+1 .. reclen-1] */
            int name_end = OFF_DNAME + d->d_namlen + 1;
            int leak_namepad = 0;
            for (int i = name_end; i < (int)reclen; i++)
                if (raw[i] != 0) leak_namepad++;

            int leak_total = leak_unused1 + leak_unused2 + leak_namepad;
            total_leak_bytes += leak_total;

            printf("[run %d] entry %3ld reclen=%3u namlen=%u type=%u name='%s'\n",
                   run, total_entries, reclen, d->d_namlen, d->d_type, d->d_name);
            printf("  bytes[0..%u]: ", reclen - 1);
            hexdump(raw, reclen);
            printf("\n");
            printf("  d_unused1=%02x d_unused2=", raw[OFF_UNUSED1]);
            hexdump(raw + OFF_UNUSED2, 4);
            printf("  leak(unused1=%d unused2=%d namepad=%d)=%d\n",
                   leak_unused1, leak_unused2, leak_namepad, leak_total);

            p += reclen;
            if ((size_t)(p - buf) > (size_t)n) break;
        }
    }
    close(fd);

    printf("\n=== SUMMARY ===\n");
    printf("entries read: %ld\n", total_entries);
    printf("non-zero leaked bytes (d_unused1+d_unused2+d_name padding): %ld\n", total_leak_bytes);

    return (total_leak_bytes > 0) ? 1 : 0;
}
