diff options
42 files changed, 3272 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..be5fde6e7 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" @@ -67,11 +68,13 @@ static void _dev_init(struct device *dev) dev->fd = -1; dev->bcache_fd = -1; dev->read_ahead = -1; + dev->part = -1; dev->ext.enabled = 0; 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 +354,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 +395,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 +476,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 +974,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 +1323,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 +1667,203 @@ 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; + } + + unlock_devices_file(cmd); + + 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/dev-type.c b/lib/device/dev-type.c index deb5d6a0f..4ac3a4e76 100644 --- a/lib/device/dev-type.c +++ b/lib/device/dev-type.c @@ -380,6 +380,45 @@ static int _loop_is_with_partscan(struct device *dev) return partscan; } +int dev_get_partition_number(struct device *dev, int *num) +{ + char path[PATH_MAX]; + char buf[8] = { 0 }; + dev_t devt = dev->dev; + struct stat sb; + + if (dev->part != -1) { + *num = dev->part; + return 1; + } + + if (dm_snprintf(path, sizeof(path), "%sdev/block/%d:%d/partition", + dm_sysfs_dir(), (int)MAJOR(devt), (int)MINOR(devt)) < 0) { + log_error("Failed to create sysfs path for %s", dev_name(dev)); + return 0; + } + + if (stat(path, &sb)) { + dev->part = 0; + *num = 0; + return 1; + } + + if (!get_sysfs_value(path, buf, sizeof(buf), 0)) { + log_error("Failed to read sysfs path for %s", dev_name(dev)); + return 0; + } + + if (!buf[0]) { + log_error("Failed to read sysfs partition value for %s", dev_name(dev)); + return 0; + } + + dev->part = atoi(buf); + *num = dev->part; + return 1; +} + /* See linux/genhd.h and fs/partitions/msdos */ #define PART_MAGIC 0xAA55 #define PART_MAGIC_OFFSET UINT64_C(0x1FE) diff --git a/lib/device/dev-type.h b/lib/device/dev-type.h index fdf7791cf..e11b4e70d 100644 --- a/lib/device/dev-type.h +++ b/lib/device/dev-type.h @@ -83,6 +83,7 @@ int dev_is_md_with_end_superblock(struct dev_types *dt, struct device *dev); int major_max_partitions(struct dev_types *dt, int major); int dev_is_partitioned(struct dev_types *dt, struct device *dev); int dev_get_primary_dev(struct dev_types *dt, struct device *dev, dev_t *result); +int dev_get_partition_number(struct device *dev, int *num); /* Various device properties */ unsigned long dev_alignment_offset(struct dev_types *dt, struct device *dev); diff --git a/lib/device/device.h b/lib/device/device.h index bd3b35557..54bfcd541 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,41 @@ 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; + int part; + 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 */ @@ -71,6 +101,7 @@ struct device { int logical_block_size; /* From BLKSSZGET: lowest possible block size that the storage device can address */ int read_ahead; int bcache_fd; + int part; /* partition number */ uint32_t flags; unsigned size_seqno; uint64_t size; diff --git a/lib/device/device_id.c b/lib/device/device_id.c new file mode 100644 index 000000000..08756c8a5 --- /dev/null +++ b/lib/device/device_id.c @@ -0,0 +1,1886 @@ +/* + * 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 "lib/cache/lvmcache.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, *part; + 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; + + /* + * Should only be one case where device_ids_read() + * is called a second time: pvscan --cache -aay + * where the initial pvscan does it, and then + * activation uses process_each_vg which does it. + * + * If we wanted to redo it, we'd need to + * free_uids(&cmd->use_device_ids) and + * clear the MATCHED_USE_ID flag in all dev->flags. + */ + if (!dm_list_empty(&cmd->use_device_ids)) { + log_debug("device_ids_read already done"); + return 1; + } + + log_debug("device_ids_read %s", cmd->devices_file_path); + + 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"); + part = strstr(line, "PART"); + + /* 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]) { + if (buf[0] && (buf[0] != '.')) + uid->idname = strdup(buf); + } + + 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); + } + + if (part) { + _copy_idline_str(part, buf, PATH_MAX); + if (buf[0] && (buf[0] != '.')) + uid->part = atoi(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 dirpath[PATH_MAX]; + char tmpfile[PATH_MAX]; + FILE *fp; + int dir_fd; + 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(dirpath, sizeof(dirpath), "%s/devices", cmd->system_dir) < 0) { + ret = 0; + goto out; + } + + 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; + } + + if ((dir_fd = open(dirpath, O_RDONLY)) < 0) { + fclose(fp); + 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; + + if (uid->part) { + fprintf(fp, "IDTYPE=%s IDNAME=%s DEVNAME=%s PVID=%s PART=%d\n", + idtype_to_str(uid->idtype) ?: ".", + uid->idname ?: ".", devname, pvid, uid->part); + } else { + 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) < 0) { + log_error("Failed to replace devices file errno %d", errno); + ret = 0; + } + + if (fsync(dir_fd) < 0) + stack; + if (close(dir_fd) < 0) + stack; +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->idname && (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); + } else { + 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); + + dev_get_partition_number(dev, &uid->part); + + if (!uid->idname || !uid->idname || !uid->pvid) { + free_uid(uid); + return 0; + } + + 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_deviceid_to_dev(struct cmd_context *cmd, struct use_id *uid, struct device *dev) +{ + struct dev_id *did; + const char *idname; + int part; + + if (!uid->idname || !uid->idtype) + return_0; + + if (!dev_get_partition_number(dev, &part)) + return_0; + if (part != uid->part) + return_0; + + 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_debug("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_debug("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_deviceid_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_deviceid_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) { + log_debug("devices idname %s previously matched %s", + uid->idname, dev_name(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_deviceid_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_deviceid_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("No system device matched to devices file entry with devname %s idtype %s idname %s pvid %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. */ + + /* FIXME: detect when the same device is listed twice and remove + duplicate entries. Could happen if user edits the file directly. */ + + dm_list_iterate_items(uid, &cmd->use_device_ids) { + if (!uid->dev) + continue; + + dev = uid->dev; + + /* + * If this device hasn't been scanned, or is not a PV, then a + * pvid has not been read and there's nothing to validate. + */ + if (!lvmcache_has_dev_info(dev)) + continue; + + /* + * 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 devname id match with wrong device 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 devname id match with unknown device 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; + + /* Defer updates to non-pvscan-cache commands. */ + if (cmd->pvscan_cache_single) { + log_print("pvscan[%d] skip updating devices file.", getpid()); + return; + } + + /* + * 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); + dev_get_partition_number(dev, &uid->part); + } + + 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; + } + } + + /* FIXME: devices with idtype devname that are simply detached from + the system (and not renamed to a new devname) will not be found but + will cause every command to search for it. We do not want a device + that's permanently removed/detached from the system to cause all + future commands to search for it. The user can use lvmdevices to + remove the entry for the removed device, but we should also have an + automatic way to avoid the constant searching. Perhaps add a + SEARCHED=<count> flag to the entry after searching for it, and then + avoid repeated searches, or search just once a minute for up to N + times, and then quit automatic searches. */ + + /* + * 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 */ + if (held) + *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..cab651874 --- /dev/null +++ b/lib/device/device_id.h @@ -0,0 +1,52 @@ +/* + * 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); +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..a82f4d990 --- /dev/null +++ b/tools/lvmdevices.c @@ -0,0 +1,339 @@ +/* + * 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; + struct id id; + char pvid[ID_LEN+1] = { 0 }; + const char *pvid_arg; + 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 = arg_str_value(cmd, addpvid_ARG, NULL); + if (!id_read_format_try(&id, pvid_arg)) { + log_error("Invalid PVID."); + goto bad; + } + memcpy(pvid, &id.uuid, ID_LEN); + + 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)) { + struct id id; + char pvid[ID_LEN+1] = { 0 }; + const char *pvid_arg; + + pvid_arg = arg_str_value(cmd, delpvid_ARG, NULL); + if (!id_read_format_try(&id, pvid_arg)) { + log_error("Invalid PVID."); + goto bad; + } + memcpy(pvid, &id.uuid, ID_LEN); + + 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) { + char part_buf[64] = { 0 }; + + if (uid->part) + snprintf(part_buf, 63, " PART=%d", uid->part); + + log_print("Device %s IDTYPE=%s IDNAME=%s DEVNAME=%s PVID=%s%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 : ".", + part_buf); + } + +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..a1e827bed 100644 --- a/tools/pvscan.c +++ b/tools/pvscan.c @@ -1254,8 +1254,17 @@ 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(); + /* + * Set up device ids. Does not open or read any devices. + * Matches devs in dev-cache to devices file entries. + * Devs that don't match an entry rejected by filter-deviceid, + * in dev_cache_get below which applies filters and returns NULL + * if the requested device name doesn't pass filters. + */ + 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..d23a0130f --- /dev/null +++ b/tools/vgimportdevices.c @@ -0,0 +1,197 @@ +/* + * 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_error("Not importing devices for VG %s with missing PV %s.", + vg->name, (const char *)&pvl->pv->id.uuid); + goto bad; + } + } + + /* + * 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 (!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; +} + |