diff options
author | stevel@tonic-gate <none@none> | 2005-06-14 00:00:00 -0700 |
---|---|---|
committer | stevel@tonic-gate <none@none> | 2005-06-14 00:00:00 -0700 |
commit | 7c478bd95313f5f23a4c958a745db2134aa03244 (patch) | |
tree | c871e58545497667cbb4b0a4f2daf204743e1fe7 /usr/src/cmd/rcm_daemon/common/rcm_script.c | |
download | illumos-joyent-7c478bd95313f5f23a4c958a745db2134aa03244.tar.gz |
OpenSolaris Launch
Diffstat (limited to 'usr/src/cmd/rcm_daemon/common/rcm_script.c')
-rw-r--r-- | usr/src/cmd/rcm_daemon/common/rcm_script.c | 2629 |
1 files changed, 2629 insertions, 0 deletions
diff --git a/usr/src/cmd/rcm_daemon/common/rcm_script.c b/usr/src/cmd/rcm_daemon/common/rcm_script.c new file mode 100644 index 0000000000..094bc6288e --- /dev/null +++ b/usr/src/cmd/rcm_daemon/common/rcm_script.c @@ -0,0 +1,2629 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License, Version 1.0 only + * (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 2004 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * rcm scripting module: + * + * This module implements rcm scripting interfaces. + * It translates rcm module based interfaces to rcm script based + * interfaces. + * + * Entry points: + * + * int script_main_init() + * Initialize the rcm scripting framework. + * Called during the rcm daemon initialization + * + * int script_main_fini() + * Called at the time of the rcm daemon exit. + * + * struct rcm_mod_ops *script_init(module_t *module) + * Initialize the given script. + * module->name contains the name of the script. + * Called at the time of loading scripts. + * Semantics are similar to module init. + * + * char *script_info(module_t *module) + * Called when the rcm daemon wishes to get the script information. + * module->name contains the name of the script. + * Semantics are similar to module info. + * + * int script_fini(module_t *module) + * Called before removing the script. + * module->name contains the name of the script. + * Semantics are similar to module fini. + * + * In addition to the above entry points rcm_mod_ops structure contains + * the other entry points. A pointer to this structure is returned when + * script_init() is called. + */ + +#include "rcm_impl.h" +#include "rcm_script_impl.h" +#include <sys/resource.h> +#include <procfs.h> +#include <sys/proc.h> +#include <ctype.h> + +/* + * All rcm scripting commands are enumerated here. + * NOTE: command positions in script_cmd_id_t and script_cmd_name must match. + */ +typedef enum { + C_SCRIPTINFO, + C_RESOURCEINFO, + C_REGISTER, + C_QUERYREMOVE, + C_PREREMOVE, + C_POSTREMOVE, + C_UNDOREMOVE, + C_QUERYCAPACITY, + C_PRECAPACITY, + C_POSTCAPACITY, + C_QUERYSUSPEND, + C_PRESUSPEND, + C_POSTRESUME, + C_CANCELSUSPEND +} script_cmd_id_t; + +/* NOTE: command positions in script_cmd_id_t and script_cmd_name must match */ +static char *script_cmd_name[] = { + "scriptinfo", + "resourceinfo", + "register", + "queryremove", + "preremove", + "postremove", + "undoremove", + "querycapacity", + "precapacity", + "postcapacity", + "querysuspend", + "presuspend", + "postresume", + "cancelsuspend", + NULL +}; + +/* + * All rcm scripting data items are enumerated here. + * NOTE: data item positions in script_data_item_id_t and + * script_data_item_name must match. + */ +typedef enum { + D_SCRIPT_VERSION, + D_SCRIPT_FUNC_INFO, + D_CMD_TIMEOUT, + D_RESOURCE_NAME, + D_RESOURCE_USAGE_INFO, + D_FAILURE_REASON, + D_LOG_ERR, + D_LOG_WARN, + D_LOG_INFO, + D_LOG_DEBUG +} script_data_item_id_t; + +/* + * NOTE: data item positions in script_data_item_id_t and + * script_data_item_name must match. + */ +static const char *script_data_item_name[] = { + "rcm_script_version", + "rcm_script_func_info", + "rcm_cmd_timeout", + "rcm_resource_name", + "rcm_resource_usage_info", + "rcm_failure_reason", + "rcm_log_err", + "rcm_log_warn", + "rcm_log_info", + "rcm_log_debug", + NULL +}; + +/* + * Maximum number of rcm scripts that can run in parallel. + * RCM daemon has no limit on the number of scripts supported. But + * at most it runs script_max_parallelism number of scripts in parallel. + * For each running script rcm daemon consumes two file descriptors + * in order to communicate with the script via pipes. + * So maximum number of file descriptor entries consumed by rcm daemon + * on behalf of rcm scripts is "script_max_parallelism * 2" + */ +static const int script_max_parallelism = 64; + +/* + * semaphore to limit the number of rcm script processes running in + * parallel to script_max_parallelism. + */ +static sema_t script_process_sema; + +/* mutex to protect the any global data */ +static mutex_t script_lock; + +/* contains head to a queue of script_info structures */ +static rcm_queue_t script_info_q; + +/* + * This mmapped state file is used to store the process id and + * rcm script name of all currently running rcm scripts. + */ +static const char *script_ps_state_file = "/var/run/rcm_script_state"; +static state_file_descr_t script_ps_statefd; + +static char *script_env_noforce = "RCM_ENV_FORCE=FALSE"; +static char *script_env_force = "RCM_ENV_FORCE=TRUE"; +static char *script_env_interval = "RCM_ENV_INTERVAL=%ld"; + +#define RSCR_TRACE RCM_TRACE1 + +/* rcm script base environment */ +static char *script_env[MAX_ENV_PARAMS]; + +struct rlimit file_limit; + +/* function prototypes */ +static void build_env(void); +static void copy_env(char *[], char *[]); +static void open_state_file(const char *, state_file_descr_t *, size_t, int, + uint32_t); +static void truncate_state_file(state_file_descr_t *); +static void close_state_file(const char *, state_file_descr_t *); +static void grow_state_file(state_file_descr_t *); +static void *get_state_element(state_file_descr_t *, int, int *); +static void *allocate_state_element(state_file_descr_t *, int *); +static void free_state_element(void *); +static void script_ps_state_file_kill_pids(void); +static void script_ps_state_file_add_entry(pid_t, char *); +static void script_ps_state_file_remove_entry(pid_t); +static int dname_to_id(char *); +static void script_process_sema_wait(void); +static int run_script(script_info_t *, char *[], char *[], char **); +static int get_line(int fd, char *, char *, int, size_t *, time_t, int *); +static void script_exited(script_info_t *); +static int kill_pid(pid_t); +static void kill_script(script_info_t *); +static char *flags_to_name(int, char *, int); +static void fill_argv(script_info_t *, char *[], char *); +static void *read_stderr(script_info_t *); +static int process_dataitem(script_info_t *, int, char *, char **); +static int do_cmd(script_info_t *, char *[], char *[], char **); +static int do_script_info(script_info_t *); +static int do_dr(script_info_t *, char *[], char *[], char **); +static int script_get_info(rcm_handle_t *, char *, pid_t, uint_t, char **, + char **, nvlist_t *, rcm_info_t **); +static void add_for_unregister(script_info_t *); +static void remove_from_unregister(script_info_t *, char *); +static void complete_unregister(script_info_t *); +static int script_register_interest(rcm_handle_t *); +static void add_drreq(script_info_t *, char *); +static void remove_drreq(script_info_t *, char *); +static void remove_drreq_all(script_info_t *); +static int script_request_offline(rcm_handle_t *, char *, pid_t, uint_t, + char **, rcm_info_t **); +static int script_notify_online(rcm_handle_t *, char *, pid_t, uint_t, + char **, rcm_info_t **); +static int script_notify_remove(rcm_handle_t *, char *, pid_t, uint_t, + char **, rcm_info_t **); +static int script_request_suspend(rcm_handle_t *, char *, pid_t, timespec_t *, + uint_t, char **, rcm_info_t **); +static int script_notify_resume(rcm_handle_t *, char *, pid_t, uint_t, + char **, rcm_info_t **); +static capacity_descr_t *get_capacity_descr(char *); +static int build_env_for_capacity(script_info_t *, char *, uint_t, nvlist_t *, + char *[], int *, char **); +static int script_request_capacity_change(rcm_handle_t *, char *, pid_t, + uint_t, nvlist_t *, char **, rcm_info_t **); +static int script_notify_capacity_change(rcm_handle_t *, char *, pid_t, + uint_t, nvlist_t *, char **, rcm_info_t **); +static void log_msg(script_info_t *, int, char *); +static char *dup_err(int, char *, ...); +static void rcmscript_snprintf(char **, int *, char **, char *, ...); +static char *rcmscript_strdup(char *); +static void *rcmscript_malloc(size_t); +static void *rcmscript_calloc(size_t, size_t); + + +static struct rcm_mod_ops script_ops = +{ + RCM_MOD_OPS_VERSION, + script_register_interest, /* register */ + script_register_interest, /* unregister */ + script_get_info, + script_request_suspend, + script_notify_resume, + script_request_offline, + script_notify_online, + script_notify_remove, + script_request_capacity_change, + script_notify_capacity_change, + NULL +}; + +/* + * Messages fall into two categories: + * framework messages (MF_..) + * errors directly attributable to scripts (MS_..) + */ +#define MF_MEMORY_ALLOCATION_ERR \ + gettext("rcm: failed to allocate memory: %1$s\n") +#define MF_STATE_FILE_ERR \ + gettext("rcm: state file error: %1$s: %2$s\n") +#define MF_FUNC_CALL_ERR \ + gettext("rcm: %1$s: %2$s\n") +#define MF_NV_ERR \ + gettext("rcm: required name-value parameters missing (%1$s)\n") +#define MF_UNKNOWN_RSRC_ERR \ + gettext("rcm: unknown resource name %1$s (%2$s)\n") +#define MS_REGISTER_RSRC_ERR \ + gettext("rcm script %1$s: failed to register %2$s\n") +#define MS_REGISTER_ERR \ + gettext("rcm script %1$s: register: %2$s\n") +#define MS_SCRIPTINFO_ERR \ + gettext("rcm script %1$s: scriptinfo: %2$s\n") +#define MS_PROTOCOL_ERR \ + gettext("rcm script %1$s: scripting protocol error\n") +#define MS_TIMEOUT_ERR \ + gettext("rcm script %1$s: timeout error\n") +#define MS_UNSUPPORTED_VER \ + gettext("rcm script %1$s: unsupported version %2$d\n") +#define MS_SCRIPT_ERR \ + gettext("rcm script %1$s: error: %2$s\n") +#define MS_UNKNOWN_ERR \ + gettext("rcm script %1$s: unknown error\n") +#define MS_LOG_MSG \ + gettext("rcm script %1$s: %2$s\n") + + +/* + * Initialize rcm scripting framework. + * Called during initialization of rcm daemon. + */ +int +script_main_init(void) +{ +#define PS_STATE_FILE_CHUNK_SIZE 32 + + /* set base script environment */ + build_env(); + + rcm_init_queue(&script_info_q); + + /* + * Initialize the semaphore to limit the number of rcm script + * process running in parallel to script_max_parallelism. + */ + (void) sema_init(&script_process_sema, script_max_parallelism, + USYNC_THREAD, NULL); + + (void) mutex_init(&script_lock, USYNC_THREAD, NULL); + + /* save original file limit */ + (void) getrlimit(RLIMIT_NOFILE, &file_limit); + + open_state_file(script_ps_state_file, &script_ps_statefd, + sizeof (ps_state_element_t), + PS_STATE_FILE_CHUNK_SIZE, + PS_STATE_FILE_VER); + + /* + * If any pids exist in the ps state file since the last incarnation of + * the rcm daemon, kill the pids. + * On a normal daemon exit no pids should exist in the ps state file. + * But on an abnormal daemon exit pids may exist in the ps state file. + */ + if (script_ps_statefd.state_file) { + script_ps_state_file_kill_pids(); + truncate_state_file(&script_ps_statefd); + } + + return (0); +} + +/* + * Do any cleanup. + * Called at the time of normal rcm daemon exit. + */ +int +script_main_fini(void) +{ + script_ps_state_file_kill_pids(); + close_state_file(script_ps_state_file, &script_ps_statefd); + return (0); +} + +/* + * Initialize the given rcm script. + * module->name contains the name of the rcm script. + */ +struct rcm_mod_ops * +script_init(module_t *module) +{ + script_info_t *rsi; + size_t len; + char *script_path; + + rcm_log_message(RSCR_TRACE, "script_init: script name = %s\n", + module->name); + + module->rsi = NULL; + + if ((script_path = rcm_get_script_dir(module->name)) == NULL) + return (NULL); + + len = strlen(script_path) + strlen(module->name) + 2; + + /* calloc also zeros the contents */ + rsi = (script_info_t *)rcmscript_calloc(1, sizeof (script_info_t)); + rsi->script_full_name = (char *)rcmscript_calloc(1, len); + + rsi->module = module; + rcm_init_queue(&rsi->drreq_q); + + (void) mutex_init(&rsi->channel_lock, USYNC_THREAD, NULL); + + (void) snprintf(rsi->script_full_name, len, "%s%s", script_path, + module->name); + rsi->script_name = strrchr(rsi->script_full_name, '/') + 1; + + (void) mutex_lock(&rsi->channel_lock); + + rsi->cmd_timeout = -1; /* don't time scriptinfo command */ + if (do_script_info(rsi) == RCM_SUCCESS) { + /* + * if the script hasn't specified a timeout value set it to + * default + */ + if (rsi->cmd_timeout == -1) + rsi->cmd_timeout = SCRIPT_CMD_TIMEOUT; + (void) mutex_unlock(&rsi->channel_lock); + + /* put rsi on script_info_q */ + (void) mutex_lock(&script_lock); + rcm_enqueue_tail(&script_info_q, &rsi->queue); + (void) mutex_unlock(&script_lock); + + module->rsi = rsi; + return (&script_ops); + } + + (void) mutex_unlock(&rsi->channel_lock); + + free(rsi->script_full_name); + free(rsi); + return (NULL); +} + +/* + * Returns a string describing the script's functionality. + * module->name contains the name of the rcm script for which information + * is requested. + */ +char * +script_info(module_t *module) +{ + script_info_t *rsi = module->rsi; + + rcm_log_message(RSCR_TRACE, "script_info: script name = %s\n", + rsi->script_name); + return (rsi->func_info_buf); +} + +/* + * Called before unloading the script. + * module->name contains the name of the rcm script which is being unloaded. + * Do any cleanup. + */ +int +script_fini(module_t *module) +{ + script_info_t *rsi = module->rsi; + + rcm_log_message(RSCR_TRACE, "script_fini: script name = %s\n", + rsi->script_name); + + /* remove rsi from script_info_q */ + (void) mutex_lock(&script_lock); + rcm_dequeue(&rsi->queue); + (void) mutex_unlock(&script_lock); + + remove_drreq_all(rsi); + + if (rsi->func_info_buf) + free(rsi->func_info_buf); + + free(rsi->script_full_name); + free(rsi); + + module->rsi = NULL; + + return (RCM_SUCCESS); +} + +/* build base environment for scripts */ +static void +build_env(void) +{ + const char *env_list[] = { "LANG", "LC_COLLATE", "LC_CTYPE", + "LC_MESSAGES", "LC_MONETARY", "LC_NUMERIC", "LC_TIME", + "LC_ALL", "TZ", NULL }; + char *x; + int len; + int i, j = 0; + int d; + extern int debug_level; + + script_env[j++] = rcmscript_strdup("PATH=/usr/sbin:/usr/bin"); + + for (i = 0; env_list[i] != NULL; i++) { + x = getenv(env_list[i]); + if (x) { + len = strlen(env_list[i]) + strlen(x) + 2; + script_env[j] = (char *)rcmscript_malloc(len); + + (void) snprintf(script_env[j++], len, "%s=%s", + env_list[i], x); + } + } + + len = strlen("RCM_ENV_DEBUG_LEVEL") + 3; + script_env[j] = (char *)rcmscript_malloc(len); + + if (debug_level < 0) + d = 0; + else if (debug_level > 9) + d = 9; + else + d = debug_level; + + (void) snprintf(script_env[j++], len, "RCM_ENV_DEBUG_LEVEL=%d", d); + + script_env[j] = NULL; +} + +static void +copy_env(char *src[], char *dst[]) +{ + int i; + + for (i = 0; src[i] != NULL; i++) + dst[i] = src[i]; + + dst[i] = NULL; +} + +/* + * Open (or create if the file does not exist) the given state file + * and mmap it. + */ +static void +open_state_file(const char *filename, + state_file_descr_t *statefd, + size_t element_size, + int chunk_size, + uint32_t version) +{ + struct stat stats; + int error_num; + + if ((statefd->fd = open(filename, O_CREAT|O_RDWR, 0600)) == + -1) { + error_num = errno; + rcm_log_message(RCM_ERROR, MF_STATE_FILE_ERR, + "open", strerror(error_num)); + rcmd_exit(error_num); + /*NOTREACHED*/ + } + + if (fstat(statefd->fd, &stats) != 0) { + error_num = errno; + rcm_log_message(RCM_ERROR, MF_STATE_FILE_ERR, + "fstat", strerror(error_num)); + rcmd_exit(error_num); + /*NOTREACHED*/ + } + + if (stats.st_size != 0) { + /* LINTED */ + statefd->state_file = (state_file_t *)mmap(NULL, + stats.st_size, PROT_READ|PROT_WRITE, MAP_SHARED, + statefd->fd, 0); + + if (statefd->state_file == MAP_FAILED) { + error_num = errno; + rcm_log_message(RCM_ERROR, MF_STATE_FILE_ERR, + "mmap", strerror(error_num)); + rcmd_exit(error_num); + /*NOTREACHED*/ + } + + if (statefd->state_file->version != version) { + (void) munmap((void *)statefd->state_file, + stats.st_size); + statefd->state_file = NULL; + (void) ftruncate(statefd->fd, 0); + } + } else { + statefd->state_file = NULL; + } + + statefd->version = version; + statefd->element_size = sizeof (state_element_t) + + RSCR_ROUNDUP(element_size, 8); + statefd->chunk_size = chunk_size; + statefd->index = 0; +} + +static void +truncate_state_file(state_file_descr_t *statefd) +{ + size_t size; + + if (statefd->state_file) { + size = sizeof (state_file_t) + statefd->element_size * + statefd->state_file->max_elements; + + (void) munmap((void *)statefd->state_file, size); + statefd->state_file = NULL; + } + (void) ftruncate(statefd->fd, 0); +} + +static void +close_state_file(const char *filename, state_file_descr_t *statefd) +{ + truncate_state_file(statefd); + (void) close(statefd->fd); + (void) unlink(filename); +} + +/* + * Grow the state file by the chunk size specified in statefd + * and mmap it. + */ +static void +grow_state_file(state_file_descr_t *statefd) +{ + size_t size; + int max_elements; + int error_num; + + max_elements = statefd->chunk_size; + if (statefd->state_file) + max_elements += statefd->state_file->max_elements; + + size = sizeof (state_file_t) + + statefd->element_size * max_elements; + + if (ftruncate(statefd->fd, size) != 0) { + error_num = errno; + rcm_log_message(RCM_ERROR, MF_STATE_FILE_ERR, + "ftruncate", strerror(error_num)); + rcmd_exit(error_num); + /*NOTREACHED*/ + } + + /* LINTED */ + statefd->state_file = (state_file_t *)mmap(NULL, size, + PROT_READ|PROT_WRITE, MAP_SHARED, statefd->fd, 0); + + if (statefd->state_file == MAP_FAILED) { + error_num = errno; + rcm_log_message(RCM_ERROR, MF_STATE_FILE_ERR, + "mmap", strerror(error_num)); + rcmd_exit(error_num); + /*NOTREACHED*/ + } + + statefd->index = statefd->state_file->max_elements; + statefd->state_file->max_elements = max_elements; + statefd->state_file->version = statefd->version; +} + +/* + * Given index into state element array, get the pointer to the actual + * state element. + * If flag is non-null set *flag to + * TRUE if the state element is currently is use. + * FALSE if the state element is free. + */ +static void * +get_state_element(state_file_descr_t *statefd, int index, int *flag) +{ + char *ptr; + + if (statefd->state_file && + (index < statefd->state_file->max_elements)) { + + ptr = (char *)(statefd->state_file); + ptr += sizeof (state_file_t) + + index * statefd->element_size; + + if (flag) { + *flag = (((state_element_t *)((void *)ptr))->flags & + STATE_ELEMENT_IN_USE) ? 1 : 0; + } + + ptr += sizeof (state_element_t); + } else + ptr = NULL; + + return ((void *)ptr); +} + +/* + * Allocate a state element entry in the state file and return a pointer + * to the allocated entry. + * If index is non-null set *index to index into the state element array + * of the allocated entry. + */ +static void * +allocate_state_element(state_file_descr_t *statefd, int *index) +{ + void *x; + int i; + int flag; + + if (statefd->state_file) { + /* find an empty slot */ + for (i = 0; i < statefd->state_file->max_elements; i++) { + x = get_state_element(statefd, statefd->index, + &flag); + assert(x != NULL); + + if (flag == 0) + /* entry is free */ + break; + + statefd->index++; + if (statefd->index >= statefd->state_file->max_elements) + statefd->index = 0; + } + } + + if (statefd->state_file == NULL || + i == statefd->state_file->max_elements) { + + /* All entries are in use. Grow the list */ + grow_state_file(statefd); + x = get_state_element(statefd, statefd->index, &flag); + assert(flag == 0); + } + + if (index != NULL) + *index = statefd->index; + + statefd->index++; + if (statefd->index >= statefd->state_file->max_elements) + statefd->index = 0; + + ((state_element_t *)x - 1)->flags |= STATE_ELEMENT_IN_USE; + return (x); +} + +static void +free_state_element(void *x) +{ + ((state_element_t *)x - 1)->flags &= ~STATE_ELEMENT_IN_USE; +} + +/* + * Kill the pids contained in ps state file. + */ +static void +script_ps_state_file_kill_pids(void) +{ + ps_state_element_t *x; + char procfile[80]; + psinfo_t psi; + int fd, i, flag; + + /* LINTED */ + for (i = 0; 1; i++) { + if ((x = (ps_state_element_t *)get_state_element( + &script_ps_statefd, i, &flag)) == NULL) + break; + + if (flag == 1) { /* the entry is in use */ + (void) snprintf(procfile, 80, "/proc/%ld/psinfo", + (long)x->pid); + if ((fd = open(procfile, O_RDONLY)) != -1 && + read(fd, &psi, sizeof (psi)) == sizeof (psi) && + strcmp(psi.pr_fname, + x->script_name) == 0) { + + (void) close(fd); + + /* + * just a safety check to not to blow up + * system processes if the file is ever corrupt + */ + if (x->pid > 1) { + rcm_log_message(RCM_DEBUG, + "script_ps_state_file_kill_pids: " + "killing script_name = %s pid = %ld\n", + x->script_name, x->pid); + + /* kill the process group */ + (void) kill(-(x->pid), SIGKILL); + } + } else { + if (fd != -1) + (void) close(fd); + } + free_state_element((void *)x); + } + } +} + +/* + * Add a state element entry to ps state file. + */ +static void +script_ps_state_file_add_entry(pid_t pid, char *script_name) +{ + ps_state_element_t *x; + + (void) mutex_lock(&script_lock); + + x = (ps_state_element_t *)allocate_state_element( + &script_ps_statefd, NULL); + + x->pid = pid; + (void) strlcpy(x->script_name, script_name, MAXNAMELEN); + + (void) fsync(script_ps_statefd.fd); + + (void) mutex_unlock(&script_lock); +} + +/* + * Remove the state element entry corresponding to pid from the + * ps state file. + */ +static void +script_ps_state_file_remove_entry(pid_t pid) +{ + ps_state_element_t *x; + int flag, i; + + (void) mutex_lock(&script_lock); + + /* LINTED */ + for (i = 0; 1; i++) { + if ((x = (ps_state_element_t *)get_state_element( + &script_ps_statefd, i, &flag)) == NULL) + break; + + /* if the state element entry is in use and pid matches */ + if (flag == 1 && x->pid == pid) { + free_state_element((void *)x); + break; + } + } + + (void) mutex_unlock(&script_lock); +} + +/* + * Get data item id given data item name + */ +static int +dname_to_id(char *dname) +{ + int i; + + for (i = 0; script_data_item_name[i] != NULL; i++) { + if (strcmp(dname, script_data_item_name[i]) == 0) + return (i); + } + + return (-1); +} + +/* + * Called before running any script. + * This routine waits until the number of script processes running in + * parallel drops down below to script_max_parallelism. + */ +static void +script_process_sema_wait(void) +{ + int error_num; + + /* LINTED */ + while (1) { + if (sema_wait(&script_process_sema) == 0) + return; + + if (errno != EINTR && errno != EAGAIN) { + error_num = errno; + rcm_log_message(RCM_ERROR, MF_FUNC_CALL_ERR, + "sema_wait", strerror(error_num)); + rcmd_exit(error_num); + /*NOTREACHED*/ + } + } + + /*NOTREACHED*/ +} + +/* + * Fork and execute the script. + */ +static int +run_script(script_info_t *rsi, char *argv[], char *envp[], char **errmsg) +{ + int i, p1 = -1, p2 = -1; + struct rlimit rlp; + struct stat stats; + + rcm_log_message(RSCR_TRACE, "run_script: script name = %s\n", + rsi->script_full_name); + + for (i = 0; argv[i] != NULL; i++) + rcm_log_message(RSCR_TRACE, "run_script: argv[%d] = %s\n", + i, argv[i]); + + *errmsg = NULL; + + /* check that the script exists */ + if (stat(rsi->script_full_name, &stats) != 0) + goto error; + + /* + * If the syscall pipe fails because of reaching the max open file + * count per process then dynamically increase the limit on the max + * open file count. + * + * At present the rcm_daemon consumes file descriptor + * entries for the following files. + * RCM_STATE_FILE - /var/run/rcm_daemon_state + * DAEMON_LOCK_FILE - /var/run/rcm_daemon_lock + * RCM_SERVICE_DOOR - /var/run/rcm_daemon_door + * proc files in the format "/proc/pid/as" for each pid + * communicating with the rcm_daemon via doors + * dlopen for each rcm module + * When in daemon mode stdin, stdout and stderr are closed; + * /dev/null opened and duped to stdout, and stderr + * openlog + * Some files which are opened briefly and closed such as + * directory files. + * Two file descriptors for each script in running state. + * Note that the constant script_max_parallelism sets an + * upper cap on how many rcm scripts can run in + * parallel. + */ + if ((p1 = pipe(rsi->pipe1)) == -1 || (p2 = pipe(rsi->pipe2)) == -1) { + if ((errno == EMFILE) && + (getrlimit(RLIMIT_NOFILE, &rlp) == 0)) { + + rlp.rlim_cur += 16; + if (rlp.rlim_max < rlp.rlim_cur) + rlp.rlim_max = rlp.rlim_cur; + (void) setrlimit(RLIMIT_NOFILE, &rlp); + + if (p1 == -1) { + if ((p1 = pipe(rsi->pipe1)) == -1) + goto error; + } + if ((p2 = pipe(rsi->pipe2)) == -1) + goto error; + } else + goto error; + } + +forkagain: + if ((rsi->pid = fork1()) == (pid_t)-1) { + if (errno == EINTR || errno == EAGAIN) + goto forkagain; + + goto error; + } + + if (rsi->pid == 0) { + /* child process */ + + (void) setsid(); + + /* close stdin, stdout and stderr */ + (void) close(0); + (void) close(1); + (void) close(2); + + /* set stdin to /dev/null */ + (void) open("/dev/null", O_RDWR, 0); + + /* redirect stdout and stderr to pipe */ + (void) dup2(rsi->pipe1[CHILD_END_OF_PIPE], 1); + (void) dup2(rsi->pipe2[CHILD_END_OF_PIPE], 2); + + /* close all other file descriptors */ + closefrom(3); + + /* restore original file limit */ + (void) setrlimit(RLIMIT_NOFILE, &file_limit); + + /* set current working dir */ + if (stats.st_uid == 0) { + /* root */ + if (chdir("/var/run") == -1) + _exit(127); + } else { + if (chdir("/tmp") == -1) + _exit(127); + } + + /* + * setuid sets real, effective and saved user ids to the + * given id. + * setgid sets real, effective and saved group ids to the + * given id. + */ + (void) setgid(stats.st_gid); + (void) setuid(stats.st_uid); + + (void) execve(rsi->script_full_name, argv, envp); + _exit(127); + /*NOTREACHED*/ + } + + (void) close(rsi->pipe1[CHILD_END_OF_PIPE]); + (void) close(rsi->pipe2[CHILD_END_OF_PIPE]); + + script_ps_state_file_add_entry(rsi->pid, rsi->script_name); + + return (0); + +error: + *errmsg = dup_err(RCM_ERROR, MS_SCRIPT_ERR, + rsi->script_name, strerror(errno)); + + if (p1 != -1) { + (void) close(rsi->pipe1[PARENT_END_OF_PIPE]); + (void) close(rsi->pipe1[CHILD_END_OF_PIPE]); + } + + if (p2 != -1) { + (void) close(rsi->pipe2[PARENT_END_OF_PIPE]); + (void) close(rsi->pipe2[CHILD_END_OF_PIPE]); + } + + return (-1); +} + +/* + * Reads one line of input (including the newline character) from the + * given file descriptor "fd" to buf. + * maxbuflen specifies the size of memory allocated for buf. + * Timeoutval is the max timeout value in seconds for the script to supply + * input. A timeoutval of 0 implies no timeout. + * + * Upon return *buflen contains the number of bytes read. + * + * Return values: + * 0 success + * -1 an error occured + * -2 timeout occurred + * -3 script exited + */ +static int +get_line(int fd, + char *fdname, + char *buf, + int maxbuflen, + size_t *buflen, + time_t timeoutval, + int *error_num) +{ + char c = '\0'; + struct pollfd fds[1]; + int x; + size_t len = 0; + char *ptr; + int timeit; + time_t deadline; + int rval = 0; + + if (timeoutval) { + timeit = TRUE; + deadline = time(NULL) + timeoutval; + fds[0].fd = fd; + fds[0].events = POLLIN; + } else + timeit = FALSE; + + ptr = buf; + + while (c != '\n' && len < (maxbuflen -1)) { + if (timeit) { +pollagain: + fds[0].revents = 0; + timeoutval = deadline - time(NULL); + if (timeoutval <= 0) { + rval = -2; + break; + } + x = poll(fds, 1, timeoutval*1000); + if (x <= 0) { + if (x == 0) + /* poll timedout */ + rval = -2; + else { + if (errno == EINTR || errno == EAGAIN) + goto pollagain; + *error_num = errno; + rval = -1; + } + break; + } + } +readagain: + if ((x = read(fd, &c, 1)) != 1) { + if (x == 0) + /* + * Script exited. Or more specifically the + * script has closed its end of the pipe. + */ + rval = -3; + else { + if (errno == EINTR || errno == EAGAIN) + goto readagain; + *error_num = errno; + rval = -1; + } + break; + } + + *ptr++ = c; + len++; + } + + *ptr = '\0'; + *buflen = len; + + rcm_log_message(RSCR_TRACE, + "get_line(%s): rval = %d buflen = %d line = %s\n", + fdname, rval, *buflen, buf); + return (rval); +} + +static void +script_exited(script_info_t *rsi) +{ + if (rsi->flags & STDERR_THREAD_CREATED) { + rcm_log_message(RSCR_TRACE, + "script_exited: doing thr_join (%s)\n", rsi->script_name); + (void) thr_join(rsi->tid, NULL, NULL); + rsi->flags &= ~STDERR_THREAD_CREATED; + } + + (void) close(rsi->pipe1[PARENT_END_OF_PIPE]); + (void) close(rsi->pipe2[PARENT_END_OF_PIPE]); + rsi->pipe1[PARENT_END_OF_PIPE] = -1; + rsi->pipe2[PARENT_END_OF_PIPE] = -1; + + script_ps_state_file_remove_entry(rsi->pid); + rsi->pid = 0; + (void) sema_post(&script_process_sema); +} + +/* + * Kill the specified process group + */ +static int +kill_pid(pid_t pid) +{ + time_t deadline, timeleft; + int child_status; + + /* kill the entire process group */ + (void) kill(-(pid), SIGKILL); + + /* give some time for the script to be killed */ + deadline = time(NULL) + SCRIPT_KILL_TIMEOUT; + do { + if (waitpid(pid, &child_status, WNOHANG) == pid) + return (0); + + /* wait for 100 ms */ + (void) poll(NULL, 0, 100); + + timeleft = deadline - time(NULL); + } while (timeleft > 0); + + /* script process was not killed successfully */ + return (-1); +} + +/* + * Kill the specified script. + */ +static void +kill_script(script_info_t *rsi) +{ + if (rsi->pid > 1) { + (void) kill_pid(rsi->pid); + script_exited(rsi); + remove_drreq_all(rsi); + } +} + +/* + * Convert rcm flags parameter to a string. + * Used for debug prints. + */ +static char * +flags_to_name(int flags, char *buf, int maxbuflen) +{ + (void) snprintf(buf, maxbuflen, "%s%s", + (flags & RCM_QUERY) ? "RCM_QUERY " : "", + (flags & RCM_FORCE) ? "RCM_FORCE" : ""); + + return (buf); +} + +static void +fill_argv(script_info_t *rsi, char *argv[], char *resource_name) +{ + argv[0] = rsi->script_full_name; + argv[1] = script_cmd_name[rsi->cmd]; + if (resource_name) { + argv[2] = resource_name; + argv[3] = NULL; + } else + argv[2] = NULL; +} + +/* + * stderr thread: + * Reads stderr and logs to syslog. + * Runs as a separate thread. + */ +static void * +read_stderr(script_info_t *rsi) +{ + char buf[MAX_LINE_LEN]; + size_t buflen; + int error_num; + + while ((get_line(rsi->pipe2[PARENT_END_OF_PIPE], "stderr", + buf, MAX_LINE_LEN, &buflen, 0, &error_num)) == 0) { + log_msg(rsi, RCM_ERROR, buf); + } + + if (buflen) + log_msg(rsi, RCM_ERROR, buf); + + return (NULL); +} + +/* process return data items passed by scripts to the framework */ +static int +process_dataitem(script_info_t *rsi, int token, char *value, char **errmsg) +{ + char *ptr; + int status; + + *errmsg = NULL; + + if (*value == '\0') + goto error; + + switch (token) { + case D_SCRIPT_VERSION: + if (rsi->cmd != C_SCRIPTINFO) + goto error; + + /* check that value contains only digits */ + for (ptr = value; *ptr != '\0'; ptr++) + if (isdigit((int)(*ptr)) == 0) + break; + + if (*ptr == '\0') + rsi->ver = atoi(value); + else + goto error; + + break; + + case D_SCRIPT_FUNC_INFO: + if (rsi->cmd != C_SCRIPTINFO) + goto error; + + rcmscript_snprintf(&rsi->func_info_buf, + &rsi->func_info_buf_len, + &rsi->func_info_buf_curptr, + "%s", value); + break; + + case D_CMD_TIMEOUT: + if (rsi->cmd != C_SCRIPTINFO) + goto error; + + /* check that value contains only digits */ + for (ptr = value; *ptr != '\0'; ptr++) + if (isdigit((int)(*ptr)) == 0) + break; + + if (*ptr == '\0') + rsi->cmd_timeout = atoi(value); + else + goto error; + break; + + case D_RESOURCE_NAME: + if (rsi->cmd != C_REGISTER) + goto error; + + if (get_capacity_descr(value) != NULL) + status = rcm_register_capacity(rsi->hdl, value, + 0, NULL); + else + status = rcm_register_interest(rsi->hdl, value, 0, + NULL); + + if (status == RCM_FAILURE && errno == EALREADY) + status = RCM_SUCCESS; + + if (status != RCM_SUCCESS) { + rcm_log_message(RCM_ERROR, MS_REGISTER_RSRC_ERR, + rsi->script_name, value); + } + + remove_from_unregister(rsi, value); + break; + + case D_RESOURCE_USAGE_INFO: + if (rsi->cmd != C_RESOURCEINFO) + goto error; + + rcmscript_snprintf(&rsi->resource_usage_info_buf, + &rsi->resource_usage_info_buf_len, + &rsi->resource_usage_info_buf_curptr, + "%s", value); + break; + + case D_FAILURE_REASON: + rcmscript_snprintf(&rsi->failure_reason_buf, + &rsi->failure_reason_buf_len, + &rsi->failure_reason_buf_curptr, + "%s", value); + break; + + default: + goto error; + } + + return (0); + +error: + *errmsg = dup_err(RCM_ERROR, MS_PROTOCOL_ERR, rsi->script_name); + return (-1); +} + +/* Send the given command to the script and process return data */ +static int +do_cmd(script_info_t *rsi, char *argv[], char *envp[], char **errmsg) +{ + char buf[MAX_LINE_LEN]; + size_t buflen; + int loglevel = -1, continuelog = 0; + char *ptr, *dname, *value; + time_t maxsecs; + time_t deadline; + int sigaborted = 0; + int rval, child_status, token; + int error_num; + int cmd_timeout = rsi->cmd_timeout; + + *errmsg = NULL; + + script_process_sema_wait(); + + if (run_script(rsi, argv, envp, errmsg) == -1) { + (void) sema_post(&script_process_sema); + goto error2; + } + + (void) time(&rsi->lastrun); + deadline = rsi->lastrun + cmd_timeout; + + if (thr_create(NULL, 0, (void *(*)(void *))read_stderr, rsi, + 0, &rsi->tid) != 0) { + *errmsg = dup_err(RCM_ERROR, MF_FUNC_CALL_ERR, + "thr_create", strerror(errno)); + goto error1; + } + rsi->flags |= STDERR_THREAD_CREATED; + + /* LINTED */ + while (1) { + if (cmd_timeout > 0) { + maxsecs = deadline - time(NULL); + if (maxsecs <= 0) + goto timedout; + } else + maxsecs = 0; + + rval = get_line(rsi->pipe1[PARENT_END_OF_PIPE], + "stdout", buf, MAX_LINE_LEN, &buflen, + maxsecs, &error_num); + + if (buflen) { + if (continuelog) + log_msg(rsi, loglevel, buf); + else { + if ((ptr = strchr(buf, '=')) == NULL) + goto error; + + *ptr = '\0'; + dname = buf; + value = ptr + 1; + if ((token = dname_to_id(dname)) == -1) + goto error; + + switch (token) { + case D_LOG_ERR: + loglevel = RCM_ERROR; + break; + + case D_LOG_WARN: + loglevel = RCM_WARNING; + break; + + case D_LOG_INFO: + loglevel = RCM_INFO; + break; + + case D_LOG_DEBUG: + loglevel = RCM_DEBUG; + break; + + default: + loglevel = -1; + break; + } + + if (loglevel != -1) { + log_msg(rsi, loglevel, value); + if (buf[buflen - 1] == '\n') + continuelog = 0; + else + continuelog = 1; + } else { + if (buf[buflen - 1] != '\n') + goto error; + + buf[buflen - 1] = '\0'; + if (process_dataitem(rsi, token, + value, errmsg) != 0) + goto error1; + } + } + } + + if (rval == -3) { + /* script exited */ +waitagain: + if (waitpid(rsi->pid, &child_status, 0) + != rsi->pid) { + if (errno == EINTR || errno == EAGAIN) + goto waitagain; + *errmsg = dup_err(RCM_ERROR, MS_SCRIPT_ERR, + rsi->script_name, strerror(errno)); + goto error1; + } + + if (WIFEXITED(child_status)) { + script_exited(rsi); + rsi->exit_status = WEXITSTATUS(child_status); + } else { + if (sigaborted) + *errmsg = dup_err(RCM_ERROR, + MS_TIMEOUT_ERR, rsi->script_name); + else + *errmsg = dup_err(RCM_ERROR, + MS_UNKNOWN_ERR, rsi->script_name); + + /* kill any remaining processes in the pgrp */ + (void) kill(-(rsi->pid), SIGKILL); + script_exited(rsi); + goto error2; + } + + break; + } + + if (rval == -1) { + *errmsg = dup_err(RCM_ERROR, MS_SCRIPT_ERR, + rsi->script_name, strerror(errno)); + goto error1; + } + + if (rval == -2) { +timedout: + /* timeout occurred */ + if (sigaborted == 0) { + (void) kill(rsi->pid, SIGABRT); + sigaborted = 1; + /* extend deadline */ + deadline += SCRIPT_ABORT_TIMEOUT; + } else { + *errmsg = dup_err(RCM_ERROR, + MS_TIMEOUT_ERR, rsi->script_name); + goto error1; + } + } + } + + return (0); + +error: + *errmsg = dup_err(RCM_ERROR, MS_PROTOCOL_ERR, rsi->script_name); + +error1: + kill_script(rsi); + +error2: + return (-1); +} + +static int +do_script_info(script_info_t *rsi) +{ + char *argv[MAX_ARGS]; + int status = RCM_FAILURE; + int err = 0; + char *errmsg = NULL; + + rcm_log_message(RSCR_TRACE, "do_script_info: script name = %s\n", + rsi->script_name); + + rsi->cmd = C_SCRIPTINFO; + rsi->func_info_buf = NULL; + rsi->failure_reason_buf = NULL; + fill_argv(rsi, argv, NULL); + + if (do_cmd(rsi, argv, script_env, &errmsg) == 0) { + switch (rsi->exit_status) { + case E_SUCCESS: + if (rsi->func_info_buf != NULL && + rsi->failure_reason_buf == NULL) { + + if (rsi->ver >= SCRIPT_API_MIN_VER && + rsi->ver <= SCRIPT_API_MAX_VER) + status = RCM_SUCCESS; + else + rcm_log_message(RCM_ERROR, + MS_UNSUPPORTED_VER, rsi->script_name, + rsi->ver); + } else + err = 1; + break; + + case E_FAILURE: + if (rsi->failure_reason_buf != NULL) { + rcm_log_message(RCM_ERROR, MS_SCRIPTINFO_ERR, + rsi->script_name, + rsi->failure_reason_buf); + } else + err = 1; + break; + + default: + err = 1; + break; + } + if (err) + rcm_log_message(RCM_ERROR, MS_PROTOCOL_ERR, + rsi->script_name); + } else if (errmsg) + (void) free(errmsg); + + if (status != RCM_SUCCESS && rsi->func_info_buf != NULL) + free(rsi->func_info_buf); + + if (rsi->failure_reason_buf) + free(rsi->failure_reason_buf); + + return (status); +} + +static int +do_dr(script_info_t *rsi, char *argv[], char *envp[], char **info) +{ + int status = RCM_FAILURE; + int err = 0; + + rsi->failure_reason_buf = NULL; + + if (do_cmd(rsi, argv, envp, info) == 0) { + switch (rsi->exit_status) { + case E_SUCCESS: + case E_UNSUPPORTED_CMD: + if (rsi->failure_reason_buf == NULL) + status = RCM_SUCCESS; + else + err = 1; + break; + + case E_FAILURE: + case E_REFUSE: + if (rsi->failure_reason_buf != NULL) { + *info = rsi->failure_reason_buf; + rsi->failure_reason_buf = NULL; + } else + err = 1; + break; + + default: + err = 1; + break; + } + + if (err) + *info = dup_err(RCM_ERROR, MS_PROTOCOL_ERR, + rsi->script_name); + } + + if (rsi->failure_reason_buf) + free(rsi->failure_reason_buf); + + return (status); +} + +/* + * get_info entry point + */ +/* ARGSUSED */ +static int +script_get_info(rcm_handle_t *hdl, + char *resource_name, + pid_t pid, + uint_t flag, + char **info, + char **error, + nvlist_t *props, + rcm_info_t **dependent_info) +{ + script_info_t *rsi = hdl->module->rsi; + char *argv[MAX_ARGS]; + int status = RCM_FAILURE; + int err = 0; + + rcm_log_message(RSCR_TRACE, "script_get_info: resource = %s\n", + resource_name); + + *info = NULL; + *error = NULL; + + (void) mutex_lock(&rsi->channel_lock); + + rsi->hdl = hdl; + rsi->cmd = C_RESOURCEINFO; + rsi->resource_usage_info_buf = NULL; + rsi->failure_reason_buf = NULL; + fill_argv(rsi, argv, resource_name); + + if (do_cmd(rsi, argv, script_env, error) == 0) { + switch (rsi->exit_status) { + case E_SUCCESS: + if (rsi->resource_usage_info_buf != NULL && + rsi->failure_reason_buf == NULL) { + + *info = rsi->resource_usage_info_buf; + rsi->resource_usage_info_buf = NULL; + status = RCM_SUCCESS; + } else + err = 1; + break; + + case E_FAILURE: + if (rsi->failure_reason_buf != NULL) { + *error = rsi->failure_reason_buf; + rsi->failure_reason_buf = NULL; + } else + err = 1; + break; + + default: + err = 1; + break; + } + if (err) + *error = dup_err(RCM_ERROR, MS_PROTOCOL_ERR, + rsi->script_name); + } + + if (rsi->resource_usage_info_buf) + free(rsi->resource_usage_info_buf); + + if (rsi->failure_reason_buf) + free(rsi->failure_reason_buf); + + (void) mutex_unlock(&rsi->channel_lock); + + return (status); +} + +static void +add_for_unregister(script_info_t *rsi) +{ + module_t *module = rsi->module; + client_t *client; + rcm_queue_t *head; + rcm_queue_t *q; + + (void) mutex_lock(&rcm_req_lock); + + head = &module->client_q; + + for (q = head->next; q != head; q = q->next) { + client = RCM_STRUCT_BASE_ADDR(client_t, q, queue); + client->prv_flags |= RCM_NEED_TO_UNREGISTER; + } + + (void) mutex_unlock(&rcm_req_lock); +} + +static void +remove_from_unregister(script_info_t *rsi, char *resource_name) +{ + module_t *module = rsi->module; + client_t *client; + rcm_queue_t *head; + rcm_queue_t *q; + + (void) mutex_lock(&rcm_req_lock); + + head = &module->client_q; + + for (q = head->next; q != head; q = q->next) { + client = RCM_STRUCT_BASE_ADDR(client_t, q, queue); + if (strcmp(client->alias, resource_name) == 0) { + client->prv_flags &= ~RCM_NEED_TO_UNREGISTER; + break; + } + } + + (void) mutex_unlock(&rcm_req_lock); +} + +static void +complete_unregister(script_info_t *rsi) +{ + module_t *module = rsi->module; + client_t *client; + rcm_queue_t *head; + rcm_queue_t *q; + + (void) mutex_lock(&rcm_req_lock); + + head = &module->client_q; + + for (q = head->next; q != head; q = q->next) { + client = RCM_STRUCT_BASE_ADDR(client_t, q, queue); + if (client->prv_flags & RCM_NEED_TO_UNREGISTER) { + client->prv_flags &= ~RCM_NEED_TO_UNREGISTER; + client->state = RCM_STATE_REMOVE; + } + } + + (void) mutex_unlock(&rcm_req_lock); +} + +/* + * register_interest entry point + */ +static int +script_register_interest(rcm_handle_t *hdl) +{ + script_info_t *rsi = hdl->module->rsi; + char *argv[MAX_ARGS]; + int status = RCM_FAILURE; + int err = 0; + char *errmsg = NULL; + + rcm_log_message(RSCR_TRACE, + "script_register_interest: script name = %s\n", + rsi->script_name); + + (void) mutex_lock(&rsi->channel_lock); + + if (rsi->drreq_q.next != &rsi->drreq_q) { + /* if DR is already in progress no need to register again */ + (void) mutex_unlock(&rsi->channel_lock); + return (RCM_SUCCESS); + } + + rsi->hdl = hdl; + rsi->cmd = C_REGISTER; + rsi->failure_reason_buf = NULL; + fill_argv(rsi, argv, NULL); + + add_for_unregister(rsi); + + if (do_cmd(rsi, argv, script_env, &errmsg) == 0) { + switch (rsi->exit_status) { + case E_SUCCESS: + status = RCM_SUCCESS; + break; + + case E_FAILURE: + if (rsi->failure_reason_buf != NULL) { + rcm_log_message(RCM_ERROR, MS_REGISTER_ERR, + rsi->script_name, + rsi->failure_reason_buf); + } else + err = 1; + break; + + default: + err = 1; + break; + } + if (err) + rcm_log_message(RCM_ERROR, MS_PROTOCOL_ERR, + rsi->script_name); + } else if (errmsg) + (void) free(errmsg); + + complete_unregister(rsi); + + if (rsi->failure_reason_buf) + free(rsi->failure_reason_buf); + + (void) mutex_unlock(&rsi->channel_lock); + + return (status); +} + +/* + * Add the specified resource name to the drreq_q. + */ +static void +add_drreq(script_info_t *rsi, char *resource_name) +{ + rcm_queue_t *head = &rsi->drreq_q; + rcm_queue_t *q; + drreq_t *drreq; + + /* check if the dr req is already in the list */ + for (q = head->next; q != head; q = q->next) { + drreq = RCM_STRUCT_BASE_ADDR(drreq_t, q, queue); + if (strcmp(drreq->resource_name, resource_name) == 0) + /* dr req is already present in the queue */ + return; + } + + drreq = (drreq_t *)rcmscript_calloc(1, sizeof (drreq_t)); + drreq->resource_name = rcmscript_strdup(resource_name); + + rcm_enqueue_tail(&rsi->drreq_q, &drreq->queue); +} + +/* + * Remove the dr req for the specified resource name from the drreq_q. + */ +static void +remove_drreq(script_info_t *rsi, char *resource_name) +{ + rcm_queue_t *head = &rsi->drreq_q; + rcm_queue_t *q; + drreq_t *drreq; + + /* search for dr req and remove from the list */ + for (q = head->next; q != head; q = q->next) { + drreq = RCM_STRUCT_BASE_ADDR(drreq_t, q, queue); + if (strcmp(drreq->resource_name, resource_name) == 0) + break; + } + + if (q != head) { + /* found drreq on the queue */ + rcm_dequeue(&drreq->queue); + free(drreq->resource_name); + free(drreq); + } +} + +/* + * Remove all dr req's. + */ +static void +remove_drreq_all(script_info_t *rsi) +{ + drreq_t *drreq; + + while (rsi->drreq_q.next != &rsi->drreq_q) { + drreq = RCM_STRUCT_BASE_ADDR(drreq_t, + rsi->drreq_q.next, queue); + remove_drreq(rsi, drreq->resource_name); + } +} + +/* + * request_offline entry point + */ +/* ARGSUSED */ +static int +script_request_offline(rcm_handle_t *hdl, + char *resource_name, + pid_t pid, + uint_t flag, + char **info, + rcm_info_t **dependent_info) +{ + script_info_t *rsi = hdl->module->rsi; + char *argv[MAX_ARGS]; + char *envp[MAX_ENV_PARAMS]; + char flags_name[MAX_FLAGS_NAME_LEN]; + int status; + int i; + + rcm_log_message(RSCR_TRACE, + "script_request_offline: resource = %s flags = %s\n", + resource_name, + flags_to_name(flag, flags_name, MAX_FLAGS_NAME_LEN)); + + *info = NULL; + + (void) mutex_lock(&rsi->channel_lock); + + rsi->hdl = hdl; + rsi->cmd = (flag & RCM_QUERY) ? C_QUERYREMOVE : C_PREREMOVE; + + if (rsi->cmd == C_PREREMOVE) + add_drreq(rsi, resource_name); + + fill_argv(rsi, argv, resource_name); + copy_env(script_env, envp); + for (i = 0; envp[i] != NULL; i++) + ; + envp[i++] = (flag & RCM_FORCE) ? script_env_force : script_env_noforce; + envp[i] = NULL; + + status = do_dr(rsi, argv, envp, info); + + (void) mutex_unlock(&rsi->channel_lock); + return (status); +} + +/* + * notify_online entry point + */ +/* ARGSUSED */ +static int +script_notify_online(rcm_handle_t *hdl, + char *resource_name, + pid_t pid, + uint_t flag, + char **info, + rcm_info_t **dependent_info) +{ + script_info_t *rsi = hdl->module->rsi; + char *argv[MAX_ARGS]; + int status; + + rcm_log_message(RSCR_TRACE, "script_notify_online: resource = %s\n", + resource_name); + + *info = NULL; + + (void) mutex_lock(&rsi->channel_lock); + + rsi->hdl = hdl; + rsi->cmd = C_UNDOREMOVE; + fill_argv(rsi, argv, resource_name); + + status = do_dr(rsi, argv, script_env, info); + + remove_drreq(rsi, resource_name); + + (void) mutex_unlock(&rsi->channel_lock); + return (status); +} + +/* + * notify_remove entry point + */ +/* ARGSUSED */ +static int +script_notify_remove(rcm_handle_t *hdl, + char *resource_name, + pid_t pid, + uint_t flag, + char **info, + rcm_info_t **dependent_info) +{ + script_info_t *rsi = hdl->module->rsi; + char *argv[MAX_ARGS]; + int status; + + rcm_log_message(RSCR_TRACE, "script_notify_remove: resource = %s\n", + resource_name); + + *info = NULL; + + (void) mutex_lock(&rsi->channel_lock); + + rsi->hdl = hdl; + rsi->cmd = C_POSTREMOVE; + fill_argv(rsi, argv, resource_name); + + status = do_dr(rsi, argv, script_env, info); + + remove_drreq(rsi, resource_name); + + (void) mutex_unlock(&rsi->channel_lock); + return (status); +} + +/* + * request_suspend entry point + */ +/* ARGSUSED */ +static int +script_request_suspend(rcm_handle_t *hdl, + char *resource_name, + pid_t pid, + timespec_t *interval, + uint_t flag, + char **info, + rcm_info_t **dependent_info) +{ + script_info_t *rsi = hdl->module->rsi; + char *buf = NULL; + char *curptr = NULL; + char *argv[MAX_ARGS]; + char *envp[MAX_ENV_PARAMS]; + char flags_name[MAX_FLAGS_NAME_LEN]; + int buflen = 0; + long seconds; + int status; + int i; + + rcm_log_message(RSCR_TRACE, + "script_request_suspend: resource = %s flags = %s\n", resource_name, + flags_to_name(flag, flags_name, MAX_FLAGS_NAME_LEN)); + + *info = NULL; + + (void) mutex_lock(&rsi->channel_lock); + + rsi->hdl = hdl; + rsi->cmd = (flag & RCM_QUERY) ? C_QUERYSUSPEND : C_PRESUSPEND; + + if (rsi->cmd == C_PRESUSPEND) + add_drreq(rsi, resource_name); + + fill_argv(rsi, argv, resource_name); + + copy_env(script_env, envp); + for (i = 0; envp[i] != NULL; i++); + + envp[i++] = (flag & RCM_FORCE) ? script_env_force : script_env_noforce; + + if (interval) { + /* + * Merge the seconds and nanoseconds, rounding up if there + * are any remainder nanoseconds. + */ + seconds = interval->tv_sec + (interval->tv_nsec / 1000000000L); + if (interval->tv_nsec % 1000000000L) + seconds += (interval->tv_sec > 0) ? 1L : -1L; + rcmscript_snprintf(&buf, &buflen, &curptr, script_env_interval, + seconds); + envp[i++] = buf; + } + + envp[i] = NULL; + + status = do_dr(rsi, argv, envp, info); + + (void) mutex_unlock(&rsi->channel_lock); + if (buf) + free(buf); + return (status); +} + +/* + * notify_resume entry point + */ +/* ARGSUSED */ +static int +script_notify_resume(rcm_handle_t *hdl, + char *resource_name, + pid_t pid, + uint_t flag, + char **info, + rcm_info_t **dependent_info) +{ + script_info_t *rsi = hdl->module->rsi; + char *argv[MAX_ARGS]; + int status; + + rcm_log_message(RSCR_TRACE, "script_notify_resume: resource = %s\n", + resource_name); + + *info = NULL; + + (void) mutex_lock(&rsi->channel_lock); + + rsi->hdl = hdl; + rsi->cmd = (flag & RCM_SUSPENDED) ? C_POSTRESUME : C_CANCELSUSPEND; + fill_argv(rsi, argv, resource_name); + + status = do_dr(rsi, argv, script_env, info); + + remove_drreq(rsi, resource_name); + + (void) mutex_unlock(&rsi->channel_lock); + return (status); +} + +static capacity_descr_t capacity_type[] = { + { "SUNW_memory", MATCH_EXACT, + "new_pages", "RCM_ENV_CAPACITY", + "page_size", "RCM_ENV_UNIT_SIZE", + "", ""}, + { "SUNW_cpu", MATCH_EXACT, + "new_total", "RCM_ENV_CAPACITY", + "new_cpu_list", "RCM_ENV_CPU_IDS", + "", ""}, + { "SUNW_cpu/set", MATCH_PREFIX, + "new_total", "RCM_ENV_CAPACITY", + "new_cpu_list", "RCM_ENV_CPU_IDS", + "", ""}, + { "", MATCH_INVALID, "", "" } +}; + +static capacity_descr_t * +get_capacity_descr(char *resource_name) +{ + int i; + + for (i = 0; *capacity_type[i].resource_name != '\0'; i++) { + if ((capacity_type[i].match_type == MATCH_EXACT && + strcmp(capacity_type[i].resource_name, + resource_name) == 0) || + (capacity_type[i].match_type == MATCH_PREFIX && + strncmp(capacity_type[i].resource_name, + resource_name, + strlen(capacity_type[i].resource_name)) == 0)) + + return (&capacity_type[i]); + } + + return (NULL); +} + +static int +build_env_for_capacity(script_info_t *rsi, + char *resource_name, + uint_t flag, + nvlist_t *capacity_info, + char *envp[], + int *dynamic_env_index, + char **errmsg) +{ + int p, i; + capacity_descr_t *capa = NULL; + nvpair_t *nvpair; + char *buf; + char *curptr; + int buflen; + int error; + uint_t n; + + copy_env(script_env, envp); + for (p = 0; envp[p] != NULL; p++) + ; + + if (rsi->cmd == C_QUERYCAPACITY || rsi->cmd == C_PRECAPACITY) + envp[p++] = (flag & RCM_FORCE) ? script_env_force : + script_env_noforce; + + envp[p] = NULL; + *dynamic_env_index = p; + + if ((capa = get_capacity_descr(resource_name)) == NULL) { + *errmsg = dup_err(RCM_ERROR, MF_UNKNOWN_RSRC_ERR, + resource_name, rsi->script_name); + return (-1); + } + + for (i = 0; *capa->param[i].nvname != '\0'; i++) { + nvpair = NULL; + while ((nvpair = nvlist_next_nvpair(capacity_info, nvpair)) + != NULL) { + if (strcmp(nvpair_name(nvpair), + capa->param[i].nvname) == 0) + break; + } + + if (nvpair == NULL) { + *errmsg = dup_err(RCM_ERROR, MF_NV_ERR, + rsi->script_name); + return (-1); + } + + error = 0; + buf = NULL; + + rcmscript_snprintf(&buf, &buflen, &curptr, "%s=", + capa->param[i].envname); + + switch (nvpair_type(nvpair)) { + case DATA_TYPE_INT16: + { + int16_t x; + + if (nvpair_value_int16(nvpair, &x) == 0) { + rcmscript_snprintf(&buf, &buflen, &curptr, + "%hd", (short)x); + } else + error = 1; + break; + } + + case DATA_TYPE_UINT16: + { + uint16_t x; + + if (nvpair_value_uint16(nvpair, &x) == 0) { + rcmscript_snprintf(&buf, &buflen, &curptr, + "%hu", (unsigned short)x); + } else + error = 1; + break; + } + + case DATA_TYPE_INT32: + { + int32_t x; + + if (nvpair_value_int32(nvpair, &x) == 0) { + rcmscript_snprintf(&buf, &buflen, &curptr, + "%d", (int)x); + } else + error = 1; + break; + } + + case DATA_TYPE_UINT32: + { + uint32_t x; + + if (nvpair_value_uint32(nvpair, &x) == 0) { + rcmscript_snprintf(&buf, &buflen, &curptr, + "%u", (uint_t)x); + } else + error = 1; + break; + } + + case DATA_TYPE_INT64: + { + int64_t x; + + if (nvpair_value_int64(nvpair, &x) == 0) { + rcmscript_snprintf(&buf, &buflen, &curptr, + "%lld", (long long)x); + } else + error = 1; + break; + } + + case DATA_TYPE_UINT64: + { + uint64_t x; + + if (nvpair_value_uint64(nvpair, &x) == 0) { + rcmscript_snprintf(&buf, &buflen, &curptr, + "%llu", (unsigned long long)x); + } else + error = 1; + break; + } + + case DATA_TYPE_INT16_ARRAY: + { + int16_t *x; + + if (nvpair_value_int16_array(nvpair, &x, &n) == 0) { + while (n--) { + rcmscript_snprintf(&buf, &buflen, + &curptr, "%hd%s", + (short)(*x), + (n == 0) ? "" : " "); + x++; + } + } else + error = 1; + break; + } + + case DATA_TYPE_UINT16_ARRAY: + { + uint16_t *x; + + if (nvpair_value_uint16_array(nvpair, &x, &n) == 0) { + while (n--) { + rcmscript_snprintf(&buf, &buflen, + &curptr, "%hu%s", + (unsigned short)(*x), + (n == 0) ? "" : " "); + x++; + } + } else + error = 1; + break; + } + + case DATA_TYPE_INT32_ARRAY: + { + int32_t *x; + + if (nvpair_value_int32_array(nvpair, &x, &n) == 0) { + while (n--) { + rcmscript_snprintf(&buf, &buflen, + &curptr, "%d%s", + (int)(*x), + (n == 0) ? "" : " "); + x++; + } + } else + error = 1; + break; + } + + case DATA_TYPE_UINT32_ARRAY: + { + uint32_t *x; + + if (nvpair_value_uint32_array(nvpair, &x, &n) == 0) { + while (n--) { + rcmscript_snprintf(&buf, &buflen, + &curptr, "%u%s", + (uint_t)(*x), + (n == 0) ? "" : " "); + x++; + } + } else + error = 1; + break; + } + + case DATA_TYPE_INT64_ARRAY: + { + int64_t *x; + + if (nvpair_value_int64_array(nvpair, &x, &n) == 0) { + while (n--) { + rcmscript_snprintf(&buf, &buflen, + &curptr, "%lld%s", + (long long)(*x), + (n == 0) ? "" : " "); + x++; + } + } else + error = 1; + break; + } + + case DATA_TYPE_UINT64_ARRAY: + { + uint64_t *x; + + if (nvpair_value_uint64_array(nvpair, &x, &n) == 0) { + while (n--) { + rcmscript_snprintf(&buf, &buflen, + &curptr, "%llu%s", + (unsigned long long)(*x), + (n == 0) ? "" : " "); + x++; + } + } else + error = 1; + break; + } + + case DATA_TYPE_STRING: + { + char *x; + + if (nvpair_value_string(nvpair, &x) == 0) { + rcmscript_snprintf(&buf, &buflen, &curptr, + "%s", x); + } else + error = 1; + break; + } + + + default: + error = 1; + break; + } + + envp[p++] = buf; + + if (error) { + envp[p] = NULL; + for (p = *dynamic_env_index; envp[p] != NULL; p++) + free(envp[p]); + *errmsg = dup_err(RCM_ERROR, MF_NV_ERR, + rsi->script_name); + return (-1); + } + } + + envp[p] = NULL; + + return (0); +} + +/* + * request_capacity_change entry point + */ +/* ARGSUSED */ +static int +script_request_capacity_change(rcm_handle_t *hdl, + char *resource_name, + pid_t pid, + uint_t flag, + nvlist_t *capacity_info, + char **info, + rcm_info_t **dependent_info) +{ + script_info_t *rsi = hdl->module->rsi; + char *argv[MAX_ARGS]; + char *envp[MAX_ENV_PARAMS]; + char flags_name[MAX_FLAGS_NAME_LEN]; + int status; + int dynamic_env_index; + + rcm_log_message(RSCR_TRACE, + "script_request_capacity_change: resource = %s flags = %s\n", + resource_name, + flags_to_name(flag, flags_name, MAX_FLAGS_NAME_LEN)); + + *info = NULL; + + (void) mutex_lock(&rsi->channel_lock); + + rsi->hdl = hdl; + rsi->cmd = (flag & RCM_QUERY) ? C_QUERYCAPACITY : C_PRECAPACITY; + fill_argv(rsi, argv, resource_name); + + if (build_env_for_capacity(rsi, resource_name, flag, + capacity_info, envp, &dynamic_env_index, info) == 0) { + + status = do_dr(rsi, argv, envp, info); + + while (envp[dynamic_env_index] != NULL) { + free(envp[dynamic_env_index]); + dynamic_env_index++; + } + } else + status = RCM_FAILURE; + + (void) mutex_unlock(&rsi->channel_lock); + return (status); +} + +/* + * notify_capacity_change entry point + */ +/* ARGSUSED */ +static int +script_notify_capacity_change(rcm_handle_t *hdl, + char *resource_name, + pid_t pid, + uint_t flag, + nvlist_t *capacity_info, + char **info, + rcm_info_t **dependent_info) +{ + script_info_t *rsi = hdl->module->rsi; + char *argv[MAX_ARGS]; + char *envp[MAX_ENV_PARAMS]; + int status; + int dynamic_env_index; + + rcm_log_message(RSCR_TRACE, + "script_notify_capacity_change: resource = %s\n", resource_name); + + *info = NULL; + + (void) mutex_lock(&rsi->channel_lock); + + rsi->hdl = hdl; + rsi->cmd = C_POSTCAPACITY; + fill_argv(rsi, argv, resource_name); + + if (build_env_for_capacity(rsi, resource_name, flag, + capacity_info, envp, &dynamic_env_index, info) == 0) { + + status = do_dr(rsi, argv, envp, info); + + while (envp[dynamic_env_index] != NULL) { + free(envp[dynamic_env_index]); + dynamic_env_index++; + } + } else + status = RCM_FAILURE; + + (void) mutex_unlock(&rsi->channel_lock); + return (status); +} + +/* Log the message to syslog */ +static void +log_msg(script_info_t *rsi, int level, char *msg) +{ + rcm_log_msg(level, MS_LOG_MSG, rsi->script_name, msg); +} + +/*PRINTFLIKE2*/ +static char * +dup_err(int level, char *format, ...) +{ + va_list ap; + char buf1[1]; + char *buf2; + int n; + + va_start(ap, format); + n = vsnprintf(buf1, 1, format, ap); + va_end(ap); + + if (n > 0) { + n++; + if (buf2 = (char *)malloc(n)) { + va_start(ap, format); + n = vsnprintf(buf2, n, format, ap); + va_end(ap); + if (n > 0) { + if (level != -1) + rcm_log_message(level, buf2); + return (buf2); + } + free(buf2); + } + } + + return (NULL); +} + +/*PRINTFLIKE4*/ +static void +rcmscript_snprintf(char **buf, int *buflen, char **curptr, char *format, ...) +{ +/* must be power of 2 otherwise RSCR_ROUNDUP would break */ +#define SPRINTF_CHUNK_LEN 512 +#define SPRINTF_MIN_CHUNK_LEN 64 + + va_list ap; + int offset, bytesneeded, bytesleft, error_num; + + if (*buf == NULL) { + *buflen = 0; + *curptr = NULL; + } + + offset = *curptr - *buf; + bytesneeded = SPRINTF_MIN_CHUNK_LEN; + bytesleft = *buflen - offset; + + /* LINTED */ + while (1) { + if (bytesneeded > bytesleft) { + *buflen += RSCR_ROUNDUP(bytesneeded - bytesleft, + SPRINTF_CHUNK_LEN); + if ((*buf = (char *)realloc(*buf, *buflen)) == NULL) { + error_num = errno; + rcm_log_message(RCM_ERROR, + MF_MEMORY_ALLOCATION_ERR, + strerror(error_num)); + rcmd_exit(error_num); + /*NOTREACHED*/ + } + *curptr = *buf + offset; + bytesleft = *buflen - offset; + } + + va_start(ap, format); + bytesneeded = vsnprintf(*curptr, bytesleft, format, ap); + va_end(ap); + + if (bytesneeded < 0) { + /* vsnprintf encountered an error */ + error_num = errno; + rcm_log_message(RCM_ERROR, MF_FUNC_CALL_ERR, + "vsnprintf", strerror(error_num)); + rcmd_exit(error_num); + /*NOTREACHED*/ + + } else if (bytesneeded < bytesleft) { + /* vsnprintf succeeded */ + *curptr += bytesneeded; + return; + + } else { + bytesneeded++; /* to account for storage for '\0' */ + } + } +} + +static char * +rcmscript_strdup(char *str) +{ + char *dupstr; + + if ((dupstr = strdup(str)) == NULL) { + rcm_log_message(RCM_ERROR, MF_MEMORY_ALLOCATION_ERR, + strerror(errno)); + rcmd_exit(errno); + /*NOTREACHED*/ + } + + return (dupstr); +} + +static void * +rcmscript_malloc(size_t len) +{ + void *ptr; + + if ((ptr = malloc(len)) == NULL) { + rcm_log_message(RCM_ERROR, MF_MEMORY_ALLOCATION_ERR, + strerror(errno)); + rcmd_exit(errno); + /*NOTREACHED*/ + } + + return (ptr); +} + +static void * +rcmscript_calloc(size_t nelem, size_t elsize) +{ + void *ptr; + + if ((ptr = calloc(nelem, elsize)) == NULL) { + rcm_log_message(RCM_ERROR, MF_MEMORY_ALLOCATION_ERR, + strerror(errno)); + rcmd_exit(errno); + /*NOTREACHED*/ + } + + return (ptr); +} |