diff options
Diffstat (limited to 'lib/device/device_id.c')
-rw-r--r-- | lib/device/device_id.c | 1249 |
1 files changed, 1249 insertions, 0 deletions
diff --git a/lib/device/device_id.c b/lib/device/device_id.c new file mode 100644 index 000000000..d9ef568b2 --- /dev/null +++ b/lib/device/device_id.c @@ -0,0 +1,1249 @@ +/* + * Copyright (C) 2020 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/commands/toolcontext.h" +#include "lib/device/device.h" +#include "lib/device/device_id.h" +#include "lib/device/dev-type.h" +#include "lib/device/device-types.h" +#include "lib/label/label.h" +#include "lib/metadata/metadata.h" +#include "lib/format_text/layout.h" + +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <time.h> +#include <sys/types.h> +#include <sys/file.h> +#include <sys/sysmacros.h> + +void free_uid(struct use_id *uid) +{ + if (uid->idname) + free(uid->idname); + if (uid->devname) + free(uid->devname); + if (uid->pvid) + free(uid->pvid); + free(uid); +} + +void free_uids(struct dm_list *uids) +{ + struct use_id *uid, *safe; + + dm_list_iterate_items_safe(uid, safe, uids) { + dm_list_del(&uid->list); + free_uid(uid); + } +} + +void free_did(struct dev_id *did) +{ + if (did->idname) + free(did->idname); + free(did); +} + +void free_dids(struct dm_list *dids) +{ + struct dev_id *did, *safe; + + dm_list_iterate_items_safe(did, safe, dids) { + dm_list_del(&did->list); + free_did(did); + } +} + +static int _read_sys_block(struct cmd_context *cmd, struct device *dev, const char *suffix, const char **idname) +{ + char path[PATH_MAX]; + char buf[PATH_MAX] = { 0 }; + dev_t devt = dev->dev; + dev_t prim = 0; + int ret; + + retry: + if (dm_snprintf(path, sizeof(path), "%sdev/block/%d:%d/%s", + dm_sysfs_dir(), (int)MAJOR(devt), (int)MINOR(devt), suffix) < 0) { + return 0; + } + + get_sysfs_value(path, buf, sizeof(buf), 0); + + if (buf[0]) { + if (prim) + log_debug("Using primary device_id for partition %s.", dev_name(dev)); + if (!(*idname = strdup(buf))) + return 0; + return 1; + } + + if (prim) + goto fail; + + /* in case it failed because dev is a partition... */ + + ret = dev_get_primary_dev(cmd->dev_types, dev, &prim); + if (ret == 2) { + devt = prim; + goto retry; + } + + fail: + *idname = NULL; + return 1; +} + +static int _read_sys_wwid(struct cmd_context *cmd, struct device *dev, const char **idname) +{ + return _read_sys_block(cmd, dev, "device/wwid", idname); +} + +static int _read_sys_serial(struct cmd_context *cmd, struct device *dev, const char **idname) +{ + return _read_sys_block(cmd, dev, "device/serial", idname); +} + +/* the dm uuid uses the wwid of the underlying dev */ + +static int _read_mpath_uuid(struct cmd_context *cmd, struct device *dev, const char **idname) +{ + return _read_sys_block(cmd, dev, "dm/uuid", idname); +} + +static int _dev_has_mpath_uuid(struct cmd_context *cmd, struct device *dev, const char **idname) +{ + dev_t devt = dev->dev; + dev_t prim; + int ret; + + ret = dev_get_primary_dev(cmd->dev_types, dev, &prim); + if (ret == 2) + devt = prim; + + if (MAJOR(devt) != cmd->dev_types->device_mapper_major) + return 0; + + _read_mpath_uuid(cmd, dev, idname); + + if (*idname) + return 1; + return 0; +} + +static int _read_loop_file(struct cmd_context *cmd, struct device *dev, const char **idname) +{ + return _read_sys_block(cmd, dev, "loop/backing_file", idname); +} + +/* + * TODO: should there be a list like lvm.conf + * device_id_types = [ "sys_wwid", "sys_serial" ] + * that controls which idtype's will be used? + * + * TODO: add a type for md devices, probably have it + * use the uuid from the md dev superblock. This would + * help in case of inconsistent md dev names, but would + * not help in case md components were all cloned. + * + * TODO: should we include some partition number information + * for the entry when we use the primary wwid for multiple + * partitions? + */ +static const char *_device_id_system_read(struct cmd_context *cmd, struct device *dev, uint16_t idtype) +{ + const char *idname = NULL; + + if (idtype == DEV_ID_TYPE_SYS_WWID) + _read_sys_wwid(cmd, dev, &idname); + + else if (idtype == DEV_ID_TYPE_SYS_SERIAL) + _read_sys_serial(cmd, dev, &idname); + + else if (idtype == DEV_ID_TYPE_DEVNAME) + idname = strdup(dev_name(dev)); + + else if (idtype == DEV_ID_TYPE_MPATH_UUID) + _read_mpath_uuid(cmd, dev, &idname); + + else if (idtype == DEV_ID_TYPE_LOOP_FILE) + _read_loop_file(cmd, dev, &idname); + + return idname; +} + +const char *idtype_to_str(uint16_t idtype) +{ + if (idtype == DEV_ID_TYPE_SYS_WWID) + return "sys_wwid"; + + if (idtype == DEV_ID_TYPE_SYS_SERIAL) + return "sys_serial"; + + if (idtype == DEV_ID_TYPE_DEVNAME) + return "devname"; + + if (idtype == DEV_ID_TYPE_MPATH_UUID) + return "mpath_uuid"; + + if (idtype == DEV_ID_TYPE_LOOP_FILE) + return "loop_file"; + + return "unknown"; +} + +uint16_t idtype_from_str(const char *str) +{ + if (!strcmp(str, "sys_wwid")) + return DEV_ID_TYPE_SYS_WWID; + if (!strcmp(str, "sys_serial")) + return DEV_ID_TYPE_SYS_SERIAL; + if (!strcmp(str, "devname")) + return DEV_ID_TYPE_DEVNAME; + if (!strcmp(str, "mpath_uuid")) + return DEV_ID_TYPE_MPATH_UUID; + if (!strcmp(str, "loop_file")) + return DEV_ID_TYPE_LOOP_FILE; + return 0; +} + +const char *dev_idtype(struct device *dev) +{ + if (!dev || !dev->id) + return NULL; + + return idtype_to_str(dev->id->idtype); +} + +const char *dev_id(struct device *dev) +{ + if (dev && dev->id) + return dev->id->idname; + return NULL; +} + +static void _copy_idline_str(char *src, char *dst, int len) +{ + char *s, *d = dst; + + memset(dst, 0, len); + + if (!(s = strchr(src, '='))) + return; + s++; + while ((*s == ' ') && (s < src + len)) + s++; + while ((*s != ' ') && (*s != '\0') && (*s != '\n') && (s < src + len)) { + *d = *s; + s++; + d++; + } +} + +int device_ids_read(struct cmd_context *cmd) +{ + char line[PATH_MAX]; + char buf[PATH_MAX]; + char *idtype, *idname, *devname, *pvid; + struct use_id *uid; + FILE *fp; + int fl_fd, fl_err = -1; + int ret = 1; + + /* + * TODO: allow the use_device_ids list to come from a + * command line option instead of devices_file? + * If so, add use_id structs to use_device_ids based + * on the reading the command line args here. + */ + + if (!cmd->enable_device_ids) + return 1; + + free_uids(&cmd->use_device_ids); + + if (cmd->nolocking) + goto use_file; + + if ((fl_fd = open(cmd->devices_file, O_RDWR|O_CREAT)) < 0) { + log_warn("Cannot open devices_file to flock."); + goto use_file; + } + if ((fl_err = flock(fl_fd, LOCK_SH))) { + log_warn("Cannot lock devices_file to read."); + close(fl_fd); + } + +use_file: + if (!(fp = fopen(cmd->devices_file, "r"))) { + log_warn("Cannot open devices_file to read."); + ret = 0; + goto out; + } + + while (fgets(line, sizeof(line), fp)) { + if (line[0] == '#') + continue; + + idtype = strstr(line, "IDTYPE"); + idname = strstr(line, "IDNAME"); + devname = strstr(line, "DEVNAME"); + pvid = strstr(line, "PVID"); + + /* These two are the minimum required. */ + if (!idtype || !idname) + continue; + + if (!(uid = zalloc(sizeof(struct use_id)))) + return 0; + + _copy_idline_str(idtype, buf, PATH_MAX); + if (buf[0]) + uid->idtype = idtype_from_str(buf); + + _copy_idline_str(idname, buf, PATH_MAX); + if (buf[0]) + uid->idname = strdup(buf); + + if (!uid->idtype || !uid->idname) { + log_print("Ignoring device: %s", line); + free_uid(uid); + continue; + } + + if (devname) { + _copy_idline_str(devname, buf, PATH_MAX); + if (buf[0] && (buf[0] != '.')) + uid->devname = strdup(buf); + } + + if (pvid) { + _copy_idline_str(pvid, buf, PATH_MAX); + if (buf[0] && (buf[0] != '.')) + uid->pvid = strdup(buf); + } + + dm_list_add(&cmd->use_device_ids, &uid->list); + } + + if (fclose(fp)) + stack; +out: + if (!cmd->nolocking && !fl_err) { + if (flock(fl_fd, LOCK_UN)) + stack; + if (close(fl_fd)) + stack; + } + return ret; +} + +int device_ids_write(struct cmd_context *cmd) +{ + FILE *fp; + time_t t; + struct use_id *uid; + const char *devname; + const char *pvid; + int fl_fd, fl_err = -1; + int ret = 1; + + if (!cmd->enable_device_ids) + return 1; + + if (cmd->nolocking) + goto use_file; + if ((fl_fd = open(cmd->devices_file, O_RDWR|O_CREAT)) < 0) { + log_warn("Cannot open devices_file to flock."); + goto use_file; + } + if ((fl_err = flock(fl_fd, LOCK_EX))) { + log_warn("Cannot lock devices_file to write."); + close(fl_fd); + } + +use_file: + if (!(fp = fopen(cmd->devices_file, "w+"))) { + log_warn("Cannot open devices_file to write."); + ret = 0; + goto out; + } + + t = time(NULL); + + fprintf(fp, "# LVM will use devices listed in this file.\n"); + fprintf(fp, "# IDTYPE and IDNAME fields are required, the DEVNAME path may change.\n"); + fprintf(fp, "# Created by LVM command %s pid %d at %s\n", cmd->name, getpid(), ctime(&t)); + + dm_list_iterate_items(uid, &cmd->use_device_ids) { + devname = uid->dev ? dev_name(uid->dev) : uid->devname; + if (!devname || devname[0] != '/') + devname = "."; + + if (!uid->pvid || !uid->pvid[0] || (uid->pvid[0] == '.')) + pvid = "."; + else + pvid = uid->pvid; + + fprintf(fp, "IDTYPE=%s IDNAME=%s DEVNAME=%s PVID=%s\n", + idtype_to_str(uid->idtype) ?: ".", + uid->idname ?: ".", devname, pvid); + } + + if (fflush(fp)) + stack; + if (fclose(fp)) + stack; + +out: + if (!cmd->nolocking && !fl_err) { + if (flock(fl_fd, LOCK_UN)) + stack; + if (close(fl_fd)) + stack; + } + return ret; +} + +struct use_id *get_uid_for_dev(struct cmd_context *cmd, struct device *dev) +{ + struct use_id *uid; + + dm_list_iterate_items(uid, &cmd->use_device_ids) { + if (uid->dev == dev) + return uid; + } + return NULL; +} + +struct use_id *get_uid_for_pvid(struct cmd_context *cmd, const char *pvid) +{ + struct use_id *uid; + + dm_list_iterate_items(uid, &cmd->use_device_ids) { + if (!uid->pvid) + continue; + if (!strcmp(uid->pvid, pvid)) + return uid; + } + return NULL; +} + +static struct use_id *_get_uid_for_devname(struct cmd_context *cmd, const char *devname) +{ + struct use_id *uid; + + dm_list_iterate_items(uid, &cmd->use_device_ids) { + if (!uid->devname) + continue; + if (!strcmp(uid->devname, devname)) + return uid; + } + return NULL; +} + +static struct use_id *_get_uid_for_device_id(struct cmd_context *cmd, uint16_t idtype, const char *idname) +{ + struct use_id *uid; + + dm_list_iterate_items(uid, &cmd->use_device_ids) { + if ((uid->idtype == idtype) && !strcmp(uid->idname, idname)) + return uid; + } + return NULL; +} + +/* + * Add or update entry for this dev. + * IDTYPE=sys_wwid IDNAME=01234566 DEVNAME=/dev/sdb PVID=99393939 [OPTS=xx,yy,zz] + * + * add an entry to dev->ids and point dev->id to it. + * add or update entry in cmd->use_device_ids + */ +int device_id_add(struct cmd_context *cmd, struct device *dev, const char *pvid, + const char *idtype_arg, const char *id_arg) +{ + uint16_t idtype = 0; + const char *idname; + struct use_id *uid = NULL, *uid_pvid, *uid_devname, *uid_devid; + struct dev_id *did; + int found_did = 0; + + if (!cmd->enable_device_ids) + return 1; + + uid_pvid = get_uid_for_pvid(cmd, pvid); + uid_devname = _get_uid_for_devname(cmd, dev_name(dev)); + + /* TODO: should deviceidtype command line option work for mpath/loop? */ + /* TODO: add more idtypes for special devs (e.g. MD, DRBD, NBD) that don't have wwid */ + + if (_dev_has_mpath_uuid(cmd, dev, &idname)) { + idtype = DEV_ID_TYPE_MPATH_UUID; + goto id_done; + } + + if (MAJOR(dev->dev) == cmd->dev_types->loop_major) { + idtype = DEV_ID_TYPE_LOOP_FILE; + goto id_name; + } + + /* + * First use type specified by user option, then use a previous + * type, then use the default type. + * TODO: allow lvm.conf device_id_types to control idtypes used here? + */ + + if (idtype_arg) { + if (!(idtype = idtype_from_str(idtype_arg))) + log_warn("WARNING: ignoring unknown device_id type %s.", idtype_arg); + else { + if (id_arg) { + idname = id_arg; + goto id_done; + } + goto id_name; + } + } + + /* If there's an entry for this pvid, use the idtype from that */ + if (!idtype && uid_pvid) { + idtype = uid_pvid->idtype; + goto id_name; + } + + idtype = DEV_ID_TYPE_SYS_WWID; + +id_name: + if (!(idname = _device_id_system_read(cmd, dev, idtype))) { + if (idtype == DEV_ID_TYPE_SYS_WWID) { + idtype = DEV_ID_TYPE_SYS_SERIAL; + goto id_name; + } + idtype = DEV_ID_TYPE_DEVNAME; + goto id_name; + } + +id_done: + dm_list_iterate_items(did, &dev->ids) { + if (did->idtype == idtype) { + found_did = 1; + break; + } + } + + if (found_did && !strcmp(did->idname, idname)) + free((char *)idname); + else if (found_did && strcmp(did->idname, idname)) { + dm_list_del(&did->list); + free_did(did); + found_did = 0; + } + + if (!found_did) { + if (!(did = zalloc(sizeof(struct dev_id)))) + return_0; + did->idtype = idtype; + did->idname = (char *)idname; + did->dev = dev; + dm_list_add(&dev->ids, &did->list); + } + + dev->id = did; + dev->flags |= DEV_MATCHED_USE_ID; + + uid_devid = _get_uid_for_device_id(cmd, did->idtype, did->idname); + + /* + * This new entry could potentially overlap three existing entries + * with matching pvid, device_id, and devname. + */ + if (uid_pvid) { + /* update the existing entry with matching pvid */ + uid = uid_pvid; + dm_list_del(&uid->list); + + log_print("Updating existing device entry for PVID"); + + if (uid_devid && (uid_devid != uid_pvid)) { + /* warn about another entry using the same device_id */ + log_warn("WARNING: duplicate device_id %s for PVIDs %s %s", + uid_devid->idname, uid_devid->pvid, uid_pvid->pvid); + } + + if (uid_devname && (uid_devname != uid_pvid)) { + /* clear devname in another entry with our devname */ + log_print("Clearing stale devname %s for PVID %s", + uid_devname->devname, uid_devname->pvid); + free(uid_devname->devname); + uid_devname->devname = NULL; + } + + } else if (uid_devid) { + const char *check_idname = NULL; + + /* + * Do we create a new uid or update the existing uid? + * If it's the same device, update the existing uid, + * buf it it's two devices with the same device_id, then + * create a new uid. + * + * We know that 'dev' has device_id 'did'. + * Check if uid_devid->dev is different from 'dev' + * and that uid_devid->idname matches did. + * If so, then there are two different devices with + * the same device_id (create a new uid for dev.) + * If not, then update the existing uid_devid. + */ + + if (uid_devid->dev != dev) + check_idname = _device_id_system_read(cmd, uid_devid->dev, did->idtype); + + if (check_idname && !strcmp(check_idname, did->idname)) { + int ret1, ret2; + dev_t devt1, devt2; + + /* two different devices have the same device_id, + create a new uid for the device being added */ + + /* dev_is_partitioned() the dev open to read it. */ + if (!label_scan_open(uid_devid->dev)) + log_print("Cannot open %s", dev_name(uid_devid->dev)); + + if (dev_is_partitioned(cmd->dev_types, uid_devid->dev)) { + /* Check if existing entry is whole device and new entry is a partition of it. */ + ret1 = dev_get_primary_dev(cmd->dev_types, dev, &devt1); + if ((ret1 == 2) && (devt1 == uid_devid->dev->dev)) + log_print("WARNING: remove partitioned device %s from devices file.", dev_name(uid_devid->dev)); + } else { + /* Check if both entries are partitions of the same device. */ + ret1 = dev_get_primary_dev(cmd->dev_types, dev, &devt1); + ret2 = dev_get_primary_dev(cmd->dev_types, uid_devid->dev, &devt2); + + if ((ret1 == 2) && (ret2 == 2) && (devt1 == devt2)) { + log_print("Partitions %s %s have same device_id %s", + dev_name(dev), dev_name(uid_devid->dev), idname); + } + } + + log_print("Duplicate device_id %s %s for %s and %s", + idtype_to_str(did->idtype), check_idname, + dev_name(dev), dev_name(uid_devid->dev)); + + if (!(uid = zalloc(sizeof(struct use_id)))) + return_0; + } else { + /* update the existing entry with matching devid */ + uid = uid_devid; + dm_list_del(&uid->list); + log_print("Updating existing device entry for device_id"); + } + + if (uid_devname && (uid_devname != uid_devid)) { + /* clear devname in another entry with our devname */ + log_print("Clearing stale devname %s for PVID %s", + uid_devname->devname, uid_devname->pvid); + free(uid_devname->devname); + uid_devname->devname = NULL; + } + + } else if (uid_devname) { + /* clear devname in another entry with our devname */ + log_print("Clearing stale devname %s for PVID %s", + uid_devname->devname, uid_devname->pvid); + free(uid_devname->devname); + uid_devname->devname = NULL; + } + + if (!uid) { + if (!(uid = zalloc(sizeof(struct use_id)))) + return_0; + } + + if (uid->idname) + free(uid->idname); + if (uid->devname) + free(uid->devname); + if (uid->pvid) + free(uid->pvid); + + uid->idtype = did->idtype; + uid->idname = strdup(did->idname); + uid->devname = strdup(dev_name(dev)); + uid->dev = dev; + uid->pvid = strdup(pvid); + + if (!uid->idname || !uid->idname || !uid->pvid) { + free_uid(uid); + return 0; + } + + log_print("Add %s %s PVID %s", dev_name(dev), uid->idname, uid->pvid); + + dm_list_add(&cmd->use_device_ids, &uid->list); + + return 1; +} + +/* + * Add an entry when there is no current device for it. + * The known info, e.g. from metadata, is used to create + * the entry. + * devname arg could be wrong since there's no dev + */ +int device_id_add_nodev(struct cmd_context *cmd, + const char *idtype_str, + const char *idname, + const char *devname, + const char *pvid) +{ + struct use_id *uid; + uint16_t idtype = 0; + + if (!cmd->enable_device_ids) + return 1; + + if (!pvid || (pvid[0] == '.')) + return 0; + + if (!idtype_str || !idname) + return 0; + + if (idtype_str) + idtype = idtype_from_str(idtype_str); + + if (!(uid = get_uid_for_pvid(cmd, pvid))) { + if (!(uid = zalloc(sizeof(struct use_id)))) + return_0; + } + + if (uid->idtype && (uid->idtype != idtype)) { + log_print("Changing device_id_type from %s to %s for %s", + idtype_to_str(uid->idtype), idtype_to_str(idtype), devname); + } + if (uid->idtype && (uid->idtype == idtype) && strcmp(uid->idname, idname)) { + log_print("Changing device_id from %s to %s for %s", + uid->idname, idname, devname); + } + + if (uid->idname) + free(uid->idname); + if (uid->devname) + free(uid->devname); + if (uid->pvid) + free(uid->pvid); + uid->idname = NULL; + uid->devname = NULL; + uid->pvid = NULL; + uid->dev = NULL; + + uid->idtype = idtype; + + if (pvid) + uid->pvid = strdup(pvid); + if (idname) + uid->idname = strdup(idname); + if (devname) + uid->devname = strdup(devname); + + log_print("Add %s %s %s", devname ?: ".", uid->idname ?: ".", uid->pvid); + + dm_list_add(&cmd->use_device_ids, &uid->list); + + return 1; +} + +/* + * Update entry for this dev. + * Set PVID=. + * update entry in cmd->use_device_ids + */ +void device_id_pvremove(struct cmd_context *cmd, struct device *dev) +{ + struct use_id *uid; + + if (!cmd->enable_device_ids) + return; + + if (!(uid = get_uid_for_dev(cmd, dev))) { + log_warn("WARNING: use_device_ids does not include %s", dev_name(dev)); + return; + } + + if (uid->pvid) { + free(uid->pvid); + uid->pvid = NULL; + } +} + +/* + * check for dev->ids entry with uid->idtype, if found compare it, + * if not, system_read of this type and add entry to dev->ids, compare it. + * When a match is found, set up links among uid/did/dev. + */ + +static int _match_uid_to_dev(struct cmd_context *cmd, struct use_id *uid, struct device *dev) +{ + struct dev_id *did; + const char *idname; + + dm_list_iterate_items(did, &dev->ids) { + if (did->idtype == uid->idtype) { + if (did->idname && !strcmp(did->idname, uid->idname)) { + uid->dev = dev; + dev->id = did; + dev->flags |= DEV_MATCHED_USE_ID; + return 1; + } else { + return_0; + } + } + } + + if (!(did = zalloc(sizeof(struct dev_id)))) + return_0; + + if (!(idname = _device_id_system_read(cmd, dev, uid->idtype))) { + /* Save a new did in dev->ids for this type to indicate no match + to avoid repeated system_read, since this called many times. + Setting idtype and NULL idname means no id of this type. */ + did->idtype = uid->idtype; + did->dev = dev; + dm_list_add(&dev->ids, &did->list); + return 0; + } + + /* Save this id for the device (so it can be quickly checked again), even + if it's not the idtype used to identify the dev in device_id_file. */ + did->idtype = uid->idtype; + did->idname = (char *)idname; + did->dev = dev; + dm_list_add(&dev->ids, &did->list); + + if (!strcmp(idname, uid->idname)) { + uid->dev = dev; + dev->id = did; + dev->flags |= DEV_MATCHED_USE_ID; + return 1; + } + + return 0; +} + +int device_ids_match_dev(struct cmd_context *cmd, struct device *dev) +{ + struct use_id *uid; + + /* First check the uid entry with matching devname since it's likely correct. */ + if ((uid = _get_uid_for_devname(cmd, dev_name(dev)))) { + if (_match_uid_to_dev(cmd, uid, dev)) + return 1; + } + + /* Check all uid entries since the devname could have changed. */ + dm_list_iterate_items(uid, &cmd->use_device_ids) { + if (!_match_uid_to_dev(cmd, uid, dev)) + continue; + return 1; + } + + return 0; +} + +/* + +pvid is needed in the devices_file, and wwid (device_id more generally) +is needed in metadata in order to handle cases where a device has no wwid +or the wwid changes. In these cases the correct set of devices can be +found and the devices_file can be corrected. (A wwid in the metadata will +also eliminate the problem of duplicate pvs for those devices.) + +Three identifiers: wwid, devname, pvid +- devname can change, cannot be duplicated, cannot be unknown +- wwid can change (rare), can be duplicated (rare), can be unknown +- pvid cannot change, can be duplicated, cannot be unknown + +(wwid is more generally the device_id, and would only change or +be duplicated when the device_id is not a wwid but some other +identifier used when wwid is not available.) + + +if devname changes +------------------ +. if wwid exists, lvm corrects devname (by reading wwid of all devices) +. if no wwid exists for the entry in devices_file, and new devname is + out of the devices_file, then PV appears missing. lvm would need + to read headers from all devices on the system to find the PVID, or + the user could run "lvmdevices --addpvid <pvid>" to read device headers + to find PVID. When found, devices_file is updated. +. if no wwid, and the new devname is in devices_file, then lvm will + find the PV on the new devname during normal scan, and update the + devices_file. + + +if wwid changes +--------------- +. same underlying storage, different wwid reported for it + +. if the new wwid is not used in another devices_file entry + devices_file: WWID=XXX DEVNAME=/dev/foo PVID=AAA + after reboot the wwid for the device changes to YYY + YYY does not appear in devices_file or lvm metadata, so lvm doesn't know to scan + the dev with that wwid + device_ids_match() will find no dev with XXX, so uid->dev will be null + the PV appears to be missing + user runs a cmd to scan all devnames on the system (outside devices_file) + to find a device with pvid AAA. when it's found, the cmd updates devices_file + entry to have WWID=YYY PVID=AAA. "lvmdevices --addpvid AAA" + (if the devname for this entry remained unchanged, then lvm could likely + avoid scanning all devs, by just scanning /dev/foo and finding AAA, + this is basically an optimization that could be applied automatically + and might avoid requiring the user to run a cmd to find the pv) + +. new wwid value is included in devices_file for a different PV, + causing duplicate wwids + devices_file: WWID=XXX DEVNAME=/dev/foo PVID=AAA + WWID=YYY DEVNAME=/dev/bar PVID=BBB + after reboot the wwid for the first device changes to YYY + device_ids_match() will see two devs with wwid YYY + lvm will scan both devices and find one with AAA and the other BBB + lvm will update devices_file to have WWID=YYY for both devs + lvm may suggest using a different idtype if that would help + +. two wwids in devices_file are swapped + devices_file: WWID=XXX DEVNAME=/dev/foo PVID=AAA + WWID=YYY DEVNAME=/dev/bar PVID=BBB + the wwid for AAA changes to YYY + the wwid for BBB changes to XXX + device_ids_match() will seem to be ok (possibly complain about devnames) + both devs will be scanned by label_scan + device_ids_validate() will see different pvids for each entry + and will update devices_file + + +if pvid and wwid are both duplicated +------------------------------------ +. devices_file: WWID=XXX DEVNAME=/dev/foo PVID=AAA + new state: + WWID=XXX DEVNAME=/dev/foo PVID=AAA + WWID=XXX DEVNAME=/dev/bar PVID=AAA + This would fall back to the old duplicate device handling. + We would need to keep the old duplicate pv handling to handle + this case. + If the wwid originates from data blocks on the storage, + then this can easily happen by cloning the disks. + + +if wwids begin as duplicates +---------------------------- +. lvm can still use the wwid for filtering, + but it won't help if pvid is also duplicated + + +if pvid is duplicated but wwid is not +------------------------------------- +. lvm will use the wwid to choose the right one + + +if wwid is unknown +------------------ +. and no other unique device_id is available +. this would work similarly to the old filter accepting only a list of devnames +. if devname changes and new devname in list, lvm fixes devname +. if devname changes and new devname out of filter, pv missing, then + either automatically read all devs to find pvid, or have user run cmd to do this + (lvmdevices --addpvid <pvid> would read every device on the system for header with pvid, + and if found update devices_file with the new devname/pvid) +*/ + +/* + * For each entry on cmd->use_device_ids, find a struct device from dev-cache. + * This must not open or read devices. filters are applied after this, + * and they may open devs in the first filter stage. The second filtering + * stage, done as a part of label_scan, is finally allowed to read devices. + * + * When a device id of a particular type is read for a dev, a did for that + * type is saved in dev->ids in case it needs to be checked again. + * + * When a particular dev_id for a dev (in dev-cache) is matched to a use_dev + * (from use_device_ids), then: + * . uid->dev = dev; + * . dev->id = did; + * . dev->flags |= DEV_MATCHED_USE_ID; + */ + +void device_ids_match(struct cmd_context *cmd) +{ + struct dev_iter *iter; + struct use_id *uid; + struct device *dev; + + if (!cmd->enable_device_ids) + return; + + /* + * We would set cmd->skip_filter_deviceid but we are disabling + * all filters so it's not necessary. + */ + + dm_list_iterate_items(uid, &cmd->use_device_ids) { + /* already matched */ + if (uid->dev && (uid->dev->flags & DEV_MATCHED_USE_ID)) + continue; + + /* + * uid->devname may be incorrect, but it's often correct, so it's the + * most efficient place to begin. + * + * NULL filter is used because we are just setting up the + * the uid/dev pairs in preparation for using the filters. + */ + if (uid->devname && + (dev = dev_cache_get(cmd, uid->devname, NULL))) { + /* On success, device_id_match_uid_to_dev() links the uid, dev, and did. */ + if (_match_uid_to_dev(cmd, uid, dev)) + continue; + else { + /* uid->devname now belongs to a different device */ + log_print("Device with name %s has changed.", uid->devname); + } + } + + /* At a minimum some devname needs to be added or updated. + device_ids_validate may find other reasons to update the + file. Are there commands where device_ids_validate would + not be run, so we should update the file here? */ + + /* + * Iterate through all devs and try to match uid. + * + * If a match is made here it means the uid->devname is wrong so the + * device_id file should be udpated with a new devname. + * + * NULL filter is used because we are just setting up the + * the uid/dev pairs in preparation for using the filters. + */ + if (!(iter = dev_iter_create(NULL, 0))) + continue; + while ((dev = dev_iter_get(cmd, iter))) { + if (dev->flags & DEV_MATCHED_USE_ID) + continue; + if (_match_uid_to_dev(cmd, uid, dev)) + break; + } + dev_iter_destroy(iter); + } + + /* + * Look for entries in devices_file for which we found no device. + */ + dm_list_iterate_items(uid, &cmd->use_device_ids) { + if (uid->dev && (uid->dev->flags & DEV_MATCHED_USE_ID)) + continue; + + if (uid->dev && !(uid->dev->flags & DEV_MATCHED_USE_ID)) { + /* FIXME: is this possible? */ + log_error("Device %s not matched to device_id", dev_name(uid->dev)); + } + + log_print("Device with previous name %s not found with %s %s PVID %s.", + uid->devname, idtype_to_str(uid->idtype), uid->idname, uid->pvid); + } +} + +/* + * This is called after label_scan() to compare what was found on disks + * vs what's in the devices_file. The devices_file could be outdated + * and need correcting; the authoritative data is what's on disk. + * Now that we have read the device labels in label_scan and have the PVID's + * we can check the pvid's of use_device_ids entries from the device_id_file. + */ +void device_ids_validate(struct cmd_context *cmd) +{ + struct use_id *uid; + int update_file = 0; + + if (!cmd->enable_device_ids) + return; + + dm_list_iterate_items(uid, &cmd->use_device_ids) { + if (!uid->dev) + continue; + + if (uid->dev->pvid[0] && (!uid->pvid || strcmp(uid->dev->pvid, uid->pvid))) { + log_print("Device %s has updated PVID %s from devices_file (was %s)", + dev_name(uid->dev), uid->dev->pvid, uid->pvid); + if (uid->pvid) + free(uid->pvid); + uid->pvid = strdup(uid->dev->pvid); + update_file = 1; + } + + if (!uid->devname || strcmp(dev_name(uid->dev), uid->devname)) { + log_print("Device %s has updated devname from devices_file (was %s).", + dev_name(uid->dev), uid->devname ?: "."); + if (uid->devname) + free(uid->devname); + uid->devname = strdup(dev_name(uid->dev)); + update_file = 1; + } + } + + if (update_file) + device_ids_write(cmd); + + /* + * Issue: if devices have no wwid or serial numbers, entries in + * devices_file are identified only by their unstable devnames. + * It the devnames then change, all devices on the system need to + * read to find the PVs. Reading all devices on the system is + * one thing that the devices_file is meant to avoid. A new + * config setting could be used to enable/disable this behavior. + * + * TODO: if there are entries on use_device_ids that have no dev + * and are using DEV_ID_TYPE_DEVNAME, then it's possible that the + * unstable devname simply changed and the new devname was not + * included in devices_file. We need to read all devices on the + * system to find one with the missing PVID, label_scan it, and + * update devices_file with the new devname for the PVID. + * + * This function should tell setup_devices() that it should do a + * label_scan_for_pvid() (which only reads the headers for a PVID) + * on system devices that it did not already cover in the completed + * label_scan(). label_scan_for_pvid() would get a list of pvids + * to look for and return a list of devs on which they are found. + * That list of devs would then be passed to label_scan_devs() + * to do the full label_scan on them. + * + * A config setting would be able to disable this automatic scan + * of all devs for missing pvids, and the usage of the new devnames + * where those PVs are found. Without this scan, PVs on unstable + * devnames would be missing until a user manually runs a command + * to search devices for the missing PVs. The user could selectively + * scan certain devs and avoid devs that should not be touched. + */ +} + +int devices_file_valid(struct cmd_context *cmd) +{ + struct stat buf; + + if (!cmd->devices_file || !strlen(cmd->devices_file)) + return 0; + + if (stat(cmd->devices_file, &buf)) + return 0; + + return 1; +} + +void device_id_read_pvid(struct cmd_context *cmd, struct device *dev) +{ + char buf[4096] __attribute__((aligned(8))); + struct pv_header *pvh; + + memset(buf, 0, sizeof(buf)); + + if (!label_scan_open(dev)) + return; + + /* + * We could do: + * dev_read_bytes(dev, 512, LABEL_SIZE, buf); + * which works, but there's a bcache issue that + * prevents proper invalidation after that. + */ + if (!dev_read_bytes(dev, 0, 4096, buf)) { + label_scan_invalidate(dev); + return; + } + + pvh = (struct pv_header *)(buf + 512 + 32); + + memcpy(dev->pvid, pvh->pv_uuid, ID_LEN); + + label_scan_invalidate(dev); +} + +/* + * Read pv_header for each uid to get pvid. + * Compare with uid->pvid, and fix uid->pvid if different. + */ +void device_ids_read_pvids(struct cmd_context *cmd) +{ + char buf[4096] __attribute__((aligned(8))); + struct device *dev; + struct pv_header *pvh; + struct use_id *uid; + + dm_list_iterate_items(uid, &cmd->use_device_ids) { + dev = uid->dev; + + if (!label_scan_open(dev)) + continue; + + memset(buf, 0, sizeof(buf)); + + /* To read the label we could read 512 bytes at offset 512, + but we read 4096 because some of the filters that are + tested will want to look beyond the label sector. */ + + if (!dev_read_bytes(dev, 0, 4096, buf)) { + label_scan_invalidate(dev); + continue; + } + + /* + * This device is already in the devices file, and this + * function is used to check/fix the devices file entries, so + * we don't want to exclude the device by applying filters. + * What may be useful is to call passes_filter on this device + * so that we can print a warning if a devices_file entry would + * be excluded by filters. + */ + + pvh = (struct pv_header *)(buf + 512 + 32); + + if ((!uid->pvid && pvh->pv_uuid[0]) || + (uid->pvid && memcmp(pvh->pv_uuid, uid->pvid, ID_LEN))) { + memcpy(dev->pvid, pvh->pv_uuid, ID_LEN); + + log_print("Device %s has PVID %s devices_file has PVID %s", + dev_name(dev), + dev->pvid[0] ? (char *)dev->pvid : ".", + uid->pvid ?: "."); + + if (uid->pvid) + free(uid->pvid); + uid->pvid = strdup(dev->pvid); + } + + /* Since we've read the first 4K of the device, the + filters should not for the most part need to do + any further reading of the device. */ + + log_debug("Checking filters with data for %s", dev_name(dev)); + if (!cmd->filter->passes_filter(cmd, cmd->filter, dev, NULL)) { + /* FIXME: print which filters it doesn't pass */ + log_warn("WARNING: %s in devices file is excluded by filters.", + dev_name(dev)); + } + + label_scan_invalidate(dev); + } +} |