OpenSolaris_b135/uts/common/ipp/ippctl.c

Compare this file to the similar file:
Show the results in this format:

/*
 * 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 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */


/*
 * IP Policy Framework config driver
 */

#include <sys/types.h>
#include <sys/cmn_err.h>
#include <sys/kmem.h>
#include <sys/errno.h>
#include <sys/cpuvar.h>
#include <sys/open.h>
#include <sys/stat.h>
#include <sys/conf.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/modctl.h>
#include <sys/stream.h>
#include <ipp/ipp.h>
#include <ipp/ippctl.h>
#include <sys/nvpair.h>
#include <sys/policy.h>

/*
 * Debug switch.
 */

#if	defined(DEBUG)
#define	IPPCTL_DEBUG
#endif

/*
 * Debug macros.
 */

#ifdef	IPPCTL_DEBUG

#define	DBG_MODLINK	0x00000001ull
#define	DBG_DEVOPS	0x00000002ull
#define	DBG_CBOPS	0x00000004ull

static	uint64_t	ippctl_debug_flags =
/*
 * DBG_MODLINK |
 * DBG_DEVOPS |
 * DBG_CBOPS |
 */
0;

static kmutex_t	debug_mutex[1];

/*PRINTFLIKE3*/
static void	ippctl_debug(uint64_t, char *, char *, ...)
	__PRINTFLIKE(3);

#define	DBG0(_type, _fmt)		    			\
	ippctl_debug((_type), __FN__, (_fmt));

#define	DBG1(_type, _fmt, _a1) 					\
	ippctl_debug((_type), __FN__, (_fmt), (_a1));

#define	DBG2(_type, _fmt, _a1, _a2)				\
	ippctl_debug((_type), __FN__, (_fmt), (_a1), (_a2));

#define	DBG3(_type, _fmt, _a1, _a2, _a3)			\
	ippctl_debug((_type), __FN__, (_fmt), (_a1), (_a2),	\
	    (_a3));

#define	DBG4(_type, _fmt, _a1, _a2, _a3, _a4)			\
	ippctl_debug((_type), __FN__, (_fmt), (_a1), (_a2),	\
	    (_a3), (_a4));

#define	DBG5(_type, _fmt, _a1, _a2, _a3, _a4, _a5)		\
	ippctl_debug((_type), __FN__, (_fmt), (_a1), (_a2),	\
	    (_a3), (_a4), (_a5));

#else	/* IPPCTL_DBG */

#define	DBG0(_type, _fmt)
#define	DBG1(_type, _fmt, _a1)
#define	DBG2(_type, _fmt, _a1, _a2)
#define	DBG3(_type, _fmt, _a1, _a2, _a3)
#define	DBG4(_type, _fmt, _a1, _a2, _a3, _a4)
#define	DBG5(_type, _fmt, _a1, _a2, _a3, _a4, _a5)

#endif	/* IPPCTL_DBG */

/*
 * cb_ops
 */

static int	ippctl_open(dev_t *, int, int, cred_t *);
static int	ippctl_close(dev_t, int, int, cred_t *);
static int	ippctl_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);

static	struct cb_ops	ippctl_cb_ops = {
	ippctl_open,	/* cb_open */
	ippctl_close,	/* cb_close */
	nodev,		/* cb_strategy */
	nodev,		/* cb_print */
	nodev,		/* cb_dump */
	nodev,		/* cb_read */
	nodev,		/* cb_write */
	ippctl_ioctl,	/* cb_ioctl */
	nodev,		/* cb_devmap */
	nodev,		/* cb_mmap */
	nodev,		/* cb_segmap */
	nochpoll,	/* cb_chpoll */
	ddi_prop_op,	/* cb_prop_op */
	0,		/* cb_str */
	D_NEW | D_MP,	/* cb_flag */
	CB_REV,		/* cb_rev */
	nodev,		/* cb_aread */
	nodev		/* cb_awrite */
};

/*
 * dev_ops
 */

static	int	ippctl_info(dev_info_t *, ddi_info_cmd_t, void *, void **);
static	int	ippctl_attach(dev_info_t *, ddi_attach_cmd_t);
static	int	ippctl_detach(dev_info_t *, ddi_detach_cmd_t);

static	struct dev_ops	ippctl_dev_ops = {
	DEVO_REV,		/* devo_rev, */
	0,			/* devo_refcnt  */
	ippctl_info,		/* devo_getinfo */
	nulldev,		/* devo_identify */
	nulldev,		/* devo_probe */
	ippctl_attach,		/* devo_attach */
	ippctl_detach,		/* devo_detach */
	nodev,			/* devo_reset */
	&ippctl_cb_ops,		/* devo_cb_ops */
	(struct bus_ops *)0,	/* devo_bus_ops */
	NULL,			/* devo_power */
	ddi_quiesce_not_needed,		/* devo_quiesce */
};

static	struct modldrv modldrv = {
	&mod_driverops,
	"IP Policy Configuration Driver",
	&ippctl_dev_ops,
};

static	struct modlinkage modlinkage = {
	MODREV_1,
	&modldrv,
	NULL
};

/*
 * Local definitions, types and prototypes.
 */

#define	MAXUBUFLEN	(1 << 16)

#define	FREE_TEXT(_string)					\
	kmem_free((_string), strlen(_string) + 1)

#define	FREE_TEXT_ARRAY(_array, _nelt)				\
	{							\
		int	j;					\
								\
		for (j = 0; j < (_nelt); j++)			\
			if ((_array)[j] != NULL)		\
				FREE_TEXT((_array)[j]);		\
		kmem_free((_array), (_nelt) * sizeof (char *));	\
	}

typedef	struct ippctl_buf	ippctl_buf_t;

struct ippctl_buf {
	char	*buf;
	size_t	buflen;
};

