#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <stropts.h>
#include <poll.h>
#include <string.h>
#include <sys/fs/zev.h>
#include <errno.h>
#include <sys/sysmacros.h>
#include <stdarg.h>
#include <sys/avl.h>
#include <sys/stat.h>

#define ZEV_DEVICE "/devices/pseudo/zev@0:ctrl"

#if !defined(offsetof)
#define	offsetof(s, m)	((size_t)(&(((s *)0)->m)))
#endif

static char *zev_device = ZEV_DEVICE;
static int do_flush = 0;

static char *zev_op_name[] = {
	"ERROR",
	"MARK",
	"ZFS_MOUNT",
	"ZFS_UMOUNT",
	"ZVOL_WRITE",
	"ZVOL_TRUNCATE",
	"ZNODE_CLOSE_AFTER_UPDATE",
	"ZNODE_CREATE",
	"ZNODE_MKDIR",
	"ZNODE_MAKE_XATTR_DIR",
	"ZNODE_REMOVE",
	"ZNODE_RMDIR",
	"ZNODE_LINK",
	"ZNODE_SYMLINK",
	"ZNODE_RENAME",
	"ZNODE_WRITE",
	"ZNODE_TRUNCATE",
	"ZNODE_SETATTR",
	"ZNODE_ACL",
	NULL
};

#define MD_STATISTICS			1
#define MD_POLL_EVENTS			2
#define MD_CHECKSUMS			3
#define MD_DEBUG_INFO			4
#define MD_LIST_QUEUES			5
#define MD_SET_GLOBAL_MAX_QUEUE_LEN	6
#define MD_SET_MAX_QUEUE_LEN		7
#define MD_SET_POLL_WAKEUP_QUEUE_LEN	8
#define MD_MUTE_POOL			9
#define MD_UNMUTE_POOL			10
#define MD_MARK				11
#define MD_ADD_QUEUE			12
#define MD_ADD_BLOCKING_QUEUE		13
#define MD_REMOVE_QUEUE			14
#define MD_QUEUE_BLOCKING		15
#define MD_QUEUE_NONBLOCKING		16
#define MD_QUEUE_PROPERTIES		17
#define MD_ZEVSTAT			18
#define MD_ZEV_REPORT			19
#define MD_DUMP_SPOOL			20
#define MD_GET_ZEV_VERSION		21

static int verbose = 0;
static int grep_friendly = 0;

#define MAX_GUID 10000
static int num_guid_filter = 0;
static uint64_t guid_filter[MAX_GUID];

static void
zpf(char *fmt, ...)
{
	va_list	ap;

	va_start(ap, fmt);
	vprintf(fmt, ap);
	va_end(ap);
	if (grep_friendly) {
		printf(" ");
	} else {
		printf("\n");
	}
}

static void
znl(void)
{
	if (grep_friendly)
		printf("\n");
}

static void
sig2hex_direct(const uint8_t *sig, char *hex)
{
	int     i;

	for (i = 0; i < SHA1_DIGEST_LENGTH; ++i) {
		sprintf(hex + 2 * i, "%02x", sig[i]);
	}
	hex[SHA1_DIGEST_LENGTH * 2] = '\0';
}

static int
zev_statistics(int fd)
{
	zev_statistics_t zs;
	if (ioctl(fd, ZEV_IOC_GET_GLOBAL_STATISTICS, &zs)) {
		perror("getting statistics data failed");
		return (EXIT_FAILURE);
	}
	printf("ZEV module state:\n");

	printf("    queue length in bytes   : %lu\n", zs.zev_queue_len);
	printf("    queue length limit      : %lu\n", zs.zev_max_queue_len);
	printf("    bytes read from device  : %lu\n", zs.zev_bytes_read);
	printf("    module internal errors  : %lu\n\n", zs.zev_cnt_errors);

	printf("    discarded events        : %lu\n",
	    zs.zev_cnt_discarded_events);
	printf("    discarded bytes         : %lu\n\n", zs.zev_bytes_discarded);

	printf("ZFS event statistics:\n");

	printf("    total ZFS events        : %lu\n", zs.zev_cnt_total_events);
	printf("    ZFS mount               : %lu\n", zs.zev_cnt_zfs_mount);
	printf("    ZFS umount              : %lu\n", zs.zev_cnt_zfs_umount);
	printf("    ZVOL write              : %lu\n", zs.zev_cnt_zvol_write);
	printf("    ZVOL truncate           : %lu\n", zs.zev_cnt_zvol_truncate);
	printf("    ZNODE close after update: %lu\n",
	    zs.zev_cnt_znode_close_after_update);
	printf("    ZNODE create            : %lu\n", zs.zev_cnt_znode_create);
	printf("    ZNODE remove            : %lu\n", zs.zev_cnt_znode_remove);
	printf("    ZNODE link              : %lu\n", zs.zev_cnt_znode_link);
	printf("    ZNODE symlink           : %lu\n", zs.zev_cnt_znode_symlink);
	printf("    ZNODE rename            : %lu\n", zs.zev_cnt_znode_rename);
	printf("    ZNODE write             : %lu\n", zs.zev_cnt_znode_write);
	printf("    ZNODE truncate          : %lu\n",
	    zs.zev_cnt_znode_truncate);
	printf("    ZNODE setattr           : %lu\n", zs.zev_cnt_znode_setattr);
	printf("    ZNODE acl               : %lu\n", zs.zev_cnt_znode_acl);
	return EXIT_SUCCESS;
}

static void
zev_print_inode_info(char *name, zev_inode_info_t *info)
{
	zpf("  %s.inode: %llu", name, info->ino);
	zpf("  %s.gen: %llu", name, info->gen);
	zpf("  %s.mtime: %llu", name, info->mtime);
	zpf("  %s.ctime: %llu", name, info->ctime);
	zpf("  %s.size: %llu", name, info->size);
	zpf("  %s.mode: %llo", name, info->mode);
	zpf("  %s.links: %llu", name, info->links);
	zpf("  %s.type: %lu", name, info->type);
	zpf("  %s.flags: %lu", name, info->flags);
}

static void
zev_print_mark_payload(zev_mark_t *rec)
{
	int i;
	int j;
	uint8_t *p;
	char c;

	zpf("  payload:");
	p = (uint8_t *)ZEV_PAYLOAD(rec);
	for (i=0; i<rec->payload_len; i+=16) {
		printf("  ");
		for (j=i; j<rec->payload_len && j<i+16; j++) {
			printf("%02x ", p[j]);
			if (j == i + 7)
				printf(" ");
		}
		if (grep_friendly)
			continue;
		for (; j<i+16; j++) {
			printf("   ");
			if (j == i + 7)
				printf(" ");
		}
		printf("    ");
		for (j=i; j<rec->payload_len && j<i+16; j++) {
			c = '.';
			if (p[j] >= ' ' && p[j] <= '~')
				c = p[j];
			printf("%c", c);
			if (j == i + 7)
				printf(" ");
		}
		printf("\n");
	}
}

static void
zev_print_error(char *buf)
{
	zev_error_t *rec = (zev_error_t *)buf;
	time_t op_time = rec->op_time;
	char *ct = ctime(&op_time); ct[24] = '\0';

	if (verbose) {
		zpf("%s %s", ct, zev_op_name[rec->op - ZEV_OP_MIN]);
		zpf("  guid: %llu", rec->guid);
		zpf("  failed.op: %s",
		    zev_op_name[rec->failed_op - ZEV_OP_MIN]);
		zpf("  message: %s", ZEV_ERRSTR(rec));
		znl();
	} else {
		printf("%s %s: failed_op=%s msg=%s\n",
		       ct, zev_op_name[rec->op - ZEV_OP_MIN],
		       zev_op_name[rec->failed_op - ZEV_OP_MIN],
		       ZEV_ERRSTR(rec));
	}
}

