diff options
-rw-r--r-- | lib/Makefile.in | 2 | ||||
-rw-r--r-- | lib/commands/toolcontext.c | 25 | ||||
-rw-r--r-- | lib/commands/toolcontext.h | 3 | ||||
-rw-r--r-- | lib/config/config_settings.h | 3 | ||||
-rw-r--r-- | lib/config/defaults.h | 2 | ||||
-rw-r--r-- | lib/device/dev-cache.c | 90 | ||||
-rw-r--r-- | lib/device/dev-cache.h | 6 | ||||
-rw-r--r-- | lib/device/device.h | 29 | ||||
-rw-r--r-- | lib/device/device_id.c | 1101 | ||||
-rw-r--r-- | lib/device/device_id.h | 43 | ||||
-rw-r--r-- | lib/filters/filter-deviceid.c | 57 | ||||
-rw-r--r-- | lib/filters/filter-regex.c | 6 | ||||
-rw-r--r-- | lib/filters/filter.h | 1 | ||||
-rw-r--r-- | lib/format_text/export.c | 6 | ||||
-rw-r--r-- | lib/format_text/import_vsn1.c | 34 | ||||
-rw-r--r-- | lib/label/label.c | 37 | ||||
-rw-r--r-- | lib/metadata/pv.h | 2 | ||||
-rw-r--r-- | tools/Makefile.in | 1 | ||||
-rw-r--r-- | tools/args.h | 9 | ||||
-rw-r--r-- | tools/command-lines.in | 17 | ||||
-rw-r--r-- | tools/commands.h | 4 | ||||
-rw-r--r-- | tools/lvmcmdline.c | 6 | ||||
-rw-r--r-- | tools/pvck.c | 3 | ||||
-rw-r--r-- | tools/pvscan.c | 9 | ||||
-rw-r--r-- | tools/toollib.c | 25 | ||||
-rw-r--r-- | tools/vgimportdevices.c | 217 |
26 files changed, 1704 insertions, 34 deletions
diff --git a/lib/Makefile.in b/lib/Makefile.in index 8e50ec45c..3409cbd8c 100644 --- a/lib/Makefile.in +++ b/lib/Makefile.in @@ -29,6 +29,7 @@ SOURCES =\ device/bcache.c \ device/bcache-utils.c \ device/dev-cache.c \ + device/device_id.c \ device/dev-ext.c \ device/dev-io.c \ device/dev-md.c \ @@ -52,6 +53,7 @@ SOURCES =\ filters/filter-usable.c \ filters/filter-internal.c \ filters/filter-signature.c \ + filters/filter-deviceid.c \ format_text/archive.c \ format_text/archiver.c \ format_text/export.c \ diff --git a/lib/commands/toolcontext.c b/lib/commands/toolcontext.c index 63b6811e5..e4e7b6757 100644 --- a/lib/commands/toolcontext.c +++ b/lib/commands/toolcontext.c @@ -1066,7 +1066,14 @@ static int _init_dev_cache(struct cmd_context *cmd) return 1; } -#define MAX_FILTERS 10 +static int _init_device_ids(struct cmd_context *cmd) +{ + dm_list_init(&cmd->use_device_ids); + cmd->devices_file = find_config_tree_str(cmd, devices_devicesfile_CFG, NULL); + return 1; +} + +#define MAX_FILTERS 11 static struct dev_filter *_init_filter_chain(struct cmd_context *cmd) { @@ -1085,6 +1092,9 @@ static struct dev_filter *_init_filter_chain(struct cmd_context *cmd) * sysfs filter. Only available on 2.6 kernels. Non-critical. * Listed first because it's very efficient at eliminating * unavailable devices. + * + * TODO: I suspect that using the lvm_type and device_id + * filters before this one may be more efficient. */ if (find_config_tree_bool(cmd, devices_sysfs_scan_CFG, NULL)) { if ((filters[nr_filt] = sysfs_filter_create())) @@ -1123,6 +1133,13 @@ static struct dev_filter *_init_filter_chain(struct cmd_context *cmd) } nr_filt++; + /* filter based on the device_ids saved in the lvm_devices file */ + if (!(filters[nr_filt] = deviceid_filter_create(cmd))) { + log_error("Failed to create deviceid device filter"); + goto bad; + } + nr_filt++; + /* usable device filter. Required. */ if (!(filters[nr_filt] = usable_filter_create(cmd, cmd->dev_types, FILTER_MODE_NO_LVMETAD))) { log_error("Failed to create usabled device filter"); @@ -1717,6 +1734,9 @@ struct cmd_context *create_toolcontext(unsigned is_clvmd, if (!_init_dev_cache(cmd)) goto_out; + if (!_init_device_ids(cmd)) + return_0; + memlock_init(cmd); if (!_init_formats(cmd)) @@ -1921,6 +1941,9 @@ int refresh_toolcontext(struct cmd_context *cmd) if (!_init_dev_cache(cmd)) return_0; + if (!_init_device_ids(cmd)) + return_0; + if (!_init_formats(cmd)) return_0; diff --git a/lib/commands/toolcontext.h b/lib/commands/toolcontext.h index c09558a42..f8fc33b31 100644 --- a/lib/commands/toolcontext.h +++ b/lib/commands/toolcontext.h @@ -182,13 +182,16 @@ struct cmd_context { unsigned pvscan_recreate_hints:1; /* enable special case hint handling for pvscan --cache */ unsigned scan_lvs:1; unsigned wipe_outdated_pvs:1; + unsigned enable_device_ids:1; /* * Devices and filtering. */ struct dev_filter *filter; struct dm_list hints; + struct dm_list use_device_ids; const char *md_component_checks; + const char *devices_file; /* * Configuration. diff --git a/lib/config/config_settings.h b/lib/config/config_settings.h index 2bb72ba71..316abc548 100644 --- a/lib/config/config_settings.h +++ b/lib/config/config_settings.h @@ -288,6 +288,9 @@ cfg_array(devices_preferred_names_CFG, "preferred_names", devices_CFG_SECTION, C "preferred_names = [ \"^/dev/mpath/\", \"^/dev/mapper/mpath\", \"^/dev/[hs]d\" ]\n" "#\n") +cfg(devices_devicesfile_CFG, "devicesfile", devices_CFG_SECTION, CFG_DEFAULT_COMMENTED, CFG_TYPE_STRING, DEFAULT_DEVICES_FILE, vsn(2, 3, 10), NULL, 0, NULL, + "The file listing devices LVM should use.\n") + cfg_array(devices_filter_CFG, "filter", devices_CFG_SECTION, CFG_DEFAULT_COMMENTED, CFG_TYPE_STRING, "#Sa|.*|", vsn(1, 0, 0), NULL, 0, NULL, "Limit the block devices that are used by LVM commands.\n" "This is a list of regular expressions used to accept or reject block\n" diff --git a/lib/config/defaults.h b/lib/config/defaults.h index be4f5ff7f..894fd2d9e 100644 --- a/lib/config/defaults.h +++ b/lib/config/defaults.h @@ -320,4 +320,6 @@ #define DEFAULT_MD_COMPONENT_CHECKS "auto" +#define DEFAULT_DEVICES_FILE "/etc/lvm/lvm_devices.dat" + #endif /* _LVM_DEFAULTS_H */ diff --git a/lib/device/dev-cache.c b/lib/device/dev-cache.c index c3f7c49be..08ad7ef00 100644 --- a/lib/device/dev-cache.c +++ b/lib/device/dev-cache.c @@ -16,6 +16,7 @@ #include "base/memory/zalloc.h" #include "lib/misc/lib.h" #include "lib/device/dev-type.h" +#include "lib/device/device_id.h" #include "lib/datastruct/btree.h" #include "lib/config/config.h" #include "lib/commands/toolcontext.h" @@ -72,6 +73,7 @@ static void _dev_init(struct device *dev) dev->ext.src = DEV_EXT_NONE; dm_list_init(&dev->aliases); + dm_list_init(&dev->ids); } void dev_destroy_file(struct device *dev) @@ -351,7 +353,7 @@ static int _add_alias(struct device *dev, const char *path) return 1; } -static int _get_sysfs_value(const char *path, char *buf, size_t buf_size, int error_if_no_value) +int get_sysfs_value(const char *path, char *buf, size_t buf_size, int error_if_no_value) { FILE *fp; size_t len; @@ -392,7 +394,7 @@ static int _get_dm_uuid_from_sysfs(char *buf, size_t buf_size, int major, int mi return 0; } - return _get_sysfs_value(path, buf, buf_size, 0); + return get_sysfs_value(path, buf, buf_size, 0); } static struct dm_list *_get_or_add_list_by_index_key(struct dm_hash_table *idx, const char *key) @@ -473,7 +475,7 @@ static struct device *_get_device_for_sysfs_dev_name_using_devno(const char *dev return NULL; } - if (!_get_sysfs_value(path, buf, sizeof(buf), 1)) + if (!get_sysfs_value(path, buf, sizeof(buf), 1)) return_NULL; if (sscanf(buf, "%d:%d", &major, &minor) != 2) { @@ -971,7 +973,7 @@ static int _dev_cache_iterate_sysfs_for_index(const char *path) return r; } -int dev_cache_index_devs(void) +static int dev_cache_index_devs(void) { static int sysfs_has_dev_block = -1; char path[PATH_MAX]; @@ -1320,12 +1322,19 @@ int dev_cache_check_for_open_devices(void) int dev_cache_exit(void) { + struct device *dev; + struct dm_hash_node *n; int num_open = 0; if (_cache.names) if ((num_open = _check_for_open_devices(1)) > 0) log_error(INTERNAL_ERROR "%d device(s) were left open and have been closed.", num_open); + dm_hash_iterate(n, _cache.names) { + dev = (struct device *) dm_hash_get_data(_cache.names, n); + free_dids(&dev->ids); + } + if (_cache.mem) dm_pool_destroy(_cache.mem); @@ -1657,3 +1666,76 @@ bool dev_cache_has_md_with_end_superblock(struct dev_types *dt) return false; } +/* + * Add all system devices to dev-cache, and attempt to + * match all devices_file entries to dev-cache entries. + */ +void setup_devices(struct cmd_context *cmd) +{ + /* + * Read the list of device ids that lvm can use. + * Adds a struct dev_id to cmd->use_device_ids for each one. + * + * (Changing enable_device_ids=0 must ensure that nothing + * in the command thus far has been done on the basis of + * enable_device_ids=1.) + */ + if (!device_ids_read(cmd)) { + log_warn("WARNING: disabling use of devices_file, failed to read file."); + cmd->enable_device_ids = 0; + } + + /* + * Add a 'struct device' to dev-cache for each device available on the system. + * This will not open or read any devices, but may look at sysfs properties. + * This list of devs comes from looking /dev entries, or from asking libudev. + * TODO: or from /proc/partitions? + * + * TODO: dev_cache_scan() optimization: start by looking only at + * devnames listed in the devices_file, and if the device_ids for + * those all match we won't need any others. + * Exceptions: the command wants a new device for pvcreate, or + * device_ids don't match the devnames. + */ + dev_cache_scan(); + + /* + * Match entries from cmd->use_device_ids with device structs in dev-cache. + */ + device_ids_match(cmd); +} + +/* + * Add one system device to dev-cache, and attempt to + * match its dev-cache entry to a devices_file entry. + */ +void setup_device(struct cmd_context *cmd, const char *devname) +{ + struct stat buf; + struct device *dev; + + if (!device_ids_read(cmd)) { + log_warn("WARNING: disabling use of devices_file, failed to read file."); + cmd->enable_device_ids = 0; + } + + if (stat(devname, &buf) < 0) + return; + + if (!S_ISBLK(buf.st_mode)) + return; + + if (!_insert_dev(devname, buf.st_rdev)) + return; + + if (!(dev = (struct device *) dm_hash_lookup(_cache.names, devname))) { + stack; + return; + } + + /* Match this device to an entry in devices_file so it will not + be rejected by filter-deviceid. */ + if (cmd->enable_device_ids) + device_ids_match_dev(cmd, dev); +} + diff --git a/lib/device/dev-cache.h b/lib/device/dev-cache.h index 46c86c27a..ae5a2853b 100644 --- a/lib/device/dev-cache.h +++ b/lib/device/dev-cache.h @@ -34,7 +34,6 @@ struct dev_filter { const char *name; }; -int dev_cache_index_devs(void); struct dm_list *dev_cache_get_dev_list_for_vgid(const char *vgid); struct dm_list *dev_cache_get_dev_list_for_lvid(const char *lvid); @@ -74,4 +73,9 @@ void dev_cache_failed_path(struct device *dev, const char *path); bool dev_cache_has_md_with_end_superblock(struct dev_types *dt); +int get_sysfs_value(const char *path, char *buf, size_t buf_size, int error_if_no_value); + +void setup_devices(struct cmd_context *cmd); +void setup_device(struct cmd_context *cmd, const char *devname); + #endif diff --git a/lib/device/device.h b/lib/device/device.h index bd3b35557..eeb591e6c 100644 --- a/lib/device/device.h +++ b/lib/device/device.h @@ -38,6 +38,7 @@ #define DEV_SCAN_FOUND_LABEL 0x00010000 /* label scan read dev and found label */ #define DEV_IS_MD_COMPONENT 0x00020000 /* device is an md component */ #define DEV_UDEV_INFO_MISSING 0x00040000 /* we have no udev info for this device */ +#define DEV_MATCHED_USE_ID 0x00080000 /* matched an entry from cmd->use_device_ids */ /* * Support for external device info. @@ -56,12 +57,40 @@ struct dev_ext { void *handle; }; +#define DEV_ID_TYPE_SYS_WWID 0x0001 +#define DEV_ID_TYPE_SYS_SERIAL 0x0002 +#define DEV_ID_TYPE_MPATH_UUID 0x0003 +#define DEV_ID_TYPE_DEVNAME 0x0004 +#define DEV_ID_TYPE_LOOP_FILE 0x0005 + +/* A device ID of a certain type for a device. */ + +struct dev_id { + struct dm_list list; + struct device *dev; + uint16_t idtype; + char *idname; +}; + +/* A device listed in devices_file that lvm should use. */ + +struct use_id { + struct dm_list list; + struct device *dev; + uint16_t idtype; + char *idname; + char *devname; + char *pvid; +}; + /* * All devices in LVM will be represented by one of these. * pointer comparisons are valid. */ struct device { struct dm_list aliases; /* struct dm_str_list */ + struct dm_list ids; /* struct dev_id */ + struct dev_id *id; /* points to the ids entry being used for this dev */ dev_t dev; /* private */ diff --git a/lib/device/device_id.c b/lib/device/device_id.c new file mode 100644 index 000000000..d950c28a6 --- /dev/null +++ b/lib/device/device_id.c @@ -0,0 +1,1101 @@ +/* + * Copyright (C) 2020 Red Hat, Inc. All rights reserved. + * + * This file is part of LVM2. + * + * This copyrighted material is made available to anyone wishing to use, + * modify, copy, or redistribute it subject to the terms and conditions + * of the GNU Lesser General Public License v.2.1. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "base/memory/zalloc.h" +#include "lib/misc/lib.h" +#include "lib/commands/toolcontext.h" +#include "lib/device/device.h" +#include "lib/device/device_id.h" +#include "lib/device/dev-type.h" +#include "lib/device/device-types.h" + +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <time.h> +#include <sys/types.h> +#include <sys/file.h> +#include <sys/sysmacros.h> + +void free_uid(struct use_id *uid) +{ + if (uid->idname) + free(uid->idname); + if (uid->devname) + free(uid->devname); + if (uid->pvid) + free(uid->pvid); + free(uid); +} + +void free_uids(struct dm_list *uids) +{ + struct use_id *uid, *safe; + + dm_list_iterate_items_safe(uid, safe, uids) { + dm_list_del(&uid->list); + free_uid(uid); + } +} + +void free_did(struct dev_id *did) +{ + if (did->idname) + free(did->idname); + free(did); +} + +void free_dids(struct dm_list *dids) +{ + struct dev_id *did, *safe; + + dm_list_iterate_items_safe(did, safe, dids) { + dm_list_del(&did->list); + free_did(did); + } +} + +static int _read_sys_block(struct cmd_context *cmd, struct device *dev, const char *suffix, const char **idname) +{ + char path[PATH_MAX]; + char buf[PATH_MAX] = { 0 }; + dev_t devt = dev->dev; + dev_t prim = 0; + int ret; + + retry: + if (dm_snprintf(path, sizeof(path), "%sdev/block/%d:%d/%s", + dm_sysfs_dir(), (int)MAJOR(devt), (int)MINOR(devt), suffix) < 0) { + return 0; + } + + get_sysfs_value(path, buf, sizeof(buf), 0); + + if (buf[0]) { + if (prim) + log_debug("Using primary device_id for partition %s.", dev_name(dev)); + if (!(*idname = strdup(buf))) + return 0; + return 1; + } + + if (prim) + goto fail; + + /* in case it failed because dev is a partition... */ + + ret = dev_get_primary_dev(cmd->dev_types, dev, &prim); + if (ret == 2) { + devt = prim; + goto retry; + } + + fail: + *idname = NULL; + return 1; +} + +static int _read_sys_wwid(struct cmd_context *cmd, struct device *dev, const char **idname) +{ + return _read_sys_block(cmd, dev, "device/wwid", idname); +} + +static int _read_sys_serial(struct cmd_context *cmd, struct device *dev, const char **idname) +{ + return _read_sys_block(cmd, dev, "device/serial", idname); +} + +/* the dm uuid uses the wwid of the underlying dev */ + +static int _read_mpath_uuid(struct cmd_context *cmd, struct device *dev, const char **idname) +{ + return _read_sys_block(cmd, dev, "dm/uuid", idname); +} + +static int _dev_has_mpath_uuid(struct cmd_context *cmd, struct device *dev, const char **idname) +{ + dev_t devt = dev->dev; + dev_t prim; + int ret; + + ret = dev_get_primary_dev(cmd->dev_types, dev, &prim); + if (ret == 2) + devt = prim; + + if (MAJOR(devt) != cmd->dev_types->device_mapper_major) + return 0; + + _read_mpath_uuid(cmd, dev, idname); + + if (*idname) + return 1; + return 0; +} + +static int _read_loop_file(struct cmd_context *cmd, struct device *dev, const char **idname) +{ + return _read_sys_block(cmd, dev, "loop/backing_file", idname); +} + +/* + * TODO: should there be a list like lvm.conf + * device_id_types = [ "sys_wwid", "sys_serial" ] + * that controls which idtype's will be used? + * + * TODO: add a type for md devices, probably have it + * use the uuid from the md dev superblock. This would + * help in case of inconsistent md dev names, but would + * not help in case md components were all cloned. + * + * TODO: should we include some partition number information + * for the entry when we use the primary wwid for multiple + * partitions? + */ +static const char *_device_id_system_read(struct cmd_context *cmd, struct device *dev, uint16_t idtype) +{ + const char *idname = NULL; + + if (idtype == DEV_ID_TYPE_SYS_WWID) + _read_sys_wwid(cmd, dev, &idname); + + else if (idtype == DEV_ID_TYPE_SYS_SERIAL) + _read_sys_serial(cmd, dev, &idname); + + else if (idtype == DEV_ID_TYPE_DEVNAME) + idname = strdup(dev_name(dev)); + + else if (idtype == DEV_ID_TYPE_MPATH_UUID) + _read_mpath_uuid(cmd, dev, &idname); + + else if (idtype == DEV_ID_TYPE_LOOP_FILE) + _read_loop_file(cmd, dev, &idname); + + return idname; +} + +const char *idtype_to_str(uint16_t idtype) +{ + if (idtype == DEV_ID_TYPE_SYS_WWID) + return "sys_wwid"; + + if (idtype == DEV_ID_TYPE_SYS_SERIAL) + return "sys_serial"; + + if (idtype == DEV_ID_TYPE_DEVNAME) + return "devname"; + + if (idtype == DEV_ID_TYPE_MPATH_UUID) + return "mpath_uuid"; + + if (idtype == DEV_ID_TYPE_LOOP_FILE) + return "loop_file"; + + return "unknown"; +} + +uint16_t idtype_from_str(const char *str) +{ + if (!strcmp(str, "sys_wwid")) + return DEV_ID_TYPE_SYS_WWID; + if (!strcmp(str, "sys_serial")) + return DEV_ID_TYPE_SYS_SERIAL; + if (!strcmp(str, "devname")) + return DEV_ID_TYPE_DEVNAME; + if (!strcmp(str, "mpath_uuid")) + return DEV_ID_TYPE_MPATH_UUID; + if (!strcmp(str, "loop_file")) + return DEV_ID_TYPE_LOOP_FILE; + return 0; +} + +const char *dev_idtype(struct device *dev) +{ + if (!dev || !dev->id) + return NULL; + + return idtype_to_str(dev->id->idtype); +} + +const char *dev_id(struct device *dev) +{ + if (dev && dev->id) + return dev->id->idname; + return NULL; +} + +static void _copy_idline_str(char *src, char *dst, int len) +{ + char *s, *d = dst; + + memset(dst, 0, len); + + if (!(s = strchr(src, '='))) + return; + s++; + while ((*s == ' ') && (s < src + len)) + s++; + while ((*s != ' ') && (*s != '\0') && (*s != '\n') && (s < src + len)) { + *d = *s; + s++; + d++; + } +} + +int device_ids_read(struct cmd_context *cmd) +{ + char line[PATH_MAX]; + char buf[PATH_MAX]; + char *idtype, *idname, *devname, *pvid; + struct use_id *uid; + FILE *fp; + int fl_fd, fl_err = -1; + int ret = 1; + + /* + * TODO: allow the use_device_ids list to come from a + * command line option instead of devices_file? + * If so, add use_id structs to use_device_ids based + * on the reading the command line args here. + */ + + if (!cmd->enable_device_ids) + return 1; + + free_uids(&cmd->use_device_ids); + + if (cmd->nolocking) + goto use_file; + + if ((fl_fd = open(cmd->devices_file, O_RDWR|O_CREAT)) < 0) { + log_warn("Cannot open devices_file to flock."); + goto use_file; + } + if ((fl_err = flock(fl_fd, LOCK_SH))) { + log_warn("Cannot lock devices_file to read."); + close(fl_fd); + } + +use_file: + if (!(fp = fopen(cmd->devices_file, "r"))) { + log_warn("Cannot open devices_file to read."); + ret = 0; + goto out; + } + + while (fgets(line, sizeof(line), fp)) { + if (line[0] == '#') + continue; + + idtype = strstr(line, "IDTYPE"); + idname = strstr(line, "IDNAME"); + devname = strstr(line, "DEVNAME"); + pvid = strstr(line, "PVID"); + + /* These two are the minimum required. */ + if (!idtype || !idname) + continue; + + if (!(uid = zalloc(sizeof(struct use_id)))) + return 0; + + _copy_idline_str(idtype, buf, PATH_MAX); + if (buf[0]) + uid->idtype = idtype_from_str(buf); + + _copy_idline_str(idname, buf, PATH_MAX); + if (buf[0]) + uid->idname = strdup(buf); + + if (!uid->idtype || !uid->idname) { + log_print("Ignoring device: %s", line); + free_uid(uid); + continue; + } + + if (devname) { + _copy_idline_str(devname, buf, PATH_MAX); + if (buf[0] && (buf[0] != '.')) + uid->devname = strdup(buf); + } + + if (pvid) { + _copy_idline_str(pvid, buf, PATH_MAX); + if (buf[0] && (buf[0] != '.')) + uid->pvid = strdup(buf); + } + + dm_list_add(&cmd->use_device_ids, &uid->list); + } + + if (fclose(fp)) + stack; +out: + if (!cmd->nolocking && !fl_err) { + if (flock(fl_fd, LOCK_UN)) + stack; + if (close(fl_fd)) + stack; + } + return ret; +} + +int device_ids_write(struct cmd_context *cmd) +{ + FILE *fp; + time_t t; + struct use_id *uid; + const char *devname; + const char *pvid; + int fl_fd, fl_err = -1; + int ret = 1; + + if (!cmd->enable_device_ids) + return 1; + + if (cmd->nolocking) + goto use_file; + if ((fl_fd = open(cmd->devices_file, O_RDWR|O_CREAT)) < 0) { + log_warn("Cannot open devices_file to flock."); + goto use_file; + } + if ((fl_err = flock(fl_fd, LOCK_EX))) { + log_warn("Cannot lock devices_file to write."); + close(fl_fd); + } + +use_file: + if (!(fp = fopen(cmd->devices_file, "w"))) { + log_warn("Cannot open devices_file to write."); + ret = 0; + goto out; + } + + t = time(NULL); + + fprintf(fp, "# LVM will use devices listed in this file.\n"); + fprintf(fp, "# IDTYPE and IDNAME fields are required, the DEVNAME path may change.\n"); + fprintf(fp, "# Created by LVM command %s pid %d at %s\n", cmd->name, getpid(), ctime(&t)); + + dm_list_iterate_items(uid, &cmd->use_device_ids) { + devname = uid->dev ? dev_name(uid->dev) : uid->devname; + if (!devname || devname[0] != '/') + devname = "."; + + if (!uid->pvid || !uid->pvid[0] || (uid->pvid[0] == '.')) + pvid = "."; + else + pvid = uid->pvid; + + fprintf(fp, "IDTYPE=%s IDNAME=%s DEVNAME=%s PVID=%s\n", + idtype_to_str(uid->idtype) ?: ".", + uid->idname ?: ".", devname, pvid); + } + + if (fflush(fp)) + stack; + if (fclose(fp)) + stack; + +out: + if (!cmd->nolocking && !fl_err) { + if (flock(fl_fd, LOCK_UN)) + stack; + if (close(fl_fd)) + stack; + } + return ret; +} + +static struct use_id *_get_uid_for_dev(struct cmd_context *cmd, struct device *dev) +{ + struct use_id *uid; + + dm_list_iterate_items(uid, &cmd->use_device_ids) { + if (uid->dev == dev) + return uid; + } + return NULL; +} + +static struct use_id *_get_uid_for_pvid(struct cmd_context *cmd, const char *pvid) +{ + struct use_id *uid; + + dm_list_iterate_items(uid, &cmd->use_device_ids) { + if (!uid->pvid) + continue; + if (!strcmp(uid->pvid, pvid)) + return uid; + } + return NULL; +} + +static struct use_id *_get_uid_for_devname(struct cmd_context *cmd, const char *devname) +{ + struct use_id *uid; + + dm_list_iterate_items(uid, &cmd->use_device_ids) { + if (!uid->devname) + continue; + if (!strcmp(uid->devname, devname)) + return uid; + } + return NULL; +} + +static struct use_id *_get_uid_for_device_id(struct cmd_context *cmd, uint16_t idtype, const char *idname) +{ + struct use_id *uid; + + dm_list_iterate_items(uid, &cmd->use_device_ids) { + if ((uid->idtype == idtype) && !strcmp(uid->idname, idname)) + return uid; + } + return NULL; +} + +/* + * Add or update entry for this dev. + * IDTYPE=sys_wwid IDNAME=01234566 DEVNAME=/dev/sdb PVID=99393939 [OPTS=xx,yy,zz] + * + * add an entry to dev->ids and point dev->id to it. + * add or update entry in cmd->use_device_ids + */ +int device_id_add(struct cmd_context *cmd, struct device *dev, const char *pvid, + const char *idtype_arg, const char *id_arg) +{ + uint16_t idtype = 0; + const char *idname; + struct use_id *uid = NULL, *uid_pvid, *uid_devname, *uid_devid; + struct dev_id *did; + int found_did = 0; + + if (!cmd->enable_device_ids) + return 1; + + uid_pvid = _get_uid_for_pvid(cmd, pvid); + uid_devname = _get_uid_for_devname(cmd, dev_name(dev)); + + /* TODO: should deviceidtype command line option work for mpath/loop? */ + /* TODO: add more idtypes for special devs (e.g. MD, DRBD, NBD) that don't have wwid */ + + if (_dev_has_mpath_uuid(cmd, dev, &idname)) { + idtype = DEV_ID_TYPE_MPATH_UUID; + goto id_done; + } + + if (MAJOR(dev->dev) == cmd->dev_types->loop_major) { + idtype = DEV_ID_TYPE_LOOP_FILE; + goto id_name; + } + + /* + * First use type specified by user option, then use a previous + * type, then use the default type. + * TODO: allow lvm.conf device_id_types to control idtypes used here? + */ + + if (idtype_arg) { + if (!(idtype = idtype_from_str(idtype_arg))) + log_warn("WARNING: ignoring unknown device_id type %s.", idtype_arg); + else { + if (id_arg) { + idname = id_arg; + goto id_done; + } + goto id_name; + } + } + + /* If there's an entry for this pvid, use the idtype from that */ + if (!idtype && uid_pvid) { + idtype = uid_pvid->idtype; + goto id_name; + } + + idtype = DEV_ID_TYPE_SYS_WWID; + +id_name: + if (!(idname = _device_id_system_read(cmd, dev, idtype))) { + if (idtype == DEV_ID_TYPE_SYS_WWID) { + idtype = DEV_ID_TYPE_SYS_SERIAL; + goto id_name; + } + idtype = DEV_ID_TYPE_DEVNAME; + goto id_name; + } + +id_done: + dm_list_iterate_items(did, &dev->ids) { + if (did->idtype == idtype) { + found_did = 1; + break; + } + } + + if (found_did && !strcmp(did->idname, idname)) + free((char *)idname); + else if (found_did && strcmp(did->idname, idname)) { + dm_list_del(&did->list); + free_did(did); + found_did = 0; + } + + if (!found_did) { + if (!(did = zalloc(sizeof(struct dev_id)))) + return_0; + did->idtype = idtype; + did->idname = (char *)idname; + did->dev = dev; + dm_list_add(&dev->ids, &did->list); + } + + dev->id = did; + dev->flags |= DEV_MATCHED_USE_ID; + + uid_devid = _get_uid_for_device_id(cmd, did->idtype, did->idname); + + /* + * This new entry could potentially overlap three existing entries + * with matching pvid, device_id, and devname. + */ + if (uid_pvid) { + /* update the existing entry with matching pvid */ + uid = uid_pvid; + dm_list_del(&uid->list); + + log_print("Updating existing device entry for PVID"); + + if (uid_devid && (uid_devid != uid_pvid)) { + /* warn about another entry using the same device_id */ + log_warn("WARNING: duplicate device_id %s for PVIDs %s %s", + uid_devid->idname, uid_devid->pvid, uid_pvid->pvid); + } + + if (uid_devname && (uid_devname != uid_pvid)) { + /* clear devname in another entry with our devname */ + log_print("Clearing stale devname %s for PVID %s", + uid_devname->devname, uid_devname->pvid); + free(uid_devname->devname); + uid_devname->devname = NULL; + } + + } else if (uid_devid) { + /* update the existing entry with matching devid */ + uid = uid_devid; + dm_list_del(&uid->list); + + log_print("Updating existing device entry for device_id"); + + /* FIXME: what about two devs with two pvids having the same devid? + we want two entries, one for each pvid, each having the same devid. + we need to verify both devs have same devid, and both devs have + different pvids by reading them. if one of the entries is not + found anywhere, then remove it? */ + /* FIXME: if overwriting existing pvid? + we want to replace pvid in existing entry */ + + if (uid_devname && (uid_devname != uid_devid)) { + /* clear devname in another entry with our devname */ + log_print("Clearing stale devname %s for PVID %s", + uid_devname->devname, uid_devname->pvid); + free(uid_devname->devname); + uid_devname->devname = NULL; + } + + } else if (uid_devname) { + /* clear devname in another entry with our devname */ + log_print("Clearing stale devname %s for PVID %s", + uid_devname->devname, uid_devname->pvid); + free(uid_devname->devname); + uid_devname->devname = NULL; + } + + if (!uid) { + if (!(uid = zalloc(sizeof(struct use_id)))) + return_0; + } + + if (uid->idname) + free(uid->idname); + if (uid->devname) + free(uid->devname); + if (uid->pvid) + free(uid->pvid); + + uid->idtype = did->idtype; + uid->idname = strdup(did->idname); + uid->devname = strdup(dev_name(dev)); + uid->dev = dev; + uid->pvid = strdup(pvid); + + if (!uid->idname || !uid->idname || !uid->pvid) { + free_uid(uid); + return 0; + } + + log_print("Add %s %s PVID %s", dev_name(dev), uid->idname, uid->pvid); + + dm_list_add(&cmd->use_device_ids, &uid->list); + + return 1; +} + +/* + * Add an entry when there is no current device for it. + * The known info, e.g. from metadata, is used to create + * the entry. + * devname arg could be wrong since there's no dev + */ +int device_id_add_nodev(struct cmd_context *cmd, + const char *idtype_str, + const char *idname, + const char *devname, + const char *pvid) +{ + struct use_id *uid; + uint16_t idtype = 0; + + if (!cmd->enable_device_ids) + return 1; + + if (!pvid || (pvid[0] == '.')) + return 0; + + if (!idtype_str || !idname) + return 0; + + if (idtype_str) + idtype = idtype_from_str(idtype_str); + + if (!(uid = _get_uid_for_pvid(cmd, pvid))) { + if (!(uid = zalloc(sizeof(struct use_id)))) + return_0; + } + + if (uid->idtype && (uid->idtype != idtype)) { + log_print("Changing device_id_type from %s to %s for %s", + idtype_to_str(uid->idtype), idtype_to_str(idtype), devname); + } + if (uid->idtype && (uid->idtype == idtype) && strcmp(uid->idname, idname)) { + log_print("Changing device_id from %s to %s for %s", + uid->idname, idname, devname); + } + + if (uid->idname) + free(uid->idname); + if (uid->devname) + free(uid->devname); + if (uid->pvid) + free(uid->pvid); + uid->idname = NULL; + uid->devname = NULL; + uid->pvid = NULL; + uid->dev = NULL; + + uid->idtype = idtype; + + if (pvid) + uid->pvid = strdup(pvid); + if (idname) + uid->idname = strdup(idname); + if (devname) + uid->devname = strdup(devname); + + log_print("Add %s %s %s", devname ?: ".", uid->idname ?: ".", uid->pvid); + + dm_list_add(&cmd->use_device_ids, &uid->list); + + return 1; +} + +/* + * Update entry for this dev. + * Set PVID=. + * update entry in cmd->use_device_ids + */ +void device_id_pvremove(struct cmd_context *cmd, struct device *dev) +{ + struct use_id *uid; + + if (!cmd->enable_device_ids) + return; + + if (!(uid = _get_uid_for_dev(cmd, dev))) { + log_warn("WARNING: use_device_ids does not include %s", dev_name(dev)); + return; + } + + if (uid->pvid) { + free(uid->pvid); + uid->pvid = NULL; + } +} + +/* + * check for dev->ids entry with uid->idtype, if found compare it, + * if not, system_read of this type and add entry to dev->ids, compare it. + * When a match is found, set up links among uid/did/dev. + */ + +static int _match_uid_to_dev(struct cmd_context *cmd, struct use_id *uid, struct device *dev) +{ + struct dev_id *did; + const char *idname; + + dm_list_iterate_items(did, &dev->ids) { + if (did->idtype == uid->idtype) { + if (did->idname && !strcmp(did->idname, uid->idname)) { + uid->dev = dev; + dev->id = did; + dev->flags |= DEV_MATCHED_USE_ID; + return 1; + } else { + return_0; + } + } + } + + if (!(did = zalloc(sizeof(struct dev_id)))) + return_0; + + if (!(idname = _device_id_system_read(cmd, dev, uid->idtype))) { + /* Save a new did in dev->ids for this type to indicate no match + to avoid repeated system_read, since this called many times. + Setting idtype and NULL idname means no id of this type. */ + did->idtype = uid->idtype; + did->dev = dev; + dm_list_add(&dev->ids, &did->list); + return 0; + } + + /* Save this id for the device (so it can be quickly checked again), even + if it's not the idtype used to identify the dev in device_id_file. */ + did->idtype = uid->idtype; + did->idname = (char *)idname; + did->dev = dev; + dm_list_add(&dev->ids, &did->list); + + if (!strcmp(idname, uid->idname)) { + uid->dev = dev; + dev->id = did; + dev->flags |= DEV_MATCHED_USE_ID; + return 1; + } + + return 0; +} + +int device_ids_match_dev(struct cmd_context *cmd, struct device *dev) +{ + struct use_id *uid; + + /* First check the uid entry with matching devname since it's likely correct. */ + if ((uid = _get_uid_for_devname(cmd, dev_name(dev)))) { + if (_match_uid_to_dev(cmd, uid, dev)) + return 1; + } + + /* Check all uid entries since the devname could have changed. */ + dm_list_iterate_items(uid, &cmd->use_device_ids) { + if (!_match_uid_to_dev(cmd, uid, dev)) + continue; + return 1; + } + + return 0; +} + +/* + +pvid is needed in the devices_file, and wwid (device_id more generally) +is needed in metadata in order to handle cases where a device has no wwid +or the wwid changes. In these cases the correct set of devices can be +found and the devices_file can be corrected. (A wwid in the metadata will +also eliminate the problem of duplicate pvs for those devices.) + +Three identifiers: wwid, devname, pvid +- devname can change, cannot be duplicated, cannot be unknown +- wwid can change (rare), can be duplicated (rare), can be unknown +- pvid cannot change, can be duplicated, cannot be unknown + +(wwid is more generally the device_id, and would only change or +be duplicated when the device_id is not a wwid but some other +identifier used when wwid is not available.) + + +if devname changes +------------------ +. if wwid exists, lvm corrects devname (by reading wwid of all devices) +. if no wwid exists for the entry in devices_file, and new devname is + out of the devices_file, then PV appears missing. lvm would need + to read headers from all devices on the system to find the PVID, or + the user could run "lvmdevices --addpvid <pvid>" to read device headers + to find PVID. When found, devices_file is updated. +. if no wwid, and the new devname is in devices_file, then lvm will + find the PV on the new devname during normal scan, and update the + devices_file. + + +if wwid changes +--------------- +. same underlying storage, different wwid reported for it + +. if the new wwid is not used in another devices_file entry + devices_file: WWID=XXX DEVNAME=/dev/foo PVID=AAA + after reboot the wwid for the device changes to YYY + YYY does not appear in devices_file or lvm metadata, so lvm doesn't know to scan + the dev with that wwid + device_ids_match() will find no dev with XXX, so uid->dev will be null + the PV appears to be missing + user runs a cmd to scan all devnames on the system (outside devices_file) + to find a device with pvid AAA. when it's found, the cmd updates devices_file + entry to have WWID=YYY PVID=AAA. "lvmdevices --addpvid AAA" + (if the devname for this entry remained unchanged, then lvm could likely + avoid scanning all devs, by just scanning /dev/foo and finding AAA, + this is basically an optimization that could be applied automatically + and might avoid requiring the user to run a cmd to find the pv) + +. new wwid value is included in devices_file for a different PV, + causing duplicate wwids + devices_file: WWID=XXX DEVNAME=/dev/foo PVID=AAA + WWID=YYY DEVNAME=/dev/bar PVID=BBB + after reboot the wwid for the first device changes to YYY + device_ids_match() will see two devs with wwid YYY + lvm will scan both devices and find one with AAA and the other BBB + lvm will update devices_file to have WWID=YYY for both devs + lvm may suggest using a different idtype if that would help + +. two wwids in devices_file are swapped + devices_file: WWID=XXX DEVNAME=/dev/foo PVID=AAA + WWID=YYY DEVNAME=/dev/bar PVID=BBB + the wwid for AAA changes to YYY + the wwid for BBB changes to XXX + device_ids_match() will seem to be ok (possibly complain about devnames) + both devs will be scanned by label_scan + device_ids_validate() will see different pvids for each entry + and will update devices_file + + +if pvid and wwid are both duplicated +------------------------------------ +. devices_file: WWID=XXX DEVNAME=/dev/foo PVID=AAA + new state: + WWID=XXX DEVNAME=/dev/foo PVID=AAA + WWID=XXX DEVNAME=/dev/bar PVID=AAA + This would fall back to the old duplicate device handling. + We would need to keep the old duplicate pv handling to handle + this case. + If the wwid originates from data blocks on the storage, + then this can easily happen by cloning the disks. + + +if wwids begin as duplicates +---------------------------- +. lvm can still use the wwid for filtering, + but it won't help if pvid is also duplicated + + +if pvid is duplicated but wwid is not +------------------------------------- +. lvm will use the wwid to choose the right one + + +if wwid is unknown +------------------ +. and no other unique device_id is available +. this would work similarly to the old filter accepting only a list of devnames +. if devname changes and new devname in list, lvm fixes devname +. if devname changes and new devname out of filter, pv missing, then + either automatically read all devs to find pvid, or have user run cmd to do this + (lvmdevices --addpvid <pvid> would read every device on the system for header with pvid, + and if found update devices_file with the new devname/pvid) +*/ + +/* + * For each entry on cmd->use_device_ids, find a struct device from dev-cache. + * This must not open or read devices. filters are applied after this, + * and they may open devs in the first filter stage. The second filtering + * stage, done as a part of label_scan, is finally allowed to read devices. + * + * When a device id of a particular type is read for a dev, a did for that + * type is saved in dev->ids in case it needs to be checked again. + * + * When a particular dev_id for a dev (in dev-cache) is matched to a use_dev + * (from use_device_ids), then: + * . uid->dev = dev; + * . dev->id = did; + * . dev->flags |= DEV_MATCHED_USE_ID; + */ + +void device_ids_match(struct cmd_context *cmd) +{ + struct dev_iter *iter; + struct use_id *uid; + struct device *dev; + + if (!cmd->enable_device_ids) + return; + + dm_list_iterate_items(uid, &cmd->use_device_ids) { + /* already matched */ + if (uid->dev && (uid->dev->flags & DEV_MATCHED_USE_ID)) + continue; + + /* + * uid->devname may be incorrect, but it's often correct, so it's the + * most efficient place to begin. + */ + if (uid->devname && + (dev = dev_cache_get(cmd, uid->devname, NULL))) { + /* On success, device_id_match_uid_to_dev() links the uid, dev, and did. */ + if (_match_uid_to_dev(cmd, uid, dev)) + continue; + else { + /* uid->devname now belongs to a different device */ + log_print("Device with name %s has changed.", uid->devname); + } + } + + /* At a minimum some devname needs to be added or updated. + device_ids_validate may find other reasons to update the + file. Are there commands where device_ids_validate would + not be run, so we should update the file here? */ + + /* + * Iterate through all devs and try to match uid. + * + * A filter is not used when iterating since this is not selecting + * devs to use. The next dev iteration in label scan does use filters. + * The device_id matches created here are used by filter-deviceid later. + * (Might we apply a couple simple filters here, though, to avoid + * doing some pointless match attempts?) + * + * If a match is made here it means the uid->devname is wrong so the + * device_id file should be udpated with a new devname. + */ + if (!(iter = dev_iter_create(NULL, 0))) + continue; + while ((dev = dev_iter_get(cmd, iter))) { + if (dev->flags & DEV_MATCHED_USE_ID) + continue; + if (_match_uid_to_dev(cmd, uid, dev)) + break; + } + dev_iter_destroy(iter); + } + + /* + * Look for entries in devices_file for which we found no device. + */ + dm_list_iterate_items(uid, &cmd->use_device_ids) { + if (uid->dev && (uid->dev->flags & DEV_MATCHED_USE_ID)) + continue; + + if (uid->dev && !(uid->dev->flags & DEV_MATCHED_USE_ID)) { + /* FIXME: is this possible? */ + log_error("Device %s not matched to device_id", dev_name(uid->dev)); + } + + log_print("Device with previous name %s not found with %s %s PVID %s.", + uid->devname, idtype_to_str(uid->idtype), uid->idname, uid->pvid); + } +} + +/* + * This is called after label_scan() to compare what was found on disks + * vs what's in the devices_file. The devices_file could be outdated + * and need correcting; the authoritative data is what's on disk. + * Now that we have read the device labels in label_scan and have the PVID's + * we can check the pvid's of use_device_ids entries from the device_id_file. + */ +void device_ids_validate(struct cmd_context *cmd) +{ + struct use_id *uid; + int update_file = 0; + + if (!cmd->enable_device_ids) + return; + + dm_list_iterate_items(uid, &cmd->use_device_ids) { + if (!uid->dev) + continue; + + if (uid->dev->pvid[0] && (!uid->pvid || strcmp(uid->dev->pvid, uid->pvid))) { + log_print("Device %s has updated PVID %s from devices_file (was %s)", + dev_name(uid->dev), uid->dev->pvid, uid->pvid); + if (uid->pvid) + free(uid->pvid); + uid->pvid = strdup(uid->dev->pvid); + update_file = 1; + } + + if (!uid->devname || strcmp(dev_name(uid->dev), uid->devname)) { + log_print("Device %s has updated devname from devices_file (was %s).", + dev_name(uid->dev), uid->devname ?: "."); + if (uid->devname) + free(uid->devname); + uid->devname = strdup(dev_name(uid->dev)); + update_file = 1; + } + } + + if (update_file) + device_ids_write(cmd); + + /* + * Issue: if devices have no wwid or serial numbers, entries in + * devices_file are identified only by their unstable devnames. + * It the devnames then change, all devices on the system need to + * read to find the PVs. Reading all devices on the system is + * one thing that the devices_file is meant to avoid. A new + * config setting could be used to enable/disable this behavior. + * + * TODO: if there are entries on use_device_ids that have no dev + * and are using DEV_ID_TYPE_DEVNAME, then it's possible that the + * unstable devname simply changed and the new devname was not + * included in devices_file. We need to read all devices on the + * system to find one with the missing PVID, label_scan it, and + * update devices_file with the new devname for the PVID. + * + * This function should tell setup_devices() that it should do a + * label_scan_for_pvid() (which only reads the headers for a PVID) + * on system devices that it did not already cover in the completed + * label_scan(). label_scan_for_pvid() would get a list of pvids + * to look for and return a list of devs on which they are found. + * That list of devs would then be passed to label_scan_devs() + * to do the full label_scan on them. + * + * A config setting would be able to disable this automatic scan + * of all devs for missing pvids, and the usage of the new devnames + * where those PVs are found. Without this scan, PVs on unstable + * devnames would be missing until a user manually runs a command + * to search devices for the missing PVs. The user could selectively + * scan certain devs and avoid devs that should not be touched. + */ +} + +int devices_file_valid(struct cmd_context *cmd) +{ + struct stat buf; + + if (!cmd->devices_file || !strlen(cmd->devices_file)) + return 0; + + if (stat(cmd->devices_file, &buf)) + return 0; + + return 1; +} + diff --git a/lib/device/device_id.h b/lib/device/device_id.h new file mode 100644 index 000000000..5f3be8e85 --- /dev/null +++ b/lib/device/device_id.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2001-2004 Sistina Software, Inc. All rights reserved. + * Copyright (C) 2004-2007 Red Hat, Inc. All rights reserved. + * + * This file is part of LVM2. + * + * This copyrighted material is made available to anyone wishing to use, + * modify, copy, or redistribute it subject to the terms and conditions + * of the GNU Lesser General Public License v.2.1. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _LVM_DEVICE_ID_H +#define _LVM_DEVICE_ID_H + +void free_uid(struct use_id *uid); +void free_uids(struct dm_list *list); +void free_did(struct dev_id *did); +void free_dids(struct dm_list *list); +const char *idtype_to_str(uint16_t idtype); +uint16_t idtype_from_str(const char *str); +const char *dev_idtype(struct device *dev); +const char *dev_id(struct device *dev); +int device_ids_read(struct cmd_context *cmd); +int device_ids_write(struct cmd_context *cmd); +int device_id_add(struct cmd_context *cmd, struct device *dev, const char *pvid, + const char *idtype_arg, const char *id_arg); +int device_id_add_nodev(struct cmd_context *cmd, + const char *idtype_str, + const char *idname, + const char *devname, + const char *pvid); +void device_id_pvremove(struct cmd_context *cmd, struct device *dev); +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 devices_file_valid(struct cmd_context *cmd); + +#endif diff --git a/lib/filters/filter-deviceid.c b/lib/filters/filter-deviceid.c new file mode 100644 index 000000000..6ee4b57e1 --- /dev/null +++ b/lib/filters/filter-deviceid.c @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2001-2004 Sistina Software, Inc. All rights reserved. + * Copyright (C) 2004-2012 Red Hat, Inc. All rights reserved. + * + * This file is part of LVM2. + * + * This copyrighted material is made available to anyone wishing to use, + * modify, copy, or redistribute it subject to the terms and conditions + * of the GNU Lesser General Public License v.2.1. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "base/memory/zalloc.h" +#include "lib/misc/lib.h" +#include "lib/filters/filter.h" +#include "lib/commands/toolcontext.h" + +static int _passes_deviceid_filter(struct cmd_context *cmd, struct dev_filter *f, struct device *dev, const char *use_filter_name) +{ + if (!cmd->enable_device_ids) + return 1; + + if (dev->flags & DEV_MATCHED_USE_ID) + return 1; + 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-regex.c b/lib/filters/filter-regex.c index e439b36b5..33edc4138 100644 --- a/lib/filters/filter-regex.c +++ b/lib/filters/filter-regex.c @@ -15,6 +15,7 @@ #include "lib/misc/lib.h" #include "lib/filters/filter.h" +#include "lib/commands/toolcontext.h" struct rfilter { struct dm_pool *mem; @@ -151,6 +152,11 @@ static int _accept_p(struct cmd_context *cmd, struct dev_filter *f, struct devic struct rfilter *rf = (struct rfilter *) f->private; struct dm_str_list *sl; + if (cmd->enable_device_ids) { + /* TODO: print a notice if the filter is set to something and we ignore it here. */ + return 1; + } + dm_list_iterate_items(sl, &dev->aliases) { m = dm_regex_match(rf->engine, sl->str); diff --git a/lib/filters/filter.h b/lib/filters/filter.h index 7333cfe3c..af329618f 100644 --- a/lib/filters/filter.h +++ b/lib/filters/filter.h @@ -30,6 +30,7 @@ struct dev_filter *partitioned_filter_create(struct dev_types *dt); struct dev_filter *persistent_filter_create(struct dev_types *dt, struct dev_filter *f); struct dev_filter *sysfs_filter_create(void); struct dev_filter *signature_filter_create(struct dev_types *dt); +struct dev_filter *deviceid_filter_create(struct cmd_context *cmd); struct dev_filter *internal_filter_create(void); int internal_filter_allow(struct dm_pool *mem, struct device *dev); diff --git a/lib/format_text/export.c b/lib/format_text/export.c index b5e987e01..1f620c603 100644 --- a/lib/format_text/export.c +++ b/lib/format_text/export.c @@ -23,6 +23,7 @@ #include "lib/metadata/segtype.h" #include "lib/format_text/text_export.h" #include "lib/commands/toolcontext.h" +#include "lib/device/device_id.h" #include "libdaemon/client/config-util.h" #include <stdarg.h> @@ -555,6 +556,11 @@ static int _print_pvs(struct formatter *f, struct volume_group *vg) dm_escape_double_quotes(buffer, pv_dev_name(pv))); outnl(f); + if (dev_idtype(pv->dev) && dev_id(pv->dev)) { + outf(f, "device_id_type = \"%s\"", dev_idtype(pv->dev)); + outf(f, "device_id = \"%s\"", dev_id(pv->dev)); + } + if (!_print_flag_config(f, pv->status, PV_FLAGS)) return_0; diff --git a/lib/format_text/import_vsn1.c b/lib/format_text/import_vsn1.c index 2bdbfeea9..ee56502e1 100644 --- a/lib/format_text/import_vsn1.c +++ b/lib/format_text/import_vsn1.c @@ -188,7 +188,7 @@ static int _read_pv(struct cmd_context *cmd, struct physical_volume *pv; struct pv_list *pvl; const struct dm_config_value *cv; - const char *device_hint; + const char *str; uint64_t size, ba_start; if (!(pvl = dm_pool_zalloc(mem, sizeof(*pvl))) || @@ -233,11 +233,21 @@ static int _read_pv(struct cmd_context *cmd, return 0; } - if (dm_config_get_str(pvn, "device", &device_hint)) { - if (!(pv->device_hint = dm_pool_strdup(mem, device_hint))) + if (dm_config_get_str(pvn, "device", &str)) { + if (!(pv->device_hint = dm_pool_strdup(mem, str))) log_error("Failed to allocate memory for device hint in read_pv."); } + if (dm_config_get_str(pvn, "device_id", &str)) { + if (!(pv->device_id = dm_pool_strdup(mem, str))) + log_error("Failed to allocate memory for device_id in read_pv."); + } + + if (dm_config_get_str(pvn, "device_id_type", &str)) { + if (!(pv->device_id_type = dm_pool_strdup(mem, str))) + log_error("Failed to allocate memory for device_id_type in read_pv."); + } + if (!_read_uint64(pvn, "pe_start", &pv->pe_start)) { log_error("Couldn't read extent start value (pe_start) " "for physical volume."); @@ -306,7 +316,7 @@ static int _read_pvsummary(struct cmd_context *cmd, { struct physical_volume *pv; struct pv_list *pvl; - const char *device_hint; + const char *str; if (!(pvl = dm_pool_zalloc(mem, sizeof(*pvl))) || !(pvl->pv = dm_pool_zalloc(mem, sizeof(*pvl->pv)))) @@ -326,9 +336,19 @@ static int _read_pvsummary(struct cmd_context *cmd, !_read_uint64(pvn, "dev_size", &pv->size)) log_warn("Couldn't read dev size for physical volume."); - if (dm_config_get_str(pvn, "device", &device_hint)) { - if (!(pv->device_hint = dm_pool_strdup(mem, device_hint))) - log_error("Failed to allocate memory for device hint in read_pv."); + if (dm_config_get_str(pvn, "device", &str)) { + if (!(pv->device_hint = dm_pool_strdup(mem, str))) + log_error("Failed to allocate memory for device hint in read_pv_sum."); + } + + if (dm_config_get_str(pvn, "device_id", &str)) { + if (!(pv->device_id = dm_pool_strdup(mem, str))) + log_error("Failed to allocate memory for device_id in read_pv_sum."); + } + + if (dm_config_get_str(pvn, "device_id_type", &str)) { + if (!(pv->device_id_type = dm_pool_strdup(mem, str))) + log_error("Failed to allocate memory for device_id_type in read_pv_sum."); } dm_list_add(&vgsummary->pvsummaries, &pvl->list); diff --git a/lib/label/label.c b/lib/label/label.c index ed6e0171c..1b15b7a8e 100644 --- a/lib/label/label.c +++ b/lib/label/label.c @@ -25,6 +25,7 @@ #include "lib/label/hints.h" #include "lib/metadata/metadata.h" #include "lib/format_text/layout.h" +#include "lib/device/device_id.h" #include <sys/stat.h> #include <fcntl.h> @@ -780,16 +781,6 @@ static int _scan_list(struct cmd_context *cmd, struct dev_filter *f, } } - /* - * This will search the system's /dev for new path names and - * could help us reopen the device if it finds a new preferred - * path name for this dev's major:minor. It does that by - * inserting a new preferred path name on dev->aliases. open - * uses the first name from that list. - */ - log_debug_devs("Scanning refreshing device paths."); - dev_cache_scan(); - /* Put devs that failed to open back on the original list to retry. */ dm_list_splice(devs, &reopen_devs); goto scan_more; @@ -923,7 +914,17 @@ int label_scan_for_pvid(struct cmd_context *cmd, char *pvid, struct device **dev dm_list_init(&devs); - dev_cache_scan(); + /* + * Creates a list of available devices, does not open or read any, + * and does not filter them. + */ + setup_devices(cmd); + + /* + * Iterating over all available devices with cmd->filter filters + * devices; those returned from dev_iter_get are the devs that + * pass filters, and are those we can use. + */ if (!(iter = dev_iter_create(cmd->filter, 0))) { log_error("Scanning failed to get devices."); @@ -1006,12 +1007,14 @@ int label_scan(struct cmd_context *cmd) dm_list_init(&hints_list); /* - * dev_cache_scan() creates a list of devices on the system - * (saved in in dev-cache) which we can iterate through to - * search for LVM devs. The dev cache list either comes from - * looking at dev nodes under /dev, or from udev. + * Creates a list of available devices, does not open or read any, + * and does not filter them. The list of all available devices + * is kept in "dev-cache", and comes from /dev entries or libudev. + * The list of devs found here needs to be filtered to get the + * list of devs we can use. The dev_iter calls using cmd->filter + * are what filters the devs. */ - dev_cache_scan(); + setup_devices(cmd); /* * If we know that there will be md components with an end @@ -1213,6 +1216,8 @@ int label_scan(struct cmd_context *cmd) if (create_hints) write_hint_file(cmd, create_hints); + device_ids_validate(cmd); + return 1; } diff --git a/lib/metadata/pv.h b/lib/metadata/pv.h index efa13e04b..ec1b21d54 100644 --- a/lib/metadata/pv.h +++ b/lib/metadata/pv.h @@ -27,6 +27,8 @@ struct physical_volume { struct id old_id; /* Set during pvchange -u. */ struct device *dev; const char *device_hint; /* primary name last time metadata was written */ + const char *device_id; + const char *device_id_type; const struct format_type *fmt; struct format_instance *fid; diff --git a/tools/Makefile.in b/tools/Makefile.in index 2620daa17..d3c084075 100644 --- a/tools/Makefile.in +++ b/tools/Makefile.in @@ -58,6 +58,7 @@ SOURCES =\ vgextend.c \ vgimport.c \ vgimportclone.c \ + vgimportdevices.c \ vgmerge.c \ vgmknodes.c \ vgreduce.c \ diff --git a/tools/args.h b/tools/args.h index 5fae21abb..66b916667 100644 --- a/tools/args.h +++ b/tools/args.h @@ -199,6 +199,15 @@ arg(detachprofile_ARG, '\0', "detachprofile", 0, 0, 0, "Detaches a metadata profile from a VG or LV.\n" "See \\fBlvm.conf\\fP(5) for more information about profiles.\n") +arg(deviceid_ARG, '\0', "deviceid", string_VAL, 0, 0, + "A device ID with a format determined by --deviceidtype.") + +arg(deviceidtype_ARG, '\0', "deviceidtype", string_VAL, 0, 0, + "A device ID type: sys_wwid, sys_serial, mpath_uuid.") + +arg(devicesfile_ARG, '\0', "devicesfile", string_VAL, 0, 0, + "The file listing device IDs that LVM should use.") + arg(discards_ARG, '\0', "discards", discards_VAL, 0, 0, "Specifies how the device-mapper thin pool layer in the kernel should\n" "handle discards.\n" diff --git a/tools/command-lines.in b/tools/command-lines.in index ed3d0413a..d3f7967b1 100644 --- a/tools/command-lines.in +++ b/tools/command-lines.in @@ -188,7 +188,7 @@ # OO_ALL: --commandprofile String, --config String, --debug, --driverloaded Bool, --help, --nolocking, --lockopt String, --longhelp, --profile String, --quiet, ---verbose, --version, --yes, --test +--verbose, --version, --yes, --test, --devicesfile String # # options for pvs, lvs, vgs, fullreport @@ -1479,7 +1479,8 @@ OO: --dataalignment SizeKB, --dataalignmentoffset SizeKB, --bootloaderareasize S --force, --labelsector Number, --metadatatype MetadataType, --pvmetadatacopies MetadataCopiesPV, --metadatasize SizeMB, --metadataignore Bool, --norestorefile, --setphysicalvolumesize SizeMB, ---reportformat ReportFmt, --restorefile String, --uuidstr String, --zero Bool +--reportformat ReportFmt, --restorefile String, --uuidstr String, --zero Bool, +--deviceidtype String, --deviceid String ID: pvcreate_general RULE: --norestorefile not --restorefile RULE: --bootloaderareasize not --restorefile @@ -1734,6 +1735,18 @@ ID: vgimportclone_general --- +vgimportdevices VG|Tag|Select ... +OO: --deviceidtype String, --select String, --foreign, --reportformat ReportFmt +ID: vgimportdevices_some +DESC: Add devices from specific VGs to the devices file. + +vgimportdevices --all +OO: --deviceidtype String, --foreign, --reportformat ReportFmt +ID: vgimportdevices_all +DESC: Add devices from all accessible VGs to the devices file. + +--- + vgmerge VG VG OO: --autobackup Bool, --list ID: vgmerge_general diff --git a/tools/commands.h b/tools/commands.h index c1670ae66..33566d039 100644 --- a/tools/commands.h +++ b/tools/commands.h @@ -207,6 +207,10 @@ xx(vgimportclone, "Import a VG from cloned PVs", ALLOW_EXPORTED) +xx(vgimportdevices, + "Add devices for a VG to the devices file.", + ALL_VGS_IS_DEFAULT | ALLOW_EXPORTED) + xx(vgmerge, "Merge volume groups", 0) diff --git a/tools/lvmcmdline.c b/tools/lvmcmdline.c index d87a8f053..eca8b47d9 100644 --- a/tools/lvmcmdline.c +++ b/tools/lvmcmdline.c @@ -17,6 +17,7 @@ #include "lvm2cmdline.h" #include "lib/label/label.h" +#include "lib/device/device_id.h" #include "lvm-version.h" #include "lib/locking/lvmlockd.h" @@ -2477,6 +2478,11 @@ static int _get_current_settings(struct cmd_context *cmd) cmd->record_historical_lvs = find_config_tree_bool(cmd, metadata_record_lvs_history_CFG, NULL) ? (arg_is_set(cmd, nohistory_ARG) ? 0 : 1) : 0; + if (arg_is_set(cmd, devicesfile_ARG)) + cmd->devices_file = arg_str_value(cmd, devicesfile_ARG, ""); + if (devices_file_valid(cmd)) + cmd->enable_device_ids = 1; + /* * This is set to zero by process_each which wants to print errors * itself rather than having them printed in vg_read. diff --git a/tools/pvck.c b/tools/pvck.c index a0f567eeb..ffcc1cfb0 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 @@ -3041,6 +3042,8 @@ int pvck(struct cmd_context *cmd, int argc, char **argv) if (arg_is_set(cmd, dump_ARG)) { pv_name = argv[0]; + setup_device(cmd, pv_name); + dev = dev_cache_get(cmd, pv_name, cmd->filter); if (!dev) diff --git a/tools/pvscan.c b/tools/pvscan.c index 4d811da55..c88b09b42 100644 --- a/tools/pvscan.c +++ b/tools/pvscan.c @@ -820,6 +820,11 @@ static void _online_pvscan_all_devs(struct cmd_context *cmd, struct device *dev; const char *pvid_without_metadata; + /* + * TODO: label_scan() calls setup_devices(), but pvscan --cache is a + * special case in which setup_devices() has already been called. + * So, we could improve things by suppressing the second setup_devices(). + */ lvmcache_label_scan(cmd); if (!(iter = dev_iter_create(cmd->filter, 1))) { @@ -1254,8 +1259,8 @@ int pvscan_cache_cmd(struct cmd_context *cmd, int argc, char **argv) _online_dir_setup(); - /* Creates a list of dev names from /dev, sysfs, etc; does not read any. */ - dev_cache_scan(); + /* Creates a list of available devices, does not open or read any. */ + setup_devices(cmd); if (cmd->md_component_detection && !cmd->use_full_md_check && !strcmp(cmd->md_component_checks, "auto") && diff --git a/tools/toollib.c b/tools/toollib.c index 9640f003e..b22d75a69 100644 --- a/tools/toollib.c +++ b/tools/toollib.c @@ -16,6 +16,7 @@ #include "tools.h" #include "lib/format_text/format-text.h" #include "lib/label/hints.h" +#include "lib/device/device_id.h" #include <sys/stat.h> #include <signal.h> @@ -5100,9 +5101,16 @@ int pvcreate_each_device(struct cmd_context *cmd, /* * Translate arg names into struct device's. + * + * TODO: do we want to add a config setting that would disable this + * ability of pvcreate to use devs outside of the devices_file? + * If devices_file is to be more strict in allowing access to devs, + * e.g. applied to pvcreate, then a user would need to add the new + * device to devices_file prior to running pvcreate on it. */ dm_list_iterate_items_safe(pd, pd2, &pp->arg_devices) { - pd->dev = dev_cache_get(cmd, pd->name, cmd->filter); + pd->dev = dev_cache_get(cmd, pd->name, + cmd->enable_device_ids ? NULL : cmd->filter); if (!pd->dev) { log_print("No device found for %s", pd->name); dm_list_del(&pd->list); @@ -5411,6 +5419,10 @@ do_command: log_debug("Using existing orphan PV %s.", pv_dev_name(vgpvl->pv)); pvl->pv = vgpvl->pv; dm_list_add(&pp->pvs, &pvl->list); + + device_id_add(cmd, pd->dev, (const char *)&pvl->pv->id.uuid, + arg_str_value(cmd, deviceidtype_ARG, NULL), + arg_str_value(cmd, deviceid_ARG, NULL)); } else { log_error("Failed to find PV %s", pd->name); dm_list_move(&pp->arg_fail, &pd->list); @@ -5449,6 +5461,10 @@ do_command: continue; } + device_id_add(cmd, pd->dev, (const char *)&pv->id.uuid, + arg_str_value(cmd, deviceidtype_ARG, NULL), + arg_str_value(cmd, deviceid_ARG, NULL)); + log_verbose("Set up physical volume for \"%s\" with %" PRIu64 " available sectors.", pv_name, pv_size(pv)); @@ -5494,6 +5510,8 @@ do_command: continue; } + device_id_pvremove(cmd, pd->dev); + log_print_unless_silent("Labels on physical volume \"%s\" successfully wiped.", pd->name); } @@ -5510,10 +5528,15 @@ do_command: lvmcache_del_dev_from_duplicates(pd->dev); + device_id_pvremove(cmd, pd->dev); + log_print_unless_silent("Labels on physical volume \"%s\" successfully wiped.", pd->name); } + /* TODO: when vgcreate uses only existing PVs this doesn't change and can be skipped */ + device_ids_write(cmd); + /* * Don't keep devs open excl in bcache because the excl will prevent * using that dev elsewhere. diff --git a/tools/vgimportdevices.c b/tools/vgimportdevices.c new file mode 100644 index 000000000..a20b78ccb --- /dev/null +++ b/tools/vgimportdevices.c @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2020 Red Hat, Inc. All rights reserved. + * + * This file is part of LVM2. + * + * This copyrighted material is made available to anyone wishing to use, + * modify, copy, or redistribute it subject to the terms and conditions + * of the GNU Lesser General Public License v.2.1. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "tools.h" +#include "lib/cache/lvmcache.h" +#include "lib/filters/filter.h" +#include "lib/device/device_id.h" + +struct vgimportdevices_params { + uint32_t added_devices; +}; + +static int _vgimportdevices_single(struct cmd_context *cmd, + const char *vg_name, + struct volume_group *vg, + struct processing_handle *handle) +{ + struct vgimportdevices_params *vp = (struct vgimportdevices_params *) handle->custom_handle; + struct pv_list *pvl; + struct physical_volume *pv; + int update_vg = 1; + int updated_pvs = 0; + const char *idtypestr; + + dm_list_iterate_items(pvl, &vg->pvs) { + if (is_missing_pv(pvl->pv) || !pvl->pv->dev) { + log_warn("WARNING: not importing VG %s with missing PV.", vg->name); + return 1; + } + } + + /* + * We want to allow importing devices of foreign and shared + * VGs, but we do not want to update device_ids in those VGs. + * + * If --foreign is set, then foreign VGs will be passed + * to this function; add devices but don't update vg. + * shared VGs are passed to this function; add devices + * and do not update. + */ + if (vg_is_foreign(vg) || vg_is_shared(vg)) + update_vg = 0; + + /* + * When no devices_file is available, do not update device_ids + * in the VG. This command is not meant to be used to just + * update device_ids of a VG; for that use vgchange. + */ + if (!devices_file_valid(cmd)) + update_vg = 0; + + /* + * TODO: let users import devices without updating VG device_ids. + * if --nodeviceidupdate; update_vg = 0; + */ + + /* + * User can select the idtype to use when importing. + */ + idtypestr = arg_str_value(cmd, deviceidtype_ARG, NULL); + + dm_list_iterate_items(pvl, &vg->pvs) { + pv = pvl->pv; + + if (!pv->dev && pv->device_id_type && pv->device_id) { + /* add an entry using the fields we have values for */ + cmd->enable_device_ids = 1; + device_id_add_nodev(cmd, pv->device_id_type, pv->device_id, NULL, (const char *)&pv->id.uuid); + cmd->enable_device_ids = 0; + + vp->added_devices++; + continue; + + } + if (!pv->dev) { + log_warn("WARNING: not adding PV with missing device PVID %s", + (const char *)&pvl->pv->id.uuid); + continue; + } + + if (!idtypestr && pv->device_id_type) + idtypestr = pv->device_id_type; + + cmd->enable_device_ids = 1; + device_id_add(cmd, pv->dev, (const char *)&pvl->pv->id.uuid, idtypestr, NULL); + cmd->enable_device_ids = 0; + + vp->added_devices++; + + /* We could skip update if the device_id has not changed. */ + + if (!update_vg) + continue; + + updated_pvs++; + } + + if (updated_pvs) { + if (!vg_write(vg) || !vg_commit(vg)) + goto_bad; + backup(vg); + } + + return ECMD_PROCESSED; +bad: + return ECMD_FAILED; +} + +/* + * This command always scans all devices on the system, + * any pre-existing devices_file does not limit the scope. + * + * This command adds the VG's devices to whichever + * devices_file is set in config or command line. + * If devices_file doesn't exist, it's created. + * + * If devices_file is "" then this file will scan all devices + * and show the devices that it would otherwise have added to + * the devices_file. The VG is not updated with device_ids. + * + * This command updates the VG metadata to add device_ids + * (if the metadata is missing them), unless an option is + * set to skip that, e.g. --nodeviceidupdate? + * + * If the VG found has a foreign system ID then an error + * will be printed. To import devices from a foreign VG: + * vgimportdevices --foreign -a + * vgimportdevices --foreign VG + * + * If there are duplicate VG names it will do nothing. + * + * If there are duplicate PVIDs related to VG it will do nothing, + * the user would need to add the PVs they want with lvmdevices --add. + * + * vgimportdevices -a (no vg arg) will import all accesible VGs. + */ + +int vgimportdevices(struct cmd_context *cmd, int argc, char **argv) +{ + struct vgimportdevices_params vp = { 0 }; + struct processing_handle *handle; + + if (arg_is_set(cmd, foreign_ARG)) + cmd->include_foreign_vgs = 1; + + cmd->include_shared_vgs = 1; + + /* So that we can warn about this. */ + cmd->handles_missing_pvs = 1; + + /* Print a notice if a regex filter is being applied? + Possibly offer an option to ignore a regex filter? */ + + if (!lock_global(cmd, "ex")) + return ECMD_FAILED; + + clear_hint_file(cmd); + + if (!(handle = init_processing_handle(cmd, NULL))) { + log_error("Failed to initialize processing handle."); + return ECMD_FAILED; + } + handle->custom_handle = &vp; + + /* + * import is an odd case where we do not want to use an + * existing devices_file for processing/filtering, because + * we want to search outside the devices_file for new devs + * to add to it, but we do want devices_file entries on + * use_device_ids so we can update and write out that list. + */ + cmd->enable_device_ids = 1; + device_ids_read(cmd); + cmd->enable_device_ids = 0; + + /* + * For each VG: + * device_id_add() each PV in the VG + * update device_ids in the VG (potentially) + */ + process_each_vg(cmd, argc, argv, NULL, NULL, READ_FOR_UPDATE, + 0, handle, _vgimportdevices_single); + + if (!vp.added_devices) { + log_print("No devices to add."); + goto out; + } + + if (!cmd->devices_file || !strlen(cmd->devices_file)) { + log_print("No devices file to update."); + goto out; + } + + cmd->enable_device_ids = 1; + if (!device_ids_write(cmd)) { + destroy_processing_handle(cmd, handle); + log_error("Failed to update devices file."); + return ECMD_FAILED; + } + + log_print("Added %u devices to devices file.", vp.added_devices); +out: + destroy_processing_handle(cmd, handle); + return ECMD_PROCESSED; +} + |