#include <sys/mode.h>
#include <sys/zfs_znode.h>
#include <sys/fs/zfs.h>
#include <sys/fs/zev.h>
#include <sys/zfs_events.h>
#include <sys/zev_checksums.h>
#include <sys/dmu_tx.h>
#include <sys/mntent.h>

#define ZEV_FILL_INODE_INFO(name, znode)				\
	do {								\
		uint64_t mtime[2], ctime[2];				\
		sa_bulk_attr_t bulk[2];					\
		int count = 0;						\
		timestruc_t mtime_s, ctime_s;				\
		SA_ADD_BULK_ATTR(bulk, count,				\
			         SA_ZPL_MTIME(znode->z_zfsvfs),		\
		                 NULL, &mtime, 16);			\
		SA_ADD_BULK_ATTR(bulk, count,				\
			         SA_ZPL_CTIME(znode->z_zfsvfs),		\
		                 NULL, &ctime, 16);			\
		if ((sa_bulk_lookup(znode->z_sa_hdl, bulk, count)) != 0) { \
			zev_queue_error(op, "znode write: "		\
			                "mtime/ctime unavailable");	\
			/* continue anyway, use fake data */		\
			mtime_s.tv_sec = ctime_s.tv_sec = 0;		\
		}							\
		ZFS_TIME_DECODE(&mtime_s, mtime);			\
		ZFS_TIME_DECODE(&ctime_s, ctime);			\
		rec->name.ino = znode->z_id;				\
		rec->name.gen = znode->z_gen;				\
		rec->name.mtime = mtime_s.tv_sec;			\
		rec->name.ctime = ctime_s.tv_sec;			\
		rec->name.size = znode->z_size;				\
		rec->name.type = znode->z_vnode->v_type;		\
		rec->name.mode =					\
			znode->z_mode | VTTOIF(znode->z_vnode->v_type);	\
		rec->name.links = znode->z_links;			\
		rec->name.flags = znode->z_pflags & ZFS_XATTR ?		\
			ZEV_FL_XATTR : 0;				\
	/* CONSTCOND */							\
	} while(0)

void
zev_zfs_mount_cb(vfs_t *vfs, vnode_t *mpt, char *dataset, boolean_t remount)
{
	int op = ZEV_OP_ZFS_MOUNT;
	char mountpoint[MAXPATHLEN+1];
	int mountpoint_len;
	int dataset_len;
	zev_zfs_mount_t *rec;
	zev_msg_t *msg = NULL;
	int msg_size;
	znode_t *zp_root;
	dmu_tx_t *tx;
	int error;
	uint64_t txg;

	/*
	 * workaround: this callback erronously got called for failed
	 * mount attempts, and then crashed the system.  Detect this
	 * from inside this callback as a workaround while the bugfix
	 * in the zfs module is not universally available.
	 */
	if (vfs == NULL)
		return;
	if (vfs->vfs_data == NULL)
		return;

	zfsvfs_t *zfsvfs = (zfsvfs_t *)vfs->vfs_data;
	if (zfsvfs->z_os->os_dsl_dataset->ds_is_snapshot)
		return;
	if (zev_skip_pool(zfsvfs->z_os))
		return;
	if (zev_skip_fs(zfsvfs))
		return;
	/* expensive, but we don't have many mount ops. */
	if ((vnodetopath(NULL, mpt, mountpoint, sizeof(mountpoint),
	    kcred)) != 0) {
		zev_queue_error(op, "unresolvable mountpoint, dataset=%s",
		                dataset);
		return;
	}
	if (zfs_zget(zfsvfs, zfsvfs->z_root, &zp_root) != 0) {
		zev_queue_error(op, "can't get root znode, dataset=%s",
		                dataset);
		return;
	}

	/* get current tgx by adding an empty tx */
	tx = dmu_tx_create(zfsvfs->z_os);
	error = dmu_tx_assign(tx, TXG_WAIT);
	if (error) {
		dmu_tx_abort(tx);
		zev_queue_error(op, "can't create tx, dataset=%s", dataset);
		return;
	}
	txg = dmu_tx_get_txg(tx);
	dmu_tx_commit(tx);

	dataset_len = strlen(dataset);
	mountpoint_len = strlen(mountpoint);
	msg_size = sizeof(*rec) + dataset_len + 1 + mountpoint_len + 1;
	msg = zev_alloc(sizeof(*msg) + msg_size);
	msg->size = msg_size;
	rec = (zev_zfs_mount_t *)(msg + 1);
	rec->record_len = msg_size;
	rec->op = op;
	rec->op_time = ddi_get_time();
	rec->guid = dsl_dataset_phys(zfsvfs->z_os->os_dsl_dataset)->ds_guid;
	rec->txg = txg;
	rec->remount = remount;
	rec->dataset_len = dataset_len;
	rec->mountpoint_len = mountpoint_len;
	ZEV_FILL_INODE_INFO(root, zp_root);
	VN_RELE(ZTOV(zp_root));
	(void) memcpy(ZEV_DATASET(rec), dataset, dataset_len + 1);
	(void) memcpy(ZEV_MOUNTPOINT(rec), mountpoint, mountpoint_len + 1);
	zev_queue_message(op, msg);
}

