/*
* Intel(R) Enclosure LED Utilities
* Copyright (C) 2017-2020 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 <libudev.h>
#include <limits.h>
#include <stdint.h>
#include <string.h>
#include "block.h"
#include "ibpi.h"
#include "status.h"
#include "sysfs.h"
#include "udev.h"
#include "utils.h"
static struct udev_monitor *udev_monitor;
static int _compare(const struct block_device *bd, const char *syspath)
{
if (!bd || !syspath)
return 0;
if (strcmp(bd->sysfs_path, syspath) == 0) {
return 1;
} else {
struct block_device *bd_new;
int ret;
bd_new = block_device_init(sysfs_get_cntrl_devices(), syspath);
if (!bd_new)
return 0;
ret = block_compare(bd, bd_new);
block_device_fini(bd_new);
return ret;
}
}
static int create_udev_monitor(void)
{
int res;
struct udev *udev = udev_new();
if (!udev) {
log_error("Failed to create udev context instance.");
return -1;
}
udev_monitor = udev_monitor_new_from_netlink(udev, "udev");
if (!udev_monitor) {
log_error("Failed to create udev monitor object.");
udev_unref(udev);
return -1;
}
res = udev_monitor_filter_add_match_subsystem_devtype(udev_monitor,
"block", "disk");
if (res < 0) {
log_error("Failed to modify udev monitor filters.");
stop_udev_monitor();
return -1;
}
res = udev_monitor_enable_receiving(udev_monitor);
if (res < 0) {
log_error("Failed to switch udev monitor to listening mode.");
stop_udev_monitor();
return -1;
}
return udev_monitor_get_fd(udev_monitor);
}
void stop_udev_monitor(void)
{
if (udev_monitor) {
struct udev *udev = udev_monitor_get_udev(udev_monitor);
udev_monitor_unref(udev_monitor);
if (udev)
udev_unref(udev);
}
}
int get_udev_monitor(void)
{
if (udev_monitor)
return udev_monitor_get_fd(udev_monitor);
return create_udev_monitor();
}
static int _check_raid(const char *path)
{
char *t = strrchr(path, '/');
if (t == NULL)
return 0;
return strncmp(t + 1, "md", 2) == 0;
}
static enum udev_action _get_udev_action(const char *action)
{
enum udev_action ret = UDEV_ACTION_UNKNOWN;
if (strncmp(action, "add", 3) == 0)
ret = UDEV_ACTION_ADD;
else if (strncmp(action, "remove", 6) == 0)
ret = UDEV_ACTION_REMOVE;
return ret;
}
static void _clear_raid_dev_info(struct block_device *block, char *raid_dev)
{
if (block->raid_dev && block->raid_dev->sysfs_path) {
char *tmp = strrchr(block->raid_dev->sysfs_path, '/');
if (tmp == NULL) {
log_debug("Device: %s have wrong raid_dev path: %s",
block->sysfs_path,
block->raid_dev->sysfs_path);
return;
}
if (strcmp(raid_dev, tmp + 1) == 0) {
log_debug("CLEAR raid_dev %s in %s ",
raid_dev, block->sysfs_path);
raid_device_fini(block->raid_dev);
block->raid_dev = NULL;
}
}
}
int handle_udev_event(struct list *ledmon_block_list)
{
struct udev_device *dev;
int status = -1;
dev = udev_monitor_receive_device(udev_monitor);
if (dev) {
const char *action = udev_device_get_action(dev);
enum udev_action act = _get_udev_action(action);
const char *syspath = udev_device_get_syspath(dev);
struct block_device *block = NULL;
if (act == UDEV_ACTION_UNKNOWN) {
status = 1;
goto exit;
}
list_for_each(ledmon_block_list, block) {
if (_compare(block, syspath))
break;
block = NULL;
}
if (!block) {
if (act == UDEV_ACTION_REMOVE && _check_raid(syspath)) {
/*ledmon is interested about removed arrays*/
char *dev_name;
dev_name = strrchr(syspath, '/') + 1;
log_debug("REMOVED %s", dev_name);
list_for_each(ledmon_block_list, block)
_clear_raid_dev_info(block, dev_name);
status = 0;
goto exit;
}
status = 1;
goto exit;
}
if (act == UDEV_ACTION_ADD) {
log_debug("ADDED %s", block->sysfs_path);
if (block->ibpi == IBPI_PATTERN_FAILED_DRIVE ||
block->ibpi == IBPI_PATTERN_REMOVED ||
block->ibpi == IBPI_PATTERN_UNKNOWN)
block->ibpi = IBPI_PATTERN_ADDED;
} else if (act == UDEV_ACTION_REMOVE) {
log_debug("REMOVED %s", block->sysfs_path);
block->ibpi = IBPI_PATTERN_REMOVED;
} else {
/* not interesting event */
status = 1;
goto exit;
}
status = 0;
} else {
return -1;
}
exit:
udev_device_unref(dev);
return status;
}