static int	ippctl_copyin(caddr_t, int, char **, size_t *);
static int	ippctl_copyout(caddr_t, int, char *, size_t);
static int	ippctl_extract_op(nvlist_t *, uint8_t *);
static int	ippctl_extract_aname(nvlist_t *, char **);
static int	ippctl_extract_modname(nvlist_t *, char **);
static int	ippctl_attach_modname(nvlist_t *nvlp, char *val);
static int	ippctl_attach_modname_array(nvlist_t *nvlp, char **val, int);
static int	ippctl_attach_aname_array(nvlist_t *nvlp, char **val, int);
static int	ippctl_extract_flags(nvlist_t *, ipp_flags_t *);
static int	ippctl_cmd(char *, size_t, size_t *);
static int	ippctl_action_create(char *, char *, nvlist_t *, ipp_flags_t);
static int	ippctl_action_destroy(char *, ipp_flags_t);
static int	ippctl_action_modify(char *, nvlist_t *, ipp_flags_t);
static int	ippctl_action_info(char *, ipp_flags_t);
static int	ippctl_action_mod(char *);
static int	ippctl_list_mods(void);
static int	ippctl_mod_list_actions(char *);
static int	ippctl_data(char **, size_t *, size_t *);
static void	ippctl_flush(void);
static int	ippctl_add_nvlist(nvlist_t *, int);
static int	ippctl_callback(nvlist_t *, void *);
static int	ippctl_set_rc(int);
static void	ippctl_alloc(int);
static void	ippctl_realloc(void);
static void	ippctl_free(void);

/*
 * Global data
 */

static dev_info_t	*ippctl_dip = NULL;
static kmutex_t		ippctl_lock;
static boolean_t	ippctl_busy;
static ippctl_buf_t	*ippctl_array = NULL;
static int		ippctl_limit = -1;
static int		ippctl_rindex = -1;
static int		ippctl_windex = -1;

/*
 * Module linkage functions
 */

#define	__FN__	"_init"
int
_init(
	void)
{
	int	rc;

	if ((rc = mod_install(&modlinkage)) != 0) {
		DBG0(DBG_MODLINK, "mod_install failed\n");
		return (rc);
	}

	return (rc);
}
#undef	__FN__

#define	__FN__	"_fini"
int
_fini(
	void)
{
	int	rc;

	if ((rc = mod_remove(&modlinkage)) == 0) {
		return (rc);
	}

	DBG0(DBG_MODLINK, "mod_remove failed\n");
	return (rc);
}
#undef	__FN__

#define	__FN__	"_info"
int
_info(
	struct modinfo	*modinfop)
{
	DBG0(DBG_MODLINK, "calling mod_info\n");
	return (mod_info(&modlinkage, modinfop));
}
#undef	__FN__

/*
 * Driver interface functions (dev_ops and cb_ops)
 */

#define	__FN__	"ippctl_info"
/*ARGSUSED*/
static	int
ippctl_info(
	dev_info_t	*dip,
	ddi_info_cmd_t	cmd,
	void		*arg,
	void 		**result)
{
	int		rc = DDI_FAILURE;

	switch (cmd) {
	case DDI_INFO_DEVT2INSTANCE:
		*result = (void *)0;	/* Single instance driver */
		rc = DDI_SUCCESS;
		break;
	case DDI_INFO_DEVT2DEVINFO:
		*result = (void *)ippctl_dip;
		rc = DDI_SUCCESS;
		break;
	default:
		break;
	}

	return (rc);
}
#undef	__FN__

#define	__FN__	"ippctl_attach"
static	int
ippctl_attach(
	dev_info_t		*dip,
	ddi_attach_cmd_t	cmd)
{
	switch (cmd) {
	case DDI_ATTACH:
		break;
	case DDI_PM_RESUME:
		/*FALLTHRU*/
	case DDI_RESUME:
		/*FALLTHRU*/
	default:
		return (DDI_FAILURE);
	}

	DBG0(DBG_DEVOPS, "DDI_ATTACH\n");

	/*
	 * This is strictly a single instance driver.
	 */

	if (ippctl_dip != NULL)
		return (DDI_FAILURE);

	/*
	 * Create minor node.
	 */

	if (ddi_create_minor_node(dip, "ctl", S_IFCHR, 0,
	    DDI_PSEUDO, 0) != DDI_SUCCESS)
		return (DDI_FAILURE);

	/*
	 * No need for per-instance structure, just store vital data in
	 * globals.
	 */

	ippctl_dip = dip;
	mutex_init(&ippctl_lock, NULL, MUTEX_DRIVER, NULL);
	ippctl_busy = B_FALSE;

	return (DDI_SUCCESS);
}
#undef	__FN__

#define	__FN__	"ippctl_detach"
/*ARGSUSED*/
static	int
ippctl_detach(
	dev_info_t		*dip,
	ddi_detach_cmd_t	cmd)
{
	switch (cmd) {
	case DDI_DETACH:
		break;
	case DDI_PM_SUSPEND:
		/*FALLTHRU*/
	case DDI_SUSPEND:
		/*FALLTHRU*/
	default:
		return (DDI_FAILURE);
	}

	DBG0(DBG_DEVOPS, "DDI_DETACH\n");

	ASSERT(dip == ippctl_dip);

	ddi_remove_minor_node(dip, NULL);
	mutex_destroy(&ippctl_lock);
	ippctl_dip = NULL;

	return (DDI_SUCCESS);
}
#undef	__FN__

#define	__FN__	"ippctl_open"
/*ARGSUSED*/
static	int
ippctl_open(
	dev_t	*devp,
	int	flag,
	int	otyp,
	cred_t	*credp)
{
	minor_t	minor = getminor(*devp);
#define	LIMIT	4

	DBG0(DBG_CBOPS, "open\n");

	/*
	 * Only allow privileged users to open our device.
	 */

	if (secpolicy_net_config(credp, B_FALSE) != 0) {
		DBG0(DBG_CBOPS, "not privileged user\n");
		return (EPERM);
	}

	/*
	 * Sanity check other arguments.
	 */

	if (minor != 0) {
		DBG0(DBG_CBOPS, "bad minor\n");
		return (ENXIO);
	}

	if (otyp != OTYP_CHR) {
		DBG0(DBG_CBOPS, "bad device type\n");
		return (EINVAL);
	}

	/*
	 * This is also a single dev_t driver.
	 */

	mutex_enter(&ippctl_lock);
	if (ippctl_busy) {
		mutex_exit(&ippctl_lock);
		return (EBUSY);
	}
	ippctl_busy = B_TRUE;
	mutex_exit(&ippctl_lock);

	/*
	 * Allocate data buffer array (starting with length LIMIT, defined
	 * at the start of this function).
	 */

	ippctl_alloc(LIMIT);

	DBG0(DBG_CBOPS, "success\n");

	return (0);

#undef	LIMIT
}
#undef	__FN__