void
zev_zfs_umount_cb(vfs_t *vfs)
{
	int op = ZEV_OP_ZFS_UMOUNT;
	zev_zfs_umount_t *rec;
	zev_msg_t *msg = NULL;
	int msg_size;
	struct vnode *vp;
	znode_t *zp;
	dmu_tx_t *tx;
	int error;
	uint64_t txg;

	zfsvfs_t *zfsvfs = (zfsvfs_t *)vfs->vfs_data;
	if (zfsvfs->z_os->os_dsl_dataset->ds_is_snapshot)
		return;
	if (zev_skip_pool(zfsvfs->z_os))
		return;
	if (zev_skip_fs(zfsvfs))
		return;

	/* get current tgx by adding an empty tx */
	tx = dmu_tx_create(zfsvfs->z_os);
	error = dmu_tx_assign(tx, TXG_WAIT);
	if (error) {
		dmu_tx_abort(tx);
		zev_queue_error(op, "can't create tx");
		return;
	}
	txg = dmu_tx_get_txg(tx);
	dmu_tx_commit(tx);

	msg_size = sizeof(*rec);
	msg = zev_alloc(sizeof(*msg) + msg_size);
	msg->size = msg_size;
	rec = (zev_zfs_umount_t *)(msg + 1);
	rec->record_len = msg_size;
	rec->op = op;
	rec->op_time = ddi_get_time();
	rec->guid = dsl_dataset_phys(zfsvfs->z_os->os_dsl_dataset)->ds_guid;
	rec->txg = txg;

	vp = vfs->vfs_vnodecovered;
	if (strcmp(vfssw[vp->v_vfsp->vfs_fstype].vsw_name, MNTTYPE_ZFS)) {
		cmn_err(CE_WARN, "covered inode not on zfs filesystem, "
		        "reporting all-zero inode struct.");
		memset(&rec->covered, 0, sizeof(rec->covered));
	} else {
		zp = VTOZ(vfs->vfs_vnodecovered);
		ZEV_FILL_INODE_INFO(covered, zp);
	}

	zev_queue_message(op, msg);
}

void
zev_zvol_truncate_cb(char *dataset,
                     objset_t *os,
                     dmu_tx_t *tx,
                     uint64_t off,
                     uint64_t len)
{
	int op = ZEV_OP_ZVOL_TRUNCATE;
	zev_zvol_truncate_t *rec;
	zev_msg_t *msg = NULL;
	int msg_size;
	int dataset_len;

	if (zev_skip_pool(os))
		return;

	dataset_len = strlen(dataset);
	msg_size = sizeof(*rec) + dataset_len + 1;
	msg = zev_alloc(sizeof(*msg) + msg_size);
	msg->size = msg_size;
	rec = (zev_zvol_truncate_t *)(msg + 1);
	rec->record_len = msg_size;
	rec->op = op;
	rec->op_time = ddi_get_time();
	rec->guid = dsl_dataset_phys(os->os_dsl_dataset)->ds_guid;
	rec->txg = dmu_tx_get_txg(tx);
	rec->offset = off;
	rec->length = len;
	rec->dataset_len = dataset_len;
	(void) memcpy(ZEV_DATASET(rec), dataset, dataset_len + 1);
	zev_queue_message(op, msg);
}

