# # 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 /* * 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