#define	__FN__	"ippctl_close"
/*ARGSUSED*/
static	int
ippctl_close(
	dev_t	dev,
	int	flag,
	int	otyp,
	cred_t	*credp)
{
	minor_t	minor = getminor(dev);

	DBG0(DBG_CBOPS, "close\n");

	ASSERT(minor == 0);

	/*
	 * Free the data buffer array.
	 */

	ippctl_free();

	mutex_enter(&ippctl_lock);
	ippctl_busy = B_FALSE;
	mutex_exit(&ippctl_lock);

	DBG0(DBG_CBOPS, "success\n");

	return (0);
}
#undef	__FN__

#define	__FN__	"ippctl_ioctl"
static int
ippctl_ioctl(
	dev_t			dev,
	int			cmd,
	intptr_t		arg,
	int			mode,
	cred_t			*credp,
	int			*rvalp)
{
	minor_t			minor = getminor(dev);
	char			*cbuf;
	char			*dbuf;
	size_t			cbuflen;
	size_t			dbuflen;
	size_t			nextbuflen;
	int			rc;

	/*
	 * Paranoia check.
	 */

	if (secpolicy_net_config(credp, B_FALSE) != 0) {
		DBG0(DBG_CBOPS, "not privileged user\n");
		return (EPERM);
	}

	if (minor != 0) {
		DBG0(DBG_CBOPS, "bad minor\n");
		return (ENXIO);
	}

	switch (cmd) {
	case IPPCTL_CMD:
		DBG0(DBG_CBOPS, "command\n");

		/*
		 * Copy in the command buffer from user space.
		 */

		if ((rc = ippctl_copyin((caddr_t)arg, mode, &cbuf,
		    &cbuflen)) != 0)
			break;

		/*
		 * Execute the command.
		 */

		rc = ippctl_cmd(cbuf, cbuflen, &nextbuflen);

		/*
		 * Pass back the length of the first data buffer.
		 */

		DBG1(DBG_CBOPS, "nextbuflen = %lu\n", nextbuflen);
		*rvalp = nextbuflen;

		/*
		 * Free the kernel copy of the command buffer.
		 */

		kmem_free(cbuf, cbuflen);
		break;

	case IPPCTL_DATA:
		DBG0(DBG_CBOPS, "data\n");

		/*
		 * Grab the next data buffer from the array of pending
		 * buffers.
		 */

		if ((rc = ippctl_data(&dbuf, &dbuflen, &nextbuflen)) != 0)
			break;

		/*
		 * Copy it out to user space.
		 */

		rc = ippctl_copyout((caddr_t)arg, mode, dbuf, dbuflen);

		/*
		 * Pass back the length of the next data buffer.
		 */

		DBG1(DBG_CBOPS, "nextbuflen = %lu\n", nextbuflen);
		*rvalp = nextbuflen;
		break;

	default:
		DBG0(DBG_CBOPS, "unrecognized ioctl\n");
		rc = EINVAL;
		break;
	}

	DBG1(DBG_CBOPS, "rc = %d\n", rc);
	return (rc);
}
#undef	__FN__

/*
 * Local functions
 */

#define	__FN__	"ippctl_copyin"
static int
ippctl_copyin(
	caddr_t		arg,
	int		mode,
	char		**kbufp,
	size_t		*kbuflenp)
{
	ippctl_ioctl_t	iioc;
	caddr_t		ubuf;
	char		*kbuf;
	size_t		ubuflen;

	DBG0(DBG_CBOPS, "copying in ioctl structure\n");

	/*
	 * Copy in the ioctl structure from user-space, converting from 32-bit
	 * as necessary.
	 */

#ifdef	_MULTI_DATAMODEL
	switch (ddi_model_convert_from(mode & FMODELS)) {
	case DDI_MODEL_ILP32:
		{
			ippctl_ioctl32_t	iioc32;

			DBG0(DBG_CBOPS, "converting from 32-bit\n");

			if (ddi_copyin(arg, (caddr_t)&iioc32,
			    sizeof (ippctl_ioctl32_t), mode) != 0)
				return (EFAULT);

			ubuf = (caddr_t)(uintptr_t)iioc32.ii32_buf;
			ubuflen = (size_t)iioc32.ii32_buflen;
		}
		break;
	case DDI_MODEL_NONE:
		if (ddi_copyin(arg, (caddr_t)&iioc, sizeof (ippctl_ioctl_t),
		    mode) != 0)
			return (EFAULT);

		ubuf = iioc.ii_buf;
		ubuflen = iioc.ii_buflen;
		break;
	default:
		return (EFAULT);
	}
#else	/* _MULTI_DATAMODEL */
	if (ddi_copyin(arg, (caddr_t)&iioc, sizeof (ippctl_ioctl_t),
	    mode) != 0)
		return (EFAULT);

	ubuf = iioc.ii_buf;
	ubuflen = iioc.ii_buflen;
#endif	/* _MULTI_DATAMODEL */

	DBG1(DBG_CBOPS, "ubuf = 0x%p\n", (void *)ubuf);
	DBG1(DBG_CBOPS, "ubuflen = %lu\n", ubuflen);

	/*
	 * Sanity check the command buffer information.
	 */

	if (ubuflen == 0 || ubuf == NULL)
		return (EINVAL);
	if (ubuflen > MAXUBUFLEN)
		return (E2BIG);

	/*
	 * Allocate some memory for the command buffer and copy it in.
	 */

	kbuf = kmem_zalloc(ubuflen, KM_SLEEP);
	DBG0(DBG_CBOPS, "copying in nvlist\n");
	if (ddi_copyin(ubuf, (caddr_t)kbuf, ubuflen, mode) != 0) {
		kmem_free(kbuf, ubuflen);
		return (EFAULT);
	}

	*kbufp = kbuf;
	*kbuflenp = ubuflen;
	return (0);
}
#undef	__FN__