static void
zev_print_mark(char *buf)
{
	zev_mark_t *rec = (zev_mark_t *)buf;
	time_t op_time = rec->op_time;
	char *ct = ctime(&op_time); ct[24] = '\0';

	if (verbose) {
		zpf("%s %s", ct, zev_op_name[rec->op - ZEV_OP_MIN]);
		zpf("  guid: %llu", rec->guid);
		zpf("  mark.id: %llu", rec->mark_id);
		zpf("  payload.len: %llu", rec->payload_len);
		if (rec->payload_len)
			zev_print_mark_payload(rec);
		znl();
	} else {
		printf("%s %s: guid=%llu mark_id=%lld payload_len=%ld "
		       "payload=\"%.*s\"\n",
		       ct, zev_op_name[rec->op - ZEV_OP_MIN], rec->guid,
		       rec->mark_id, rec->payload_len,
		       rec->payload_len, (char *)(rec + 1));
	}
}

static void
zev_print_zfs_mount(char *buf)
{
	zev_zfs_mount_t *rec = (zev_zfs_mount_t *)buf;
	time_t op_time = rec->op_time;
	char *ct = ctime(&op_time); ct[24] = '\0';

	if (verbose) {
		zpf("%s %s", ct, zev_op_name[rec->op - ZEV_OP_MIN]);
		zpf("  guid: %llu", rec->guid);
		zpf("  txg: %llu", rec->txg);
		zpf("  dataset: %s", ZEV_DATASET(rec));
		zpf("  mountpoint: %s", ZEV_MOUNTPOINT(rec));
		zpf("  remount: %s", rec->remount ? "true" : "false");
		zev_print_inode_info("root", &rec->root);
		znl();
	} else {
		printf("%s %s: guid=%llu remount=%s dataset='%s' "
		       "mountpoint='%s'\n",
		       ct, zev_op_name[rec->op - ZEV_OP_MIN],
		       rec->guid,
		       rec->remount ? "true" : "false",
		       ZEV_DATASET(rec),
		       ZEV_MOUNTPOINT(rec));
	}
}

static void
zev_print_zfs_umount(char *buf)
{
	zev_zfs_umount_t *rec = (zev_zfs_umount_t *)buf;
	time_t op_time = rec->op_time;
	char *ct = ctime(&op_time); ct[24] = '\0';

	if (verbose) {
		zpf("%s %s", ct, zev_op_name[rec->op - ZEV_OP_MIN]);
		zpf("  guid: %llu", rec->guid);
		zpf("  txg: %llu", rec->txg);
		zev_print_inode_info("covered", &rec->covered);
		znl();
	} else {
		printf("%s %s: guid=%llu\n",
		       ct, zev_op_name[rec->op - ZEV_OP_MIN],
		       rec->guid);
	}
}

static void
zev_print_zvol_truncate(char *buf)
{
	zev_zvol_truncate_t *rec = (zev_zvol_truncate_t *)buf;
	time_t op_time = rec->op_time;
	char *ct = ctime(&op_time); ct[24] = '\0';

	if (verbose) {
		zpf("%s %s", ct, zev_op_name[rec->op - ZEV_OP_MIN]);
		zpf("  guid: %llu", rec->guid);
		zpf("  txg: %llu", rec->txg);
		zpf("  offset: %llu", rec->offset);
		zpf("  length: %llu", rec->length);
		znl();
	} else {
		printf("%s %s: guid=%llu offset=%llu length=%llu\n",
		       ct, zev_op_name[rec->op - ZEV_OP_MIN],
		       rec->guid,
		       rec->offset,
		       rec->length);
	}
}

static void
zev_print_zvol_write(char *buf)
{
	zev_print_zvol_truncate(buf);
}

static void
zev_print_znode_close_after_update(char *buf)
{
	zev_znode_close_after_update_t *rec =
	    (zev_znode_close_after_update_t *)buf;
	time_t op_time = rec->op_time;
	char *ct = ctime(&op_time); ct[24] = '\0';

	if (verbose) {
		zpf("%s %s", ct, zev_op_name[rec->op - ZEV_OP_MIN]);
		zpf("  guid: %llu", rec->guid);
		zev_print_inode_info("file", &rec->file);
		znl();
	} else {
		printf("%s %s: guid=%llu file=%llu.%llu\n",
		       ct, zev_op_name[rec->op - ZEV_OP_MIN],
		       rec->guid,
		       rec->file.ino, rec->file.gen);
	}
}

static void
zev_print_znode_create(char *buf)
{
	zev_znode_create_t *rec = (zev_znode_create_t *)buf;
	time_t op_time = rec->op_time;
	char *ct = ctime(&op_time); ct[24] = '\0';
	zev_sig_t *sig;
	char sigval[(SHA1_DIGEST_LENGTH * 2) + 1];

	if (verbose) {
		zpf("%s %s", ct, zev_op_name[rec->op - ZEV_OP_MIN]);
		zpf("  guid: %llu", rec->guid);
		zpf("  txg: %llu", rec->txg);
		zpf("  name: '%s'", ZEV_NAME(rec));
		sig = &rec->signature;
		sig2hex_direct(sig->value, sigval);
		zpf("  sig: level %d, offset %llu, value %s",
		    sig->level, sig->block_offset, sigval);
		zev_print_inode_info("file", &rec->file);
		zev_print_inode_info("parent", &rec->parent);
		znl();
	} else {
		printf("%s %s: guid=%llu parent=%llu.%llu file=%llu.%llu "
		       "file.mtime=%llu, parent.mtime=%llu, name='%s'\n",
		       ct, zev_op_name[rec->op - ZEV_OP_MIN],
		       rec->guid,
		       rec->parent.ino, rec->parent.gen,
		       rec->file.ino, rec->file.gen,
		       rec->file.mtime, rec->parent.mtime,
		       ZEV_NAME(rec));
	}
}

static void
zev_print_znode_mkdir(char *buf)
{
	zev_print_znode_create(buf);
}

static void
zev_print_znode_make_xattr_dir(char *buf)
{
	zev_print_znode_create(buf);
}

static void
zev_print_znode_remove(char *buf)
{
	zev_znode_remove_t *rec = (zev_znode_remove_t *)buf;
	time_t op_time = rec->op_time;
	char *ct = ctime(&op_time); ct[24] = '\0';

	if (verbose) {
		zpf("%s %s", ct, zev_op_name[rec->op - ZEV_OP_MIN]);
		zpf("  guid: %llu", rec->guid);
		zpf("  txg: %llu", rec->txg);
		zpf("  file.name: '%s'", ZEV_NAME(rec));
		zev_print_inode_info("file", &rec->file);
		zev_print_inode_info("parent", &rec->parent);
		znl();
	} else {
		printf("%s %s: guid=%llu parent=%llu.%llu "
		       "file.mtime=%llu name='%s'\n",
		       ct, zev_op_name[rec->op - ZEV_OP_MIN],
		       rec->guid,
		       rec->parent.ino, rec->parent.gen,
		       rec->file.mtime,
		       ZEV_NAME(rec));
	}
}

static void
zev_print_znode_rmdir(char *buf)
{
	zev_print_znode_remove(buf);
}