void
zev_zvol_write_cb(char *dataset,
                  objset_t *os,
                  dmu_tx_t *tx,
                  uint64_t off,
                  uint64_t len)
{
	int op = ZEV_OP_ZVOL_WRITE;
	zev_zvol_write_t *rec;
	zev_msg_t *msg = NULL;
	int msg_size;
	int dataset_len;

	if (zev_skip_pool(os))
		return;

	dataset_len = strlen(dataset);
	msg_size = sizeof(*rec) + dataset_len + 1;
	msg = zev_alloc(sizeof(*msg) + msg_size);
	msg->size = msg_size;
	rec = (zev_zvol_write_t *)(msg + 1);
	rec->record_len = msg_size;
	rec->op = op;
	rec->op_time = ddi_get_time();
	rec->guid = dsl_dataset_phys(os->os_dsl_dataset)->ds_guid;
	rec->txg = dmu_tx_get_txg(tx);
	rec->offset = off;
	rec->length = len;
	rec->dataset_len = dataset_len;
	(void) memcpy(ZEV_DATASET(rec), dataset, dataset_len + 1);
	zev_queue_message(op, msg);
}

void
zev_znode_close_after_update_cb(znode_t *zp)
{
	int op = ZEV_OP_ZNODE_CLOSE_AFTER_UPDATE;
	zev_znode_close_after_update_t *rec;
	zev_msg_t *msg = NULL;
	int msg_size;

	if (zev_skip_pool(zp->z_zfsvfs->z_os))
		return;
	if (zev_skip_fs(zp->z_zfsvfs))
		return;

	msg_size = sizeof(*rec);
	msg = zev_alloc(sizeof(*msg) + msg_size);
	msg->size = msg_size;
	rec = (zev_znode_close_after_update_t *)(msg + 1);
	rec->record_len = msg_size;
	rec->op = op;
	rec->op_time = ddi_get_time();
	rec->guid =
		dsl_dataset_phys(zp->z_zfsvfs->z_os->os_dsl_dataset)->ds_guid;
	ZEV_FILL_INODE_INFO(file, zp);
	zev_queue_message(op, msg);
}

void
zev_znode_create_cb(znode_t *dzp,
                    znode_t *zp,
                    dmu_tx_t *tx,
                    char *name,
                    uint64_t txtype)
{
	int op = ZEV_OP_ZNODE_CREATE;
	zev_znode_create_t *rec;
	zev_msg_t *msg = NULL;
	int msg_size;
	int name_len;

	if (zev_skip_pool(zp->z_zfsvfs->z_os))
		return;
	if (zev_skip_fs(zp->z_zfsvfs))
		return;

	int type = (int)txtype;
	switch(type) {
	case TX_CREATE:
	case TX_CREATE_ACL:
	case TX_CREATE_ATTR:
	case TX_CREATE_ACL_ATTR:
		op = ZEV_OP_ZNODE_CREATE;
		break;
	case TX_MKDIR:
	case TX_MKDIR_ACL:
	case TX_MKDIR_ATTR:
	case TX_MKDIR_ACL_ATTR:
		op = ZEV_OP_ZNODE_MKDIR;
		break;
	case TX_MKXATTR:
		op = ZEV_OP_ZNODE_MAKE_XATTR_DIR;
		break;
	default:
		zev_queue_error(ZEV_OP_ZNODE_CREATE, 
		    "ERROR: ZNODE_CREATE: unknown txtype %d "
		    "(dir_inode=%d:%d inode=%d:%d name='%s')\n",
		    type,
		    dzp->z_gen,
		    dzp->z_id,
		    zp->z_gen,
		    zp->z_id,
		    name);
		return;
	}

	/* all three types use the same struct, so this works for all types: */
	name_len = strlen(name);
	msg_size = sizeof(*rec) + name_len + 1;
	msg = zev_alloc(sizeof(*msg) + msg_size);
	msg->size = msg_size;
	rec = (zev_znode_create_t *)(msg + 1);
	rec->record_len = msg_size;
	rec->op = op;
	rec->op_time = ddi_get_time();
	rec->guid =
		dsl_dataset_phys(zp->z_zfsvfs->z_os->os_dsl_dataset)->ds_guid;
	rec->txg = dmu_tx_get_txg(tx);
	ZEV_FILL_INODE_INFO(parent, dzp);
	ZEV_FILL_INODE_INFO(file, zp);
	rec->name_len = name_len;
	(void) memcpy(ZEV_NAME(rec), name, name_len + 1);
	zev_create_checksum(rec, zp);
	zev_queue_message(op, msg);
}