#define	__FN__	"ippctl_copyout"
static int
ippctl_copyout(
	caddr_t		arg,
	int		mode,
	char		*kbuf,
	size_t		kbuflen)
{
	ippctl_ioctl_t	iioc;
	caddr_t		ubuf;
	int		ubuflen;

	DBG0(DBG_CBOPS, "copying out ioctl structure\n");

	/*
	 * Copy in the ioctl structure from user-space, converting from 32-bit
	 * as necessary.
	 */

#ifdef	_MULTI_DATAMODEL
	switch (ddi_model_convert_from(mode & FMODELS)) {
	case DDI_MODEL_ILP32:
		{
			ippctl_ioctl32_t	iioc32;

			if (ddi_copyin(arg, (caddr_t)&iioc32,
			    sizeof (ippctl_ioctl32_t), mode) != 0)
				return (EFAULT);

			ubuf = (caddr_t)(uintptr_t)iioc32.ii32_buf;
			ubuflen = iioc32.ii32_buflen;
		}
		break;
	case DDI_MODEL_NONE:
		if (ddi_copyin(arg, (caddr_t)&iioc, sizeof (ippctl_ioctl_t),
		    mode) != 0)
			return (EFAULT);

		ubuf = iioc.ii_buf;
		ubuflen = iioc.ii_buflen;
		break;
	default:
		return (EFAULT);
	}
#else	/* _MULTI_DATAMODEL */
	if (ddi_copyin(arg, (caddr_t)&iioc, sizeof (ippctl_ioctl_t),
	    mode) != 0)
		return (EFAULT);

	ubuf = iioc.ii_buf;
	ubuflen = iioc.ii_buflen;
#endif	/* _MULTI_DATAMODEL */

	DBG1(DBG_CBOPS, "ubuf = 0x%p\n", (void *)ubuf);
	DBG1(DBG_CBOPS, "ubuflen = %d\n", ubuflen);

	/*
	 * Sanity check the data buffer details.
	 */

	if (ubuflen == 0 || ubuf == NULL)
		return (EINVAL);

	if (ubuflen < kbuflen)
		return (ENOSPC);
	if (ubuflen > MAXUBUFLEN)
		return (E2BIG);

	/*
	 * Copy out the data buffer to user space.
	 */

	DBG0(DBG_CBOPS, "copying out nvlist\n");
	if (ddi_copyout((caddr_t)kbuf, ubuf, kbuflen, mode) != 0)
		return (EFAULT);

	return (0);
}
#undef	__FN__

#define	__FN__	"ippctl_extract_op"
static int
ippctl_extract_op(
	nvlist_t	*nvlp,
	uint8_t		*valp)
{
	int		rc;

	/*
	 * Look-up and remove the opcode passed from libipp from the
	 * nvlist.
	 */

	if ((rc = nvlist_lookup_byte(nvlp, IPPCTL_OP, valp)) != 0)
		return (rc);

	(void) nvlist_remove_all(nvlp, IPPCTL_OP);
	return (0);
}
#undef	__FN__

#define	__FN__	"ippctl_extract_aname"
static int
ippctl_extract_aname(
	nvlist_t	*nvlp,
	char		**valp)
{
	int		rc;
	char		*ptr;

	/*
	 * Look-up and remove the action name passed from libipp from the
	 * nvlist.
	 */

	if ((rc = nvlist_lookup_string(nvlp, IPPCTL_ANAME, &ptr)) != 0)
		return (rc);

	*valp = kmem_alloc(strlen(ptr) + 1, KM_SLEEP);
	(void) strcpy(*valp, ptr);
	(void) nvlist_remove_all(nvlp, IPPCTL_ANAME);
	return (0);
}
#undef	__FN__

#define	__FN__	"ippctl_extract_modname"
static int
ippctl_extract_modname(
	nvlist_t	*nvlp,
	char		**valp)
{
	int		rc;
	char		*ptr;

	/*
	 * Look-up and remove the module name passed from libipp from the
	 * nvlist.
	 */

	if ((rc = nvlist_lookup_string(nvlp, IPPCTL_MODNAME, &ptr)) != 0)
		return (rc);

	*valp = kmem_alloc(strlen(ptr) + 1, KM_SLEEP);
	(void) strcpy(*valp, ptr);
	(void) nvlist_remove_all(nvlp, IPPCTL_MODNAME);
	return (0);
}
#undef	__FN__

#define	__FN__	"ippctl_attach_modname"
static int
ippctl_attach_modname(
	nvlist_t	*nvlp,
	char		*modname)
{
	/*
	 * Add a module name to an nvlist for passing back to user
	 * space.
	 */

	return (nvlist_add_string(nvlp, IPPCTL_MODNAME, modname));
}
#undef	__FN__

#define	__FN__	"ippctl_attach_modname_array"
static int
ippctl_attach_modname_array(
	nvlist_t	*nvlp,
	char		**modname_array,
	int		nelt)
{
	/*
	 * Add a module name array to an nvlist for passing back to user
	 * space.
	 */

	return (nvlist_add_string_array(nvlp, IPPCTL_MODNAME_ARRAY,
	    modname_array, nelt));
}
#undef	__FN__

#define	__FN__	"ippctl_attach_aname_array"
static int
ippctl_attach_aname_array(
	nvlist_t	*nvlp,
	char		**aname_array,
	int		nelt)
{
	/*
	 * Add an action name array to an nvlist for passing back to user
	 * space.
	 */

	return (nvlist_add_string_array(nvlp, IPPCTL_ANAME_ARRAY,
	    aname_array, nelt));
}
#undef	__FN__

