/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2013 Martin Matuska <mm@FreeBSD.org>. All rights reserved.
 * Portions Copyright 2005, 2010, Oracle and/or its affiliates.
 * All rights reserved.
 * Use is subject to license terms.
 */

#include <sys/types.h>
#include <sys/param.h>
#include <sys/cred.h>
#include <sys/dmu.h>
#include <sys/zio.h>
#include <sys/nvpair.h>
#include <sys/dsl_deleg.h>
#include <sys/zfs_ioctl.h>
#include "zfs_namecheck.h"
#include "zfs_ioctl_compat.h"

static int zfs_version_ioctl = ZFS_IOCVER_CURRENT;
SYSCTL_DECL(_vfs_zfs_version);
SYSCTL_INT(_vfs_zfs_version, OID_AUTO, ioctl, CTLFLAG_RD, &zfs_version_ioctl,
    0, "ZFS_IOCTL_VERSION");

/*
 * FreeBSD zfs_cmd compatibility with older binaries
 * appropriately remap/extend the zfs_cmd_t structure
 */
void
zfs_cmd_compat_get(zfs_cmd_t *zc, caddr_t addr, const int cflag)
{
	zfs_cmd_v15_t *zc_c;
	zfs_cmd_v28_t *zc28_c;
	zfs_cmd_deadman_t *zcdm_c;

	switch (cflag) {
	case ZFS_CMD_COMPAT_DEADMAN:
		zcdm_c = (void *)addr;
		/* zc */
		strlcpy(zc->zc_name, zcdm_c->zc_name, MAXPATHLEN);
		strlcpy(zc->zc_value, zcdm_c->zc_value, MAXPATHLEN * 2);
		strlcpy(zc->zc_string, zcdm_c->zc_string, MAXPATHLEN);
		zc->zc_guid = zcdm_c->zc_guid;
		zc->zc_nvlist_conf = zcdm_c->zc_nvlist_conf;
		zc->zc_nvlist_conf_size = zcdm_c->zc_nvlist_conf_size;
		zc->zc_nvlist_src = zcdm_c->zc_nvlist_src;
		zc->zc_nvlist_src_size = zcdm_c->zc_nvlist_src_size;
		zc->zc_nvlist_dst = zcdm_c->zc_nvlist_dst;
		zc->zc_nvlist_dst_size = zcdm_c->zc_nvlist_dst_size;
		zc->zc_cookie = zcdm_c->zc_cookie;
		zc->zc_objset_type = zcdm_c->zc_objset_type;
		zc->zc_perm_action = zcdm_c->zc_perm_action;
		zc->zc_history = zcdm_c->zc_history;
		zc->zc_history_len = zcdm_c->zc_history_len;
		zc->zc_history_offset = zcdm_c->zc_history_offset;
		zc->zc_obj = zcdm_c->zc_obj;
		zc->zc_iflags = zcdm_c->zc_iflags;
		zc->zc_share = zcdm_c->zc_share;
		zc->zc_jailid = zcdm_c->zc_jailid;
		zc->zc_objset_stats = zcdm_c->zc_objset_stats;
		zc->zc_begin_record = zcdm_c->zc_begin_record;
		zc->zc_defer_destroy = zcdm_c->zc_defer_destroy;
		zc->zc_temphold = zcdm_c->zc_temphold;
		zc->zc_action_handle = zcdm_c->zc_action_handle;
		zc->zc_cleanup_fd = zcdm_c->zc_cleanup_fd;
		zc->zc_simple = zcdm_c->zc_simple;
		bcopy(zcdm_c->zc_pad, zc->zc_pad, sizeof(zc->zc_pad));
		zc->zc_sendobj = zcdm_c->zc_sendobj;
		zc->zc_fromobj = zcdm_c->zc_fromobj;
		zc->zc_createtxg = zcdm_c->zc_createtxg;
		zc->zc_stat = zcdm_c->zc_stat;

		/* zc_inject_record doesn't change in libzfs_core */
		zcdm_c->zc_inject_record = zc->zc_inject_record;

		/* we always assume zc_nvlist_dst_filled is true */
		zc->zc_nvlist_dst_filled = B_TRUE;
	break;

	case ZFS_CMD_COMPAT_V28:
		zc28_c = (void *)addr;

		/* zc */
		strlcpy(zc->zc_name, zc28_c->zc_name, MAXPATHLEN);
		strlcpy(zc->zc_value, zc28_c->zc_value, MAXPATHLEN * 2);
		strlcpy(zc->zc_string, zc28_c->zc_string, MAXPATHLEN);
		zc->zc_guid = zc28_c->zc_guid;
		zc->zc_nvlist_conf = zc28_c->zc_nvlist_conf;
		zc->zc_nvlist_conf_size = zc28_c->zc_nvlist_conf_size;
		zc->zc_nvlist_src = zc28_c->zc_nvlist_src;
		zc->zc_nvlist_src_size = zc28_c->zc_nvlist_src_size;
		zc->zc_nvlist_dst = zc28_c->zc_nvlist_dst;
		zc->zc_nvlist_dst_size = zc28_c->zc_nvlist_dst_size;
		zc->zc_cookie = zc28_c->zc_cookie;
		zc->zc_objset_type = zc28_c->zc_objset_type;
		zc->zc_perm_action = zc28_c->zc_perm_action;
		zc->zc_history = zc28_c->zc_history;
		zc->zc_history_len = zc28_c->zc_history_len;
		zc->zc_history_offset = zc28_c->zc_history_offset;
		zc->zc_obj = zc28_c->zc_obj;
		zc->zc_iflags = zc28_c->zc_iflags;
		zc->zc_share = zc28_c->zc_share;
		zc->zc_jailid = zc28_c->zc_jailid;
		zc->zc_objset_stats = zc28_c->zc_objset_stats;
		zc->zc_begin_record = zc28_c->zc_begin_record;
		zc->zc_defer_destroy = zc28_c->zc_defer_destroy;
		zc->zc_temphold = zc28_c->zc_temphold;
		zc->zc_action_handle = zc28_c->zc_action_handle;
		zc->zc_cleanup_fd = zc28_c->zc_cleanup_fd;
		zc->zc_simple = zc28_c->zc_simple;
		bcopy(zc28_c->zc_pad, zc->zc_pad, sizeof(zc->zc_pad));
		zc->zc_sendobj = zc28_c->zc_sendobj;
		zc->zc_fromobj = zc28_c->zc_fromobj;
		zc->zc_createtxg = zc28_c->zc_createtxg;
		zc->zc_stat = zc28_c->zc_stat;

		/* zc->zc_inject_record */
		zc->zc_inject_record.zi_objset =
		    zc28_c->zc_inject_record.zi_objset;
		zc->zc_inject_record.zi_object =
		    zc28_c->zc_inject_record.zi_object;
		zc->zc_inject_record.zi_start =
		    zc28_c->zc_inject_record.zi_start;
		zc->zc_inject_record.zi_end =
		    zc28_c->zc_inject_record.zi_end;
		zc->zc_inject_record.zi_guid =
		    zc28_c->zc_inject_record.zi_guid;
		zc->zc_inject_record.zi_level =
		    zc28_c->zc_inject_record.zi_level;
		zc->zc_inject_record.zi_error =
		    zc28_c->zc_inject_record.zi_error;
		zc->zc_inject_record.zi_type =
		    zc28_c->zc_inject_record.zi_type;
		zc->zc_inject_record.zi_freq =
		    zc28_c->zc_inject_record.zi_freq;
		zc->zc_inject_record.zi_failfast =
		    zc28_c->zc_inject_record.zi_failfast;
		strlcpy(zc->zc_inject_record.zi_func,
		    zc28_c->zc_inject_record.zi_func, MAXNAMELEN);
		zc->zc_inject_record.zi_iotype =
		    zc28_c->zc_inject_record.zi_iotype;
		zc->zc_inject_record.zi_duration =
		    zc28_c->zc_inject_record.zi_duration;
		zc->zc_inject_record.zi_timer =
		    zc28_c->zc_inject_record.zi_timer;
		zc->zc_inject_record.zi_cmd = ZINJECT_UNINITIALIZED;
		zc->zc_inject_record.zi_pad = 0;
		break;

	case ZFS_CMD_COMPAT_V15:
		zc_c = (void *)addr;

		/* zc */
		strlcpy(zc->zc_name, zc_c->zc_name, MAXPATHLEN);
		strlcpy(zc->zc_value, zc_c->zc_value, MAXPATHLEN);
		strlcpy(zc->zc_string, zc_c->zc_string, MAXPATHLEN);
		zc->zc_guid = zc_c->zc_guid;
		zc->zc_nvlist_conf = zc_c->zc_nvlist_conf;
		zc->zc_nvlist_conf_size = zc_c->zc_nvlist_conf_size;
		zc->zc_nvlist_src = zc_c->zc_nvlist_src;
		zc->zc_nvlist_src_size = zc_c->zc_nvlist_src_size;
		zc->zc_nvlist_dst = zc_c->zc_nvlist_dst;
		zc->zc_nvlist_dst_size = zc_c->zc_nvlist_dst_size;
		zc->zc_cookie = zc_c->zc_cookie;
		zc->zc_objset_type = zc_c->zc_objset_type;
		zc->zc_perm_action = zc_c->zc_perm_action;
		zc->zc_history = zc_c->zc_history;
		zc->zc_history_len = zc_c->zc_history_len;
		zc->zc_history_offset = zc_c->zc_history_offset;
		zc->zc_obj = zc_c->zc_obj;
		zc->zc_share = zc_c->zc_share;
		zc->zc_jailid = zc_c->zc_jailid;
		zc->zc_objset_stats = zc_c->zc_objset_stats;
		zc->zc_begin_record = zc_c->zc_begin_record;

		/* zc->zc_inject_record */
		zc->zc_inject_record.zi_objset =
		    zc_c->zc_inject_record.zi_objset;
		zc->zc_inject_record.zi_object =
		    zc_c->zc_inject_record.zi_object;
		zc->zc_inject_record.zi_start =
		    zc_c->zc_inject_record.zi_start;
		zc->zc_inject_record.zi_end =
		    zc_c->zc_inject_record.zi_end;
		zc->zc_inject_record.zi_guid =
		    zc_c->zc_inject_record.zi_guid;
		zc->zc_inject_record.zi_level =
		    zc_c->zc_inject_record.zi_level;
		zc->zc_inject_record.zi_error =
		    zc_c->zc_inject_record.zi_error;
		zc->zc_inject_record.zi_type =
		    zc_c->zc_inject_record.zi_type;
		zc->zc_inject_record.zi_freq =
		    zc_c->zc_inject_record.zi_freq;
		zc->zc_inject_record.zi_failfast =
		    zc_c->zc_inject_record.zi_failfast;
		break;
	}
}

