summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Teigland <teigland@redhat.com>2020-06-23 13:25:41 -0500
committerDavid Teigland <teigland@redhat.com>2020-08-17 11:50:06 -0500
commit7fd0a8b0d52cae22532644bf203fdbaa163a38d7 (patch)
tree1f1cc47990642e3ea2083632ca9b7e5e79c14354
parentb0787033023fdc213f3fdd3efc488e70f44e1fcd (diff)
downloadlvm2-dev-dct-deviceid-11.tar.gz
device usage based on devices filedev-dct-deviceid-11
The devices file, e.g. /etc/lvm/devices/lvm_devices.dat, is a list of devices that lvm can use. The option --devicesfile can specify 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 the devices file. When the devices file is used, the filter-regex is not used and the filter settings in lvm.conf are ignored. filter-deviceid is used when the devices file is enabled and rejects any device that does not match an entry in the devices file. Set use_devicesfile = 0 in lvm.conf or set --devicesfile "" on the command line to disable the use of a devices file. When disabled, lvm will see and use any device on the system that passes the regex filter. A device_id, e.g. wwid or serial number from sysfs, is a unique ID that identifies a device without reading it. Two devices with identical content should have different device_ids in most common cases. 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 the 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 is also possible to explicitly add new PVs to the devices file before using them in pvcreate/etc, in which case these commands would not need to access devices outside the devices file. 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 regex filter applies. Adding --foreign will import devices for foreign VGs, but device_ids are not added to foreign VGs. Incomplete VGs are not imported. The lvmdevices command manages the devices file. The primary purpose is to edit the devices file, but it will read PV headers to find/check PVIDs. (It does not read, process or modify VG metadata.) lvmdevices . Displays devices file entries. lvmdevices --check . Checks devices file entries. lvmdevices --update . Updates devices file entries. lvmdevices --adddev <devname> . Adds devices_file entry (reads pv header). lvmdevices --deldev <devname> . Removes devices file entry. lvmdevices --addpvid <pvid> . Reads pv header of all devices to find <pvid>, and if found adds devices file entry. lvmdevices --delpvid <pvid> . Removes devices file entry. TODO: pvchange --deviceidupdate PV vgchange --deviceidupdate VG duplicate PV resolution using device_id vgimportclone needs to update device file (change to pvids) pvchange --uuid needs to update device file device_id_type for md devices shortsystemid crc of systemid and written in pv header use shortsystemid for new filter and orphan PV ownership dmeventd and lvmpolld with devices file
-rw-r--r--lib/Makefile.in2
-rw-r--r--lib/cache/lvmcache.c24
-rw-r--r--lib/commands/toolcontext.c19
-rw-r--r--lib/commands/toolcontext.h8
-rw-r--r--lib/config/config_settings.h8
-rw-r--r--lib/config/defaults.h3
-rw-r--r--lib/device/dev-cache.c215
-rw-r--r--lib/device/dev-cache.h9
-rw-r--r--lib/device/device.h29
-rw-r--r--lib/device/device_id.c1868
-rw-r--r--lib/device/device_id.h57
-rw-r--r--lib/filters/filter-composite.c6
-rw-r--r--lib/filters/filter-deviceid.c60
-rw-r--r--lib/filters/filter-persistent.c10
-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/hints.c55
-rw-r--r--lib/label/label.c54
-rw-r--r--lib/label/label.h2
-rw-r--r--lib/metadata/pv.c14
-rw-r--r--lib/metadata/pv.h4
-rw-r--r--lib/report/columns.h2
-rw-r--r--lib/report/properties.c4
-rw-r--r--lib/report/report.c36
-rw-r--r--tools/Makefile.in2
-rw-r--r--tools/args.h24
-rw-r--r--tools/command-lines.in42
-rw-r--r--tools/commands.h8
-rw-r--r--tools/lvmcmdline.c20
-rw-r--r--tools/lvmdevices.c319
-rw-r--r--tools/pvck.c11
-rw-r--r--tools/pvcreate.c2
-rw-r--r--tools/pvscan.c12
-rw-r--r--tools/toollib.c64
-rw-r--r--tools/tools.h1
-rw-r--r--tools/vgcreate.c4
-rw-r--r--tools/vgextend.c4
-rw-r--r--tools/vgimportdevices.c209
40 files changed, 3205 insertions, 53 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/cache/lvmcache.c b/lib/cache/lvmcache.c
index b1d05fb07..04ae6786f 100644
--- a/lib/cache/lvmcache.c
+++ b/lib/cache/lvmcache.c
@@ -18,6 +18,7 @@
#include "lib/cache/lvmcache.h"
#include "lib/commands/toolcontext.h"
#include "lib/device/dev-cache.h"
+#include "lib/device/device_id.h"
#include "lib/locking/locking.h"
#include "lib/metadata/metadata.h"
#include "lib/mm/memlock.h"
@@ -1024,13 +1025,15 @@ int lvmcache_label_scan(struct cmd_context *cmd)
{
struct dm_list del_cache_devs;
struct dm_list add_cache_devs;
+ struct dm_list renamed_devs;
struct lvmcache_info *info;
struct lvmcache_vginfo *vginfo;
struct device_list *devl;
int vginfo_count = 0;
-
int r = 0;
+ dm_list_init(&renamed_devs);
+
log_debug_cache("Finding VG info");
/*
@@ -1043,14 +1046,25 @@ int lvmcache_label_scan(struct cmd_context *cmd)
* Do the actual scanning. This populates lvmcache
* with infos/vginfos based on reading headers from
* each device, and a vg summary from each mda.
- *
- * Note that this will *skip* scanning a device if
- * an info struct already exists in lvmcache for
- * the device.
*/
label_scan(cmd);
/*
+ * When devnames are used as device ids (which is dispreferred),
+ * changing/unstable devnames can lead to entries in the devices file
+ * not being matched to a dev even if the PV is present on the system.
+ * Or, a devices file entry may have been matched to the wrong device
+ * (with the previous name) that does not have the PVID specified in
+ * the entry. This function detects that problem, scans labels on all
+ * devs on the system to find the missing PVIDs, and corrects the
+ * devices file. We then need to run label scan on these correct
+ * devices.
+ */
+ device_ids_find_renamed_devs(cmd, &renamed_devs);
+ if (!dm_list_empty(&renamed_devs))
+ label_scan_devs(cmd, cmd->filter, &renamed_devs);
+
+ /*
* _choose_duplicates() returns:
*
* . del_cache_devs: a list of devs currently in lvmcache that should
diff --git a/lib/commands/toolcontext.c b/lib/commands/toolcontext.c
index 63b6811e5..0f5f8ada1 100644
--- a/lib/commands/toolcontext.c
+++ b/lib/commands/toolcontext.c
@@ -32,6 +32,7 @@
#include "lib/cache/lvmcache.h"
#include "lib/format_text/archiver.h"
#include "lib/lvmpolld/lvmpolld-client.h"
+#include "lib/device/device_id.h"
#include <locale.h>
#include <sys/stat.h>
@@ -1066,7 +1067,7 @@ static int _init_dev_cache(struct cmd_context *cmd)
return 1;
}
-#define MAX_FILTERS 10
+#define MAX_FILTERS 11
static struct dev_filter *_init_filter_chain(struct cmd_context *cmd)
{
@@ -1085,6 +1086,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 +1127,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 +1728,8 @@ struct cmd_context *create_toolcontext(unsigned is_clvmd,
if (!_init_dev_cache(cmd))
goto_out;
+ device_ids_init(cmd);
+
memlock_init(cmd);
if (!_init_formats(cmd))
@@ -1842,6 +1855,7 @@ int refresh_toolcontext(struct cmd_context *cmd)
_destroy_segtypes(&cmd->segtypes);
_destroy_formats(cmd, &cmd->formats);
+ device_ids_exit(cmd);
if (!dev_cache_exit())
stack;
_destroy_dev_types(cmd);
@@ -1921,6 +1935,8 @@ int refresh_toolcontext(struct cmd_context *cmd)
if (!_init_dev_cache(cmd))
return_0;
+ device_ids_init(cmd);
+
if (!_init_formats(cmd))
return_0;
@@ -1970,6 +1986,7 @@ void destroy_toolcontext(struct cmd_context *cmd)
_destroy_filters(cmd);
if (cmd->mem)
dm_pool_destroy(cmd->mem);
+ device_ids_exit(cmd);
dev_cache_exit();
_destroy_dev_types(cmd);
_destroy_tags(cmd);
diff --git a/lib/commands/toolcontext.h b/lib/commands/toolcontext.h
index c09558a42..b7c430efc 100644
--- a/lib/commands/toolcontext.h
+++ b/lib/commands/toolcontext.h
@@ -182,13 +182,20 @@ 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_devices_file:1; /* command is using devices file */
+ unsigned create_edit_devices_file:1; /* command expects to create and/or edit devices file */
+ unsigned edit_devices_file:1; /* command expects to edit devices file */
+ unsigned filter_deviceid_skip:1; /* don't use filter-deviceid */
+ unsigned filter_regex_with_devices_file:1; /* use filter-regex even when devices file is enabled */
/*
* Devices and filtering.
*/
struct dev_filter *filter;
struct dm_list hints;
+ struct dm_list use_device_ids;
const char *md_component_checks;
+ const char *devicesfile; /* from --devicesfile option */
/*
* Configuration.
@@ -220,6 +227,7 @@ struct cmd_context {
char system_dir[PATH_MAX];
char dev_dir[PATH_MAX];
char proc_dir[PATH_MAX];
+ char devices_file_path[PATH_MAX];
/*
* Reporting.
diff --git a/lib/config/config_settings.h b/lib/config/config_settings.h
index 2bb72ba71..71ef10229 100644
--- a/lib/config/config_settings.h
+++ b/lib/config/config_settings.h
@@ -288,6 +288,14 @@ 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_use_devicesfile_CFG, "use_devicesfile", devices_CFG_SECTION, CFG_DEFAULT_COMMENTED, CFG_TYPE_BOOL, DEFAULT_USE_DEVICES_FILE, vsn(2, 3, 10), NULL, 0, NULL,
+ "Enable or disable the use of a devices file.\n"
+ "When enabled, lvm will only use devices that\n"
+ "are lised in the devices file.\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 name of the devices file that lists 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..7ccce58ca 100644
--- a/lib/config/defaults.h
+++ b/lib/config/defaults.h
@@ -320,4 +320,7 @@
#define DEFAULT_MD_COMPONENT_CHECKS "auto"
+#define DEFAULT_USE_DEVICES_FILE 1
+#define DEFAULT_DEVICES_FILE "lvm_devices.dat"
+
#endif /* _LVM_DEFAULTS_H */
diff --git a/lib/device/dev-cache.c b/lib/device/dev-cache.c
index c3f7c49be..67212c09e 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,201 @@ bool dev_cache_has_md_with_end_superblock(struct dev_types *dt)
return false;
}
+int setup_devices_file(struct cmd_context *cmd)
+{
+ const char *filename = NULL;
+
+ if (cmd->devicesfile) {
+ /* --devicesfile <filename> or "" has been set which overrides
+ lvm.conf settings use_devicesfile and devicesfile. */
+ if (!strlen(cmd->devicesfile))
+ cmd->enable_devices_file = 0;
+ else {
+ cmd->enable_devices_file = 1;
+ filename = cmd->devicesfile;
+ }
+ } else {
+ if (!find_config_tree_bool(cmd, devices_use_devicesfile_CFG, NULL))
+ cmd->enable_devices_file = 0;
+ else {
+ cmd->enable_devices_file = 1;
+ filename = find_config_tree_str(cmd, devices_devicesfile_CFG, NULL);
+ if (!validate_name(filename)) {
+ log_error("Invalid devices file name from config setting \"%s\".", filename);
+ return 0;
+ }
+ }
+ }
+
+ if (!cmd->enable_devices_file)
+ return 1;
+
+ if (dm_snprintf(cmd->devices_file_path, sizeof(cmd->devices_file_path),
+ "%s/devices/%s", cmd->system_dir, filename) < 0) {
+ log_error("Failed to copy devices file path");
+ return 0;
+ }
+ return 1;
+}
+
+/*
+ * Add all system devices to dev-cache, and attempt to
+ * match all devices_file entries to dev-cache entries.
+ */
+int setup_devices(struct cmd_context *cmd)
+{
+ int file_exists;
+ int lock_mode = 0;
+
+ if (!setup_devices_file(cmd))
+ return_0;
+
+ if (!cmd->enable_devices_file)
+ goto scan;
+
+ file_exists = devices_file_exists(cmd);
+
+ /*
+ * Removing the devices file is another way of disabling the use of
+ * a devices file, unless the command creates the devices file.
+ */
+ if (!file_exists && !cmd->create_edit_devices_file) {
+ log_print("Devices file not found, ignoring.");
+ cmd->enable_devices_file = 0;
+ goto scan;
+ }
+
+ if (!file_exists) {
+ /* pvcreate/vgcreate/vgimportdevices/lvmdevices-add
+ create a new devices file here if it doesn't exist.
+ They have the create_edit_devices_file flag set.
+ First they create/lock-ex the devices file lockfile.
+ Other commands will not use a devices file if none exists. */
+
+ lock_mode = LOCK_EX;
+
+ if (!lock_devices_file(cmd, lock_mode)) {
+ log_error("Failed to lock the devices file to create.");
+ return 0;
+ }
+ if (!devices_file_touch(cmd)) {
+ log_error("Failed to create the devices file.");
+ return 0;
+ }
+ } else {
+ /* Commands that intend to edit the devices file have
+ edit_devices_file or create_edit_devices_file set (create if
+ they can also create a new devices file) and lock it ex
+ here prior to reading. Other commands that intend to just
+ read the devices file lock sh. */
+
+ lock_mode = (cmd->create_edit_devices_file || cmd->edit_devices_file) ? LOCK_EX : LOCK_SH;
+
+ if (!lock_devices_file(cmd, lock_mode)) {
+ log_error("Failed to lock the devices file.");
+ return 0;
+ }
+ }
+
+ /*
+ * Read the list of device ids that lvm can use.
+ * Adds a struct dev_id to cmd->use_device_ids for each one.
+ */
+ if (!device_ids_read(cmd)) {
+ log_error("Failed to read the devices file.");
+ return 0;
+ }
+
+ /*
+ * When the command is editing the devices file, it acquires
+ * the ex lock above, will later call device_ids_write(), and
+ * then unlock the lock after writing the file.
+ * When the command is just reading the devices file, it's
+ * locked sh above just before reading the file, and unlocked
+ * here after reading.
+ */
+ if (lock_mode && (lock_mode == LOCK_SH))
+ unlock_devices_file(cmd);
+
+ scan:
+ /*
+ * 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);
+
+ return 1;
+}
+
+/*
+ * The alternative to setup_devices() when the command is interested
+ * in using only one PV.
+ *
+ * Add one system device to dev-cache, and attempt to
+ * match its dev-cache entry to a devices_file entry.
+ */
+int setup_device(struct cmd_context *cmd, const char *devname)
+{
+ struct stat buf;
+ struct device *dev;
+
+ if (!setup_devices_file(cmd))
+ return_0;
+
+ if (!cmd->enable_devices_file)
+ goto scan;
+
+ if (!devices_file_exists(cmd)) {
+ log_print("Devices file not found, ignoring.");
+ cmd->enable_devices_file = 0;
+ goto scan;
+ }
+
+ if (!lock_devices_file(cmd, LOCK_SH)) {
+ log_error("Failed to lock the devices file to read.");
+ return 0;
+ }
+
+ if (!device_ids_read(cmd)) {
+ log_error("Failed to read the devices file.");
+ return 0;
+ }
+
+ scan:
+ if (stat(devname, &buf) < 0) {
+ log_error("Cannot access device %s.", devname);
+ return 0;
+ }
+
+ if (!S_ISBLK(buf.st_mode)) {
+ log_error("Invaild device type %s.", devname);
+ return 0;
+ }
+
+ if (!_insert_dev(devname, buf.st_rdev))
+ return_0;
+
+ if (!(dev = (struct device *) dm_hash_lookup(_cache.names, devname)))
+ return_0;
+
+ /* Match this device to an entry in devices_file so it will not
+ be rejected by filter-deviceid. */
+ if (cmd->enable_devices_file)
+ device_ids_match_dev(cmd, dev);
+
+ return 1;
+}
+
diff --git a/lib/device/dev-cache.h b/lib/device/dev-cache.h
index 46c86c27a..bba7bfac7 100644
--- a/lib/device/dev-cache.h
+++ b/lib/device/dev-cache.h
@@ -28,13 +28,12 @@ struct cmd_context;
struct dev_filter {
int (*passes_filter) (struct cmd_context *cmd, struct dev_filter *f, struct device *dev, const char *use_filter_name);
void (*destroy) (struct dev_filter *f);
- void (*wipe) (struct dev_filter *f);
+ void (*wipe) (struct cmd_context *cmd, struct dev_filter *f, struct device *dev, const char *use_filter_name);
void *private;
unsigned use_count;
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,10 @@ void dev_cache_failed_path(struct device *dev, const char *path);
bool dev_cache_has_md_with_end_superblock(struct dev_types *dt);
+int get_sysfs_value(const char *path, char *buf, size_t buf_size, int error_if_no_value);
+
+int setup_devices_file(struct cmd_context *cmd);
+int setup_devices(struct cmd_context *cmd);
+int setup_device(struct cmd_context *cmd, const char *devname);
+
#endif
diff --git a/lib/device/device.h b/lib/device/device.h
index bd3b35557..791ef5915 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..68c190b71
--- /dev/null
+++ b/lib/device/device_id.c
@@ -0,0 +1,1868 @@
+/*
+ * 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>
+
+#define DEVICES_FILE_MAJOR 1
+#define DEVICES_FILE_MINOR 1
+
+static int _devices_fd = -1;
+static int _no_devices_file;
+static int _devices_file_locked;
+static char _devices_lockfile[PATH_MAX];
+static char _devices_file_version[256];
+
+/*
+ * How the devices file and device IDs are used by an ordinary command:
+ *
+ * 1. device_ids_read() reads the devices file, and adds a 'struct uid'
+ * to cmd->use_device_ids for each entry. These are the devices lvm
+ * can use, but we do not yet know which devnames they correspond to.
+ * 2. dev_cache_scan() gets a list of all devices (devnames) on the system,
+ * and adds a 'struct device' to dev-cache for each.
+ * 3. device_ids_match() matches uid entries from the devices file
+ * with devices from dev-cache. With this complete, we know the
+ * devnames to use for each of the entries in the devices file.
+ * 4. label_scan (or equivalent) iterates through all devices in
+ * dev-cache, checks each one with filters, which excludes many,
+ * and reads lvm headers and metadata from the devs that pass the
+ * filters. lvmcache is populated with summary info about each PV
+ * during this phase.
+ * 5. device_ids_validate() checks if the PVIDs saved in the devices
+ * file are correct based on the PVIDs read from disk in the
+ * previous step. If not it updates the devices file.
+ *
+ * cmd->use_device_ids reflect the entries in the devices file.
+ * When reading the devices file, a 'uid' struct is added to use_device_ids
+ * for each entry.
+ * When adding devices to the devices file, a new uid struct is added
+ * to use_device_ids, and then a new file entry is written for each uid.
+ *
+ * After reading the devices file, we want to "match" each uid from
+ * the file to an actual device on the system. We look at struct device's
+ * in dev-cache to find one that matches each uid, based on the device_id.
+ * When a match is made, uid->dev is set, and DEV_MATCHED_USE_ID is set
+ * in the dev.
+ *
+ * After the use_device_ids entries are matched to system devices,
+ * label_scan can be called to filter and scan devices. After
+ * label_scan, device_ids_validate() is called to check if the
+ * PVID read from each device matches the PVID recorded in the
+ * devices file for the device.
+ *
+ * A device can have multiple device IDs, e.g. a dev could have
+ * both a wwid and a serial number, but only one of these IDs is
+ * used as the device ID in the devices file, e.g. the wwid is
+ * preferred so that would be used in the devices file.
+ * Each of the different types of device IDs can be saved in
+ * dev->ids list (struct dev_id). So, one dev may have two
+ * entries in dev->ids, one for wwid and one for serial.
+ * The dev_id struct that is actually being used for the device
+ * is set in dev->id.
+ * The reason for saving multiple IDs in dev->ids is because
+ * the process of matching devs to devices file entries can
+ * involve repeatedly checking other dev_id types for a given
+ * device, so we save each type as it is read to avoid rereading
+ * the same id type many times.
+ */
+
+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) {
+ log_error("Failed to create sysfs path for %s", dev_name(dev));
+ 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;
+}
+
+/* the dm uuid uses the wwid of the underlying dev */
+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;
+
+ /* if it's a partitioned mpath device, use the primary */
+ 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_sys_block(cmd, dev, "dm/uuid", idname);
+
+ if (*idname)
+ return 1;
+ return 0;
+}
+
+/*
+ * Should there be a list like lvm.conf
+ * device_id_types = [ "sys_wwid", "sys_serial" ]
+ * that controls which idtype's will be used?
+ *
+ * If two partitions use the same device_id it doesn't really
+ * matter since the device_id is primarily about selecting
+ * an acceptable device to process for the PV.
+ */
+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_block(cmd, dev, "device/wwid", &idname);
+
+ else if (idtype == DEV_ID_TYPE_SYS_SERIAL)
+ _read_sys_block(cmd, dev, "device/serial", &idname);
+
+ else if (idtype == DEV_ID_TYPE_MPATH_UUID)
+ _read_sys_block(cmd, dev, "dm/uuid", &idname);
+
+ else if (idtype == DEV_ID_TYPE_LOOP_FILE)
+ _read_sys_block(cmd, dev, "loop/backing_file", &idname);
+
+ else if (idtype == DEV_ID_TYPE_DEVNAME)
+ idname = strdup(dev_name(dev));
+
+ 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++;
+ }
+
+ dst[len-1] = '\0';
+}
+
+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 ret = 1;
+
+ /*
+ * 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_devices_file)
+ return 1;
+
+ free_uids(&cmd->use_device_ids);
+
+ if (!(fp = fopen(cmd->devices_file_path, "r"))) {
+ log_warn("Cannot open devices file to read.");
+ return 0;
+ }
+
+ while (fgets(line, sizeof(line), fp)) {
+ if (line[0] == '#')
+ continue;
+
+ if (!strncmp(line, "VERSION", 7)) {
+ _copy_idline_str(line, _devices_file_version, sizeof(_devices_file_version));
+ log_debug("read devices file version %s", _devices_file_version);
+ 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;
+
+ return ret;
+}
+
+int device_ids_write(struct cmd_context *cmd)
+{
+ char tmpfile[PATH_MAX];
+ FILE *fp;
+ time_t t;
+ struct use_id *uid;
+ const char *devname;
+ const char *pvid;
+ uint32_t df_major = 0, df_minor = 0, df_counter = 0;
+ int ret = 1;
+
+ if (!cmd->enable_devices_file)
+ return 1;
+
+ if (_devices_file_version[0]) {
+ if (sscanf(_devices_file_version, "%u.%u.%u", &df_major, &df_minor, &df_counter) != 3) {
+ /* don't update a file we can't parse */
+ log_print("Not updating devices file with unparsed version.");
+ return 0;
+ }
+ if (df_major > DEVICES_FILE_MAJOR) {
+ /* don't update a file with a newer major version */
+ log_print("Not updating devices file with larger major version.");
+ return 0;
+ }
+ }
+
+ if (dm_snprintf(tmpfile, sizeof(tmpfile), "%s_new", cmd->devices_file_path) < 0) {
+ ret = 0;
+ goto out;
+ }
+
+ unlink(tmpfile); /* in case a previous file was left */
+
+ if (!(fp = fopen(tmpfile, "w+"))) {
+ log_warn("Cannot open tmp 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", cmd->name, getpid(), ctime(&t));
+ fprintf(fp, "VERSION=%u.%u.%u\n", DEVICES_FILE_MAJOR, DEVICES_FILE_MINOR, df_counter+1);
+
+ 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;
+
+ if (rename(tmpfile, cmd->devices_file_path)) {
+ log_error("Failed to replace devices file errno %d", errno);
+ ret = 0;
+ }
+out:
+ return ret;
+}
+
+int device_ids_version_unchanged(struct cmd_context *cmd)
+{
+ char line[PATH_MAX];
+ char version_buf[256];
+ FILE *fp;
+
+ if (!(fp = fopen(cmd->devices_file_path, "r"))) {
+ log_warn("Cannot open devices file to read.");
+ return 0;
+ }
+
+ while (fgets(line, sizeof(line), fp)) {
+ if (line[0] == '#')
+ continue;
+
+ if (!strncmp(line, "VERSION", 7)) {
+ if (fclose(fp))
+ stack;
+
+ _copy_idline_str(line, version_buf, sizeof(version_buf));
+
+ log_debug("check devices file version %s prev %s", version_buf, _devices_file_version);
+
+ if (!strcmp(version_buf, _devices_file_version))
+ return 1;
+ return 0;
+ }
+ }
+
+ return 0;
+}
+
+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 = NULL;
+ const char *check_idname = NULL;
+ const char *update_matching_kind = NULL;
+ const char *update_matching_name = NULL;
+ struct use_id *uid, *update_uid = NULL, *uid_dev, *uid_pvid, *uid_devname, *uid_devid;
+ struct dev_id *did;
+ int found_did = 0;
+
+ if (!cmd->enable_devices_file)
+ return 1;
+
+ uid_dev = get_uid_for_dev(cmd, dev);
+ uid_pvid = get_uid_for_pvid(cmd, pvid);
+ uid_devname = _get_uid_for_devname(cmd, dev_name(dev));
+
+ /*
+ * Choose the device_id type for the device being added.
+ *
+ * 1. use an idtype dictated if this is a special kind
+ * of device, e.g. loop, mpath, md, nbd, etc
+ * (TODO: some special types not yet implemented)
+ *
+ * 2. use an idtype specified by user option.
+ *
+ * 3. use an idtype from an existing matching devices_file entry.
+ *
+ * 4. use sys_wwid, if it exists.
+ *
+ * 5. use sys_serial, if it exists.
+ *
+ * 6. use devname as the last resort.
+ *
+ * TODO: allow lvm.conf device_id_types to control the
+ * idtypes that can be used above?
+ *
+ * If this device is part of a VG, and the VG metadata already
+ * includes a device_id for this device, then it would be nice
+ * to use that device_id. But, lvmdevices is in principle not
+ * reading/writing VG metadata. Adding with vgimportdevices
+ * would have access to the VG metadata and use a device_id
+ * from the metadata if it's set.
+ */
+
+ 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;
+ }
+
+ if (MAJOR(dev->dev) == cmd->dev_types->md_major) {
+ /* TODO */
+ log_print("Missing support for MD idtype");
+ }
+
+ if (MAJOR(dev->dev) == cmd->dev_types->drbd_major) {
+ /* TODO */
+ log_print("Missing support for DRBD idtype");
+ }
+
+ 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 existing entry for this pvid, use that idtype.
+ */
+ if (!idtype && uid_pvid) {
+ idtype = uid_pvid->idtype;
+ goto id_name;
+ }
+
+ /*
+ * No device-specific, existing, or user-specified idtypes,
+ * so use first available of sys_wwid / sys_serial / devname.
+ */
+ 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:
+
+ /*
+ * Create a dev_id struct for the new idtype on dev->ids.
+ */
+ 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;
+
+ /*
+ * Update the cmd->use_device_ids list for the new device. The
+ * use_device_ids list will be used to update the devices file.
+ *
+ * The dev being added can potentially overlap existing entries
+ * in various ways. If one of the existing entries is truely for
+ * this device being added, then we want to update that entry.
+ * If some other existing entries are not for the same device, but
+ * have some overlapping values, then we want to try to update
+ * those other entries to fix any incorrect info.
+ */
+
+ uid_devid = _get_uid_for_device_id(cmd, did->idtype, did->idname);
+
+ if (uid_dev)
+ log_debug("device_id_add %s pvid %s matches uid_dev %p dev %s",
+ dev_name(dev), pvid, uid_dev, dev_name(uid_dev->dev));
+ if (uid_pvid)
+ log_debug("device_id_add %s pvid %s matches uid_pvid %p dev %s pvid %s",
+ dev_name(dev), pvid, uid_pvid, uid_pvid->dev ? dev_name(uid_pvid->dev) : ".",
+ uid_pvid->pvid);
+ if (uid_devid)
+ log_debug("device_id_add %s pvid %s matches uid_devid %p dev %s pvid %s",
+ dev_name(dev), pvid, uid_devid, uid_devid->dev ? dev_name(uid_devid->dev) : ".",
+ uid_devid->pvid);
+ if (uid_devname)
+ log_debug("device_id_add %s pvid %s matches uid_devname %p dev %s pvid %s",
+ dev_name(dev), pvid, uid_devname, uid_devname->dev ? dev_name(uid_devname->dev) : ".",
+ uid_devname->pvid);
+
+ /*
+ * If one of the existing entries (uid_dev, uid_pvid, uid_devid, uid_devname)
+ * is truely for the same device that is being added, then set update_uid to
+ * that existing entry to be updated.
+ */
+
+ if (uid_dev) {
+ update_uid = uid_dev;
+ dm_list_del(&update_uid->list);
+ update_matching_kind = "device";
+ update_matching_name = dev_name(dev);
+
+ if (uid_devid && (uid_devid != uid_dev)) {
+ log_warn("WARNING: device %s (%s) and %s (%s) have duplicate device ID.",
+ dev_name(dev), idname,
+ uid_pvid->dev ? dev_name(uid_pvid->dev) : ".", uid_pvid->idname);
+ }
+
+ if (uid_pvid && (uid_pvid != uid_dev)) {
+ log_warn("WARNING: device %s (%s) and %s (%s) have duplicate PVID %s",
+ dev_name(dev), idname,
+ uid_pvid->dev ? dev_name(uid_pvid->dev) : ".", uid_pvid->idname,
+ pvid);
+ }
+
+ if (uid_devname && (uid_devname != uid_dev)) {
+ /* 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_pvid) {
+ /*
+ * If the device_id of the existing entry for PVID is the same
+ * as the device_id of the device being added, then update the
+ * existing entry. If the device_ids differ, then the devices
+ * have duplicate PVIDs, and the new device gets a new entry
+ * (if we allow it to be added.)
+ */
+ if (uid_pvid->idtype == idtype)
+ check_idname = idname;
+ else
+ check_idname = _device_id_system_read(cmd, dev, uid_pvid->idtype);
+
+ if (check_idname && !strcmp(check_idname, uid_pvid->idname)) {
+ update_uid = uid_pvid;
+ dm_list_del(&update_uid->list);
+ update_matching_kind = "PVID";
+ update_matching_name = pvid;
+ } else {
+ log_warn("WARNING: device %s (%s) and %s (%s) have duplicate PVID %s",
+ dev_name(dev), idname,
+ uid_pvid->dev ? dev_name(uid_pvid->dev) : ".", uid_pvid->idname,
+ pvid);
+
+ /* require a force or similar option to allow adding duplicate? */
+ }
+
+ 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) {
+ /*
+ * Do we create a new uid or update the existing uid?
+ * If it's the same device, update the existing uid,
+ * but if 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));
+ } else {
+ /* update the existing entry with matching devid */
+ update_uid = uid_devid;
+ dm_list_del(&update_uid->list);
+ update_matching_kind = "device_id";
+ update_matching_name = did->idname;
+ }
+
+ 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 (!update_uid) {
+ log_print("Adding new entry to devices file for %s PVID %s %s %s.",
+ dev_name(dev), pvid, idtype_to_str(did->idtype), did->idname);
+ if (!(uid = zalloc(sizeof(struct use_id))))
+ return_0;
+ } else {
+ uid = update_uid;
+ log_print("Updating existing entry in devices file for %s that matches %s %s.",
+ dev_name(dev), update_matching_kind, update_matching_name);
+ }
+
+ 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;
+ }
+
+ 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_devices_file)
+ 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_devices_file)
+ 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;
+ log_print("devices idname %s devname %s matched %s", uid->idname, uid->devname, dev_name(dev));
+ 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;
+ log_print("devices idname %s devname %s matched %s", uid->idname, uid->devname, dev_name(dev));
+ 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. This function cannot use filters.
+ * filters are applied after this, and the filters may open devs in the first
+ * filter stage. The second filtering stage, done after label_scan has read
+ * a device, is allowed to read a device to evaluate filters that need to see
+ * data from the dev.
+ *
+ * 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_devices_file)
+ return;
+
+ /*
+ * We would set cmd->filter_deviceid_skip but we are disabling
+ * all filters (dev_cache_get NULL arg) so it's not necessary.
+ */
+
+ dm_list_iterate_items(uid, &cmd->use_device_ids) {
+ /* already matched */
+ if (uid->dev)
+ continue;
+
+ /*
+ * uid->devname from the devices file is the last known
+ * device name. It may be incorrect, but it's usually
+ * correct, so it's an efficient place to check for a
+ * match first.
+ *
+ * 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 successful match, uid, dev, and did are linked. */
+ if (_match_uid_to_dev(cmd, uid, dev))
+ continue;
+ else {
+ /* The device node may exist but the device is disconnected / zero size,
+ and likely has no sysfs entry to check for wwid. Continue to look
+ for the device id on other devs. */
+ log_debug("devices entry %s %s devname found but not matched", uid->devname, uid->pvid ?: ".");
+ }
+ }
+
+ /*
+ * 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 updated 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) {
+ /* Found a device for this entry. */
+ if (uid->dev && (uid->dev->flags & DEV_MATCHED_USE_ID))
+ continue;
+
+ /* This shouldn't be possible. */
+ if (uid->dev && !(uid->dev->flags & DEV_MATCHED_USE_ID)) {
+ log_error("Device %s not matched to device_id", dev_name(uid->dev));
+ continue;
+ }
+
+ /* The device is detached, this is not uncommon. */
+ log_print("devices entry not found %s %s %s %s.",
+ uid->devname, idtype_to_str(uid->idtype), uid->idname, uid->pvid);
+ }
+}
+
+/*
+ * This is called after devices are scanned 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 and know the PVID's from disk we can check the PVID's in
+ * use_device_ids entries from the devices file.
+ */
+
+void device_ids_validate(struct cmd_context *cmd, int force_update)
+{
+ struct device *dev;
+ struct use_id *uid;
+ int update_file = 0;
+
+ if (!cmd->enable_devices_file)
+ return;
+
+ /* FIXME: detect a situation where device_ids_match() has linked a
+ uid and dev, but the device ended up being excluded by filters.
+ Perhaps print a warning, and undo the match. */
+
+ dm_list_iterate_items(uid, &cmd->use_device_ids) {
+ if (!uid->dev)
+ continue;
+
+ dev = uid->dev;
+
+ /*
+ * If the uid pvid from the devices file does not match the
+ * pvid read from disk, replace the uid pvid with the pvid from
+ * disk and update the pvid in the devices file entry.
+ *
+ * However, don't do this if the uid and dev were matched using
+ * a devname idtype, because it may be a case of the unstable
+ * devname changing. In that case, unlink the uid and dev
+ * structs, and let device_ids_find_renamed_devs() figure out
+ * what to do.
+ */
+
+ if (dev->pvid[0]) {
+ if (!uid->pvid || strcmp(dev->pvid, uid->pvid)) {
+ if ((uid->idtype == DEV_ID_TYPE_DEVNAME) && uid->pvid && (uid->pvid[0] != '.')) {
+ /* undo the uid/dev match for this entry */
+ log_print("Device %s undo device id devname match with wrong PVID.", dev_name(dev));
+ dev->flags &= ~DEV_MATCHED_USE_ID;
+ dev->id = NULL;
+ uid->dev = NULL;
+ continue;
+ }
+
+ log_print("Device %s has updated PVID %s (devices file %s)",
+ dev_name(dev), dev->pvid, uid->pvid ?: ".");
+ if (uid->pvid)
+ free(uid->pvid);
+ if (!(uid->pvid = strdup(dev->pvid)))
+ stack;
+ update_file = 1;
+ }
+ } else {
+ if (uid->pvid && (uid->pvid[0] != '.')) {
+ if (uid->idtype == DEV_ID_TYPE_DEVNAME) {
+ /* undo the uid/dev match for this entry */
+ log_print("Device %s undo device id devname match with wrong PVID.", dev_name(dev));
+ dev->flags &= ~DEV_MATCHED_USE_ID;
+ dev->id = NULL;
+ uid->dev = NULL;
+ continue;
+ }
+
+ log_print("Device %s has no PVID (devices file %s)",
+ dev_name(dev), uid->pvid);
+ if (uid->pvid)
+ free(uid->pvid);
+ uid->pvid = NULL;
+ update_file = 1;
+ }
+ }
+
+ if (!uid->devname || strcmp(dev_name(uid->dev), uid->devname)) {
+ log_print("Device %s has updated name (devices file %s)",
+ dev_name(uid->dev), uid->devname ?: ".");
+ if (uid->devname)
+ free(uid->devname);
+ if (!(uid->devname = strdup(dev_name(uid->dev))))
+ stack;
+ update_file = 1;
+ }
+ }
+
+ if (update_file || force_update) {
+ int held;
+
+ /*
+ * Use a non-blocking lock since it's not essential to
+ * make this update, the next cmd will if we skip it.
+ * If the command already holds an ex lock on the
+ * devices file, lock_devices_file ex succeeds and
+ * held is set.
+ * If we get the lock, only update the devices file if
+ * it's not been changed since we read it.
+ */
+ if (!lock_devices_file_try(cmd, LOCK_EX, &held)) {
+ log_debug("Skip devices file update (busy).");
+ } else {
+ if (device_ids_version_unchanged(cmd))
+ device_ids_write(cmd);
+ else
+ log_debug("Skip devices file update (changed).");
+ }
+ if (!held)
+ unlock_devices_file(cmd);
+ }
+}
+
+int device_id_read_pvid(struct cmd_context *cmd, struct device *dev)
+{
+ char buf[4096] __attribute__((aligned(8)));
+ struct label_header *lh;
+ struct pv_header *pvh;
+ int ret = 0;
+
+ memset(buf, 0, sizeof(buf));
+
+ if (!label_scan_open(dev))
+ return_0;
+
+ /*
+ * 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))
+ goto_out;
+
+ lh = (struct label_header *)(buf + 512);
+ if (memcmp(lh->id, LABEL_ID, sizeof(lh->id)))
+ goto_out;
+
+ pvh = (struct pv_header *)(buf + 512 + 32);
+ memcpy(dev->pvid, pvh->pv_uuid, ID_LEN);
+ ret = 1;
+out:
+ label_scan_invalidate(dev);
+ return ret;
+}
+
+/*
+ * 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);
+ }
+}
+
+void device_ids_find_renamed_devs(struct cmd_context *cmd, struct dm_list *dev_list)
+{
+ struct device *dev;
+ struct use_id *uid;
+ struct dev_id *did;
+ struct dev_iter *iter;
+ struct device_list *devl; /* holds struct device */
+ struct device_id_list *dil, *dil2; /* holds struct device + pvid */
+ struct dm_list search_pvids; /* list of device_id_list */
+ struct dm_list search_devs ; /* list of device_list */
+ const char *devname;
+
+ dm_list_init(&search_pvids);
+ dm_list_init(&search_devs);
+
+ /* FIXME: add an lvm.conf setting to disable this automatic search */
+ /* search_for_unstable_devnames=0|1 */
+
+ if (!cmd->enable_devices_file)
+ return;
+
+ dm_list_iterate_items(uid, &cmd->use_device_ids) {
+ if (uid->dev)
+ continue;
+ if (!uid->pvid)
+ continue;
+ if (uid->idtype != DEV_ID_TYPE_DEVNAME)
+ continue;
+ if (!(dil = dm_pool_zalloc(cmd->mem, sizeof(*dil))))
+ continue;
+
+ memcpy(dil->pvid, uid->pvid, ID_LEN);
+ dm_list_add(&search_pvids, &dil->list);
+
+ log_print("Missing PVID %s previous devname %s.",
+ uid->pvid, uid->devname);
+ }
+
+ if (dm_list_empty(&search_pvids))
+ return;
+
+ /*
+ * Now we want to look at devs on the system that were previously
+ * rejected by filter-deviceid (based on a devname device id) to check
+ * if the missing PVID is on a device with a new name.
+ */
+ log_print("Searching devices for missing PVIDs");
+
+ /*
+ * Initial list of devs to search, eliminating any that have already
+ * been matched, or don't pass filters that do not read dev. We do not
+ * want to modify the command's existing filter chain (the persistent
+ * filter), in the process of doing this search outside the deviceid
+ * filter.
+ */
+ cmd->filter_regex_with_devices_file = 0;
+ if (!(iter = dev_iter_create(NULL, 0)))
+ return;
+ while ((dev = dev_iter_get(cmd, iter))) {
+ if (dev->flags & DEV_MATCHED_USE_ID)
+ continue;
+ /* TODO: use bitfield to select filters to use. */
+ if (!cmd->filter->passes_filter(cmd, cmd->filter, dev, "sysfs"))
+ continue;
+ if (!cmd->filter->passes_filter(cmd, cmd->filter, dev, "regex"))
+ continue;
+ if (!cmd->filter->passes_filter(cmd, cmd->filter, dev, "type"))
+ continue;
+ if (!cmd->filter->passes_filter(cmd, cmd->filter, dev, "usable"))
+ continue;
+ if (!cmd->filter->passes_filter(cmd, cmd->filter, dev, "mpath"))
+ continue;
+ if (!(devl = dm_pool_zalloc(cmd->mem, sizeof(*devl))))
+ continue;
+ devl->dev = dev;
+ dm_list_add(&search_devs, &devl->list);
+ }
+ dev_iter_destroy(iter);
+ cmd->filter_regex_with_devices_file = 1;
+
+ /*
+ * Read the dev to get the pvid, and run the filters that will use the
+ * data that has been read to get the pvid. Like above, we do not want
+ * to modify the command's existing filter chain or the persistent
+ * filter values.
+ */
+ dm_list_iterate_items(devl, &search_devs) {
+ dev = devl->dev;
+
+ /*
+ * Reads 4K from the start of the disk.
+ * Looks for LVM header, and sets dev->pvid if the device is a PV.
+ * Returns 0 if the dev has no lvm label or no PVID.
+ * This loop may look at and skip many non-LVM devices.
+ */
+ if (!device_id_read_pvid(cmd, dev))
+ continue;
+
+ /*
+ * These filters will use the block of data from bcache that
+ * was read device_id_read_pvid(), and may read other
+ * data blocks beyond that.
+ */
+ if (!cmd->filter->passes_filter(cmd, cmd->filter, dev, "partitioned"))
+ continue;
+ if (!cmd->filter->passes_filter(cmd, cmd->filter, dev, "signature"))
+ continue;
+ if (!cmd->filter->passes_filter(cmd, cmd->filter, dev, "md"))
+ continue;
+ if (!cmd->filter->passes_filter(cmd, cmd->filter, dev, "fwraid"))
+ continue;
+
+ log_print("Checking for missing PVIDs on %s.", dev_name(dev));
+
+ /*
+ * Check if the the PVID is one we are searching for.
+ * Loop below looks at search_pvid entries that
+ * have dil->dev set.
+ */
+ dm_list_iterate_items_safe(dil, dil2, &search_pvids) {
+ if (!memcmp(dil->pvid, dev->pvid, ID_LEN)) {
+ if (dil->dev) {
+ log_warn("WARNING: located PVID %s on multiple devices %s %s.",
+ dil->pvid, dev_name(dil->dev), dev_name(dev));
+ log_warn("WARNING: duplicate PVIDs should be changed to be unique.");
+ log_warn("WARNING: use lvmdevices to select a device for PVID %s.", dil->pvid);
+ dm_list_del(&dil->list);
+ } else {
+ dil->dev = dev;
+ }
+ }
+ }
+ }
+
+ /*
+ * The use_device_ids entries (repesenting the devices file) are
+ * updated for the new devices on which the PVs reside.
+ *
+ * The uid/dev/did are set up and linked for the new devs.
+ *
+ * The command's full filter chain is updated for the new devs now that
+ * filter-deviceid will pass.
+ *
+ * The devices file is updated by device_ids_validate.
+ */
+ dm_list_iterate_items(dil, &search_pvids) {
+ if (!dil->dev)
+ continue;
+ dev = dil->dev;
+ devname = dev_name(dev);
+
+ if (!(uid = get_uid_for_pvid(cmd, dil->pvid))) {
+ /* shouldn't happen */
+ continue;
+ }
+ if (uid->idtype != DEV_ID_TYPE_DEVNAME) {
+ /* shouldn't happen */
+ continue;
+ }
+
+ if (uid->idname)
+ free(uid->idname);
+ if (uid->devname)
+ free(uid->devname);
+ if (!(uid->idname = strdup(devname)))
+ stack;
+ if (!(uid->devname = strdup(devname)))
+ stack;
+
+ free_dids(&dev->ids);
+
+ if (!(did = zalloc(sizeof(struct dev_id)))) {
+ stack;
+ continue;
+ }
+
+ if (!((did->idname = strdup(devname)))) {
+ stack;
+ continue;
+ }
+ did->idtype = DEV_ID_TYPE_DEVNAME;
+ did->dev = dev;
+ uid->dev = dev;
+ dev->id = did;
+ dev->flags |= DEV_MATCHED_USE_ID;
+ dm_list_add(&dev->ids, &did->list);
+ }
+
+ dm_list_iterate_items(dil, &search_pvids) {
+ if (!dil->dev)
+ continue;
+ dev = dil->dev;
+
+ cmd->filter->wipe(cmd, cmd->filter, dev, NULL);
+
+ if (!cmd->filter->passes_filter(cmd, cmd->filter, dev, NULL)) {
+ /* I don't think this would happen */
+ log_warn("New dev %s for PVID %s does not pass filter.", dev_name(dev), dil->pvid);
+ uid->dev = NULL;
+ dev->flags &= ~DEV_MATCHED_USE_ID;
+ }
+ }
+
+ /*
+ * Calling device_ids_validate() to write the udpated devices file.
+ * We've made the corrections above, so it shouldn't find any problems
+ * during the validation checks. We need to pass the force_update flag
+ * to make it write the file for the fixes we've already made, since it
+ * will probably find no reason to write the file otherwise.
+ */
+ device_ids_validate(cmd, 1);
+
+ /*
+ * The entries in search_pvids with a dev set are the new devs found
+ * for the PVIDs that we want to return to the caller in a device_list
+ * format.
+ */
+ dm_list_iterate_items(dil, &search_pvids) {
+ if (!dil->dev)
+ continue;
+ dev = dil->dev;
+
+ if (!(devl = dm_pool_zalloc(cmd->mem, sizeof(*devl))))
+ continue;
+ devl->dev = dev;
+ dm_list_add(dev_list, &devl->list);
+ log_print("Missing PVID %s found on %s.", dil->pvid, dev_name(dev));
+ }
+}
+
+int devices_file_touch(struct cmd_context *cmd)
+{
+ int fd;
+ fd = open(cmd->devices_file_path, O_CREAT);
+ if (fd < 0)
+ return 0;
+ if (close(fd))
+ stack;
+ return 1;
+}
+
+int devices_file_exists(struct cmd_context *cmd)
+{
+ struct stat buf;
+
+ if (!cmd->devices_file_path[0])
+ return 0;
+
+ if (stat(cmd->devices_file_path, &buf))
+ return 0;
+
+ return 1;
+}
+
+/*
+ * If a command also uses the global lock, the global lock
+ * is acquired first, then the devices file is locked.
+ *
+ * There are three categories of commands in terms of
+ * reading/writing the devices file:
+ *
+ * 1. Commands that we know intend to modify the file,
+ * lvmdevices --add|--del, vgimportdevices,
+ * pvcreate/vgcreate/vgextend, pvchange --uuid,
+ * vgimportclone.
+ *
+ * 2. Most other commands that do not normally modify the file.
+ *
+ * 3. Commands from 2 that find something to correct in
+ * the devices file during device_ids_validate().
+ * These corrections are not essential and can be
+ * skipped, they will just be done by a subsequent
+ * command.
+ *
+ * Locking for each case:
+ *
+ * 1. lock ex, read file, write file, unlock
+ *
+ * 2. lock sh, read file, unlock, (validate ok)
+ *
+ * 3. lock sh, read file, unlock, validate wants update,
+ * lock ex (nonblocking - skip update if fails),
+ * read file, check file is unchanged from prior read,
+ * write file, unlock
+ */
+
+static int _lock_devices_file(struct cmd_context *cmd, int mode, int nonblock, int *held)
+{
+ const char *lock_dir;
+ const char *filename;
+ int fd;
+ int op = mode;
+ int ret;
+
+ if (!cmd->enable_devices_file) {
+ _no_devices_file = 1;
+ return 1;
+ }
+
+ _no_devices_file = 0;
+
+ if (cmd->nolocking)
+ return 1;
+
+ if (_devices_file_locked == mode) {
+ /* can happen when a command holds an ex lock and does an update in device_ids_validate */
+ *held = 1;
+ return 1;
+ }
+
+ if (_devices_file_locked) {
+ /* shouldn't happen */
+ log_print("lock_devices_file %d already locked %d", mode, _devices_file_locked);
+ return 0;
+ }
+
+ if (!(lock_dir = find_config_tree_str(cmd, global_locking_dir_CFG, NULL)))
+ return_0;
+ if (!(filename = cmd->devicesfile ?: find_config_tree_str(cmd, devices_devicesfile_CFG, NULL)))
+ return_0;
+ if (dm_snprintf(_devices_lockfile, sizeof(_devices_lockfile), "%s/D_%s", lock_dir, filename) < 0)
+ return_0;
+
+ if (nonblock)
+ op |= LOCK_NB;
+
+ if (_devices_fd != -1) {
+ log_warn("lock_devices_file existing fd %d", _devices_fd);
+ return 0;
+ }
+
+ fd = open(_devices_lockfile, O_CREAT|O_RDWR);
+ if (fd < 0) {
+ log_debug("lock_devices_file open errno %d", errno);
+ return 0;
+ }
+
+
+ ret = flock(fd, op);
+ if (!ret) {
+ _devices_fd = fd;
+ _devices_file_locked = mode;
+ return 1;
+ }
+
+ if (close(fd))
+ stack;
+ return 0;
+}
+
+int lock_devices_file(struct cmd_context *cmd, int mode)
+{
+ return _lock_devices_file(cmd, mode, 0, NULL);
+}
+
+int lock_devices_file_try(struct cmd_context *cmd, int mode, int *held)
+{
+ return _lock_devices_file(cmd, mode, 1, held);
+}
+
+void unlock_devices_file(struct cmd_context *cmd)
+{
+ int ret;
+
+ if (cmd->nolocking)
+ return;
+
+ if (_no_devices_file)
+ return;
+
+ if (_devices_fd == -1) {
+ log_warn("unlock_devices_file no existing fd");
+ return;
+ }
+
+ if (!_devices_file_locked)
+ log_warn("unlock_devices_file not locked");
+
+ ret = flock(_devices_fd, LOCK_UN);
+ if (ret)
+ log_warn("unlock_devices_file flock errno %d", errno);
+
+ _devices_file_locked = 0;
+
+ if (close(_devices_fd))
+ stack;
+ _devices_fd = -1;
+}
+
+void device_ids_init(struct cmd_context *cmd)
+{
+ dm_list_init(&cmd->use_device_ids);
+}
+
+void device_ids_exit(struct cmd_context *cmd)
+{
+ free_uids(&cmd->use_device_ids);
+ if (_devices_fd == -1)
+ return;
+ unlock_devices_file(cmd);
+}
+
diff --git a/lib/device/device_id.h b/lib/device/device_id.h
new file mode 100644
index 000000000..899224a30
--- /dev/null
+++ b/lib/device/device_id.h
@@ -0,0 +1,57 @@
+/*
+ * 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);
+void device_ids_match(struct cmd_context *cmd);
+int device_ids_match_dev(struct cmd_context *cmd, struct device *dev);
+void device_ids_validate(struct cmd_context *cmd, int force_update);
+int device_ids_version_unchanged(struct cmd_context *cmd);
+int device_id_read_pvid(struct cmd_context *cmd, struct device *dev);
+void device_ids_read_pvids(struct cmd_context *cmd);
+void device_ids_find_renamed_devs(struct cmd_context *cmd, struct dm_list *dev_list);
+
+struct use_id *get_uid_for_dev(struct cmd_context *cmd, struct device *dev);
+struct use_id *get_uid_for_pvid(struct cmd_context *cmd, const char *pvid);
+
+int devices_file_exists(struct cmd_context *cmd);
+int devices_file_touch(struct cmd_context *cmd);
+int lock_devices_file(struct cmd_context *cmd, int mode);
+int lock_devices_file_try(struct cmd_context *cmd, int mode, int *held);
+void unlock_devices_file(struct cmd_context *cmd);
+
+void device_ids_init(struct cmd_context *cmd);
+void device_ids_exit(struct cmd_context *cmd);
+
+#endif
diff --git a/lib/filters/filter-composite.c b/lib/filters/filter-composite.c
index b0063f149..174297193 100644
--- a/lib/filters/filter-composite.c
+++ b/lib/filters/filter-composite.c
@@ -60,13 +60,15 @@ static void _composite_destroy(struct dev_filter *f)
free(f);
}
-static void _wipe(struct dev_filter *f)
+static void _wipe(struct cmd_context *cmd, struct dev_filter *f, struct device *dev, const char *use_filter_name)
{
struct dev_filter **filters;
for (filters = (struct dev_filter **) f->private; *filters; ++filters)
+ if (use_filter_name && strcmp((*filters)->name, use_filter_name))
+ continue;
if ((*filters)->wipe)
- (*filters)->wipe(*filters);
+ (*filters)->wipe(cmd, *filters, dev, use_filter_name);
}
struct dev_filter *composite_filter_create(int n, int use_dev_ext_info, struct dev_filter **filters)
diff --git a/lib/filters/filter-deviceid.c b/lib/filters/filter-deviceid.c
new file mode 100644
index 000000000..ee58bf5af
--- /dev/null
+++ b/lib/filters/filter-deviceid.c
@@ -0,0 +1,60 @@
+/*
+ * 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_devices_file)
+ return 1;
+
+ if (cmd->filter_deviceid_skip)
+ return 1;
+
+ if (dev->flags & DEV_MATCHED_USE_ID)
+ return 1;
+ log_debug_devs("%s: Skipping (deviceid)", dev_name(dev));
+ 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-persistent.c b/lib/filters/filter-persistent.c
index afa32d4b9..291f3932f 100644
--- a/lib/filters/filter-persistent.c
+++ b/lib/filters/filter-persistent.c
@@ -64,11 +64,17 @@ static int _init_hash(struct pfilter *pf)
return 1;
}
-static void _persistent_filter_wipe(struct dev_filter *f)
+static void _persistent_filter_wipe(struct cmd_context *cmd, struct dev_filter *f, struct device *dev, const char *use_filter_name)
{
struct pfilter *pf = (struct pfilter *) f->private;
+ struct dm_str_list *sl;
- dm_hash_wipe(pf->devices);
+ if (!dev) {
+ dm_hash_wipe(pf->devices);
+ } else {
+ dm_list_iterate_items(sl, &dev->aliases)
+ dm_hash_remove(pf->devices, sl->str);
+ }
}
static int _lookup_p(struct cmd_context *cmd, struct dev_filter *f, struct device *dev, const char *use_filter_name)
diff --git a/lib/filters/filter-regex.c b/lib/filters/filter-regex.c
index e439b36b5..a38f647f2 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_devices_file && !cmd->filter_regex_with_devices_file) {
+ /* 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/hints.c b/lib/label/hints.c
index 9546f4880..d681c5184 100644
--- a/lib/label/hints.c
+++ b/lib/label/hints.c
@@ -145,6 +145,7 @@
#include "lib/activate/activate.h"
#include "lib/label/hints.h"
#include "lib/device/dev-type.h"
+#include "lib/device/device_id.h"
#include <sys/stat.h>
#include <fcntl.h>
@@ -166,8 +167,10 @@ static const char *_newhints_file = DEFAULT_RUN_DIR "/newhints";
* than they were built with. Increase the minor number
* when adding features that older lvm versions can just
* ignore while continuing to use the other content.
+ *
+ * MAJOR 2: add devices_file
*/
-#define HINTS_VERSION_MAJOR 1
+#define HINTS_VERSION_MAJOR 2
#define HINTS_VERSION_MINOR 1
#define HINT_LINE_LEN (PATH_MAX + NAME_LEN + ID_LEN + 64)
@@ -701,8 +704,9 @@ static int _read_hint_file(struct cmd_context *cmd, struct dm_list *hints, int *
break;
}
- if (hv_major > HINTS_VERSION_MAJOR) {
- log_debug("ignore hints with newer major version %d.%d", hv_major, hv_minor);
+ if (hv_major != HINTS_VERSION_MAJOR) {
+ log_debug("ignore hints with version %d.%d current %d.%d",
+ hv_major, hv_minor, HINTS_VERSION_MAJOR, HINTS_VERSION_MINOR);
*needs_refresh = 1;
break;
}
@@ -747,6 +751,25 @@ static int _read_hint_file(struct cmd_context *cmd, struct dm_list *hints, int *
continue;
}
+ keylen = strlen("devices_file:");
+ if (!strncmp(_hint_line, "devices_file:", keylen)) {
+ const char *df_hint = _hint_line + keylen;
+ const char *df_config = find_config_tree_str(cmd, devices_devicesfile_CFG, NULL);
+ /* when a devices file is not used, hints should have devices_file:. */
+ if (!cmd->enable_devices_file || !df_hint || !df_config) {
+ if (df_hint[0] != '.') {
+ log_debug("ignore hints with different devices_file: not enabled vs %s", df_hint);
+ *needs_refresh = 1;
+ break;
+ }
+ } else if (strcmp(df_hint, df_config)) {
+ log_debug("ignore hints with different devices_file: %s vs %s", df_hint, df_config);
+ *needs_refresh = 1;
+ break;
+ }
+ continue;
+ }
+
keylen = strlen("devs_hash:");
if (!strncmp(_hint_line, "devs_hash:", keylen)) {
if (sscanf(_hint_line + keylen, "%u %u", &read_hash, &read_count) != 2) {
@@ -818,8 +841,12 @@ static int _read_hint_file(struct cmd_context *cmd, struct dm_list *hints, int *
if (!(iter = dev_iter_create(NULL, 0)))
return 0;
while ((dev = dev_iter_get(cmd, iter))) {
+ if (cmd->enable_devices_file && !get_uid_for_dev(cmd, dev))
+ continue;
+
if (!_dev_in_hint_hash(cmd, dev))
continue;
+
memset(devpath, 0, sizeof(devpath));
strncpy(devpath, dev_name(dev), PATH_MAX);
calc_hash = calc_crc(calc_hash, (const uint8_t *)devpath, strlen(devpath));
@@ -879,6 +906,7 @@ int write_hint_file(struct cmd_context *cmd, int newhints)
struct device *dev;
const char *vgname;
char *filter_str = NULL;
+ const char *config_devices_file = NULL;
uint32_t hash = INITIAL_CRC;
uint32_t count = 0;
time_t t;
@@ -939,6 +967,19 @@ int write_hint_file(struct cmd_context *cmd, int newhints)
fprintf(fp, "scan_lvs:%d\n", cmd->scan_lvs);
+ /*
+ * Only associate hints with the default/system devices file.
+ * If no default/system devices file is used, "." is set.
+ * If we are using a devices file other than the config setting
+ * (from --devicesfile), then we should not be using hints and
+ * shouldn't get here.
+ */
+ config_devices_file = find_config_tree_str(cmd, devices_devicesfile_CFG, NULL);
+ if (cmd->enable_devices_file && !cmd->devicesfile && config_devices_file)
+ fprintf(fp, "devices_file:%s\n", config_devices_file);
+ else
+ fprintf(fp, "devices_file:.\n");
+
/*
* iterate through all devs and write a line for each
* dev flagged DEV_SCAN_FOUND_LABEL
@@ -956,6 +997,14 @@ int write_hint_file(struct cmd_context *cmd, int newhints)
* 2. add PVs to the hint file
*/
while ((dev = dev_iter_get(cmd, iter))) {
+ if (cmd->enable_devices_file && !get_uid_for_dev(cmd, dev)) {
+ if (dev->flags & DEV_SCAN_FOUND_LABEL) {
+ /* should never happen */
+ log_error("skip hint hash no uid but found label %s", dev_name(dev));
+ }
+ continue;
+ }
+
if (!_dev_in_hint_hash(cmd, dev)) {
if (dev->flags & DEV_SCAN_FOUND_LABEL) {
/* should never happen */
diff --git a/lib/label/label.c b/lib/label/label.c
index ed6e0171c..239770090 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;
@@ -911,6 +902,12 @@ static void _prepare_open_file_limit(struct cmd_context *cmd, unsigned int num_d
#endif
}
+/*
+ * Currently the only caller is pvck which probably doesn't need
+ * deferred filters checked after the read... it wants to know if
+ * anything has the pvid, even a dev that might be filtered.
+ */
+
int label_scan_for_pvid(struct cmd_context *cmd, char *pvid, struct device **dev_out)
{
char buf[LABEL_SIZE] __attribute__((aligned(8)));
@@ -923,7 +920,20 @@ 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.
+ */
+ if (!setup_devices(cmd)) {
+ log_error("Failed to set up devices.");
+ return 0;
+ }
+
+ /*
+ * 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 +1016,17 @@ 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();
+ if (!setup_devices(cmd)) {
+ log_error("Failed to set up devices.");
+ return 0;
+ }
/*
* If we know that there will be md components with an end
@@ -1111,6 +1126,12 @@ int label_scan(struct cmd_context *cmd)
_scan_list(cmd, cmd->filter, &scan_devs, NULL);
/*
+ * Check if the devices_file content is up to date and
+ * if not update it.
+ */
+ device_ids_validate(cmd, 0);
+
+ /*
* Metadata could be larger than total size of bcache, and bcache
* cannot currently be resized during the command. If this is the
* case (or within reach), warn that io_memory_size needs to be
@@ -1666,4 +1687,3 @@ void dev_unset_last_byte(struct device *dev)
{
bcache_unset_last_byte(scan_bcache, dev->bcache_fd);
}
-
diff --git a/lib/label/label.h b/lib/label/label.h
index 9a4b630bb..7d0437d16 100644
--- a/lib/label/label.h
+++ b/lib/label/label.h
@@ -111,8 +111,6 @@ void label_scan_invalidate_lv(struct cmd_context *cmd, struct logical_volume *lv
void label_scan_drop(struct cmd_context *cmd);
void label_scan_destroy(struct cmd_context *cmd);
int label_read(struct device *dev);
-int label_read_sector(struct device *dev, uint64_t scan_sector);
-void label_scan_confirm(struct device *dev);
int label_scan_setup_bcache(void);
int label_scan_open(struct device *dev);
int label_scan_open_excl(struct device *dev);
diff --git a/lib/metadata/pv.c b/lib/metadata/pv.c
index 9cebbac01..855d52594 100644
--- a/lib/metadata/pv.c
+++ b/lib/metadata/pv.c
@@ -52,6 +52,20 @@ char *pv_tags_dup(const struct physical_volume *pv)
return tags_format_and_copy(pv->vg->vgmem, &pv->tags);
}
+char *pv_deviceid_dup(struct dm_pool *mem, const struct physical_volume *pv)
+{
+ if (!pv->device_id)
+ return NULL;
+ return dm_pool_strdup(mem, pv->device_id);
+}
+
+char *pv_deviceidtype_dup(struct dm_pool *mem, const struct physical_volume *pv)
+{
+ if (!pv->device_id_type)
+ return NULL;
+ return dm_pool_strdup(mem, pv->device_id_type);
+}
+
const struct format_type *pv_format_type(const struct physical_volume *pv)
{
return pv_field(pv, fmt);
diff --git a/lib/metadata/pv.h b/lib/metadata/pv.h
index efa13e04b..95525a41a 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;
@@ -77,6 +79,8 @@ char *pv_attr_dup(struct dm_pool *mem, const struct physical_volume *pv);
const char *pv_dev_name(const struct physical_volume *pv);
char *pv_uuid_dup(struct dm_pool *mem, const struct physical_volume *pv);
char *pv_tags_dup(const struct physical_volume *pv);
+char *pv_deviceid_dup(struct dm_pool *mem, const struct physical_volume *pv);
+char *pv_deviceidtype_dup(struct dm_pool *mem, const struct physical_volume *pv);
uint64_t pv_size(const struct physical_volume *pv);
uint64_t pv_size_field(const struct physical_volume *pv);
uint64_t pv_dev_size(const struct physical_volume *pv);
diff --git a/lib/report/columns.h b/lib/report/columns.h
index 31a1d1702..116644a0e 100644
--- a/lib/report/columns.h
+++ b/lib/report/columns.h
@@ -203,6 +203,8 @@ FIELD(PVS, pv, SIZ, "BA Start", ba_start, 0, size64, pv_ba_start, "Offset to the
FIELD(PVS, pv, SIZ, "BA Size", ba_size, 0, size64, pv_ba_size, "Size of PV Bootloader Area in current units.", 0)
FIELD(PVS, pv, BIN, "PInUse", id, 0, pvinuse, pv_in_use, "Set if PV is used.", 0)
FIELD(PVS, pv, BIN, "Duplicate", id, 0, pvduplicate, pv_duplicate, "Set if PV is an unchosen duplicate.", 0)
+FIELD(PVS, pv, STR, "DeviceID", id, 0, pvdeviceid, pv_device_id, "Device ID such as the WWID.", 0)
+FIELD(PVS, pv, STR, "DeviceIDType", id, 0, pvdeviceidtype, pv_device_id_type, "Type of device ID such as WWID.", 0)
/*
* End of PVS type fields
*/
diff --git a/lib/report/properties.c b/lib/report/properties.c
index 325750559..83211ed39 100644
--- a/lib/report/properties.c
+++ b/lib/report/properties.c
@@ -183,6 +183,10 @@ GET_PV_NUM_PROPERTY_FN(pv_ba_start, SECTOR_SIZE * pv->ba_start)
#define _pv_ba_start_set prop_not_implemented_set
GET_PV_NUM_PROPERTY_FN(pv_ba_size, SECTOR_SIZE * pv->ba_size)
#define _pv_ba_size_set prop_not_implemented_set
+GET_PV_STR_PROPERTY_FN(pv_device_id, pv->device_id)
+#define _pv_device_id_set prop_not_implemented_set
+GET_PV_STR_PROPERTY_FN(pv_device_id_type, pv->device_id_type)
+#define _pv_device_id_type_set prop_not_implemented_set
#define _pv_allocatable_set prop_not_implemented_set
#define _pv_allocatable_get prop_not_implemented_get
diff --git a/lib/report/report.c b/lib/report/report.c
index 170df6995..18395f478 100644
--- a/lib/report/report.c
+++ b/lib/report/report.c
@@ -3427,6 +3427,42 @@ static int _pvduplicate_disp(struct dm_report *rh, struct dm_pool *mem,
return _binary_disp(rh, mem, field, duplicate, GET_FIRST_RESERVED_NAME(pv_duplicate_y), private);
}
+static int _pvdeviceid_disp(struct dm_report *rh, struct dm_pool *mem,
+ struct dm_report_field *field,
+ const void *data, void *private)
+{
+ const struct physical_volume *pv = (const struct physical_volume *) data;
+ char *repstr;
+
+ if (!pv->device_id)
+ return _field_set_value(field, "", NULL);
+
+ if (!(repstr = pv_deviceid_dup(mem, pv))) {
+ log_error("Failed to allocate buffer.");
+ return 0;
+ }
+
+ return _field_set_value(field, repstr, NULL);
+}
+
+static int _pvdeviceidtype_disp(struct dm_report *rh, struct dm_pool *mem,
+ struct dm_report_field *field,
+ const void *data, void *private)
+{
+ const struct physical_volume *pv = (const struct physical_volume *) data;
+ char *repstr;
+
+ if (!pv->device_id_type)
+ return _field_set_value(field, "", NULL);
+
+ if (!(repstr = pv_deviceidtype_dup(mem, pv))) {
+ log_error("Failed to allocate buffer.");
+ return 0;
+ }
+
+ return _field_set_value(field, repstr, NULL);
+}
+
static int _vgpermissions_disp(struct dm_report *rh, struct dm_pool *mem,
struct dm_report_field *field,
const void *data, void *private)
diff --git a/tools/Makefile.in b/tools/Makefile.in
index 2620daa17..54d67262d 100644
--- a/tools/Makefile.in
+++ b/tools/Makefile.in
@@ -27,6 +27,7 @@ SOURCES =\
lvdisplay.c \
lvextend.c \
lvmcmdline.c \
+ lvmdevices.c \
lvmdiskscan.c \
lvpoll.c \
lvreduce.c \
@@ -58,6 +59,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..13a157ab9 100644
--- a/tools/args.h
+++ b/tools/args.h
@@ -44,6 +44,15 @@ arg(addtag_ARG, '\0', "addtag", tag_VAL, ARG_GROUPABLE, 0,
"Adds a tag to a PV, VG or LV. This option can be repeated to add\n"
"multiple tags at once. See \\fBlvm\\fP(8) for information about tags.\n")
+arg(adddev_ARG, '\0', "adddev", pv_VAL, 0, 0,
+ "Add a device to the devices file.\n")
+arg(deldev_ARG, '\0', "deldev", pv_VAL, 0, 0,
+ "Remove a device from the devices file.\n")
+arg(addpvid_ARG, '\0', "addpvid", string_VAL, 0, 0,
+ "Find a device with the PVID and add the device to the devices file.\n")
+arg(delpvid_ARG, '\0', "delpvid", string_VAL, 0, 0,
+ "Remove a device with the PVID from the devices file.\n")
+
arg(aligned_ARG, '\0', "aligned", 0, 0, 0,
"Use with --separator to align the output columns\n")
@@ -126,6 +135,9 @@ arg(cachepool_ARG, '\0', "cachepool", lv_VAL, 0, 0,
arg(cachevol_ARG, '\0', "cachevol", lv_VAL, 0, 0,
"The name of a cache volume.\n")
+arg(check_ARG, '\0', "check", 0, 0, 0,
+ "Check the content of the devices file.\n")
+
arg(commandprofile_ARG, '\0', "commandprofile", string_VAL, 0, 0,
"The command profile to use for command configuration.\n"
"See \\fBlvm.conf\\fP(5) for more information about profiles.\n")
@@ -199,6 +211,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"
@@ -769,6 +790,9 @@ arg(uncache_ARG, '\0', "uncache", 0, 0, 0,
"Separates a cache pool from a cache LV, and deletes the unused cache pool LV.\n"
"Before the separation, the cache is flushed. Also see --splitcache.\n")
+arg(update_ARG, '\0', "update", 0, 0, 0,
+ "Update the content of the devices file.\n")
+
arg(cachepolicy_ARG, '\0', "cachepolicy", string_VAL, 0, 0,
"Specifies the cache policy for a cache LV.\n"
"See \\fBlvmcache\\fP(7) for more information.\n")
diff --git a/tools/command-lines.in b/tools/command-lines.in
index ed3d0413a..a890b3e1f 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
@@ -1349,6 +1349,31 @@ ID: lvmconfig_general
---
+lvmdevices
+ID: lvmdevices_list
+
+lvmdevices --check
+ID: lvmdevices_check
+
+lvmdevices --update
+ID: lvmdevices_update
+
+lvmdevices --adddev PV
+OO: --deviceidtype String, --deviceid String
+ID: lvmdevices_edit
+
+lvmdevices --deldev PV
+ID: lvmdevices_edit
+
+lvmdevices --addpvid String
+OO: --deviceidtype String, --deviceid String
+ID: lvmdevices_edit
+
+lvmdevices --delpvid String
+ID: lvmdevices_edit
+
+---
+
lvreduce --size NSizeMB LV
OO: --autobackup Bool, --force, --nofsck, --noudevsync,
--reportformat ReportFmt, --resizefs
@@ -1479,7 +1504,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 +1760,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..fefab193a 100644
--- a/tools/commands.h
+++ b/tools/commands.h
@@ -69,6 +69,10 @@ xx(lvmconfig,
"Display and manipulate configuration information",
PERMITTED_READ_ONLY | NO_METADATA_PROCESSING)
+xx(lvmdevices,
+ "Manage the devices file listing devices for LVM to use",
+ 0)
+
xx(lvmdiskscan,
"List devices that may be used as physical volumes",
PERMITTED_READ_ONLY | ENABLE_ALL_DEVS | ALLOW_EXPORTED)
@@ -207,6 +211,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..eab1405e8 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"
@@ -2420,7 +2421,7 @@ static int _get_current_settings(struct cmd_context *cmd)
/*
* enable_hints is set to 1 if any commands are using hints.
- * use_hints is set to 1 if this command doesn't use the hints.
+ * use_hints is set to 0 if this command doesn't use the hints.
* enable_hints=1 and use_hints=0 means that this command won't
* use the hints, but it may invalidate the hints that are used
* by other commands.
@@ -2436,6 +2437,10 @@ static int _get_current_settings(struct cmd_context *cmd)
else
cmd->use_hints = 0;
+ /* The hints file is associated with the default/system devices file. */
+ if (arg_is_set(cmd, devicesfile_ARG))
+ cmd->use_hints = 0;
+
if ((hint_mode = find_config_tree_str(cmd, devices_hints_CFG, NULL))) {
if (!strcmp(hint_mode, "none"))
cmd->enable_hints = 0;
@@ -2477,6 +2482,19 @@ 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)) {
+ const char *devices_file = arg_str_value(cmd, devicesfile_ARG, NULL);
+ if (devices_file && !strlen(devices_file)) {
+ cmd->devicesfile = "";
+ } else if (!devices_file || !validate_name(devices_file)) {
+ log_error("Invalid devices file name.");
+ return EINVALID_CMD_LINE;
+ } else if (!(cmd->devicesfile = dm_pool_strdup(cmd->libmem, devices_file))) {
+ log_error("Failed to copy devices file name.");
+ return EINVALID_CMD_LINE;
+ }
+ }
+
/*
* 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/lvmdevices.c b/tools/lvmdevices.c
new file mode 100644
index 000000000..87e6d9dae
--- /dev/null
+++ b/tools/lvmdevices.c
@@ -0,0 +1,319 @@
+/*
+ * 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"
+
+int lvmdevices(struct cmd_context *cmd, int argc, char **argv)
+{
+ struct device *dev;
+ struct use_id *uid;
+
+ if (!setup_devices_file(cmd))
+ return ECMD_FAILED;
+
+ if (!cmd->enable_devices_file) {
+ log_error("Devices file not enabled.");
+ return ECMD_FAILED;
+ }
+
+ if (arg_is_set(cmd, update_ARG) ||
+ arg_is_set(cmd, adddev_ARG) || arg_is_set(cmd, deldev_ARG) ||
+ arg_is_set(cmd, addpvid_ARG) || arg_is_set(cmd, delpvid_ARG)) {
+ if (!lock_devices_file(cmd, LOCK_EX)) {
+ log_error("Failed to lock the devices file to create.");
+ return ECMD_FAILED;
+ }
+ if (!devices_file_exists(cmd)) {
+ if (!devices_file_touch(cmd)) {
+ log_error("Failed to create the devices file.");
+ return ECMD_FAILED;
+ }
+ }
+
+ /*
+ * The hint file is associated with the default/system devices file,
+ * so don't clear hints when using a different --devicesfile.
+ */
+ if (!cmd->devicesfile)
+ clear_hint_file(cmd);
+ } else {
+ if (!lock_devices_file(cmd, LOCK_SH)) {
+ log_error("Failed to lock the devices file.");
+ return ECMD_FAILED;
+ }
+ if (!devices_file_exists(cmd)) {
+ log_error("Devices file does not exist.");
+ return ECMD_FAILED;
+ }
+ }
+
+ if (!device_ids_read(cmd)) {
+ log_error("Failed to read the devices file.");
+ return ECMD_FAILED;
+ }
+ dev_cache_scan();
+ device_ids_match(cmd);
+
+ if (arg_is_set(cmd, check_ARG)) {
+ /* For each cmd->use_device_ids:
+ reads pvid from header, sets dev->pvid and uid->pvid */
+ label_scan_setup_bcache();
+ device_ids_read_pvids(cmd);
+ goto out;
+ }
+
+ if (arg_is_set(cmd, update_ARG)) {
+ /* For each cmd->use_device_ids:
+ reads pvid from header, sets dev->pvid and uid->pvid */
+ label_scan_setup_bcache();
+ device_ids_read_pvids(cmd);
+
+ /* Any uid fields found/set/fixed will be written. */
+ if (!device_ids_write(cmd))
+ goto_bad;
+ goto out;
+ }
+
+ if (arg_is_set(cmd, adddev_ARG)) {
+ const char *devname;
+
+ if (!(devname = arg_str_value(cmd, adddev_ARG, NULL)))
+ goto_bad;
+
+ /*
+ * addev will add a device to devices_file even if that device
+ * is excluded by filters.
+ */
+
+ /*
+ * No filter applied here (only the non-data filters would
+ * be applied since we haven't read the device yet.
+ */
+ if (!(dev = dev_cache_get(cmd, devname, NULL))) {
+ log_error("No device found for %s.", devname);
+ goto_bad;
+ }
+
+ /*
+ * reads pvid from dev header, sets dev->pvid.
+ * (it's ok if the device is not a PV and has no PVID)
+ */
+ label_scan_setup_bcache();
+ device_id_read_pvid(cmd, dev);
+
+ /*
+ * Allow filtered devices to be added to devices_file, but
+ * check if it's excluded by filters to print a warning.
+ * Since device_id_read_pvid has read the first 4K of the device,
+ * the filters should not for the most part need to do any further
+ * reading of the device.
+ *
+ * (This is the first time filters are being run, so we do
+ * not need to wipe filters of any previous result that was
+ * based on filter_deviceid_skip=0.)
+ */
+ cmd->filter_deviceid_skip = 1;
+ cmd->filter_regex_with_devices_file = 1;
+
+ if (!cmd->filter->passes_filter(cmd, cmd->filter, dev, NULL)) {
+ /* FIXME: print which filters */
+ log_warn("WARNING: %s is currently excluded by filters.", dev_name(dev));
+ }
+
+ if (!device_id_add(cmd, dev, dev->pvid,
+ arg_str_value(cmd, deviceidtype_ARG, NULL),
+ arg_str_value(cmd, deviceid_ARG, NULL)))
+ goto_bad;
+ if (!device_ids_write(cmd))
+ goto_bad;
+ goto out;
+ }
+
+ if (arg_is_set(cmd, addpvid_ARG)) {
+ struct dev_iter *iter;
+ const char *pvid;
+ struct device_list *devl, *safe;
+ struct dm_list devs;
+
+ dm_list_init(&devs);
+
+ /*
+ * Iterate through all devs on the system, reading the
+ * pvid of each to check if it has this pvid.
+ * Devices that are excluded by no-data filters will not
+ * be checked for the PVID.
+ * addpvid will not add a device to devices_file if it's
+ * excluded by filters.
+ */
+
+ pvid = arg_str_value(cmd, addpvid_ARG, NULL);
+
+ if ((uid = get_uid_for_pvid(cmd, pvid))) {
+ log_error("PVID already exists in devices_file for %s.", dev_name(uid->dev));
+ goto bad;
+ }
+
+ /*
+ * Create a list of all devices on the system, without applying
+ * any filters, since we do not want filters to read any of the
+ * devices yet.
+ */
+ if (!(iter = dev_iter_create(NULL, 0)))
+ goto_bad;
+ while ((dev = dev_iter_get(cmd, iter))) {
+ if (!(devl = zalloc(sizeof(*devl))))
+ continue;
+ devl->dev = dev;
+ dm_list_add(&devs, &devl->list);
+ }
+ dev_iter_destroy(iter);
+
+ /*
+ * Apply the filters that do not require reading the devices
+ * (bcache is not set up yet, so any filter that requires
+ * reading the device will return EAGAIN.) The regex filter
+ * will be used and filter-deviceid not used.
+ */
+ log_debug("Filtering devices without data");
+ cmd->filter_deviceid_skip = 1;
+ cmd->filter_regex_with_devices_file = 1;
+ dm_list_iterate_items_safe(devl, safe, &devs) {
+ if (!cmd->filter->passes_filter(cmd, cmd->filter, devl->dev, NULL))
+ dm_list_del(&devl->list);
+ }
+
+ label_scan_setup_bcache();
+ dev = NULL;
+ dm_list_iterate_items(devl, &devs) {
+ if (!device_id_read_pvid(cmd, devl->dev))
+ continue;
+ if (!strcmp(devl->dev->pvid, pvid)) {
+ dev = devl->dev;
+ break;
+ }
+ }
+
+ if (!dev) {
+ log_error("PVID %s not found on any devices.", pvid);
+ goto bad;
+ }
+
+ /*
+ * Now that the device has been read, apply the filters again
+ * which will now include filters that read data from the device.
+ * N.B. we've already skipped devs that were excluded by the
+ * no-data filters, so if the PVID exists on one of those devices
+ * no warning is printed.
+ */
+ log_debug("Filtering device with data");
+ cmd->filter_deviceid_skip = 1;
+ cmd->filter_regex_with_devices_file = 1;
+ cmd->filter->wipe(cmd, cmd->filter, dev, NULL);
+ if (!cmd->filter->passes_filter(cmd, cmd->filter, dev, NULL)) {
+ /* FIXME: print which filters */
+ log_error("PVID %s found on %s which is excluded by filters",
+ pvid, dev_name(dev));
+ goto_bad;
+ }
+
+ if (!device_id_add(cmd, dev, dev->pvid, NULL, NULL))
+ goto_bad;
+ if (!device_ids_write(cmd))
+ goto_bad;
+ goto out;
+ }
+
+ if (arg_is_set(cmd, deldev_ARG)) {
+ const char *devname;
+
+ if (!(devname = arg_str_value(cmd, deldev_ARG, NULL)))
+ goto_bad;
+
+ /* we don't need to filter_deviceid_skip since we're
+ removing a dev from devices_file, that dev should
+ be in the devices_file and pass the filter */
+ if (!(dev = dev_cache_get(cmd, devname, cmd->filter))) {
+ log_error("No device found for %s.", devname);
+ goto bad;
+ }
+
+ /* dev_cache_scan uses sysfs to check if an LV is using each dev
+ and sets this flag is so. */
+ if (dev->flags & DEV_USED_FOR_LV) {
+ if (!arg_count(cmd, yes_ARG) &&
+ yes_no_prompt("Device %s is used by an active LV, continue to remove? ", devname) == 'n') {
+ log_error("Device not removed.");
+ goto bad;
+ }
+ }
+
+ if (!(uid = get_uid_for_dev(cmd, dev))) {
+ log_error("Device not found in devices_file.");
+ goto bad;
+ }
+
+ dm_list_del(&uid->list);
+ free_uid(uid);
+ device_ids_write(cmd);
+ goto out;
+ }
+
+ if (arg_is_set(cmd, delpvid_ARG)) {
+ const char *pvid;
+
+ pvid = arg_str_value(cmd, delpvid_ARG, NULL);
+
+ if (!(uid = get_uid_for_pvid(cmd, pvid))) {
+ log_error("PVID not found in devices_file.");
+ goto_bad;
+ }
+
+ if (uid->devname && (uid->devname[0] != '.')) {
+ if ((dev = dev_cache_get(cmd, uid->devname, cmd->filter)) &&
+ (dev->flags & DEV_USED_FOR_LV)) {
+ if (!arg_count(cmd, yes_ARG) &&
+ yes_no_prompt("Device %s is used by an active LV, continue to remove? ", uid->devname) == 'n') {
+ log_error("Device not removed.");
+ goto bad;
+ }
+ }
+ }
+
+ dm_list_del(&uid->list);
+ free_uid(uid);
+ device_ids_write(cmd);
+ goto out;
+ }
+
+ /* If no options, print use_device_ids list */
+
+ dm_list_iterate_items(uid, &cmd->use_device_ids) {
+ log_print("Device %s IDTYPE=%s IDNAME=%s DEVNAME=%s PVID=%s",
+ uid->dev ? dev_name(uid->dev) : ".",
+ uid->idtype ? idtype_to_str(uid->idtype) : ".",
+ uid->idname ? uid->idname : ".",
+ uid->devname ? uid->devname : ".",
+ uid->pvid ? (char *)uid->pvid : ".");
+ }
+
+out:
+ return ECMD_PROCESSED;
+
+bad:
+ return ECMD_FAILED;
+}
+
diff --git a/tools/pvck.c b/tools/pvck.c
index a0f567eeb..6178e3f5b 100644
--- a/tools/pvck.c
+++ b/tools/pvck.c
@@ -19,6 +19,7 @@
#include "lib/format_text/layout.h"
#include "lib/mm/xlate.h"
#include "lib/misc/crc.h"
+#include "lib/device/device_id.h"
#define ONE_MB_IN_BYTES 1048576
@@ -3032,6 +3033,11 @@ int pvck(struct cmd_context *cmd, int argc, char **argv)
if (arg_is_set(cmd, repairtype_ARG) || arg_is_set(cmd, repair_ARG)) {
pv_name = argv[0];
+ if (!setup_device(cmd, pv_name)) {
+ log_error("Failed to set up device %s.", pv_name);
+ return ECMD_FAILED;
+ }
+
if (!(dev = dev_cache_get(cmd, pv_name, cmd->filter))) {
log_error("No device found for %s %s.", pv_name, dev_cache_filtered_reason(pv_name));
return ECMD_FAILED;
@@ -3041,6 +3047,11 @@ int pvck(struct cmd_context *cmd, int argc, char **argv)
if (arg_is_set(cmd, dump_ARG)) {
pv_name = argv[0];
+ if (!setup_device(cmd, pv_name)) {
+ log_error("Failed to set up device %s.", pv_name);
+ return ECMD_FAILED;
+ }
+
dev = dev_cache_get(cmd, pv_name, cmd->filter);
if (!dev)
diff --git a/tools/pvcreate.c b/tools/pvcreate.c
index 29ae0fa2e..71eb060a3 100644
--- a/tools/pvcreate.c
+++ b/tools/pvcreate.c
@@ -142,6 +142,8 @@ int pvcreate(struct cmd_context *cmd, int argc, char **argv)
clear_hint_file(cmd);
+ cmd->create_edit_devices_file = 1;
+
lvmcache_label_scan(cmd);
if (!(handle = init_processing_handle(cmd, NULL))) {
diff --git a/tools/pvscan.c b/tools/pvscan.c
index 4d811da55..8f630795a 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,11 @@ 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. */
+ if (!setup_devices(cmd)) {
+ log_error("Failed to set up devices.");
+ return ECMD_FAILED;
+ }
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..f6a4ebda1 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>
@@ -5101,15 +5102,57 @@ int pvcreate_each_device(struct cmd_context *cmd,
/*
* Translate arg names into struct device's.
*/
+
+ /*
+ * We allow pvcreate to look outside devices file here to find
+ * the target device, in case the user has not added the device
+ * being pvcreated to the devices file.
+ *
+ * First, wipe the filter to remove the previous result of filtering
+ * the target device that was done during label_scan. The persistent
+ * filter is storing the result of filtering the device when we
+ * were not skipping filter-deviceid.
+ * Then look up the device in dev-cache which will rerun the filters
+ * against the target device, skipping filter-deviceid. The target
+ * device could be excluded by a filter other than deviceid.
+ *
+ * TODO: do we want to add a config setting that would disable this
+ * ability of pvcreate to use devs outside of the devices_file?
+ * i.e. disable the ability to skip filter-deviceid.
+ * 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.
+ */
+ cmd->filter_deviceid_skip = 1;
+
dm_list_iterate_items_safe(pd, pd2, &pp->arg_devices) {
- pd->dev = dev_cache_get(cmd, pd->name, cmd->filter);
- if (!pd->dev) {
- log_print("No device found for %s", pd->name);
+ struct device *new_dev;
+
+ /*
+ * No filter applied here because we first need to wipe the
+ * previous result that was based on using filter-deviceid.
+ */
+ if (!(new_dev = dev_cache_get(cmd, pd->name, NULL))) {
+ log_error("No device found for %s", pd->name);
dm_list_del(&pd->list);
dm_list_add(&pp->arg_fail, &pd->list);
+ continue;
}
+
+ cmd->filter->wipe(cmd, cmd->filter, new_dev, NULL);
+
+ if (!cmd->filter->passes_filter(cmd, cmd->filter, new_dev, NULL)) {
+ log_error("Device %s is excluded by filter.", pd->name);
+ dm_list_del(&pd->list);
+ dm_list_add(&pp->arg_fail, &pd->list);
+ continue;
+ }
+
+ pd->dev = new_dev;
}
+ cmd->filter_deviceid_skip = 0;
+
/*
* Can the command continue if some specified devices were not found?
*/
@@ -5411,6 +5454,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 +5496,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 +5545,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 +5563,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/tools.h b/tools/tools.h
index 7f2434d06..aa7a03ba2 100644
--- a/tools/tools.h
+++ b/tools/tools.h
@@ -29,6 +29,7 @@
#include "lib/config/defaults.h"
#include "lib/device/dev-cache.h"
#include "lib/device/device.h"
+#include "lib/device/device_id.h"
#include "lib/display/display.h"
#include "errors.h"
#include "lib/metadata/metadata-exported.h"
diff --git a/tools/vgcreate.c b/tools/vgcreate.c
index 09b6a6c95..73066e9a4 100644
--- a/tools/vgcreate.c
+++ b/tools/vgcreate.c
@@ -82,6 +82,8 @@ int vgcreate(struct cmd_context *cmd, int argc, char **argv)
return ECMD_FAILED;
}
+ cmd->create_edit_devices_file = 1;
+
lvmcache_label_scan(cmd);
if (lvmcache_vginfo_from_vgname(vp_new.vg_name, NULL)) {
@@ -100,6 +102,8 @@ int vgcreate(struct cmd_context *cmd, int argc, char **argv)
return_ECMD_FAILED;
}
+ unlock_devices_file(cmd);
+
if (!(vg = vg_create(cmd, vp_new.vg_name)))
goto_bad;
diff --git a/tools/vgextend.c b/tools/vgextend.c
index da768981d..73e2e632a 100644
--- a/tools/vgextend.c
+++ b/tools/vgextend.c
@@ -168,6 +168,8 @@ int vgextend(struct cmd_context *cmd, int argc, char **argv)
clear_hint_file(cmd);
+ cmd->edit_devices_file = 1;
+
lvmcache_label_scan(cmd);
if (!(handle = init_processing_handle(cmd, NULL))) {
@@ -182,6 +184,8 @@ int vgextend(struct cmd_context *cmd, int argc, char **argv)
}
}
+ unlock_devices_file(cmd);
+
/*
* It is always ok to add new PVs to a VG - even if there are
* missing PVs. No LVs are affected by this operation, but
diff --git a/tools/vgimportdevices.c b/tools/vgimportdevices.c
new file mode 100644
index 000000000..d70a41f9a
--- /dev/null
+++ b/tools/vgimportdevices.c
@@ -0,0 +1,209 @@
+/*
+ * 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;
+
+ /*
+ * 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 */
+ device_id_add_nodev(cmd, pv->device_id_type, pv->device_id, NULL, (const char *)&pv->id.uuid);
+ 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;
+
+ device_id_add(cmd, pv->dev, (const char *)&pvl->pv->id.uuid, idtypestr, NULL);
+ 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;
+ int ret = ECMD_PROCESSED;
+
+ 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;
+
+ /*
+ * The hint file is associated with the default/system devices file,
+ * so don't clear hints when using a different --devicesfile.
+ */
+ if (!cmd->devicesfile)
+ clear_hint_file(cmd);
+
+ if (!(handle = init_processing_handle(cmd, NULL))) {
+ log_error("Failed to initialize processing handle.");
+ ret = ECMD_FAILED;
+ goto out;
+ }
+ 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.
+ *
+ * Ususally when devices_file is enabled, we use
+ * filter-deviceid and skip filter-regex. In this
+ * import case it's reversed, and we skip filter-deviceid
+ * and use filter-regex.
+ */
+ cmd->filter_deviceid_skip = 1;
+ cmd->filter_regex_with_devices_file = 1;
+ cmd->create_edit_devices_file = 1;
+
+ /*
+ * 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 (!device_ids_write(cmd)) {
+ log_print("Failed to update devices file.");
+ ret = ECMD_FAILED;
+ goto out;
+ }
+
+ log_print("Added %u devices to devices file.", vp.added_devices);
+out:
+ destroy_processing_handle(cmd, handle);
+ return ret;
+}
+