#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <librcm.h>
#include <libhotplug.h>
#include <libhotplug_impl.h>
#include <sys/sunddi.h>
#include <sys/ddi_hp.h>
#include "hotplugd_impl.h"
typedef struct info_entry {
char *rsrc;
char *usage;
struct info_entry *next;
} info_entry_t;
typedef struct {
char *path;
info_entry_t *entries;
} info_table_t;
typedef struct {
int error;
int n_rsrcs;
char **rsrcs;
char path[MAXPATHLEN];
char connection[MAXPATHLEN];
char dev_path[MAXPATHLEN];
} resource_cb_arg_t;
typedef struct {
int error;
info_table_t *table;
size_t table_len;
char path[MAXPATHLEN];
char connection[MAXPATHLEN];
} merge_cb_arg_t;
static int merge_rcm_info(hp_node_t root, rcm_info_t *info);
static int get_rcm_usage(char **rsrcs, rcm_info_t **info_p);
static int build_table(rcm_info_t *info, info_table_t **tablep,
size_t *table_lenp);
static void free_table(info_table_t *table, size_t table_len);
static int resource_callback(hp_node_t node, void *argp);
static int merge_callback(hp_node_t node, void *argp);
static int rsrc2path(const char *rsrc, char *path);
static int compare_info(const void *a, const void *b);
int
copy_usage(hp_node_t root)
{
rcm_info_t *info = NULL;
char **rsrcs = NULL;
int rv;
if ((rv = rcm_resources(root, &rsrcs)) != 0) {
log_err("Cannot get RCM resources (%s)\n", strerror(rv));
return (rv);
}
if (rsrcs == NULL)
return (0);
if ((rv = get_rcm_usage(rsrcs, &info)) != 0) {
log_err("Cannot get RCM information (%s)\n", strerror(rv));
free_rcm_resources(rsrcs);
return (rv);
}
free_rcm_resources(rsrcs);
if (info != NULL) {
rv = merge_rcm_info(root, info);
rcm_free_info(info);
return (rv);
}
return (0);
}
int
rcm_resources(hp_node_t root, char ***rsrcsp)
{
resource_cb_arg_t arg;
*rsrcsp = NULL;
(void) memset(&arg, 0, sizeof (resource_cb_arg_t));
(void) hp_traverse(root, &arg, resource_callback);
if (arg.error != 0) {
free_rcm_resources(arg.rsrcs);
return (arg.error);
}
*rsrcsp = arg.rsrcs;
return (0);
}
void
free_rcm_resources(char **rsrcs)
{
int i;
if (rsrcs != NULL) {
for (i = 0; rsrcs[i] != NULL; i++)
free(rsrcs[i]);
free(rsrcs);
}
}
int
rcm_offline(char **rsrcs, uint_t flags, hp_node_t root)
{
rcm_handle_t *handle;
rcm_info_t *info = NULL;
uint_t rcm_flags = 0;
int rv = 0;
hp_dprintf("rcm_offline()\n");
if (flags & HPFORCE)
rcm_flags |= RCM_FORCE;
if (flags & HPQUERY)
rcm_flags |= RCM_QUERY;
if (rcm_alloc_handle(NULL, 0, NULL, &handle) != RCM_SUCCESS) {
log_err("Cannot allocate RCM handle (%s)\n", strerror(errno));
return (EFAULT);
}
if (rcm_request_offline_list(handle, rsrcs, rcm_flags,
&info) != RCM_SUCCESS)
rv = EBUSY;
(void) rcm_free_handle(handle);
if (info != NULL) {
if (rv != 0)
(void) merge_rcm_info(root, info);
rcm_free_info(info);
}
return (rv);
}
void
rcm_online(char **rsrcs)
{
rcm_handle_t *handle;
rcm_info_t *info = NULL;
hp_dprintf("rcm_online()\n");
if (rcm_alloc_handle(NULL, 0, NULL, &handle) != RCM_SUCCESS) {
log_err("Cannot allocate RCM handle (%s)\n", strerror(errno));
return;
}
(void) rcm_notify_online_list(handle, rsrcs, 0, &info);
(void) rcm_free_handle(handle);
if (info != NULL)
rcm_free_info(info);
}
void
rcm_remove(char **rsrcs)
{
rcm_handle_t *handle;
rcm_info_t *info = NULL;
hp_dprintf("rcm_remove()\n");
if (rcm_alloc_handle(NULL, 0, NULL, &handle) != RCM_SUCCESS) {
log_err("Cannot allocate RCM handle (%s)\n", strerror(errno));
return;
}
(void) rcm_notify_remove_list(handle, rsrcs, 0, &info);
(void) rcm_free_handle(handle);
if (info != NULL)
rcm_free_info(info);
}
static int
get_rcm_usage(char **rsrcs, rcm_info_t **info_p)
{
rcm_handle_t *handle;
rcm_info_t *info = NULL;
int rv = 0;
if (rsrcs == NULL)
return (0);
if (rcm_alloc_handle(NULL, RCM_NOPID, NULL, &handle) != RCM_SUCCESS) {
log_err("Cannot allocate RCM handle (%s)\n", strerror(errno));
return (EFAULT);
}
if (rcm_get_info_list(handle, rsrcs,
RCM_INCLUDE_DEPENDENT | RCM_INCLUDE_SUBTREE,
&info) != RCM_SUCCESS) {
log_err("Failed to get RCM information (%s)\n",
strerror(errno));
rv = EFAULT;
}
(void) rcm_free_handle(handle);
*info_p = info;
return (rv);
}
static int
merge_rcm_info(hp_node_t root, rcm_info_t *info)
{
merge_cb_arg_t arg;
info_table_t *table;
size_t table_len;
int rv;
if ((rv = build_table(info, &table, &table_len)) != 0) {
log_err("Cannot build RCM lookup table (%s)\n", strerror(rv));
return (rv);
}
if ((table == NULL) || (table_len == 0)) {
log_err("Unable to gather RCM usage.\n");
return (0);
}
(void) memset(&arg, 0, sizeof (merge_cb_arg_t));
arg.table = table;
arg.table_len = table_len;
(void) hp_traverse(root, (void *)&arg, merge_callback);
free_table(table, table_len);
if (arg.error != 0) {
log_err("Cannot merge RCM information (%s)\n", strerror(rv));
return (rv);
}
return (0);
}
static int
resource_callback(hp_node_t node, void *argp)
{
resource_cb_arg_t *arg = (resource_cb_arg_t *)argp;
char **new_rsrcs;
size_t new_size;
int type;
type = hp_type(node);
if ((type == HP_NODE_PORT) && HP_IS_OFFLINE(hp_state(node)))
return (HP_WALK_PRUNECHILD);
if (type != HP_NODE_DEVICE)
return (HP_WALK_CONTINUE);
if (hp_path(node, arg->path, arg->connection) != 0) {
log_err("Cannot get RCM resource path.\n");
arg->error = EFAULT;
return (HP_WALK_TERMINATE);
}
(void) snprintf(arg->dev_path, MAXPATHLEN, "/devices%s", arg->path);
new_size = (arg->n_rsrcs + 2) * sizeof (char *);
if (arg->rsrcs == NULL)
new_rsrcs = (char **)malloc(new_size);
else
new_rsrcs = (char **)realloc(arg->rsrcs, new_size);
if (new_rsrcs != NULL) {
arg->rsrcs = new_rsrcs;
} else {
log_err("Cannot allocate RCM resource array.\n");
arg->error = ENOMEM;
return (HP_WALK_TERMINATE);
}
arg->rsrcs[arg->n_rsrcs] = strdup(arg->dev_path);
arg->rsrcs[arg->n_rsrcs + 1] = NULL;
if (arg->rsrcs[arg->n_rsrcs] == NULL) {
log_err("Cannot allocate RCM resource path.\n");
arg->error = ENOMEM;
return (HP_WALK_TERMINATE);
}
arg->n_rsrcs += 1;
return (HP_WALK_PRUNECHILD);
}
static int
merge_callback(hp_node_t node, void *argp)
{
merge_cb_arg_t *arg = (merge_cb_arg_t *)argp;
hp_node_t usage;
info_table_t lookup;
info_table_t *slot;
info_entry_t *entry;
int rv;
if (hp_type(node) != HP_NODE_DEVICE)
return (HP_WALK_CONTINUE);
if ((rv = hp_path(node, arg->path, arg->connection)) != 0) {
log_err("Cannot lookup hotplug path (%s)\n", strerror(rv));
arg->error = rv;
return (HP_WALK_TERMINATE);
}
lookup.path = arg->path;
if ((slot = bsearch(&lookup, arg->table, arg->table_len,
sizeof (info_table_t), compare_info)) == NULL)
return (HP_WALK_CONTINUE);
for (entry = slot->entries; entry != NULL; entry = entry->next) {
usage = (hp_node_t)calloc(1, sizeof (struct hp_node));
if (usage == NULL) {
log_err("Cannot allocate hotplug usage node.\n");
arg->error = ENOMEM;
return (HP_WALK_TERMINATE);
}
usage->hp_type = HP_NODE_USAGE;
if ((usage->hp_name = strdup(entry->rsrc)) == NULL) {
log_err("Cannot allocate hotplug usage node name.\n");
free(usage);
arg->error = ENOMEM;
return (HP_WALK_TERMINATE);
}
if ((usage->hp_usage = strdup(entry->usage)) == NULL) {
log_err("Cannot allocate hotplug usage node info.\n");
free(usage->hp_name);
free(usage);
arg->error = ENOMEM;
return (HP_WALK_TERMINATE);
}
usage->hp_parent = node;
usage->hp_sibling = node->hp_child;
node->hp_child = usage;
}
return (HP_WALK_CONTINUE);
}
static int
build_table(rcm_info_t *info, info_table_t **tablep, size_t *table_lenp)
{
rcm_info_tuple_t *tuple;
info_entry_t *entry;
info_table_t *slot;
info_table_t *table;
size_t table_len;
const char *rsrc;
const char *usage;
char path[MAXPATHLEN];
*tablep = NULL;
*table_lenp = 0;
table_len = 0;
for (tuple = NULL; (tuple = rcm_info_next(info, tuple)) != NULL; )
table_len++;
if (table_len == 0)
return (ENOENT);
table = (info_table_t *)calloc(table_len, sizeof (info_table_t));
if (table == NULL)
return (ENOMEM);
slot = NULL;
table_len = 0;
for (tuple = NULL; (tuple = rcm_info_next(info, tuple)) != NULL; ) {
if (((rsrc = rcm_info_rsrc(tuple)) == NULL) ||
((usage = rcm_info_info(tuple)) == NULL)) {
log_err("RCM returned invalid resource or usage.\n");
continue;
}
if ((rsrc2path(rsrc, path) == 0) &&
((slot == NULL) || (strcmp(slot->path, path) != 0))) {
slot = &table[table_len];
if ((slot->path = strdup(path)) == NULL) {
log_err("Cannot build info table slot.\n");
free_table(table, table_len);
return (ENOMEM);
}
table_len++;
}
if (slot != NULL) {
entry = (info_entry_t *)malloc(sizeof (info_entry_t));
if (entry == NULL) {
log_err("Cannot allocate info table entry.\n");
free_table(table, table_len);
return (ENOMEM);
}
entry->next = slot->entries;
slot->entries = entry;
if (((entry->rsrc = strdup(rsrc)) == NULL) ||
((entry->usage = strdup(usage)) == NULL)) {
log_err("Cannot build info table entry.\n");
free_table(table, table_len);
return (ENOMEM);
}
}
}
if (table_len == 0) {
free(table);
return (0);
}
qsort(table, table_len, sizeof (info_table_t), compare_info);
*tablep = table;
*table_lenp = table_len;
return (0);
}
static void
free_table(info_table_t *table, size_t table_len)
{
info_entry_t *entry;
int index;
if (table != NULL) {
for (index = 0; index < table_len; index++) {
if (table[index].path != NULL)
free(table[index].path);
while (table[index].entries != NULL) {
entry = table[index].entries;
table[index].entries = entry->next;
if (entry->rsrc != NULL)
free(entry->rsrc);
if (entry->usage != NULL)
free(entry->usage);
free(entry);
}
}
free(table);
}
}
static int
rsrc2path(const char *rsrc, char *path)
{
char *s;
char tmp[MAXPATHLEN];
if (strncmp(rsrc, "/dev", 4) == 0) {
if (realpath(rsrc, tmp) == NULL) {
log_err("Cannot resolve RCM resource (%s)\n",
strerror(errno));
return (-1);
}
(void) strlcpy(path, &tmp[strlen(S_DEVICES)], MAXPATHLEN);
if ((s = strrchr(path, ':')) != NULL)
*s = '\0';
return (0);
}
return (-1);
}
static int
compare_info(const void *a, const void *b)
{
info_table_t *slot_a = (info_table_t *)a;
info_table_t *slot_b = (info_table_t *)b;
return (strcmp(slot_a->path, slot_b->path));
}