/* * 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 "rcm_impl.h" #include "rcm_module.h" /* * Short-circuits unloading of modules with no registrations, so that * they are present during the next db_sync cycle. */ #define MOD_REFCNT_INIT 2 int need_cleanup; /* flag indicating if clean up is needed */ static mutex_t mod_lock; /* protects module list */ static module_t *module_head; /* linked list of modules */ static rsrc_node_t *rsrc_root; /* root of all resources */ /* * Misc help routines */ static void rcmd_db_print(); static void rcm_handle_free(rcm_handle_t *); static rcm_handle_t *rcm_handle_alloc(module_t *); static void rsrc_clients_free(client_t *); static struct rcm_mod_ops *modops_from_v1(void *); static int call_getinfo(struct rcm_mod_ops *, rcm_handle_t *, char *, id_t, uint_t, char **, char **, nvlist_t *, rcm_info_t **); static int node_action(rsrc_node_t *, void *); extern void start_polling_thread(); /* * translate /dev name to a /devices path * * N.B. This routine can be enhanced to understand network names * and friendly names in the future. */ char * resolve_name(char *alias) { char *tmp; const char *dev = "/dev/"; if (strlen(alias) == 0) return (NULL); if (strncmp(alias, dev, strlen(dev)) == 0) { /* * Treat /dev/... as a symbolic link */ tmp = s_malloc(PATH_MAX); if (realpath(alias, tmp) != NULL) { return (tmp); } else { free(tmp); } /* Fail to resolve /dev/ name, use the name as is */ } return (s_strdup(alias)); } /* * Figure out resource type based on "resolved" name * * N.B. This routine does not figure out file system mount points. * This is determined at runtime when filesys module register * with RCM_FILESYS flag. */ int rsrc_get_type(const char *resolved_name) { if (resolved_name[0] != '/') return (RSRC_TYPE_ABSTRACT); if (strncmp("/devices/", resolved_name, 9) == 0) return (RSRC_TYPE_DEVICE); return (RSRC_TYPE_NORMAL); } /* * Module operations: * module_load, module_unload, module_info, module_attach, module_detach, * cli_module_hold, cli_module_rele */ #ifdef ENABLE_MODULE_DETACH /* * call unregister() entry point to allow module to unregister for * resources without getting confused. */ static void module_detach(module_t *module) { struct rcm_mod_ops *ops = module->modops; rcm_log_message(RCM_TRACE2, "module_detach(name=%s)\n", module->name); ops->rcmop_unregister(module->rcmhandle); } #endif /* ENABLE_MODULE_DETACH */ /* * call register() entry point to allow module to register for resources */ static void module_attach(module_t *module) { struct rcm_mod_ops *ops = module->modops; rcm_log_message(RCM_TRACE2, "module_attach(name=%s)\n", module->name); if (ops->rcmop_register(module->rcmhandle) != RCM_SUCCESS) { rcm_log_message(RCM_WARNING, gettext("module %s register() failed\n"), module->name); } } struct rcm_mod_ops * module_init(module_t *module) { if (module->dlhandle) /* rcm module */ return (module->init()); else /* rcm script */ return (script_init(module)); } /* * call rmc_mod_info() entry of module */ static const char * module_info(module_t *module) { if (module->dlhandle) /* rcm module */ return (module->info()); else /* rcm script */ return (script_info(module)); } int module_fini(module_t *module) { if (module->dlhandle) /* rcm module */ return (module->fini()); else /* rcm script */ return (script_fini(module)); } /* * call rmc_mod_fini() entry of module, dlclose module, and free memory */ static void module_unload(module_t *module) { int version = module->modops->version; rcm_log_message(RCM_DEBUG, "module_unload(name=%s)\n", module->name); (void) module_fini(module); rcm_handle_free(module->rcmhandle); free(module->name); switch (version) { case RCM_MOD_OPS_V1: /* * Free memory associated with converted ops vector */ free(module->modops); break; case RCM_MOD_OPS_VERSION: default: break; } if (module->dlhandle) rcm_module_close(module->dlhandle); free(module); } /* * Locate the module, execute rcm_mod_init() and check ops vector version */ static module_t * module_load(char *modname) { module_t *module; rcm_log_message(RCM_DEBUG, "module_load(name=%s)\n", modname); /* * dlopen the module */ module = s_calloc(1, sizeof (*module)); module->name = s_strdup(modname); module->modops = NULL; rcm_init_queue(&module->client_q); if (rcm_is_script(modname) == 0) { /* rcm module */ module->dlhandle = rcm_module_open(modname); if (module->dlhandle == NULL) { rcm_log_message(RCM_NOTICE, gettext("cannot open module %s\n"), modname); goto fail; } /* * dlsym rcm_mod_init/fini/info() entry points */ module->init = (struct rcm_mod_ops *(*)())dlsym( module->dlhandle, "rcm_mod_init"); module->fini = (int (*)())dlsym( module->dlhandle, "rcm_mod_fini"); module->info = (const char *(*)())dlsym(module->dlhandle, "rcm_mod_info"); if (module->init == NULL || module->fini == NULL || module->info == NULL) { rcm_log_message(RCM_ERROR, gettext("missing entries in module %s\n"), modname); goto fail; } } else { /* rcm script */ module->dlhandle = NULL; module->init = (struct rcm_mod_ops *(*)()) NULL; module->fini = (int (*)()) NULL; module->info = (const char *(*)()) NULL; } if ((module->modops = module_init(module)) == NULL) { if (module->dlhandle) rcm_log_message(RCM_ERROR, gettext("cannot init module %s\n"), modname); goto fail; } /* * Check ops vector version */ switch (module->modops->version) { case RCM_MOD_OPS_V1: module->modops = modops_from_v1((void *)module->modops); break; case RCM_MOD_OPS_VERSION: break; default: rcm_log_message(RCM_ERROR, gettext("module %s rejected: version %d not supported\n"), modname, module->modops->version); (void) module_fini(module); goto fail; } /* * Make sure all fields are set */ if ((module->modops->rcmop_register == NULL) || (module->modops->rcmop_unregister == NULL) || (module->modops->rcmop_get_info == NULL) || (module->modops->rcmop_request_suspend == NULL) || (module->modops->rcmop_notify_resume == NULL) || (module->modops->rcmop_request_offline == NULL) || (module->modops->rcmop_notify_online == NULL) || (module->modops->rcmop_notify_remove == NULL)) { rcm_log_message(RCM_ERROR, gettext("module %s rejected: has NULL ops fields\n"), modname); (void) module_fini(module); goto fail; } module->rcmhandle = rcm_handle_alloc(module); return (module); fail: if (module->modops && module->modops->version == RCM_MOD_OPS_V1) free(module->modops); if (module->dlhandle) rcm_module_close(module->dlhandle); free(module->name); free(module); return (NULL); } /* * add one to module hold count. load the module if not loaded */ static module_t * cli_module_hold(char *modname) { module_t *module; rcm_log_message(RCM_TRACE3, "cli_module_hold(%s)\n", modname); (void) mutex_lock(&mod_lock); module = module_head; while (module) { if (strcmp(module->name, modname) == 0) { break; } module = module->next; } if (module) { module->ref_count++; (void) mutex_unlock(&mod_lock); return (module); } /* * Module not found, attempt to load it */ if ((module = module_load(modname)) == NULL) { (void) mutex_unlock(&mod_lock); return (NULL); } /* * Hold module and link module into module list */ module->ref_count = MOD_REFCNT_INIT; module->next = module_head; module_head = module; (void) mutex_unlock(&mod_lock); return (module); } /* * decrement module hold count. Unload it if no reference */ static void cli_module_rele(module_t *module) { module_t *curr = module_head, *prev = NULL; rcm_log_message(RCM_TRACE3, "cli_module_rele(name=%s)\n", module->name); (void) mutex_lock(&mod_lock); if (--(module->ref_count) != 0) { (void) mutex_unlock(&mod_lock); return; } rcm_log_message(RCM_TRACE2, "unloading module %s\n", module->name); /* * Unlink the module from list */ while (curr && (curr != module)) { prev = curr; curr = curr->next; } if (curr == NULL) { rcm_log_message(RCM_ERROR, gettext("Unexpected error: module %s not found.\n"), module->name); } else if (prev == NULL) { module_head = curr->next; } else { prev->next = curr->next; } (void) mutex_unlock(&mod_lock); module_unload(module); } /* * Gather usage info be passed back to requester. Discard info if user does * not care (list == NULL). */ void add_busy_rsrc_to_list(char *alias, pid_t pid, int state, int seq_num, char *modname, const char *infostr, const char *errstr, nvlist_t *client_props, rcm_info_t **list) { rcm_info_t *info; rcm_info_t *tmp; char *buf = NULL; size_t buflen = 0; if (list == NULL) { return; } info = s_calloc(1, sizeof (*info)); if (errno = nvlist_alloc(&(info->info), NV_UNIQUE_NAME, 0)) { rcm_log_message(RCM_ERROR, "failed (nvlist_alloc=%s).\n", strerror(errno)); rcmd_exit(errno); } /*LINTED*/ if ((errno = nvlist_add_string(info->info, RCM_RSRCNAME, alias)) || (errno = nvlist_add_int32(info->info, RCM_SEQ_NUM, seq_num)) || (errno = nvlist_add_int64(info->info, RCM_CLIENT_ID, pid)) || (errno = nvlist_add_int32(info->info, RCM_RSRCSTATE, state))) { rcm_log_message(RCM_ERROR, "failed (nvlist_add=%s).\n", strerror(errno)); rcmd_exit(errno); } /* * Daemon calls to add_busy_rsrc_to_list may pass in * error/info. Add these through librcm interfaces. */ if (errstr) { rcm_log_message(RCM_TRACE3, "adding error string: %s\n", errstr); if (errno = nvlist_add_string(info->info, RCM_CLIENT_ERROR, (char *)errstr)) { rcm_log_message(RCM_ERROR, "failed (nvlist_add=%s).\n", strerror(errno)); rcmd_exit(errno); } } if (infostr) { if (errno = nvlist_add_string(info->info, RCM_CLIENT_INFO, (char *)infostr)) { rcm_log_message(RCM_ERROR, "failed (nvlist_add=%s).\n", strerror(errno)); rcmd_exit(errno); } } if (modname) { if (errno = nvlist_add_string(info->info, RCM_CLIENT_MODNAME, modname)) { rcm_log_message(RCM_ERROR, "failed (nvlist_add=%s).\n", strerror(errno)); rcmd_exit(errno); } } if (client_props) { if (errno = nvlist_pack(client_props, &buf, &buflen, NV_ENCODE_NATIVE, 0)) { rcm_log_message(RCM_ERROR, "failed (nvlist_pack=%s).\n", strerror(errno)); rcmd_exit(errno); } if (errno = nvlist_add_byte_array(info->info, RCM_CLIENT_PROPERTIES, (uchar_t *)buf, buflen)) { rcm_log_message(RCM_ERROR, "failed (nvlist_add=%s).\n", strerror(errno)); rcmd_exit(errno); } (void) free(buf); } /* link info at end of list */ if (*list) { tmp = *list; while (tmp->next) tmp = tmp->next; tmp->next = info; } else { *list = info; } } /* * Resource client realted operations: * rsrc_client_alloc, rsrc_client_find, rsrc_client_add, * rsrc_client_remove, rsrc_client_action, rsrc_client_action_list */ /* Allocate rsrc_client_t structure. Load module if necessary. */ /*ARGSUSED*/ static client_t * rsrc_client_alloc(char *alias, char *modname, pid_t pid, uint_t flag) { client_t *client; module_t *mod; assert((alias != NULL) && (modname != NULL)); rcm_log_message(RCM_TRACE4, "rsrc_client_alloc(%s, %s, %ld)\n", alias, modname, pid); if ((mod = cli_module_hold(modname)) == NULL) { return (NULL); } client = s_calloc(1, sizeof (client_t)); client->module = mod; client->pid = pid; client->alias = s_strdup(alias); client->prv_flags = 0; client->state = RCM_STATE_ONLINE; client->flag = flag; /* This queue is protected by rcm_req_lock */ rcm_enqueue_tail(&mod->client_q, &client->queue); return (client); } /* Find client in list matching modname and pid */ client_t * rsrc_client_find(char *modname, pid_t pid, client_t **list) { client_t *client = *list; rcm_log_message(RCM_TRACE4, "rsrc_client_find(%s, %ld, %p)\n", modname, pid, (void *)list); while (client) { if ((client->pid == pid) && strcmp(modname, client->module->name) == 0) { break; } client = client->next; } return (client); } /* Add a client to client list */ static void rsrc_client_add(client_t *client, client_t **list) { rcm_log_message(RCM_TRACE4, "rsrc_client_add: %s, %s, %ld\n", client->alias, client->module->name, client->pid); client->next = *list; *list = client; } /* Remove client from list and destroy it */ static void rsrc_client_remove(client_t *client, client_t **list) { client_t *tmp, *prev = NULL; rcm_log_message(RCM_TRACE4, "rsrc_client_remove: %s, %s, %ld\n", client->alias, client->module->name, client->pid); tmp = *list; while (tmp) { if (client != tmp) { prev = tmp; tmp = tmp->next; continue; } if (prev) { prev->next = tmp->next; } else { *list = tmp->next; } tmp->next = NULL; rsrc_clients_free(tmp); return; } } /* Free a list of clients. Called from cleanup thread only */ static void rsrc_clients_free(client_t *list) { client_t *client = list; while (client) { /* * Note that the rcm daemon is single threaded while * executing this routine. So there is no need to acquire * rcm_req_lock here while dequeuing. */ rcm_dequeue(&client->queue); if (client->module) { cli_module_rele(client->module); } list = client->next; if (client->alias) { free(client->alias); } free(client); client = list; } } /* * Invoke a callback into a single client * This is the core of rcm_mod_ops interface */ static int rsrc_client_action(client_t *client, int cmd, void *arg) { int rval = RCM_SUCCESS; char *dummy_error = NULL; char *error = NULL; char *info = NULL; rcm_handle_t *hdl; nvlist_t *client_props = NULL; rcm_info_t *depend_info = NULL; struct rcm_mod_ops *ops = client->module->modops; tree_walk_arg_t *targ = (tree_walk_arg_t *)arg; rcm_log_message(RCM_TRACE4, "rsrc_client_action: %s, %s, cmd=%d, flag=0x%x\n", client->alias, client->module->name, cmd, targ->flag); /* * Create a per-operation handle, increment seq_num by 1 so we will * know if a module uses this handle to callback into rcm_daemon. */ hdl = rcm_handle_alloc(client->module); hdl->seq_num = targ->seq_num + 1; /* * Filter out operations for which the client didn't register. */ switch (cmd) { case CMD_SUSPEND: case CMD_RESUME: case CMD_OFFLINE: case CMD_ONLINE: case CMD_REMOVE: if ((client->flag & RCM_REGISTER_DR) == 0) { rcm_handle_free(hdl); return (RCM_SUCCESS); } break; case CMD_REQUEST_CHANGE: case CMD_NOTIFY_CHANGE: if ((client->flag & RCM_REGISTER_CAPACITY) == 0) { rcm_handle_free(hdl); return (RCM_SUCCESS); } break; case CMD_EVENT: if ((client->flag & RCM_REGISTER_EVENT) == 0) { rcm_handle_free(hdl); return (RCM_SUCCESS); } break; } /* * Create nvlist_t for any client-specific properties. */ if (errno = nvlist_alloc(&client_props, NV_UNIQUE_NAME, 0)) { rcm_log_message(RCM_ERROR, "client action failed (nvlist_alloc=%s)\n", strerror(errno)); rcmd_exit(errno); } /* * Process the operation via a callback to the client module. */ switch (cmd) { case CMD_GETINFO: rval = call_getinfo(ops, hdl, client->alias, client->pid, targ->flag, &info, &error, client_props, &depend_info); break; case CMD_SUSPEND: if (((targ->flag & RCM_QUERY_CANCEL) == 0) && (client->state == RCM_STATE_SUSPEND)) { break; } if ((targ->flag & RCM_QUERY) == 0) { rcm_log_message(RCM_DEBUG, "suspending %s\n", client->alias); } else if ((targ->flag & RCM_QUERY_CANCEL) == 0) { rcm_log_message(RCM_DEBUG, "suspend query %s\n", client->alias); } else { rcm_log_message(RCM_DEBUG, "suspend query %s cancelled\n", client->alias); } /* * Update the client's state before the operation. * If this is a cancelled query, then updating the state is * the only thing that needs to be done, so break afterwards. */ if ((targ->flag & RCM_QUERY) == 0) { client->state = RCM_STATE_SUSPENDING; } else if ((targ->flag & RCM_QUERY_CANCEL) == 0) { client->state = RCM_STATE_SUSPEND_QUERYING; } else { client->state = RCM_STATE_ONLINE; break; } rval = ops->rcmop_request_suspend(hdl, client->alias, client->pid, targ->interval, targ->flag, &error, &depend_info); /* Update the client's state after the operation. */ if ((targ->flag & RCM_QUERY) == 0) { if (rval == RCM_SUCCESS) { client->state = RCM_STATE_SUSPEND; } else { client->state = RCM_STATE_SUSPEND_FAIL; } } else { if (rval == RCM_SUCCESS) { client->state = RCM_STATE_SUSPEND_QUERY; } else { client->state = RCM_STATE_SUSPEND_QUERY_FAIL; } } break; case CMD_RESUME: if (client->state == RCM_STATE_ONLINE) { break; } client->state = RCM_STATE_RESUMING; rval = ops->rcmop_notify_resume(hdl, client->alias, client->pid, targ->flag, &error, &depend_info); /* online state is unconditional */ client->state = RCM_STATE_ONLINE; break; case CMD_OFFLINE: if (((targ->flag & RCM_QUERY_CANCEL) == 0) && (client->state == RCM_STATE_OFFLINE)) { break; } if ((targ->flag & RCM_QUERY) == 0) { rcm_log_message(RCM_DEBUG, "offlining %s\n", client->alias); } else if ((targ->flag & RCM_QUERY_CANCEL) == 0) { rcm_log_message(RCM_DEBUG, "offline query %s\n", client->alias); } else { rcm_log_message(RCM_DEBUG, "offline query %s cancelled\n", client->alias); } /* * Update the client's state before the operation. * If this is a cancelled query, then updating the state is * the only thing that needs to be done, so break afterwards. */ if ((targ->flag & RCM_QUERY) == 0) { client->state = RCM_STATE_OFFLINING; } else if ((targ->flag & RCM_QUERY_CANCEL) == 0) { client->state = RCM_STATE_OFFLINE_QUERYING; } else { client->state = RCM_STATE_ONLINE; break; } rval = ops->rcmop_request_offline(hdl, client->alias, client->pid, targ->flag, &error, &depend_info); /* * If this is a retire operation and we managed to call * into at least one client, set retcode to RCM_SUCCESS to * indicate that retire has been subject to constraints * This retcode will be further modified by actual return * code. */ if ((targ->flag & RCM_RETIRE_REQUEST) && (targ->retcode == RCM_NO_CONSTRAINT)) { rcm_log_message(RCM_DEBUG, "at least 1 client, constraint applied: %s\n", client->alias); targ->retcode = RCM_SUCCESS; } /* Update the client's state after the operation. */ if ((targ->flag & RCM_QUERY) == 0) { if (rval == RCM_SUCCESS) { client->state = RCM_STATE_OFFLINE; } else { client->state = RCM_STATE_OFFLINE_FAIL; } } else { if (rval == RCM_SUCCESS) { client->state = RCM_STATE_OFFLINE_QUERY; } else { client->state = RCM_STATE_OFFLINE_QUERY_FAIL; } } break; case CMD_ONLINE: if (client->state == RCM_STATE_ONLINE) { break; } rcm_log_message(RCM_DEBUG, "onlining %s\n", client->alias); client->state = RCM_STATE_ONLINING; rval = ops->rcmop_notify_online(hdl, client->alias, client->pid, targ->flag, &error, &depend_info); client->state = RCM_STATE_ONLINE; break; case CMD_REMOVE: rcm_log_message(RCM_DEBUG, "removing %s\n", client->alias); client->state = RCM_STATE_REMOVING; rval = ops->rcmop_notify_remove(hdl, client->alias, client->pid, targ->flag, &error, &depend_info); client->state = RCM_STATE_REMOVE; break; case CMD_REQUEST_CHANGE: rcm_log_message(RCM_DEBUG, "requesting state change of %s\n", client->alias); if (ops->rcmop_request_capacity_change) rval = ops->rcmop_request_capacity_change(hdl, client->alias, client->pid, targ->flag, targ->nvl, &error, &depend_info); break; case CMD_NOTIFY_CHANGE: rcm_log_message(RCM_DEBUG, "requesting state change of %s\n", client->alias); if (ops->rcmop_notify_capacity_change) rval = ops->rcmop_notify_capacity_change(hdl, client->alias, client->pid, targ->flag, targ->nvl, &error, &depend_info); break; case CMD_EVENT: rcm_log_message(RCM_DEBUG, "delivering event to %s\n", client->alias); if (ops->rcmop_notify_event) rval = ops->rcmop_notify_event(hdl, client->alias, client->pid, targ->flag, &error, targ->nvl, &depend_info); break; default: rcm_log_message(RCM_ERROR, gettext("unknown command %d\n"), cmd); rval = RCM_FAILURE; break; } /* reset error code to the most significant error */ if (rval != RCM_SUCCESS) targ->retcode = rval; /* * XXX - The code below may produce duplicate rcm_info_t's on error? */ if ((cmd != CMD_GETINFO) && ((rval != RCM_SUCCESS) || (error != NULL) || (targ->flag & RCM_SCOPE))) { (void) call_getinfo(ops, hdl, client->alias, client->pid, targ->flag & (~(RCM_INCLUDE_DEPENDENT|RCM_INCLUDE_SUBTREE)), &info, &dummy_error, client_props, &depend_info); if (dummy_error) (void) free(dummy_error); } else if (cmd != CMD_GETINFO) { nvlist_free(client_props); client_props = NULL; } if (client_props) { add_busy_rsrc_to_list(client->alias, client->pid, client->state, targ->seq_num, client->module->name, info, error, client_props, targ->info); nvlist_free(client_props); } if (info) (void) free(info); if (error) (void) free(error); if (depend_info) { if (targ->info) { (void) rcm_append_info(targ->info, depend_info); } else { rcm_free_info(depend_info); } } rcm_handle_free(hdl); return (rval); } /* * invoke a callback into a list of clients, return 0 if all success */ int rsrc_client_action_list(client_t *list, int cmd, void *arg) { int error, rval = RCM_SUCCESS; tree_walk_arg_t *targ = (tree_walk_arg_t *)arg; while (list) { client_t *client = list; list = client->next; /* * Make offline idempotent in the retire * case */ if ((targ->flag & RCM_RETIRE_REQUEST) && client->state == RCM_STATE_REMOVE) { client->state = RCM_STATE_ONLINE; rcm_log_message(RCM_DEBUG, "RETIRE: idempotent client " "state: REMOVE -> ONLINE: %s\n", client->alias); } if (client->state == RCM_STATE_REMOVE) continue; error = rsrc_client_action(client, cmd, arg); if (error != RCM_SUCCESS) { rval = error; } } return (rval); } /* * Node realted operations: * * rn_alloc, rn_free, rn_find_child, * rn_get_child, rn_get_sibling, * rsrc_node_find, rsrc_node_add_user, rsrc_node_remove_user, */ /* Allocate node based on a logical or physical name */ static rsrc_node_t * rn_alloc(char *name, int type) { rsrc_node_t *node; rcm_log_message(RCM_TRACE4, "rn_alloc(%s, %d)\n", name, type); node = s_calloc(1, sizeof (*node)); node->name = s_strdup(name); node->type = type; return (node); } /* * Free node along with its siblings and children */ static void rn_free(rsrc_node_t *node) { if (node == NULL) { return; } if (node->child) { rn_free(node->child); } if (node->sibling) { rn_free(node->sibling); } rsrc_clients_free(node->users); free(node->name); free(node); } /* * Find next sibling */ static rsrc_node_t * rn_get_sibling(rsrc_node_t *node) { return (node->sibling); } /* * Find first child */ static rsrc_node_t * rn_get_child(rsrc_node_t *node) { return (node->child); } /* * Find child named childname. Create it if flag is RSRC_NODE_CRTEATE */ static rsrc_node_t * rn_find_child(rsrc_node_t *parent, char *childname, int flag, int type) { rsrc_node_t *child = parent->child; rsrc_node_t *new, *prev = NULL; rcm_log_message(RCM_TRACE4, "rn_find_child(parent=%s, child=%s, 0x%x, %d)\n", parent->name, childname, flag, type); /* * Children are ordered based on strcmp. */ while (child && (strcmp(child->name, childname) < 0)) { prev = child; child = child->sibling; } if (child && (strcmp(child->name, childname) == 0)) { return (child); } if (flag != RSRC_NODE_CREATE) return (NULL); new = rn_alloc(childname, type); new->parent = parent; new->sibling = child; /* * Set this linkage last so we don't break ongoing operations. * * N.B. Assume setting a pointer is an atomic operation. */ if (prev == NULL) { parent->child = new; } else { prev->sibling = new; } return (new); } /* * Pathname related help functions */ static void pn_preprocess(char *pathname, int type) { char *tmp; if (type != RSRC_TYPE_DEVICE) return; /* * For devices, convert ':' to '/' (treat minor nodes and children) */ tmp = strchr(pathname, ':'); if (tmp == NULL) return; *tmp = '/'; } static char * pn_getnextcomp(char *pathname, char **lasts) { char *slash; if (pathname == NULL) return (NULL); /* skip slashes' */ while (*pathname == '/') ++pathname; if (*pathname == '\0') return (NULL); slash = strchr(pathname, '/'); if (slash != NULL) { *slash = '\0'; *lasts = slash + 1; } else { *lasts = NULL; } return (pathname); } /* * Find a node in tree based on device, which is the physical pathname * of the form /sbus@.../esp@.../sd@... */ int rsrc_node_find(char *rsrcname, int flag, rsrc_node_t **nodep) { char *pathname, *nodename, *lasts; rsrc_node_t *node; int type; rcm_log_message(RCM_TRACE4, "rn_node_find(%s, 0x%x)\n", rsrcname, flag); /* * For RSRC_TYPE_ABSTRACT, look under /ABSTRACT. For other types, * look under /SYSTEM. */ pathname = resolve_name(rsrcname); if (pathname == NULL) return (EINVAL); type = rsrc_get_type(pathname); switch (type) { case RSRC_TYPE_DEVICE: case RSRC_TYPE_NORMAL: node = rn_find_child(rsrc_root, "SYSTEM", RSRC_NODE_CREATE, RSRC_TYPE_NORMAL); break; case RSRC_TYPE_ABSTRACT: node = rn_find_child(rsrc_root, "ABSTRACT", RSRC_NODE_CREATE, RSRC_TYPE_NORMAL); break; default: /* just to make sure */ free(pathname); return (EINVAL); } /* * Find position of device within tree. Upon exiting the loop, device * should be placed between prev and curr. */ pn_preprocess(pathname, type); lasts = pathname; while ((nodename = pn_getnextcomp(lasts, &lasts)) != NULL) { rsrc_node_t *parent = node; node = rn_find_child(parent, nodename, flag, type); if (node == NULL) { assert((flag & RSRC_NODE_CREATE) == 0); free(pathname); *nodep = NULL; return (RCM_SUCCESS); } } free(pathname); *nodep = node; return (RCM_SUCCESS); } /* * add a usage client to a node */ /*ARGSUSED*/ int rsrc_node_add_user(rsrc_node_t *node, char *alias, char *modname, pid_t pid, uint_t flag) { client_t *user; rcm_log_message(RCM_TRACE3, "rsrc_node_add_user(%s, %s, %s, %ld, 0x%x)\n", node->name, alias, modname, pid, flag); user = rsrc_client_find(modname, pid, &node->users); /* * If a client_t already exists, add the registration and return * success if it's a valid registration request. * * Return EALREADY if the resource is already registered. * This means either the client_t already has the requested * registration flagged, or that a DR registration was attempted * on a resource already in use in the DR operations state model. */ if (user != NULL) { if (user->flag & (flag & RCM_REGISTER_MASK)) { return (EALREADY); } if ((flag & RCM_REGISTER_DR) && (user->state != RCM_STATE_REMOVE)) { return (EALREADY); } user->flag |= (flag & RCM_REGISTER_MASK); if ((flag & RCM_REGISTER_DR) || (user->state == RCM_STATE_REMOVE)) { user->state = RCM_STATE_ONLINE; } return (RCM_SUCCESS); } /* * Otherwise create a new client_t and create a new registration. */ if ((user = rsrc_client_alloc(alias, modname, pid, flag)) != NULL) { rsrc_client_add(user, &node->users); } if (flag & RCM_FILESYS) node->type = RSRC_TYPE_FILESYS; return (RCM_SUCCESS); } /* * remove a usage client of a node */ int rsrc_node_remove_user(rsrc_node_t *node, char *modname, pid_t pid, uint_t flag) { client_t *user; rcm_log_message(RCM_TRACE3, "rsrc_node_remove_user(%s, %s, %ld, 0x%x)\n", node->name, modname, pid, flag); user = rsrc_client_find(modname, pid, &node->users); if ((user == NULL) || (user->state == RCM_STATE_REMOVE)) { rcm_log_message(RCM_NOTICE, gettext( "client not registered: module=%s, pid=%d, dev=%s\n"), modname, pid, node->name); return (ENOENT); } /* Strip off the registration being removed (DR, event, capacity) */ user->flag = user->flag & (~(flag & RCM_REGISTER_MASK)); /* * Mark the client as removed if all registrations have been removed */ if ((user->flag & RCM_REGISTER_MASK) == 0) user->state = RCM_STATE_REMOVE; return (RCM_SUCCESS); } /* * Tree walking function - rsrc_walk */ #define MAX_TREE_DEPTH 32 #define RN_WALK_CONTINUE 0 #define RN_WALK_PRUNESIB 1 #define RN_WALK_PRUNECHILD 2 #define RN_WALK_TERMINATE 3 #define EMPTY_STACK(sp) ((sp)->depth == 0) #define TOP_NODE(sp) ((sp)->node[(sp)->depth - 1]) #define PRUNE_SIB(sp) ((sp)->prunesib[(sp)->depth - 1]) #define PRUNE_CHILD(sp) ((sp)->prunechild[(sp)->depth - 1]) #define POP_STACK(sp) ((sp)->depth)-- #define PUSH_STACK(sp, rn) \ (sp)->node[(sp)->depth] = (rn); \ (sp)->prunesib[(sp)->depth] = 0; \ (sp)->prunechild[(sp)->depth] = 0; \ ((sp)->depth)++ struct rn_stack { rsrc_node_t *node[MAX_TREE_DEPTH]; char prunesib[MAX_TREE_DEPTH]; char prunechild[MAX_TREE_DEPTH]; int depth; }; /* walking one node and update node stack */ /*ARGSUSED*/ static void walk_one_node(struct rn_stack *sp, void *arg, int (*node_callback)(rsrc_node_t *, void *)) { int prunesib; rsrc_node_t *child, *sibling; rsrc_node_t *node = TOP_NODE(sp); rcm_log_message(RCM_TRACE4, "walk_one_node(%s)\n", node->name); switch (node_callback(node, arg)) { case RN_WALK_TERMINATE: POP_STACK(sp); while (!EMPTY_STACK(sp)) { node = TOP_NODE(sp); POP_STACK(sp); } return; case RN_WALK_PRUNESIB: PRUNE_SIB(sp) = 1; break; case RN_WALK_PRUNECHILD: PRUNE_CHILD(sp) = 1; break; case RN_WALK_CONTINUE: default: break; } /* * Push child on the stack */ if (!PRUNE_CHILD(sp) && (child = rn_get_child(node)) != NULL) { PUSH_STACK(sp, child); return; } /* * Pop the stack till a node's sibling can be pushed */ prunesib = PRUNE_SIB(sp); POP_STACK(sp); while (!EMPTY_STACK(sp) && (prunesib || (sibling = rn_get_sibling(node)) == NULL)) { node = TOP_NODE(sp); prunesib = PRUNE_SIB(sp); POP_STACK(sp); } if (EMPTY_STACK(sp)) { return; } /* * push sibling onto the stack */ PUSH_STACK(sp, sibling); } /* * walk tree rooted at root in child-first order */ static void rsrc_walk(rsrc_node_t *root, void *arg, int (*node_callback)(rsrc_node_t *, void *)) { struct rn_stack stack; rcm_log_message(RCM_TRACE3, "rsrc_walk(%s)\n", root->name); /* * Push root on stack and walk in child-first order */ stack.depth = 0; PUSH_STACK(&stack, root); PRUNE_SIB(&stack) = 1; while (!EMPTY_STACK(&stack)) { walk_one_node(&stack, arg, node_callback); } } /* * Callback for a command action on a node */ static int node_action(rsrc_node_t *node, void *arg) { tree_walk_arg_t *targ = (tree_walk_arg_t *)arg; uint_t flag = targ->flag; rcm_log_message(RCM_TRACE4, "node_action(%s)\n", node->name); /* * If flag indicates operation on a filesystem, we don't callback on * the filesystem root to avoid infinite recursion on filesystem module. * * N.B. Such request should only come from filesystem RCM module. */ if (flag & RCM_FILESYS) { assert(node->type == RSRC_TYPE_FILESYS); targ->flag &= ~RCM_FILESYS; return (RN_WALK_CONTINUE); } /* * Execute state change callback */ (void) rsrc_client_action_list(node->users, targ->cmd, arg); /* * Upon hitting a filesys root, prune children. * The filesys module should have taken care of * children by now. */ if (node->type == RSRC_TYPE_FILESYS) return (RN_WALK_PRUNECHILD); return (RN_WALK_CONTINUE); } /* * Execute a command on a subtree under root. */ int rsrc_tree_action(rsrc_node_t *root, int cmd, tree_walk_arg_t *arg) { rcm_log_message(RCM_TRACE2, "tree_action(%s, %d)\n", root->name, cmd); arg->cmd = cmd; /* * If RCM_RETIRE_REQUEST is set, just walk one node and preset * retcode to NO_CONSTRAINT */ if (arg->flag & RCM_RETIRE_REQUEST) { rcm_log_message(RCM_TRACE1, "tree_action: RETIRE_REQ: walking " "only root node: %s\n", root->name); arg->retcode = RCM_NO_CONSTRAINT; (void) node_action(root, arg); } else { arg->retcode = RCM_SUCCESS; rsrc_walk(root, (void *)arg, node_action); } return (arg->retcode); } /* * Get info on current regsitrations */ int rsrc_usage_info(char **rsrcnames, uint_t flag, int seq_num, rcm_info_t **info) { rsrc_node_t *node; rcm_info_t *result = NULL; tree_walk_arg_t arg; int initial_req; int rv; int i; arg.flag = flag; arg.info = &result; arg.seq_num = seq_num; for (i = 0; rsrcnames[i] != NULL; i++) { rcm_log_message(RCM_TRACE2, "rsrc_usage_info(%s, 0x%x, %d)\n", rsrcnames[i], flag, seq_num); if (flag & RCM_INCLUDE_DEPENDENT) { initial_req = ((seq_num & SEQ_NUM_MASK) == 0); /* * if redundant request, skip the operation */ if (info_req_add(rsrcnames[i], flag, seq_num) != 0) { continue; } } rv = rsrc_node_find(rsrcnames[i], 0, &node); if ((rv != RCM_SUCCESS) || (node == NULL)) { if ((flag & RCM_INCLUDE_DEPENDENT) && initial_req) info_req_remove(seq_num); continue; } /* * Based on RCM_INCLUDE_SUBTREE flag, query either the subtree * or just the node. */ if (flag & RCM_INCLUDE_SUBTREE) { (void) rsrc_tree_action(node, CMD_GETINFO, &arg); } else { arg.cmd = CMD_GETINFO; (void) node_action(node, (void *)&arg); } if ((flag & RCM_INCLUDE_DEPENDENT) && initial_req) info_req_remove(seq_num); } out: (void) rcm_append_info(info, result); return (rv); } /* * Get the list of currently loaded module */ rcm_info_t * rsrc_mod_info() { module_t *mod; rcm_info_t *info = NULL; (void) mutex_lock(&mod_lock); mod = module_head; while (mod) { char *modinfo = s_strdup(module_info(mod)); add_busy_rsrc_to_list("dummy", 0, 0, 0, mod->name, modinfo, NULL, NULL, &info); mod = mod->next; } (void) mutex_unlock(&mod_lock); return (info); } /* * Initialize resource map - load all modules */ void rcmd_db_init() { char *tmp; DIR *mod_dir; struct dirent *entp; int i; char *dir_name; int rcm_script; rcm_log_message(RCM_DEBUG, "rcmd_db_init(): initialize database\n"); if (script_main_init() == -1) rcmd_exit(errno); rsrc_root = rn_alloc("/", RSRC_TYPE_NORMAL); for (i = 0; (dir_name = rcm_dir(i, &rcm_script)) != NULL; i++) { if ((mod_dir = opendir(dir_name)) == NULL) { continue; /* try next directory */ } rcm_log_message(RCM_TRACE2, "search directory %s\n", dir_name); while ((entp = readdir(mod_dir)) != NULL) { module_t *module; if (strcmp(entp->d_name, ".") == 0 || strcmp(entp->d_name, "..") == 0) continue; if (rcm_script == 0) { /* rcm module */ if (((tmp = strstr(entp->d_name, RCM_MODULE_SUFFIX)) == NULL) || (tmp[strlen(RCM_MODULE_SUFFIX)] != '\0')) { continue; } } module = cli_module_hold(entp->d_name); if (module == NULL) { if (rcm_script == 0) rcm_log_message(RCM_ERROR, gettext("%s: failed to load\n"), entp->d_name); continue; } if (module->ref_count == MOD_REFCNT_INIT) { /* * ask module to register for resource 1st time */ module_attach(module); } cli_module_rele(module); } (void) closedir(mod_dir); } rcmd_db_print(); } /* * sync resource map - ask all modules to register again */ void rcmd_db_sync() { static time_t sync_time = (time_t)-1; const time_t interval = 5; /* resync at most every 5 sec */ module_t *mod; time_t curr = time(NULL); if ((sync_time != (time_t)-1) && (curr - sync_time < interval)) return; sync_time = curr; (void) mutex_lock(&mod_lock); mod = module_head; while (mod) { /* * Hold module by incrementing ref count and release * mod_lock to avoid deadlock, since rcmop_register() * may callback into the daemon and request mod_lock. */ mod->ref_count++; (void) mutex_unlock(&mod_lock); mod->modops->rcmop_register(mod->rcmhandle); (void) mutex_lock(&mod_lock); mod->ref_count--; mod = mod->next; } (void) mutex_unlock(&mod_lock); } /* * Determine if a process is alive */ int proc_exist(pid_t pid) { char path[64]; const char *procfs = "/proc"; struct stat sb; if (pid == (pid_t)0) { return (1); } (void) snprintf(path, sizeof (path), "%s/%ld", procfs, pid); return (stat(path, &sb) == 0); } /* * Cleaup client list * * N.B. This routine runs in a single-threaded environment only. It is only * called by the cleanup thread, which never runs in parallel with other * threads. */ static void clean_client_list(client_t **listp) { client_t *client = *listp; /* * Cleanup notification clients for which pid no longer exists */ while (client) { if ((client->state != RCM_STATE_REMOVE) && proc_exist(client->pid)) { listp = &client->next; client = *listp; continue; } /* * Destroy this client_t. rsrc_client_remove updates * listp to point to the next client. */ rsrc_client_remove(client, listp); client = *listp; } } /*ARGSUSED*/ static int clean_node(rsrc_node_t *node, void *arg) { rcm_log_message(RCM_TRACE4, "clean_node(%s)\n", node->name); clean_client_list(&node->users); return (RN_WALK_CONTINUE); } static void clean_rsrc_tree() { rcm_log_message(RCM_TRACE4, "clean_rsrc_tree(): delete stale dr clients\n"); rsrc_walk(rsrc_root, NULL, clean_node); } static void * db_clean(void *arg __unused) { extern barrier_t barrier; extern void clean_dr_list(); for (;;) { (void) mutex_lock(&rcm_req_lock); start_polling_thread(); (void) mutex_unlock(&rcm_req_lock); (void) mutex_lock(&barrier.lock); while (need_cleanup == 0) (void) cond_wait(&barrier.cv, &barrier.lock); (void) mutex_unlock(&barrier.lock); /* * Make sure all other threads are either blocked or exited. */ rcmd_set_state(RCMD_CLEANUP); need_cleanup = 0; /* * clean dr_req_list */ clean_dr_list(); /* * clean resource tree */ clean_rsrc_tree(); rcmd_set_state(RCMD_NORMAL); } return (NULL); } void rcmd_db_clean(void) { rcm_log_message(RCM_DEBUG, "rcm_db_clean(): launch thread to clean database\n"); if (thr_create(NULL, 0, db_clean, NULL, THR_DETACHED, NULL) != 0) { rcm_log_message(RCM_WARNING, gettext("failed to create cleanup thread %s\n"), strerror(errno)); } } /*ARGSUSED*/ static int print_node(rsrc_node_t *node, void *arg) { client_t *user; rcm_log_message(RCM_DEBUG, "rscname: %s, state = 0x%x\n", node->name); rcm_log_message(RCM_DEBUG, " users:\n"); if ((user = node->users) == NULL) { rcm_log_message(RCM_DEBUG, " none\n"); return (RN_WALK_CONTINUE); } while (user) { rcm_log_message(RCM_DEBUG, " %s, %d, %s\n", user->module->name, user->pid, user->alias); user = user->next; } return (RN_WALK_CONTINUE); } static void rcmd_db_print() { module_t *mod; rcm_log_message(RCM_DEBUG, "modules:\n"); (void) mutex_lock(&mod_lock); mod = module_head; while (mod) { rcm_log_message(RCM_DEBUG, " %s\n", mod->name); mod = mod->next; } (void) mutex_unlock(&mod_lock); rcm_log_message(RCM_DEBUG, "\nresource tree:\n"); rsrc_walk(rsrc_root, NULL, print_node); rcm_log_message(RCM_DEBUG, "\n"); } /* * Allocate handle from calling into each RCM module */ static rcm_handle_t * rcm_handle_alloc(module_t *module) { rcm_handle_t *hdl; hdl = s_malloc(sizeof (rcm_handle_t)); hdl->modname = module->name; hdl->pid = 0; hdl->lrcm_ops = &rcm_ops; /* for callback into daemon directly */ hdl->module = module; return (hdl); } /* * Free rcm_handle_t */ static void rcm_handle_free(rcm_handle_t *handle) { free(handle); } /* * help function that exit on memory outage */ void * s_malloc(size_t size) { void *buf = malloc(size); if (buf == NULL) { rcmd_exit(ENOMEM); } return (buf); } void * s_calloc(int n, size_t size) { void *buf = calloc(n, size); if (buf == NULL) { rcmd_exit(ENOMEM); } return (buf); } void * s_realloc(void *ptr, size_t size) { void *new = realloc(ptr, size); if (new == NULL) { rcmd_exit(ENOMEM); } return (new); } char * s_strdup(const char *str) { char *buf = strdup(str); if (buf == NULL) { rcmd_exit(ENOMEM); } return (buf); } /* * Convert a version 1 ops vector to current ops vector * Fields missing in version 1 are set to NULL. */ static struct rcm_mod_ops * modops_from_v1(void *ops_v1) { struct rcm_mod_ops *ops; ops = s_calloc(1, sizeof (struct rcm_mod_ops)); bcopy(ops_v1, ops, sizeof (struct rcm_mod_ops_v1)); return (ops); } /* call a module's getinfo routine; detects v1 ops and adjusts the call */ static int call_getinfo(struct rcm_mod_ops *ops, rcm_handle_t *hdl, char *alias, id_t pid, uint_t flag, char **info, char **error, nvlist_t *client_props, rcm_info_t **infop) { int rval; struct rcm_mod_ops_v1 *v1_ops; if (ops->version == RCM_MOD_OPS_V1) { v1_ops = (struct rcm_mod_ops_v1 *)ops; rval = v1_ops->rcmop_get_info(hdl, alias, pid, flag, info, infop); if (rval != RCM_SUCCESS && *info != NULL) *error = strdup(*info); return (rval); } else { return (ops->rcmop_get_info(hdl, alias, pid, flag, info, error, client_props, infop)); } } void rcm_init_queue(rcm_queue_t *head) { head->next = head->prev = head; } void rcm_enqueue_head(rcm_queue_t *head, rcm_queue_t *element) { rcm_enqueue(head, element); } void rcm_enqueue_tail(rcm_queue_t *head, rcm_queue_t *element) { rcm_enqueue(head->prev, element); } void rcm_enqueue(rcm_queue_t *list_element, rcm_queue_t *element) { element->next = list_element->next; element->prev = list_element; element->next->prev = element; list_element->next = element; } rcm_queue_t * rcm_dequeue_head(rcm_queue_t *head) { rcm_queue_t *element = head->next; rcm_dequeue(element); return (element); } rcm_queue_t * rcm_dequeue_tail(rcm_queue_t *head) { rcm_queue_t *element = head->prev; rcm_dequeue(element); return (element); } void rcm_dequeue(rcm_queue_t *element) { element->prev->next = element->next; element->next->prev = element->prev; element->next = element->prev = NULL; }