/* * 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 #include #include #include /* * 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); }