void
zev_znode_remove_cb(znode_t *dzp,
                    znode_t *zp,
                    dmu_tx_t *tx,
                    char *name,
                    uint64_t txtype)
{
	int op = ZEV_OP_ZNODE_REMOVE;
	zev_znode_remove_t *rec;
	zev_msg_t *msg = NULL;
	int msg_size;
	int name_len;

	if (zev_skip_pool(dzp->z_zfsvfs->z_os))
		return;
	if (zev_skip_fs(dzp->z_zfsvfs))
		return;

	int type = (int)txtype;
	switch(type) {
	case TX_REMOVE:
		op = ZEV_OP_ZNODE_REMOVE;
		break;
	case TX_RMDIR:
		op = ZEV_OP_ZNODE_RMDIR;
		break;
	default:
		zev_queue_error(ZEV_OP_ZNODE_REMOVE,
		    "ERROR: ZNODE_REMOVE: unknown txtype %d "
		    "(dir_inode=%d:%d name='%s')\n",
		    type,
		    dzp->z_gen,
		    dzp->z_id,
		    name);
		return;
	}

	/* both types use the same struct, so this works for all types: */
	name_len = strlen(name);
	msg_size = sizeof(*rec) + name_len + 1;
	msg = zev_alloc(sizeof(*msg) + msg_size);
	msg->size = msg_size;
	rec = (zev_znode_remove_t *)(msg + 1);
	rec->record_len = msg_size;
	rec->op = op;
	rec->op_time = ddi_get_time();
	rec->guid =
		dsl_dataset_phys(dzp->z_zfsvfs->z_os->os_dsl_dataset)->ds_guid;
	rec->txg = dmu_tx_get_txg(tx);
	ZEV_FILL_INODE_INFO(file, zp);
	ZEV_FILL_INODE_INFO(parent, dzp);
	rec->name_len = name_len;
	(void) memcpy(ZEV_NAME(rec), name, name_len + 1);
	zev_queue_message(op, msg);
}

void
zev_znode_link_cb(znode_t *dzp, znode_t *zp, dmu_tx_t *tx, char *name)
{
	int op = ZEV_OP_ZNODE_LINK;
	zev_znode_link_t *rec;
	zev_msg_t *msg = NULL;
	int msg_size;
	int name_len;

	if (zev_skip_pool(zp->z_zfsvfs->z_os))
		return;
	if (zev_skip_fs(zp->z_zfsvfs))
		return;

	name_len = strlen(name);
	msg_size = sizeof(*rec) + name_len + 1;
	msg = zev_alloc(sizeof(*msg) + msg_size);
	msg->size = msg_size;
	rec = (zev_znode_link_t *)(msg + 1);
	rec->record_len = msg_size;
	rec->op = op;
	rec->op_time = ddi_get_time();
	rec->guid =
		dsl_dataset_phys(zp->z_zfsvfs->z_os->os_dsl_dataset)->ds_guid;
	rec->txg = dmu_tx_get_txg(tx);
	ZEV_FILL_INODE_INFO(parent, dzp);
	ZEV_FILL_INODE_INFO(file, zp);
	rec->name_len = name_len;
	(void) memcpy(ZEV_NAME(rec), name, name_len + 1);
	zev_queue_message(op, msg);
}

void
zev_znode_symlink_cb(znode_t *dzp,
                     znode_t *zp,
                     dmu_tx_t *tx,
                     char *name,
                     char *link)
{
	int op = ZEV_OP_ZNODE_SYMLINK;
	zev_znode_symlink_t *rec;
	zev_msg_t *msg = NULL;
	int msg_size;
	int name_len;
	int link_len;

	if (zev_skip_pool(zp->z_zfsvfs->z_os))
		return;
	if (zev_skip_fs(zp->z_zfsvfs))
		return;

	name_len = strlen(name);
	link_len = strlen(link);
	msg_size = sizeof(*rec) + name_len + 1 + link_len + 1;
	msg = zev_alloc(sizeof(*msg) + msg_size);
	msg->size = msg_size;
	rec = (zev_znode_symlink_t *)(msg + 1);
	rec->record_len = msg_size;
	rec->op = op;
	rec->op_time = ddi_get_time();
	rec->guid =
		dsl_dataset_phys(dzp->z_zfsvfs->z_os->os_dsl_dataset)->ds_guid;
	rec->txg = dmu_tx_get_txg(tx);
	ZEV_FILL_INODE_INFO(parent, dzp);
	ZEV_FILL_INODE_INFO(file, zp);
	rec->name_len = name_len;
	rec->link_len = link_len;
	(void) memcpy(ZEV_NAME(rec), name, name_len + 1);
	(void) memcpy(ZEV_LINK(rec), link, link_len + 1);
	zev_symlink_checksum(rec, link);
	zev_queue_message(op, msg);
}

