mirror of
git://sourceware.org/git/lvm2.git
synced 2024-12-21 13:34:40 +03:00
3b0f9cec7e
to compare with wwids in /etc/multipath/wwids when excluding multipath components. The wwid printed from the sysfs wwid file may not be the wwid used in multipath wwids. Save the wwids found for each device on dev->wwids to avoid repeating reading and parsing the sysfs files.
876 lines
21 KiB
C
876 lines
21 KiB
C
/*
|
|
* Copyright (C) 2011 Red Hat, Inc. All rights reserved.
|
|
*
|
|
* This file is part of LVM2.
|
|
*
|
|
* This copyrighted material is made available to anyone wishing to use,
|
|
* modify, copy, or redistribute it subject to the terms and conditions
|
|
* of the GNU Lesser General Public License v.2.1.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#include "base/memory/zalloc.h"
|
|
#include "lib/misc/lib.h"
|
|
#include "lib/activate/activate.h"
|
|
#include "lib/commands/toolcontext.h"
|
|
#include "lib/device/device_id.h"
|
|
#include "lib/datastruct/str_list.h"
|
|
#ifdef UDEV_SYNC_SUPPORT
|
|
#include <libudev.h>
|
|
#include "lib/device/dev-ext-udev-constants.h"
|
|
#endif
|
|
|
|
#include <dirent.h>
|
|
#include <ctype.h>
|
|
|
|
#define MPATH_PREFIX "mpath-"
|
|
|
|
/*
|
|
* This hash table keeps track of whether a given dm device
|
|
* is a mpath device or not.
|
|
*
|
|
* If dm-3 is an mpath device, then the constant "2" is stored in
|
|
* the hash table with the key of the dm minor number ("3" for dm-3).
|
|
* If dm-3 is not an mpath device, then the constant "1" is stored in
|
|
* the hash table with the key of the dm minor number.
|
|
*/
|
|
static struct dm_pool *_wwid_mem;
|
|
static struct dm_hash_table *_minor_hash_tab;
|
|
static struct dm_hash_table *_wwid_hash_tab;
|
|
static struct dm_list _ignored;
|
|
static struct dm_list _ignored_exceptions;
|
|
|
|
#define MAX_WWID_LINE 512
|
|
|
|
static void _read_blacklist_file(const char *path)
|
|
{
|
|
FILE *fp;
|
|
char line[MAX_WWID_LINE];
|
|
char wwid[MAX_WWID_LINE];
|
|
char *word, *p;
|
|
int section_black = 0;
|
|
int section_exceptions = 0;
|
|
int found_quote;
|
|
int found_type;
|
|
int i, j;
|
|
|
|
if (!(fp = fopen(path, "r")))
|
|
return;
|
|
|
|
while (fgets(line, sizeof(line), fp)) {
|
|
word = NULL;
|
|
|
|
/* skip initial white space on the line */
|
|
for (i = 0; i < MAX_WWID_LINE; i++) {
|
|
if ((line[i] == '\n') || (line[i] == '\0'))
|
|
break;
|
|
if (isspace(line[i]))
|
|
continue;
|
|
word = &line[i];
|
|
break;
|
|
}
|
|
|
|
if (!word || word[0] == '#')
|
|
continue;
|
|
|
|
/* identify the start of the section we want to read */
|
|
if (strchr(word, '{')) {
|
|
if (!strncmp(word, "blacklist_exceptions", 20))
|
|
section_exceptions = 1;
|
|
else if (!strncmp(word, "blacklist", 9))
|
|
section_black = 1;
|
|
continue;
|
|
}
|
|
/* identify the end of the section we've been reading */
|
|
if (strchr(word, '}')) {
|
|
section_exceptions = 0;
|
|
section_black = 0;
|
|
continue;
|
|
}
|
|
/* skip lines that are not in a section we want */
|
|
if (!section_black && !section_exceptions)
|
|
continue;
|
|
|
|
/*
|
|
* read a wwid from the blacklist{_exceptions} section.
|
|
* does not recognize other non-wwid entries in the
|
|
* section, and skips those (should the entire mp
|
|
* config filtering be disabled if non-wwids are seen?
|
|
*/
|
|
if (!(p = strstr(word, "wwid")))
|
|
continue;
|
|
|
|
i += 4; /* skip "wwid" */
|
|
|
|
/*
|
|
* copy wwid value from the line.
|
|
* the wwids copied here need to match the
|
|
* wwids read from /etc/multipath/wwids,
|
|
* which are matched to wwids from sysfs.
|
|
*/
|
|
|
|
memset(wwid, 0, sizeof(wwid));
|
|
found_quote = 0;
|
|
found_type = 0;
|
|
j = 0;
|
|
|
|
for (; i < MAX_WWID_LINE; i++) {
|
|
if ((line[i] == '\n') || (line[i] == '\0'))
|
|
break;
|
|
if (!j && isspace(line[i]))
|
|
continue;
|
|
if (isspace(line[i]))
|
|
break;
|
|
/* quotes around wwid are optional */
|
|
if ((line[i] == '"') && !found_quote) {
|
|
found_quote = 1;
|
|
continue;
|
|
}
|
|
/* second quote is end of wwid */
|
|
if ((line[i] == '"') && found_quote)
|
|
break;
|
|
/* exclude initial 3/2/1 for naa/eui/t10 */
|
|
if (!j && !found_type &&
|
|
((line[i] == '3') || (line[i] == '2') || (line[i] == '1'))) {
|
|
found_type = 1;
|
|
continue;
|
|
}
|
|
|
|
wwid[j] = line[i];
|
|
j++;
|
|
}
|
|
|
|
if (j < 8)
|
|
continue;
|
|
|
|
log_debug("multipath wwid %s in %s %s",
|
|
wwid, section_exceptions ? "blacklist_exceptions" : "blacklist", path);
|
|
|
|
if (section_exceptions) {
|
|
if (!str_list_add(_wwid_mem, &_ignored_exceptions, dm_pool_strdup(_wwid_mem, wwid)))
|
|
stack;
|
|
} else {
|
|
if (!str_list_add(_wwid_mem, &_ignored, dm_pool_strdup(_wwid_mem, wwid)))
|
|
stack;
|
|
}
|
|
}
|
|
|
|
if (fclose(fp))
|
|
stack;
|
|
}
|
|
|
|
static void _read_wwid_exclusions(void)
|
|
{
|
|
char path[PATH_MAX] = { 0 };
|
|
DIR *dir;
|
|
struct dirent *de;
|
|
struct dm_str_list *sl, *sl2;
|
|
int rem_count = 0;
|
|
|
|
_read_blacklist_file("/etc/multipath.conf");
|
|
|
|
if ((dir = opendir("/etc/multipath/conf.d"))) {
|
|
while ((de = readdir(dir))) {
|
|
if (de->d_name[0] == '.')
|
|
continue;
|
|
snprintf(path, PATH_MAX-1, "/etc/multipath/conf.d/%s", de->d_name);
|
|
_read_blacklist_file(path);
|
|
}
|
|
closedir(dir);
|
|
}
|
|
|
|
/* for each wwid in ignored_exceptions, remove it from ignored */
|
|
|
|
dm_list_iterate_items_safe(sl, sl2, &_ignored) {
|
|
if (str_list_match_item(&_ignored_exceptions, sl->str))
|
|
str_list_del(&_ignored, sl->str);
|
|
}
|
|
|
|
/* for each wwid in ignored, remove it from wwid_hash */
|
|
|
|
dm_list_iterate_items(sl, &_ignored) {
|
|
dm_hash_remove_binary(_wwid_hash_tab, sl->str, strlen(sl->str));
|
|
rem_count++;
|
|
}
|
|
|
|
if (rem_count)
|
|
log_debug("multipath config ignored %d wwids", rem_count);
|
|
}
|
|
|
|
static void _read_wwid_file(const char *config_wwids_file, int *entries)
|
|
{
|
|
FILE *fp;
|
|
char line[MAX_WWID_LINE];
|
|
char *wwid, *p;
|
|
char typestr[2] = { 0 };
|
|
int count = 0;
|
|
|
|
if (config_wwids_file[0] != '/') {
|
|
log_print("Ignoring unknown multipath_wwids_file.");
|
|
return;
|
|
}
|
|
|
|
if (!(fp = fopen(config_wwids_file, "r"))) {
|
|
log_debug("multipath wwids file not found");
|
|
return;
|
|
}
|
|
|
|
while (fgets(line, sizeof(line), fp)) {
|
|
if (line[0] == '#')
|
|
continue;
|
|
|
|
wwid = line;
|
|
|
|
if (line[0] == '/')
|
|
wwid++;
|
|
|
|
|
|
/*
|
|
* the initial character is the id type,
|
|
* 1 is t10, 2 is eui, 3 is naa, 8 is scsi name.
|
|
* wwids are stored in the hash table without the type charater.
|
|
* It seems that sometimes multipath does not include
|
|
* the type charater (seen with t10 scsi_debug devs).
|
|
*/
|
|
typestr[0] = *wwid;
|
|
if (typestr[0] == '1' || typestr[0] == '2' || typestr[0] == '3')
|
|
wwid++;
|
|
|
|
if ((p = strchr(wwid, '/')))
|
|
*p = '\0';
|
|
|
|
(void) dm_hash_insert_binary(_wwid_hash_tab, wwid, strlen(wwid), (void*)1);
|
|
count++;
|
|
}
|
|
|
|
if (fclose(fp))
|
|
stack;
|
|
|
|
log_debug("multipath wwids read %d from %s", count, config_wwids_file);
|
|
*entries = count;
|
|
}
|
|
|
|
int dev_mpath_init(const char *config_wwids_file)
|
|
{
|
|
struct dm_pool *mem;
|
|
struct dm_hash_table *minor_tab;
|
|
struct dm_hash_table *wwid_tab;
|
|
int entries = 0;
|
|
|
|
dm_list_init(&_ignored);
|
|
dm_list_init(&_ignored_exceptions);
|
|
|
|
if (!(mem = dm_pool_create("mpath", 256))) {
|
|
log_error("mpath pool creation failed.");
|
|
return 0;
|
|
}
|
|
|
|
if (!(minor_tab = dm_hash_create(110))) {
|
|
log_error("mpath hash table creation failed.");
|
|
dm_pool_destroy(mem);
|
|
return 0;
|
|
}
|
|
|
|
_wwid_mem = mem;
|
|
_minor_hash_tab = minor_tab;
|
|
|
|
/* multipath_wwids_file="" disables the use of the file */
|
|
if (config_wwids_file && !strlen(config_wwids_file)) {
|
|
log_debug("multipath wwids file disabled.");
|
|
return 1;
|
|
}
|
|
|
|
if (!(wwid_tab = dm_hash_create(110))) {
|
|
log_error("mpath hash table creation failed.");
|
|
dm_hash_destroy(_minor_hash_tab);
|
|
dm_pool_destroy(_wwid_mem);
|
|
_minor_hash_tab = NULL;
|
|
_wwid_mem = NULL;
|
|
return 0;
|
|
}
|
|
|
|
_wwid_hash_tab = wwid_tab;
|
|
|
|
if (config_wwids_file) {
|
|
_read_wwid_file(config_wwids_file, &entries);
|
|
_read_wwid_exclusions();
|
|
}
|
|
|
|
if (!entries) {
|
|
/* reading dev wwids is skipped with null wwid_hash_tab */
|
|
dm_hash_destroy(_wwid_hash_tab);
|
|
_wwid_hash_tab = NULL;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
void dev_mpath_exit(void)
|
|
{
|
|
if (_minor_hash_tab)
|
|
dm_hash_destroy(_minor_hash_tab);
|
|
if (_wwid_hash_tab)
|
|
dm_hash_destroy(_wwid_hash_tab);
|
|
if (_wwid_mem)
|
|
dm_pool_destroy(_wwid_mem);
|
|
|
|
_minor_hash_tab = NULL;
|
|
_wwid_hash_tab = NULL;
|
|
_wwid_mem = NULL;
|
|
}
|
|
|
|
|
|
/*
|
|
* given "/dev/foo" return "foo"
|
|
*/
|
|
static const char *_get_sysfs_name(struct device *dev)
|
|
{
|
|
const char *name;
|
|
|
|
if (!(name = strrchr(dev_name(dev), '/'))) {
|
|
log_error("Cannot find '/' in device name.");
|
|
return NULL;
|
|
}
|
|
name++;
|
|
|
|
if (!*name) {
|
|
log_error("Device name is not valid.");
|
|
return NULL;
|
|
}
|
|
|
|
return name;
|
|
}
|
|
|
|
/*
|
|
* given major:minor
|
|
* readlink translates /sys/dev/block/major:minor to /sys/.../foo
|
|
* from /sys/.../foo return "foo"
|
|
*/
|
|
static const char *_get_sysfs_name_by_devt(const char *sysfs_dir, dev_t devno,
|
|
char *buf, size_t buf_size)
|
|
{
|
|
const char *name;
|
|
char path[PATH_MAX];
|
|
int size;
|
|
|
|
if (dm_snprintf(path, sizeof(path), "%sdev/block/%d:%d", sysfs_dir,
|
|
(int) MAJOR(devno), (int) MINOR(devno)) < 0) {
|
|
log_error("Sysfs path string is too long.");
|
|
return NULL;
|
|
}
|
|
|
|
if ((size = readlink(path, buf, buf_size - 1)) < 0) {
|
|
log_sys_error("readlink", path);
|
|
return NULL;
|
|
}
|
|
buf[size] = '\0';
|
|
|
|
if (!(name = strrchr(buf, '/'))) {
|
|
log_error("Cannot find device name in sysfs path.");
|
|
return NULL;
|
|
}
|
|
name++;
|
|
|
|
return name;
|
|
}
|
|
|
|
static int _get_sysfs_string(const char *path, char *buffer, int max_size)
|
|
{
|
|
FILE *fp;
|
|
int r = 0;
|
|
|
|
if (!(fp = fopen(path, "r"))) {
|
|
log_sys_error("fopen", path);
|
|
return 0;
|
|
}
|
|
|
|
if (!fgets(buffer, max_size, fp))
|
|
log_sys_error("fgets", path);
|
|
else
|
|
r = 1;
|
|
|
|
if (fclose(fp))
|
|
log_sys_error("fclose", path);
|
|
|
|
return r;
|
|
}
|
|
|
|
static int _get_sysfs_dm_mpath(struct dev_types *dt, const char *sysfs_dir, const char *holder_name)
|
|
{
|
|
char path[PATH_MAX];
|
|
char buffer[128];
|
|
|
|
if (dm_snprintf(path, sizeof(path), "%sblock/%s/dm/uuid", sysfs_dir, holder_name) < 0) {
|
|
log_error("Sysfs path string is too long.");
|
|
return 0;
|
|
}
|
|
|
|
buffer[0] = '\0';
|
|
|
|
if (!_get_sysfs_string(path, buffer, sizeof(buffer)))
|
|
return_0;
|
|
|
|
if (!strncmp(buffer, MPATH_PREFIX, 6))
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef UDEV_SYNC_SUPPORT
|
|
static int _dev_is_mpath_component_udev(struct device *dev)
|
|
{
|
|
const char *value;
|
|
struct dev_ext *ext;
|
|
|
|
/*
|
|
* external_device_info_source="udev" enables these udev checks.
|
|
* external_device_info_source="none" disables them.
|
|
*/
|
|
|
|
if (!(ext = dev_ext_get(dev)))
|
|
return_0;
|
|
|
|
value = udev_device_get_property_value((struct udev_device *)ext->handle, DEV_EXT_UDEV_BLKID_TYPE);
|
|
if (value && !strcmp(value, DEV_EXT_UDEV_BLKID_TYPE_MPATH))
|
|
return 1;
|
|
|
|
value = udev_device_get_property_value((struct udev_device *)ext->handle, DEV_EXT_UDEV_MPATH_DEVICE_PATH);
|
|
if (value && !strcmp(value, "1"))
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
#else
|
|
static int _dev_is_mpath_component_udev(struct device *dev)
|
|
{
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
/* mpath_devno is major:minor of the dm multipath device currently using the component dev. */
|
|
|
|
static int _dev_is_mpath_component_sysfs(struct cmd_context *cmd, struct device *dev,
|
|
int primary_result, dev_t primary_dev, dev_t *mpath_devno)
|
|
{
|
|
struct dev_types *dt = cmd->dev_types;
|
|
const char *name; /* e.g. "sda" for "/dev/sda" */
|
|
char link_path[PATH_MAX]; /* some obscure, unpredictable sysfs path */
|
|
char holders_path[PATH_MAX]; /* e.g. "/sys/block/sda/holders/" */
|
|
char dm_dev_path[PATH_MAX]; /* e.g. "/dev/dm-1" */
|
|
char *holder_name; /* e.g. "dm-1" */
|
|
const char *sysfs_dir = dm_sysfs_dir();
|
|
DIR *dr;
|
|
struct dirent *de;
|
|
int dev_major = MAJOR(dev->dev);
|
|
int dev_minor = MINOR(dev->dev);
|
|
int dm_dev_major;
|
|
int dm_dev_minor;
|
|
struct stat info;
|
|
int is_mpath_component = 0;
|
|
|
|
switch (primary_result) {
|
|
|
|
case 2: /* The dev is partition. */
|
|
|
|
/* gets "foo" for "/dev/foo" where "/dev/foo" comes from major:minor */
|
|
if (!(name = _get_sysfs_name_by_devt(sysfs_dir, primary_dev, link_path, sizeof(link_path))))
|
|
return_0;
|
|
break;
|
|
|
|
case 1: /* The dev is already a primary dev. Just continue with the dev. */
|
|
|
|
/* gets "foo" for "/dev/foo" */
|
|
if (!(name = _get_sysfs_name(dev)))
|
|
return_0;
|
|
break;
|
|
|
|
default: /* 0, error. */
|
|
log_warn("Failed to get primary device for %d:%d.", dev_major, dev_minor);
|
|
return 0;
|
|
}
|
|
|
|
if (dm_snprintf(holders_path, sizeof(holders_path), "%sblock/%s/holders", sysfs_dir, name) < 0) {
|
|
log_warn("Sysfs path to check mpath is too long.");
|
|
return 0;
|
|
}
|
|
|
|
/* also will filter out partitions */
|
|
if (stat(holders_path, &info))
|
|
return 0;
|
|
|
|
if (!S_ISDIR(info.st_mode)) {
|
|
log_warn("Path %s is not a directory.", holders_path);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* If any holder is a dm mpath device, then return 1;
|
|
*/
|
|
|
|
if (!(dr = opendir(holders_path))) {
|
|
log_debug("Device %s has no holders dir", dev_name(dev));
|
|
return 0;
|
|
}
|
|
|
|
while ((de = readdir(dr))) {
|
|
if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
|
|
continue;
|
|
|
|
/*
|
|
* holder_name is e.g. "dm-1"
|
|
* dm_dev_path is then e.g. "/dev/dm-1"
|
|
*/
|
|
holder_name = de->d_name;
|
|
|
|
if (dm_snprintf(dm_dev_path, sizeof(dm_dev_path), "%s/%s", cmd->dev_dir, holder_name) < 0) {
|
|
log_warn("dm device path to check mpath is too long.");
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* stat "/dev/dm-1" which is the holder of the dev we're checking
|
|
* dm_dev_major:dm_dev_minor come from stat("/dev/dm-1")
|
|
*/
|
|
if (stat(dm_dev_path, &info)) {
|
|
log_debug_devs("dev_is_mpath_component %s holder %s stat result %d",
|
|
dev_name(dev), dm_dev_path, errno);
|
|
continue;
|
|
}
|
|
dm_dev_major = (int)MAJOR(info.st_rdev);
|
|
dm_dev_minor = (int)MINOR(info.st_rdev);
|
|
|
|
if (dm_dev_major != dt->device_mapper_major) {
|
|
log_debug_devs("dev_is_mpath_component %s holder %s %d:%d does not have dm major",
|
|
dev_name(dev), dm_dev_path, dm_dev_major, dm_dev_minor);
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* A previous call may have checked if dm_dev_minor is mpath and saved
|
|
* the result in the hash table. If there's a saved result just use that.
|
|
*
|
|
* The minor number of "/dev/dm-1" is added to the hash table with
|
|
* const value 2 meaning that dm minor 1 (for /dev/dm-1) is a multipath dev
|
|
* and const value 1 meaning that dm minor 1 is not a multipath dev.
|
|
*/
|
|
|
|
if (_minor_hash_tab) {
|
|
long look = (long) dm_hash_lookup_binary(_minor_hash_tab, &dm_dev_minor, sizeof(dm_dev_minor));
|
|
if (look > 0) {
|
|
log_debug_devs("dev_is_mpath_component %s holder %s %u:%u already checked as %sbeing mpath.",
|
|
dev_name(dev), holder_name, dm_dev_major, dm_dev_minor, (look > 1) ? "" : "not ");
|
|
|
|
is_mpath_component = (look == 2);
|
|
goto out;
|
|
}
|
|
|
|
/* no saved result for dm_dev_minor, so check the uuid for it */
|
|
}
|
|
|
|
/*
|
|
* Returns 1 if /sys/block/<holder_name>/dm/uuid indicates that
|
|
* <holder_name> is a dm device with dm uuid prefix mpath-.
|
|
* When true, <holder_name> will be something like "dm-1".
|
|
*/
|
|
if (_get_sysfs_dm_mpath(dt, sysfs_dir, holder_name)) {
|
|
log_debug_devs("dev_is_mpath_component %s holder %s %u:%u ignore mpath component",
|
|
dev_name(dev), holder_name, dm_dev_major, dm_dev_minor);
|
|
|
|
/* For future checks, save that the dm minor refers to mpath ("2" == is mpath) */
|
|
if (_minor_hash_tab)
|
|
(void) dm_hash_insert_binary(_minor_hash_tab, &dm_dev_minor, sizeof(dm_dev_minor), (void*)2);
|
|
|
|
is_mpath_component = 1;
|
|
goto out;
|
|
}
|
|
|
|
/* For future checks, save that the dm minor does not refer to mpath ("1" == is not mpath) */
|
|
if (_minor_hash_tab)
|
|
(void) dm_hash_insert_binary(_minor_hash_tab, &dm_dev_minor, sizeof(dm_dev_minor), (void*)1);
|
|
}
|
|
|
|
out:
|
|
if (closedir(dr))
|
|
stack;
|
|
|
|
if (is_mpath_component)
|
|
*mpath_devno = MKDEV(dm_dev_major, dm_dev_minor);
|
|
return is_mpath_component;
|
|
}
|
|
|
|
static int _read_sys_wwid(struct cmd_context *cmd, struct device *dev,
|
|
char *idbuf, int idbufsize)
|
|
{
|
|
char idtmp[DEV_WWID_SIZE];
|
|
|
|
if (!read_sys_block(cmd, dev, "device/wwid", idbuf, idbufsize)) {
|
|
/* the wwid file is not under device for nvme devs */
|
|
if (!read_sys_block(cmd, dev, "wwid", idbuf, idbufsize))
|
|
return 0;
|
|
}
|
|
if (!idbuf[0])
|
|
return 0;
|
|
|
|
/* in t10 id, replace series of spaces with one _ like multipath */
|
|
if (!strncmp(idbuf, "t10.", 4) && strchr(idbuf, ' ')) {
|
|
if (idbufsize < DEV_WWID_SIZE)
|
|
return 0;
|
|
memcpy(idtmp, idbuf, DEV_WWID_SIZE);
|
|
memset(idbuf, 0, idbufsize);
|
|
format_t10_id((const unsigned char *)idtmp, DEV_WWID_SIZE, (unsigned char *)idbuf, idbufsize);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
#define VPD_SIZE 4096
|
|
|
|
static int _read_sys_vpd_wwids(struct cmd_context *cmd, struct device *dev,
|
|
struct dm_list *ids)
|
|
{
|
|
unsigned char vpd_data[VPD_SIZE] = { 0 };
|
|
int vpd_datalen = 0;
|
|
|
|
if (!read_sys_block_binary(cmd, dev, "device/vpd_pg83", (char *)vpd_data, VPD_SIZE, &vpd_datalen))
|
|
return 0;
|
|
if (!vpd_datalen)
|
|
return 0;
|
|
|
|
/* adds dev_wwid entry to dev->wwids for each id in vpd data */
|
|
parse_vpd_ids(vpd_data, vpd_datalen, ids);
|
|
return 1;
|
|
}
|
|
|
|
void free_wwids(struct dm_list *ids)
|
|
{
|
|
struct dev_wwid *dw, *safe;
|
|
|
|
dm_list_iterate_items_safe(dw, safe, ids) {
|
|
dm_list_del(&dw->list);
|
|
free(dw);
|
|
}
|
|
}
|
|
|
|
static int _wwid_type_num(char *id)
|
|
{
|
|
if (!strncmp(id, "naa.", 4))
|
|
return 3;
|
|
else if (!strncmp(id, "eui.", 4))
|
|
return 2;
|
|
else if (!strncmp(id, "t10.", 4))
|
|
return 1;
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* TODO: if each of the different wwid types (naa/eui/t10) were
|
|
* represented by different DEV_ID_TYPE_FOO values, and used
|
|
* as device_id types, then we could drop struct dev_wwid and
|
|
* drop dev->wwids, and just use dev->ids for each of the
|
|
* different wwids found in vpd_pg83. This would also require
|
|
* the ability to handle both the original method of replacing
|
|
* every space in the id string with _ and the new/multipath
|
|
* format_t10_id replacing series of spaces with one _.
|
|
*/
|
|
struct dev_wwid *add_wwid(char *id, int id_type, struct dm_list *ids)
|
|
{
|
|
struct dev_wwid *dw;
|
|
int len;
|
|
|
|
if (!id_type) {
|
|
id_type = _wwid_type_num(id);
|
|
if (id_type == -1)
|
|
log_debug("unknown wwid type %s", id);
|
|
}
|
|
|
|
if (!(dw = zalloc(sizeof(struct dev_wwid))))
|
|
return NULL;
|
|
len = strlen(id);
|
|
if (len >= DEV_WWID_SIZE)
|
|
len = DEV_WWID_SIZE - 1;
|
|
memcpy(dw->id, id, len);
|
|
dw->type = id_type;
|
|
dm_list_add(ids, &dw->list);
|
|
return dw;
|
|
}
|
|
|
|
/*
|
|
* we save ids with format: naa.<value>, eui.<value>, t10.<value>.
|
|
* multipath wwids file uses format: 3<value>, 2<value>, 1<value>.
|
|
* The values are saved in wwid_hash_tab without the type prefix.
|
|
*/
|
|
|
|
static int _dev_in_wwid_file(struct cmd_context *cmd, struct device *dev,
|
|
int primary_result, dev_t primary_dev)
|
|
{
|
|
char idbuf[DEV_WWID_SIZE] = { 0 };
|
|
struct dev_wwid *dw;
|
|
char *wwid;
|
|
|
|
if (!_wwid_hash_tab)
|
|
return 0;
|
|
|
|
/*
|
|
* Check the primary device, not the partition.
|
|
*/
|
|
if (primary_result == 2) {
|
|
if (!(dev = dev_cache_get_by_devt(cmd, primary_dev))) {
|
|
log_debug("dev_is_mpath_component %s no primary dev", dev_name(dev));
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This function may be called multiple times for the same device, in
|
|
* particular if partitioned for each partition.
|
|
*/
|
|
if (!dm_list_empty(&dev->wwids))
|
|
goto lookup;
|
|
|
|
/*
|
|
* Get all the ids for the device from vpd_pg83 and check if any of
|
|
* those are in /etc/multipath/wwids. These ids should include the
|
|
* value printed from the sysfs wwid file.
|
|
*/
|
|
_read_sys_vpd_wwids(cmd, dev, &dev->wwids);
|
|
if (!dm_list_empty(&dev->wwids))
|
|
goto lookup;
|
|
|
|
/*
|
|
* This will read the sysfs wwid file, nvme devices in particular have
|
|
* a wwid file but not a vpd_pg83 file.
|
|
*/
|
|
if (_read_sys_wwid(cmd, dev, idbuf, sizeof(idbuf)))
|
|
add_wwid(idbuf, 0, &dev->wwids);
|
|
|
|
lookup:
|
|
dm_list_iterate_items(dw, &dev->wwids) {
|
|
if (dw->type == 1 || dw->type == 2 || dw->type == 3)
|
|
wwid = &dw->id[4];
|
|
else
|
|
wwid = dw->id;
|
|
|
|
if (dm_hash_lookup_binary(_wwid_hash_tab, wwid, strlen(wwid))) {
|
|
log_debug_devs("dev_is_mpath_component %s %s in wwids file", dev_name(dev), dw->id);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int dev_is_mpath_component(struct cmd_context *cmd, struct device *dev, dev_t *holder_devno)
|
|
{
|
|
struct dev_types *dt = cmd->dev_types;
|
|
int primary_result;
|
|
dev_t primary_dev;
|
|
|
|
/*
|
|
* multipath only uses SCSI or NVME devices
|
|
*/
|
|
if (!major_is_scsi_device(dt, MAJOR(dev->dev)) && !dev_is_nvme(dt, dev))
|
|
return 0;
|
|
|
|
/*
|
|
* primary_result 2: dev is a partition, primary_dev is the whole device
|
|
* primary_result 1: dev is a whole device
|
|
*/
|
|
primary_result = dev_get_primary_dev(dt, dev, &primary_dev);
|
|
|
|
if (_dev_is_mpath_component_sysfs(cmd, dev, primary_result, primary_dev, holder_devno) == 1)
|
|
goto found;
|
|
|
|
if (_dev_in_wwid_file(cmd, dev, primary_result, primary_dev))
|
|
goto found;
|
|
|
|
if (external_device_info_source() == DEV_EXT_UDEV) {
|
|
if (_dev_is_mpath_component_udev(dev) == 1)
|
|
goto found;
|
|
}
|
|
|
|
/*
|
|
* TODO: save the result of this function in dev->flags and use those
|
|
* flags on repeated calls to avoid repeating the work multiple times
|
|
* for the same device when there are partitions on the device.
|
|
*/
|
|
|
|
return 0;
|
|
found:
|
|
return 1;
|
|
}
|
|
|
|
const char *dev_mpath_component_wwid(struct cmd_context *cmd, struct device *dev)
|
|
{
|
|
char slaves_path[PATH_MAX];
|
|
char wwid_path[PATH_MAX];
|
|
char sysbuf[PATH_MAX] = { 0 };
|
|
char *slave_name;
|
|
const char *wwid = NULL;
|
|
struct stat info;
|
|
DIR *dr;
|
|
struct dirent *de;
|
|
|
|
/* /sys/dev/block/253:7/slaves/sda/device/wwid */
|
|
|
|
if (dm_snprintf(slaves_path, sizeof(slaves_path), "%s/dev/block/%d:%d/slaves",
|
|
dm_sysfs_dir(), (int)MAJOR(dev->dev), (int)MINOR(dev->dev)) < 0) {
|
|
log_warn("Sysfs path to check mpath components is too long.");
|
|
return NULL;
|
|
}
|
|
|
|
if (stat(slaves_path, &info))
|
|
return NULL;
|
|
|
|
if (!S_ISDIR(info.st_mode)) {
|
|
log_warn("Path %s is not a directory.", slaves_path);
|
|
return NULL;
|
|
}
|
|
|
|
/* Get wwid from first component */
|
|
|
|
if (!(dr = opendir(slaves_path))) {
|
|
log_debug("Device %s has no slaves dir", dev_name(dev));
|
|
return NULL;
|
|
}
|
|
|
|
while ((de = readdir(dr))) {
|
|
if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
|
|
continue;
|
|
|
|
/* slave_name "sda" */
|
|
slave_name = de->d_name;
|
|
|
|
/* read /sys/block/sda/device/wwid */
|
|
|
|
if (dm_snprintf(wwid_path, sizeof(wwid_path), "%s/block/%s/device/wwid",
|
|
dm_sysfs_dir(), slave_name) < 0) {
|
|
log_warn("Failed to create sysfs wwid path for %s", slave_name);
|
|
continue;
|
|
}
|
|
|
|
get_sysfs_value(wwid_path, sysbuf, sizeof(sysbuf), 0);
|
|
if (!sysbuf[0])
|
|
continue;
|
|
|
|
if (strstr(sysbuf, "scsi_debug")) {
|
|
int i;
|
|
for (i = 0; i < strlen(sysbuf); i++) {
|
|
if (sysbuf[i] == ' ')
|
|
sysbuf[i] = '_';
|
|
}
|
|
}
|
|
|
|
if ((wwid = dm_pool_strdup(cmd->mem, sysbuf)))
|
|
break;
|
|
}
|
|
if (closedir(dr))
|
|
stack;
|
|
|
|
return wwid;
|
|
}
|
|
|
|
|