static void
zev_print_znode_link(char *buf)
{
	zev_znode_link_t *rec = (zev_znode_link_t *)buf;
	time_t op_time = rec->op_time;
	char *ct = ctime(&op_time); ct[24] = '\0';

	if (verbose) {
		zpf("%s %s", ct, zev_op_name[rec->op - ZEV_OP_MIN]);
		zpf("  guid: %llu", rec->guid);
		zpf("  txg: %llu", rec->txg);
		zpf("  link.name: '%s'", ZEV_NAME(rec));
		zev_print_inode_info("file", &rec->file);
		zev_print_inode_info("parent", &rec->parent);
		znl();
	} else {
		printf("%s %s: parent=%llu.%llu file=%llu.%llu "
		       "file.ctime=%llu parent.ctime=%llu name='%s'\n",
		       ct, zev_op_name[rec->op - ZEV_OP_MIN],
		       rec->parent.ino, rec->parent.gen,
		       rec->file.ino, rec->file.gen,
		       rec->file.ctime, rec->parent.ctime,
		       ZEV_NAME(rec));
	}
}

static void
zev_print_znode_symlink(char *buf)
{
	zev_znode_symlink_t *rec = (zev_znode_symlink_t *)buf;
	time_t op_time = rec->op_time;
	char *ct = ctime(&op_time); ct[24] = '\0';
	zev_sig_t *sig;
	char sigval[(SHA1_DIGEST_LENGTH * 2) + 1];

	if (verbose) {
		zpf("%s %s", ct, zev_op_name[rec->op - ZEV_OP_MIN]);
		zpf("  guid: %llu", rec->guid);
		zpf("  txg: %llu", rec->txg);
		zpf("  symlink.name: '%s'", ZEV_NAME(rec));
		zpf("  symlink.link: '%s'", ZEV_LINK(rec));
		sig = &rec->signature;
		sig2hex_direct(sig->value, sigval);
		zpf("  sig: level %d, offset %llu, value %s",
		    sig->level, sig->block_offset, sigval);
		zev_print_inode_info("file", &rec->file);
		zev_print_inode_info("parent", &rec->parent);
		znl();
	} else {
		printf("%s %s: parent=%llu.%llu file=%llu.%llu "
		       "name='%s' link='%s'\n",
		       ct, zev_op_name[rec->op - ZEV_OP_MIN],
		       rec->parent.ino, rec->parent.gen,
		       rec->file.ino, rec->file.gen,
		       ZEV_NAME(rec),
		       ZEV_LINK(rec));
	}
}

static void
zev_print_znode_rename(char *buf)
{
	zev_znode_rename_t *rec = (zev_znode_rename_t *)buf;
	time_t op_time = rec->op_time;
	char *ct = ctime(&op_time); ct[24] = '\0';

	if (verbose) {
		zpf("%s %s", ct, zev_op_name[rec->op - ZEV_OP_MIN]);
		zpf("  guid: %llu", rec->guid);
		zpf("  txg: %llu", rec->txg);
		zpf("  file.srcname: '%s'", ZEV_SRCNAME(rec));
		zpf("  file.dstname: '%s'", ZEV_DSTNAME(rec));
		zev_print_inode_info("file", &rec->file);
		if (rec->clobbered_file.ino)
			zev_print_inode_info("clobbered_file",
			                     &rec->clobbered_file);
		zev_print_inode_info("srcdir", &rec->srcdir);
		zev_print_inode_info("dstdir", &rec->dstdir);
		znl();
	} else {
		printf("%s %s: srcdir=%llu.%llu dstdir=%llu.%llu "
		       "file=%llu.%llu file.mtime=%llu, file.ctime=%llu, "
		       "srcdir.mtime=%llu, srcdir.ctime=%llu, "
		       "dstdir.mtime=%llu, dstdir.ctime=%llu, "
		       "srcname='%s' dstname='%s'\n",
		       ct, zev_op_name[rec->op - ZEV_OP_MIN],
		       rec->srcdir.ino, rec->srcdir.gen,
		       rec->dstdir.ino, rec->dstdir.gen,
		       rec->file.ino, rec->file.gen,
		       rec->file.mtime, rec->file.ctime,
		       rec->srcdir.mtime, rec->srcdir.ctime,
		       rec->dstdir.mtime, rec->dstdir.ctime,
		       ZEV_SRCNAME(rec),
		       ZEV_DSTNAME(rec));
	}
}

static void
zev_print_znode_write(char *buf)
{
	zev_znode_write_t *rec = (zev_znode_write_t *)buf;
	time_t op_time = rec->op_time;
	char *ct = ctime(&op_time); ct[24] = '\0';
	zev_sig_t *sig;
	char sigval[(SHA1_DIGEST_LENGTH * 2) + 1];
	int i;

	if (verbose) {
		zpf("%s %s", ct, zev_op_name[rec->op - ZEV_OP_MIN]);
		zpf("  guid: %llu", rec->guid);
		zpf("  txg: %llu", rec->txg);
		zpf("  offset: %llu", rec->offset);
		zpf("  length: %llu", rec->length);
		zev_print_inode_info("file", &rec->file);
		znl();
		for (i=0; i<rec->signature_cnt; i++) {
			sig = (zev_sig_t *)ZEV_SIGNATURES(rec);
			sig += i;
			sig2hex_direct(sig->value, sigval);
			zpf("  sig: level %d, offset %llu, value %s",
			    sig->level, sig->block_offset, sigval);
		}
	} else {
		printf("%s %s: file=%llu.%llu offset=%llu length=%llu\n",
		       ct, zev_op_name[rec->op - ZEV_OP_MIN],
		       rec->file.ino, rec->file.gen,
		       rec->offset, rec->length);
	}
}

static void
zev_print_znode_truncate(char *buf)
{
	zev_print_znode_write(buf);
}

static void
zev_print_znode_setattr(char *buf)
{
	zev_znode_setattr_t *rec = (zev_znode_setattr_t *)buf;
	time_t op_time = rec->op_time;
	char *ct = ctime(&op_time); ct[24] = '\0';

	if (verbose) {
		zpf("%s %s", ct, zev_op_name[rec->op - ZEV_OP_MIN]);
		zpf("  guid: %llu", rec->guid);
		zpf("  txg: %llu", rec->txg);
		zev_print_inode_info("file", &rec->file);
		znl();
	} else {
		printf("%s %s: file=%llu.%llu mtime=%llu\n",
		       ct, zev_op_name[rec->op - ZEV_OP_MIN],
		       rec->file.ino, rec->file.gen, rec->file.mtime);
	}
}

static void
zev_print_znode_acl(char *buf)
{
	zev_print_znode_setattr(buf);
}

static void
zev_print_event(char *buf, int len)
{
	int record_len;
	int op;

	record_len = *(uint32_t *)buf;
	if (record_len != len) {
		fprintf(stderr, "record length mismatch: got %d, expected %d\n",
		        record_len, len);
		exit(1);
	}
	op = *((uint32_t *)buf + 1);
	if (op < ZEV_OP_MIN || op > ZEV_OP_MAX) {
		fprintf(stderr, "unknown op code: %d\n", op);
		exit(1);
	}
	if (num_guid_filter) {
		uint64_t guid = *((uint64_t *)buf + 2);
		int i;

		for (i = 0; i < num_guid_filter; ++i)
			if (guid_filter[i] == guid)
				break;

		if (i == num_guid_filter)
			/* no match, filtered */
			return;
	}
	switch (op) {
	case ZEV_OP_ERROR:
		zev_print_error(buf);
		break;
	case ZEV_OP_MARK:
		zev_print_mark(buf);
		break;
	case ZEV_OP_ZFS_MOUNT:
		zev_print_zfs_mount(buf);
		break;
	case ZEV_OP_ZFS_UMOUNT:
		zev_print_zfs_umount(buf);
		break;
	case ZEV_OP_ZVOL_TRUNCATE:
		zev_print_zvol_truncate(buf);
		break;
	case ZEV_OP_ZVOL_WRITE:
		zev_print_zvol_write(buf);
		break;
	case ZEV_OP_ZNODE_CLOSE_AFTER_UPDATE:
		zev_print_znode_close_after_update(buf);
		break;
	case ZEV_OP_ZNODE_CREATE:
		zev_print_znode_create(buf);
		break;
	case ZEV_OP_ZNODE_MKDIR:
		zev_print_znode_mkdir(buf);
		break;
	case ZEV_OP_ZNODE_MAKE_XATTR_DIR:
		zev_print_znode_make_xattr_dir(buf);
		break;
	case ZEV_OP_ZNODE_REMOVE:
		zev_print_znode_remove(buf);
		break;
	case ZEV_OP_ZNODE_RMDIR:
		zev_print_znode_rmdir(buf);
		break;
	case ZEV_OP_ZNODE_LINK:
		zev_print_znode_link(buf);
		break;
	case ZEV_OP_ZNODE_SYMLINK:
		zev_print_znode_symlink(buf);
		break;
	case ZEV_OP_ZNODE_RENAME:
		zev_print_znode_rename(buf);
		break;
	case ZEV_OP_ZNODE_WRITE:
		zev_print_znode_write(buf);
		break;
	case ZEV_OP_ZNODE_TRUNCATE:
		zev_print_znode_truncate(buf);
		break;
	case ZEV_OP_ZNODE_SETATTR:
		zev_print_znode_setattr(buf);
		break;
	case ZEV_OP_ZNODE_ACL:
		zev_print_znode_acl(buf);
		break;
	default:
		fprintf(stderr, "unhandled op code: %d\n", op);
		exit(1);
	}
	if (do_flush)
		fflush(stdout);
}