#define	__FN__	"ippctl_extract_flags"
static int
ippctl_extract_flags(
	nvlist_t	*nvlp,
	ipp_flags_t	*valp)
{
	int		rc;

	/*
	 * Look-up and remove the flags passed from libipp from the
	 * nvlist.
	 */

	if ((rc = nvlist_lookup_uint32(nvlp, IPPCTL_FLAGS,
	    (uint32_t *)valp)) != 0)
		return (rc);

	(void) nvlist_remove_all(nvlp, IPPCTL_FLAGS);
	return (0);
}
#undef	__FN__

#define	__FN__	"ippctl_cmd"
static int
ippctl_cmd(
	char		*cbuf,
	size_t		cbuflen,
	size_t		*nextbuflenp)
{
	nvlist_t	*nvlp = NULL;
	int		rc;
	char		*aname = NULL;
	char		*modname = NULL;
	ipp_flags_t	flags;
	uint8_t		op;

	/*
	 * Start a new command cycle by flushing any previous data buffers.
	 */

	ippctl_flush();
	*nextbuflenp = 0;

	/*
	 * Unpack the nvlist from the command buffer.
	 */

	if ((rc = nvlist_unpack(cbuf, cbuflen, &nvlp, KM_SLEEP)) != 0)
		return (rc);

	/*
	 * Extract the opcode to find out what we should do.
	 */

	if ((rc = ippctl_extract_op(nvlp, &op)) != 0) {
		nvlist_free(nvlp);
		return (rc);
	}

	switch (op) {
	case IPPCTL_OP_ACTION_CREATE:
		/*
		 * Create a new action.
		 */

		DBG0(DBG_CBOPS, "op = IPPCTL_OP_ACTION_CREATE\n");

		/*
		 * Extract the module name, action name and flags from the
		 * nvlist.
		 */

		if ((rc = ippctl_extract_modname(nvlp, &modname)) != 0) {
			nvlist_free(nvlp);
			return (rc);
		}

		if ((rc = ippctl_extract_aname(nvlp, &aname)) != 0) {
			FREE_TEXT(modname);
			nvlist_free(nvlp);
			return (rc);
		}

		if ((rc = ippctl_extract_flags(nvlp, &flags)) != 0) {
			FREE_TEXT(aname);
			FREE_TEXT(modname);
			nvlist_free(nvlp);
			return (rc);
		}


		rc = ippctl_action_create(modname, aname, nvlp, flags);
		break;

	case IPPCTL_OP_ACTION_MODIFY:

		/*
		 * Modify an existing action.
		 */

		DBG0(DBG_CBOPS, "op = IPPCTL_OP_ACTION_MODIFY\n");

		/*
		 * Extract the action name and flags from the nvlist.
		 */

		if ((rc = ippctl_extract_aname(nvlp, &aname)) != 0) {
			nvlist_free(nvlp);
			return (rc);
		}

		if ((rc = ippctl_extract_flags(nvlp, &flags)) != 0) {
			FREE_TEXT(aname);
			nvlist_free(nvlp);
			return (rc);
		}

		rc = ippctl_action_modify(aname, nvlp, flags);
		break;

	case IPPCTL_OP_ACTION_DESTROY:

		/*
		 * Destroy an action.
		 */

		DBG0(DBG_CBOPS, "op = IPPCTL_OP_ACTION_DESTROY\n");

		/*
		 * Extract the action name and flags from the nvlist.
		 */

		if ((rc = ippctl_extract_aname(nvlp, &aname)) != 0) {
			nvlist_free(nvlp);
			return (rc);
		}

		if ((rc = ippctl_extract_flags(nvlp, &flags)) != 0) {
			FREE_TEXT(aname);
			nvlist_free(nvlp);
			return (rc);
		}

		nvlist_free(nvlp);
		rc = ippctl_action_destroy(aname, flags);
		break;

	case IPPCTL_OP_ACTION_INFO:

		/*
		 * Retrive the configuration of an action.
		 */

		DBG0(DBG_CBOPS, "op = IPPCTL_OP_ACTION_INFO\n");

		/*
		 * Extract the action name and flags from the nvlist.
		 */

		if ((rc = ippctl_extract_aname(nvlp, &aname)) != 0) {
			nvlist_free(nvlp);
			return (rc);
		}

		if ((rc = ippctl_extract_flags(nvlp, &flags)) != 0) {
			nvlist_free(nvlp);
			FREE_TEXT(aname);
			return (rc);
		}

		nvlist_free(nvlp);
		rc = ippctl_action_info(aname, flags);
		break;

	case IPPCTL_OP_ACTION_MOD:

		/*
		 * Find the module that implements a given action.
		 */

		DBG0(DBG_CBOPS, "op = IPPCTL_OP_ACTION_MOD\n");

		/*
		 * Extract the action name from the nvlist.
		 */

		if ((rc = ippctl_extract_aname(nvlp, &aname)) != 0) {
			nvlist_free(nvlp);
			return (rc);
		}

		nvlist_free(nvlp);
		rc = ippctl_action_mod(aname);
		break;

	case IPPCTL_OP_LIST_MODS:

		/*
		 * List all the modules.
		 */

		DBG0(DBG_CBOPS, "op = IPPCTL_OP_LIST_MODS\n");

		nvlist_free(nvlp);
		rc = ippctl_list_mods();
		break;

	case IPPCTL_OP_MOD_LIST_ACTIONS:

		/*
		 * List all the actions for a given module.
		 */

		DBG0(DBG_CBOPS, "op = IPPCTL_OP_LIST_MODS\n");

		if ((rc = ippctl_extract_modname(nvlp, &modname)) != 0) {
			nvlist_free(nvlp);
			return (rc);
		}

		nvlist_free(nvlp);
		rc = ippctl_mod_list_actions(modname);
		break;

	default:

		/*
		 * Unrecognized opcode.
		 */

		nvlist_free(nvlp);
		rc = EINVAL;
		break;
	}

	/*
	 * The length of buffer that we need to notify back to libipp with
	 * the command ioctl's return is the length of the first data buffer
	 * in the array. We only expact to pass back data buffers if the
	 * operation succeeds (NOTE: this does not mean the kernel call has
	 * to succeed, merely that we successfully issued it and processed
	 * the results).
	 */

	if (rc == 0)
		*nextbuflenp = ippctl_array[0].buflen;

	return (rc);
}
#undef	__FN__

