/*
* 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) {
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 */