static int
zev_poll_events(int fd, int create_tmp_queue)
{
	struct pollfd pfd[1];
	int ret;
	char buf[4096];
	zev_event_t *ev;
	int off = 0;
	int q_fd;

	if (create_tmp_queue) {
		snprintf(buf, sizeof(buf),
		         "/devices/pseudo/zev@0:%s", ZEV_TMPQUEUE_DEVICE_NAME);
		q_fd = open(buf, O_RDONLY);
		if (q_fd < 0) {
			perror("opening queue device failed");
			return (EXIT_FAILURE);
		}
	} else {
		q_fd = fd;
	}

	while (1) {
		pfd[0].fd = q_fd;
		pfd[0].events = POLLIN;
		ret = poll(pfd, 1, 1000);
		if (ret < 0) {
			perror("poll failed");
			close(q_fd);
			return(EXIT_FAILURE);
		}
		if (!(pfd[0].revents & POLLIN))
			continue;
		/* data available */
		ret = read(q_fd, buf, sizeof(buf));
		if (ret < 0) {
			perror("read failed");
			close(q_fd);
			return(EXIT_FAILURE);
		}
		if (ret == 0)
			continue;
		while (ret > off) {
			ev = (zev_event_t *)(buf + off);
			zev_print_event(buf + off, ev->header.record_len);
			off += ev->header.record_len;
		}
		off = 0;
	}
	if (create_tmp_queue)
		close(q_fd);
	return EXIT_SUCCESS;
}

static int
zev_dump_spool(int fd)
{
	int len;
	char buf[4096];
	int off = 0;

	while (1) {
		len = read(fd, buf + off, sizeof(buf) - off);
		if (len == -1) {
			fprintf(stderr, "reading from spool failed: %s\n",
				strerror(errno));
			return EXIT_FAILURE;
		}
		if (len == 0)
			break;

		len += off;
		off = 0;
		while (len > off + sizeof(uint32_t)) {
			uint32_t evlen;
			char *mp;
			zev_event_t *ev;

			ev = (zev_event_t *)(buf + off);
			evlen = ev->header.record_len;
			if (len < off + evlen + 1)
				break;
			mp = buf + off + evlen;
			if (!memchr(mp, 0, len - off - evlen))
				break;
			zev_print_event(buf + off, ev->header.record_len);
			off += ev->header.record_len + strlen(mp) + 1;
		}

		memmove(buf, buf + off, len - off);
		off = len - off;
	}

	return EXIT_SUCCESS;
}

static void
usage(char *progname)
{
	fprintf(stderr, "usage: %s [-d <dev>] [options]\n", progname);
	fprintf(stderr, "\n");
	fprintf(stderr, " Status information:\n");
	fprintf(stderr, "   -s                   show zev statistics\n");
	fprintf(stderr, "   -p                   poll for ZFS events\n");
	fprintf(stderr, "   -f <name>            dump events from spool\n");
	fprintf(stderr, "   -D                   print zev module debug "
	        "information\n");
	fprintf(stderr, "   -T <interval> <cnt>  zevstat mode\n");
	fprintf(stderr, "   -R <base filename>   zevreport mode\n");
	fprintf(stderr, "\n");
	fprintf(stderr, " Tune zev module settings:\n");
	fprintf(stderr, "   -Q <bytes>           set maximum event queue "
	        "length\n");
	fprintf(stderr, "   -m <pool>            mute pool, no events for "
	        "this pool\n");
	fprintf(stderr, "   -M <pool>            unmute pool\n");
	fprintf(stderr, "\n");
	fprintf(stderr, " Queue management:\n");
	fprintf(stderr, "   -l                   list queues\n");
	fprintf(stderr, "   -a <name>            add non-blocking queue\n");
	fprintf(stderr, "   -A <name>            add blocking queue\n");
	fprintf(stderr, "   -r <name>            remove queue\n");
	fprintf(stderr, "   -b <name>            make queue non-blocking "
	        "(default)\n");
	fprintf(stderr, "   -B <name>            make queue block when full\n");
	fprintf(stderr, "   -P <name>            display queue properties\n");
	fprintf(stderr, "   -L <name> <bytes>    set maximum event queue "
	        "length\n");
	fprintf(stderr, "   -t <name> <bytes>    set queue length poll "
	        "throttle\n");
	fprintf(stderr, "\n");
	fprintf(stderr, " Other options:\n");
	fprintf(stderr, "   -d <dev>             non-default device file. "
	        "('%s')\n", ZEV_DEVICE);
	fprintf(stderr, "   -q <name>            use device file for this "
		"queue name\n");
	fprintf(stderr, "   -k <guid>:<payload>  queue mark event\n");
	fprintf(stderr, "   -c <filename>        list file's content "
		"checksums\n");
	fprintf(stderr, "   -v                   verbose: additional output "
	        "for some operations\n");
	fprintf(stderr, "   -g                   grep-friendly event output, "
	        "one event per line\n");
	fprintf(stderr, "   -G <guid>            filter event output by guid, "
		"can be given more than once\n");
	fprintf(stderr, "   -V                   query zev module version\n");
	fprintf(stderr, "   -F                   flush output after each line\n");
	exit (EXIT_FAILURE);
}

static void
zevstat_usage(char *progname)
{
	fprintf(stderr, "usage: %s [-v] <interval> [count]\n", progname);
	fprintf(stderr, "   -v   verbose, show counters for all event types\n");
	exit (EXIT_FAILURE);
}

static void
zevreport_usage(char *progname)
{
	fprintf(stderr, "usage: %s <output base filename>\n", progname);
	exit (EXIT_FAILURE);
}

static int
zev_add_queue(int fd, char *arg, int blocking)
{
	zev_ioctl_add_queue_t aq;
	int namelen;

	namelen = strlen(arg);
	if (namelen > ZEV_MAX_QUEUE_NAME_LEN) {
		fprintf(stderr, "queue name too long: %s\n", arg);
		return (EXIT_FAILURE);
	}

	aq.zev_namelen = namelen;
	strcpy(aq.zev_name, arg);
	aq.zev_flags = ZEV_FL_PERSISTENT | ZEV_FL_INITIALLY_EMPTY;
	if (blocking) {
		aq.zev_flags |= ZEV_FL_BLOCK_WHILE_QUEUE_FULL;
		aq.zev_max_queue_len = ZEV_MAX_QUEUE_LEN;
	} else {
		aq.zev_max_queue_len = (1024 * 1024);
	}

	if (ioctl(fd, ZEV_IOC_ADD_QUEUE, &aq)) {
		perror("adding queue failed");
		return (EXIT_FAILURE);
	}
	return (0);
}