void
zfs_cmd_compat_put(zfs_cmd_t *zc, caddr_t addr, const int request,
    const int cflag)
{
	zfs_cmd_v15_t *zc_c;
	zfs_cmd_v28_t *zc28_c;
	zfs_cmd_deadman_t *zcdm_c;

	switch (cflag) {
	case ZFS_CMD_COMPAT_DEADMAN:
		zcdm_c = (void *)addr;

		strlcpy(zcdm_c->zc_name, zc->zc_name, MAXPATHLEN);
		strlcpy(zcdm_c->zc_value, zc->zc_value, MAXPATHLEN * 2);
		strlcpy(zcdm_c->zc_string, zc->zc_string, MAXPATHLEN);
		zcdm_c->zc_guid = zc->zc_guid;
		zcdm_c->zc_nvlist_conf = zc->zc_nvlist_conf;
		zcdm_c->zc_nvlist_conf_size = zc->zc_nvlist_conf_size;
		zcdm_c->zc_nvlist_src = zc->zc_nvlist_src;
		zcdm_c->zc_nvlist_src_size = zc->zc_nvlist_src_size;
		zcdm_c->zc_nvlist_dst = zc->zc_nvlist_dst;
		zcdm_c->zc_nvlist_dst_size = zc->zc_nvlist_dst_size;
		zcdm_c->zc_cookie = zc->zc_cookie;
		zcdm_c->zc_objset_type = zc->zc_objset_type;
		zcdm_c->zc_perm_action = zc->zc_perm_action;
		zcdm_c->zc_history = zc->zc_history;
		zcdm_c->zc_history_len = zc->zc_history_len;
		zcdm_c->zc_history_offset = zc->zc_history_offset;
		zcdm_c->zc_obj = zc->zc_obj;
		zcdm_c->zc_iflags = zc->zc_iflags;
		zcdm_c->zc_share = zc->zc_share;
		zcdm_c->zc_jailid = zc->zc_jailid;
		zcdm_c->zc_objset_stats = zc->zc_objset_stats;
		zcdm_c->zc_begin_record = zc->zc_begin_record;
		zcdm_c->zc_defer_destroy = zc->zc_defer_destroy;
		zcdm_c->zc_temphold = zc->zc_temphold;
		zcdm_c->zc_action_handle = zc->zc_action_handle;
		zcdm_c->zc_cleanup_fd = zc->zc_cleanup_fd;
		zcdm_c->zc_simple = zc->zc_simple;
		bcopy(zc->zc_pad, zcdm_c->zc_pad, sizeof(zcdm_c->zc_pad));
		zcdm_c->zc_sendobj = zc->zc_sendobj;
		zcdm_c->zc_fromobj = zc->zc_fromobj;
		zcdm_c->zc_createtxg = zc->zc_createtxg;
		zcdm_c->zc_stat = zc->zc_stat;

		/* zc_inject_record doesn't change in libzfs_core */
		zc->zc_inject_record = zcdm_c->zc_inject_record;
#ifndef _KERNEL
		if (request == ZFS_IOC_RECV)
			strlcpy(zcdm_c->zc_top_ds,
			    zc->zc_value + strlen(zc->zc_value) + 1,
			    (MAXPATHLEN * 2) - strlen(zc->zc_value) - 1);
#endif
	break;

	case ZFS_CMD_COMPAT_V28:
		zc28_c = (void *)addr;

		strlcpy(zc28_c->zc_name, zc->zc_name, MAXPATHLEN);
		strlcpy(zc28_c->zc_value, zc->zc_value, MAXPATHLEN * 2);
		strlcpy(zc28_c->zc_string, zc->zc_string, MAXPATHLEN);
		zc28_c->zc_guid = zc->zc_guid;
		zc28_c->zc_nvlist_conf = zc->zc_nvlist_conf;
		zc28_c->zc_nvlist_conf_size = zc->zc_nvlist_conf_size;
		zc28_c->zc_nvlist_src = zc->zc_nvlist_src;
		zc28_c->zc_nvlist_src_size = zc->zc_nvlist_src_size;
		zc28_c->zc_nvlist_dst = zc->zc_nvlist_dst;
		zc28_c->zc_nvlist_dst_size = zc->zc_nvlist_dst_size;
		zc28_c->zc_cookie = zc->zc_cookie;
		zc28_c->zc_objset_type = zc->zc_objset_type;
		zc28_c->zc_perm_action = zc->zc_perm_action;
		zc28_c->zc_history = zc->zc_history;
		zc28_c->zc_history_len = zc->zc_history_len;
		zc28_c->zc_history_offset = zc->zc_history_offset;
		zc28_c->zc_obj = zc->zc_obj;
		zc28_c->zc_iflags = zc->zc_iflags;
		zc28_c->zc_share = zc->zc_share;
		zc28_c->zc_jailid = zc->zc_jailid;
		zc28_c->zc_objset_stats = zc->zc_objset_stats;
		zc28_c->zc_begin_record = zc->zc_begin_record;
		zc28_c->zc_defer_destroy = zc->zc_defer_destroy;
		zc28_c->zc_temphold = zc->zc_temphold;
		zc28_c->zc_action_handle = zc->zc_action_handle;
		zc28_c->zc_cleanup_fd = zc->zc_cleanup_fd;
		zc28_c->zc_simple = zc->zc_simple;
		bcopy(zc->zc_pad, zc28_c->zc_pad, sizeof(zc28_c->zc_pad));
		zc28_c->zc_sendobj = zc->zc_sendobj;
		zc28_c->zc_fromobj = zc->zc_fromobj;
		zc28_c->zc_createtxg = zc->zc_createtxg;
		zc28_c->zc_stat = zc->zc_stat;
#ifndef _KERNEL
		if (request == ZFS_IOC_RECV)
			strlcpy(zc28_c->zc_top_ds,
			    zc->zc_value + strlen(zc->zc_value) + 1,
			    MAXPATHLEN * 2 - strlen(zc->zc_value) - 1);
#endif
		/* zc_inject_record */
		zc28_c->zc_inject_record.zi_objset =
		    zc->zc_inject_record.zi_objset;
		zc28_c->zc_inject_record.zi_object =
		    zc->zc_inject_record.zi_object;
		zc28_c->zc_inject_record.zi_start =
		    zc->zc_inject_record.zi_start;
		zc28_c->zc_inject_record.zi_end =
		    zc->zc_inject_record.zi_end;
		zc28_c->zc_inject_record.zi_guid =
		    zc->zc_inject_record.zi_guid;
		zc28_c->zc_inject_record.zi_level =
		    zc->zc_inject_record.zi_level;
		zc28_c->zc_inject_record.zi_error =
		    zc->zc_inject_record.zi_error;
		zc28_c->zc_inject_record.zi_type =
		    zc->zc_inject_record.zi_type;
		zc28_c->zc_inject_record.zi_freq =
		    zc->zc_inject_record.zi_freq;
		zc28_c->zc_inject_record.zi_failfast =
		    zc->zc_inject_record.zi_failfast;
		strlcpy(zc28_c->zc_inject_record.zi_func,
		    zc->zc_inject_record.zi_func, MAXNAMELEN);
		zc28_c->zc_inject_record.zi_iotype =
		    zc->zc_inject_record.zi_iotype;
		zc28_c->zc_inject_record.zi_duration =
		    zc->zc_inject_record.zi_duration;
		zc28_c->zc_inject_record.zi_timer =
		    zc->zc_inject_record.zi_timer;
		break;

	case ZFS_CMD_COMPAT_V15:
		zc_c = (void *)addr;

		/* zc */
		strlcpy(zc_c->zc_name, zc->zc_name, MAXPATHLEN);
		strlcpy(zc_c->zc_value, zc->zc_value, MAXPATHLEN);
		strlcpy(zc_c->zc_string, zc->zc_string, MAXPATHLEN);
		zc_c->zc_guid = zc->zc_guid;
		zc_c->zc_nvlist_conf = zc->zc_nvlist_conf;
		zc_c->zc_nvlist_conf_size = zc->zc_nvlist_conf_size;
		zc_c->zc_nvlist_src = zc->zc_nvlist_src;
		zc_c->zc_nvlist_src_size = zc->zc_nvlist_src_size;
		zc_c->zc_nvlist_dst = zc->zc_nvlist_dst;
		zc_c->zc_nvlist_dst_size = zc->zc_nvlist_dst_size;
		zc_c->zc_cookie = zc->zc_cookie;
		zc_c->zc_objset_type = zc->zc_objset_type;
		zc_c->zc_perm_action = zc->zc_perm_action;
		zc_c->zc_history = zc->zc_history;
		zc_c->zc_history_len = zc->zc_history_len;
		zc_c->zc_history_offset = zc->zc_history_offset;
		zc_c->zc_obj = zc->zc_obj;
		zc_c->zc_share = zc->zc_share;
		zc_c->zc_jailid = zc->zc_jailid;
		zc_c->zc_objset_stats = zc->zc_objset_stats;
		zc_c->zc_begin_record = zc->zc_begin_record;

		/* zc_inject_record */
		zc_c->zc_inject_record.zi_objset =
		    zc->zc_inject_record.zi_objset;
		zc_c->zc_inject_record.zi_object =
		    zc->zc_inject_record.zi_object;
		zc_c->zc_inject_record.zi_start =
		    zc->zc_inject_record.zi_start;
		zc_c->zc_inject_record.zi_end =
		    zc->zc_inject_record.zi_end;
		zc_c->zc_inject_record.zi_guid =
		    zc->zc_inject_record.zi_guid;
		zc_c->zc_inject_record.zi_level =
		    zc->zc_inject_record.zi_level;
		zc_c->zc_inject_record.zi_error =
		    zc->zc_inject_record.zi_error;
		zc_c->zc_inject_record.zi_type =
		    zc->zc_inject_record.zi_type;
		zc_c->zc_inject_record.zi_freq =
		    zc->zc_inject_record.zi_freq;
		zc_c->zc_inject_record.zi_failfast =
		    zc->zc_inject_record.zi_failfast;

		break;
	}
}

