/* * 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 #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 "pidfile.h" #include "raid.h" #include "scsi.h" #include "slave.h" #include "smp.h" #include "status.h" #include "sysfs.h" #include "udev.h" #include "utils.h" #include "version.h" #include "vmdssd.h" /** * @brief List of active block devices. * * This list holds all block devices attached to supported storage controllers. * Only devices which have enclosure management feature enabled are on the * list, other devices are ignored (except protocol is forced). */ static struct list ledmon_block_list; /** * @brief Daemon process termination flag. * * This flag indicates that daemon process should terminate. User must send * SIGTERM to daemon in order to terminate the process gently. */ static sig_atomic_t terminate; /** * @brief Path to ledmon configuration file. * * This string contains path of the ledmon configuration file. The value is * set to LEDMON_DEF_CONF_FILE by default and it can be changed by command line * option. */ static char *ledmon_conf_path; /** * @brief Boolean flag whether to run foreground or not. * * This flag is turned on with --foreground option. Primary use of this option * is to use it in systemd service file. */ static int foreground; /** * @brief Name of IBPI patterns. * * This is internal array with names of IBPI patterns. Logging routines use this * entries to translate enumeration type into the string. */ const char *ibpi_str[] = { [IBPI_PATTERN_UNKNOWN] = "None", [IBPI_PATTERN_NORMAL] = "Off", [IBPI_PATTERN_ONESHOT_NORMAL] = "Oneshot Off", [IBPI_PATTERN_DEGRADED] = "In a Critical Array", [IBPI_PATTERN_REBUILD] = "Rebuild", [IBPI_PATTERN_FAILED_ARRAY] = "In a Failed Array", [IBPI_PATTERN_HOTSPARE] = "Hotspare", [IBPI_PATTERN_PFA] = "Predicted Failure Analysis", [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 monitor service. It is the pattern used to print out * information about the version of monitor service. */ static char *ledmon_version = "Intel(R) Enclosure LED Monitor Service %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_ALL, OPT_CONFIG, OPT_DEBUG, OPT_ERROR, OPT_HELP, OPT_INFO, OPT_INTERVAL, OPT_LOG, OPT_QUIET, OPT_VERSION, OPT_WARNING, OPT_LOG_LEVEL, OPT_FOREGROUND, }; static int possible_params_size = sizeof(possible_params) / sizeof(possible_params[0]); /** * @brief Monitor service finalize function. * * This is internal function of monitor service. It is used to finalize daemon * process i.e. free allocated memory, unlock and remove pidfile and close log * file and syslog. The function is registered as on_exit() handler. * * @param[in] status The function ignores this parameter. * @param[in] program_name The name of the binary file. This argument * is passed via on_exit() function. * * @return The function does not return a value. */ static void _ledmon_fini(int __attribute__ ((unused)) status, void *program_name) { sysfs_reset(); list_erase(&ledmon_block_list); log_close(); pidfile_remove(program_name); } /** * @brief Puts exit status to a log file. * * This is internal function of monitor service. It is used to report an exit * status of the monitor service. The message is logged in to syslog and to log * file. The function is registered as on_exit() hander. * * @param[in] status Status given in the last call to exit() * function. * @param[in] arg Argument passed to on_exit(). * * @return The function does not return a value. */ static void _ledmon_status(int status, void *arg) { int log_level; char message[4096]; int ignore = *((int *)arg); if (ignore) return; if (status == STATUS_SUCCESS) log_level = LOG_LEVEL_INFO; else log_level = LOG_LEVEL_ERROR; snprintf(message, sizeof(message), "exit status is %s.", strstatus(status)); if (get_log_fd() >= 0) _log(log_level, message); else syslog(log_level_infos[log_level].priority, "%s", message); } /** * @brief Displays the credits. * * This is internal function of monitor service. 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 _ledmon_version(void) { printf(ledmon_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 monitor service. 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 _ledmon_help(void) { printf(ledmon_version, VERSION_MAJOR, VERSION_MINOR, BUILD_LABEL); printf("\nUsage: %s [OPTIONS]\n\n", progname); printf("Mandatory arguments for long options are mandatory for short " "options, too.\n\n"); print_opt("--interval=VALUE", "-t VALUE", "Set time interval to VALUE seconds."); print_opt("", "", "The smallest interval is 5 seconds."); print_opt("--config=PATH", "-c PATH", "Use alternate configuration file."); print_opt("--log=PATH", "-l PATH", "Use local log file instead /var/log/ledmon.log"); print_opt("--log-level=VALUE", "-l VALUE", "Allows user to set ledmon verbose level in logs."); print_opt("--foreground", "", "Do not run as daemon."); print_opt("--help", "-h", "Displays this help text."); print_opt("--version", "-v", "Displays version and license information."); printf("\nRefer to ledmon(8) man page for more detailed description.\n"); printf("Bugs should be reported at: https://github.com/intel/ledmon/issues\n"); } /** * @brief Sets the path to configuration file. * * This is internal function of monitor service. This function sets the path and * name of configuration file. The function is checking whether the given path * is valid or it is invalid and should be ignored. * * @param[in] path the new location and name of config file. * * @return STATUS_SUCCESS if successful, otherwise a valid status_t status code. */ static status_t _set_config_path(char **conf_path, const char *path) { if (!path) path = LEDMON_DEF_CONF_FILE; if (*conf_path) free(*conf_path); *conf_path = str_dup(path); return STATUS_SUCCESS; } /** * @brief Sets the value of sleep interval. * * This function is used by command line handler to set new value of time * interval, @see time_interval for details. * * @param[in] optarg String containing the new value of time * interval, given in command line option. * * @return STATUS_SUCCESS if successful, otherwise a valid status_t status code. */ static status_t _set_sleep_interval(const char *optarg) { conf.scan_interval = atoi(optarg); if (conf.scan_interval < LEDMON_MIN_SLEEP_INTERVAL) { log_warning("sleep interval too small... using default."); conf.scan_interval = LEDMON_DEF_SLEEP_INTERVAL; } return STATUS_SUCCESS; } /** * @brief Reads config file path and checks if command line input contains * options which don't require to run ledmon as daemon. * * This is internal function of monitor service. This function looks for * config file path in command line options given to the program from * command line interface. It also handles options to print help and version. * * @param[in] argc - number of arguments. * @param[in] argv - array of command line arguments. * * @return STATUS_SUCCESS if successful, otherwise a valid status_t status code. */ static status_t _cmdline_parse_non_daemonise(int argc, char *argv[]) { int opt_index = -1; int opt = -1; status_t status = STATUS_SUCCESS; do { opt = getopt_long(argc, argv, shortopt, longopt, &opt_index); switch (opt) { case 'c': status = _set_config_path(&ledmon_conf_path, optarg); break; case 'h': _ledmon_help(); exit(EXIT_SUCCESS); case 'v': _ledmon_version(); exit(EXIT_SUCCESS); case ':': case '?': return STATUS_CMDLINE_ERROR; } } while (opt >= 0); return status; } /** * @brief Command line interface handler function. * * This is internal function of monitor service. This function interprets the * options and commands given to the program from command line interface. * * @param[in] argc - number of arguments. * @param[in] argv - array of 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; if (opt == 'c') continue; 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; case OPT_FOREGROUND: foreground = 1; break; default: status = set_verbose_level( possible_params[opt_index]); } break; case 'l': status = set_log_path(optarg); break; case 't': status = _set_sleep_interval(optarg); break; } opt_index = -1; if (status != STATUS_SUCCESS) return status; } while (1); return STATUS_SUCCESS; } /** * @brief SIGTERM handler function. * * This is internal function of monitor service. * * @param[in] signum - the number of signal received. * * @return The function does not return a value. */ static void _ledmon_sig_term(int signum) { if (signum == SIGTERM) { log_info("SIGTERM caught - terminating daemon process."); terminate = 1; } } /** * @brief Configures signal handlers. * * This is internal function of monitor services. It sets to ignore SIGALRM, * SIGHUP and SIGPIPE signals. The function installs a handler for SIGTERM * signal. User must send SIGTERM to daemon process in order to shutdown the * daemon gently. * * @return The function does not return a value. */ static void _ledmon_setup_signals(void) { struct sigaction act; sigset_t sigset; sigemptyset(&sigset); sigaddset(&sigset, SIGALRM); sigaddset(&sigset, SIGHUP); sigaddset(&sigset, SIGTERM); sigaddset(&sigset, SIGPIPE); sigaddset(&sigset, SIGUSR1); sigprocmask(SIG_BLOCK, &sigset, NULL); act.sa_handler = SIG_IGN; act.sa_flags = 0; sigemptyset(&act.sa_mask); sigaction(SIGALRM, &act, NULL); sigaction(SIGHUP, &act, NULL); sigaction(SIGPIPE, &act, NULL); act.sa_handler = _ledmon_sig_term; sigaction(SIGTERM, &act, NULL); sigaction(SIGUSR1, &act, NULL); sigprocmask(SIG_UNBLOCK, &sigset, NULL); } /** * @brief Puts the calling process into sleep. * * This is internal function of monitor service. The function puts the calling * process into a sleep for the given amount of time (expressed in seconds). The * function will give control back to the process as soon as time elapses or * SIGTERM occurs. * * @param[in] seconds - the time interval given in seconds. * * @return The function does not return a value. */ static void _ledmon_wait(int seconds) { int fd, udev_fd, max_fd, res; fd_set rdfds, exfds; struct timespec timeout; sigset_t sigset; sigprocmask(SIG_UNBLOCK, NULL, &sigset); sigdelset(&sigset, SIGTERM); timeout.tv_nsec = 0; timeout.tv_sec = seconds; fd = open("/proc/mdstat", O_RDONLY); udev_fd = get_udev_monitor(); max_fd = MAX(fd, udev_fd) + 1; do { FD_ZERO(&rdfds); FD_ZERO(&exfds); if (fd > 0) FD_SET(fd, &exfds); if (udev_fd > 0) FD_SET(udev_fd, &rdfds); res = pselect(max_fd, &rdfds, NULL, &exfds, &timeout, &sigset); if (terminate || !FD_ISSET(udev_fd, &rdfds) || handle_udev_event(&ledmon_block_list) <= 0) break; } while (res > 0); if (fd >= 0) close(fd); } /** * @brief Determine failed state by comparing saved block device with new * scanned. * * This is internal function of monitor service. Due race conditions related * with removing files from /sys/block/md* when raid is stopped or disk is * failed, this function analyse state of every block device between scans. * * @param[in] block Pointer to new (scanned) block device * structure. * @param[in] temp Pointer to previously saved state of block * device structure. * * @return The function does not return a value. */ static void _handle_fail_state(struct block_device *block, struct block_device *temp) { struct raid_device *temp_raid_device = NULL; if (!temp->raid_dev) /* * Device is a RAID member now, so keep information about * related with device RAID. */ temp->raid_dev = raid_device_duplicate(block->raid_dev); if (!temp->raid_dev) return; temp_raid_device = find_raid_device(sysfs_get_volumes(), temp->raid_dev->sysfs_path); if (!block->raid_dev) { if (temp->raid_dev->type == DEVICE_TYPE_VOLUME && temp_raid_device) { /* * Device is outside of the volume, but related raid * still exist, so disk has been removed from volume - * blink fail LED. It is case when drive is removed * by mdadm -If. */ temp->ibpi = IBPI_PATTERN_FAILED_DRIVE; /* * Changing failed state to hotspare will be prevent by * code from _add_block function. If disk come back to * container failed state should be removed. By setting * type to CONTAINER ledmon can react in this case. */ temp->raid_dev->type = DEVICE_TYPE_CONTAINER; } else { /* * Device was RAID member and was failed (was outside * of array and container). Now again is in container. * Release object to perform hotspare state. * Or: * Device is outside of the volume, but related raid is * removed (or stopped) so device is no longer a RAID * member. */ raid_device_fini(temp->raid_dev); temp->raid_dev = NULL; } } else if (block->raid_dev) { if (temp->raid_dev->type == DEVICE_TYPE_VOLUME && block->raid_dev->type == DEVICE_TYPE_CONTAINER) { /* * Drive is removed from volume, but still exist * in container. */ enum raid_level new_level; if (!temp_raid_device) new_level = RAID_LEVEL_UNKNOWN; else new_level = temp_raid_device->level; if ((temp->raid_dev->level == RAID_LEVEL_10 || temp->raid_dev->level == RAID_LEVEL_1) && new_level == RAID_LEVEL_0) { /* * Device is removed from volume due to * migration to raid. State of this disk is * hotspare now. */ temp->ibpi = IBPI_PATTERN_HOTSPARE; } else { /* * Trasitions other than raid 0 migration. * Like reshape, volume stopping etc. */ if (temp_raid_device) { /* * Drive is removed from volume, * but still exist in container. This * situation can be caused by bad * blocks or calling mdadm * --set-faulty. */ temp->ibpi = IBPI_PATTERN_FAILED_DRIVE; } } } else if (temp->raid_dev->type == DEVICE_TYPE_CONTAINER && block->raid_dev->type == DEVICE_TYPE_VOLUME) { /* * Disk was in container and is added to volume. * Release object for recreating. */ raid_device_fini(temp->raid_dev); temp->raid_dev = raid_device_duplicate(block->raid_dev); } } } /** * @brief Adds the block device to list. * * This is internal function of monitor service. The function adds a block * device to the ledmon_block_list list or if the device is already on the list * it updates the IBPI state of the given device. The function updates timestamp * value which indicates the time of last structure modification. The function * is design to be used as 'action' parameter of list_for_each() function. * Each change of state is logged to the file and to the syslog. * * @param[in] block Pointer to block device structure. * * @return The function does not return a value. */ static void _add_block(struct block_device *block) { struct block_device *temp = NULL; list_for_each(&ledmon_block_list, temp) { if (block_compare(temp, block)) break; temp = NULL; } if (temp) { enum ibpi_pattern ibpi = temp->ibpi; temp->timestamp = block->timestamp; if (temp->ibpi == IBPI_PATTERN_ADDED) { temp->ibpi = IBPI_PATTERN_ONESHOT_NORMAL; } else if (temp->ibpi == IBPI_PATTERN_ONESHOT_NORMAL) { temp->ibpi = IBPI_PATTERN_UNKNOWN; } else if (temp->ibpi != IBPI_PATTERN_FAILED_DRIVE) { if (block->ibpi == IBPI_PATTERN_UNKNOWN) { if ((temp->ibpi != IBPI_PATTERN_UNKNOWN) && (temp->ibpi != IBPI_PATTERN_NORMAL)) { temp->ibpi = IBPI_PATTERN_ONESHOT_NORMAL; } else { temp->ibpi = IBPI_PATTERN_UNKNOWN; } } else { temp->ibpi = block->ibpi; } } else if (!(temp->ibpi == IBPI_PATTERN_FAILED_DRIVE && block->ibpi == IBPI_PATTERN_HOTSPARE) || (temp->ibpi == IBPI_PATTERN_FAILED_DRIVE && block->ibpi == IBPI_PATTERN_NONE)) { temp->ibpi = block->ibpi; } _handle_fail_state(block, temp); if (ibpi != temp->ibpi && ibpi <= IBPI_PATTERN_REMOVED) { log_info("CHANGE %s: from '%s' to '%s'.", temp->sysfs_path, ibpi2str(ibpi), ibpi2str(temp->ibpi)); } /* Check if name of the device changed.*/ if (strcmp(temp->sysfs_path, block->sysfs_path)) { log_info("NAME CHANGED %s to %s", temp->sysfs_path, block->sysfs_path); free(temp->sysfs_path); temp->sysfs_path = str_dup(block->sysfs_path); } } else { /* Device not found, it's a new one! */ temp = block_device_duplicate(block); if (temp != NULL) { log_info("NEW %s: state '%s'.", temp->sysfs_path, ibpi2str(temp->ibpi)); list_append(&ledmon_block_list, temp); } } } /** * @brief Sends LED control message. * * This is internal function of monitor service. The function sends a LED * command to storage controller or enclosure device. The function checks * the time of last modification of block device structure. If the timestamp * is different then the current global timestamp this means the device is * missing due to hot-remove or hardware failure so it must be reported on * LEDs appropriately. Note that controller device and host attached to this * block device points to invalid pointer so it must be 'refreshed'. * * @param[in] block Pointer to block device structure. * * @return The function does not return a value. */ static void _send_msg(struct block_device *block) { if (!block->cntrl) { log_debug("Missing cntrl for dev: %s. Not sending anything.", strstr(block->sysfs_path, "host")); return; } if (block->timestamp != timestamp || block->ibpi == IBPI_PATTERN_REMOVED) { if (block->ibpi != IBPI_PATTERN_FAILED_DRIVE) { log_info("CHANGE %s: from '%s' to '%s'.", block->sysfs_path, ibpi2str(block->ibpi), ibpi2str(IBPI_PATTERN_FAILED_DRIVE)); block->ibpi = IBPI_PATTERN_FAILED_DRIVE; } else { char *host = strstr(block->sysfs_path, "host"); log_debug("DETACHED DEV '%s' in failed state", host ? host : block->sysfs_path); } } block->send_fn(block, block->ibpi); block->ibpi_prev = block->ibpi; } static void _flush_msg(struct block_device *block) { if (!block->cntrl) return; block->flush_fn(block); } static void _revalidate_dev(struct block_device *block) { /* Bring back controller and host to the device. */ block->cntrl = block_get_controller(sysfs_get_cntrl_devices(), block->cntrl_path); if (!block->cntrl) { /* It could be removed VMD drive */ log_debug("Failed to get controller for dev: %s, ctrl path: %s", block->sysfs_path, block->cntrl_path); return; } if (block->cntrl->cntrl_type == CNTRL_TYPE_SCSI) { block->host = block_get_host(block->cntrl, block->host_id); if (block->host) { if (dev_directly_attached(block->sysfs_path)) cntrl_init_smp(NULL, block->cntrl); else scsi_get_enclosure(block); } else { log_debug("Failed to get host for dev: %s, hostId: %d", block->sysfs_path, block->host_id); /* If failed, invalidate cntrl */ block->cntrl = NULL; } } return; } static void _invalidate_dev(struct block_device *block) { /* Those fields are valid only per 'session' - through single scan. */ block->cntrl = NULL; block->host = NULL; block->enclosure = NULL; block->encl_index = -1; } static void _check_block_dev(struct block_device *block, int *restart) { if (!block->cntrl) { (*restart)++; } } /** * @brief Sets a list of block devices and sends LED control messages. * * This is internal function of monitor service. Based on current layout of * sysfs tree the function extracts block devices and for each block device it * send LED control message to storage controller or enclosure. The message is * determine by appropriate field in block device's structure. See _add_block() * and _send_msg() functions description for more details. * * @return The function does not return a value. */ static void _ledmon_execute(void) { int restart = 0; /* ledmon_block_list needs restart? */ struct block_device *device; /* Revalidate each device in the list. Bring back controller and host */ list_for_each(&ledmon_block_list, device) _revalidate_dev(device); /* Scan all devices and compare them against saved list */ list_for_each(sysfs_get_block_devices(), device) _add_block(device); /* Send message to all devices in the list if needed. */ list_for_each(&ledmon_block_list, device) _send_msg(device); /* Flush unsent messages from internal buffers. */ list_for_each(&ledmon_block_list, device) _flush_msg(device); /* Check if there is any orphaned device. */ list_for_each(&ledmon_block_list, device) _check_block_dev(device, &restart); if (restart) { /* there is at least one detached element in the list. */ list_erase(&ledmon_block_list); } } static status_t _init_ledmon_conf(void) { memset(&conf, 0, sizeof(struct ledmon_conf)); /* initialize with default values */ conf.blink_on_init = 1; conf.blink_on_migration = 1; conf.rebuild_blink_on_all = 0; conf.raid_members_only = 0; conf.log_level = LOG_LEVEL_WARNING; conf.scan_interval = LEDMON_DEF_SLEEP_INTERVAL; list_init(&conf.cntrls_whitelist, NULL); list_init(&conf.cntrls_blacklist, NULL); return set_log_path(LEDMON_DEF_LOG_FILE); } static void _close_parent_fds(void) { struct list dir; if (scan_dir("/proc/self/fd", &dir) == 0) { char *elem; list_for_each(&dir, elem) { int fd = (int)strtol(basename(elem), NULL, 10); if (fd != get_log_fd()) close(fd); } list_erase(&dir); } } /** */ int main(int argc, char *argv[]) { status_t status = STATUS_SUCCESS; int ignore = 0; setup_options(&longopt, &shortopt, possible_params, possible_params_size); set_invocation_name(argv[0]); openlog(progname, LOG_PID | LOG_PERROR, LOG_DAEMON); if (on_exit(_ledmon_status, &ignore)) return STATUS_ONEXIT_ERROR; if (_cmdline_parse_non_daemonise(argc, argv) != STATUS_SUCCESS) return STATUS_CMDLINE_ERROR; if (getuid() != 0) { fprintf(stderr, "Only root can run this application.\n"); return STATUS_NOT_A_PRIVILEGED_USER; } status = _init_ledmon_conf(); if (status != STATUS_SUCCESS) return status; status = ledmon_read_config(ledmon_conf_path); if (status != STATUS_SUCCESS) return status; if (_cmdline_parse(argc, argv) != STATUS_SUCCESS) return STATUS_CMDLINE_ERROR; ledmon_write_shared_conf(); if (log_open(conf.log_path) != STATUS_SUCCESS) return STATUS_LOG_FILE_ERROR; free(shortopt); free(longopt); if (pidfile_check(progname, NULL) == 0) { log_warning("daemon is running..."); return STATUS_LEDMON_RUNNING; } if (!foreground) { pid_t pid = fork(); if (pid < 0) { log_debug("main(): fork() failed (errno=%d).", errno); exit(EXIT_FAILURE); } if (pid > 0) { ignore = 1; /* parent: don't print exit status */ exit(EXIT_SUCCESS); } pid_t sid = setsid(); if (sid < 0) { log_debug("main(): setsid() failed (errno=%d).", errno); exit(EXIT_FAILURE); } _close_parent_fds(); int t = open("/dev/null", O_RDWR); UNUSED(dup(t)); UNUSED(dup(t)); } umask(027); if (chdir("/") < 0) { log_debug("main(): chdir() failed (errno=%d).", errno); exit(EXIT_FAILURE); } if (pidfile_create(progname)) { log_debug("main(): pidfile_creat() failed."); exit(EXIT_FAILURE); } _ledmon_setup_signals(); if (on_exit(_ledmon_fini, progname)) exit(STATUS_ONEXIT_ERROR); list_init(&ledmon_block_list, (item_free_t)block_device_fini); sysfs_init(); log_info("monitor service has been started..."); while (terminate == 0) { struct block_device *device; timestamp = time(NULL); sysfs_scan(); _ledmon_execute(); _ledmon_wait(conf.scan_interval); /* Invalidate each device in the list. Clear controller and host. */ list_for_each(&ledmon_block_list, device) _invalidate_dev(device); sysfs_reset(); } ledmon_remove_shared_conf(); stop_udev_monitor(); exit(EXIT_SUCCESS); }