static int
zev_remove_queue(int fd, char *arg)
{
	zev_ioctl_remove_queue_t aq;
	int namelen;

	namelen = strlen(arg);
	if (namelen > ZEV_MAX_QUEUE_NAME_LEN) {
		fprintf(stderr, "queue name too long: %s\n", arg);
		return (EXIT_FAILURE);
	}

	aq.zev_queue_name.zev_namelen = namelen;
	strcpy(aq.zev_queue_name.zev_name, arg);

	if (ioctl(fd, ZEV_IOC_REMOVE_QUEUE, &aq)) {
		perror("removing queue failed");
		return (EXIT_FAILURE);
	}
	return (0);
}

static int
zev_set_global_max_queue_len(int fd, char *arg)
{
	uint64_t maxqueuelen;

	if (!arg) {
		fprintf(stderr, "missing queue length parameter\n");
		return (EXIT_FAILURE);
	}

	errno = 0;
	maxqueuelen = strtol(arg, (char **)NULL, 10);
	if (errno) {
		fprintf(stderr, "invalid queue length parameter: %s\n", arg);
		return (EXIT_FAILURE);
	}
	if (ioctl(fd, ZEV_IOC_SET_MAX_QUEUE_LEN, &maxqueuelen)) {
		perror("setting max queue length failed");
		return (EXIT_FAILURE);
	}
	return (0);
}

static int
zev_mute_unmute_impl(int fd, char *poolname, int mute)
{
	zev_ioctl_poolarg_t pa;
	int len;
	int op = mute ? ZEV_IOC_MUTE_POOL : ZEV_IOC_UNMUTE_POOL;
	len = strlen(poolname);
	if (len <= 0 || len >= sizeof(pa.zev_poolname)) {
		fprintf(stderr, "invalid poolname: %s\n", poolname);
		return (EXIT_FAILURE);
	}
	strcpy(pa.zev_poolname, poolname);
	pa.zev_poolname_len = len;
	if (ioctl(fd, op, &pa)) {
		perror("muting pool data failed");
		return (EXIT_FAILURE);
	}
	return (0);
}

int
zev_mute_pool(int fd, char *poolname)
{
	return zev_mute_unmute_impl(fd, poolname, 1);
}

int
zev_unmute_pool(int fd, char *poolname)
{
	return zev_mute_unmute_impl(fd, poolname, 0);
}

static int
zev_debug_info(int fd)
{
	zev_ioctl_debug_info_t di;

	if (ioctl(fd, ZEV_IOC_GET_DEBUG_INFO, &di)) {
		perror("getting zev debug info failed");
		return (EXIT_FAILURE);
	}

	printf("memory allocated: %llu bytes\n", di.zev_memory_allocated);
	printf("checksum cache size: %llu\n", di.zev_chksum_cache_size);
	printf("checksum cache hits: %llu\n", di.zev_chksum_cache_hits);
	printf("checksum cache misses: %llu\n", di.zev_chksum_cache_misses);
	return 0;
}

static int
zev_mark(int fd, char *arg)
{
	zev_ioctl_mark_t *mark;
	uint64_t guid;
	int len;
	char *p;

	p = strchr(arg, ':');
	if (!p) {
		fprintf(stderr, "expected value is <guid>:<payload>, "
		        "e.g. '123:hello'\n");
		exit (EXIT_FAILURE);
	}
	*p = '\n';
	p++;

	errno = 0;
	guid = strtoll(arg, (char **)NULL, 10);
	if (errno) {
		fprintf(stderr, "guid must be a number.\n");
		exit (EXIT_FAILURE);
	}

	len = strlen(p);

	mark = malloc(sizeof(*mark) + len + 1);
	if (!mark) {
		fprintf(stderr, "can't allocate mark structure: %s\n",
		        strerror(errno));
		exit (EXIT_FAILURE);
	}
	mark->zev_guid = guid;
	mark->zev_mark_id = 0;
	mark->zev_payload_len = len;
	strcpy(ZEV_PAYLOAD(mark), p);

	if (ioctl(fd, ZEV_IOC_MARK, mark)) {
		perror("queueing mark failed");
		return (EXIT_FAILURE);
	}

	printf("mark id: %lu\n", mark->zev_mark_id);
	return (0);
}

static int
zev_queue_blocking(int fd, char *arg, int block)
{
	zev_ioctl_get_queue_properties_t gqp;

	gqp.zev_queue_name.zev_namelen = strlen(arg);
	if (gqp.zev_queue_name.zev_namelen > ZEV_MAX_QUEUE_NAME_LEN) {
		fprintf(stderr, "queue name too long.\n");
		return EXIT_FAILURE;
	}
	strcpy(gqp.zev_queue_name.zev_name, arg);

	if (ioctl(fd, ZEV_IOC_GET_QUEUE_PROPERTIES, &gqp)) {
		perror("getting queue properties failed");
		return (EXIT_FAILURE);
	}
	if (block) {
		gqp.zev_flags |= ZEV_FL_BLOCK_WHILE_QUEUE_FULL;
	} else {
		gqp.zev_flags &= ~ZEV_FL_BLOCK_WHILE_QUEUE_FULL;
	}
	if (ioctl(fd, ZEV_IOC_SET_QUEUE_PROPERTIES, &gqp)) {
		perror("setting queue properties failed");
		return (EXIT_FAILURE);
	}
	return (0);
}

static int
zev_set_max_queue_len(int fd, char *arg, char *len)
{
	zev_ioctl_get_queue_properties_t gqp;

	if (!len) {
		fprintf(stderr, "queue size parameter missing.\n");
		return EXIT_FAILURE;
	}

	gqp.zev_queue_name.zev_namelen = strlen(arg);
	if (gqp.zev_queue_name.zev_namelen > ZEV_MAX_QUEUE_NAME_LEN) {
		fprintf(stderr, "queue name too long.\n");
		return EXIT_FAILURE;
	}
	strcpy(gqp.zev_queue_name.zev_name, arg);

	if (ioctl(fd, ZEV_IOC_GET_QUEUE_PROPERTIES, &gqp)) {
		perror("getting queue properties failed");
		return (EXIT_FAILURE);
	}
	gqp.zev_max_queue_len = atol(len);
	if (gqp.zev_max_queue_len == 0 && strcmp("0", len)) {
		fprintf(stderr, "queue size parameter garbled.\n");
		return (EXIT_FAILURE);
	}
	if (gqp.zev_max_queue_len > ZEV_MAX_QUEUE_LEN) {
		fprintf(stderr, "queue size parameter out of bounds.\n");
		return (EXIT_FAILURE);
	}

	if (ioctl(fd, ZEV_IOC_SET_QUEUE_PROPERTIES, &gqp)) {
		perror("setting queue properties failed");
		return (EXIT_FAILURE);
	}
	return (0);
}