static int
zfs_ioctl_compat_get_nvlist(uint64_t nvl, size_t size, int iflag,
    nvlist_t **nvp)
{
	char *packed;
	int error;
	nvlist_t *list = NULL;

	/*
	 * Read in and unpack the user-supplied nvlist.
	 */
	if (size == 0)
		return (EINVAL);

#ifdef _KERNEL
	packed = kmem_alloc(size, KM_SLEEP);
	if ((error = ddi_copyin((void *)(uintptr_t)nvl, packed, size,
	    iflag)) != 0) {
		kmem_free(packed, size);
		return (error);
	}
#else
	packed = (void *)(uintptr_t)nvl;
#endif

	error = nvlist_unpack(packed, size, &list, 0);

#ifdef _KERNEL
	kmem_free(packed, size);
#endif

	if (error != 0)
		return (error);

	*nvp = list;
	return (0);
}

static int
zfs_ioctl_compat_put_nvlist(zfs_cmd_t *zc, nvlist_t *nvl)
{
	char *packed = NULL;
	int error = 0;
	size_t size;

	VERIFY(nvlist_size(nvl, &size, NV_ENCODE_NATIVE) == 0);

#ifdef _KERNEL
	packed = kmem_alloc(size, KM_SLEEP);
	VERIFY(nvlist_pack(nvl, &packed, &size, NV_ENCODE_NATIVE,
	    KM_SLEEP) == 0);

	if (ddi_copyout(packed,
	    (void *)(uintptr_t)zc->zc_nvlist_dst, size, zc->zc_iflags) != 0)
		error = EFAULT;
	kmem_free(packed, size);
#else
	packed = (void *)(uintptr_t)zc->zc_nvlist_dst;
	VERIFY(nvlist_pack(nvl, &packed, &size, NV_ENCODE_NATIVE,
	    0) == 0);
#endif

	zc->zc_nvlist_dst_size = size;
	return (error);
}

