/*
* Intel(R) Enclosure LED Utilities
* Copyright (C) 2009-2021 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 <config_ac.h>
#include <ctype.h>
#include <errno.h>
#include <getopt.h>
#include <limits.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <syslog.h>
#include <time.h>
#include <unistd.h>
#include <sys/sysmacros.h>
#if _HAVE_DMALLOC_H
#include <dmalloc.h>
#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"
/**
* @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 %s %s\n"
"Copyright (C) 2009-2021 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, PACKAGE_VERSION, 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, PACKAGE_VERSION, 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] name path to block device.
*
* @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 - checks if command line input contains
* options which don't require to run ledctl as root.
*
* The function parses options of ledctl application.
* It handles option to print version and help.
*
* @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_non_root(int argc, char *argv[])
{
int opt_index, opt = -1;
status_t status = STATUS_SUCCESS;
do {
opt = getopt_long(argc, argv, shortopt, longopt, &opt_index);
switch (opt) {
case 'v':
_ledctl_version();
exit(EXIT_SUCCESS);
case 'h':
_ledctl_help();
exit(EXIT_SUCCESS);
case ':':
case '?':
return STATUS_CMDLINE_ERROR;
}
} while (opt >= 0);
return 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;
optind = 1;
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 '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 Send IBPI pattern.
*
* This is internal function of ledctl utility. The function set a requested
* ibpi_state for devices linked with this ibpi_state on ibpi_local_list.
* For other devices IBPI_PATTERN_LOCATE_OFF might be set - depending on
* listed_only parameter. Then it sends a LED control message to controller
* to visualize the pattern.
*
* @param[in] ibpi_local_list pointer to list of ipbi_state.
*
* @return STATUS_SUCCESS if successful, otherwise STATUS_IBPI_DETERMINE_ERROR
*/
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) {
if (state->ibpi != device->ibpi) {
log_debug("Mismatch detected for %s, ibpi state: %s, device state %s\n",
device->sysfs_path, state->ibpi,
device->ibpi);
return STATUS_IBPI_DETERMINE_ERROR;
}
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]);
if (_cmdline_parse_non_root(argc, argv) != STATUS_SUCCESS)
return STATUS_CMDLINE_ERROR;
openlog(progname, LOG_PERROR, LOG_USER);
if (geteuid() != 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);
}