static int
zev_set_poll_wakeup_queue_len(int fd, char *arg, char *len)
{
	zev_ioctl_get_queue_properties_t gqp;

	if (!len) {
		fprintf(stderr, "poll throttle parameter missing.\n");
		return EXIT_FAILURE;
	}

	gqp.zev_queue_name.zev_namelen = strlen(arg);
	if (gqp.zev_queue_name.zev_namelen > ZEV_MAX_QUEUE_NAME_LEN) {
		fprintf(stderr, "queue name too long.\n");
		return EXIT_FAILURE;
	}
	strcpy(gqp.zev_queue_name.zev_name, arg);

	if (ioctl(fd, ZEV_IOC_GET_QUEUE_PROPERTIES, &gqp)) {
		perror("getting queue properties failed");
		return (EXIT_FAILURE);
	}
	gqp.zev_poll_wakeup_threshold = atol(len);
	if (gqp.zev_poll_wakeup_threshold == 0 && strcmp("0", len)) {
		fprintf(stderr, "poll throttle parameter garbled.\n");
		return (EXIT_FAILURE);
	}
	if (gqp.zev_poll_wakeup_threshold > ZEV_MAX_POLL_WAKEUP_QUEUE_LEN) {
		fprintf(stderr, "poll throttle parameter out of bounds.\n");
		return (EXIT_FAILURE);
	}

	if (ioctl(fd, ZEV_IOC_SET_QUEUE_PROPERTIES, &gqp)) {
		perror("setting queue properties failed");
		return (EXIT_FAILURE);
	}
	return (0);
}

static int
zev_queue_properties(int fd, char *arg)
{
	zev_ioctl_get_queue_properties_t gqp;

	gqp.zev_queue_name.zev_namelen = strlen(arg);
	if (gqp.zev_queue_name.zev_namelen > ZEV_MAX_QUEUE_NAME_LEN) {
		fprintf(stderr, "queue name too long.\n");
		return EXIT_FAILURE;
	}
	strcpy(gqp.zev_queue_name.zev_name, arg);

	if (ioctl(fd, ZEV_IOC_GET_QUEUE_PROPERTIES, &gqp)) {
		perror("getting queue properties failed");
		return (EXIT_FAILURE);
	}

	printf("queue        : %s\n", arg);
	printf("max size     : %" PRIu64 "\n", gqp.zev_max_queue_len);
	printf("poll throttle: %" PRIu64 "\n", gqp.zev_poll_wakeup_threshold);
	printf("persistent   : %s\n",
		gqp.zev_flags & ZEV_FL_PERSISTENT ? "yes" : "no");
	printf("blocking     : %s\n",
		gqp.zev_flags & ZEV_FL_BLOCK_WHILE_QUEUE_FULL ? "yes" : "no");

	return (0);
}

static int
zev_list_queues(int fd)
{
	zev_ioctl_get_queue_properties_t gqp;
	zev_ioctl_get_queue_list_t gql;
	zev_ioctl_get_queue_statistics_t gs;
	uint64_t	i;
	char		name[ZEV_MAX_QUEUE_NAME_LEN+1];
	zev_statistics_t zs;

	if (ioctl(fd, ZEV_IOC_GET_GLOBAL_STATISTICS, &zs)) {
		perror("getting statistics data failed");
		return (EXIT_FAILURE);
	}

	if (ioctl(fd, ZEV_IOC_GET_QUEUE_LIST, &gql)) {
		perror("getting queue list failed");
		return (EXIT_FAILURE);
	}

	printf("Name                                     Size       "
	       "Size%% Max Size   Per Block\n");

	for (i=0; i<gql.zev_n_queues; i++) {
		strncpy(name, gql.zev_queue_name[i].zev_name,
		        ZEV_MAX_QUEUE_NAME_LEN);
		name[gql.zev_queue_name[i].zev_namelen] = '\0';

		memcpy(gqp.zev_queue_name.zev_name,
		    gql.zev_queue_name[i].zev_name, ZEV_MAX_QUEUE_NAME_LEN);
		gqp.zev_queue_name.zev_namelen =
		    gql.zev_queue_name[i].zev_namelen;

		if (ioctl(fd, ZEV_IOC_GET_QUEUE_PROPERTIES, &gqp)) {
			if (errno == ENOENT)
				continue;
			perror("getting queue properties failed");
			return (EXIT_FAILURE);
		}

		memcpy(gs.zev_queue_name.zev_name,
		    gql.zev_queue_name[i].zev_name, ZEV_MAX_QUEUE_NAME_LEN);
		gs.zev_queue_name.zev_namelen =
		    gql.zev_queue_name[i].zev_namelen;

		if (ioctl(fd, ZEV_IOC_GET_QUEUE_STATISTICS, &gs)) {
			if (errno == ENOENT)
				continue;
			perror("getting statistics data failed");
			return (EXIT_FAILURE);
		}

		if (gqp.zev_max_queue_len == 0) {
			gqp.zev_max_queue_len = zs.zev_max_queue_len;
		}
		printf("%-40s %-10" PRIu64 " %5.1f %-10" PRIu64 
		       " %-3s %-3s\n",
			name,
			gs.zev_statistics.zev_queue_len * 100.0 /
				gqp.zev_max_queue_len,
			gs.zev_statistics.zev_queue_len,
			gqp.zev_max_queue_len,
			gqp.zev_flags & ZEV_FL_PERSISTENT ? "yes" : "no",
			gqp.zev_flags & ZEV_FL_BLOCK_WHILE_QUEUE_FULL ?
				 "yes" : "no");
	}

	return (0);
}

static int
zev_checksum(int dev_fd, char *filename)
{
	int fd;
	offset_t off;
	offset_t data;
	zev_sig_t *sig;
	char *buf;
	zev_ioctl_get_signatures_t *gs;
	int i;
	char sigval[(SHA1_DIGEST_LENGTH * 2) + 1];
	int buf_size;

	/* control struct, one lv1 signature and up to 256 lv0 signatures */
	buf_size = (1 + 256) * sizeof(zev_sig_t);
	buf = malloc(sizeof(zev_ioctl_get_signatures_t) + buf_size);
	if (!buf) {
		perror("can't allocate checksum buffer");
		return (EXIT_FAILURE);
	}

	fd = open(filename, O_RDONLY);
	if (fd < 0) {
		perror("can't open file");
		return (EXIT_FAILURE);
	}

	gs = (zev_ioctl_get_signatures_t *)buf;
	gs->zev_fd = fd;
	gs->zev_bufsize = buf_size;

	off = 0;
	data = 0;
	while (1) {
		errno = 0;
		data = llseek(fd, off, SEEK_DATA);
		if (data < 0) {
			if (errno == ENXIO)	/* no more data */
				break;
			perror("llseek failed");
			goto err;
		}
		data = P2ALIGN(data, ZEV_L1_SIZE);
		off = data + ZEV_L1_SIZE;

		gs->zev_offset = data;
		gs->zev_len = ZEV_L1_SIZE;

		if (ioctl(dev_fd, ZEV_IOC_GET_FILE_SIGNATURES, gs)) {
			perror("ioctl to get signatures failed");
			goto err;
		}

		for (i=0; i<gs->zev_signature_cnt; i++) {
			sig = (zev_sig_t *)ZEV_SIGNATURES(gs);
			sig += i;
			sig2hex_direct(sig->value, sigval);
			printf("level %d, offset %llu, value %s\n",
			       sig->level, sig->block_offset, sigval);
		}
	}

	free(buf);
	close(fd);
	return 0;
err:
	free(buf);
	close(fd);
	return (EXIT_FAILURE);
}

typedef struct zevstat {
	uint64_t	ns_start;
	uint64_t	events[ZEV_OP_MIN + ZEV_OP_MAX];
	uint64_t	guids;
	uint64_t	total_events;
	uint64_t	total_guids;
	avl_tree_t	guids_interval;
	avl_tree_t	guids_runtime;
} zevstat_t;

typedef struct zev_guidtrack_t {
	uint64_t	guid;
	avl_node_t	avl_interval;
	avl_node_t	avl_runtime;
} zev_guidtrack_t;

zevstat_t zevstat;