#define	__FN__	"ippctl_action_create"
static int
ippctl_action_create(
	char		*modname,
	char		*aname,
	nvlist_t	*nvlp,
	ipp_flags_t	flags)
{
	int		ipp_rc;
	int		rc;
	ipp_mod_id_t	mid;
	ipp_action_id_t	aid;

	/*
	 * Look up the module id from the name and create the new
	 * action.
	 */

	mid = ipp_mod_lookup(modname);
	FREE_TEXT(modname);

	ipp_rc = ipp_action_create(mid, aname, &nvlp, flags, &aid);
	FREE_TEXT(aname);

	/*
	 * Add an nvlist containing the kernel return code to the
	 * set of nvlists to pass back to libipp.
	 */

	if ((rc = ippctl_set_rc(ipp_rc)) != 0) {
		if (nvlp != NULL) {
			nvlist_free(nvlp);
			if (ipp_action_destroy(aid, 0) != 0) {
				cmn_err(CE_PANIC,
				    "ippctl: unrecoverable error (aid = %d)",
				    aid);
				/*NOTREACHED*/
			}
		}
		return (rc);
	}

	/*
	 * If the module passed back an nvlist, add this as
	 * well.
	 */

	if (nvlp != NULL) {
		rc = ippctl_callback(nvlp, NULL);
		nvlist_free(nvlp);
	} else
		rc = 0;

	return (rc);
}
#undef	__FN__

#define	__FN__	"ippctl_action_destroy"
static int
ippctl_action_destroy(
	char		*aname,
	ipp_flags_t	flags)
{
	ipp_action_id_t	aid;
	int		ipp_rc;
	int		rc;

	/*
	 * Look up the action id and destroy the action.
	 */

	aid = ipp_action_lookup(aname);
	FREE_TEXT(aname);

	ipp_rc = ipp_action_destroy(aid, flags);

	/*
	 * Add an nvlist containing the kernel return code to the
	 * set of nvlists to pass back to libipp.
	 */

	if ((rc = ippctl_set_rc(ipp_rc)) != 0)
		return (rc);

	/*
	 * There's no more information to pass back.
	 */

	return (0);
}
#undef	__FN__

#define	__FN__	"ippctl_action_modify"
static int
ippctl_action_modify(
	char		*aname,
	nvlist_t	*nvlp,
	ipp_flags_t	flags)
{
	ipp_action_id_t	aid;
	int		ipp_rc;
	int		rc;

	/*
	 * Look up the action id and modify the action.
	 */

	aid = ipp_action_lookup(aname);
	FREE_TEXT(aname);

	ipp_rc = ipp_action_modify(aid, &nvlp, flags);

	/*
	 * Add an nvlist containing the kernel return code to the
	 * set of nvlists to pass back to libipp.
	 */

	if ((rc = ippctl_set_rc(ipp_rc)) != 0) {
		if (nvlp != NULL)
			nvlist_free(nvlp);
		return (rc);
	}

	/*
	 * If the module passed back an nvlist, add this as
	 * well.
	 */

	if (nvlp != NULL) {
		rc = ippctl_callback(nvlp, NULL);
		nvlist_free(nvlp);
	} else
		rc = 0;

	return (rc);
}
#undef	__FN__

#define	__FN__	"ippctl_action_info"
static int
ippctl_action_info(
	char		*aname,
	ipp_flags_t	flags)
{
	ipp_action_id_t	aid;
	int		ipp_rc;
	int		rc;

	/*
	 * Look up the action and call the information retrieval
	 * entry point.
	 *
	 * NOTE: The callback function that is passed in packs and
	 * stores each of the nvlists it is called with in the array
	 * that will be passed back to libipp.
	 */

	aid = ipp_action_lookup(aname);
	FREE_TEXT(aname);

	ipp_rc = ipp_action_info(aid, ippctl_callback, NULL, flags);

	/*
	 * Add an nvlist containing the kernel return code to the
	 * set of nvlists to pass back to libipp.
	 */

	if ((rc = ippctl_set_rc(ipp_rc)) != 0)
		return (rc);

	/*
	 * There's no more information to pass back.
	 */

	return (0);
}
#undef	__FN__

#define	__FN__	"ippctl_action_mod"
static int
ippctl_action_mod(
	char		*aname)
{
	ipp_mod_id_t	mid;
	ipp_action_id_t	aid;
	char		*modname;
	nvlist_t	*nvlp;
	int		ipp_rc;
	int		rc;

	/*
	 * Look up the action id and get the id of the module that
	 * implements the action. If that succeeds then look up the
	 * name of the module.
	 */

	aid = ipp_action_lookup(aname);
	FREE_TEXT(aname);

	if ((ipp_rc = ipp_action_mod(aid, &mid)) == 0)
		ipp_rc = ipp_mod_name(mid, &modname);

	/*
	 * Add an nvlist containing the kernel return code to the
	 * set of nvlists to pass back to libipp.
	 */

	if ((rc = ippctl_set_rc(ipp_rc)) != 0)
		return (rc);

	/*
	 * If everything succeeded add an nvlist containing the
	 * module name to the set of nvlists to pass back to libipp.
	 */

	if (ipp_rc == 0) {
		if ((rc = nvlist_alloc(&nvlp, NV_UNIQUE_NAME, KM_SLEEP)) != 0)
			return (rc);

		if ((rc = ippctl_attach_modname(nvlp, modname)) != 0) {
			nvlist_free(nvlp);
			return (rc);
		}

		FREE_TEXT(modname);

		rc = ippctl_callback(nvlp, NULL);
		nvlist_free(nvlp);
	} else
		rc = 0;

	return (rc);
}
#undef	__FN__