void
zev_znode_rename_cb(znode_t *sdzp,
                    char *sname,
                    znode_t *tdzp,
                    char *tname,
                    znode_t *szp,
                    znode_t *tzp,
                    dmu_tx_t *tx)
{
	int op = ZEV_OP_ZNODE_RENAME;
	zev_znode_rename_t *rec;
	zev_msg_t *msg = NULL;
	int msg_size;
	int srcname_len;
	int dstname_len;

	if (zev_skip_pool(szp->z_zfsvfs->z_os))
		return;
	if (zev_skip_fs(szp->z_zfsvfs))
		return;

	srcname_len = strlen(sname);
	dstname_len = strlen(tname);
	msg_size = sizeof(*rec) + srcname_len + 1 + dstname_len + 1;
	msg = zev_alloc(sizeof(*msg) + msg_size);
	msg->size = msg_size;
	rec = (zev_znode_rename_t *)(msg + 1);
	rec->record_len = msg_size;
	rec->op = op;
	rec->op_time = ddi_get_time();
	rec->guid =
		dsl_dataset_phys(szp->z_zfsvfs->z_os->os_dsl_dataset)->ds_guid;
	rec->txg = dmu_tx_get_txg(tx);
	ZEV_FILL_INODE_INFO(srcdir, sdzp);
	ZEV_FILL_INODE_INFO(dstdir, tdzp);
	ZEV_FILL_INODE_INFO(file, szp);
	if (tzp) {
		ZEV_FILL_INODE_INFO(clobbered_file, tzp);
	} else {
		memset(&rec->clobbered_file, 0, sizeof(rec->clobbered_file));
	}
	rec->srcname_len = srcname_len;
	rec->dstname_len = dstname_len;
	(void) memcpy(ZEV_SRCNAME(rec), sname, srcname_len + 1);
	(void) memcpy(ZEV_DSTNAME(rec), tname, dstname_len + 1);
	zev_queue_message(op, msg);
}

void
zev_znode_write_cb(znode_t *zp, dmu_tx_t *tx, uint64_t off, uint64_t len)
{
	int op = ZEV_OP_ZNODE_WRITE;
	zev_znode_write_t *rec;
	zev_msg_t *msg = NULL;
	int msg_size;
	zev_sig_t *sig_buf;
	uint64_t sig_buf_len;
	uint64_t sig_len;
	uint64_t sig_cnt;
	int ret;

	if (zev_skip_pool(zp->z_zfsvfs->z_os))
		return;
	if (zev_skip_fs(zp->z_zfsvfs))
		return;

	ret = zev_get_checksums(&sig_buf, &sig_buf_len, &sig_cnt, 0,
	                        zp, off, len, zev_write);
	if (ret) {
		zev_queue_error(op,
		    "ERROR: ZNODE_WRITE: can't get checksum (inode=%d:%d)\n",
		    zp->z_gen,
		    zp->z_id);
		return;
	}
	sig_len = sig_cnt * sizeof(zev_sig_t);

	msg_size = sizeof(*rec) + sig_len;
	msg = zev_alloc(sizeof(*msg) + msg_size);
	msg->size = msg_size;
	rec = (zev_znode_write_t *)(msg + 1);
	rec->record_len = msg_size;
	rec->op = op;
	rec->op_time = ddi_get_time();
	rec->guid =
		dsl_dataset_phys(zp->z_zfsvfs->z_os->os_dsl_dataset)->ds_guid;
	rec->txg = dmu_tx_get_txg(tx);
	ZEV_FILL_INODE_INFO(file, zp);
	rec->offset = off;
	rec->length = len;
	rec->signature_cnt = sig_cnt;
	if (sig_cnt && sig_buf)
		memcpy(ZEV_SIGNATURES(rec), sig_buf, sig_len);
	if (sig_buf)
		zev_free(sig_buf, sig_buf_len);
	zev_queue_message(op, msg);
}