static void
zev_eventstat(char *buf, int len)
{
	zev_header_t *rec = (zev_header_t *)buf;
	zev_guidtrack_t *gt;
	zev_guidtrack_t *gt_int;
	zev_guidtrack_t to_find;
	avl_index_t where;

	zevstat.total_events++;
	zevstat.events[rec->op]++;

	to_find.guid = rec->guid;
	gt = avl_find(&zevstat.guids_runtime, &to_find, &where);
	if (!gt) {
		gt = malloc(sizeof(*gt));
		if (!gt) {
			perror("can't get guid tracking record");
			exit (EXIT_FAILURE);
		}
		gt->guid = rec->guid;
		avl_insert(&zevstat.guids_runtime, gt, where);
	}
	gt_int = avl_find(&zevstat.guids_interval, &to_find, &where);
	if (!gt_int)
		avl_insert(&zevstat.guids_interval, gt, where);
}

static void
zev_eventstat_interval(FILE *out)
{
	uint64_t events;
	int i;
	zev_guidtrack_t *gt;

	events = 0;
	for (i = ZEV_OP_MIN; i <= ZEV_OP_MAX; i++) {
		events += zevstat.events[i];
	}

	if (verbose) {
		fprintf(out, "%u  %6llu  %6llu %6llu %6llu  ",
		        time(NULL),
		        events,
		        zevstat.total_events,
		        avl_numnodes(&zevstat.guids_interval),
		        avl_numnodes(&zevstat.guids_runtime));
		for (i = ZEV_OP_MIN; i <= ZEV_OP_MAX; i++)
			fprintf(out, "%6llu ", zevstat.events[i]);
		fprintf(out, "\n");
	} else {
		fprintf(out, "%u  %6llu  %6llu %6llu %6llu\n",
		        time(NULL),
		        events,
		        zevstat.total_events,
		        avl_numnodes(&zevstat.guids_interval),
		        avl_numnodes(&zevstat.guids_runtime));
	}
	memset(&zevstat.events, 0, sizeof(zevstat.events));
	zevstat.guids = 0;
	while (gt = avl_first(&zevstat.guids_interval))
		avl_remove(&zevstat.guids_interval, gt);
	fflush(out);
}

static int
zev_evcompar(const void *a, const void *b)
{
	const zev_guidtrack_t *ga = a;
	const zev_guidtrack_t *gb = b;

	if (ga->guid > gb->guid)
		return 1;
	if (ga->guid < gb->guid)
		return -1;
	return 0;
}

static int
zev_zevstat(int fd, char *s_interval, char *s_count, char *outfile)
{
	uint64_t interval = 1000;
	uint64_t ms;
	uint64_t t_until;
	uint64_t t_now;
	int cnt = -1;
	struct pollfd pfd[1];
	int ret;
	char buf[4096];
	zev_event_t *ev;
	int off = 0;
	zev_ioctl_add_queue_t aq;
	int q_fd;
	zev_guidtrack_t *gt;
	FILE *out = stdout;
	struct stat st;
	char filename[MAXPATHLEN];
	int retry;

	if (outfile) {
		retry = 0;
		strncpy(filename, outfile, sizeof(filename));
		while (stat(filename, &st) == 0) {
			/* file exists */
			snprintf(filename, sizeof(filename),
			         "%s.%d", outfile, retry);
			retry++;
		}
		out = fopen(filename, "wb+");
		if (!out) {
			perror("opening output file failed");
			return (EXIT_FAILURE);
		}
	}

	memset(&zevstat, 0, sizeof(zevstat));
	avl_create(&zevstat.guids_runtime, zev_evcompar,
	           sizeof(zev_guidtrack_t),
	           offsetof(zev_guidtrack_t, avl_runtime));
	avl_create(&zevstat.guids_interval, zev_evcompar,
	           sizeof(zev_guidtrack_t),
	           offsetof(zev_guidtrack_t, avl_interval));

	if (s_interval) {
		interval = atol(s_interval);
		if (interval == 0) {
			fprintf(stderr, "invalid interval.\n");
			return (EXIT_FAILURE);
		}
		interval *= 1000;
	}
	if (s_count) {
		cnt = atol(s_count);
		if (interval == 0) {
			fprintf(stderr, "invalid count.\n");
			return (EXIT_FAILURE);
		}
	}

	aq.zev_max_queue_len = 1024 * 1024;
	aq.zev_flags = ZEV_FL_INITIALLY_EMPTY;
	snprintf(aq.zev_name, ZEV_MAX_QUEUE_NAME_LEN,
		 "zevstat.%ld.%ld", time(NULL), getpid());
	aq.zev_namelen = strlen(aq.zev_name);

	if (ioctl(fd, ZEV_IOC_ADD_QUEUE, &aq)) {
		perror("adding temporary queue failed");
		return (EXIT_FAILURE);
	}

	snprintf(buf, sizeof(buf),
		 "/devices/pseudo/zev@0:%s", aq.zev_name);
	q_fd = open(buf, O_RDONLY);
	if (q_fd < 0) {
		perror("opening queue device failed");
		return (EXIT_FAILURE);
	}

	pfd[0].fd = q_fd;
	pfd[0].events = POLLIN;

	/* drain queue */
	while ((ret = poll(pfd, 1, 0)) > 0) {
		if (read(q_fd, buf, sizeof(buf)) < 0) {
			perror("read failed");
			close(q_fd);
			return(EXIT_FAILURE);
		}
	}
	if (ret < 0) {
		perror("poll failed");
		close(q_fd);
		return(EXIT_FAILURE);
	}

	fprintf(out, "timestamp   events tevents  guids tguids");
	if (verbose) {
		fprintf(out, "   error   mark  mount umount zvol_w ");
		fprintf(out, "zvol_t  close create  mkdir mxattr ");
		fprintf(out, "remove  rmdir   link symlnk rename  ");
		fprintf(out, "write  trunc setatt    acl");
	}
	fprintf(out, "\n");
	while (cnt) {
		t_until = gethrtime() + (interval * 1000000);
		ms = interval;
		do {
			ret = poll(pfd, 1, ms);
			t_now = gethrtime();
			if (t_now < t_until) {
				ms = t_until - t_now;
				ms /= 1000000ull;
			}
			if (ret < 0) {
				perror("poll failed");
				close(q_fd);
				return(EXIT_FAILURE);
			}
			if (!(pfd[0].revents & POLLIN))
				continue;
			/* data available */
			ret = read(q_fd, buf, sizeof(buf));
			if (ret < 0) {
				perror("read failed");
				close(q_fd);
				return(EXIT_FAILURE);
			}
			if (ret == 0)
				continue;
			while (ret > off) {
				ev = (zev_event_t *)(buf + off);
				zev_eventstat(buf + off, ev->header.record_len);
				off += ev->header.record_len;
			}
			off = 0;
		} while ((t_now) < t_until && (ms > 0));
		zev_eventstat_interval(out);
		if (cnt > 0)
			cnt--;
	}
	close(q_fd);
	if (outfile)
		fclose(out);
	while (gt = avl_first(&zevstat.guids_interval))
		avl_remove(&zevstat.guids_interval, gt);
	while (gt = avl_first(&zevstat.guids_runtime)) {
		avl_remove(&zevstat.guids_runtime, gt);
		free(gt);
	}
	return EXIT_SUCCESS;
}