static void
zfs_ioctl_compat_fix_stats_nvlist(nvlist_t *nvl)
{
	nvlist_t **child;
	nvlist_t *nvroot = NULL;
	vdev_stat_t *vs;
	uint_t c, children, nelem;

	if (nvlist_lookup_nvlist_array(nvl, ZPOOL_CONFIG_CHILDREN,
	    &child, &children) == 0) {
		for (c = 0; c < children; c++) {
			zfs_ioctl_compat_fix_stats_nvlist(child[c]);
		}
	}

	if (nvlist_lookup_nvlist(nvl, ZPOOL_CONFIG_VDEV_TREE,
	    &nvroot) == 0)
		zfs_ioctl_compat_fix_stats_nvlist(nvroot);
#ifdef _KERNEL
	if ((nvlist_lookup_uint64_array(nvl, ZPOOL_CONFIG_VDEV_STATS,
#else
	if ((nvlist_lookup_uint64_array(nvl, "stats",
#endif

	    (uint64_t **)&vs, &nelem) == 0)) {
		nvlist_add_uint64_array(nvl,
#ifdef _KERNEL
		    "stats",
#else
		    ZPOOL_CONFIG_VDEV_STATS,
#endif
		    (uint64_t *)vs, nelem);
#ifdef _KERNEL
		nvlist_remove(nvl, ZPOOL_CONFIG_VDEV_STATS,
#else
		nvlist_remove(nvl, "stats",
#endif
		    DATA_TYPE_UINT64_ARRAY);
	}
}

static int
zfs_ioctl_compat_fix_stats(zfs_cmd_t *zc, const int nc)
{
	nvlist_t *nv, *nvp = NULL;
	nvpair_t *elem;
	int error;

	if ((error = zfs_ioctl_compat_get_nvlist(zc->zc_nvlist_dst,
	    zc->zc_nvlist_dst_size, zc->zc_iflags, &nv)) != 0)
		return (error);

	if (nc == 5) { /* ZFS_IOC_POOL_STATS */
		elem = NULL;
		while ((elem = nvlist_next_nvpair(nv, elem)) != NULL) {
			if (nvpair_value_nvlist(elem, &nvp) == 0)
				zfs_ioctl_compat_fix_stats_nvlist(nvp);
		}
		elem = NULL;
	} else
		zfs_ioctl_compat_fix_stats_nvlist(nv);

	error = zfs_ioctl_compat_put_nvlist(zc, nv);

	nvlist_free(nv);

	return (error);
}

static int
zfs_ioctl_compat_pool_get_props(zfs_cmd_t *zc)
{
	nvlist_t *nv, *nva = NULL;
	int error;

	if ((error = zfs_ioctl_compat_get_nvlist(zc->zc_nvlist_dst,
	    zc->zc_nvlist_dst_size, zc->zc_iflags, &nv)) != 0)
		return (error);

#ifdef _KERNEL
	if (nvlist_lookup_nvlist(nv, "allocated", &nva) == 0) {
		nvlist_add_nvlist(nv, "used", nva);
		nvlist_remove(nv, "allocated", DATA_TYPE_NVLIST);
	}

	if (nvlist_lookup_nvlist(nv, "free", &nva) == 0) {
		nvlist_add_nvlist(nv, "available", nva);
		nvlist_remove(nv, "free", DATA_TYPE_NVLIST);
	}
#else
	if (nvlist_lookup_nvlist(nv, "used", &nva) == 0) {
		nvlist_add_nvlist(nv, "allocated", nva);
		nvlist_remove(nv, "used", DATA_TYPE_NVLIST);
	}

	if (nvlist_lookup_nvlist(nv, "available", &nva) == 0) {
		nvlist_add_nvlist(nv, "free", nva);
		nvlist_remove(nv, "available", DATA_TYPE_NVLIST);
	}
#endif

	error = zfs_ioctl_compat_put_nvlist(zc, nv);

	nvlist_free(nv);

	return (error);
}

#ifndef _KERNEL
int
zcmd_ioctl_compat(int fd, int request, zfs_cmd_t *zc, const int cflag)
{
	int nc, ret;
	void *zc_c;
	unsigned long ncmd;
	zfs_iocparm_t zp;

	switch (cflag) {
	case ZFS_CMD_COMPAT_NONE:
		ncmd = _IOWR('Z', request, struct zfs_iocparm);
		zp.zfs_cmd = (uint64_t)zc;
		zp.zfs_cmd_size = sizeof(zfs_cmd_t);
		zp.zfs_ioctl_version = ZFS_IOCVER_CURRENT;
		return (ioctl(fd, ncmd, &zp));
	case ZFS_CMD_COMPAT_LZC:
		ncmd = _IOWR('Z', request, struct zfs_cmd);
		return (ioctl(fd, ncmd, zc));
	case ZFS_CMD_COMPAT_DEADMAN:
		zc_c = malloc(sizeof(zfs_cmd_deadman_t));
		ncmd = _IOWR('Z', request, struct zfs_cmd_deadman);
		break;
	case ZFS_CMD_COMPAT_V28:
		zc_c = malloc(sizeof(zfs_cmd_v28_t));
		ncmd = _IOWR('Z', request, struct zfs_cmd_v28);
		break;
	case ZFS_CMD_COMPAT_V15:
		nc = zfs_ioctl_v28_to_v15[request];
		zc_c = malloc(sizeof(zfs_cmd_v15_t));
		ncmd = _IOWR('Z', nc, struct zfs_cmd_v15);
		break;
	default:
		return (EINVAL);
	}

	if (ZFS_IOCREQ(ncmd) == ZFS_IOC_COMPAT_FAIL)
		return (ENOTSUP);

	zfs_cmd_compat_put(zc, (caddr_t)zc_c, request, cflag);

	ret = ioctl(fd, ncmd, zc_c);
	if (cflag == ZFS_CMD_COMPAT_V15 &&
	    nc == ZFS_IOC_POOL_IMPORT)
		ret = ioctl(fd, _IOWR('Z', ZFS_IOC_POOL_CONFIGS,
		    struct zfs_cmd_v15), zc_c);
	zfs_cmd_compat_get(zc, (caddr_t)zc_c, cflag);
	free(zc_c);

	if (cflag == ZFS_CMD_COMPAT_V15) {
		switch (nc) {
		case ZFS_IOC_POOL_IMPORT:
		case ZFS_IOC_POOL_CONFIGS:
		case ZFS_IOC_POOL_STATS:
		case ZFS_IOC_POOL_TRYIMPORT:
			zfs_ioctl_compat_fix_stats(zc, nc);
			break;
		case 41: /* ZFS_IOC_POOL_GET_PROPS (v15) */
			zfs_ioctl_compat_pool_get_props(zc);
			break;
		}
	}

	return (ret);
}
#else /* _KERNEL */
int
zfs_ioctl_compat_pre(zfs_cmd_t *zc, int *vec, const int cflag)
{
	int error = 0;

	/* are we creating a clone? */
	if (*vec == ZFS_IOC_CREATE && zc->zc_value[0] != '\0')
		*vec = ZFS_IOC_CLONE;

	if (cflag == ZFS_CMD_COMPAT_V15) {
		switch (*vec) {

		case 7: /* ZFS_IOC_POOL_SCRUB (v15) */
			zc->zc_cookie = POOL_SCAN_SCRUB;
			break;
		}
	}

	return (error);
}

void
zfs_ioctl_compat_post(zfs_cmd_t *zc, int vec, const int cflag)
{
	if (cflag == ZFS_CMD_COMPAT_V15) {
		switch (vec) {
		case ZFS_IOC_POOL_CONFIGS:
		case ZFS_IOC_POOL_STATS:
		case ZFS_IOC_POOL_TRYIMPORT:
			zfs_ioctl_compat_fix_stats(zc, vec);
			break;
		case 41: /* ZFS_IOC_POOL_GET_PROPS (v15) */
			zfs_ioctl_compat_pool_get_props(zc);
			break;
		}
	}
}

nvlist_t *
zfs_ioctl_compat_innvl(zfs_cmd_t *zc, nvlist_t * innvl, const int vec,
    const int cflag)
{
	nvlist_t *nvl, *tmpnvl, *hnvl;
	nvpair_t *elem;
	char *poolname, *snapname;
	int err;

	if (cflag == ZFS_CMD_COMPAT_NONE || cflag == ZFS_CMD_COMPAT_LZC)
		goto out;

	switch (vec) {
	case ZFS_IOC_CREATE:
		nvl = fnvlist_alloc();
		fnvlist_add_int32(nvl, "type", zc->zc_objset_type);
		if (innvl != NULL) {
			fnvlist_add_nvlist(nvl, "props", innvl);
			nvlist_free(innvl);
		}
		return (nvl);
	break;
	case ZFS_IOC_CLONE:
		nvl = fnvlist_alloc();
		fnvlist_add_string(nvl, "origin", zc->zc_value);
		if (innvl != NULL) {
			fnvlist_add_nvlist(nvl, "props", innvl);
			nvlist_free(innvl);
		}
		return (nvl);
	break;
	case ZFS_IOC_SNAPSHOT:
		if (innvl == NULL)
			goto out;
		nvl = fnvlist_alloc();
		fnvlist_add_nvlist(nvl, "props", innvl);
		tmpnvl = fnvlist_alloc();
		snapname = kmem_asprintf("%s@%s", zc->zc_name, zc->zc_value);
		fnvlist_add_boolean(tmpnvl, snapname);
		kmem_free(snapname, strlen(snapname + 1));
		/* check if we are doing a recursive snapshot */
		if (zc->zc_cookie)
			dmu_get_recursive_snaps_nvl(zc->zc_name, zc->zc_value,
			    tmpnvl);
		fnvlist_add_nvlist(nvl, "snaps", tmpnvl);
		fnvlist_free(tmpnvl);
		nvlist_free(innvl);
		/* strip dataset part from zc->zc_name */
		zc->zc_name[strcspn(zc->zc_name, "/@")] = '\0';
		return (nvl);
	break;
	case ZFS_IOC_SPACE_SNAPS:
		nvl = fnvlist_alloc();
		fnvlist_add_string(nvl, "firstsnap", zc->zc_value);
		if (innvl != NULL)
			nvlist_free(innvl);
		return (nvl);
	break;
	case ZFS_IOC_DESTROY_SNAPS:
		if (innvl == NULL && cflag == ZFS_CMD_COMPAT_DEADMAN)
			goto out;
		nvl = fnvlist_alloc();
		if (innvl != NULL) {
			fnvlist_add_nvlist(nvl, "snaps", innvl);
		} else {
			/*
			 * We are probably called by even older binaries,
			 * allocate and populate nvlist with recursive
			 * snapshots
			 */
			if (snapshot_namecheck(zc->zc_value, NULL,
			    NULL) == 0) {
				tmpnvl = fnvlist_alloc();
				if (dmu_get_recursive_snaps_nvl(zc->zc_name,
				    zc->zc_value, tmpnvl) == 0)
					fnvlist_add_nvlist(nvl, "snaps",
					    tmpnvl);
				nvlist_free(tmpnvl);
			}
		}
		if (innvl != NULL)
			nvlist_free(innvl);
		/* strip dataset part from zc->zc_name */
		zc->zc_name[strcspn(zc->zc_name, "/@")] = '\0';
		return (nvl);
	break;
	case ZFS_IOC_HOLD:
		nvl = fnvlist_alloc();
		tmpnvl = fnvlist_alloc();
		if (zc->zc_cleanup_fd != -1)
			fnvlist_add_int32(nvl, "cleanup_fd",
			    (int32_t)zc->zc_cleanup_fd);
		if (zc->zc_cookie) {
			hnvl = fnvlist_alloc();
			if (dmu_get_recursive_snaps_nvl(zc->zc_name,
			    zc->zc_value, hnvl) == 0) {
				elem = NULL;
				while ((elem = nvlist_next_nvpair(hnvl,
				    elem)) != NULL) {
					nvlist_add_string(tmpnvl,
					    nvpair_name(elem), zc->zc_string);
				}
			}
			nvlist_free(hnvl);
		} else {
			snapname = kmem_asprintf("%s@%s", zc->zc_name,
			    zc->zc_value);
			nvlist_add_string(tmpnvl, snapname, zc->zc_string);
			kmem_free(snapname, strlen(snapname + 1));
		}
		fnvlist_add_nvlist(nvl, "holds", tmpnvl);
		nvlist_free(tmpnvl);
		if (innvl != NULL)
			nvlist_free(innvl);
		/* strip dataset part from zc->zc_name */
		zc->zc_name[strcspn(zc->zc_name, "/@")] = '\0';
		return (nvl);
	break;
	case ZFS_IOC_RELEASE:
		nvl = fnvlist_alloc();
		tmpnvl = fnvlist_alloc();
		if (zc->zc_cookie) {
			hnvl = fnvlist_alloc();
			if (dmu_get_recursive_snaps_nvl(zc->zc_name,
			    zc->zc_value, hnvl) == 0) {
				elem = NULL;
				while ((elem = nvlist_next_nvpair(hnvl,
				    elem)) != NULL) {
					fnvlist_add_boolean(tmpnvl,
					    zc->zc_string);
					fnvlist_add_nvlist(nvl,
					    nvpair_name(elem), tmpnvl);
				}
			}
			nvlist_free(hnvl);
		} else {
			snapname = kmem_asprintf("%s@%s", zc->zc_name,
			    zc->zc_value);
			fnvlist_add_boolean(tmpnvl, zc->zc_string);
			fnvlist_add_nvlist(nvl, snapname, tmpnvl);
			kmem_free(snapname, strlen(snapname + 1));
		}
		nvlist_free(tmpnvl);
		if (innvl != NULL)
			nvlist_free(innvl);
		/* strip dataset part from zc->zc_name */
		zc->zc_name[strcspn(zc->zc_name, "/@")] = '\0';
		return (nvl);
	break;
	}
out:
	return (innvl);
}

nvlist_t *
zfs_ioctl_compat_outnvl(zfs_cmd_t *zc, nvlist_t * outnvl, const int vec,
    const int cflag)
{
	nvlist_t *tmpnvl;

	if (cflag == ZFS_CMD_COMPAT_NONE || cflag == ZFS_CMD_COMPAT_LZC)
		return (outnvl);

	switch (vec) {
	case ZFS_IOC_SPACE_SNAPS:
		(void) nvlist_lookup_uint64(outnvl, "used", &zc->zc_cookie);
		(void) nvlist_lookup_uint64(outnvl, "compressed",
		    &zc->zc_objset_type);
		(void) nvlist_lookup_uint64(outnvl, "uncompressed",
		    &zc->zc_perm_action);
		nvlist_free(outnvl);
		/* return empty outnvl */
		tmpnvl = fnvlist_alloc();
		return (tmpnvl);
	break;
	case ZFS_IOC_CREATE:
	case ZFS_IOC_CLONE:
	case ZFS_IOC_HOLD:
	case ZFS_IOC_RELEASE:
		nvlist_free(outnvl);
		/* return empty outnvl */
		tmpnvl = fnvlist_alloc();
		return (tmpnvl);
	break;
	}

	return (outnvl);
}
#endif /* KERNEL */