void
zev_znode_truncate_cb(znode_t *zp, dmu_tx_t *tx, uint64_t off, uint64_t len)
{
	int op = ZEV_OP_ZNODE_TRUNCATE;
	zev_znode_truncate_t *rec;
	zev_msg_t *msg = NULL;
	int msg_size;
	zev_sig_t *sig_buf;
	uint64_t sig_buf_len;
	uint64_t sig_len;
	uint64_t sig_cnt;
	int ret;

	if (zev_skip_pool(zp->z_zfsvfs->z_os))
		return;
	if (zev_skip_fs(zp->z_zfsvfs))
		return;

	ret = zev_get_checksums(&sig_buf, &sig_buf_len, &sig_cnt, 0,
	                        zp, off, len, zev_truncate);
	if (ret) {
		zev_queue_error(op,
		    "ERROR: ZNODE_TRUNCATE: can't get checksum (inode=%d:%d)\n",
		    zp->z_gen,
		    zp->z_id);
		return;
	}
	sig_len = sig_cnt * sizeof(zev_sig_t);

	msg_size = sizeof(*rec) + sig_len;
	msg = zev_alloc(sizeof(*msg) + msg_size);
	msg->size = msg_size;
	rec = (zev_znode_truncate_t *)(msg + 1);
	rec->record_len = msg_size;
	rec->op = op;
	rec->op_time = ddi_get_time();
	rec->guid =
		dsl_dataset_phys(zp->z_zfsvfs->z_os->os_dsl_dataset)->ds_guid;
	rec->txg = dmu_tx_get_txg(tx);
	ZEV_FILL_INODE_INFO(file, zp);
	rec->offset = off;
	rec->length = len;
	rec->signature_cnt = sig_cnt;
	if (sig_cnt && sig_buf)
		memcpy(ZEV_SIGNATURES(rec), sig_buf, sig_len);
	if (sig_buf)
		zev_free(sig_buf, sig_buf_len);
	zev_queue_message(op, msg);
}

void
zev_znode_setattr_cb(znode_t *zp, dmu_tx_t *tx)
{
	int op = ZEV_OP_ZNODE_SETATTR;
	zev_znode_setattr_t *rec;
	zev_msg_t *msg = NULL;
	int msg_size;

	if (zev_skip_pool(zp->z_zfsvfs->z_os))
		return;
	if (zev_skip_fs(zp->z_zfsvfs))
		return;

	msg_size = sizeof(*rec);
	msg = zev_alloc(sizeof(*msg) + msg_size);
	msg->size = msg_size;
	rec = (zev_znode_setattr_t *)(msg + 1);
	rec->record_len = msg_size;
	rec->op = op;
	rec->op_time = ddi_get_time();
	rec->guid =
		dsl_dataset_phys(zp->z_zfsvfs->z_os->os_dsl_dataset)->ds_guid;
	rec->txg = dmu_tx_get_txg(tx);
	ZEV_FILL_INODE_INFO(file, zp);
	zev_queue_message(op, msg);
}

void
zev_znode_acl_cb(znode_t *zp, dmu_tx_t *tx)
{
	int op = ZEV_OP_ZNODE_ACL;
	zev_znode_acl_t *rec;
	zev_msg_t *msg = NULL;
	int msg_size;

	if (zev_skip_pool(zp->z_zfsvfs->z_os))
		return;
	if (zev_skip_fs(zp->z_zfsvfs))
		return;

	msg_size = sizeof(*rec);
	msg = zev_alloc(sizeof(*msg) + msg_size);
	msg->size = msg_size;
	rec = (zev_znode_acl_t *)(msg + 1);
	rec->record_len = msg_size;
	rec->op = op;
	rec->op_time = ddi_get_time();
	rec->guid =
		dsl_dataset_phys(zp->z_zfsvfs->z_os->os_dsl_dataset)->ds_guid;
	rec->txg = dmu_tx_get_txg(tx);
	ZEV_FILL_INODE_INFO(file, zp);
	zev_queue_message(op, msg);
}

rz_zev_callbacks_t zev_callbacks = {
	/* zfsvfs events */
	.rz_zev_zfs_mount                = zev_zfs_mount_cb,
	.rz_zev_zfs_umount               = zev_zfs_umount_cb,

	/* zvol zil events */
	.rz_zev_zvol_truncate            = zev_zvol_truncate_cb,
	.rz_zev_zvol_write               = zev_zvol_write_cb,

	/* znode zil events */
	.rz_zev_znode_close_after_update = zev_znode_close_after_update_cb,
	.rz_zev_znode_create             = zev_znode_create_cb,
	.rz_zev_znode_remove             = zev_znode_remove_cb,
	.rz_zev_znode_link               = zev_znode_link_cb,
	.rz_zev_znode_symlink            = zev_znode_symlink_cb,
	.rz_zev_znode_rename             = zev_znode_rename_cb,
	.rz_zev_znode_write              = zev_znode_write_cb,
	.rz_zev_znode_truncate           = zev_znode_truncate_cb,
	.rz_zev_znode_setattr            = zev_znode_setattr_cb,
	.rz_zev_znode_acl                = zev_znode_acl_cb,
};