static int
zev_report(int fd, char *basename)
{
	char filename[MAXPATHLEN];
	char count[10];
	time_t now;
	time_t midnight;
	struct tm tm;
	int minutes;
	int ret;

	verbose++;
	while (1) {
		now = time(NULL);
		localtime_r(&now, &tm);
		snprintf(filename, sizeof(filename), "%s.%04d-%02d-%02d",
		         basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
		tm.tm_sec = 0;
		tm.tm_min = 0;
		tm.tm_hour = 0;
		tm.tm_mday++;  /* works for Jan 32nd, Feb 30th, etc. */
		midnight = mktime(&tm);
		if (now % 60)
			sleep(60 - (now % 60));
		minutes = (midnight - time(NULL)) / 60;
		snprintf(count, sizeof(count), "%d", minutes);
		ret = zev_zevstat(fd, "60", count, filename);
		if (ret)
			return EXIT_FAILURE;
	}
	return EXIT_SUCCESS; /* never reached */
}

static int
zev_get_zev_version(int fd)
{
	zev_ioctl_get_zev_version vi;

	if (ioctl(fd, ZEV_IOC_GET_ZEV_VERSION, &vi)) {
		perror("getting zev cersion info failed");
		return (EXIT_FAILURE);
	}

	printf("zev major version: %llu\n", vi.zev_major_version);
	printf("zev minor version: %llu\n", vi.zev_minor_version);
	return 0;
}

static void
zev_sigint(int sig)
{
	fflush(stdout);
}

int
main(int argc, char **argv)
{
	int fd;
	int c;
	extern char *optarg;
	int create_tmp_queue = 1;
	char buf[MAXPATHLEN];
	int mode = 0;
	char *arg = NULL;
	char *arg2 = NULL;
	char *p;

	sigset(SIGINT, zev_sigint);

	/* open device */
	fd = open(zev_device, O_RDONLY);
	if (fd < 0) {
		perror("opening zev device failed");
		return EXIT_FAILURE;
	}

	p = strrchr(argv[0], '/');
	if (!p) {
		p = argv[0];
	} else {
		p++;
	}
	if (!strcmp(p, "zevstat")) {
		mode = MD_ZEVSTAT;
		if (argc < 2)
			zevstat_usage(argv[0]);
		if (!strcmp(argv[1], "-v")) {
			if (argc < 3)
				zevstat_usage(argv[0]);
			verbose++;
			arg = argv[2];
			arg2 = argv[3];
		} else {
			arg = argv[1];
			arg2 = argv[2];
		}
		return zev_zevstat(fd, arg, arg2, NULL);
	} else if(!strcmp(p, "zevreport")) {
		mode = MD_ZEV_REPORT;
		if (argc != 2)
			zevreport_usage(argv[0]);
		return zev_report(fd, argv[1]);
	}

	while ((c = getopt(argc, argv,
	   "a:A:b:B:c:d:Df:FgG:hk:lL:m:M:pP:q:Q:r:R:st:T:vV?")) != -1) {
		switch(c) {
		case 'g':
			grep_friendly++;
			verbose++;
			break;
		case 'v':
			verbose++;
			break;
		case 's':
			mode = MD_STATISTICS;
			break;
		case 'p':
			mode = MD_POLL_EVENTS;
			break;
		case 'c':
			mode = MD_CHECKSUMS;
			arg = optarg;
			break;
		case 'D':
			mode = MD_DEBUG_INFO;
			break;
		case 'd':
			close(fd);
			zev_device = optarg;
			fd = open(zev_device, O_RDONLY);
			if (fd < 0) {
				perror("opening zev device failed");
				return EXIT_FAILURE;
			}
			create_tmp_queue = 0;
			break;
		case 'q':
			snprintf(buf, sizeof(buf),
				 "/devices/pseudo/zev@0:%s", optarg);
			close(fd);
			zev_device = buf;
			fd = open(zev_device, O_RDONLY);
			if (fd < 0) {
				perror("opening zev device failed");
				return EXIT_FAILURE;
			}
			create_tmp_queue = 0;
			break;
		case 'f':
			fd = open(optarg, O_RDONLY);
			if (fd < 0) {
				perror("opening spool file failed");
				return EXIT_FAILURE;
			}
			mode = MD_DUMP_SPOOL;
			break;
		case 'l':
			mode = MD_LIST_QUEUES;
			break;
		case 'Q':
			mode = MD_SET_GLOBAL_MAX_QUEUE_LEN;
			arg = optarg;
			break;
		case 'L':
			mode = MD_SET_MAX_QUEUE_LEN;
			arg = optarg;
			arg2 = argv[optind];
			break;
		case 'T':
			mode = MD_ZEVSTAT;
			arg = optarg;
			arg2 = argv[optind];
			break;
		case 'R':
			mode = MD_ZEV_REPORT;
			arg = optarg;
			break;
		case 't':
			mode = MD_SET_POLL_WAKEUP_QUEUE_LEN;
			arg = optarg;
			arg2 = argv[optind];
			break;
		case 'm':
			mode = MD_MUTE_POOL;
			arg = optarg;
			break;
		case 'M':
			mode = MD_UNMUTE_POOL;
			arg = optarg;
			break;
		case 'k':
			mode = MD_MARK;
			arg = optarg;
			break;
		case 'a':
			mode = MD_ADD_QUEUE;
			arg = optarg;
			break;
		case 'A':
			mode = MD_ADD_BLOCKING_QUEUE;
			arg = optarg;
			break;
		case 'r':
			mode = MD_REMOVE_QUEUE;
			arg = optarg;
			break;
		case 'b':
			mode = MD_QUEUE_BLOCKING;
			arg = optarg;
			break;
		case 'B':
			mode = MD_QUEUE_NONBLOCKING;
			arg = optarg;
			break;
		case 'P':
			mode = MD_QUEUE_PROPERTIES;
			arg = optarg;
			break;
		case 'G':
			if (num_guid_filter == MAX_GUID) {
				fprintf(stderr,
				 "too many guids given, max is %d\n", MAX_GUID);
				exit(1);
			}
			guid_filter[num_guid_filter++] = atoll(optarg);
			break;
		case 'V':
			mode = MD_GET_ZEV_VERSION;
			break;
		case 'F':
			do_flush = 1;
			break;
		case 'h':
		case '?':
		default:
			usage(argv[0]);
		}
	}

	switch (mode) {
	case MD_STATISTICS:
		return zev_statistics(fd);
	case MD_POLL_EVENTS:
		return zev_poll_events(fd, create_tmp_queue);
	case MD_DUMP_SPOOL:
		return zev_dump_spool(fd);
	case MD_CHECKSUMS:
		return zev_checksum(fd, arg);
	case MD_DEBUG_INFO:
		return zev_debug_info(fd);
	case MD_LIST_QUEUES:
		return zev_list_queues(fd);
	case MD_SET_GLOBAL_MAX_QUEUE_LEN:
		return zev_set_global_max_queue_len(fd, arg);
	case MD_SET_MAX_QUEUE_LEN:
		return zev_set_max_queue_len(fd, arg, arg2);
	case MD_SET_POLL_WAKEUP_QUEUE_LEN:
		return zev_set_poll_wakeup_queue_len(fd, arg, arg2);
	case MD_ZEVSTAT:
		return zev_zevstat(fd, arg, arg2, NULL);
	case MD_ZEV_REPORT:
		return zev_report(fd, arg);
	case MD_MUTE_POOL:
		return zev_mute_pool(fd, arg);
	case MD_UNMUTE_POOL:
		return zev_unmute_pool(fd, arg);
	case MD_MARK:
		return zev_mark(fd, arg);
	case MD_ADD_QUEUE:
		return zev_add_queue(fd, arg, 0);
	case MD_ADD_BLOCKING_QUEUE:
		return zev_add_queue(fd, arg, 1);
	case MD_REMOVE_QUEUE:
		return zev_remove_queue(fd, arg);
	case MD_QUEUE_BLOCKING:
		return zev_queue_blocking(fd, arg, 0);
	case MD_QUEUE_NONBLOCKING:
		return zev_queue_blocking(fd, arg, 1);
	case MD_QUEUE_PROPERTIES:
		return zev_queue_properties(fd, arg);
	case MD_GET_ZEV_VERSION:
		return zev_get_zev_version(fd);
	default:
		close(fd);
		usage(argv[0]);
		return EXIT_FAILURE;
	};
}