summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Teigland <teigland@redhat.com>2020-06-23 13:25:41 -0500
committerDavid Teigland <teigland@redhat.com>2020-07-06 17:39:54 -0500
commit771eb2f23025f949b3f4f77557d4eee31e750a4c (patch)
tree5e74ce94468c11cbfb3d33ad7bababcb08176978
parentb0787033023fdc213f3fdd3efc488e70f44e1fcd (diff)
downloadlvm2-dev-dct-deviceid-4.tar.gz
device usage based on devices_file and device_iddev-dct-deviceid-4
devices_file, e.g. /etc/lvm/lvm_devices.dat, is a list of devices that lvm can use. Option --devicesfile can sepecify a different file with a separate set of devices for lvm to use. This option allows different applications to use lvm on different sets of devices. In most cases (with limited exceptions), lvm will not read or use a device not listed in devices_file. When the devices_file is used, the filter-regex is not used and the filter settings in lvm.conf are ignored. lvm uses the devices_file to control access to devices using the new filter-deviceid. Setting --devicesfile to "" on the command line, or devicesfile to "" in lvm.conf will disable the use of the devices_file and filter-deviceid. This allows lvm to see and use any device on the system. In this case lvm will fall back to using filter-regex and the filter config settings in lvm.conf. device_id, e.g. wwid or serial number from sysfs, is a unique ID that identifies a device without reading it, and which will change if the device is copied to another. The device_id is used in the devices_file and is included in VG metadata sections. Each device_id has a device_id_type which indicates where the device_id comes from, e.g. "sys_wwid" means the device_id comes from the sysfs wwid file. Others are sys_serial, mpath_uuid, loop_file, devname. (devname is the device path which is a fallback when no other proper device_id_type is available.) filter-deviceid permits lvm to use only devices on the system that have a device_id matching a devices_file entry. Using the device_id, lvm can determine the set of devices to use without reading any devices, so devices_file will constrain lvm in two ways: 1. it limits the devices that lvm will read. 2. it limits the devices that lvm will use. In some uncommon cases, e.g. when devices have no unique ID and device_id has to fall back to using the devname, lvm may need to read all devices on the system to determine which ones correspond to the devices_file entries. In this case, the devices_file does not limit the devices that lvm reads, but it does limit the devices that lvm uses. pvcreate/vgcreate/vgextend are not constrained by the devices_file, and will look outside it to find the new PV. They assign the new PV a device_id and add it to the devices_file. It would be possible to explicitly add new PVs to devices_file before using them in pvcreate/etc, in which case these commands would not need to access devices outside devices_file. (A config setting may be added to control the ability of these commands to search outside devices_list.) vgimportdevices VG looks at all devices on the system to find an existing VG and add its devices to the devices_file. The command is not limited by an existing devices_file. The command will also add device_ids to the VG metadata if the VG does not yet include device_ids. vgimportdevices -a imports devices for all accessible VGs. Since vgimportdevices does not limit itself to devices in an existing devices_file, the lvm.conf filter applies. Adding --foreign will import devices for foreign VGs, but device_ids are not added to foreign VGs. TODO: pvchange --deviceidupdate PV vgchange --deviceidupdate VG pvs -o deviceidtype,deviceid duplicate PV resolution using device_id vgimportclone needs to update device_file pvchange --uuid needs to update device_file config setting device_id_types to control idtypes used lvmdevices command to manage devices_file search for pvid when no wwid is available and PV is missing md device_id_type config setting pvcreate_extends_devices_file=0|1 shortsystemid crc of systemid and written in pv header use shortsystemid for new filter and orphan PV ownership TODO: new lvmdevices command: --adddev <devname> Adds devices_file entry, reads device header. --deldev <devname> Removes devices_file entry. --addpvid <pvid> Reads pv header of all devices to find <pvid>, if found adds devices_file entry. --delpvid <pvid> Removes devices_file entry. --adddeviceid <device_id> --deviceidtype <idtype> Reads <idtype> of all devices (e.g. from sysfs) to find a match, if found, reads pv header and adds devices_file entry. --deldeviceid <device_id> Removes devices_file entry.
-rw-r--r--lib/Makefile.in2
-rw-r--r--lib/commands/toolcontext.c25
-rw-r--r--lib/commands/toolcontext.h3
-rw-r--r--lib/config/config_settings.h3
-rw-r--r--lib/config/defaults.h2
-rw-r--r--lib/device/dev-cache.c52
-rw-r--r--lib/device/dev-cache.h5
-rw-r--r--lib/device/device.h29
-rw-r--r--lib/device/device_id.c1081
-rw-r--r--lib/device/device_id.h43
-rw-r--r--lib/filters/filter-deviceid.c56
-rw-r--r--lib/filters/filter-regex.c6
-rw-r--r--lib/filters/filter.h1
-rw-r--r--lib/format_text/export.c6
-rw-r--r--lib/format_text/import_vsn1.c34
-rw-r--r--lib/label/label.c37
-rw-r--r--lib/metadata/pv.h2
-rw-r--r--tools/Makefile.in1
-rw-r--r--tools/args.h9
-rw-r--r--tools/command-lines.in17
-rw-r--r--tools/commands.h4
-rw-r--r--tools/lvmcmdline.c6
-rw-r--r--tools/pvscan.c9
-rw-r--r--tools/toollib.c25
-rw-r--r--tools/vgimportdevices.c217
25 files changed, 1641 insertions, 34 deletions
diff --git a/lib/Makefile.in b/lib/Makefile.in
index 8e50ec45c..3409cbd8c 100644
--- a/lib/Makefile.in
+++ b/lib/Makefile.in
@@ -29,6 +29,7 @@ SOURCES =\
device/bcache.c \
device/bcache-utils.c \
device/dev-cache.c \
+ device/device_id.c \
device/dev-ext.c \
device/dev-io.c \
device/dev-md.c \
@@ -52,6 +53,7 @@ SOURCES =\
filters/filter-usable.c \
filters/filter-internal.c \
filters/filter-signature.c \
+ filters/filter-deviceid.c \
format_text/archive.c \
format_text/archiver.c \
format_text/export.c \
diff --git a/lib/commands/toolcontext.c b/lib/commands/toolcontext.c
index 63b6811e5..e4e7b6757 100644
--- a/lib/commands/toolcontext.c
+++ b/lib/commands/toolcontext.c
@@ -1066,7 +1066,14 @@ static int _init_dev_cache(struct cmd_context *cmd)
return 1;
}
-#define MAX_FILTERS 10
+static int _init_device_ids(struct cmd_context *cmd)
+{
+ dm_list_init(&cmd->use_device_ids);
+ cmd->devices_file = find_config_tree_str(cmd, devices_devicesfile_CFG, NULL);
+ return 1;
+}
+
+#define MAX_FILTERS 11
static struct dev_filter *_init_filter_chain(struct cmd_context *cmd)
{
@@ -1085,6 +1092,9 @@ static struct dev_filter *_init_filter_chain(struct cmd_context *cmd)
* sysfs filter. Only available on 2.6 kernels. Non-critical.
* Listed first because it's very efficient at eliminating
* unavailable devices.
+ *
+ * TODO: I suspect that using the lvm_type and device_id
+ * filters before this one may be more efficient.
*/
if (find_config_tree_bool(cmd, devices_sysfs_scan_CFG, NULL)) {
if ((filters[nr_filt] = sysfs_filter_create()))
@@ -1123,6 +1133,13 @@ static struct dev_filter *_init_filter_chain(struct cmd_context *cmd)
}
nr_filt++;
+ /* filter based on the device_ids saved in the lvm_devices file */
+ if (!(filters[nr_filt] = deviceid_filter_create(cmd))) {
+ log_error("Failed to create deviceid device filter");
+ goto bad;
+ }
+ nr_filt++;
+
/* usable device filter. Required. */
if (!(filters[nr_filt] = usable_filter_create(cmd, cmd->dev_types, FILTER_MODE_NO_LVMETAD))) {
log_error("Failed to create usabled device filter");
@@ -1717,6 +1734,9 @@ struct cmd_context *create_toolcontext(unsigned is_clvmd,
if (!_init_dev_cache(cmd))
goto_out;
+ if (!_init_device_ids(cmd))
+ return_0;
+
memlock_init(cmd);
if (!_init_formats(cmd))
@@ -1921,6 +1941,9 @@ int refresh_toolcontext(struct cmd_context *cmd)
if (!_init_dev_cache(cmd))
return_0;
+ if (!_init_device_ids(cmd))
+ return_0;
+
if (!_init_formats(cmd))
return_0;
diff --git a/lib/commands/toolcontext.h b/lib/commands/toolcontext.h
index c09558a42..f8fc33b31 100644
--- a/lib/commands/toolcontext.h
+++ b/lib/commands/toolcontext.h
@@ -182,13 +182,16 @@ struct cmd_context {
unsigned pvscan_recreate_hints:1; /* enable special case hint handling for pvscan --cache */
unsigned scan_lvs:1;
unsigned wipe_outdated_pvs:1;
+ unsigned enable_device_ids:1;
/*
* Devices and filtering.
*/
struct dev_filter *filter;
struct dm_list hints;
+ struct dm_list use_device_ids;
const char *md_component_checks;
+ const char *devices_file;
/*
* Configuration.
diff --git a/lib/config/config_settings.h b/lib/config/config_settings.h
index 2bb72ba71..316abc548 100644
--- a/lib/config/config_settings.h
+++ b/lib/config/config_settings.h
@@ -288,6 +288,9 @@ cfg_array(devices_preferred_names_CFG, "preferred_names", devices_CFG_SECTION, C
"preferred_names = [ \"^/dev/mpath/\", \"^/dev/mapper/mpath\", \"^/dev/[hs]d\" ]\n"
"#\n")
+cfg(devices_devicesfile_CFG, "devicesfile", devices_CFG_SECTION, CFG_DEFAULT_COMMENTED, CFG_TYPE_STRING, DEFAULT_DEVICES_FILE, vsn(2, 3, 10), NULL, 0, NULL,
+ "The file listing devices LVM should use.\n")
+
cfg_array(devices_filter_CFG, "filter", devices_CFG_SECTION, CFG_DEFAULT_COMMENTED, CFG_TYPE_STRING, "#Sa|.*|", vsn(1, 0, 0), NULL, 0, NULL,
"Limit the block devices that are used by LVM commands.\n"
"This is a list of regular expressions used to accept or reject block\n"
diff --git a/lib/config/defaults.h b/lib/config/defaults.h
index be4f5ff7f..894fd2d9e 100644
--- a/lib/config/defaults.h
+++ b/lib/config/defaults.h
@@ -320,4 +320,6 @@
#define DEFAULT_MD_COMPONENT_CHECKS "auto"
+#define DEFAULT_DEVICES_FILE "/etc/lvm/lvm_devices.dat"
+
#endif /* _LVM_DEFAULTS_H */
diff --git a/lib/device/dev-cache.c b/lib/device/dev-cache.c
index c3f7c49be..6b99bfc96 100644
--- a/lib/device/dev-cache.c
+++ b/lib/device/dev-cache.c
@@ -16,6 +16,7 @@
#include "base/memory/zalloc.h"
#include "lib/misc/lib.h"
#include "lib/device/dev-type.h"
+#include "lib/device/device_id.h"
#include "lib/datastruct/btree.h"
#include "lib/config/config.h"
#include "lib/commands/toolcontext.h"
@@ -72,6 +73,7 @@ static void _dev_init(struct device *dev)
dev->ext.src = DEV_EXT_NONE;
dm_list_init(&dev->aliases);
+ dm_list_init(&dev->ids);
}
void dev_destroy_file(struct device *dev)
@@ -351,7 +353,7 @@ static int _add_alias(struct device *dev, const char *path)
return 1;
}
-static int _get_sysfs_value(const char *path, char *buf, size_t buf_size, int error_if_no_value)
+int get_sysfs_value(const char *path, char *buf, size_t buf_size, int error_if_no_value)
{
FILE *fp;
size_t len;
@@ -392,7 +394,7 @@ static int _get_dm_uuid_from_sysfs(char *buf, size_t buf_size, int major, int mi
return 0;
}
- return _get_sysfs_value(path, buf, buf_size, 0);
+ return get_sysfs_value(path, buf, buf_size, 0);
}
static struct dm_list *_get_or_add_list_by_index_key(struct dm_hash_table *idx, const char *key)
@@ -473,7 +475,7 @@ static struct device *_get_device_for_sysfs_dev_name_using_devno(const char *dev
return NULL;
}
- if (!_get_sysfs_value(path, buf, sizeof(buf), 1))
+ if (!get_sysfs_value(path, buf, sizeof(buf), 1))
return_NULL;
if (sscanf(buf, "%d:%d", &major, &minor) != 2) {
@@ -971,7 +973,7 @@ static int _dev_cache_iterate_sysfs_for_index(const char *path)
return r;
}
-int dev_cache_index_devs(void)
+static int dev_cache_index_devs(void)
{
static int sysfs_has_dev_block = -1;
char path[PATH_MAX];
@@ -1320,12 +1322,19 @@ int dev_cache_check_for_open_devices(void)
int dev_cache_exit(void)
{
+ struct device *dev;
+ struct dm_hash_node *n;
int num_open = 0;
if (_cache.names)
if ((num_open = _check_for_open_devices(1)) > 0)
log_error(INTERNAL_ERROR "%d device(s) were left open and have been closed.", num_open);
+ dm_hash_iterate(n, _cache.names) {
+ dev = (struct device *) dm_hash_get_data(_cache.names, n);
+ free_dids(&dev->ids);
+ }
+
if (_cache.mem)
dm_pool_destroy(_cache.mem);
@@ -1657,3 +1666,38 @@ bool dev_cache_has_md_with_end_superblock(struct dev_types *dt)
return false;
}
+void setup_devices(struct cmd_context *cmd)
+{
+ /*
+ * Read the list of device ids that lvm can use.
+ * Adds a struct dev_id to cmd->use_device_ids for each one.
+ *
+ * (Changing enable_device_ids=0 must ensure that nothing
+ * in the command thus far has been done on the basis of
+ * enable_device_ids=1.)
+ */
+ if (!device_ids_read(cmd)) {
+ log_warn("WARNING: disabling use of devices_file, failed to read file.");
+ cmd->enable_device_ids = 0;
+ }
+
+ /*
+ * Add a 'struct device' to dev-cache for each device available on the system.
+ * This will not open or read any devices, but may look at sysfs properties.
+ * This list of devs comes from looking /dev entries, or from asking libudev.
+ * TODO: or from /proc/partitions?
+ *
+ * TODO: dev_cache_scan() optimization: start by looking only at
+ * devnames listed in the devices_file, and if the device_ids for
+ * those all match we won't need any others.
+ * Exceptions: the command wants a new device for pvcreate, or
+ * device_ids don't match the devnames.
+ */
+ dev_cache_scan();
+
+ /*
+ * Match entries from cmd->use_device_ids with device structs in dev-cache.
+ */
+ device_ids_match(cmd);
+}
+
diff --git a/lib/device/dev-cache.h b/lib/device/dev-cache.h
index 46c86c27a..2cecd6abb 100644
--- a/lib/device/dev-cache.h
+++ b/lib/device/dev-cache.h
@@ -34,7 +34,6 @@ struct dev_filter {
const char *name;
};
-int dev_cache_index_devs(void);
struct dm_list *dev_cache_get_dev_list_for_vgid(const char *vgid);
struct dm_list *dev_cache_get_dev_list_for_lvid(const char *lvid);
@@ -74,4 +73,8 @@ 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);
+
+void setup_devices(struct cmd_context *cmd);
+
#endif
diff --git a/lib/device/device.h b/lib/device/device.h
index bd3b35557..eeb591e6c 100644
--- a/lib/device/device.h
+++ b/lib/device/device.h
@@ -38,6 +38,7 @@
#define DEV_SCAN_FOUND_LABEL 0x00010000 /* label scan read dev and found label */
#define DEV_IS_MD_COMPONENT 0x00020000 /* device is an md component */
#define DEV_UDEV_INFO_MISSING 0x00040000 /* we have no udev info for this device */
+#define DEV_MATCHED_USE_ID 0x00080000 /* matched an entry from cmd->use_device_ids */
/*
* Support for external device info.
@@ -56,12 +57,40 @@ struct dev_ext {
void *handle;
};
+#define DEV_ID_TYPE_SYS_WWID 0x0001
+#define DEV_ID_TYPE_SYS_SERIAL 0x0002
+#define DEV_ID_TYPE_MPATH_UUID 0x0003
+#define DEV_ID_TYPE_DEVNAME 0x0004
+#define DEV_ID_TYPE_LOOP_FILE 0x0005
+
+/* A device ID of a certain type for a device. */
+
+struct dev_id {
+ struct dm_list list;
+ struct device *dev;
+ uint16_t idtype;
+ char *idname;
+};
+
+/* A device listed in devices_file that lvm should use. */
+
+struct use_id {
+ struct dm_list list;
+ struct device *dev;
+ uint16_t idtype;
+ char *idname;
+ char *devname;
+ char *pvid;
+};
+
/*
* All devices in LVM will be represented by one of these.
* pointer comparisons are valid.
*/
struct device {
struct dm_list aliases; /* struct dm_str_list */
+ struct dm_list ids; /* struct dev_id */
+ struct dev_id *id; /* points to the ids entry being used for this dev */
dev_t dev;
/* private */
diff --git a/lib/device/device_id.c b/lib/device/device_id.c
new file mode 100644
index 000000000..1e0c46f5b
--- /dev/null
+++ b/lib/device/device_id.c
@@ -0,0 +1,1081 @@
+/*
+ * 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 <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;
+}
+
+static 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;
+}
+
+static 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) {
+ /* update the existing entry with matching devid */
+ uid = uid_devid;
+ dm_list_del(&uid->list);
+
+ log_print("Updating existing device entry for device_id");
+
+ /* FIXME: what about two devs having the same devid?
+ we want to separate entries, one with each pvid.
+ can we check if this is the case by reading system
+ device_id for both devnames?
+ if uid_devid->pvid exists, read the pvid from that
+ dev to check if it's still there */
+ /* FIXME: if overwriting existing pvid?
+ we want to replace pvid in existing entry */
+
+ 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.
+ */
+
+int device_id_match(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;
+}
+
+/*
+
+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;
+
+ 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.
+ */
+ if (uid->devname &&
+ (dev = dev_cache_get(cmd, uid->devname, NULL))) {
+ /* On success, device_id_match() links the uid, dev, and did. */
+ if (device_id_match(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.
+ *
+ * A filter is not used when iterating since this is not selecting
+ * devs to use. The next dev iteration in label scan does use filters.
+ * The device_id matches created here are used by filter-deviceid later.
+ * (Might we apply a couple simple filters here, though, to avoid
+ * doing some pointless match attempts?)
+ *
+ * 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.
+ */
+ if (!(iter = dev_iter_create(NULL, 0)))
+ continue;
+ while ((dev = dev_iter_get(cmd, iter))) {
+ if (dev->flags & DEV_MATCHED_USE_ID)
+ continue;
+ if (device_id_match(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)) {
+ /* is this possible? */
+ }
+
+ 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 (%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 (%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;
+}
+
diff --git a/lib/device/device_id.h b/lib/device/device_id.h
new file mode 100644
index 000000000..4346418a1
--- /dev/null
+++ b/lib/device/device_id.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2001-2004 Sistina Software, Inc. All rights reserved.
+ * Copyright (C) 2004-2007 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
+ */
+
+#ifndef _LVM_DEVICE_ID_H
+#define _LVM_DEVICE_ID_H
+
+void free_uid(struct use_id *uid);
+void free_uids(struct dm_list *list);
+void free_did(struct dev_id *did);
+void free_dids(struct dm_list *list);
+const char *idtype_to_str(uint16_t idtype);
+uint16_t idtype_from_str(const char *str);
+const char *dev_idtype(struct device *dev);
+const char *dev_id(struct device *dev);
+int device_ids_read(struct cmd_context *cmd);
+int device_ids_write(struct cmd_context *cmd);
+int device_id_add(struct cmd_context *cmd, struct device *dev, const char *pvid,
+ const char *idtype_arg, const char *id_arg);
+int device_id_add_nodev(struct cmd_context *cmd,
+ const char *idtype_str,
+ const char *idname,
+ const char *devname,
+ const char *pvid);
+void device_id_pvremove(struct cmd_context *cmd, struct device *dev);
+int device_id_match(struct cmd_context *cmd, struct use_id *uid, struct device *dev);
+void device_ids_match(struct cmd_context *cmd);
+void device_ids_validate(struct cmd_context *cmd);
+
+int devices_file_valid(struct cmd_context *cmd);
+
+#endif
diff --git a/lib/filters/filter-deviceid.c b/lib/filters/filter-deviceid.c
new file mode 100644
index 000000000..15b40ea66
--- /dev/null
+++ b/lib/filters/filter-deviceid.c
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2001-2004 Sistina Software, Inc. All rights reserved.
+ * Copyright (C) 2004-2012 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/filters/filter.h"
+#include "lib/commands/toolcontext.h"
+
+static int _passes_deviceid_filter(struct cmd_context *cmd, struct dev_filter *f, struct device *dev, const char *use_filter_name)
+{
+ if (!cmd->enable_device_ids)
+ return 1;
+
+ if (dev->flags & DEV_MATCHED_USE_ID)
+ return 1;
+ return 0;
+}
+
+static void _destroy_deviceid_filter(struct dev_filter *f)
+{
+ if (f->use_count)
+ log_error(INTERNAL_ERROR "Destroying deviceid filter while in use %u times.", f->use_count);
+
+ free(f);
+}
+
+struct dev_filter *deviceid_filter_create(struct cmd_context *cmd)
+{
+ struct dev_filter *f;
+
+ if (!(f = zalloc(sizeof(struct dev_filter)))) {
+ log_error("deviceid filter allocation failed");
+ return NULL;
+ }
+
+ f->passes_filter = _passes_deviceid_filter;
+ f->destroy = _destroy_deviceid_filter;
+ f->use_count = 0;
+ f->name = "deviceid";
+
+ log_debug_devs("deviceid filter initialised.");
+
+ return f;
+}
diff --git a/lib/filters/filter-regex.c b/lib/filters/filter-regex.c
index e439b36b5..33edc4138 100644
--- a/lib/filters/filter-regex.c
+++ b/lib/filters/filter-regex.c
@@ -15,6 +15,7 @@
#include "lib/misc/lib.h"
#include "lib/filters/filter.h"
+#include "lib/commands/toolcontext.h"
struct rfilter {
struct dm_pool *mem;
@@ -151,6 +152,11 @@ static int _accept_p(struct cmd_context *cmd, struct dev_filter *f, struct devic
struct rfilter *rf = (struct rfilter *) f->private;
struct dm_str_list *sl;
+ if (cmd->enable_device_ids) {
+ /* TODO: print a notice if the filter is set to something and we ignore it here. */
+ return 1;
+ }
+
dm_list_iterate_items(sl, &dev->aliases) {
m = dm_regex_match(rf->engine, sl->str);
diff --git a/lib/filters/filter.h b/lib/filters/filter.h
index 7333cfe3c..af329618f 100644
--- a/lib/filters/filter.h
+++ b/lib/filters/filter.h
@@ -30,6 +30,7 @@ struct dev_filter *partitioned_filter_create(struct dev_types *dt);
struct dev_filter *persistent_filter_create(struct dev_types *dt, struct dev_filter *f);
struct dev_filter *sysfs_filter_create(void);
struct dev_filter *signature_filter_create(struct dev_types *dt);
+struct dev_filter *deviceid_filter_create(struct cmd_context *cmd);
struct dev_filter *internal_filter_create(void);
int internal_filter_allow(struct dm_pool *mem, struct device *dev);
diff --git a/lib/format_text/export.c b/lib/format_text/export.c
index b5e987e01..1f620c603 100644
--- a/lib/format_text/export.c
+++ b/lib/format_text/export.c
@@ -23,6 +23,7 @@
#include "lib/metadata/segtype.h"
#include "lib/format_text/text_export.h"
#include "lib/commands/toolcontext.h"
+#include "lib/device/device_id.h"
#include "libdaemon/client/config-util.h"
#include <stdarg.h>
@@ -555,6 +556,11 @@ static int _print_pvs(struct formatter *f, struct volume_group *vg)
dm_escape_double_quotes(buffer, pv_dev_name(pv)));
outnl(f);
+ if (dev_idtype(pv->dev) && dev_id(pv->dev)) {
+ outf(f, "device_id_type = \"%s\"", dev_idtype(pv->dev));
+ outf(f, "device_id = \"%s\"", dev_id(pv->dev));
+ }
+
if (!_print_flag_config(f, pv->status, PV_FLAGS))
return_0;
diff --git a/lib/format_text/import_vsn1.c b/lib/format_text/import_vsn1.c
index 2bdbfeea9..ee56502e1 100644
--- a/lib/format_text/import_vsn1.c
+++ b/lib/format_text/import_vsn1.c
@@ -188,7 +188,7 @@ static int _read_pv(struct cmd_context *cmd,
struct physical_volume *pv;
struct pv_list *pvl;
const struct dm_config_value *cv;
- const char *device_hint;
+ const char *str;
uint64_t size, ba_start;
if (!(pvl = dm_pool_zalloc(mem, sizeof(*pvl))) ||
@@ -233,11 +233,21 @@ static int _read_pv(struct cmd_context *cmd,
return 0;
}
- if (dm_config_get_str(pvn, "device", &device_hint)) {
- if (!(pv->device_hint = dm_pool_strdup(mem, device_hint)))
+ if (dm_config_get_str(pvn, "device", &str)) {
+ if (!(pv->device_hint = dm_pool_strdup(mem, str)))
log_error("Failed to allocate memory for device hint in read_pv.");
}
+ if (dm_config_get_str(pvn, "device_id", &str)) {
+ if (!(pv->device_id = dm_pool_strdup(mem, str)))
+ log_error("Failed to allocate memory for device_id in read_pv.");
+ }
+
+ if (dm_config_get_str(pvn, "device_id_type", &str)) {
+ if (!(pv->device_id_type = dm_pool_strdup(mem, str)))
+ log_error("Failed to allocate memory for device_id_type in read_pv.");
+ }
+
if (!_read_uint64(pvn, "pe_start", &pv->pe_start)) {
log_error("Couldn't read extent start value (pe_start) "
"for physical volume.");
@@ -306,7 +316,7 @@ static int _read_pvsummary(struct cmd_context *cmd,
{
struct physical_volume *pv;
struct pv_list *pvl;
- const char *device_hint;
+ const char *str;
if (!(pvl = dm_pool_zalloc(mem, sizeof(*pvl))) ||
!(pvl->pv = dm_pool_zalloc(mem, sizeof(*pvl->pv))))
@@ -326,9 +336,19 @@ static int _read_pvsummary(struct cmd_context *cmd,
!_read_uint64(pvn, "dev_size", &pv->size))
log_warn("Couldn't read dev size for physical volume.");
- if (dm_config_get_str(pvn, "device", &device_hint)) {
- if (!(pv->device_hint = dm_pool_strdup(mem, device_hint)))
- log_error("Failed to allocate memory for device hint in read_pv.");
+ if (dm_config_get_str(pvn, "device", &str)) {
+ if (!(pv->device_hint = dm_pool_strdup(mem, str)))
+ log_error("Failed to allocate memory for device hint in read_pv_sum.");
+ }
+
+ if (dm_config_get_str(pvn, "device_id", &str)) {
+ if (!(pv->device_id = dm_pool_strdup(mem, str)))
+ log_error("Failed to allocate memory for device_id in read_pv_sum.");
+ }
+
+ if (dm_config_get_str(pvn, "device_id_type", &str)) {
+ if (!(pv->device_id_type = dm_pool_strdup(mem, str)))
+ log_error("Failed to allocate memory for device_id_type in read_pv_sum.");
}
dm_list_add(&vgsummary->pvsummaries, &pvl->list);
diff --git a/lib/label/label.c b/lib/label/label.c
index ed6e0171c..1b15b7a8e 100644
--- a/lib/label/label.c
+++ b/lib/label/label.c
@@ -25,6 +25,7 @@
#include "lib/label/hints.h"
#include "lib/metadata/metadata.h"
#include "lib/format_text/layout.h"
+#include "lib/device/device_id.h"
#include <sys/stat.h>
#include <fcntl.h>
@@ -780,16 +781,6 @@ static int _scan_list(struct cmd_context *cmd, struct dev_filter *f,
}
}
- /*
- * This will search the system's /dev for new path names and
- * could help us reopen the device if it finds a new preferred
- * path name for this dev's major:minor. It does that by
- * inserting a new preferred path name on dev->aliases. open
- * uses the first name from that list.
- */
- log_debug_devs("Scanning refreshing device paths.");
- dev_cache_scan();
-
/* Put devs that failed to open back on the original list to retry. */
dm_list_splice(devs, &reopen_devs);
goto scan_more;
@@ -923,7 +914,17 @@ int label_scan_for_pvid(struct cmd_context *cmd, char *pvid, struct device **dev
dm_list_init(&devs);
- dev_cache_scan();
+ /*
+ * Creates a list of available devices, does not open or read any,
+ * and does not filter them.
+ */
+ setup_devices(cmd);
+
+ /*
+ * Iterating over all available devices with cmd->filter filters
+ * devices; those returned from dev_iter_get are the devs that
+ * pass filters, and are those we can use.
+ */
if (!(iter = dev_iter_create(cmd->filter, 0))) {
log_error("Scanning failed to get devices.");
@@ -1006,12 +1007,14 @@ int label_scan(struct cmd_context *cmd)
dm_list_init(&hints_list);
/*
- * dev_cache_scan() creates a list of devices on the system
- * (saved in in dev-cache) which we can iterate through to
- * search for LVM devs. The dev cache list either comes from
- * looking at dev nodes under /dev, or from udev.
+ * Creates a list of available devices, does not open or read any,
+ * and does not filter them. The list of all available devices
+ * is kept in "dev-cache", and comes from /dev entries or libudev.
+ * The list of devs found here needs to be filtered to get the
+ * list of devs we can use. The dev_iter calls using cmd->filter
+ * are what filters the devs.
*/
- dev_cache_scan();
+ setup_devices(cmd);
/*
* If we know that there will be md components with an end
@@ -1213,6 +1216,8 @@ int label_scan(struct cmd_context *cmd)
if (create_hints)
write_hint_file(cmd, create_hints);
+ device_ids_validate(cmd);
+
return 1;
}
diff --git a/lib/metadata/pv.h b/lib/metadata/pv.h
index efa13e04b..ec1b21d54 100644
--- a/lib/metadata/pv.h
+++ b/lib/metadata/pv.h
@@ -27,6 +27,8 @@ struct physical_volume {
struct id old_id; /* Set during pvchange -u. */
struct device *dev;
const char *device_hint; /* primary name last time metadata was written */
+ const char *device_id;
+ const char *device_id_type;
const struct format_type *fmt;
struct format_instance *fid;
diff --git a/tools/Makefile.in b/tools/Makefile.in
index 2620daa17..d3c084075 100644
--- a/tools/Makefile.in
+++ b/tools/Makefile.in
@@ -58,6 +58,7 @@ SOURCES =\
vgextend.c \
vgimport.c \
vgimportclone.c \
+ vgimportdevices.c \
vgmerge.c \
vgmknodes.c \
vgreduce.c \
diff --git a/tools/args.h b/tools/args.h
index 5fae21abb..66b916667 100644
--- a/tools/args.h
+++ b/tools/args.h
@@ -199,6 +199,15 @@ arg(detachprofile_ARG, '\0', "detachprofile", 0, 0, 0,
"Detaches a metadata profile from a VG or LV.\n"
"See \\fBlvm.conf\\fP(5) for more information about profiles.\n")
+arg(deviceid_ARG, '\0', "deviceid", string_VAL, 0, 0,
+ "A device ID with a format determined by --deviceidtype.")
+
+arg(deviceidtype_ARG, '\0', "deviceidtype", string_VAL, 0, 0,
+ "A device ID type: sys_wwid, sys_serial, mpath_uuid.")
+
+arg(devicesfile_ARG, '\0', "devicesfile", string_VAL, 0, 0,
+ "The file listing device IDs that LVM should use.")
+
arg(discards_ARG, '\0', "discards", discards_VAL, 0, 0,
"Specifies how the device-mapper thin pool layer in the kernel should\n"
"handle discards.\n"
diff --git a/tools/command-lines.in b/tools/command-lines.in
index ed3d0413a..d3f7967b1 100644
--- a/tools/command-lines.in
+++ b/tools/command-lines.in
@@ -188,7 +188,7 @@
#
OO_ALL: --commandprofile String, --config String, --debug,
--driverloaded Bool, --help, --nolocking, --lockopt String, --longhelp, --profile String, --quiet,
---verbose, --version, --yes, --test
+--verbose, --version, --yes, --test, --devicesfile String
#
# options for pvs, lvs, vgs, fullreport
@@ -1479,7 +1479,8 @@ OO: --dataalignment SizeKB, --dataalignmentoffset SizeKB, --bootloaderareasize S
--force, --labelsector Number, --metadatatype MetadataType,
--pvmetadatacopies MetadataCopiesPV, --metadatasize SizeMB,
--metadataignore Bool, --norestorefile, --setphysicalvolumesize SizeMB,
---reportformat ReportFmt, --restorefile String, --uuidstr String, --zero Bool
+--reportformat ReportFmt, --restorefile String, --uuidstr String, --zero Bool,
+--deviceidtype String, --deviceid String
ID: pvcreate_general
RULE: --norestorefile not --restorefile
RULE: --bootloaderareasize not --restorefile
@@ -1734,6 +1735,18 @@ ID: vgimportclone_general
---
+vgimportdevices VG|Tag|Select ...
+OO: --deviceidtype String, --select String, --foreign, --reportformat ReportFmt
+ID: vgimportdevices_some
+DESC: Add devices from specific VGs to the devices file.
+
+vgimportdevices --all
+OO: --deviceidtype String, --foreign, --reportformat ReportFmt
+ID: vgimportdevices_all
+DESC: Add devices from all accessible VGs to the devices file.
+
+---
+
vgmerge VG VG
OO: --autobackup Bool, --list
ID: vgmerge_general
diff --git a/tools/commands.h b/tools/commands.h
index c1670ae66..33566d039 100644
--- a/tools/commands.h
+++ b/tools/commands.h
@@ -207,6 +207,10 @@ xx(vgimportclone,
"Import a VG from cloned PVs",
ALLOW_EXPORTED)
+xx(vgimportdevices,
+ "Add devices for a VG to the devices file.",
+ ALL_VGS_IS_DEFAULT | ALLOW_EXPORTED)
+
xx(vgmerge,
"Merge volume groups",
0)
diff --git a/tools/lvmcmdline.c b/tools/lvmcmdline.c
index d87a8f053..eca8b47d9 100644
--- a/tools/lvmcmdline.c
+++ b/tools/lvmcmdline.c
@@ -17,6 +17,7 @@
#include "lvm2cmdline.h"
#include "lib/label/label.h"
+#include "lib/device/device_id.h"
#include "lvm-version.h"
#include "lib/locking/lvmlockd.h"
@@ -2477,6 +2478,11 @@ static int _get_current_settings(struct cmd_context *cmd)
cmd->record_historical_lvs = find_config_tree_bool(cmd, metadata_record_lvs_history_CFG, NULL) ?
(arg_is_set(cmd, nohistory_ARG) ? 0 : 1) : 0;
+ if (arg_is_set(cmd, devicesfile_ARG))
+ cmd->devices_file = arg_str_value(cmd, devicesfile_ARG, "");
+ if (devices_file_valid(cmd))
+ cmd->enable_device_ids = 1;
+
/*
* This is set to zero by process_each which wants to print errors
* itself rather than having them printed in vg_read.
diff --git a/tools/pvscan.c b/tools/pvscan.c
index 4d811da55..c88b09b42 100644
--- a/tools/pvscan.c
+++ b/tools/pvscan.c
@@ -820,6 +820,11 @@ static void _online_pvscan_all_devs(struct cmd_context *cmd,
struct device *dev;
const char *pvid_without_metadata;
+ /*
+ * TODO: label_scan() calls setup_devices(), but pvscan --cache is a
+ * special case in which setup_devices() has already been called.
+ * So, we could improve things by suppressing the second setup_devices().
+ */
lvmcache_label_scan(cmd);
if (!(iter = dev_iter_create(cmd->filter, 1))) {
@@ -1254,8 +1259,8 @@ int pvscan_cache_cmd(struct cmd_context *cmd, int argc, char **argv)
_online_dir_setup();
- /* Creates a list of dev names from /dev, sysfs, etc; does not read any. */
- dev_cache_scan();
+ /* Creates a list of available devices, does not open or read any. */
+ setup_devices(cmd);
if (cmd->md_component_detection && !cmd->use_full_md_check &&
!strcmp(cmd->md_component_checks, "auto") &&
diff --git a/tools/toollib.c b/tools/toollib.c
index 9640f003e..b22d75a69 100644
--- a/tools/toollib.c
+++ b/tools/toollib.c
@@ -16,6 +16,7 @@
#include "tools.h"
#include "lib/format_text/format-text.h"
#include "lib/label/hints.h"
+#include "lib/device/device_id.h"
#include <sys/stat.h>
#include <signal.h>
@@ -5100,9 +5101,16 @@ int pvcreate_each_device(struct cmd_context *cmd,
/*
* Translate arg names into struct device's.
+ *
+ * TODO: do we want to add a config setting that would disable this
+ * ability of pvcreate to use devs outside of the devices_file?
+ * If devices_file is to be more strict in allowing access to devs,
+ * e.g. applied to pvcreate, then a user would need to add the new
+ * device to devices_file prior to running pvcreate on it.
*/
dm_list_iterate_items_safe(pd, pd2, &pp->arg_devices) {
- pd->dev = dev_cache_get(cmd, pd->name, cmd->filter);
+ pd->dev = dev_cache_get(cmd, pd->name,
+ cmd->enable_device_ids ? NULL : cmd->filter);
if (!pd->dev) {
log_print("No device found for %s", pd->name);
dm_list_del(&pd->list);
@@ -5411,6 +5419,10 @@ do_command:
log_debug("Using existing orphan PV %s.", pv_dev_name(vgpvl->pv));
pvl->pv = vgpvl->pv;
dm_list_add(&pp->pvs, &pvl->list);
+
+ device_id_add(cmd, pd->dev, (const char *)&pvl->pv->id.uuid,
+ arg_str_value(cmd, deviceidtype_ARG, NULL),
+ arg_str_value(cmd, deviceid_ARG, NULL));
} else {
log_error("Failed to find PV %s", pd->name);
dm_list_move(&pp->arg_fail, &pd->list);
@@ -5449,6 +5461,10 @@ do_command:
continue;
}
+ device_id_add(cmd, pd->dev, (const char *)&pv->id.uuid,
+ arg_str_value(cmd, deviceidtype_ARG, NULL),
+ arg_str_value(cmd, deviceid_ARG, NULL));
+
log_verbose("Set up physical volume for \"%s\" with %" PRIu64
" available sectors.", pv_name, pv_size(pv));
@@ -5494,6 +5510,8 @@ do_command:
continue;
}
+ device_id_pvremove(cmd, pd->dev);
+
log_print_unless_silent("Labels on physical volume \"%s\" successfully wiped.",
pd->name);
}
@@ -5510,10 +5528,15 @@ do_command:
lvmcache_del_dev_from_duplicates(pd->dev);
+ device_id_pvremove(cmd, pd->dev);
+
log_print_unless_silent("Labels on physical volume \"%s\" successfully wiped.",
pd->name);
}
+ /* TODO: when vgcreate uses only existing PVs this doesn't change and can be skipped */
+ device_ids_write(cmd);
+
/*
* Don't keep devs open excl in bcache because the excl will prevent
* using that dev elsewhere.
diff --git a/tools/vgimportdevices.c b/tools/vgimportdevices.c
new file mode 100644
index 000000000..a20b78ccb
--- /dev/null
+++ b/tools/vgimportdevices.c
@@ -0,0 +1,217 @@
+/*
+ * 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 "tools.h"
+#include "lib/cache/lvmcache.h"
+#include "lib/filters/filter.h"
+#include "lib/device/device_id.h"
+
+struct vgimportdevices_params {
+ uint32_t added_devices;
+};
+
+static int _vgimportdevices_single(struct cmd_context *cmd,
+ const char *vg_name,
+ struct volume_group *vg,
+ struct processing_handle *handle)
+{
+ struct vgimportdevices_params *vp = (struct vgimportdevices_params *) handle->custom_handle;
+ struct pv_list *pvl;
+ struct physical_volume *pv;
+ int update_vg = 1;
+ int updated_pvs = 0;
+ const char *idtypestr;
+
+ dm_list_iterate_items(pvl, &vg->pvs) {
+ if (is_missing_pv(pvl->pv) || !pvl->pv->dev) {
+ log_warn("WARNING: not importing VG %s with missing PV.", vg->name);
+ return 1;
+ }
+ }
+
+ /*
+ * We want to allow importing devices of foreign and shared
+ * VGs, but we do not want to update device_ids in those VGs.
+ *
+ * If --foreign is set, then foreign VGs will be passed
+ * to this function; add devices but don't update vg.
+ * shared VGs are passed to this function; add devices
+ * and do not update.
+ */
+ if (vg_is_foreign(vg) || vg_is_shared(vg))
+ update_vg = 0;
+
+ /*
+ * When no devices_file is available, do not update device_ids
+ * in the VG. This command is not meant to be used to just
+ * update device_ids of a VG; for that use vgchange.
+ */
+ if (!devices_file_valid(cmd))
+ update_vg = 0;
+
+ /*
+ * TODO: let users import devices without updating VG device_ids.
+ * if --nodeviceidupdate; update_vg = 0;
+ */
+
+ /*
+ * User can select the idtype to use when importing.
+ */
+ idtypestr = arg_str_value(cmd, deviceidtype_ARG, NULL);
+
+ dm_list_iterate_items(pvl, &vg->pvs) {
+ pv = pvl->pv;
+
+ if (!pv->dev && pv->device_id_type && pv->device_id) {
+ /* add an entry using the fields we have values for */
+ cmd->enable_device_ids = 1;
+ device_id_add_nodev(cmd, pv->device_id_type, pv->device_id, NULL, (const char *)&pv->id.uuid);
+ cmd->enable_device_ids = 0;
+
+ vp->added_devices++;
+ continue;
+
+ }
+ if (!pv->dev) {
+ log_warn("WARNING: not adding PV with missing device PVID %s",
+ (const char *)&pvl->pv->id.uuid);
+ continue;
+ }
+
+ if (!idtypestr && pv->device_id_type)
+ idtypestr = pv->device_id_type;
+
+ cmd->enable_device_ids = 1;
+ device_id_add(cmd, pv->dev, (const char *)&pvl->pv->id.uuid, idtypestr, NULL);
+ cmd->enable_device_ids = 0;
+
+ vp->added_devices++;
+
+ /* We could skip update if the device_id has not changed. */
+
+ if (!update_vg)
+ continue;
+
+ updated_pvs++;
+ }
+
+ if (updated_pvs) {
+ if (!vg_write(vg) || !vg_commit(vg))
+ goto_bad;
+ backup(vg);
+ }
+
+ return ECMD_PROCESSED;
+bad:
+ return ECMD_FAILED;
+}
+
+/*
+ * This command always scans all devices on the system,
+ * any pre-existing devices_file does not limit the scope.
+ *
+ * This command adds the VG's devices to whichever
+ * devices_file is set in config or command line.
+ * If devices_file doesn't exist, it's created.
+ *
+ * If devices_file is "" then this file will scan all devices
+ * and show the devices that it would otherwise have added to
+ * the devices_file. The VG is not updated with device_ids.
+ *
+ * This command updates the VG metadata to add device_ids
+ * (if the metadata is missing them), unless an option is
+ * set to skip that, e.g. --nodeviceidupdate?
+ *
+ * If the VG found has a foreign system ID then an error
+ * will be printed. To import devices from a foreign VG:
+ * vgimportdevices --foreign -a
+ * vgimportdevices --foreign VG
+ *
+ * If there are duplicate VG names it will do nothing.
+ *
+ * If there are duplicate PVIDs related to VG it will do nothing,
+ * the user would need to add the PVs they want with lvmdevices --add.
+ *
+ * vgimportdevices -a (no vg arg) will import all accesible VGs.
+ */
+
+int vgimportdevices(struct cmd_context *cmd, int argc, char **argv)
+{
+ struct vgimportdevices_params vp = { 0 };
+ struct processing_handle *handle;
+
+ if (arg_is_set(cmd, foreign_ARG))
+ cmd->include_foreign_vgs = 1;
+
+ cmd->include_shared_vgs = 1;
+
+ /* So that we can warn about this. */
+ cmd->handles_missing_pvs = 1;
+
+ /* Print a notice if a regex filter is being applied?
+ Possibly offer an option to ignore a regex filter? */
+
+ if (!lock_global(cmd, "ex"))
+ return ECMD_FAILED;
+
+ clear_hint_file(cmd);
+
+ if (!(handle = init_processing_handle(cmd, NULL))) {
+ log_error("Failed to initialize processing handle.");
+ return ECMD_FAILED;
+ }
+ handle->custom_handle = &vp;
+
+ /*
+ * import is an odd case where we do not want to use an
+ * existing devices_file for processing/filtering, because
+ * we want to search outside the devices_file for new devs
+ * to add to it, but we do want devices_file entries on
+ * use_device_ids so we can update and write out that list.
+ */
+ cmd->enable_device_ids = 1;
+ device_ids_read(cmd);
+ cmd->enable_device_ids = 0;
+
+ /*
+ * For each VG:
+ * device_id_add() each PV in the VG
+ * update device_ids in the VG (potentially)
+ */
+ process_each_vg(cmd, argc, argv, NULL, NULL, READ_FOR_UPDATE,
+ 0, handle, _vgimportdevices_single);
+
+ if (!vp.added_devices) {
+ log_print("No devices to add.");
+ goto out;
+ }
+
+ if (!cmd->devices_file || !strlen(cmd->devices_file)) {
+ log_print("No devices file to update.");
+ goto out;
+ }
+
+ cmd->enable_device_ids = 1;
+ if (!device_ids_write(cmd)) {
+ destroy_processing_handle(cmd, handle);
+ log_error("Failed to update devices file.");
+ return ECMD_FAILED;
+ }
+
+ log_print("Added %u devices to devices file.", vp.added_devices);
+out:
+ destroy_processing_handle(cmd, handle);
+ return ECMD_PROCESSED;
+}
+