/* * Intel(R) Enclosure LED Utilities * Copyright (C) 2009-2019 Intel Corporation. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along with * this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if _HAVE_DMALLOC_H #include #endif #include "ahci.h" #include "block.h" #include "cntrl.h" #include "config.h" #include "config_file.h" #include "ibpi.h" #include "list.h" #include "scsi.h" #include "status.h" #include "sysfs.h" #include "utils.h" #include "version.h" /** * @brief An IBPI state structure. * * This structure connects an IBPI pattern and block devices. It is used by * _determine() function to figure the correct pattern out. */ struct ibpi_state { enum ibpi_pattern ibpi; struct list block_list; }; /** * @brief List of IBPI patterns. * * This is a list of IBPI patterns the user requested to be visualized. * Each element on the list is struct ibpi_state type. There's only one * instance of each IBPI pattern on the list (no duplicates). */ static struct list ibpi_list; /** * @brief IBPI pattern names. * * This is internal array holding names of IBPI pattern. Logging routines use * this entries to translate enumeration type values into the string. */ const char *ibpi_str[] = { [IBPI_PATTERN_UNKNOWN] = "", [IBPI_PATTERN_NORMAL] = "NORMAL", [IBPI_PATTERN_ONESHOT_NORMAL] = "", [IBPI_PATTERN_DEGRADED] = "ICA", [IBPI_PATTERN_REBUILD] = "REBUILD", [IBPI_PATTERN_FAILED_ARRAY] = "IFA", [IBPI_PATTERN_HOTSPARE] = "HOTSPARE", [IBPI_PATTERN_PFA] = "PFA", [IBPI_PATTERN_FAILED_DRIVE] = "FAILURE", [IBPI_PATTERN_LOCATE] = "LOCATE", [IBPI_PATTERN_LOCATE_OFF] = "LOCATE_OFF", [IBPI_PATTERN_ADDED] = "ADDED", [IBPI_PATTERN_REMOVED] = "REMOVED" }; /** * Internal variable of ledctl utility. It is the pattern used to print out * information about the version of ledctl utility. */ static char *ledctl_version = "Intel(R) Enclosure LED Control Application %d.%d %s\n" "Copyright (C) 2009-2019 Intel Corporation.\n"; /** * Internal variable of monitor service. It is used to help parse command line * short options. */ static char *shortopt; struct option *longopt; static int possible_params[] = { OPT_HELP, OPT_LOG, OPT_VERSION, OPT_LIST_CTRL, OPT_LISTED_ONLY, OPT_ALL, OPT_DEBUG, OPT_ERROR, OPT_INFO, OPT_QUIET, OPT_WARNING, OPT_LOG_LEVEL, }; static const int possible_params_size = sizeof(possible_params) / sizeof(possible_params[0]); static int listed_only; static void ibpi_state_fini(struct ibpi_state *p) { list_clear(&p->block_list); free(p); } /** * @brief Finalizes LED control utility. * * This is internal function of ledctl utility. The function cleans up a memory * allocated for the application and closes all opened handles. This function is * design to be registered as on_exit() handler function. * * @param[in] status exit status of the ledctl application. * @param[in] ignored function ignores this argument. * * @return The function does not return a value. */ static void _ledctl_fini(int status __attribute__ ((unused)), void *ignore __attribute__ ((unused))) { sysfs_reset(); list_erase(&ibpi_list); log_close(); } /** * @brief Displays the credits. * * This is internal function of ledctl utility. It prints out the name and * version of the program. It displays the copyright notice and information * about the author and license, too. * * @return The function does not return a value. */ static void _ledctl_version(void) { printf(ledctl_version, VERSION_MAJOR, VERSION_MINOR, BUILD_LABEL); printf("\nThis is free software; see the source for copying conditions." \ " There is NO warranty;\nnot even for MERCHANTABILITY or FITNESS" \ " FOR A PARTICULAR PURPOSE.\n\n"); } /** * @brief Displays the help. * * This is internal function of ledctl utility. The function prints the name * and version of the program out. It displays the usage and available options * and its arguments (if any). Each option is described. This is an extract * from user manual page. * * @return The function does not return a value. */ static void _ledctl_help(void) { printf(ledctl_version, VERSION_MAJOR, VERSION_MINOR, BUILD_LABEL); printf("\nUsage: %s [OPTIONS] pattern=list_of_devices ...\n\n", progname); printf("Mandatory arguments for long options are mandatory for short options, too.\n\n"); print_opt("--listed-only", "-x", "Ledctl will change state only for given devices."); print_opt("--list-controllers", "-L", "Displays list of controllers detected by ledmon."); print_opt("--log=PATH", "-l PATH", "Use local log file instead /var/log/ledctl.log."); print_opt("--help", "-h", "Displays this help text."); print_opt("--version", "-v", "Displays version and license information."); print_opt("--log-level=VALUE", "-l VALUE", "Allows user to set ledctl verbose level in logs."); printf("\nPatterns:\n" "\tCommon patterns are:\n" "\t\tlocate, locate_off, normal, off, degraded, rebuild,\n" "" "\t\tfailed_array, hotspare, pfa, failure, disk_failed\n" "\tSES-2 only patterns:\n" "\t\tses_abort, ses_rebuild, ses_ifa, ses_ica, ses_cons_check,\n" "\t\tses_hotspare, ses_rsvd_dev, ses_ok, ses_ident, ses_rm,\n" "\t\tses_insert, ses_missing, ses_dnr, ses_active, ses_prdfail,\n" "\t\tses_enable_bb, ses_enable_ba, ses_devoff, ses_fault\n" "\tAutomatic translation form IBPI into SES-2:\n" "\t\tlocate=ses_ident, locate_off=~ses_ident,\n" "\t\tnormal=ses_ok, off=ses_ok, degraded=ses_ica,\n" "\t\trebuild=ses_rebuild, failed_array=ses_ifa,\n" "\t\thotspare=ses_hotspare, pfa=ses_prdfail, failure=ses_fault,\n" "\t\tdisk_failed=ses_fault\n"); printf("Refer to ledctl(8) man page for more detailed description.\n"); printf("Bugs should be reported at: " \ "https://github.com/intel/ledmon/issues\n"); } /** * @brief Puts new IBPI state on the list. * * This is internal function of ledctl utility. The function creates a new entry * of the list with IBPI patterns. Each IBPI state has a list of block devices * attached. The function initializes this list and sets empty. * * @param[in] ibpi an IBPI pattern to add. * * @return Pointer to the created element if successful, otherwise function * returns NULL. The NULL pointer means element allocation failed. */ static struct ibpi_state *_ibpi_state_init(enum ibpi_pattern ibpi) { struct ibpi_state *state = malloc(sizeof(struct ibpi_state)); if (!state) return NULL; list_init(&state->block_list, NULL); state->ibpi = ibpi; list_append(&ibpi_list, state); return state; } /** * @brief Sets a state of block device. * * This is internal function of ledctl utility. The function sets * an IBPI pattern for block devices. The function is design to be used * as action parameter of list_for_each() function. * * @param[in] state pointer to structure holding the IBPI pattern * identifier and list of block devices. * * @return The function does not return a value. */ static void _determine(struct ibpi_state *state) { if (list_is_empty(&state->block_list) == 0) { struct block_device *block; list_for_each(&state->block_list, block) { if (block->ibpi < state->ibpi) block->ibpi = state->ibpi; } } else { log_warning ("IBPI %s: missing block device(s)... pattern ignored.", ibpi2str(state->ibpi)); } } /** * @brief Determines a state of block devices. * * This is internal function of ledctl utility. The functions goes through list * of IBPI states and calls _determine() function for each element. If the list * is empty the function logs a warning message and does nothing. * * @param[in] ibpi_local_list pointer to list of IBPI states. * * @return STATUS_SUCCESS if successful, otherwise a valid status_t status code. * The following status codes function returns: * * STATUS_LIST_EMPTY the specified list has no elements. */ static status_t _ibpi_state_determine(struct list *ibpi_local_list) { if (list_is_empty(ibpi_local_list) == 0) { struct ibpi_state *state; list_for_each(ibpi_local_list, state) _determine(state); return STATUS_SUCCESS; } log_error("missing operand(s)... run %s --help for details.", progname); return STATUS_LIST_EMPTY; } static struct ibpi_state *_ibpi_find(const struct list *ibpi_local_list, enum ibpi_pattern ibpi) { struct ibpi_state *state; list_for_each(ibpi_local_list, state) { if (state->ibpi == ibpi) return state; } return NULL; } /** * @brief Gets a pointer to IBPI state structure. * * This is internal function of ledctl utility. The function retrieves an entry * to an IBPI state structure from ibpi_list list. If such an entry does not * exist the memory is allocated and entry is added to the list. * * @param[in] name a name of IBPI pattern i.e. taken from command * line interface. It might be 'locate', 'normal', * 'locate_off', 'off', 'ica', 'degraded', 'rebuild', * 'ifa', 'failed_array', 'hotspare', * 'pfa', 'failure' or 'disk_failed' string. * * @return Pointer to IBPI state structure if successful, otherwise the function * returns NULL. The NULL pointer means either the invalid status name * has been given or there's not enough memory available in the system * to allocate the structure. */ static struct ibpi_state *_ibpi_state_get(const char *name) { struct ibpi_state *state = NULL; enum ibpi_pattern ibpi; if (strcmp(name, "locate") == 0) { ibpi = IBPI_PATTERN_LOCATE; } else if (strcmp(name, "locate_off") == 0) { ibpi = IBPI_PATTERN_LOCATE_OFF; } else if (strcmp(name, "normal") == 0) { ibpi = IBPI_PATTERN_NORMAL; } else if (strcmp(name, "off") == 0) { ibpi = IBPI_PATTERN_NORMAL; } else if ((strcmp(name, "ica") == 0) || (strcmp(name, "degraded") == 0)) { ibpi = IBPI_PATTERN_DEGRADED; } else if (strcmp(name, "rebuild") == 0) { ibpi = IBPI_PATTERN_REBUILD; } else if ((strcmp(name, "ifa") == 0) || (strcmp(name, "failed_array") == 0)) { ibpi = IBPI_PATTERN_FAILED_ARRAY; } else if (strcmp(name, "hotspare") == 0) { ibpi = IBPI_PATTERN_HOTSPARE; } else if (strcmp(name, "pfa") == 0) { ibpi = IBPI_PATTERN_PFA; } else if ((strcmp(name, "failure") == 0) || (strcmp(name, "disk_failed") == 0)) { ibpi = IBPI_PATTERN_FAILED_DRIVE; } else if (strcmp(name, "ses_abort") == 0) { ibpi = SES_REQ_ABORT; } else if (strcmp(name, "ses_rebuild") == 0) { ibpi = SES_REQ_REBUILD; } else if (strcmp(name, "ses_ifa") == 0) { ibpi = SES_REQ_IFA; } else if (strcmp(name, "ses_ica") == 0) { ibpi = SES_REQ_ICA; } else if (strcmp(name, "ses_cons_check") == 0) { ibpi = SES_REQ_CONS_CHECK; } else if (strcmp(name, "ses_hotspare") == 0) { ibpi = SES_REQ_HOSTSPARE; } else if (strcmp(name, "ses_rsvd_dev") == 0) { ibpi = SES_REQ_RSVD_DEV; } else if (strcmp(name, "ses_ok") == 0) { ibpi = SES_REQ_OK; } else if (strcmp(name, "ses_ident") == 0) { ibpi = SES_REQ_IDENT; } else if (strcmp(name, "ses_rm") == 0) { ibpi = SES_REQ_RM; } else if (strcmp(name, "ses_insert") == 0) { ibpi = SES_REQ_INS; } else if (strcmp(name, "ses_missing") == 0) { ibpi = SES_REQ_MISSING; } else if (strcmp(name, "ses_dnr") == 0) { ibpi = SES_REQ_DNR; } else if (strcmp(name, "ses_active") == 0) { ibpi = SES_REQ_ACTIVE; } else if (strcmp(name, "ses_enable_bb") == 0) { ibpi = SES_REQ_EN_BB; } else if (strcmp(name, "ses_enable_ba") == 0) { ibpi = SES_REQ_EN_BA; } else if (strcmp(name, "ses_devoff") == 0) { ibpi = SES_REQ_DEV_OFF; } else if (strcmp(name, "ses_fault") == 0) { ibpi = SES_REQ_FAULT; } else if (strcmp(name, "ses_prdfail") == 0) { ibpi = SES_REQ_PRDFAIL; } else { return NULL; } state = _ibpi_find(&ibpi_list, ibpi); if (state == NULL) state = _ibpi_state_init(ibpi); return state; } static struct block_device *_block_device_search(const struct list *block_list, const char *path) { struct block_device *block; list_for_each(block_list, block) { if (strcmp(block->sysfs_path, path) == 0) return block; } return NULL; } /** * @brief Adds a block device to a block list. * * This is internal function of ledctl utility. Each IBPI state has list of * block devices attached to. The function puts a pointer to a block device * on that list. First the function determines the canonical version of the * given path and checks if it is correct. If the path to /dev directory is * given the function finds out the correct entry in sysfs tree. * * @param[in] state pointer to IBPI state structure the block * device will be added to. * @param[in] block pointer to block device structure. * * @return The function does not return a value. */ static status_t _ibpi_state_add_block(struct ibpi_state *state, char *name) { struct stat st; char temp[PATH_MAX], path[PATH_MAX]; struct block_device *blk1, *blk2; if ((realpath(name, temp) == NULL) && (errno != ENOTDIR)) return STATUS_INVALID_PATH; if (strstr(temp, "/dev/") != NULL) { if (stat(temp, &st) < 0) return STATUS_STAT_ERROR; sprintf(temp, "/sys/dev/block/%u:%u", major(st.st_rdev), minor(st.st_rdev)); if ((realpath(temp, path) == NULL) && (errno != ENOTDIR)) return STATUS_INVALID_PATH; } else { str_cpy(path, temp, PATH_MAX); } blk1 = _block_device_search(sysfs_get_block_devices(), path); if (blk1 == NULL) { log_error("%s: device not supported", name); return STATUS_NOT_SUPPORTED; } blk2 = _block_device_search(&state->block_list, path); if (blk2 == NULL) list_append(&state->block_list, blk1); else log_info("%s: %s: device already on the list.", ibpi2str(state->ibpi), path); return STATUS_SUCCESS; } /** * @brief Command line parser - operands. * * This is internal function of ledctl utility. The function parses operands of * ledctl application. The operands section contains the pattern name and a list * of block devices associated with each pattern. There are two different * formats for the operand. First format is pattern={ dev_list }, where elements * are space separated on the dev_list. Second format is pattern=dev1,dev2,... * where elements are comma separated on the list of devices. * * @param[in] argc number of elements in argv array. * @param[in] argv command line arguments. * * @return STATUS_SUCCESS if successful, otherwise a valid status_t status code. */ static status_t _cmdline_ibpi_parse(int argc, char *argv[]) { status_t t_status, ret_status = STATUS_SUCCESS; while (optind < argc) { struct ibpi_state *state = NULL; char *p = argv[optind++]; char *t; t = strchrnul(p, '='); if (*t != '\0') { *(t++) = '\0'; state = _ibpi_state_get(p); if (state == NULL) { log_error("%s - unknown pattern name.", p); return STATUS_INVALID_STATE; } if (*t == '{') { while ((t = argv[optind++]) != NULL) { if (*t == '}') break; t_status = _ibpi_state_add_block(state, t); if (t_status != STATUS_SUCCESS) ret_status = t_status; } } else { while (*(p = t) != '\0') { t = strchrnul(p, ','); if (*t != '\0') *(t++) = '\0'; t_status = _ibpi_state_add_block(state, p); if (t_status != STATUS_SUCCESS) ret_status = t_status; } } } } if (_ibpi_state_determine(&ibpi_list) != STATUS_SUCCESS) ret_status = STATUS_IBPI_DETERMINE_ERROR; return ret_status; } /** * @brief Command line parser - options. * * This is internal function of ledctl utility. The function parses options of * ledctl application. Refer to ledctl help in order to get more information * about ledctl command line options. * * @param[in] argc number of elements in argv array. * @param[in] argv command line arguments. * * @return STATUS_SUCCESS if successful, otherwise a valid status_t status code. */ static status_t _cmdline_parse(int argc, char *argv[]) { int opt, opt_index = -1; status_t status = STATUS_SUCCESS; do { opt = getopt_long(argc, argv, shortopt, longopt, &opt_index); if (opt == -1) break; switch (opt) { int log_level; case 0: switch (get_option_id(longopt[opt_index].name)) { case OPT_LOG_LEVEL: log_level = get_option_id(optarg); if (log_level != -1) status = set_verbose_level(log_level); else status = STATUS_CMDLINE_ERROR; break; default: status = set_verbose_level( possible_params[opt_index]); } break; case 'v': _ledctl_version(); exit(EXIT_SUCCESS); case 'h': _ledctl_help(); exit(EXIT_SUCCESS); case 'l': status = set_log_path(optarg); break; case 'x': status = STATUS_SUCCESS; listed_only = 1; break; case 'L': { struct cntrl_device *ctrl_dev; sysfs_init(); sysfs_scan(); list_for_each(sysfs_get_cntrl_devices(), ctrl_dev) print_cntrl(ctrl_dev); sysfs_reset(); exit(EXIT_SUCCESS); } case ':': case '?': default: log_debug("[opt='%c', opt_index=%d]", opt, opt_index); return STATUS_CMDLINE_ERROR; } opt_index = -1; if (status != STATUS_SUCCESS) return status; } while (1); return STATUS_SUCCESS; } /** * @brief Determine and send IBPI pattern. * * This is internal function of ledctl utility. The function determines a state * of block device based on ibpi_list list. Then it sends a LED control message * to controller to visualize the pattern. * * @param[in] sysfs pointer to sysfs structure holding information * about the existing controllers, block devices, * and software RAID devices. * @param[in] ibpi_local_list TBD * * @return STATUS_SUCCESS if successful, otherwise a valid status_t status code. */ static status_t _ledctl_execute(struct list *ibpi_local_list) { struct ibpi_state *state; struct block_device *device; if (!listed_only) { list_for_each(sysfs_get_block_devices(), device) device->send_fn(device, IBPI_PATTERN_LOCATE_OFF); } list_for_each(ibpi_local_list, state) list_for_each(&state->block_list, device) device->send_fn(device, device->ibpi); list_for_each(sysfs_get_block_devices(), device) device->flush_fn(device); return STATUS_SUCCESS; } static status_t _read_shared_conf(void) { status_t status; char share_conf_path[PATH_MAX]; memset(share_conf_path, 0, sizeof(share_conf_path)); snprintf(share_conf_path, sizeof(share_conf_path), "/dev/shm%s", LEDMON_SHARE_MEM_FILE); status = ledmon_read_config(share_conf_path); return status; } static status_t _init_ledctl_conf(void) { memset(&conf, 0, sizeof(struct ledmon_conf)); /* initialize with default values */ conf.log_level = LOG_LEVEL_WARNING; list_init(&conf.cntrls_whitelist, NULL); list_init(&conf.cntrls_blacklist, NULL); return set_log_path(LEDCTL_DEF_LOG_FILE); } /** * @brief Application's entry point. * * This is the entry point of ledctl utility. This function does all the work. * It allocates and initializes all used structures. Registers on_exit() * handlers. * Then the function parses command line options and commands given and scans * sysfs tree for controllers, block devices and RAID devices. If no error is * the function send LED control messages according to IBPI pattern set. * * @param[in] argc number of elements in argv array, number of * command line arguments. * @param[in] argv array of command line arguments. The last * element on the list is NULL pointer. * * @return STATUS_SUCCESS if successful, otherwise a valid status_t status code. */ int main(int argc, char *argv[]) { status_t status; setup_options(&longopt, &shortopt, possible_params, possible_params_size); set_invocation_name(argv[0]); openlog(progname, LOG_PERROR, LOG_USER); if (getuid() != 0) { fprintf(stderr, "Only root can run this application.\n"); return STATUS_NOT_A_PRIVILEGED_USER; } status = _init_ledctl_conf(); if (status != STATUS_SUCCESS) return status; if (on_exit(_ledctl_fini, progname)) exit(STATUS_ONEXIT_ERROR); if (_cmdline_parse(argc, argv)) exit(STATUS_CMDLINE_ERROR); free(shortopt); free(longopt); status = _read_shared_conf(); if (status != STATUS_SUCCESS) return status; status = log_open(conf.log_path); if (status != STATUS_SUCCESS) return STATUS_LOG_FILE_ERROR; list_init(&ibpi_list, (item_free_t)ibpi_state_fini); sysfs_init(); sysfs_scan(); status = _cmdline_ibpi_parse(argc, argv); if (status != STATUS_SUCCESS) { log_debug("main(): _ibpi_parse() failed (status=%s).", strstatus(status)); exit(status); } return _ledctl_execute(&ibpi_list); }