#define	__FN__	"ippctl_list_mods"
static int
ippctl_list_mods(
	void)
{
	nvlist_t	*nvlp;
	int		ipp_rc;
	int		rc = 0;
	ipp_mod_id_t	*mid_array;
	char		**modname_array = NULL;
	int		nelt;
	int		length;
	int		i;

	/*
	 * Get a list of all the module ids. If that succeeds,
	 * translate the ids into names.
	 *
	 * NOTE: This translation may fail if a module is
	 * unloaded during this operation. If this occurs, EAGAIN
	 * will be passed back to libipp note that a transient
	 * problem occured.
	 */

	if ((ipp_rc = ipp_list_mods(&mid_array, &nelt)) == 0) {

		/*
		 * It is possible that there are no modules
		 * registered.
		 */

		if (nelt > 0) {
			length = nelt * sizeof (char *);
			modname_array = kmem_zalloc(length, KM_SLEEP);

			for (i = 0; i < nelt; i++) {
				if (ipp_mod_name(mid_array[i],
				    &modname_array[i]) != 0) {
					kmem_free(mid_array, nelt *
					    sizeof (ipp_mod_id_t));
					FREE_TEXT_ARRAY(modname_array, nelt);
					ipp_rc = EAGAIN;
					goto done;
				}
			}

			kmem_free(mid_array, nelt * sizeof (ipp_mod_id_t));

			if ((rc = nvlist_alloc(&nvlp, NV_UNIQUE_NAME,
			    KM_SLEEP)) != 0) {
				FREE_TEXT_ARRAY(modname_array, nelt);
				return (rc);
			}

			if ((rc = ippctl_attach_modname_array(nvlp,
			    modname_array, nelt)) != 0) {
				FREE_TEXT_ARRAY(modname_array, nelt);
				nvlist_free(nvlp);
				return (rc);
			}

			FREE_TEXT_ARRAY(modname_array, nelt);

			if ((rc = ippctl_callback(nvlp, NULL)) != 0) {
				nvlist_free(nvlp);
				return (rc);
			}

			nvlist_free(nvlp);
		}
	}

done:
	/*
	 * Add an nvlist containing the kernel return code to the
	 * set of nvlists to pass back to libipp.
	 */

	if ((rc = ippctl_set_rc(ipp_rc)) != 0)
		return (rc);

	return (0);
}
#undef	__FN__

#define	__FN__	"ippctl_mod_list_actions"
static int
ippctl_mod_list_actions(
	char		*modname)
{
	ipp_mod_id_t	mid;
	nvlist_t	*nvlp;
	int		ipp_rc;
	int		rc = 0;
	ipp_action_id_t	*aid_array;
	char		**aname_array = NULL;
	int		nelt;
	int		length;
	int		i;

	/*
	 * Get the module id.
	 */

	mid = ipp_mod_lookup(modname);
	FREE_TEXT(modname);

	/*
	 * Get a list of all the action ids for the module. If that succeeds,
	 * translate the ids into names.
	 *
	 * NOTE: This translation may fail if an action is
	 * destroyed during this operation. If this occurs, EAGAIN
	 * will be passed back to libipp note that a transient
	 * problem occured.
	 */

	if ((ipp_rc = ipp_mod_list_actions(mid, &aid_array, &nelt)) == 0) {

		/*
		 * It is possible that there are no actions defined.
		 * (This is unlikely though as the module would normally
		 * be auto-unloaded fairly quickly)
		 */

		if (nelt > 0) {
			length = nelt * sizeof (char *);
			aname_array = kmem_zalloc(length, KM_SLEEP);

			for (i = 0; i < nelt; i++) {
				if (ipp_action_name(aid_array[i],
				    &aname_array[i]) != 0) {
					kmem_free(aid_array, nelt *
					    sizeof (ipp_action_id_t));
					FREE_TEXT_ARRAY(aname_array, nelt);
					ipp_rc = EAGAIN;
					goto done;
				}
			}

			kmem_free(aid_array, nelt * sizeof (ipp_action_id_t));

			if ((rc = nvlist_alloc(&nvlp, NV_UNIQUE_NAME,
			    KM_SLEEP)) != 0) {
				FREE_TEXT_ARRAY(aname_array, nelt);
				return (rc);
			}

			if ((rc = ippctl_attach_aname_array(nvlp, aname_array,
			    nelt)) != 0) {
				FREE_TEXT_ARRAY(aname_array, nelt);
				nvlist_free(nvlp);
				return (rc);
			}

			FREE_TEXT_ARRAY(aname_array, nelt);

			if ((rc = ippctl_callback(nvlp, NULL)) != 0) {
				nvlist_free(nvlp);
				return (rc);
			}

			nvlist_free(nvlp);
		}
	}

done:
	/*
	 * Add an nvlist containing the kernel return code to the
	 * set of nvlists to pass back to libipp.
	 */

	if ((rc = ippctl_set_rc(ipp_rc)) != 0)
		return (rc);

	return (0);
}
#undef	__FN__

#define	__FN__	"ippctl_data"
static int
ippctl_data(
	char	**dbufp,
	size_t	*dbuflenp,
	size_t	*nextbuflenp)
{
	int	i;

	DBG0(DBG_CBOPS, "called\n");

	/*
	 * Get the next data buffer from the array by looking at the
	 * 'read index'. If this is the same as the 'write index' then
	 * there's no more buffers in the array.
	 */

	i = ippctl_rindex;
	if (i == ippctl_windex)
		return (ENOENT);

	/*
	 * Extract the buffer details. It is a pre-packed nvlist.
	 */

	*dbufp = ippctl_array[i].buf;
	*dbuflenp = ippctl_array[i].buflen;

	DBG2(DBG_CBOPS, "accessing nvlist[%d], length %lu\n", i, *dbuflenp);
	ASSERT(*dbufp != NULL);

	/*
	 * Advance the 'read index' and check if there's another buffer.
	 * If there is then we need to pass back its length to libipp so that
	 * another data ioctl will be issued.
	 */

	i++;
	if (i < ippctl_windex)
		*nextbuflenp = ippctl_array[i].buflen;
	else
		*nextbuflenp = 0;

	ippctl_rindex = i;
	return (0);
}
#undef	__FN__

