#
# 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 2009 Sun Microsystems, Inc. All rights reserved.
# Use is subject to license terms.
#
#
# cmd/hotplugd/Makefile
#
PROG= hotplugd
OBJS= hotplugd.o \
hotplugd_impl.o \
hotplugd_door.o \
hotplugd_info.o \
hotplugd_rcm.o
SRCS= $(OBJS:.o=.c)
SVCMETHOD= svc-hotplug
MANIFEST= hotplug.xml
include ../Makefile.cmd
ROOTCMDDIR= $(ROOTLIB)
ROOTMANIFESTDIR= $(ROOTSVCSYSTEM)
$(ROOTMANIFEST) : FILEMODE= 444
CPPFLAGS += -I$(SRC)/lib/libhotplug/common
CERRWARN += -Wno-parentheses
CERRWARN += $(CNOWARN_UNINIT)
LDLIBS += -ldevinfo -lhotplug -lnvpair -lsecdb -lrcm -lbsm
.KEEP_STATE:
all: $(PROG)
$(PROG): $(OBJS)
$(LINK.c) -o $@ $(OBJS) $(LDLIBS)
$(POST_PROCESS)
.PARALLEL: $(OBJS)
install: all $(ROOTCMD) $(ROOTMANIFEST) $(ROOTSVCMETHOD)
clean:
$(RM) $(PROG) $(OBJS) $(LLOBJS)
check: $(CHKMANIFEST)
$(CSTYLE) -pP $(SRCS:%=%)
lint: lint_SRCS
include ../Makefile.targ
hotplug daemon
/*
* 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 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "hotplugd_impl.h"
/*
* Define long options for command line.
*/
static const struct option lopts[] = {
{ "help", no_argument, 0, '?' },
{ "version", no_argument, 0, 'V' },
{ "debug", no_argument, 0, 'd' },
{ 0, 0, 0, 0 }
};
/*
* Local functions.
*/
static void usage(void);
static boolean_t check_privileges(void);
static int daemonize(void);
static void init_signals(void);
static void signal_handler(int signum);
static void shutdown_daemon(void);
/*
* Global variables.
*/
static char *prog;
static char version[] = "1.0";
static boolean_t log_flag = B_FALSE;
static boolean_t debug_flag = B_FALSE;
static boolean_t exit_flag = B_FALSE;
static sema_t signal_sem;
/*
* main()
*
* The hotplug daemon is designed to be a background daemon
* controlled by SMF. So by default it will daemonize and
* do some coordination with its parent process in order to
* indicate proper success or failure back to SMF. And all
* output will be sent to syslog.
*
* But if given the '-d' command line option, it will instead
* run in the foreground in a standalone, debug mode. Errors
* and additional debug messages will be printed to the controlling
* terminal instead of to syslog.
*/
int
main(int argc, char *argv[])
{
int opt;
int pfd;
int status;
if ((prog = strrchr(argv[0], '/')) == NULL)
prog = argv[0];
else
prog++;
/* Check privileges */
if (!check_privileges()) {
(void) fprintf(stderr, "Insufficient privileges. "
"(All privileges are required.)\n");
return (-1);
}
/* Process options */
while ((opt = getopt_clip(argc, argv, "dV?", lopts, NULL)) != -1) {
switch (opt) {
case 'd':
debug_flag = B_TRUE;
break;
case 'V':
(void) printf("%s: Version %s\n", prog, version);
return (0);
default:
if (optopt == '?') {
usage();
return (0);
}
(void) fprintf(stderr, "Unrecognized option '%c'.\n",
optopt);
usage();
return (-1);
}
}
/* Initialize semaphore for daemon shutdown */
if (sema_init(&signal_sem, 1, USYNC_THREAD, NULL) != 0)
exit(EXIT_FAILURE);
/* Initialize signal handling */
init_signals();
/* Daemonize, if not in DEBUG mode */
if (!debug_flag)
pfd = daemonize();
/* Initialize door service */
if (!door_server_init()) {
if (!debug_flag) {
status = EXIT_FAILURE;
(void) write(pfd, &status, sizeof (status));
(void) close(pfd);
}
exit(EXIT_FAILURE);
}
/* Daemon initialized */
if (!debug_flag) {
status = 0;
(void) write(pfd, &status, sizeof (status));
(void) close(pfd);
}
/* Note that daemon is running */
log_info("hotplug daemon started.\n");
/* Wait for shutdown signal */
while (!exit_flag)
(void) sema_wait(&signal_sem);
shutdown_daemon();
return (0);
}
/*
* usage()
*
* Print a brief usage synopsis for the command line options.
*/
static void
usage(void)
{
(void) printf("Usage: %s [-d]\n", prog);
}
/*
* check_privileges()
*
* Check if the current process has enough privileges
* to run the daemon. Note that all privileges are
* required in order for RCM interactions to work.
*/
static boolean_t
check_privileges(void)
{
priv_set_t *privset;
boolean_t rv = B_FALSE;
if ((privset = priv_allocset()) != NULL) {
if (getppriv(PRIV_EFFECTIVE, privset) == 0) {
rv = priv_isfullset(privset);
}
priv_freeset(privset);
}
return (rv);
}
/*
* daemonize()
*
* Fork the daemon process into the background, and detach from
* the controlling terminal. Setup a shared pipe that will later
* be used to report startup status to the parent process.
*/
static int
daemonize(void)
{
int status;
int pfds[2];
pid_t pid;
sigset_t set;
sigset_t oset;
/*
* Temporarily block all signals. They will remain blocked in
* the parent, but will be unblocked in the child once it has
* notified the parent of its startup status.
*/
(void) sigfillset(&set);
(void) sigdelset(&set, SIGABRT);
(void) sigprocmask(SIG_BLOCK, &set, &oset);
/* Create the shared pipe */
if (pipe(pfds) == -1) {
log_err("Cannot create pipe (%s)\n", strerror(errno));
exit(EXIT_FAILURE);
}
/* Fork the daemon process */
if ((pid = fork()) == -1) {
log_err("Cannot fork daemon process (%s)\n", strerror(errno));
exit(EXIT_FAILURE);
}
/* Parent: waits for exit status from child. */
if (pid > 0) {
(void) close(pfds[1]);
if (read(pfds[0], &status, sizeof (status)) == sizeof (status))
_exit(status);
if ((waitpid(pid, &status, 0) == pid) && WIFEXITED(status))
_exit(WEXITSTATUS(status));
log_err("Failed to spawn daemon process.\n");
_exit(EXIT_FAILURE);
}
/* Child continues... */
(void) setsid();
(void) chdir("/");
(void) umask(CMASK);
(void) sigprocmask(SIG_SETMASK, &oset, NULL);
(void) close(pfds[0]);
/* Detach from controlling terminal */
(void) close(0);
(void) close(1);
(void) close(2);
(void) open("/dev/null", O_RDONLY);
(void) open("/dev/null", O_WRONLY);
(void) open("/dev/null", O_WRONLY);
/* Use syslog for future messages */
log_flag = B_TRUE;
openlog(prog, LOG_PID, LOG_DAEMON);
return (pfds[1]);
}
/*
* init_signals()
*
* Initialize signal handling.
*/
static void
init_signals(void)
{
struct sigaction act;
sigset_t set;
(void) sigfillset(&set);
(void) sigdelset(&set, SIGABRT);
(void) sigfillset(&act.sa_mask);
act.sa_handler = signal_handler;
act.sa_flags = 0;
(void) sigaction(SIGTERM, &act, NULL);
(void) sigaction(SIGHUP, &act, NULL);
(void) sigaction(SIGINT, &act, NULL);
(void) sigaction(SIGPIPE, &act, NULL);
(void) sigdelset(&set, SIGTERM);
(void) sigdelset(&set, SIGHUP);
(void) sigdelset(&set, SIGINT);
(void) sigdelset(&set, SIGPIPE);
}
/*
* signal_handler()
*
* Most signals cause the hotplug daemon to shut down.
* Shutdown is triggered using a semaphore to wake up
* the main thread for a clean exit.
*
* Except SIGPIPE is used to coordinate between the parent
* and child processes when the daemon first starts.
*/
static void
signal_handler(int signum)
{
log_info("Received signal %d.\n", signum);
switch (signum) {
case 0:
case SIGPIPE:
break;
default:
exit_flag = B_TRUE;
(void) sema_post(&signal_sem);
break;
}
}
/*
* shutdown_daemon()
*
* Perform a clean shutdown of the daemon.
*/
static void
shutdown_daemon(void)
{
log_info("Hotplug daemon shutting down.\n");
door_server_fini();
if (log_flag)
closelog();
(void) sema_destroy(&signal_sem);
}
/*
* log_err()
*
* Display an error message. Use syslog if in daemon
* mode, otherwise print to stderr when in debug mode.
*/
/*PRINTFLIKE1*/
void
log_err(char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
if (debug_flag || !log_flag)
(void) vfprintf(stderr, fmt, ap);
else
vsyslog(LOG_ERR, fmt, ap);
va_end(ap);
}
/*
* log_info()
*
* Display an information message. Use syslog if in daemon
* mode, otherwise print to stdout when in debug mode.
*/
/*PRINTFLIKE1*/
void
log_info(char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
if (debug_flag || !log_flag)
(void) vfprintf(stdout, fmt, ap);
else
vsyslog(LOG_INFO, fmt, ap);
va_end(ap);
}
/*
* hp_dprintf()
*
* Print a debug tracing statement. Only works in debug
* mode, and always prints to stdout.
*/
/*PRINTFLIKE1*/
void
hp_dprintf(char *fmt, ...)
{
va_list ap;
if (debug_flag) {
va_start(ap, fmt);
(void) vprintf(fmt, ap);
va_end(ap);
}
}
/*
* 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 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "hotplugd_impl.h"
/*
* Buffer management for results.
*/
typedef struct i_buffer {
uint64_t seqnum;
char *buffer;
struct i_buffer *next;
} i_buffer_t;
static uint64_t buffer_seqnum = 1;
static i_buffer_t *buffer_list = NULL;
static pthread_mutex_t buffer_lock = PTHREAD_MUTEX_INITIALIZER;
/*
* Door file descriptor.
*/
static int door_fd = -1;
/*
* Function prototypes.
*/
static void door_server(void *, char *, size_t, door_desc_t *, uint_t);
static int check_auth(ucred_t *, const char *);
static int cmd_getinfo(nvlist_t *, nvlist_t **);
static int cmd_changestate(nvlist_t *, nvlist_t **);
static int cmd_private(hp_cmd_t, nvlist_t *, nvlist_t **);
static void add_buffer(uint64_t, char *);
static void free_buffer(uint64_t);
static uint64_t get_seqnum(void);
static char *state_str(int);
static int audit_session(ucred_t *, adt_session_data_t **);
static void audit_changestate(ucred_t *, char *, char *, char *, int, int,
int);
static void audit_setprivate(ucred_t *, char *, char *, char *, char *,
int);
/*
* door_server_init()
*
* Create the door file, and initialize the door server.
*/
boolean_t
door_server_init(void)
{
int fd;
/* Create the door file */
if ((fd = open(HOTPLUGD_DOOR, O_CREAT|O_EXCL|O_RDONLY, 0644)) == -1) {
if (errno == EEXIST) {
log_err("Door service is already running.\n");
} else {
log_err("Cannot open door file '%s': %s\n",
HOTPLUGD_DOOR, strerror(errno));
}
return (B_FALSE);
}
(void) close(fd);
/* Initialize the door service */
if ((door_fd = door_create(door_server, NULL,
DOOR_REFUSE_DESC | DOOR_NO_CANCEL)) == -1) {
log_err("Cannot create door service: %s\n", strerror(errno));
return (B_FALSE);
}
/* Cleanup stale door associations */
(void) fdetach(HOTPLUGD_DOOR);
/* Associate door service with door file */
if (fattach(door_fd, HOTPLUGD_DOOR) != 0) {
log_err("Cannot attach to door file '%s': %s\n", HOTPLUGD_DOOR,
strerror(errno));
(void) door_revoke(door_fd);
(void) fdetach(HOTPLUGD_DOOR);
door_fd = -1;
return (B_FALSE);
}
return (B_TRUE);
}
/*
* door_server_fini()
*
* Terminate and cleanup the door server.
*/
void
door_server_fini(void)
{
if (door_fd != -1) {
(void) door_revoke(door_fd);
(void) fdetach(HOTPLUGD_DOOR);
}
(void) unlink(HOTPLUGD_DOOR);
}
/*
* door_server()
*
* This routine is the handler which responds to each door call.
* Each incoming door call is expected to send a packed nvlist
* of arguments which describe the requested action. And each
* response is sent back as a packed nvlist of results.
*
* Results are always allocated on the heap. A global list of
* allocated result buffers is managed, and each one is tracked
* by a unique sequence number. The final step in the protocol
* is for the caller to send a short response using the sequence
* number when the buffer can be released.
*/
/*ARGSUSED*/
static void
door_server(void *cookie, char *argp, size_t sz, door_desc_t *dp, uint_t ndesc)
{
nvlist_t *args = NULL;
nvlist_t *results = NULL;
hp_cmd_t cmd;
int rv;
hp_dprintf("Door call: cookie=%p, argp=%p, sz=%d\n", cookie,
(void *)argp, sz);
/* Special case to free a results buffer */
if (sz == sizeof (uint64_t)) {
free_buffer(*(uint64_t *)(uintptr_t)argp);
(void) door_return(NULL, 0, NULL, 0);
return;
}
/* Unpack the arguments nvlist */
if (nvlist_unpack(argp, sz, &args, 0) != 0) {
log_err("Cannot unpack door arguments.\n");
rv = EINVAL;
goto fail;
}
/* Extract the requested command */
if (nvlist_lookup_int32(args, HPD_CMD, (int32_t *)&cmd) != 0) {
log_err("Cannot decode door command.\n");
rv = EINVAL;
goto fail;
}
/* Implement the command */
switch (cmd) {
case HP_CMD_GETINFO:
rv = cmd_getinfo(args, &results);
break;
case HP_CMD_CHANGESTATE:
rv = cmd_changestate(args, &results);
break;
case HP_CMD_SETPRIVATE:
case HP_CMD_GETPRIVATE:
rv = cmd_private(cmd, args, &results);
break;
default:
rv = EINVAL;
break;
}
/* The arguments nvlist is no longer needed */
nvlist_free(args);
args = NULL;
/*
* If an nvlist was constructed for the results,
* then pack the results nvlist and return it.
*/
if (results != NULL) {
uint64_t seqnum;
char *buf = NULL;
size_t len = 0;
/* Add a sequence number to the results */
seqnum = get_seqnum();
if (nvlist_add_uint64(results, HPD_SEQNUM, seqnum) != 0) {
log_err("Cannot add sequence number.\n");
rv = EFAULT;
goto fail;
}
/* Pack the results nvlist */
if (nvlist_pack(results, &buf, &len,
NV_ENCODE_NATIVE, 0) != 0) {
log_err("Cannot pack door results.\n");
rv = EFAULT;
goto fail;
}
/* Link results buffer into list */
add_buffer(seqnum, buf);
/* The results nvlist is no longer needed */
nvlist_free(results);
/* Return the results */
(void) door_return(buf, len, NULL, 0);
return;
}
/* Return result code (when no nvlist) */
(void) door_return((char *)&rv, sizeof (int), NULL, 0);
return;
fail:
log_err("Door call failed (%s)\n", strerror(rv));
nvlist_free(args);
nvlist_free(results);
(void) door_return((char *)&rv, sizeof (int), NULL, 0);
}
/*
* check_auth()
*
* Perform an RBAC authorization check.
*/
static int
check_auth(ucred_t *ucred, const char *auth)
{
struct passwd pwd;
uid_t euid;
char buf[MAXPATHLEN];
euid = ucred_geteuid(ucred);
if ((getpwuid_r(euid, &pwd, buf, sizeof (buf)) == NULL) ||
(chkauthattr(auth, pwd.pw_name) == 0)) {
log_info("Unauthorized door call.\n");
return (-1);
}
return (0);
}
/*
* cmd_getinfo()
*
* Implements the door command to get a hotplug information snapshot.
*/
static int
cmd_getinfo(nvlist_t *args, nvlist_t **resultsp)
{
hp_node_t root;
nvlist_t *results;
char *path;
char *connection;
char *buf = NULL;
size_t len = 0;
uint_t flags;
int rv;
hp_dprintf("cmd_getinfo:\n");
/* Get arguments */
if (nvlist_lookup_string(args, HPD_PATH, &path) != 0) {
hp_dprintf("cmd_getinfo: invalid arguments.\n");
return (EINVAL);
}
if (nvlist_lookup_string(args, HPD_CONNECTION, &connection) != 0)
connection = NULL;
if (nvlist_lookup_uint32(args, HPD_FLAGS, (uint32_t *)&flags) != 0)
flags = 0;
/* Get and pack the requested snapshot */
if ((rv = getinfo(path, connection, flags, &root)) == 0) {
rv = hp_pack(root, &buf, &len);
hp_fini(root);
}
hp_dprintf("cmd_getinfo: getinfo(): rv = %d, buf = %p.\n", rv,
(void *)buf);
/*
* If the above failed or there is no snapshot,
* then only return a status code.
*/
if (rv != 0)
return (rv);
if (buf == NULL)
return (EFAULT);
/* Allocate nvlist for results */
if (nvlist_alloc(&results, NV_UNIQUE_NAME_TYPE, 0) != 0) {
hp_dprintf("cmd_getinfo: nvlist_alloc() failed.\n");
free(buf);
return (ENOMEM);
}
/* Add snapshot and successful status to results */
if ((nvlist_add_int32(results, HPD_STATUS, 0) != 0) ||
(nvlist_add_byte_array(results, HPD_INFO,
(uchar_t *)buf, len) != 0)) {
hp_dprintf("cmd_getinfo: nvlist add failure.\n");
nvlist_free(results);
free(buf);
return (ENOMEM);
}
/* Packed snapshot no longer needed */
free(buf);
/* Success */
*resultsp = results;
return (0);
}
/*
* cmd_changestate()
*
* Implements the door command to initate a state change operation.
*
* NOTE: requires 'modify' authorization.
*/
static int
cmd_changestate(nvlist_t *args, nvlist_t **resultsp)
{
hp_node_t root = NULL;
nvlist_t *results = NULL;
char *path, *connection;
ucred_t *uc = NULL;
uint_t flags;
int rv, state, old_state, status;
hp_dprintf("cmd_changestate:\n");
/* Get arguments */
if ((nvlist_lookup_string(args, HPD_PATH, &path) != 0) ||
(nvlist_lookup_string(args, HPD_CONNECTION, &connection) != 0) ||
(nvlist_lookup_int32(args, HPD_STATE, &state) != 0)) {
hp_dprintf("cmd_changestate: invalid arguments.\n");
return (EINVAL);
}
if (nvlist_lookup_uint32(args, HPD_FLAGS, (uint32_t *)&flags) != 0)
flags = 0;
/* Get caller's credentials */
if (door_ucred(&uc) != 0) {
log_err("Cannot get door credentials (%s)\n", strerror(errno));
return (EACCES);
}
/* Check authorization */
if (check_auth(uc, HP_MODIFY_AUTH) != 0) {
hp_dprintf("cmd_changestate: access denied.\n");
audit_changestate(uc, HP_MODIFY_AUTH, path, connection,
state, -1, ADT_FAIL_VALUE_AUTH);
ucred_free(uc);
return (EACCES);
}
/* Perform the state change operation */
status = changestate(path, connection, state, flags, &old_state, &root);
hp_dprintf("cmd_changestate: changestate() == %d\n", status);
/* Audit the operation */
audit_changestate(uc, HP_MODIFY_AUTH, path, connection, state,
old_state, status);
/* Caller's credentials no longer needed */
ucred_free(uc);
/*
* Pack the results into an nvlist if there is an error snapshot.
*
* If any error occurs while packing the results, the original
* error code from changestate() above is still returned.
*/
if (root != NULL) {
char *buf = NULL;
size_t len = 0;
hp_dprintf("cmd_changestate: results nvlist required.\n");
/* Pack and discard the error snapshot */
rv = hp_pack(root, &buf, &len);
hp_fini(root);
if (rv != 0) {
hp_dprintf("cmd_changestate: hp_pack() failed (%s).\n",
strerror(rv));
return (status);
}
/* Allocate nvlist for results */
if (nvlist_alloc(&results, NV_UNIQUE_NAME_TYPE, 0) != 0) {
hp_dprintf("cmd_changestate: nvlist_alloc() failed.\n");
free(buf);
return (status);
}
/* Add the results into the nvlist */
if ((nvlist_add_int32(results, HPD_STATUS, status) != 0) ||
(nvlist_add_byte_array(results, HPD_INFO, (uchar_t *)buf,
len) != 0)) {
hp_dprintf("cmd_changestate: nvlist add failed.\n");
nvlist_free(results);
free(buf);
return (status);
}
*resultsp = results;
}
return (status);
}
/*
* cmd_private()
*
* Implementation of the door command to set or get bus private options.
*
* NOTE: requires 'modify' authorization for the 'set' command.
*/
static int
cmd_private(hp_cmd_t cmd, nvlist_t *args, nvlist_t **resultsp)
{
nvlist_t *results = NULL;
ucred_t *uc = NULL;
char *path, *connection, *options;
char *values = NULL;
int status;
hp_dprintf("cmd_private:\n");
/* Get caller's credentials */
if ((cmd == HP_CMD_SETPRIVATE) && (door_ucred(&uc) != 0)) {
log_err("Cannot get door credentials (%s)\n", strerror(errno));
return (EACCES);
}
/* Get arguments */
if ((nvlist_lookup_string(args, HPD_PATH, &path) != 0) ||
(nvlist_lookup_string(args, HPD_CONNECTION, &connection) != 0) ||
(nvlist_lookup_string(args, HPD_OPTIONS, &options) != 0)) {
hp_dprintf("cmd_private: invalid arguments.\n");
return (EINVAL);
}
/* Check authorization */
if ((cmd == HP_CMD_SETPRIVATE) &&
(check_auth(uc, HP_MODIFY_AUTH) != 0)) {
hp_dprintf("cmd_private: access denied.\n");
audit_setprivate(uc, HP_MODIFY_AUTH, path, connection, options,
ADT_FAIL_VALUE_AUTH);
ucred_free(uc);
return (EACCES);
}
/* Perform the operation */
status = private_options(path, connection, cmd, options, &values);
hp_dprintf("cmd_private: private_options() == %d\n", status);
/* Audit the operation */
if (cmd == HP_CMD_SETPRIVATE) {
audit_setprivate(uc, HP_MODIFY_AUTH, path, connection, options,
status);
ucred_free(uc);
}
/* Construct an nvlist if values were returned */
if (values != NULL) {
/* Allocate nvlist for results */
if (nvlist_alloc(&results, NV_UNIQUE_NAME_TYPE, 0) != 0) {
hp_dprintf("cmd_private: nvlist_alloc() failed.\n");
free(values);
return (ENOMEM);
}
/* Add values and status to the results */
if ((nvlist_add_int32(results, HPD_STATUS, status) != 0) ||
(nvlist_add_string(results, HPD_OPTIONS, values) != 0)) {
hp_dprintf("cmd_private: nvlist add failed.\n");
nvlist_free(results);
free(values);
return (ENOMEM);
}
/* The values string is no longer needed */
free(values);
*resultsp = results;
}
return (status);
}
/*
* get_seqnum()
*
* Allocate the next unique sequence number for a results buffer.
*/
static uint64_t
get_seqnum(void)
{
uint64_t seqnum;
(void) pthread_mutex_lock(&buffer_lock);
seqnum = buffer_seqnum++;
(void) pthread_mutex_unlock(&buffer_lock);
return (seqnum);
}
/*
* add_buffer()
*
* Link a results buffer into the list containing all buffers.
*/
static void
add_buffer(uint64_t seqnum, char *buf)
{
i_buffer_t *node;
if ((node = (i_buffer_t *)malloc(sizeof (i_buffer_t))) == NULL) {
/* The consequence is a memory leak. */
log_err("Cannot allocate results buffer: %s\n",
strerror(errno));
return;
}
node->seqnum = seqnum;
node->buffer = buf;
(void) pthread_mutex_lock(&buffer_lock);
node->next = buffer_list;
buffer_list = node;
(void) pthread_mutex_unlock(&buffer_lock);
}
/*
* free_buffer()
*
* Remove a results buffer from the list containing all buffers.
*/
static void
free_buffer(uint64_t seqnum)
{
i_buffer_t *node, *prev;
(void) pthread_mutex_lock(&buffer_lock);
prev = NULL;
node = buffer_list;
while (node) {
if (node->seqnum == seqnum) {
hp_dprintf("Free buffer %lld\n", seqnum);
if (prev) {
prev->next = node->next;
} else {
buffer_list = node->next;
}
free(node->buffer);
free(node);
break;
}
prev = node;
node = node->next;
}
(void) pthread_mutex_unlock(&buffer_lock);
}
/*
* audit_session()
*
* Initialize an audit session.
*/
static int
audit_session(ucred_t *ucred, adt_session_data_t **sessionp)
{
adt_session_data_t *session;
if (adt_start_session(&session, NULL, 0) != 0) {
log_err("Cannot start audit session.\n");
return (-1);
}
if (adt_set_from_ucred(session, ucred, ADT_NEW) != 0) {
log_err("Cannot set audit session from ucred.\n");
(void) adt_end_session(session);
return (-1);
}
*sessionp = session;
return (0);
}
/*
* audit_changestate()
*
* Audit a 'changestate' door command.
*/
static void
audit_changestate(ucred_t *ucred, char *auth, char *path, char *connection,
int new_state, int old_state, int result)
{
adt_session_data_t *session;
adt_event_data_t *event;
int pass_fail, fail_reason;
if (audit_session(ucred, &session) != 0)
return;
if ((event = adt_alloc_event(session, ADT_hotplug_state)) == NULL) {
(void) adt_end_session(session);
return;
}
if (result == 0) {
pass_fail = ADT_SUCCESS;
fail_reason = ADT_SUCCESS;
} else {
pass_fail = ADT_FAILURE;
fail_reason = result;
}
event->adt_hotplug_state.auth_used = auth;
event->adt_hotplug_state.device_path = path;
event->adt_hotplug_state.connection = connection;
event->adt_hotplug_state.new_state = state_str(new_state);
event->adt_hotplug_state.old_state = state_str(old_state);
/* Put the event */
if (adt_put_event(event, pass_fail, fail_reason) != 0)
log_err("Cannot put audit event.\n");
adt_free_event(event);
(void) adt_end_session(session);
}
/*
* audit_setprivate()
*
* Audit a 'set private' door command.
*/
static void
audit_setprivate(ucred_t *ucred, char *auth, char *path, char *connection,
char *options, int result)
{
adt_session_data_t *session;
adt_event_data_t *event;
int pass_fail, fail_reason;
if (audit_session(ucred, &session) != 0)
return;
if ((event = adt_alloc_event(session, ADT_hotplug_set)) == NULL) {
(void) adt_end_session(session);
return;
}
if (result == 0) {
pass_fail = ADT_SUCCESS;
fail_reason = ADT_SUCCESS;
} else {
pass_fail = ADT_FAILURE;
fail_reason = result;
}
event->adt_hotplug_set.auth_used = auth;
event->adt_hotplug_set.device_path = path;
event->adt_hotplug_set.connection = connection;
event->adt_hotplug_set.options = options;
/* Put the event */
if (adt_put_event(event, pass_fail, fail_reason) != 0)
log_err("Cannot put audit event.\n");
adt_free_event(event);
(void) adt_end_session(session);
}
/*
* state_str()
*
* Convert a state from integer to string.
*/
static char *
state_str(int state)
{
switch (state) {
case DDI_HP_CN_STATE_EMPTY:
return ("EMPTY");
case DDI_HP_CN_STATE_PRESENT:
return ("PRESENT");
case DDI_HP_CN_STATE_POWERED:
return ("POWERED");
case DDI_HP_CN_STATE_ENABLED:
return ("ENABLED");
case DDI_HP_CN_STATE_PORT_EMPTY:
return ("PORT-EMPTY");
case DDI_HP_CN_STATE_PORT_PRESENT:
return ("PORT-PRESENT");
case DDI_HP_CN_STATE_OFFLINE:
return ("OFFLINE");
case DDI_HP_CN_STATE_ATTACHED:
return ("ATTACHED");
case DDI_HP_CN_STATE_MAINTENANCE:
return ("MAINTENANCE");
case DDI_HP_CN_STATE_ONLINE:
return ("ONLINE");
default:
return ("UNKNOWN");
}
}
/*
* 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 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "hotplugd_impl.h"
/*
* All operations affecting kernel state are serialized.
*/
static pthread_mutex_t hotplug_lock = PTHREAD_MUTEX_INITIALIZER;
/*
* Local functions.
*/
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 *);
/*
* changestate()
*
* Perform a state change operation.
*
* NOTE: all operations are serialized, using a global lock.
*/
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);
/* Initialize results */
*resultsp = NULL;
*old_statep = -1;
(void) pthread_mutex_lock(&hotplug_lock);
/* Get an information snapshot, without usage details */
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);
}
/* Record current state (used in hotplugd_door.c for auditing) */
*old_statep = hp_state(root);
/* Check if RCM interactions are required */
use_rcm = check_rcm_required(root, state);
/* If RCM is required, perform RCM offline */
if (use_rcm) {
hp_dprintf("changestate: RCM offline is required.\n");
/* Get RCM resources */
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);
}
/* Request RCM offline */
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);
}
}
/* The information snapshot is no longer needed */
hp_fini(root);
/* Stop now if QUERY flag was specified */
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);
}
/* Do state change in kernel */
rv = 0;
if (modctl(MODHPOPS, MODHPOPS_CHANGE_STATE, path, connection, state))
rv = errno;
hp_dprintf("changestate: modctl(MODHPOPS_CHANGE_STATE) = %d.\n", rv);
/*
* If RCM is required, then perform an RCM online or RCM remove
* operation. Which depends upon if modctl succeeded or failed.
*/
if (use_rcm && (rsrcs != NULL)) {
/* RCM online if failure, or RCM remove if successful */
if (rv == 0)
rcm_remove(rsrcs);
else
rcm_online(rsrcs);
/* RCM resources no longer required */
free_rcm_resources(rsrcs);
}
(void) pthread_mutex_unlock(&hotplug_lock);
*resultsp = NULL;
return (rv);
}
/*
* private_options()
*
* Implement set/get of bus private options.
*/
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);
/* Initialize property arguments */
if ((rv = pack_properties(options, &prop)) != 0) {
hp_dprintf("private_options: failed to pack properties.\n");
return (rv);
}
/* Initialize results */
(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);
}
/* Lock hotplug */
(void) pthread_mutex_lock(&hotplug_lock);
/* Perform the command */
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);
}
/* Unlock hotplug */
(void) pthread_mutex_unlock(&hotplug_lock);
/* Parse results */
if (rv == 0) {
unpack_properties(&results, &values);
*resultsp = values;
}
/* Cleanup */
free_properties(&prop);
free_properties(&results);
return (rv);
}
/*
* check_rcm_required()
*
* Given the root of a changestate operation and the target
* state, determine if RCM interactions will be required.
*/
static boolean_t
check_rcm_required(hp_node_t root, int target_state)
{
/*
* RCM is required when transitioning an ENABLED
* connector to a non-ENABLED state.
*/
if ((root->hp_type == HP_NODE_CONNECTOR) &&
HP_IS_ENABLED(root->hp_state) && !HP_IS_ENABLED(target_state))
return (B_TRUE);
/*
* RCM is required when transitioning an OPERATIONAL
* port to a non-OPERATIONAL state.
*/
if ((root->hp_type == HP_NODE_PORT) &&
HP_IS_ONLINE(root->hp_state) && HP_IS_OFFLINE(target_state))
return (B_TRUE);
/* RCM is not required in other cases */
return (B_FALSE);
}
/*
* pack_properties()
*
* Given a specified set/get command and an options string,
* construct the structure containing a packed nvlist that
* contains the specified options.
*/
static int
pack_properties(const char *options, ddi_hp_property_t *prop)
{
nvlist_t *nvl;
char *buf, *tmp, *name, *value, *next;
size_t len;
/* Initialize results */
(void) memset(prop, 0, sizeof (ddi_hp_property_t));
/* Do nothing if options string is empty */
if ((len = strlen(options)) == 0) {
hp_dprintf("pack_properties: options string is empty.\n");
return (ENOENT);
}
/* Avoid modifying the input string by using a copy on the stack */
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);
/* Allocate the nvlist */
if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0) {
log_err("Failed to allocate private options nvlist.\n");
return (ENOMEM);
}
/* Add each option from the string */
for (name = tmp; name != NULL; name = next) {
/* Isolate current name/value, and locate the next */
if ((next = strchr(name, ',')) != NULL) {
*next = '\0';
next++;
}
/* Split current name/value pair */
if ((value = strchr(name, '=')) != NULL) {
*value = '\0';
value++;
} else {
value = "";
}
/* Add the option to the nvlist */
if (nvlist_add_string(nvl, name, value) != 0) {
log_err("Failed to add private option to nvlist.\n");
nvlist_free(nvl);
return (EFAULT);
}
}
/* Pack the nvlist */
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);
}
/* Save results */
prop->nvlist_buf = buf;
prop->buf_size = len;
/* The nvlist is no longer needed */
nvlist_free(nvl);
return (0);
}
/*
* unpack_properties()
*
* Given a structure possibly containing a packed nvlist of
* bus private options, unpack the nvlist and expand its
* contents into an options string.
*/
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;
/* Initialize results */
*optionsp = NULL;
/* Do nothing if properties do not exist */
if ((prop->nvlist_buf == NULL) || (prop->buf_size == 0)) {
hp_dprintf("unpack_properties: no properties exist.\n");
return;
}
/* Unpack the nvlist */
if (nvlist_unpack(prop->nvlist_buf, prop->buf_size, &nvl, 0) != 0) {
log_err("Failed to unpack private options nvlist.\n");
return;
}
/* Compute the size of the options string */
for (len = 0, nvp = NULL; nvp = nvlist_next_nvpair(nvl, nvp); ) {
name = nvpair_name(nvp);
/* Skip the command, and anything not a string */
if ((strcmp(name, "cmd") == 0) ||
(nvpair_type(nvp) != DATA_TYPE_STRING))
continue;
(void) nvpair_value_string(nvp, &value);
/* Account for '=' signs, commas, and terminating NULL */
len += (strlen(name) + strlen(value) + 2);
}
/* Allocate the resulting options string */
if ((options = (char *)calloc(len, sizeof (char))) == NULL) {
log_err("Failed to allocate private options string.\n");
nvlist_free(nvl);
return;
}
/* Copy name/value pairs into the options string */
first_flag = B_TRUE;
for (nvp = NULL; nvp = nvlist_next_nvpair(nvl, nvp); ) {
name = nvpair_name(nvp);
/* Skip the command, and anything not a string */
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;
}
/* The unpacked nvlist is no longer needed */
nvlist_free(nvl);
/* Save results */
*optionsp = options;
}
/*
* free_properties()
*
* Destroy a structure containing a packed nvlist of bus
* private properties.
*/
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));
}
}
/*
* 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 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#ifndef _HOTPLUGD_IMPL_H
#define _HOTPLUGD_IMPL_H
#ifdef __cplusplus
extern "C" {
#endif
/*
* Define macros to test connection states.
*/
#define HP_IS_ENABLED(s) (s == DDI_HP_CN_STATE_ENABLED)
#define HP_IS_ONLINE(s) ((s == DDI_HP_CN_STATE_ONLINE) || \
(s == DDI_HP_CN_STATE_MAINTENANCE))
#define HP_IS_OFFLINE(s) ((s == DDI_HP_CN_STATE_PORT_EMPTY) || \
(s == DDI_HP_CN_STATE_PORT_PRESENT) || \
(s == DDI_HP_CN_STATE_OFFLINE))
/*
* Define size of nvlist buffer for set/get commands.
*/
#define HP_PRIVATE_BUF_SZ 4096
/*
* Define a string for parsing /devices paths.
*/
#define S_DEVICES "/devices"
/*
* Global functions.
*/
void log_err(char *fmt, ...);
void log_info(char *fmt, ...);
void hp_dprintf(char *fmt, ...);
boolean_t door_server_init(void);
void door_server_fini(void);
int getinfo(const char *path, const char *connection, uint_t flags,
hp_node_t *rootp);
int changestate(const char *path, const char *connection, int state,
uint_t flags, int *old_statep, hp_node_t *resultsp);
int private_options(const char *path, const char *connection,
hp_cmd_t cmd, const char *options, char **resultsp);
int copy_usage(hp_node_t root);
int rcm_resources(hp_node_t root, char ***rsrcsp);
void free_rcm_resources(char **rsrcs);
int rcm_offline(char **rsrcs, uint_t flags, hp_node_t root);
void rcm_online(char **rsrcs);
void rcm_remove(char **rsrcs);
#ifdef __cplusplus
}
#endif
#endif /* _HOTPLUGD_IMPL_H */
/*
* 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 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "hotplugd_impl.h"
/*
* Define a list of hotplug nodes.
* (Only used within this module.)
*/
typedef struct {
hp_node_t head;
hp_node_t prev;
} hp_node_list_t;
/*
* Local functions.
*/
static int copy_devinfo(const char *, const char *, uint_t,
hp_node_t *);
static int copy_devices(hp_node_t, di_node_t, uint_t, hp_node_t *);
static int copy_hotplug(hp_node_t, di_node_t, const char *, uint_t,
hp_node_t *);
static char *base_path(const char *);
static int search_cb(di_node_t, void *);
static int check_search(di_node_t, uint_t);
static hp_node_t new_device_node(hp_node_t, di_node_t);
static hp_node_t new_hotplug_node(hp_node_t, di_hp_t);
static void node_list_add(hp_node_list_t *, hp_node_t);
/*
* getinfo()
*
* Build a hotplug information snapshot. The path, connection,
* and flags indicate what information should be included.
*/
int
getinfo(const char *path, const char *connection, uint_t flags, hp_node_t *retp)
{
hp_node_t root = NULL;
hp_node_t child;
char *basepath;
int rv;
if ((path == NULL) || (retp == NULL))
return (EINVAL);
hp_dprintf("getinfo: path=%s, connection=%s, flags=0x%x\n", path,
(connection == NULL) ? "NULL" : connection, flags);
/* Allocate the base path */
if ((basepath = base_path(path)) == NULL)
return (ENOMEM);
/* Copy in device and hotplug nodes from libdevinfo */
if ((rv = copy_devinfo(basepath, connection, flags, &root)) != 0) {
hp_fini(root);
free(basepath);
return (rv);
}
/* Check if there were no connections */
if (root == NULL) {
hp_dprintf("getinfo: no hotplug connections.\n");
free(basepath);
return (ENOENT);
}
/* Special case: exclude root nexus from snapshot */
if (strcmp(basepath, "/") == 0) {
child = root->hp_child;
if (root->hp_name != NULL)
free(root->hp_name);
free(root);
root = child;
for (child = root; child; child = child->hp_sibling)
child->hp_parent = NULL;
}
/* Store a pointer to the base path in each root node */
for (child = root; child != NULL; child = child->hp_sibling)
child->hp_basepath = basepath;
/* Copy in usage information from RCM */
if (flags & HPINFOUSAGE) {
if ((rv = copy_usage(root)) != 0) {
(void) hp_fini(root);
return (rv);
}
}
*retp = root;
return (0);
}
/*
* copy_devinfo()
*
* Copy information about device and hotplug nodes from libdevinfo.
*
* When path is set to "/", the results need to be limited only to
* branches that contain hotplug information. An initial search
* is performed to mark which branches contain hotplug nodes.
*/
static int
copy_devinfo(const char *path, const char *connection, uint_t flags,
hp_node_t *rootp)
{
hp_node_t hp_root = NULL;
di_node_t di_root;
int rv;
/* Get libdevinfo snapshot */
if ((di_root = di_init(path, DINFOSUBTREE | DINFOHP)) == DI_NODE_NIL)
return (errno);
/* Do initial search pass, if required */
if (strcmp(path, "/") == 0) {
flags |= HPINFOSEARCH;
(void) di_walk_node(di_root, DI_WALK_CLDFIRST, NULL, search_cb);
}
/*
* If a connection is specified, just copy immediate hotplug info.
* Else, copy the device tree normally.
*/
if (connection != NULL)
rv = copy_hotplug(NULL, di_root, connection, flags, &hp_root);
else
rv = copy_devices(NULL, di_root, flags, &hp_root);
/* Destroy devinfo snapshot */
di_fini(di_root);
*rootp = (rv == 0) ? hp_root : NULL;
return (rv);
}
/*
* copy_devices()
*
* Copy a full branch of device nodes. Used by copy_devinfo() and
* copy_hotplug().
*/
static int
copy_devices(hp_node_t parent, di_node_t dev, uint_t flags, hp_node_t *rootp)
{
hp_node_list_t children;
hp_node_t self, branch;
di_node_t child;
int rv = 0;
/* Initialize results */
*rootp = NULL;
/* Enforce search semantics */
if (check_search(dev, flags) == 0)
return (0);
/* Allocate new node for current device */
if ((self = new_device_node(parent, dev)) == NULL)
return (ENOMEM);
/*
* If the device has hotplug nodes, then use copy_hotplug()
* instead to build the branch associated with current device.
*/
if (di_hp_next(dev, DI_HP_NIL) != DI_HP_NIL) {
if ((rv = copy_hotplug(self, dev, NULL, flags,
&self->hp_child)) != 0) {
free(self);
return (rv);
}
*rootp = self;
return (0);
}
/*
* The device does not have hotplug nodes. Use normal
* approach of iterating through its child device nodes.
*/
(void) memset(&children, 0, sizeof (hp_node_list_t));
for (child = di_child_node(dev); child != DI_NODE_NIL;
child = di_sibling_node(child)) {
branch = NULL;
if ((rv = copy_devices(self, child, flags, &branch)) != 0) {
(void) hp_fini(children.head);
free(self);
return (rv);
}
if (branch != NULL)
node_list_add(&children, branch);
}
self->hp_child = children.head;
/* Done */
*rootp = self;
return (0);
}
/*
* copy_hotplug()
*
* Copy a full branch of hotplug nodes. Used by copy_devinfo()
* and copy_devices().
*
* If a connection is specified, the results are limited only
* to the branch associated with that specific connection.
*/
static int
copy_hotplug(hp_node_t parent, di_node_t dev, const char *connection,
uint_t flags, hp_node_t *retp)
{
hp_node_list_t connections, ports;
hp_node_t node, port_node;
di_node_t child_dev;
di_hp_t hp, port_hp;
uint_t child_flags;
int rv, physnum;
/* Stop implementing the HPINFOSEARCH flag */
child_flags = flags & ~(HPINFOSEARCH);
/* Clear lists of discovered ports and connections */
(void) memset(&ports, 0, sizeof (hp_node_list_t));
(void) memset(&connections, 0, sizeof (hp_node_list_t));
/*
* Scan virtual ports.
*
* If a connection is specified and it matches a virtual port,
* this will build the branch associated with that connection.
* Else, this will only build branches for virtual ports that
* are not associated with a physical connector.
*/
for (hp = DI_HP_NIL; (hp = di_hp_next(dev, hp)) != DI_HP_NIL; ) {
/* Ignore connectors */
if (di_hp_type(hp) != DDI_HP_CN_TYPE_VIRTUAL_PORT)
continue;
/*
* Ignore ports associated with connectors, unless
* a specific connection is being sought.
*/
if ((connection == NULL) && (di_hp_depends_on(hp) != -1))
continue;
/* If a connection is specified, ignore non-matching ports */
if ((connection != NULL) &&
(strcmp(di_hp_name(hp), connection) != 0))
continue;
/* Create a new port node */
if ((node = new_hotplug_node(parent, hp)) == NULL) {
rv = ENOMEM;
goto fail;
}
/* Add port node to connection list */
node_list_add(&connections, node);
/* Add branch of child devices to port node */
if ((child_dev = di_hp_child(hp)) != DI_NODE_NIL)
if ((rv = copy_devices(node, child_dev, child_flags,
&node->hp_child)) != 0)
goto fail;
}
/*
* Scan physical connectors.
*
* If a connection is specified, the results will be limited
* only to the branch associated with that connection.
*/
for (hp = DI_HP_NIL; (hp = di_hp_next(dev, hp)) != DI_HP_NIL; ) {
/* Ignore ports */
if (di_hp_type(hp) == DDI_HP_CN_TYPE_VIRTUAL_PORT)
continue;
/* If a connection is specified, ignore non-matching ports */
if ((connection != NULL) &&
(strcmp(di_hp_name(hp), connection) != 0))
continue;
/* Create a new connector node */
if ((node = new_hotplug_node(parent, hp)) == NULL) {
rv = ENOMEM;
goto fail;
}
/* Add connector node to connection list */
node_list_add(&connections, node);
/* Add branches of associated port nodes */
physnum = di_hp_connection(hp);
port_hp = DI_HP_NIL;
while ((port_hp = di_hp_next(dev, port_hp)) != DI_HP_NIL) {
/* Ignore irrelevant connections */
if (di_hp_depends_on(port_hp) != physnum)
continue;
/* Add new port node to port list */
if ((port_node = new_hotplug_node(node,
port_hp)) == NULL) {
rv = ENOMEM;
goto fail;
}
node_list_add(&ports, port_node);
/* Add branch of child devices */
if ((child_dev = di_hp_child(port_hp)) != DI_NODE_NIL) {
if ((rv = copy_devices(port_node, child_dev,
child_flags, &port_node->hp_child)) != 0)
goto fail;
}
}
node->hp_child = ports.head;
(void) memset(&ports, 0, sizeof (hp_node_list_t));
}
if (connections.head == NULL)
return (ENXIO);
*retp = connections.head;
return (0);
fail:
(void) hp_fini(ports.head);
(void) hp_fini(connections.head);
return (rv);
}
/*
* base_path()
*
* Normalize the base path of a hotplug information snapshot.
* The caller must free the string that is allocated.
*/
static char *
base_path(const char *path)
{
char *base_path;
size_t devices_len;
devices_len = strlen(S_DEVICES);
if (strncmp(path, S_DEVICES, devices_len) == 0)
base_path = strdup(&path[devices_len]);
else
base_path = strdup(path);
return (base_path);
}
/*
* search_cb()
*
* Callback function used by di_walk_node() to search for branches
* of the libdevinfo snapshot that contain hotplug nodes.
*/
/*ARGSUSED*/
static int
search_cb(di_node_t node, void *arg)
{
di_node_t parent;
uint_t flags;
(void) di_node_private_set(node, (void *)(uintptr_t)0);
if (di_hp_next(node, DI_HP_NIL) == DI_HP_NIL)
return (DI_WALK_CONTINUE);
for (parent = node; parent != DI_NODE_NIL;
parent = di_parent_node(parent)) {
flags = (uint_t)(uintptr_t)di_node_private_get(parent);
flags |= HPINFOSEARCH;
(void) di_node_private_set(parent, (void *)(uintptr_t)flags);
}
return (DI_WALK_CONTINUE);
}
/*
* check_search()
*
* Check if a device node was marked by an initial search pass.
*/
static int
check_search(di_node_t dev, uint_t flags)
{
uint_t dev_flags;
if (flags & HPINFOSEARCH) {
dev_flags = (uint_t)(uintptr_t)di_node_private_get(dev);
if ((dev_flags & HPINFOSEARCH) == 0)
return (0);
}
return (1);
}
/*
* node_list_add()
*
* Utility function to append one node to a list of hotplug nodes.
*/
static void
node_list_add(hp_node_list_t *listp, hp_node_t node)
{
if (listp->prev != NULL)
listp->prev->hp_sibling = node;
else
listp->head = node;
listp->prev = node;
}
/*
* new_device_node()
*
* Build a new hotplug node based on a specified devinfo node.
*/
static hp_node_t
new_device_node(hp_node_t parent, di_node_t dev)
{
hp_node_t node;
char *node_name, *bus_addr;
char name[MAXPATHLEN];
node = (hp_node_t)calloc(1, sizeof (struct hp_node));
if (node != NULL) {
node->hp_parent = parent;
node->hp_type = HP_NODE_DEVICE;
node_name = di_node_name(dev);
bus_addr = di_bus_addr(dev);
if (bus_addr && (strlen(bus_addr) > 0)) {
if (snprintf(name, sizeof (name), "%s@%s", node_name,
bus_addr) >= sizeof (name)) {
log_err("Path too long for device node.\n");
free(node);
return (NULL);
}
node->hp_name = strdup(name);
} else
node->hp_name = strdup(node_name);
}
return (node);
}
/*
* new_hotplug_node()
*
* Build a new hotplug node based on a specified devinfo hotplug node.
*/
static hp_node_t
new_hotplug_node(hp_node_t parent, di_hp_t hp)
{
hp_node_t node;
char *s;
node = (hp_node_t)calloc(1, sizeof (struct hp_node));
if (node != NULL) {
node->hp_parent = parent;
node->hp_state = di_hp_state(hp);
node->hp_last_change = di_hp_last_change(hp);
if ((s = di_hp_name(hp)) != NULL)
node->hp_name = strdup(s);
if ((s = di_hp_description(hp)) != NULL)
node->hp_description = strdup(s);
if (di_hp_type(hp) == DDI_HP_CN_TYPE_VIRTUAL_PORT)
node->hp_type = HP_NODE_PORT;
else
node->hp_type = HP_NODE_CONNECTOR;
}
return (node);
}
/*
* 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 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "hotplugd_impl.h"
/*
* Define structures for a path-to-usage lookup table.
*/
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;
/*
* Define callback argument used when getting resources.
*/
typedef struct {
int error;
int n_rsrcs;
char **rsrcs;
char path[MAXPATHLEN];
char connection[MAXPATHLEN];
char dev_path[MAXPATHLEN];
} resource_cb_arg_t;
/*
* Define callback argument used when merging info.
*/
typedef struct {
int error;
info_table_t *table;
size_t table_len;
char path[MAXPATHLEN];
char connection[MAXPATHLEN];
} merge_cb_arg_t;
/*
* Local functions.
*/
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);
/*
* copy_usage()
*
* Given an information snapshot, get the corresponding
* RCM usage information and merge it into the snapshot.
*/
int
copy_usage(hp_node_t root)
{
rcm_info_t *info = NULL;
char **rsrcs = NULL;
int rv;
/* Get resource names */
if ((rv = rcm_resources(root, &rsrcs)) != 0) {
log_err("Cannot get RCM resources (%s)\n", strerror(rv));
return (rv);
}
/* Do nothing if no resources */
if (rsrcs == NULL)
return (0);
/* Get RCM usage information */
if ((rv = get_rcm_usage(rsrcs, &info)) != 0) {
log_err("Cannot get RCM information (%s)\n", strerror(rv));
free_rcm_resources(rsrcs);
return (rv);
}
/* Done with resource names */
free_rcm_resources(rsrcs);
/* If there is RCM usage information, merge it in */
if (info != NULL) {
rv = merge_rcm_info(root, info);
rcm_free_info(info);
return (rv);
}
return (0);
}
/*
* rcm_resources()
*
* Given the root of a hotplug information snapshot,
* construct a list of RCM compatible resource names.
*/
int
rcm_resources(hp_node_t root, char ***rsrcsp)
{
resource_cb_arg_t arg;
/* Initialize results */
*rsrcsp = NULL;
/* Traverse snapshot to get resources */
(void) memset(&arg, 0, sizeof (resource_cb_arg_t));
(void) hp_traverse(root, &arg, resource_callback);
/* Check for errors */
if (arg.error != 0) {
free_rcm_resources(arg.rsrcs);
return (arg.error);
}
/* Success */
*rsrcsp = arg.rsrcs;
return (0);
}
/*
* free_rcm_resources()
*
* Free a table of RCM resource names.
*/
void
free_rcm_resources(char **rsrcs)
{
int i;
if (rsrcs != NULL) {
for (i = 0; rsrcs[i] != NULL; i++)
free(rsrcs[i]);
free(rsrcs);
}
}
/*
* rcm_offline()
*
* Implement an RCM offline request.
*
* NOTE: errors from RCM will be merged into the snapshot.
*/
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");
/* Set flags */
if (flags & HPFORCE)
rcm_flags |= RCM_FORCE;
if (flags & HPQUERY)
rcm_flags |= RCM_QUERY;
/* Allocate RCM handle */
if (rcm_alloc_handle(NULL, 0, NULL, &handle) != RCM_SUCCESS) {
log_err("Cannot allocate RCM handle (%s)\n", strerror(errno));
return (EFAULT);
}
/* Request RCM offline */
if (rcm_request_offline_list(handle, rsrcs, rcm_flags,
&info) != RCM_SUCCESS)
rv = EBUSY;
/* RCM handle is no longer needed */
(void) rcm_free_handle(handle);
/*
* Check if RCM returned any information tuples. If so,
* then also check if the RCM operation failed, and possibly
* merge the RCM info into the caller's hotplug snapshot.
*/
if (info != NULL) {
if (rv != 0)
(void) merge_rcm_info(root, info);
rcm_free_info(info);
}
return (rv);
}
/*
* rcm_online()
*
* Implement an RCM online notification.
*/
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);
}
/*
* rcm_remove()
*
* Implement an RCM remove notification.
*/
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);
}
/*
* get_rcm_usage()
*
* Lookup usage information for a set of resources from RCM.
*/
static int
get_rcm_usage(char **rsrcs, rcm_info_t **info_p)
{
rcm_handle_t *handle;
rcm_info_t *info = NULL;
int rv = 0;
/* No-op if no RCM resources */
if (rsrcs == NULL)
return (0);
/* Allocate RCM handle */
if (rcm_alloc_handle(NULL, RCM_NOPID, NULL, &handle) != RCM_SUCCESS) {
log_err("Cannot allocate RCM handle (%s)\n", strerror(errno));
return (EFAULT);
}
/* Get usage information from RCM */
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;
}
/* RCM handle is no longer needed */
(void) rcm_free_handle(handle);
*info_p = info;
return (rv);
}
/*
* merge_rcm_info()
*
* Merge RCM information into a hotplug information snapshot.
* First a lookup table is built to map lists of RCM usage to
* pathnames. Then during a full traversal of the snapshot,
* the lookup table is used for each node to find matching
* RCM info tuples for each path in the snapshot.
*/
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;
/* Build a lookup table, mapping paths to usage information */
if ((rv = build_table(info, &table, &table_len)) != 0) {
log_err("Cannot build RCM lookup table (%s)\n", strerror(rv));
return (rv);
}
/* Stop if no valid entries were inserted in table */
if ((table == NULL) || (table_len == 0)) {
log_err("Unable to gather RCM usage.\n");
return (0);
}
/* Initialize callback argument */
(void) memset(&arg, 0, sizeof (merge_cb_arg_t));
arg.table = table;
arg.table_len = table_len;
/* Perform a merge traversal */
(void) hp_traverse(root, (void *)&arg, merge_callback);
/* Done with the table */
free_table(table, table_len);
/* Check for errors */
if (arg.error != 0) {
log_err("Cannot merge RCM information (%s)\n", strerror(rv));
return (rv);
}
return (0);
}
/*
* resource_callback()
*
* A callback function for hp_traverse() that builds an RCM
* compatible list of resource path names. The array has
* been pre-allocated based on results from the related
* callback resource_count_callback().
*/
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;
/* Get node type */
type = hp_type(node);
/* Prune OFFLINE ports */
if ((type == HP_NODE_PORT) && HP_IS_OFFLINE(hp_state(node)))
return (HP_WALK_PRUNECHILD);
/* Skip past non-devices */
if (type != HP_NODE_DEVICE)
return (HP_WALK_CONTINUE);
/* Lookup resource path */
if (hp_path(node, arg->path, arg->connection) != 0) {
log_err("Cannot get RCM resource path.\n");
arg->error = EFAULT;
return (HP_WALK_TERMINATE);
}
/* Insert "/devices" to path name */
(void) snprintf(arg->dev_path, MAXPATHLEN, "/devices%s", arg->path);
/*
* Grow resource array to accomodate new /devices path.
* NOTE: include an extra NULL pointer at end of array.
*/
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);
}
/* Initialize new entries */
arg->rsrcs[arg->n_rsrcs] = strdup(arg->dev_path);
arg->rsrcs[arg->n_rsrcs + 1] = NULL;
/* Check for errors */
if (arg->rsrcs[arg->n_rsrcs] == NULL) {
log_err("Cannot allocate RCM resource path.\n");
arg->error = ENOMEM;
return (HP_WALK_TERMINATE);
}
/* Increment resource count */
arg->n_rsrcs += 1;
/* Do not visit children */
return (HP_WALK_PRUNECHILD);
}
/*
* merge_callback()
*
* A callback function for hp_traverse() that merges RCM information
* tuples into an existing hotplug information snapshot. The RCM
* information will be turned into HP_NODE_USAGE nodes.
*/
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;
/* Only process device nodes (other nodes cannot have usage) */
if (hp_type(node) != HP_NODE_DEVICE)
return (HP_WALK_CONTINUE);
/* Get path of current node, using buffer provided in 'arg' */
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);
}
/* Check the lookup table for associated usage */
lookup.path = arg->path;
if ((slot = bsearch(&lookup, arg->table, arg->table_len,
sizeof (info_table_t), compare_info)) == NULL)
return (HP_WALK_CONTINUE);
/* Usage information was found. Append HP_NODE_USAGE nodes. */
for (entry = slot->entries; entry != NULL; entry = entry->next) {
/* Allocate a new usage node */
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);
}
/* Initialize the usage node's contents */
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);
}
/* Link the usage node as a child of the device node */
usage->hp_parent = node;
usage->hp_sibling = node->hp_child;
node->hp_child = usage;
}
return (HP_WALK_CONTINUE);
}
/*
* build_table()
*
* Build a lookup table that will be used to map paths to their
* corresponding RCM information tuples.
*/
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];
/* Initialize results */
*tablep = NULL;
*table_lenp = 0;
/* Count the RCM info tuples to determine the table's size */
table_len = 0;
for (tuple = NULL; (tuple = rcm_info_next(info, tuple)) != NULL; )
table_len++;
/* If the table would be empty, then do nothing */
if (table_len == 0)
return (ENOENT);
/* Allocate the lookup table */
table = (info_table_t *)calloc(table_len, sizeof (info_table_t));
if (table == NULL)
return (ENOMEM);
/*
* Fill in the lookup table. Fill one slot in the table
* for each device path that has a set of associated RCM
* information tuples. In some cases multiple tuples will
* be joined together within the same slot.
*/
slot = NULL;
table_len = 0;
for (tuple = NULL; (tuple = rcm_info_next(info, tuple)) != NULL; ) {
/*
* Extract RCM resource name and usage description.
*
* NOTE: skip invalid tuples to return as much as possible.
*/
if (((rsrc = rcm_info_rsrc(tuple)) == NULL) ||
((usage = rcm_info_info(tuple)) == NULL)) {
log_err("RCM returned invalid resource or usage.\n");
continue;
}
/*
* Try to convert the RCM resource name to a hotplug path.
* If conversion succeeds and this path differs from the
* current slot in the table, then initialize the next
* slot in the table.
*/
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++;
}
/* Append current usage to entry list in the current slot */
if (slot != NULL) {
/* Allocate new entry */
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);
}
/* Link entry into current slot list */
entry->next = slot->entries;
slot->entries = entry;
/* Initialize entry values */
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);
}
}
}
/* Check if valid entries were inserted in table */
if (table_len == 0) {
free(table);
return (0);
}
/* Sort the lookup table by hotplug path */
qsort(table, table_len, sizeof (info_table_t), compare_info);
/* Done */
*tablep = table;
*table_lenp = table_len;
return (0);
}
/*
* free_table()
*
* Destroy a lookup table.
*/
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);
}
}
/*
* rsrc2path()
*
* Convert from an RCM resource name to a hotplug device path.
*/
static int
rsrc2path(const char *rsrc, char *path)
{
char *s;
char tmp[MAXPATHLEN];
/* Only convert /dev and /devices paths */
if (strncmp(rsrc, "/dev", 4) == 0) {
/* Follow symbolic links for /dev paths */
if (realpath(rsrc, tmp) == NULL) {
log_err("Cannot resolve RCM resource (%s)\n",
strerror(errno));
return (-1);
}
/* Remove the leading "/devices" part */
(void) strlcpy(path, &tmp[strlen(S_DEVICES)], MAXPATHLEN);
/* Remove any trailing minor node part */
if ((s = strrchr(path, ':')) != NULL)
*s = '\0';
/* Successfully converted */
return (0);
}
/* Not converted */
return (-1);
}
/*
* compare_info()
*
* Compare two slots in the lookup table that maps paths to usage.
*
* NOTE: for use with qsort() and bsearch().
*/
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));
}
#!/sbin/sh
#
# 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 2009 Sun Microsystems, Inc. All rights reserved.
# Use is subject to license terms.
#
#
# Startup script for the hotplugd(8) daemon.
#
. /lib/svc/share/smf_include.sh
HOTPLUGD_DOOR=/var/run/hotplugd_door
# The hotplug service is only supported in the global zone.
if smf_is_nonglobalzone; then
/sbin/svcadm disable $SMF_FMRI
echo "$SMF_FMRI is not supported in a local zone"
sleep 5 &
exit $SMF_EXIT_OK
fi
# If a hotplug door exists, check for a hotplugd process and exit
# if the daemon is already running.
if [ -f $HOTPLUGD_DOOR ]; then
if /usr/bin/pgrep -x -u 0 hotplugd >/dev/null 2>&1; then
echo "$0: hotplugd is already running"
exit 1
fi
fi
rm -f $HOTPLUGD_DOOR
exec /usr/lib/hotplugd