#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <pthread.h>
#include <alloca.h>
#include <libnvpair.h>
#include <libhotplug.h>
#include <libhotplug_impl.h>
#include <sys/types.h>
#include <sys/sunddi.h>
#include <sys/ddi_hp.h>
#include <sys/modctl.h>
#include "hotplugd_impl.h"
static pthread_mutex_t hotplug_lock = PTHREAD_MUTEX_INITIALIZER;
static boolean_t check_rcm_required(hp_node_t, int);
static int pack_properties(const char *, ddi_hp_property_t *);
static void unpack_properties(ddi_hp_property_t *, char **);
static void free_properties(ddi_hp_property_t *);
int
changestate(const char *path, const char *connection, int state, uint_t flags,
int *old_statep, hp_node_t *resultsp)
{
hp_node_t root = NULL;
char **rsrcs = NULL;
boolean_t use_rcm = B_FALSE;
int rv;
hp_dprintf("changestate(path=%s, connection=%s, state=0x%x, "
"flags=0x%x)\n", path, connection, state, flags);
*resultsp = NULL;
*old_statep = -1;
(void) pthread_mutex_lock(&hotplug_lock);
if ((rv = getinfo(path, connection, 0, &root)) != 0) {
(void) pthread_mutex_unlock(&hotplug_lock);
hp_dprintf("changestate: getinfo() failed (%s)\n",
strerror(rv));
return (rv);
}
*old_statep = hp_state(root);
use_rcm = check_rcm_required(root, state);
if (use_rcm) {
hp_dprintf("changestate: RCM offline is required.\n");
if ((rv = rcm_resources(root, &rsrcs)) != 0) {
hp_dprintf("changestate: rcm_resources() failed.\n");
(void) pthread_mutex_unlock(&hotplug_lock);
hp_fini(root);
return (rv);
}
if ((rsrcs != NULL) &&
((rv = rcm_offline(rsrcs, flags, root)) != 0)) {
hp_dprintf("changestate: rcm_offline() failed.\n");
rcm_online(rsrcs);
(void) pthread_mutex_unlock(&hotplug_lock);
free_rcm_resources(rsrcs);
*resultsp = root;
return (rv);
}
}
hp_fini(root);
if (flags & HPQUERY) {
hp_dprintf("changestate: operation was QUERY only.\n");
rcm_online(rsrcs);
(void) pthread_mutex_unlock(&hotplug_lock);
free_rcm_resources(rsrcs);
return (0);
}
rv = 0;
if (modctl(MODHPOPS, MODHPOPS_CHANGE_STATE, path, connection, state))
rv = errno;
hp_dprintf("changestate: modctl(MODHPOPS_CHANGE_STATE) = %d.\n", rv);
if (use_rcm && (rsrcs != NULL)) {
if (rv == 0)
rcm_remove(rsrcs);
else
rcm_online(rsrcs);
free_rcm_resources(rsrcs);
}
(void) pthread_mutex_unlock(&hotplug_lock);
*resultsp = NULL;
return (rv);
}
int
private_options(const char *path, const char *connection, hp_cmd_t cmd,
const char *options, char **resultsp)
{
ddi_hp_property_t prop;
ddi_hp_property_t results;
char *values = NULL;
int rv;
hp_dprintf("private_options(path=%s, connection=%s, options='%s')\n",
path, connection, options);
if ((rv = pack_properties(options, &prop)) != 0) {
hp_dprintf("private_options: failed to pack properties.\n");
return (rv);
}
(void) memset(&results, 0, sizeof (ddi_hp_property_t));
results.buf_size = HP_PRIVATE_BUF_SZ;
results.nvlist_buf = (char *)calloc(1, HP_PRIVATE_BUF_SZ);
if (results.nvlist_buf == NULL) {
hp_dprintf("private_options: failed to allocate buffer.\n");
free_properties(&prop);
return (ENOMEM);
}
(void) pthread_mutex_lock(&hotplug_lock);
rv = 0;
if (cmd == HP_CMD_GETPRIVATE) {
if (modctl(MODHPOPS, MODHPOPS_BUS_GET, path, connection,
&prop, &results))
rv = errno;
hp_dprintf("private_options: modctl(MODHPOPS_BUS_GET) = %d\n",
rv);
} else {
if (modctl(MODHPOPS, MODHPOPS_BUS_SET, path, connection,
&prop, &results))
rv = errno;
hp_dprintf("private_options: modctl(MODHPOPS_BUS_SET) = %d\n",
rv);
}
(void) pthread_mutex_unlock(&hotplug_lock);
if (rv == 0) {
unpack_properties(&results, &values);
*resultsp = values;
}
free_properties(&prop);
free_properties(&results);
return (rv);
}
static boolean_t
check_rcm_required(hp_node_t root, int target_state)
{
if ((root->hp_type == HP_NODE_CONNECTOR) &&
HP_IS_ENABLED(root->hp_state) && !HP_IS_ENABLED(target_state))
return (B_TRUE);
if ((root->hp_type == HP_NODE_PORT) &&
HP_IS_ONLINE(root->hp_state) && HP_IS_OFFLINE(target_state))
return (B_TRUE);
return (B_FALSE);
}
static int
pack_properties(const char *options, ddi_hp_property_t *prop)
{
nvlist_t *nvl;
char *buf, *tmp, *name, *value, *next;
size_t len;
(void) memset(prop, 0, sizeof (ddi_hp_property_t));
if ((len = strlen(options)) == 0) {
hp_dprintf("pack_properties: options string is empty.\n");
return (ENOENT);
}
if ((tmp = (char *)alloca(len + 1)) == NULL) {
log_err("Failed to allocate buffer for private options.\n");
return (ENOMEM);
}
(void) strlcpy(tmp, options, len + 1);
if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0) {
log_err("Failed to allocate private options nvlist.\n");
return (ENOMEM);
}
for (name = tmp; name != NULL; name = next) {
if ((next = strchr(name, ',')) != NULL) {
*next = '\0';
next++;
}
if ((value = strchr(name, '=')) != NULL) {
*value = '\0';
value++;
} else {
value = "";
}
if (nvlist_add_string(nvl, name, value) != 0) {
log_err("Failed to add private option to nvlist.\n");
nvlist_free(nvl);
return (EFAULT);
}
}
len = 0;
buf = NULL;
if (nvlist_pack(nvl, &buf, &len, NV_ENCODE_NATIVE, 0) != 0) {
log_err("Failed to pack private options nvlist.\n");
nvlist_free(nvl);
return (EFAULT);
}
prop->nvlist_buf = buf;
prop->buf_size = len;
nvlist_free(nvl);
return (0);
}
static void
unpack_properties(ddi_hp_property_t *prop, char **optionsp)
{
nvlist_t *nvl = NULL;
nvpair_t *nvp;
boolean_t first_flag;
char *name, *value, *options;
size_t len;
*optionsp = NULL;
if ((prop->nvlist_buf == NULL) || (prop->buf_size == 0)) {
hp_dprintf("unpack_properties: no properties exist.\n");
return;
}
if (nvlist_unpack(prop->nvlist_buf, prop->buf_size, &nvl, 0) != 0) {
log_err("Failed to unpack private options nvlist.\n");
return;
}
for (len = 0, nvp = NULL; nvp = nvlist_next_nvpair(nvl, nvp); ) {
name = nvpair_name(nvp);
if ((strcmp(name, "cmd") == 0) ||
(nvpair_type(nvp) != DATA_TYPE_STRING))
continue;
(void) nvpair_value_string(nvp, &value);
len += (strlen(name) + strlen(value) + 2);
}
if ((options = (char *)calloc(len, sizeof (char))) == NULL) {
log_err("Failed to allocate private options string.\n");
nvlist_free(nvl);
return;
}
first_flag = B_TRUE;
for (nvp = NULL; nvp = nvlist_next_nvpair(nvl, nvp); ) {
name = nvpair_name(nvp);
if ((strcmp(name, "cmd") == 0) ||
(nvpair_type(nvp) != DATA_TYPE_STRING))
continue;
if (!first_flag)
(void) strlcat(options, ",", len);
(void) strlcat(options, name, len);
(void) nvpair_value_string(nvp, &value);
if (strlen(value) > 0) {
(void) strlcat(options, "=", len);
(void) strlcat(options, value, len);
}
first_flag = B_FALSE;
}
nvlist_free(nvl);
*optionsp = options;
}
static void
free_properties(ddi_hp_property_t *prop)
{
if (prop) {
if (prop->nvlist_buf)
free(prop->nvlist_buf);
(void) memset(prop, 0, sizeof (ddi_hp_property_t));
}
}