#define	__FN__	"ippctl_flush"
static void
ippctl_flush(
	void)
{
	int	i;
	char	*buf;
	size_t	buflen;

	/*
	 * Free any buffers left in the array.
	 */

	for (i = 0; i < ippctl_limit; i++) {
		if ((buflen = ippctl_array[i].buflen) > 0) {
			buf = ippctl_array[i].buf;
			ASSERT(buf != NULL);
			kmem_free(buf, buflen);
		}
	}

	/*
	 * NULL all the entries.
	 */

	bzero(ippctl_array, ippctl_limit * sizeof (ippctl_buf_t));

	/*
	 * Reset the indexes.
	 */

	ippctl_rindex = 0;
	ippctl_windex = 1;
}
#undef	__FN__

#define	__FN__	"ippctl_add_nvlist"
static int
ippctl_add_nvlist(
	nvlist_t	*nvlp,
	int		i)
{
	char		*buf;
	size_t		buflen;
	int		rc;

	/*
	 * NULL the buffer pointer so that a buffer is automatically
	 * allocated for us.
	 */

	buf = NULL;

	/*
	 * Pack the nvlist and get back the buffer pointer and length.
	 */

	if ((rc = nvlist_pack(nvlp, &buf, &buflen, NV_ENCODE_NATIVE,
	    KM_SLEEP)) != 0) {
		ippctl_array[i].buf = NULL;
		ippctl_array[i].buflen = 0;
		return (rc);
	}

	DBG2(DBG_CBOPS, "added nvlist[%d]: length %lu\n", i, buflen);

	/*
	 * Store the pointer an length in the array at the given index.
	 */

	ippctl_array[i].buf = buf;
	ippctl_array[i].buflen = buflen;

	return (0);
}
#undef	__FN__

#define	__FN__	"ippctl_callback"
/*ARGSUSED*/
static int
ippctl_callback(
	nvlist_t	*nvlp,
	void		*arg)
{
	int		i;
	int		rc;

	/*
	 * Check the 'write index' to see if there's space in the array for
	 * a new entry.
	 */

	i = ippctl_windex;
	ASSERT(i != 0);

	/*
	 * If there's no space, re-allocate the array (see comments in
	 * ippctl_realloc() for details).
	 */

	if (i == ippctl_limit)
		ippctl_realloc();

	/*
	 * Add the nvlist to the array.
	 */

	if ((rc = ippctl_add_nvlist(nvlp, i)) == 0)
		ippctl_windex++;

	return (rc);
}
#undef	__FN__

#define	__FN__	"ippctl_set_rc"
static int
ippctl_set_rc(
	int		val)
{
	nvlist_t	*nvlp;
	int		rc;

	/*
	 * Create an nvlist to store the return code,
	 */

	if ((rc = nvlist_alloc(&nvlp, NV_UNIQUE_NAME, KM_SLEEP)) != 0)
		return (ENOMEM);

	if ((rc = nvlist_add_int32(nvlp, IPPCTL_RC, val)) != 0) {
		nvlist_free(nvlp);
		return (rc);
	}

	/*
	 * Add it at the beginning of the array.
	 */

	rc = ippctl_add_nvlist(nvlp, 0);

	nvlist_free(nvlp);
	return (rc);
}
#undef	__FN__

#define	__FN__	"ippctl_alloc"
static void
ippctl_alloc(
	int	limit)
{
	/*
	 * Allocate the data buffer array and initialize the indexes.
	 */

	ippctl_array = kmem_zalloc(limit * sizeof (ippctl_buf_t), KM_SLEEP);
	ippctl_limit = limit;
	ippctl_rindex = 0;
	ippctl_windex = 1;
}
#undef	__FN__

#define	__FN__	"ippctl_realloc"
static void
ippctl_realloc(
	void)
{
	ippctl_buf_t	*array;
	int		limit;
	int		i;

	/*
	 * Allocate a new array twice the size of the old one.
	 */

	limit = ippctl_limit << 1;
	array = kmem_zalloc(limit * sizeof (ippctl_buf_t), KM_SLEEP);

	/*
	 * Copy across the information from the old array into the new one.
	 */

	for (i = 0; i < ippctl_limit; i++)
		array[i] = ippctl_array[i];

	/*
	 * Free the old array.
	 */

	kmem_free(ippctl_array, ippctl_limit * sizeof (ippctl_buf_t));

	ippctl_array = array;
	ippctl_limit = limit;
}
#undef	__FN__

#define	__FN__	"ippctl_free"
static void
ippctl_free(
	void)
{
	/*
	 * Flush the array prior to freeing it to make sure no buffers are
	 * leaked.
	 */

	ippctl_flush();

	/*
	 * Free the array.
	 */

	kmem_free(ippctl_array, ippctl_limit * sizeof (ippctl_buf_t));
	ippctl_array = NULL;
	ippctl_limit = -1;
	ippctl_rindex = -1;
	ippctl_windex = -1;
}
#undef	__FN__

#ifdef	IPPCTL_DEBUG
static void
ippctl_debug(
	uint64_t	type,
	char		*fn,
	char		*fmt,
			...)
{
	char		buf[255];
	va_list		adx;

	if ((type & ippctl_debug_flags) == 0)
		return;

	mutex_enter(debug_mutex);
	va_start(adx, fmt);
	(void) vsnprintf(buf, 255, fmt, adx);
	va_end(adx);

	printf("%s: %s", fn, buf);
	mutex_exit(debug_mutex);
}
#endif	/* IPPCTL_DBG */