/* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include "librcm_impl.h" #include "librcm_event.h" #ifdef DEBUG static int rcm_debug = 1; #define dprintf(args) if (rcm_debug) (void) fprintf args #else #define dprintf(args) /* nothing */ #endif /* DEBUG */ static int extract_info(nvlist_t *, rcm_info_t **); static int rcm_daemon_is_alive(); static int rcm_common(int, rcm_handle_t *, char **, uint_t, void *, rcm_info_t **); static int rcm_direct_call(int, rcm_handle_t *, char **, uint_t, void *, rcm_info_t **); static int rcm_daemon_call(int, rcm_handle_t *, char **, uint_t, void *, rcm_info_t **); static int rcm_generate_nvlist(int, rcm_handle_t *, char **, uint_t, void *, char **, size_t *); static int rcm_check_permission(void); /* * Allocate a handle structure */ /*ARGSUSED2*/ int rcm_alloc_handle(char *modname, uint_t flag, void *arg, rcm_handle_t **hdp) { rcm_handle_t *hd; void *temp; char namebuf[MAXPATHLEN]; if ((hdp == NULL) || (flag & ~RCM_ALLOC_HDL_MASK)) { errno = EINVAL; return (RCM_FAILURE); } if (rcm_check_permission() == 0) { errno = EPERM; return (RCM_FAILURE); } if ((hd = calloc(1, sizeof (*hd))) == NULL) { return (RCM_FAILURE); } if (modname) { (void) snprintf(namebuf, MAXPATHLEN, "%s%s", modname, RCM_MODULE_SUFFIX); if ((hd->modname = strdup(namebuf)) == NULL) { free(hd); return (RCM_FAILURE); } if ((temp = rcm_module_open(namebuf)) == NULL) { free(hd->modname); free(hd); errno = EINVAL; return (RCM_FAILURE); } rcm_module_close(temp); } if (flag & RCM_NOPID) { hd->pid = (pid_t)0; } else { hd->pid = (pid_t)getpid(); } *hdp = hd; return (RCM_SUCCESS); } /* free handle structure */ int rcm_free_handle(rcm_handle_t *hd) { if (hd == NULL) { errno = EINVAL; return (RCM_FAILURE); } if (hd->modname) { free(hd->modname); } free(hd); return (RCM_SUCCESS); } /* * Operations which require daemon processing */ /* get registration and DR information from rcm_daemon */ int rcm_get_info(rcm_handle_t *hd, char *rsrcname, uint_t flag, rcm_info_t **infop) { char *rsrcnames[2]; if ((flag & ~RCM_GET_INFO_MASK) || (infop == NULL)) { errno = EINVAL; return (RCM_FAILURE); } /* * rsrcname may be NULL if requesting dr operations or modinfo */ if (rsrcname == NULL && (flag & (RCM_DR_OPERATION | RCM_MOD_INFO)) == 0) { errno = EINVAL; return (RCM_FAILURE); } rsrcnames[0] = rsrcname; rsrcnames[1] = NULL; return (rcm_common(CMD_GETINFO, hd, rsrcnames, flag, NULL, infop)); } /* get registration and DR information from rcm_daemon (list version) */ int rcm_get_info_list(rcm_handle_t *hd, char **rsrcnames, uint_t flag, rcm_info_t **infop) { /* Requesting the current DR operations with a *list() is invalid */ if ((flag & RCM_DR_OPERATION) || (flag & RCM_MOD_INFO)) { errno = EINVAL; return (RCM_FAILURE); } return (rcm_common(CMD_GETINFO, hd, rsrcnames, flag, NULL, infop)); } /* request to offline a resource before DR removal */ int rcm_request_offline(rcm_handle_t *hd, char *rsrcname, uint_t flag, rcm_info_t **infop) { char *rsrcnames[2]; rsrcnames[0] = rsrcname; rsrcnames[1] = NULL; return (rcm_request_offline_list(hd, rsrcnames, flag, infop)); } /* request to offline a resource before DR removal (list version) */ int rcm_request_offline_list(rcm_handle_t *hd, char **rsrcnames, uint_t flag, rcm_info_t **infop) { if (flag & ~RCM_REQUEST_MASK) { errno = EINVAL; return (RCM_FAILURE); } return (rcm_common(CMD_OFFLINE, hd, rsrcnames, flag, NULL, infop)); } /* cancel offline request and allow apps to use rsrcname */ int rcm_notify_online(rcm_handle_t *hd, char *rsrcname, uint_t flag, rcm_info_t **infop) { char *rsrcnames[2]; rsrcnames[0] = rsrcname; rsrcnames[1] = NULL; return (rcm_notify_online_list(hd, rsrcnames, flag, infop)); } /* cancel offline and allow apps to use resources (list version) */ int rcm_notify_online_list(rcm_handle_t *hd, char **rsrcnames, uint_t flag, rcm_info_t **infop) { if (flag & ~RCM_NOTIFY_MASK) { errno = EINVAL; return (RCM_FAILURE); } return (rcm_common(CMD_ONLINE, hd, rsrcnames, flag, NULL, infop)); } /* notify that rsrcname has been removed */ int rcm_notify_remove(rcm_handle_t *hd, char *rsrcname, uint_t flag, rcm_info_t **infop) { char *rsrcnames[2]; rsrcnames[0] = rsrcname; rsrcnames[1] = NULL; return (rcm_notify_remove_list(hd, rsrcnames, flag, infop)); } /* notify that resrouces have been removed (list form) */ int rcm_notify_remove_list(rcm_handle_t *hd, char **rsrcnames, uint_t flag, rcm_info_t **infop) { if (flag & ~RCM_NOTIFY_MASK) { errno = EINVAL; return (RCM_FAILURE); } return (rcm_common(CMD_REMOVE, hd, rsrcnames, flag, NULL, infop)); } /* request for permission to suspend resource of interval time */ int rcm_request_suspend(rcm_handle_t *hd, char *rsrcname, uint_t flag, timespec_t *interval, rcm_info_t **infop) { char *rsrcnames[2]; rsrcnames[0] = rsrcname; rsrcnames[1] = NULL; return (rcm_request_suspend_list(hd, rsrcnames, flag, interval, infop)); } /* request for permission to suspend resource of interval time (list form) */ int rcm_request_suspend_list(rcm_handle_t *hd, char **rsrcnames, uint_t flag, timespec_t *interval, rcm_info_t **infop) { if ((flag & ~RCM_REQUEST_MASK) || (interval == NULL) || (interval->tv_sec < 0) || (interval->tv_nsec < 0)) { errno = EINVAL; return (RCM_FAILURE); } return (rcm_common(CMD_SUSPEND, hd, rsrcnames, flag, (void *)interval, infop)); } /* notify apps of the completion of resource suspension */ int rcm_notify_resume(rcm_handle_t *hd, char *rsrcname, uint_t flag, rcm_info_t **infop) { char *rsrcnames[2]; rsrcnames[0] = rsrcname; rsrcnames[1] = NULL; return (rcm_notify_resume_list(hd, rsrcnames, flag, infop)); } /* notify apps of the completion of resource suspension (list form) */ int rcm_notify_resume_list(rcm_handle_t *hd, char **rsrcnames, uint_t flag, rcm_info_t **infop) { if (flag & ~(RCM_NOTIFY_MASK | RCM_SUSPENDED)) { errno = EINVAL; return (RCM_FAILURE); } return (rcm_common(CMD_RESUME, hd, rsrcnames, flag, NULL, infop)); } /* request a capacity change from apps */ int rcm_request_capacity_change(rcm_handle_t *hd, char *rsrcname, uint_t flag, nvlist_t *nvl, rcm_info_t **infop) { int rv; char *rsrcnames[2]; if ((nvl == NULL) || (flag & ~RCM_REQUEST_MASK)) { errno = EINVAL; return (RCM_FAILURE); } rsrcnames[0] = rsrcname; rsrcnames[1] = NULL; rv = rcm_common(CMD_REQUEST_CHANGE, hd, rsrcnames, flag, (void *)nvl, infop); return (rv); } /* notify apps of a capacity change */ int rcm_notify_capacity_change(rcm_handle_t *hd, char *rsrcname, uint_t flag, nvlist_t *nvl, rcm_info_t **infop) { int rv; char *rsrcnames[2]; if ((nvl == NULL) || (flag & ~RCM_REQUEST_MASK)) { errno = EINVAL; return (RCM_FAILURE); } rsrcnames[0] = rsrcname; rsrcnames[1] = NULL; rv = rcm_common(CMD_NOTIFY_CHANGE, hd, rsrcnames, flag, (void *)nvl, infop); return (rv); } /* notify apps of an event */ int rcm_notify_event(rcm_handle_t *hd, char *rsrcname, uint_t flag, nvlist_t *nvl, rcm_info_t **infop) { int rv; char *rsrcnames[2]; /* No flags are defined yet for rcm_notify_event() */ if ((nvl == NULL) || (flag != 0)) { errno = EINVAL; return (RCM_FAILURE); } rsrcnames[0] = rsrcname; rsrcnames[1] = NULL; rv = rcm_common(CMD_EVENT, hd, rsrcnames, 0, (void *)nvl, infop); return (rv); } /* * Register to receive capacity changes. This requires a module to exist in * module directory. It should be called prior to using a new resource. */ /* ARGSUSED */ int rcm_register_capacity(rcm_handle_t *hd, char *rsrcname, uint_t flag, rcm_info_t **infop) { char *rsrcnames[2]; if (flag & ~RCM_REGISTER_MASK) { errno = EINVAL; return (RCM_FAILURE); } flag |= RCM_REGISTER_CAPACITY; rsrcnames[0] = rsrcname; rsrcnames[1] = NULL; return (rcm_common(CMD_REGISTER, hd, rsrcnames, flag, NULL, NULL)); } /* unregister interest in capacity changes */ int rcm_unregister_capacity(rcm_handle_t *hd, char *rsrcname, uint_t flag) { char *rsrcnames[2]; if (flag & ~RCM_REGISTER_MASK) { errno = EINVAL; return (RCM_FAILURE); } flag |= RCM_REGISTER_CAPACITY; rsrcnames[0] = rsrcname; rsrcnames[1] = NULL; return (rcm_common(CMD_UNREGISTER, hd, rsrcnames, flag, NULL, NULL)); } /* * Register to receive events. This requires a module to exist in module * directory. It should be called prior to using a new resource. */ /* ARGSUSED */ int rcm_register_event(rcm_handle_t *hd, char *rsrcname, uint_t flag, rcm_info_t **infop) { char *rsrcnames[2]; if (flag & ~RCM_REGISTER_MASK) { errno = EINVAL; return (RCM_FAILURE); } flag |= RCM_REGISTER_EVENT; rsrcnames[0] = rsrcname; rsrcnames[1] = NULL; return (rcm_common(CMD_REGISTER, hd, rsrcnames, flag, NULL, NULL)); } /* unregister interest in events */ int rcm_unregister_event(rcm_handle_t *hd, char *rsrcname, uint_t flag) { char *rsrcnames[2]; if (flag & ~RCM_REGISTER_MASK) { errno = EINVAL; return (RCM_FAILURE); } flag |= RCM_REGISTER_EVENT; rsrcnames[0] = rsrcname; rsrcnames[1] = NULL; return (rcm_common(CMD_UNREGISTER, hd, rsrcnames, flag, NULL, NULL)); } /* * Register interest in a resource. This requires a module to exist in module * directory. It should be called prior to using a new resource. * * Registration may be denied if it is presently locked by a DR operation. */ /* ARGSUSED */ int rcm_register_interest(rcm_handle_t *hd, char *rsrcname, uint_t flag, rcm_info_t **infop) { char *rsrcnames[2]; if (flag & ~RCM_REGISTER_MASK) { errno = EINVAL; return (RCM_FAILURE); } flag |= RCM_REGISTER_DR; rsrcnames[0] = rsrcname; rsrcnames[1] = NULL; return (rcm_common(CMD_REGISTER, hd, rsrcnames, flag, NULL, NULL)); } /* unregister interest in rsrcname */ int rcm_unregister_interest(rcm_handle_t *hd, char *rsrcname, uint_t flag) { char *rsrcnames[2]; if (flag & ~RCM_REGISTER_MASK) { errno = EINVAL; return (RCM_FAILURE); } flag |= RCM_REGISTER_DR; rsrcnames[0] = rsrcname; rsrcnames[1] = NULL; return (rcm_common(CMD_UNREGISTER, hd, rsrcnames, flag, NULL, NULL)); } /* get the current state of a resource */ int rcm_get_rsrcstate(rcm_handle_t *hd, char *rsrcname, int *statep) { int result; int flag = 0; rcm_info_t *infop = NULL; rcm_info_tuple_t *tuple = NULL; char *rsrcnames[2]; if (statep == NULL) { errno = EINVAL; return (RCM_FAILURE); } rsrcnames[0] = rsrcname; rsrcnames[1] = NULL; result = rcm_common(CMD_GETSTATE, hd, rsrcnames, flag, NULL, &infop); /* * A successful result implies the presence of exactly one RCM info * tuple containing the state of this resource (a combination of each * client's resources). If that's not true, change the result to * RCM_FAILURE. */ if (result == RCM_SUCCESS) { if ((infop == NULL) || ((tuple = rcm_info_next(infop, NULL)) == NULL) || (rcm_info_next(infop, tuple) != NULL)) { result = RCM_FAILURE; } else if (infop && tuple) { *statep = rcm_info_state(tuple); } } if (infop) rcm_free_info(infop); return (result); } /* * RCM helper functions exposed to librcm callers. */ /* Free linked list of registration info */ void rcm_free_info(rcm_info_t *info) { while (info) { rcm_info_t *tmp = info->next; nvlist_free(info->info); free(info); info = tmp; } } /* return the next tuple in the info structure */ rcm_info_tuple_t * rcm_info_next(rcm_info_t *info, rcm_info_tuple_t *tuple) { if (info == NULL) { errno = EINVAL; return (NULL); } if (tuple == NULL) { return ((rcm_info_tuple_t *)info); } return ((rcm_info_tuple_t *)tuple->next); } /* return resource name */ const char * rcm_info_rsrc(rcm_info_tuple_t *tuple) { char *rsrcname = NULL; if (tuple == NULL || tuple->info == NULL) { errno = EINVAL; return (NULL); } if (errno = nvlist_lookup_string(tuple->info, RCM_RSRCNAME, &rsrcname)) return (NULL); return (rsrcname); } const char * rcm_info_info(rcm_info_tuple_t *tuple) { char *info = NULL; if (tuple == NULL || tuple->info == NULL) { errno = EINVAL; return (NULL); } if (errno = nvlist_lookup_string(tuple->info, RCM_CLIENT_INFO, &info)) return (NULL); return (info); } const char * rcm_info_error(rcm_info_tuple_t *tuple) { char *errstr = NULL; if (tuple == NULL || tuple->info == NULL) { errno = EINVAL; return (NULL); } if (errno = nvlist_lookup_string(tuple->info, RCM_CLIENT_ERROR, &errstr)) return (NULL); return (errstr); } /* return info string in the tuple */ const char * rcm_info_modname(rcm_info_tuple_t *tuple) { char *modname = NULL; if (tuple == NULL || tuple->info == NULL) { errno = EINVAL; return (NULL); } if (errno = nvlist_lookup_string(tuple->info, RCM_CLIENT_MODNAME, &modname)) return (NULL); return (modname); } /* return client pid in the tuple */ pid_t rcm_info_pid(rcm_info_tuple_t *tuple) { uint64_t pid64 = (uint64_t)0; if (tuple == NULL || tuple->info == NULL) { errno = EINVAL; return ((pid_t)0); } if (errno = nvlist_lookup_uint64(tuple->info, RCM_CLIENT_ID, &pid64)) return ((pid_t)0); return ((pid_t)pid64); } /* return client state in the tuple */ int rcm_info_state(rcm_info_tuple_t *tuple) { int state; if (tuple == NULL || tuple->info == NULL) { errno = EINVAL; return (RCM_STATE_UNKNOWN); } if (errno = nvlist_lookup_int32(tuple->info, RCM_RSRCSTATE, &state)) return (RCM_STATE_UNKNOWN); return (state); } /* return the generic properties in the tuple */ nvlist_t * rcm_info_properties(rcm_info_tuple_t *tuple) { char *buf; uint_t buflen; nvlist_t *nvl; if (tuple == NULL || tuple->info == NULL) { errno = EINVAL; return (NULL); } if (errno = nvlist_lookup_byte_array(tuple->info, RCM_CLIENT_PROPERTIES, (uchar_t **)&buf, &buflen)) return (NULL); if (errno = nvlist_unpack(buf, buflen, &nvl, 0)) { free(buf); return (NULL); } return (nvl); } /* * return operation sequence number * * This is private. Called by rcmctl only for testing purposes. */ int rcm_info_seqnum(rcm_info_tuple_t *tuple) { int seqnum; if (tuple == NULL || tuple->info == NULL) { errno = EINVAL; return (-1); } if (errno = nvlist_lookup_int32(tuple->info, RCM_SEQ_NUM, &seqnum)) return (-1); return (seqnum); } /* * The following interfaces are PRIVATE to the RCM framework. They are not * declared static because they are called by rcm_daemon. */ /* * Invoke shell to execute command in MT safe manner. * Returns wait status or -1 on error. */ int rcm_exec_cmd(char *cmd) { pid_t pid; int status, w; char *argvec[] = {"sh", "-c", NULL, NULL}; argvec[2] = cmd; if ((pid = fork1()) == 0) { (void) execv("/bin/sh", argvec); _exit(127); } else if (pid == -1) { return (-1); } do { w = waitpid(pid, &status, 0); } while (w == -1 && errno == EINTR); return ((w == -1) ? w : status); } /* Append info at the very end */ int rcm_append_info(rcm_info_t **head, rcm_info_t *info) { rcm_info_t *tuple; if (head == NULL) { errno = EINVAL; return (RCM_FAILURE); } if ((tuple = *head) == NULL) { *head = info; return (RCM_SUCCESS); } while (tuple->next) { tuple = tuple->next; } tuple->next = info; return (RCM_SUCCESS); } /* get rcm module and rcm script directory names */ #define N_MODULE_DIR 3 /* search 3 directories for modules */ #define MODULE_DIR_HW "/usr/platform/%s/lib/rcm/modules/" #define MODULE_DIR_GEN "/usr/lib/rcm/modules/" #define N_SCRIPT_DIR 4 /* search 4 directories for scripts */ #define SCRIPT_DIR_HW "/usr/platform/%s/lib/rcm/scripts/" #define SCRIPT_DIR_GEN "/usr/lib/rcm/scripts/" #define SCRIPT_DIR_ETC "/etc/rcm/scripts/" char * rcm_module_dir(uint_t dirnum) { if (dirnum < N_MODULE_DIR) return (rcm_dir(dirnum, NULL)); else return (NULL); } char * rcm_script_dir(uint_t dirnum) { if (dirnum < N_SCRIPT_DIR) return (rcm_dir(dirnum + N_MODULE_DIR, NULL)); else return (NULL); } char * rcm_dir(uint_t dirnum, int *rcm_script) { static char dir_name[N_MODULE_DIR + N_SCRIPT_DIR][MAXPATHLEN]; char infobuf[MAXPATHLEN]; if (dirnum >= (N_MODULE_DIR + N_SCRIPT_DIR)) return (NULL); if (dir_name[0][0] == '\0') { /* * construct the module directory names */ if (sysinfo(SI_PLATFORM, infobuf, MAXPATHLEN) == -1) { dprintf((stderr, "sysinfo %s\n", strerror(errno))); return (NULL); } else { if (snprintf(dir_name[0], MAXPATHLEN, MODULE_DIR_HW, infobuf) >= MAXPATHLEN || snprintf(dir_name[N_MODULE_DIR + 1], MAXPATHLEN, SCRIPT_DIR_HW, infobuf) >= MAXPATHLEN) { dprintf((stderr, "invalid module or script directory for " "platform %s\n", infobuf)); return (NULL); } } if (sysinfo(SI_MACHINE, infobuf, MAXPATHLEN) == -1) { dprintf((stderr, "sysinfo %s\n", strerror(errno))); return (NULL); } else { if (snprintf(dir_name[1], MAXPATHLEN, MODULE_DIR_HW, infobuf) >= MAXPATHLEN || snprintf(dir_name[N_MODULE_DIR + 2], MAXPATHLEN, SCRIPT_DIR_HW, infobuf) >= MAXPATHLEN) { dprintf((stderr, "invalid module or script directory for " "machine type %s\n", infobuf)); return (NULL); } } if (strlcpy(dir_name[2], MODULE_DIR_GEN, MAXPATHLEN) >= MAXPATHLEN || strlcpy(dir_name[N_MODULE_DIR + 3], SCRIPT_DIR_GEN, MAXPATHLEN) >= MAXPATHLEN || strlcpy(dir_name[N_MODULE_DIR + 0], SCRIPT_DIR_ETC, MAXPATHLEN) >= MAXPATHLEN) { dprintf((stderr, "invalid module or script generic directory\n")); return (NULL); } } if (rcm_script) *rcm_script = (dirnum < N_MODULE_DIR) ? 0 : 1; return (dir_name[dirnum]); } /* * Find the directory where the script is located. * If the script is found return a pointer to the directory where the * script was found otherwise return NULL. */ char * rcm_get_script_dir(char *script_name) { uint_t i; char *dir_name; char path[MAXPATHLEN]; struct stat stats; for (i = 0; (dir_name = rcm_script_dir(i)) != NULL; i++) { if (snprintf(path, MAXPATHLEN, "%s%s", dir_name, script_name) >= MAXPATHLEN) { dprintf((stderr, "invalid script %s skipped\n", script_name)); continue; } if (stat(path, &stats) == 0) return (dir_name); } return (NULL); } /* * Returns 1 if the filename is an rcm script. * Returns 0 if the filename is an rcm module. */ int rcm_is_script(char *filename) { char *tmp; if (((tmp = strstr(filename, RCM_MODULE_SUFFIX)) != NULL) && (tmp[strlen(RCM_MODULE_SUFFIX)] == '\0')) return (0); else return (1); } /* Locate the module and call dlopen */ void * rcm_module_open(char *modname) { unsigned i; char *dir_name; void *dlhandle = NULL; char modpath[MAXPATHLEN]; #ifdef DEBUG struct stat sbuf; #endif /* * dlopen the module */ for (i = 0; (dir_name = rcm_module_dir(i)) != NULL; i++) { if (snprintf(modpath, MAXPATHLEN, "%s%s", dir_name, modname) >= MAXPATHLEN) { dprintf((stderr, "invalid module %s skipped\n", modname)); continue; } if ((dlhandle = dlopen(modpath, RTLD_LAZY)) != NULL) { return (dlhandle); } dprintf((stderr, "failure (dlopen=%s)\n", dlerror())); #ifdef DEBUG if (stat(modpath, &sbuf) == 0) { (void) fprintf(stderr, "%s is not a valid module\n", modpath); } #endif } dprintf((stderr, "module %s not found\n", modname)); return (NULL); } /* dlclose module */ void rcm_module_close(void *dlhandle) { if (dlclose(dlhandle) == 0) return; dprintf((stderr, "dlclose: %s\n", dlerror())); } /* * stub implementation of rcm_log_message allows dlopen of rcm modules * to proceed in absence of rcm_daemon. * * This definition is interposed by the definition in rcm_daemon because of the * default search order implemented by the linker and dlsym(). All RCM modules * will see the daemon version when loaded by the rcm_daemon. */ /* ARGSUSED */ void rcm_log_message(int level, char *message, ...) { dprintf((stderr, "rcm_log_message stub\n")); } /* * Helper functions */ /* * Common routine for all rcm calls which require daemon processing */ static int rcm_common(int cmd, rcm_handle_t *hd, char **rsrcnames, uint_t flag, void *arg, rcm_info_t **infop) { int i; if (hd == NULL) { errno = EINVAL; return (RCM_FAILURE); } if (getuid() != 0) { errno = EPERM; return (RCM_FAILURE); } if ((flag & (RCM_DR_OPERATION | RCM_MOD_INFO)) == 0) { if ((rsrcnames == NULL) || (rsrcnames[0] == NULL)) { errno = EINVAL; return (RCM_FAILURE); } for (i = 0; rsrcnames[i] != NULL; i++) { if (*rsrcnames[i] == '\0') { errno = EINVAL; return (RCM_FAILURE); } } } /* * Check if handle is allocated by rcm_daemon. If so, this call came * from an RCM module, so we make a direct call into rcm_daemon. */ if (hd->lrcm_ops != NULL) { return (rcm_direct_call(cmd, hd, rsrcnames, flag, arg, infop)); } /* * When not called from a RCM module (i.e. no recursion), zero the * pointer just in case caller did not do so. For recursive calls, * we want to append rcm_info_t after infop; zero it may cause * memory leaks. */ if (infop) { *infop = NULL; } /* * Now call into the daemon. */ return (rcm_daemon_call(cmd, hd, rsrcnames, flag, arg, infop)); } /* * Caller is an RCM module, call directly into rcm_daemon. */ static int rcm_direct_call(int cmd, rcm_handle_t *hd, char **rsrcnames, uint_t flag, void *arg, rcm_info_t **infop) { int error; librcm_ops_t *ops = (librcm_ops_t *)hd->lrcm_ops; switch (cmd) { case CMD_GETINFO: error = ops->librcm_getinfo(rsrcnames, flag, hd->seq_num, infop); break; case CMD_OFFLINE: error = ops->librcm_offline(rsrcnames, hd->pid, flag, hd->seq_num, infop); break; case CMD_ONLINE: error = ops->librcm_online(rsrcnames, hd->pid, flag, hd->seq_num, infop); break; case CMD_REMOVE: error = ops->librcm_remove(rsrcnames, hd->pid, flag, hd->seq_num, infop); break; case CMD_SUSPEND: error = ops->librcm_suspend(rsrcnames, hd->pid, flag, hd->seq_num, (timespec_t *)arg, infop); break; case CMD_RESUME: error = ops->librcm_resume(rsrcnames, hd->pid, flag, hd->seq_num, infop); break; case CMD_REGISTER: error = ops->librcm_regis(hd->modname, rsrcnames[0], hd->pid, flag, infop); break; case CMD_UNREGISTER: error = ops->librcm_unregis(hd->modname, rsrcnames[0], hd->pid, flag); break; case CMD_REQUEST_CHANGE: error = ops->librcm_request_change(rsrcnames[0], hd->pid, flag, hd->seq_num, (nvlist_t *)arg, infop); break; case CMD_NOTIFY_CHANGE: error = ops->librcm_notify_change(rsrcnames[0], hd->pid, flag, hd->seq_num, (nvlist_t *)arg, infop); break; case CMD_EVENT: error = ops->librcm_notify_event(rsrcnames[0], hd->pid, flag, hd->seq_num, (nvlist_t *)arg, infop); break; case CMD_GETSTATE: error = ops->librcm_getstate(rsrcnames[0], hd->pid, infop); break; default: dprintf((stderr, "invalid command: %d\n", cmd)); error = EFAULT; } if (error > 0) { errno = error; error = RCM_FAILURE; } return (error); } /* * Call into rcm_daemon door to process the request */ static int rcm_daemon_call(int cmd, rcm_handle_t *hd, char **rsrcnames, uint_t flag, void *arg, rcm_info_t **infop) { int errno_found; int daemon_errno = 0; int error = RCM_SUCCESS; int delay = 300; int maxdelay = 10000; /* 10 seconds */ char *nvl_packed = NULL; size_t nvl_size = 0; nvlist_t *ret = NULL; nvpair_t *nvp; size_t rsize = 0; rcm_info_t *info = NULL; errno = 0; /* * Decide whether to start the daemon */ switch (cmd) { case CMD_GETINFO: case CMD_OFFLINE: case CMD_ONLINE: case CMD_REMOVE: case CMD_SUSPEND: case CMD_RESUME: case CMD_REGISTER: case CMD_UNREGISTER: case CMD_EVENT: case CMD_REQUEST_CHANGE: case CMD_NOTIFY_CHANGE: case CMD_GETSTATE: break; default: errno = EFAULT; return (RCM_FAILURE); } if (rcm_daemon_is_alive() != 1) { dprintf((stderr, "failed to start rcm_daemon\n")); errno = EFAULT; return (RCM_FAILURE); } /* * Generate a packed nvlist for the request */ if (rcm_generate_nvlist(cmd, hd, rsrcnames, flag, arg, &nvl_packed, &nvl_size) < 0) { dprintf((stderr, "error in nvlist generation\n")); errno = EFAULT; return (RCM_FAILURE); } /* * Make the door call and get a return event. We go into a retry loop * when RCM_ET_EAGAIN is returned. */ retry: if (get_event_service(RCM_SERVICE_DOOR, (void *)nvl_packed, nvl_size, (void **)&ret, &rsize) < 0) { dprintf((stderr, "rcm_daemon call failed: %s\n", strerror(errno))); free(nvl_packed); return (RCM_FAILURE); } assert(ret != NULL); /* * nvlist_lookup_* routines don't work because the returned nvlist * was nvlist_alloc'ed without the NV_UNIQUE_NAME flag. Implement * a sequential search manually, which is fine since there is only * one RCM_RESULT value in the nvlist. */ errno_found = 0; nvp = NULL; while (nvp = nvlist_next_nvpair(ret, nvp)) { if (strcmp(nvpair_name(nvp), RCM_RESULT) == 0) { if (errno = nvpair_value_int32(nvp, &daemon_errno)) { error = RCM_FAILURE; goto out; } errno_found++; break; } } if (errno_found == 0) { errno = EFAULT; error = RCM_FAILURE; goto out; } if (daemon_errno == EAGAIN) { /* * Wait and retry */ dprintf((stderr, "retry door_call\n")); if (delay > maxdelay) { errno = EAGAIN; error = RCM_FAILURE; goto out; } (void) poll(NULL, 0, delay); delay *= 2; /* exponential back off */ nvlist_free(ret); goto retry; } /* * The door call succeeded. Now extract info from returned event. */ if (extract_info(ret, &info) != 0) { dprintf((stderr, "error in extracting event data\n")); errno = EFAULT; error = RCM_FAILURE; goto out; } if (infop) *infop = info; else rcm_free_info(info); if (daemon_errno) { if (daemon_errno > 0) { errno = daemon_errno; error = RCM_FAILURE; } else { error = daemon_errno; } } out: if (nvl_packed) free(nvl_packed); nvlist_free(ret); dprintf((stderr, "daemon call is done. error = %d, errno = %s\n", error, strerror(errno))); return (error); } /* * Extract registration info from event data. * Return 0 on success and -1 on failure. */ static int extract_info(nvlist_t *nvl, rcm_info_t **infop) { rcm_info_t *info = NULL; rcm_info_t *prev = NULL; rcm_info_t *tmp = NULL; char *buf; uint_t buflen; nvpair_t *nvp = NULL; while (nvp = nvlist_next_nvpair(nvl, nvp)) { buf = NULL; buflen = 0; if (strcmp(nvpair_name(nvp), RCM_RESULT_INFO) != 0) continue; if ((tmp = calloc(1, sizeof (*tmp))) == NULL) { dprintf((stderr, "out of memory\n")); goto fail; } if (errno = nvpair_value_byte_array(nvp, (uchar_t **)&buf, &buflen)) { free(tmp); dprintf((stderr, "failed (nvpair_value=%s)\n", strerror(errno))); goto fail; } if (errno = nvlist_unpack(buf, buflen, &(tmp->info), 0)) { free(tmp); dprintf((stderr, "failed (nvlist_unpack=%s)\n", strerror(errno))); goto fail; } if (info == NULL) { prev = info = tmp; } else { prev->next = tmp; prev = tmp; } } *infop = info; return (0); fail: rcm_free_info(info); *infop = NULL; return (-1); } /* Generate a packed nvlist for communicating with RCM daemon */ static int rcm_generate_nvlist(int cmd, rcm_handle_t *hd, char **rsrcnames, uint_t flag, void *arg, char **nvl_packed, size_t *nvl_size) { int nrsrcnames; char *buf = NULL; size_t buflen = 0; nvlist_t *nvl = NULL; assert((nvl_packed != NULL) && (nvl_size != NULL)); *nvl_size = 0; *nvl_packed = NULL; /* Allocate an empty nvlist */ if ((errno = nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0)) > 0) { dprintf((stderr, "failed (nvlist_alloc=%s).\n", strerror(errno))); return (-1); } /* Stuff in all the arguments for the daemon call */ if (nvlist_add_int32(nvl, RCM_CMD, cmd) != 0) { dprintf((stderr, "failed (nvlist_add(CMD)=%s).\n", strerror(errno))); goto fail; } if (rsrcnames) { nrsrcnames = 0; while (rsrcnames[nrsrcnames] != NULL) nrsrcnames++; if (nvlist_add_string_array(nvl, RCM_RSRCNAMES, rsrcnames, nrsrcnames) != 0) { dprintf((stderr, "failed (nvlist_add(RSRCNAMES)=%s).\n", strerror(errno))); goto fail; } } if (hd->modname) { if (nvlist_add_string(nvl, RCM_CLIENT_MODNAME, hd->modname) != 0) { dprintf((stderr, "failed (nvlist_add(CLIENT_MODNAME)=%s).\n", strerror(errno))); goto fail; } } if (hd->pid) { if (nvlist_add_uint64(nvl, RCM_CLIENT_ID, hd->pid) != 0) { dprintf((stderr, "failed (nvlist_add(CLIENT_ID)=%s).\n", strerror(errno))); goto fail; } } if (flag) { if (nvlist_add_uint32(nvl, RCM_REQUEST_FLAG, flag) != 0) { dprintf((stderr, "failed (nvlist_add(REQUEST_FLAG)=%s).\n", strerror(errno))); goto fail; } } if (arg && cmd == CMD_SUSPEND) { if (nvlist_add_byte_array(nvl, RCM_SUSPEND_INTERVAL, (uchar_t *)arg, sizeof (timespec_t)) != 0) { dprintf((stderr, "failed (nvlist_add(SUSPEND_INTERVAL)=%s).\n", strerror(errno))); goto fail; } } if (arg && ((cmd == CMD_REQUEST_CHANGE) || (cmd == CMD_NOTIFY_CHANGE))) { if (errno = nvlist_pack(arg, &buf, &buflen, NV_ENCODE_NATIVE, 0)) { dprintf((stderr, "failed (nvlist_pack(CHANGE_DATA)=%s).\n", strerror(errno))); goto fail; } if (nvlist_add_byte_array(nvl, RCM_CHANGE_DATA, (uchar_t *)buf, buflen) != 0) { dprintf((stderr, "failed (nvlist_add(CHANGE_DATA)=%s).\n", strerror(errno))); goto fail; } } if (arg && cmd == CMD_EVENT) { if (errno = nvlist_pack(arg, &buf, &buflen, NV_ENCODE_NATIVE, 0)) { dprintf((stderr, "failed (nvlist_pack(CHANGE_DATA)=%s).\n", strerror(errno))); goto fail; } if (nvlist_add_byte_array(nvl, RCM_EVENT_DATA, (uchar_t *)buf, buflen) != 0) { dprintf((stderr, "failed (nvlist_add(EVENT_DATA)=%s).\n", strerror(errno))); goto fail; } } /* Pack the nvlist */ if (errno = nvlist_pack(nvl, nvl_packed, nvl_size, NV_ENCODE_NATIVE, 0)) { dprintf((stderr, "failed (nvlist_pack=%s).\n", strerror(errno))); goto fail; } /* If an argument was packed intermediately, free the buffer */ if (buf) free(buf); /* Free the unpacked version of the nvlist and return the packed list */ nvlist_free(nvl); return (0); fail: if (buf) free(buf); nvlist_free(nvl); if (*nvl_packed) free(*nvl_packed); *nvl_packed = NULL; *nvl_size = 0; return (-1); } /* check if rcm_daemon is up and running */ static int rcm_daemon_is_alive() { int lasttry; struct stat st; nvlist_t *nvl; char *buf = NULL; size_t buflen = 0; int delay = 300; const int maxdelay = 10000; /* 10 sec */ /* generate a packed nvlist for the door knocking */ if (errno = nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0)) { dprintf((stderr, "nvlist_alloc failed: %s\n", strerror(errno))); return (0); } if (errno = nvlist_add_int32(nvl, RCM_CMD, CMD_KNOCK)) { dprintf((stderr, "nvlist_add failed: %s\n", strerror(errno))); nvlist_free(nvl); return (0); } if (errno = nvlist_pack(nvl, &buf, &buflen, NV_ENCODE_NATIVE, 0)) { dprintf((stderr, "nvlist_pack failed: %s\n", strerror(errno))); nvlist_free(nvl); return (0); } nvlist_free(nvl); /* * check the door and knock on it */ if ((stat(RCM_SERVICE_DOOR, &st) == 0) && (get_event_service(RCM_SERVICE_DOOR, (void *)buf, buflen, NULL, NULL) == 0)) { free(buf); return (1); /* daemon is alive */ } /* * Attempt to start the daemon. * If caller has SIGCHLD set to SIG_IGN or its SA_NOCLDWAIT * flag set, waitpid(2) (hence rcm_exec_cmd) will fail. * get_event_service will determine if the rcm_daemon started. */ dprintf((stderr, "exec: %s\n", RCM_DAEMON_START)); (void) rcm_exec_cmd(RCM_DAEMON_START); /* * Wait for daemon to respond, timeout at 10 sec */ while (((lasttry = get_event_service(RCM_SERVICE_DOOR, (void *)buf, buflen, NULL, NULL)) != 0) && ((errno == EBADF) || (errno == ESRCH))) { if (delay > maxdelay) { break; } (void) poll(NULL, 0, delay); delay *= 2; } free(buf); if (lasttry == 0) return (1); return (0); } /* * Check permission. * * The policy is root only for now. Need to relax this when interface level * is raised. */ static int rcm_check_permission(void) { return (getuid() == 0); } /* * Project private function - for use by RCM MSTC tests * * Get the client name (rcm module name or script name) corresponding to * the given rcm handle. */ const char * rcm_get_client_name(rcm_handle_t *hd) { return (hd->modname); }