diff --git a/lib/Makefile.in b/lib/Makefile.in index 22b96134b..3ab5cb2f1 100644 --- a/lib/Makefile.in +++ b/lib/Makefile.in @@ -41,6 +41,7 @@ SOURCES =\ device/dev-dasd.c \ device/dev-lvm1-pool.c \ device/online.c \ + device/parse_vpd.c \ display/display.c \ error/errseg.c \ unknown/unknown.c \ diff --git a/lib/device/dev-cache.c b/lib/device/dev-cache.c index ed9c726c9..193eb7585 100644 --- a/lib/device/dev-cache.c +++ b/lib/device/dev-cache.c @@ -80,6 +80,7 @@ static void _dev_init(struct device *dev) dm_list_init(&dev->aliases); dm_list_init(&dev->ids); + dm_list_init(&dev->wwids); } void dev_destroy_file(struct device *dev) @@ -383,6 +384,22 @@ out: return 1; } +int get_sysfs_binary(const char *path, char *buf, size_t buf_size, int *retlen) +{ + int ret; + int fd; + + fd = open(path, O_RDONLY); + if (fd < 0) + return 0; + ret = read(fd, buf, buf_size); + close(fd); + if (ret <= 0) + return 0; + *retlen = ret; + return 1; +} + int get_sysfs_value(const char *path, char *buf, size_t buf_size, int error_if_no_value) { FILE *fp; @@ -1336,6 +1353,7 @@ int dev_cache_exit(void) dm_hash_iterate(n, _cache.names) { dev = (struct device *) dm_hash_get_data(_cache.names, n); free_dids(&dev->ids); + free_wwids(&dev->wwids); } } diff --git a/lib/device/dev-cache.h b/lib/device/dev-cache.h index 46b1da72c..7ffe01152 100644 --- a/lib/device/dev-cache.h +++ b/lib/device/dev-cache.h @@ -74,6 +74,7 @@ void dev_cache_failed_path(struct device *dev, const char *path); bool dev_cache_has_md_with_end_superblock(struct dev_types *dt); int get_sysfs_value(const char *path, char *buf, size_t buf_size, int error_if_no_value); +int get_sysfs_binary(const char *path, char *buf, size_t buf_size, int *retlen); int get_dm_uuid_from_sysfs(char *buf, size_t buf_size, int major, int minor); int setup_devices_file(struct cmd_context *cmd); diff --git a/lib/device/dev-mpath.c b/lib/device/dev-mpath.c index 846f6c8ba..27b0f41a6 100644 --- a/lib/device/dev-mpath.c +++ b/lib/device/dev-mpath.c @@ -200,11 +200,12 @@ static void _read_wwid_exclusions(void) log_debug("multipath config ignored %d wwids", rem_count); } -static void _read_wwid_file(const char *config_wwids_file) +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] != '/') { @@ -226,8 +227,17 @@ static void _read_wwid_file(const char *config_wwids_file) if (line[0] == '/') wwid++; - /* skip the initial '3' */ - 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'; @@ -240,6 +250,7 @@ static void _read_wwid_file(const char *config_wwids_file) stack; log_debug("multipath wwids read %d from %s", count, config_wwids_file); + *entries = count; } int dev_mpath_init(const char *config_wwids_file) @@ -247,6 +258,7 @@ 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); @@ -283,10 +295,16 @@ int dev_mpath_init(const char *config_wwids_file) _wwid_hash_tab = wwid_tab; if (config_wwids_file) { - _read_wwid_file(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; } @@ -434,10 +452,10 @@ static int _dev_is_mpath_component_udev(struct device *dev) /* 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, dev_t *mpath_devno) +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 *part_name; 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/" */ @@ -451,25 +469,15 @@ static int _dev_is_mpath_component_sysfs(struct cmd_context *cmd, struct device int dm_dev_major; int dm_dev_minor; struct stat info; - dev_t primary_dev; int is_mpath_component = 0; - /* multipathing is only known to exist for SCSI or NVME devices */ - if (!major_is_scsi_device(dt, dev_major) && !dev_is_nvme(dt, dev)) - return 0; - - switch (dev_get_primary_dev(dt, dev, &primary_dev)) { + switch (primary_result) { case 2: /* The dev is partition. */ - part_name = dev_name(dev); /* name of original dev for log_debug msg */ /* 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; - - log_debug_devs("%s: Device is a partition, using primary " - "device %s for mpath component detection", - part_name, name); break; case 1: /* The dev is already a primary dev. Just continue with the dev. */ @@ -593,47 +601,189 @@ static int _dev_is_mpath_component_sysfs(struct cmd_context *cmd, struct device return is_mpath_component; } -static int _dev_in_wwid_file(struct cmd_context *cmd, struct device *dev) +static int _read_sys_wwid(struct cmd_context *cmd, struct device *dev, + char *idbuf, int idbufsize) { - char sysbuf[PATH_MAX] = { 0 }; + 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., eui., t10.. + * multipath wwids file uses format: 3, 2, 1. + * 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; - long look; if (!_wwid_hash_tab) return 0; - if (!read_sys_block(cmd, dev, "device/wwid", sysbuf, sizeof(sysbuf))) - return 0; - - if (!sysbuf[0]) - 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; + } + } /* - * sysfs prints wwid as . - * multipath wwid uses '3' - * does "." always correspond to "3"? + * This function may be called multiple times for the same device, in + * particular if partitioned for each partition. */ - if (!(wwid = strchr(sysbuf, '.'))) - return 0; + if (!dm_list_empty(&dev->wwids)) + goto lookup; - /* skip the type and dot, just as '3' was skipped from wwids entry */ - wwid++; - - look = (long) dm_hash_lookup_binary(_wwid_hash_tab, wwid, strlen(wwid)); + /* + * 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; - if (look) { - log_debug_devs("dev_is_mpath_component %s multipath wwid %s", dev_name(dev), wwid); - return 1; + /* + * 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) { - if (_dev_is_mpath_component_sysfs(cmd, dev, holder_devno) == 1) + 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)) + if (_dev_in_wwid_file(cmd, dev, primary_result, primary_dev)) goto found; if (external_device_info_source() == DEV_EXT_UDEV) { @@ -641,6 +791,12 @@ int dev_is_mpath_component(struct cmd_context *cmd, struct device *dev, dev_t *h 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; diff --git a/lib/device/device.h b/lib/device/device.h index d0d670ec3..06440f44b 100644 --- a/lib/device/device.h +++ b/lib/device/device.h @@ -59,6 +59,14 @@ struct dev_ext { void *handle; }; +#define DEV_WWID_SIZE 128 + +struct dev_wwid { + struct dm_list list; + int type; + char id[DEV_WWID_SIZE]; +}; + #define DEV_ID_TYPE_SYS_WWID 0x0001 #define DEV_ID_TYPE_SYS_SERIAL 0x0002 #define DEV_ID_TYPE_MPATH_UUID 0x0003 @@ -105,6 +113,7 @@ struct dev_use { */ struct device { struct dm_list aliases; /* struct dm_str_list */ + struct dm_list wwids; /* struct dev_wwid, used for multipath component detection */ struct dm_list ids; /* struct dev_id, different entries for different idtypes */ struct dev_id *id; /* points to the the ids entry being used for this dev */ dev_t dev; @@ -206,5 +215,9 @@ void dev_destroy_file(struct device *dev); int dev_mpath_init(const char *config_wwids_file); void dev_mpath_exit(void); +struct dev_wwid *add_wwid(char *id, int id_type, struct dm_list *ids); +void free_wwids(struct dm_list *ids); +int parse_vpd_ids(const unsigned char *vpd_data, int vpd_datalen, struct dm_list *ids); +int format_t10_id(const unsigned char *in, int in_bytes, unsigned char *out, int out_bytes); #endif diff --git a/lib/device/device_id.c b/lib/device/device_id.c index f1928347c..9dec9f884 100644 --- a/lib/device/device_id.c +++ b/lib/device/device_id.c @@ -182,7 +182,9 @@ void free_dids(struct dm_list *ids) } } -int read_sys_block(struct cmd_context *cmd, struct device *dev, const char *suffix, char *sysbuf, int sysbufsize) +static int _read_sys_block(struct cmd_context *cmd, struct device *dev, + const char *suffix, char *sysbuf, int sysbufsize, + int binary, int *retlen) { char path[PATH_MAX]; dev_t devt = dev->dev; @@ -196,11 +198,17 @@ int read_sys_block(struct cmd_context *cmd, struct device *dev, const char *suff return 0; } - get_sysfs_value(path, sysbuf, sysbufsize, 0); + if (binary) { + ret = get_sysfs_binary(path, sysbuf, sysbufsize, retlen); + if (ret && !*retlen) + ret = 0; + } else { + ret = get_sysfs_value(path, sysbuf, sysbufsize, 0); + if (ret && !sysbuf[0]) + ret = 0; + } - if (sysbuf[0]) { - if (prim) - log_debug("Using primary device_id for partition %s.", dev_name(dev)); + if (ret) { sysbuf[sysbufsize - 1] = '\0'; return 1; } @@ -220,6 +228,19 @@ int read_sys_block(struct cmd_context *cmd, struct device *dev, const char *suff return 0; } +int read_sys_block(struct cmd_context *cmd, struct device *dev, + const char *suffix, char *sysbuf, int sysbufsize) +{ + return _read_sys_block(cmd, dev, suffix, sysbuf, sysbufsize, 0, NULL); +} + +int read_sys_block_binary(struct cmd_context *cmd, struct device *dev, + const char *suffix, char *sysbuf, int sysbufsize, + int *retlen) +{ + return _read_sys_block(cmd, dev, suffix, sysbuf, sysbufsize, 1, retlen); +} + static int _dm_uuid_has_prefix(char *sysbuf, const char *prefix) { if (!strncmp(sysbuf, prefix, strlen(prefix))) diff --git a/lib/device/device_id.h b/lib/device/device_id.h index 94773a65e..9b9c9ce03 100644 --- a/lib/device/device_id.h +++ b/lib/device/device_id.h @@ -58,6 +58,8 @@ void devices_file_exit(struct cmd_context *cmd); void unlink_searched_devnames(struct cmd_context *cmd); int read_sys_block(struct cmd_context *cmd, struct device *dev, const char *suffix, char *sysbuf, int sysbufsize); +int read_sys_block_binary(struct cmd_context *cmd, struct device *dev, + const char *suffix, char *sysbuf, int sysbufsize, int *retlen); int dev_has_mpath_uuid(struct cmd_context *cmd, struct device *dev, const char **idname_out); diff --git a/lib/device/parse_vpd.c b/lib/device/parse_vpd.c new file mode 100644 index 000000000..4bafa7b9e --- /dev/null +++ b/lib/device/parse_vpd.c @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2022 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/device/device.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Replace series of spaces with a single _. + */ +int format_t10_id(const unsigned char *in, int in_bytes, unsigned char *out, int out_bytes) +{ + int in_space = 0; + int retlen = 0; + int j = 0; + int i; + + for (i = 0; i < in_bytes; i++) { + if (!in[i]) + break; + if (j >= (out_bytes - 2)) + break; + /* skip leading spaces */ + if (!retlen && (in[i] == ' ')) + continue; + /* replace one or more spaces with _ */ + if (in[i] == ' ') { + in_space = 1; + continue; + } + /* spaces are finished so insert _ */ + if (in_space) { + out[j++] = '_'; + in_space = 0; + retlen++; + } + out[j++] = in[i]; + retlen++; + } + return retlen; +} + +static int _to_hex(const unsigned char *in, int in_bytes, unsigned char *out, int out_bytes) +{ + int off = 0; + int num; + int i; + + for (i = 0; i < in_bytes; i++) { + num = sprintf((char *)out + off, "%02x", in[i]); + if (num < 0) + break; + off += num; + if (off + 2 >= out_bytes) + break; + } + return off; +} + +#define ID_BUFSIZE 1024 + +/* + * based on linux kernel function + */ +int parse_vpd_ids(const unsigned char *vpd_data, int vpd_datalen, struct dm_list *ids) +{ + char id[ID_BUFSIZE]; + unsigned char tmp_str[ID_BUFSIZE]; + const unsigned char *d, *cur_id_str; + size_t id_len = ID_BUFSIZE; + int id_size = -1; + uint8_t cur_id_size = 0; + + memset(id, 0, ID_BUFSIZE); + for (d = vpd_data + 4; + d < vpd_data + vpd_datalen; + d += d[3] + 4) { + memset(tmp_str, 0, sizeof(tmp_str)); + + switch (d[1] & 0xf) { + case 0x1: + /* T10 Vendor ID */ + cur_id_size = d[3]; + if (cur_id_size + 4 > id_len) + cur_id_size = id_len - 4; + cur_id_str = d + 4; + format_t10_id(cur_id_str, cur_id_size, tmp_str, sizeof(tmp_str)); + id_size = snprintf(id, ID_BUFSIZE, "t10.%s", tmp_str); + if (id_size < 0) + break; + if (id_size >= ID_BUFSIZE) + id_size = ID_BUFSIZE - 1; + add_wwid(id, 1, ids); + break; + case 0x2: + /* EUI-64 */ + cur_id_size = d[3]; + cur_id_str = d + 4; + switch (cur_id_size) { + case 8: + _to_hex(cur_id_str, 8, tmp_str, sizeof(tmp_str)); + id_size = snprintf(id, ID_BUFSIZE, "eui.%s", tmp_str); + break; + case 12: + _to_hex(cur_id_str, 12, tmp_str, sizeof(tmp_str)); + id_size = snprintf(id, ID_BUFSIZE, "eui.%s", tmp_str); + break; + case 16: + _to_hex(cur_id_str, 16, tmp_str, sizeof(tmp_str)); + id_size = snprintf(id, ID_BUFSIZE, "eui.%s", tmp_str); + break; + default: + break; + } + if (id_size < 0) + break; + if (id_size >= ID_BUFSIZE) + id_size = ID_BUFSIZE - 1; + add_wwid(id, 2, ids); + break; + case 0x3: + /* NAA */ + cur_id_size = d[3]; + cur_id_str = d + 4; + switch (cur_id_size) { + case 8: + _to_hex(cur_id_str, 8, tmp_str, sizeof(tmp_str)); + id_size = snprintf(id, ID_BUFSIZE, "naa.%s", tmp_str); + break; + case 16: + _to_hex(cur_id_str, 16, tmp_str, sizeof(tmp_str)); + id_size = snprintf(id, ID_BUFSIZE, "naa.%s", tmp_str); + break; + default: + break; + } + if (id_size < 0) + break; + if (id_size >= ID_BUFSIZE) + id_size = ID_BUFSIZE - 1; + add_wwid(id, 3, ids); + break; + case 0x8: + /* SCSI name string */ + cur_id_size = d[3]; + cur_id_str = d + 4; + if (cur_id_size >= id_len) + cur_id_size = id_len - 1; + memcpy(id, cur_id_str, cur_id_size); + id_size = cur_id_size; + + /* + * Not in the kernel version, copying multipath code, + * which checks if this string begins with naa or eui + * and if so does tolower() on the chars. + */ + if (!strncmp(id, "naa.", 4) || !strncmp(id, "eui.", 4)) { + int i; + for (i = 0; i < id_size; i++) + id[i] = tolower(id[i]); + } + add_wwid(id, 8, ids); + break; + default: + break; + } + } + + return id_size; +}