summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--daemons/lvmpolld/lvmpolld-cmd-utils.c6
-rw-r--r--daemons/lvmpolld/lvmpolld-core.c8
-rw-r--r--daemons/lvmpolld/lvmpolld-data-utils.c7
-rw-r--r--daemons/lvmpolld/lvmpolld-data-utils.h4
-rw-r--r--daemons/lvmpolld/lvmpolld-protocol.h1
-rw-r--r--lib/Makefile.in2
-rw-r--r--lib/cache/lvmcache.c72
-rw-r--r--lib/commands/toolcontext.c19
-rw-r--r--lib/commands/toolcontext.h9
-rw-r--r--lib/config/config_settings.h8
-rw-r--r--lib/config/defaults.h3
-rw-r--r--lib/device/dev-cache.c218
-rw-r--r--lib/device/dev-cache.h9
-rw-r--r--lib/device/dev-type.c39
-rw-r--r--lib/device/dev-type.h1
-rw-r--r--lib/device/device.h31
-rw-r--r--lib/device/device_id.c1826
-rw-r--r--lib/device/device_id.h53
-rw-r--r--lib/filters/filter-composite.c9
-rw-r--r--lib/filters/filter-deviceid.c60
-rw-r--r--lib/filters/filter-fwraid.c4
-rw-r--r--lib/filters/filter-md.c3
-rw-r--r--lib/filters/filter-partitioned.c4
-rw-r--r--lib/filters/filter-persistent.c10
-rw-r--r--lib/filters/filter-regex.c6
-rw-r--r--lib/filters/filter-signature.c4
-rw-r--r--lib/filters/filter.h1
-rw-r--r--lib/format_text/export.c6
-rw-r--r--lib/format_text/import_vsn1.c34
-rw-r--r--lib/label/hints.c55
-rw-r--r--lib/label/label.c54
-rw-r--r--lib/label/label.h2
-rw-r--r--lib/lvmpolld/lvmpolld-client.c7
-rw-r--r--lib/lvmpolld/polldaemon.h1
-rw-r--r--lib/metadata/pv.c14
-rw-r--r--lib/metadata/pv.h4
-rw-r--r--lib/report/columns.h2
-rw-r--r--lib/report/properties.c4
-rw-r--r--lib/report/report.c36
-rw-r--r--tools/Makefile.in2
-rw-r--r--tools/args.h27
-rw-r--r--tools/command-lines.in44
-rw-r--r--tools/commands.h8
-rw-r--r--tools/lvmcmdline.c20
-rw-r--r--tools/lvmdevices.c339
-rw-r--r--tools/polldaemon.c9
-rw-r--r--tools/pvchange.c17
-rw-r--r--tools/pvck.c11
-rw-r--r--tools/pvcreate.c2
-rw-r--r--tools/pvscan.c13
-rw-r--r--tools/toollib.c64
-rw-r--r--tools/tools.h1
-rw-r--r--tools/vgcreate.c4
-rw-r--r--tools/vgextend.c4
-rw-r--r--tools/vgimportclone.c404
-rw-r--r--tools/vgimportdevices.c210
56 files changed, 3628 insertions, 187 deletions
diff --git a/daemons/lvmpolld/lvmpolld-cmd-utils.c b/daemons/lvmpolld/lvmpolld-cmd-utils.c
index 865b47ec9..56751bd56 100644
--- a/daemons/lvmpolld/lvmpolld-cmd-utils.c
+++ b/daemons/lvmpolld/lvmpolld-cmd-utils.c
@@ -92,6 +92,12 @@ const char **cmdargv_ctr(const struct lvmpolld_lv *pdlv, const char *lvm_binary,
if (!add_to_cmd_arr(&cmd_argv, "-An", &i))
goto err;
+ if (pdlv->devicesfile) {
+ if (!add_to_cmd_arr(&cmd_argv, "--devicesfile", &i) ||
+ !add_to_cmd_arr(&cmd_argv, pdlv->devicesfile, &i))
+ goto err;
+ }
+
/* terminating NULL */
if (!add_to_cmd_arr(&cmd_argv, NULL, &i))
goto err;
diff --git a/daemons/lvmpolld/lvmpolld-core.c b/daemons/lvmpolld/lvmpolld-core.c
index af98843c9..f7381eb35 100644
--- a/daemons/lvmpolld/lvmpolld-core.c
+++ b/daemons/lvmpolld/lvmpolld-core.c
@@ -555,14 +555,15 @@ static struct lvmpolld_lv *construct_pdlv(request req, struct lvmpolld_state *ls
const char *interval, const char *id,
const char *vgname, const char *lvname,
const char *sysdir, enum poll_type type,
- unsigned abort_polling, unsigned uinterval)
+ unsigned abort_polling, unsigned uinterval,
+ const char *devicesfile)
{
const char **cmdargv, **cmdenvp;
struct lvmpolld_lv *pdlv;
unsigned handle_missing_pvs = daemon_request_int(req, LVMPD_PARM_HANDLE_MISSING_PVS, 0);
pdlv = pdlv_create(ls, id, vgname, lvname, sysdir, type,
- interval, uinterval, pdst);
+ interval, uinterval, pdst, devicesfile);
if (!pdlv) {
ERROR(ls, "%s: %s", PD_LOG_PREFIX, "failed to create internal LV data structure.");
@@ -621,6 +622,7 @@ static response poll_init(client_handle h, struct lvmpolld_state *ls, request re
const char *lvname = daemon_request_str(req, LVMPD_PARM_LVNAME, NULL);
const char *vgname = daemon_request_str(req, LVMPD_PARM_VGNAME, NULL);
const char *sysdir = daemon_request_str(req, LVMPD_PARM_SYSDIR, NULL);
+ const char *devicesfile = daemon_request_str(req, LVMPD_PARM_DEVICESFILE, NULL);
unsigned abort_polling = daemon_request_int(req, LVMPD_PARM_ABORT, 0);
assert(type < POLL_TYPE_MAX);
@@ -680,7 +682,7 @@ static response poll_init(client_handle h, struct lvmpolld_state *ls, request re
pdlv->init_rq_count++; /* safe. protected by store lock */
} else {
pdlv = construct_pdlv(req, ls, pdst, interval, id, vgname,
- lvname, sysdir, type, abort_polling, 2 * uinterval);
+ lvname, sysdir, type, abort_polling, 2 * uinterval, devicesfile);
if (!pdlv) {
pdst_unlock(pdst);
free(id);
diff --git a/daemons/lvmpolld/lvmpolld-data-utils.c b/daemons/lvmpolld/lvmpolld-data-utils.c
index 23e316a96..6b66f1b67 100644
--- a/daemons/lvmpolld/lvmpolld-data-utils.c
+++ b/daemons/lvmpolld/lvmpolld-data-utils.c
@@ -93,11 +93,13 @@ struct lvmpolld_lv *pdlv_create(struct lvmpolld_state *ls, const char *id,
const char *vgname, const char *lvname,
const char *sysdir, enum poll_type type,
const char *sinterval, unsigned pdtimeout,
- struct lvmpolld_store *pdst)
+ struct lvmpolld_store *pdst,
+ const char *devicesfile)
{
char *lvmpolld_id = strdup(id), /* copy */
*full_lvname = _construct_full_lvname(vgname, lvname), /* copy */
*lvm_system_dir_env = _construct_lvm_system_dir_env(sysdir); /* copy */
+ char *devicesfile_dup = devicesfile ? strdup(devicesfile) : NULL;
struct lvmpolld_lv tmp = {
.ls = ls,
@@ -105,6 +107,7 @@ struct lvmpolld_lv *pdlv_create(struct lvmpolld_state *ls, const char *id,
.lvmpolld_id = lvmpolld_id,
.lvid = _get_lvid(lvmpolld_id, sysdir),
.lvname = full_lvname,
+ .devicesfile = devicesfile_dup,
.lvm_system_dir_env = lvm_system_dir_env,
.sinterval = strdup(sinterval), /* copy */
.pdtimeout = pdtimeout < MIN_POLLING_TIMEOUT ? MIN_POLLING_TIMEOUT : pdtimeout,
@@ -124,6 +127,7 @@ struct lvmpolld_lv *pdlv_create(struct lvmpolld_state *ls, const char *id,
return pdlv;
err:
+ free((void *)devicesfile_dup);
free((void *)full_lvname);
free((void *)lvmpolld_id);
free((void *)lvm_system_dir_env);
@@ -136,6 +140,7 @@ err:
void pdlv_destroy(struct lvmpolld_lv *pdlv)
{
free((void *)pdlv->lvmpolld_id);
+ free((void *)pdlv->devicesfile);
free((void *)pdlv->lvname);
free((void *)pdlv->sinterval);
free((void *)pdlv->lvm_system_dir_env);
diff --git a/daemons/lvmpolld/lvmpolld-data-utils.h b/daemons/lvmpolld/lvmpolld-data-utils.h
index 5bb5c863d..96a27f1c8 100644
--- a/daemons/lvmpolld/lvmpolld-data-utils.h
+++ b/daemons/lvmpolld/lvmpolld-data-utils.h
@@ -49,6 +49,7 @@ struct lvmpolld_lv {
const enum poll_type type;
const char *const lvid;
const char *const lvmpolld_id;
+ const char *const devicesfile;
const char *const lvname; /* full vg/lv name */
const unsigned pdtimeout; /* in seconds */
const char *const sinterval;
@@ -101,7 +102,8 @@ struct lvmpolld_lv *pdlv_create(struct lvmpolld_state *ls, const char *id,
const char *vgname, const char *lvname,
const char *sysdir, enum poll_type type,
const char *sinterval, unsigned pdtimeout,
- struct lvmpolld_store *pdst);
+ struct lvmpolld_store *pdst,
+ const char *devicesfile);
/* only call with appropriate struct lvmpolld_store lock held */
void pdlv_destroy(struct lvmpolld_lv *pdlv);
diff --git a/daemons/lvmpolld/lvmpolld-protocol.h b/daemons/lvmpolld/lvmpolld-protocol.h
index 9b9890d7b..18c7de85a 100644
--- a/daemons/lvmpolld/lvmpolld-protocol.h
+++ b/daemons/lvmpolld/lvmpolld-protocol.h
@@ -35,6 +35,7 @@
#define LVMPD_PARM_SYSDIR "sysdir"
#define LVMPD_PARM_VALUE "value" /* either retcode or signal value */
#define LVMPD_PARM_VGNAME "vgname"
+#define LVMPD_PARM_DEVICESFILE "devicesfile"
#define LVMPD_RESP_FAILED "failed"
#define LVMPD_RESP_FINISHED "finished"
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..f5cd68aa7 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"
@@ -491,6 +492,25 @@ static const char *_get_pvsummary_device_hint(char *pvid)
return NULL;
}
+static const char *_get_pvsummary_device_id(char *pvid, const char **device_id_type)
+{
+ char pvid_s[ID_LEN + 1] __attribute__((aligned(8)));
+ struct lvmcache_vginfo *vginfo;
+ struct pv_list *pvl;
+
+ dm_list_iterate_items(vginfo, &_vginfos) {
+ dm_list_iterate_items(pvl, &vginfo->pvsummaries) {
+ (void) dm_strncpy(pvid_s, (char *) &pvl->pv->id, sizeof(pvid_s));
+ if (!strcmp(pvid_s, pvid)) {
+ *device_id_type = pvl->pv->device_id_type;
+ return pvl->pv->device_id;
+ }
+ }
+ }
+
+ return NULL;
+}
+
/*
* Check if any PVs in vg->pvs have the same PVID as any
* entries in _unused_duplicates.
@@ -596,6 +616,8 @@ static void _choose_duplicates(struct cmd_context *cmd,
struct device_list *devl, *devl_safe, *devl_add, *devl_del;
struct lvmcache_info *info;
struct device *dev1, *dev2;
+ const char *device_id = NULL, *device_id_type = NULL;
+ const char *idname1 = NULL, *idname2 = NULL;
uint32_t dev1_major, dev1_minor, dev2_major, dev2_minor;
uint64_t dev1_size, dev2_size, pvsummary_size;
int in_subsys1, in_subsys2;
@@ -604,6 +626,7 @@ static void _choose_duplicates(struct cmd_context *cmd,
int has_lv1, has_lv2;
int same_size1, same_size2;
int same_name1 = 0, same_name2 = 0;
+ int same_id1 = 0, same_id2 = 0;
int prev_unchosen1, prev_unchosen2;
int change;
@@ -734,6 +757,19 @@ next:
same_name2 = !strcmp(device_hint, dev_name(dev2));
}
+ if ((device_id = _get_pvsummary_device_id(devl->dev->pvid, &device_id_type))) {
+ uint16_t idtype = idtype_from_str(device_id_type);
+
+ if (idtype) {
+ idname1 = device_id_system_read(cmd, dev1, idtype);
+ idname2 = device_id_system_read(cmd, dev2, idtype);
+ }
+ if (idname1)
+ same_id1 = !strcmp(idname1, device_id);
+ if (idname2)
+ same_id2 = !strcmp(idname2, device_id);
+ }
+
has_lv1 = (dev1->flags & DEV_USED_FOR_LV) ? 1 : 0;
has_lv2 = (dev2->flags & DEV_USED_FOR_LV) ? 1 : 0;
@@ -752,6 +788,12 @@ next:
dev_name(dev2), dev2_major, dev2_minor,
device_hint ?: "none");
+ log_debug_cache("PV %s: device_id %s. %s is %s. %s is %s.",
+ devl->dev->pvid,
+ device_id ?: ".",
+ dev_name(dev1), idname1 ?: ".",
+ dev_name(dev2), idname2 ?: ".");
+
log_debug_cache("PV %s: size %llu. %s is %llu. %s is %llu.",
devl->dev->pvid,
(unsigned long long)pvsummary_size,
@@ -792,6 +834,13 @@ next:
} else if (prev_unchosen2 && !prev_unchosen1) {
/* keep 1 (NB when unchosen is set we unprefer) */
reason = "of previous preference";
+ } else if (same_id1 && !same_id2) {
+ /* keep 1 */
+ reason = "device id";
+ } else if (same_id2 && !same_id1) {
+ /* change to 2 */
+ change = 1;
+ reason = "device id";
} else if (has_lv1 && !has_lv2) {
/* keep 1 */
reason = "device is used by LV";
@@ -1024,13 +1073,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 +1094,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..5166ef2a8 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 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..02e2beaff 100644
--- a/lib/commands/toolcontext.h
+++ b/lib/commands/toolcontext.h
@@ -182,13 +182,21 @@ 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 */
+ unsigned filter_nodata_only:1; /* only use filters that do not require data from the dev */
/*
* 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 +228,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..12d4129fc 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 "system.devices"
+
#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..c29d4a880
--- /dev/null
+++ b/lib/device/device_id.c
@@ -0,0 +1,1826 @@
+/*
+ * 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 _using_devices_file;
+static int _devices_file_locked;
+static char _devices_lockfile[PATH_MAX];
+static char _devices_file_systemid[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.
+ */
+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, "SYSTEMID", 8)) {
+ _copy_idline_str(line, _devices_file_systemid, sizeof(_devices_file_systemid));
+ log_debug("read devices file systemid %s", _devices_file_systemid);
+ if ((!cmd->system_id && _devices_file_systemid[0]) ||
+ strcmp(cmd->system_id, _devices_file_systemid)) {
+ log_print("Ignoring devices file with wrong system id %s vs local %s.",
+ _devices_file_systemid[0] ? _devices_file_systemid : ".", cmd->system_id ?: ".");
+ free_uids(&cmd->use_device_ids);
+ ret = 0;
+ goto out;
+ }
+ 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);
+ }
+out:
+ 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));
+
+ /*
+ * It's useful to ensure that this devices file is associated to a
+ * single system because this file can be used to control access to
+ * shared devices. If this file is copied/cloned to another system,
+ * that new system should not automatically gain access to the devices
+ * that the original system is using.
+ */
+ if (cmd->system_id)
+ fprintf(fp, "SYSTEMID=%s\n", cmd->system_id);
+
+ 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_arg,
+ const char *idtype_arg, const char *id_arg)
+{
+ char pvid[ID_LEN+1] = { 0 };
+ 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;
+
+ /*
+ * The pvid_arg may be passed from a 'struct id' (pv->id) which
+ * may not have a terminating \0.
+ * Make a terminated copy to use as a string.
+ */
+ memcpy(&pvid, pvid_arg, ID_LEN);
+
+ 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)
+ * "md_uuid_udev" from udev MD_UUID=ddea39b4:664929c0:8058b96d:164de08d
+ *
+ * 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->devname || !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;
+}
+
+/*
+ * 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;
+
+ log_debug("validating devices file entries");
+
+ dm_list_iterate_items(uid, &cmd->use_device_ids) {
+ if (!uid->dev)
+ continue;
+
+ dev = uid->dev;
+
+ /*
+ * uid and dev may have been matched, but the dev could still
+ * have been excluded by other filters during label scan.
+ * This shouldn't generally happen, but if it does the user
+ * probably wants to do something about it.
+ */
+ if (!cmd->filter->passes_filter(cmd, cmd->filter, dev, "persistent")) {
+ /* FIXME: print which filter */
+ log_print("Devices file entry for %s excluded by filter.", dev_name(dev));
+ continue;
+ }
+
+ /*
+ * 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)
+{
+ struct stat buf;
+ char dirpath[PATH_MAX];
+ int fd;
+
+ if (dm_snprintf(dirpath, sizeof(dirpath), "%s/devices", cmd->system_dir) < 0) {
+ log_error("Failed to copy devices dir path");
+ return 0;
+ }
+
+ if (stat(dirpath, &buf)) {
+ log_error("Cannot create devices file, missing devices directory %s.", dirpath);
+ return 0;
+ }
+
+ 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 || cmd->nolocking)
+ return 1;
+
+ _using_devices_file = 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->enable_devices_file || cmd->nolocking || !_using_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..6075b783b
--- /dev/null
+++ b/lib/device/device_id.h
@@ -0,0 +1,53 @@
+/*
+ * 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);
+const char *device_id_system_read(struct cmd_context *cmd, struct device *dev, uint16_t idtype);
+
+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..f4cbeefe8 100644
--- a/lib/filters/filter-composite.c
+++ b/lib/filters/filter-composite.c
@@ -60,13 +60,16 @@ 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)
+ 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-fwraid.c b/lib/filters/filter-fwraid.c
index 992eba8b8..9764b51e5 100644
--- a/lib/filters/filter-fwraid.c
+++ b/lib/filters/filter-fwraid.c
@@ -15,6 +15,7 @@
#include "base/memory/zalloc.h"
#include "lib/misc/lib.h"
#include "lib/filters/filter.h"
+#include "lib/commands/toolcontext.h"
#ifdef UDEV_SYNC_SUPPORT
#include <libudev.h>
@@ -69,6 +70,9 @@ static int _ignore_fwraid(struct cmd_context *cmd, struct dev_filter *f __attrib
{
int ret;
+ if (cmd->filter_nodata_only)
+ return 1;
+
if (!fwraid_filtering())
return 1;
diff --git a/lib/filters/filter-md.c b/lib/filters/filter-md.c
index 28903be3f..3a8f7dc46 100644
--- a/lib/filters/filter-md.c
+++ b/lib/filters/filter-md.c
@@ -86,6 +86,9 @@ static int _passes_md_filter(struct cmd_context *cmd, struct dev_filter *f __att
{
int ret;
+ if (cmd->filter_nodata_only)
+ return 1;
+
/*
* When md_component_dectection=0, don't even try to skip md
* components.
diff --git a/lib/filters/filter-partitioned.c b/lib/filters/filter-partitioned.c
index 1a700543c..429be47f9 100644
--- a/lib/filters/filter-partitioned.c
+++ b/lib/filters/filter-partitioned.c
@@ -16,6 +16,7 @@
#include "base/memory/zalloc.h"
#include "lib/misc/lib.h"
#include "lib/filters/filter.h"
+#include "lib/commands/toolcontext.h"
#define MSG_SKIPPING "%s: Skipping: Partition table signature found"
@@ -24,6 +25,9 @@ static int _passes_partitioned_filter(struct cmd_context *cmd, struct dev_filter
struct dev_types *dt = (struct dev_types *) f->private;
int ret;
+ if (cmd->filter_nodata_only)
+ return 1;
+
ret = dev_is_partitioned(dt, dev);
if (ret == -EAGAIN) {
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-signature.c b/lib/filters/filter-signature.c
index 6a81203a9..de26ef0f2 100644
--- a/lib/filters/filter-signature.c
+++ b/lib/filters/filter-signature.c
@@ -16,6 +16,7 @@
#include "base/memory/zalloc.h"
#include "lib/misc/lib.h"
#include "lib/filters/filter.h"
+#include "lib/commands/toolcontext.h"
#ifdef __linux__
@@ -27,6 +28,9 @@ static int _ignore_signature(struct cmd_context *cmd, struct dev_filter *f __att
char buf[BUFSIZE];
int ret = 0;
+ if (cmd->filter_nodata_only)
+ return 1;
+
if (!scan_bcache) {
/* let pass, call again after scan */
log_debug_devs("filter signature deferred %s", dev_name(dev));
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/lvmpolld/lvmpolld-client.c b/lib/lvmpolld/lvmpolld-client.c
index 1a76485c1..015cefafb 100644
--- a/lib/lvmpolld/lvmpolld-client.c
+++ b/lib/lvmpolld/lvmpolld-client.c
@@ -247,6 +247,13 @@ static int _process_poll_init(const struct cmd_context *cmd, const char *poll_ty
goto out_req;
}
+ if (parms->devicesfile[0] &&
+ !(daemon_request_extend(req, LVMPD_PARM_DEVICESFILE " = %s",
+ parms->devicesfile, NULL))) {
+ log_error("Failed to create %s request." , poll_type);
+ goto out_req;
+ }
+
rep = daemon_send(_lvmpolld, req);
if (rep.error) {
diff --git a/lib/lvmpolld/polldaemon.h b/lib/lvmpolld/polldaemon.h
index 17ef7cb44..40ce7a546 100644
--- a/lib/lvmpolld/polldaemon.h
+++ b/lib/lvmpolld/polldaemon.h
@@ -60,6 +60,7 @@ struct daemon_parms {
const char *progress_title;
uint64_t lv_type;
struct poll_functions *poll_fns;
+ char devicesfile[128];
};
int poll_daemon(struct cmd_context *cmd, unsigned background,
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..6948c5e27 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"
@@ -274,6 +295,9 @@ arg(ignoreunsupported_ARG, '\0', "ignoreunsupported", 0, 0, 0,
"and \\fBdiff\\fP types include unsupported settings in their output by default,\n"
"all the other types ignore unsupported settings.\n")
+arg(importdevices_ARG, '\0', "importdevices", 0, 0, 0,
+ "Add devices to the devices file.\n")
+
arg(labelsector_ARG, '\0', "labelsector", number_VAL, 0, 0,
"By default the PV is labelled with an LVM2 identifier in its second\n"
"sector (sector 1). This lets you use a different sector near the\n"
@@ -769,6 +793,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..d0621d711 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
@@ -1729,11 +1755,23 @@ DESC: Import all VGs.
---
vgimportclone PV ...
-OO: --basevgname VG, --import
+OO: --basevgname VG, --import, --importdevices
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..af45b5e82
--- /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
+ * The regex filter will be used and filter-deviceid not used.
+ */
+ log_debug("Filtering devices without data");
+ cmd->filter_nodata_only = 1;
+ 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_nodata_only = 0;
+ 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, NULL)) &&
+ (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/polldaemon.c b/tools/polldaemon.c
index b0294ebb3..d4e0be13e 100644
--- a/tools/polldaemon.c
+++ b/tools/polldaemon.c
@@ -670,6 +670,15 @@ static int _daemon_parms_init(struct cmd_context *cmd, struct daemon_parms *parm
parms->progress_display = parms->interval ? 1 : 0;
+ memset(parms->devicesfile, 0, sizeof(parms->devicesfile));
+ if (cmd->devicesfile) {
+ if (strlen(cmd->devicesfile) >= sizeof(parms->devicesfile)) {
+ log_error("devicefile name too long for lvmpolld");
+ return 0;
+ }
+ strcpy(parms->devicesfile, cmd->devicesfile);
+ }
+
return 1;
}
diff --git a/tools/pvchange.c b/tools/pvchange.c
index dd72bf1bd..a92bdd8b0 100644
--- a/tools/pvchange.c
+++ b/tools/pvchange.c
@@ -26,6 +26,7 @@ static int _pvchange_single(struct cmd_context *cmd, struct volume_group *vg,
struct pvchange_params *params = (struct pvchange_params *) handle->custom_handle;
const char *pv_name = pv_dev_name(pv);
char uuid[64] __attribute__((aligned(8)));
+ struct use_id *uid;
unsigned done = 0;
int used;
@@ -147,6 +148,9 @@ static int _pvchange_single(struct cmd_context *cmd, struct volume_group *vg,
if (arg_is_set(cmd, uuid_ARG)) {
/* --uuid: Change PV ID randomly */
+
+ uid = get_uid_for_pvid(cmd, pv->dev->pvid);
+
memcpy(&pv->old_id, &pv->id, sizeof(pv->id));
if (!id_create(&pv->id)) {
log_error("Failed to generate new random UUID for %s.",
@@ -184,6 +188,16 @@ static int _pvchange_single(struct cmd_context *cmd, struct volume_group *vg,
goto bad;
}
+ if (uid) {
+ if (uid->pvid)
+ free(uid->pvid);
+ if (!(uid->pvid = strndup((char *)&pv->id, ID_LEN)))
+ log_error("Failed to set pvid for devices file.");
+ if (!device_ids_write(cmd))
+ log_warn("Failed to update devices file.");
+ unlock_devices_file(cmd);
+ }
+
log_print_unless_silent("Physical volume \"%s\" changed", pv_name);
params->done++;
@@ -210,6 +224,9 @@ int pvchange(struct cmd_context *cmd, int argc, char **argv)
goto out;
}
+ if (arg_is_set(cmd, uuid_ARG))
+ cmd->edit_devices_file = 1;
+
if (!(handle = init_processing_handle(cmd, NULL))) {
log_error("Failed to initialize processing handle.");
ret = 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/vgimportclone.c b/tools/vgimportclone.c
index ee1c28fae..7c961c4bb 100644
--- a/tools/vgimportclone.c
+++ b/tools/vgimportclone.c
@@ -15,62 +15,39 @@
#include "tools.h"
#include "lib/cache/lvmcache.h"
#include "lib/filters/filter.h"
+#include "lib/device/device_id.h"
struct vgimportclone_params {
- unsigned done;
- unsigned total;
-
- int import_vg;
- int found_args;
- struct dm_list arg_import;
+ struct dm_list new_devs;
const char *base_vgname;
const char *old_vgname;
const char *new_vgname;
+ unsigned import_devices:1;
+ unsigned import_vg:1;
};
-struct vgimportclone_device {
- struct dm_list list;
- struct device *dev;
- unsigned found_in_vg : 1;
-};
-
-static int _vgimportclone_pv_single(struct cmd_context *cmd, struct volume_group *vg,
- struct physical_volume *pv, struct processing_handle *handle)
+static struct device_list *_get_device_list(struct dm_list *list, struct device *dev)
{
- struct vgimportclone_params *vp = (struct vgimportclone_params *) handle->custom_handle;
- struct vgimportclone_device *vd;
+ struct device_list *devl;
- if (vg && is_orphan_vg(vg->name)) {
- log_error("Cannot import clone of orphan PV %s.", dev_name(pv->dev));
- return ECMD_FAILED;
- }
-
- if (!(vd = dm_pool_zalloc(cmd->mem, sizeof(*vd)))) {
- log_error("alloc failed.");
- return ECMD_FAILED;
+ dm_list_iterate_items(devl, list) {
+ if (devl->dev == dev)
+ return devl;
}
-
- vd->dev = pv->dev;
- dm_list_add(&vp->arg_import, &vd->list);
-
- log_debug("vgimportclone dev %s VG %s found to import",
- dev_name(vd->dev), vg ? vg->name : "<none>");
-
- vp->found_args++;
-
- return ECMD_PROCESSED;
+ return NULL;
}
-static int _vgimportclone_vg_single(struct cmd_context *cmd, const char *vg_name,
- struct volume_group *vg, struct processing_handle *handle)
+static int _update_vg(struct cmd_context *cmd, struct volume_group *vg,
+ struct vgimportclone_params *vp)
{
char uuid[64] __attribute__((aligned(8)));
- struct vgimportclone_params *vp = (struct vgimportclone_params *) handle->custom_handle;
- struct vgimportclone_device *vd;
struct pv_list *pvl, *new_pvl;
struct lv_list *lvl;
+ struct device_list *devl;
+ struct dm_list tmp_devs;
int devs_used_for_lv = 0;
- int found;
+
+ dm_list_init(&tmp_devs);
if (vg_is_exported(vg) && !vp->import_vg) {
log_error("VG %s is exported, use the --import option.", vg->name);
@@ -88,6 +65,10 @@ static int _vgimportclone_vg_single(struct cmd_context *cmd, const char *vg_name
* that's being imported, so check DEV_USED_FOR_LV.
*/
dm_list_iterate_items(pvl, &vg->pvs) {
+ if (is_missing_pv(pvl->pv) || !pvl->pv->dev) {
+ log_error("VG is missing a device.");
+ goto bad;
+ }
if (pvl->pv->dev->flags & DEV_USED_FOR_LV) {
log_error("Device %s has active LVs, deactivate first.", dev_name(pvl->pv->dev));
devs_used_for_lv++;
@@ -98,21 +79,14 @@ static int _vgimportclone_vg_single(struct cmd_context *cmd, const char *vg_name
goto_bad;
/*
- * The arg_import list must match the PVs in VG.
+ * The new_devs list must match the PVs in VG.
*/
dm_list_iterate_items(pvl, &vg->pvs) {
- found = 0;
-
- dm_list_iterate_items(vd, &vp->arg_import) {
- if (pvl->pv->dev != vd->dev)
- continue;
- vd->found_in_vg = 1;
- found = 1;
- break;
- }
-
- if (!found) {
+ if ((devl = _get_device_list(&vp->new_devs, pvl->pv->dev))) {
+ dm_list_del(&devl->list);
+ dm_list_add(&tmp_devs, &devl->list);
+ } else {
if (!id_write_format(&pvl->pv->id, uuid, sizeof(uuid)))
goto_bad;
@@ -124,21 +98,21 @@ static int _vgimportclone_vg_single(struct cmd_context *cmd, const char *vg_name
}
}
- dm_list_iterate_items(vd, &vp->arg_import) {
- if (!vd->found_in_vg) {
- /* device arg is not in the VG. */
- log_error("Device %s was not found in VG %s.", dev_name(vd->dev), vg->name);
- log_error("The devices to import must match the devices in the VG.");
- goto bad;
- }
+ dm_list_iterate_items(devl, &vp->new_devs) {
+ /* device arg is not in the VG. */
+ log_error("Device %s was not found in VG %s.", dev_name(devl->dev), vg->name);
+ log_error("The devices to import must match the devices in the VG.");
+ goto bad;
}
+ dm_list_splice(&vp->new_devs, &tmp_devs);
+
/*
* Write changes.
*/
if (!archive(vg))
- return_ECMD_FAILED;
+ goto_bad;
if (vp->import_vg)
vg->status &= ~EXPORTED_VG;
@@ -175,6 +149,8 @@ static int _vgimportclone_vg_single(struct cmd_context *cmd, const char *vg_name
if (!id_create(&new_pvl->pv->id))
goto_bad;
+ memcpy(&pvl->pv->dev->pvid, &new_pvl->pv->id.uuid, ID_LEN);
+
dm_list_add(&vg->pv_write_list, &new_pvl->list);
}
@@ -183,97 +159,255 @@ static int _vgimportclone_vg_single(struct cmd_context *cmd, const char *vg_name
lvl->lv->lock_args = NULL;
}
+ /*
+ * Add the device id before writing the vg so that the device id
+ * will be included in the metadata. The device file is written
+ * (with these additions) at the end of the command.
+ */
+ if (vp->import_devices) {
+ dm_list_iterate_items(devl, &vp->new_devs) {
+ if (!device_id_add(cmd, devl->dev, devl->dev->pvid, NULL, NULL)) {
+ log_error("Failed to add device id for %s.", dev_name(devl->dev));
+ goto bad;
+ }
+ }
+ }
+
if (!vg_write(vg) || !vg_commit(vg))
goto_bad;
- return ECMD_PROCESSED;
+ return 1;
bad:
- return ECMD_FAILED;
+ return 0;
+}
+
+/*
+ * Create a list of devices that label_scan would scan excluding devs in
+ * new_devs.
+ */
+static int _get_other_devs(struct cmd_context *cmd, struct dm_list *new_devs, struct dm_list *other_devs)
+{
+ struct dev_iter *iter;
+ struct device *dev;
+ struct device_list *devl;
+
+ if (!(iter = dev_iter_create(cmd->filter, 0)))
+ return_0;
+
+ while ((dev = dev_iter_get(cmd, iter))) {
+ if (_get_device_list(new_devs, dev))
+ continue;
+ if (!(devl = malloc(sizeof(*devl))))
+ return_0;
+ devl->dev = dev;
+ dm_list_add(other_devs, &devl->list);
+ }
+
+ dev_iter_destroy(iter);
+ return 1;
}
int vgimportclone(struct cmd_context *cmd, int argc, char **argv)
{
struct vgimportclone_params vp = { 0 };
- struct processing_handle *handle = NULL;
- struct dm_list vgnameids_on_system; /* vgnameid_list */
+ struct dm_list vgnames;
struct vgnameid_list *vgnl;
- struct vgimportclone_device *vd;
- struct lvmcache_info *info;
+ struct device *dev;
+ struct device_list *devl;
+ struct dm_list other_devs;
+ struct volume_group *vg;
const char *vgname;
char base_vgname[NAME_LEN] = { 0 };
char tmp_vgname[NAME_LEN] = { 0 };
+ uint32_t lockd_state = 0;
+ uint32_t error_flags = 0;
unsigned int vgname_count;
int ret = ECMD_FAILED;
+ int i;
- if (!argc) {
- log_error("PV names required.");
- return EINVALID_CMD_LINE;
- }
-
- dm_list_init(&vgnameids_on_system);
- dm_list_init(&vp.arg_import);
+ dm_list_init(&vgnames);
+ dm_list_init(&other_devs);
+ dm_list_init(&vp.new_devs);
set_pv_notify(cmd);
+ vp.import_devices = arg_is_set(cmd, importdevices_ARG);
vp.import_vg = arg_is_set(cmd, import_ARG);
- if (!(handle = init_processing_handle(cmd, NULL))) {
- log_error("Failed to initialize processing handle.");
+ if (!lock_global(cmd, "ex"))
return ECMD_FAILED;
- }
- handle->custom_handle = &vp;
- if (!lock_global(cmd, "ex")) {
- destroy_processing_handle(cmd, handle);
+ clear_hint_file(cmd);
+
+ cmd->edit_devices_file = 1;
+
+ if (!setup_devices(cmd)) {
+ log_error("Failed to set up devices.");
return ECMD_FAILED;
}
/*
- * Find the devices being imported which are named on the command line.
- * They may be in the list of unchosen duplicates.
+ * When importing devices not in the devices file
+ * we cannot use the device id filter when looking
+ * for the devs.
*/
+ if (vp.import_devices) {
+ if (!cmd->enable_devices_file) {
+ log_print("Devices file not enabled, ignoring importdevices.");
+ vp.import_devices = 0;
+ } else if (!devices_file_exists(cmd)) {
+ log_print("Devices file does not exist, ignoring importdevices.");
+ vp.import_devices = 0;
+ } else {
+ cmd->filter_deviceid_skip = 1;
+ }
+ }
- log_debug("Finding devices to import.");
- cmd->cname->flags |= ENABLE_DUPLICATE_DEVS;
- process_each_pv(cmd, argc, argv, NULL, 0, 0, handle, _vgimportclone_pv_single);
+ /*
+ * For each device arg, get the dev from dev-cache.
+ * Only apply nodata filters when getting the devs
+ * from dev cache. The data filters will be applied
+ * next when label scan is done on them.
+ */
+ cmd->filter_nodata_only = 1;
- if (vp.found_args != argc) {
- log_error("Failed to find all devices.");
- goto out;
+ for (i = 0; i < argc; i++) {
+ if (!(dev = dev_cache_get(cmd, argv[i], cmd->filter))) {
+ /* FIXME: if filtered print which */
+ log_error("Failed to find device %s.", argv[i]);
+ goto out;
+ }
+
+ if (!(devl = malloc(sizeof(*devl))))
+ goto_out;
+
+ devl->dev = dev;
+ dm_list_add(&vp.new_devs, &devl->list);
+ }
+
+ /*
+ * Clear the result of nodata filtering so all
+ * filters will be applied in label_scan.
+ */
+ dm_list_iterate_items(devl, &vp.new_devs)
+ cmd->filter->wipe(cmd, cmd->filter, devl->dev, NULL);
+
+ /*
+ * Scan lvm info from each new dev, and apply the filters
+ * again, this time applying filters that use data.
+ */
+ log_debug("scan new devs");
+
+ label_scan_setup_bcache();
+
+ cmd->filter_nodata_only = 0;
+
+ label_scan_devs(cmd, cmd->filter, &vp.new_devs);
+
+ /*
+ * Check if any new devs were excluded by filters
+ * in label scan, where all filters were applied.
+ * (incl those that need data.)
+ */
+ dm_list_iterate_items(devl, &vp.new_devs) {
+ if (!cmd->filter->passes_filter(cmd, cmd->filter, devl->dev, "persistent")) {
+ /* FIXME: print which filter */
+ log_error("Device %s was excluded by filters.", dev_name(devl->dev));
+ goto out;
+ }
}
/*
- * Find the VG name of the PVs being imported, save as old_vgname.
- * N.B. If vd->dev is a duplicate, then it may not match info->dev.
+ * Look up vg info in lvmcache for each new_devs entry. This info was
+ * found by label scan. Verify all the new devs are from the same vg.
+ * The lvmcache at this point only reflects a label scan, not a vg_read
+ * which would assign PV info's for PVs without metadata. So this
+ * check is incomplete, and the same vg for devs is verified again
+ * later.
*/
+ dm_list_iterate_items(devl, &vp.new_devs) {
+ struct lvmcache_info *info;
- dm_list_iterate_items(vd, &vp.arg_import) {
- if (!(info = lvmcache_info_from_pvid(vd->dev->pvid, NULL, 0))) {
- log_error("Failed to find PVID for device %s in lvmcache.", dev_name(vd->dev));
+ if (!(info = lvmcache_info_from_pvid(devl->dev->pvid, devl->dev, 0))) {
+ log_error("Failed to find PVID for device %s.", dev_name(devl->dev));
goto out;
}
if (!(vgname = lvmcache_vgname_from_info(info))) {
- log_error("Failed to find VG name for device %s in lvmcache.", dev_name(vd->dev));
- goto out;
+ /* The PV may not have metadata, this will be resolved in
+ the process_each_vg/vg_read at the end. */
+ continue;
}
if (!vp.old_vgname) {
if (!(vp.old_vgname = dm_pool_strdup(cmd->mem, vgname)))
goto_out;
- } else {
- if (strcmp(vp.old_vgname, vgname)) {
- log_error("Devices must be from the same VG.");
- goto out;
- }
+ } else if (strcmp(vp.old_vgname, vgname)) {
+ log_error("Devices must be from the same VG.");
+ goto out;
}
}
+ if (!vp.old_vgname) {
+ log_error("No VG found on devices.");
+ goto out;
+ }
+
+ /*
+ * Get rid of lvmcache info from the new devs because we are going to
+ * read the other devs next (which conflict with the new devs because
+ * of the duplicated info.)
+ */
+ lvmcache_destroy(cmd, 1, 0);
+
+ /*
+ * Now processing other devs instead of new devs, so return to using
+ * the deviceid filter. (wiping filters not needed since these other
+ * devs have not been filtered yet.)
+ */
+ cmd->filter_deviceid_skip = 0;
+
+ /*
+ * Scan all other devs (devs that would normally be seen excluding new
+ * devs). This is necessary to check if the new vgname conflicts with
+ * an existing vgname on other devices. We don't need to actually
+ * process any existing VGs, we only process the VG on the new devs
+ * being imported after this.
+ *
+ * This only requires a label_scan of the other devs which is enough to
+ * see what the other vgnames are.
+ *
+ * Only apply nodata filters when creating the other_devs list.
+ * Then apply all filters when label_scan_devs processes the label.
+ */
+
+ log_debug("get other devices");
+
+ cmd->filter_nodata_only = 1;
+
+ if (!_get_other_devs(cmd, &vp.new_devs, &other_devs))
+ goto_out;
+
+ log_debug("scan other devices");
+
+ cmd->filter_nodata_only = 0;
+
+ /*
+ * Clear the result of nodata filtering so all
+ * filters will be applied in label_scan.
+ */
+ dm_list_iterate_items(devl, &other_devs)
+ cmd->filter->wipe(cmd, cmd->filter, devl->dev, NULL);
+
+ label_scan_devs(cmd, cmd->filter, &other_devs);
+
+ if (!lvmcache_get_vgnameids(cmd, &vgnames, NULL, 0))
+ goto_out;
+
/*
* Pick a new VG name, save as new_vgname. The new name begins with
* the basevgname or old_vgname, plus a $i suffix, if necessary, to
- * make it unique. This requires comparing the old_vgname with all the
- * VG names on the system.
+ * make it unique.
*/
if (arg_is_set(cmd, basevgname_ARG)) {
@@ -296,11 +430,8 @@ int vgimportclone(struct cmd_context *cmd, int argc, char **argv)
vgname_count = 1;
}
- if (!lvmcache_get_vgnameids(cmd, &vgnameids_on_system, NULL, 0))
- goto_out;
-
retry_name:
- dm_list_iterate_items(vgnl, &vgnameids_on_system) {
+ dm_list_iterate_items(vgnl, &vgnames) {
if (!strcmp(vgnl->vg_name, tmp_vgname)) {
vgname_count++;
if (dm_snprintf(tmp_vgname, sizeof(tmp_vgname), "%s%u", base_vgname, vgname_count) < 0) {
@@ -315,42 +446,61 @@ retry_name:
goto_out;
log_debug("Using new VG name %s.", vp.new_vgname);
- lvmcache_destroy(cmd, 1, 0);
-
/*
- * Create a device filter so that we are only working with the devices
- * in arg_import. With the original devs hidden (that arg_import were
- * cloned from), we can read and write the cloned PVs and VG without
- * touching the original PVs/VG.
+ * Get rid of lvmcache info from the other devs because we are going to
+ * read the new devs again, now to update them.
*/
+ lvmcache_destroy(cmd, 1, 0);
- init_internal_filtering(1);
- dm_list_iterate_items(vd, &vp.arg_import)
- internal_filter_allow(cmd->mem, vd->dev);
- refresh_filters(cmd);
-
- log_debug("Changing VG %s to %s.", vp.old_vgname, vp.new_vgname);
+ log_debug("import vg on new devices");
if (!lock_vol(cmd, vp.new_vgname, LCK_VG_WRITE, NULL)) {
log_error("Can't get lock for new VG name %s", vp.new_vgname);
goto out;
}
- /*
- * Trying to lock the duplicated VG would conflict with the original,
- * and it's not needed because the new VG will be imported as a local VG.
- */
- cmd->lockd_vg_disable = 1;
+ /* No filter used since these devs have already been filtered above. */
+ label_scan_devs_rw(cmd, NULL, &vp.new_devs);
- clear_hint_file(cmd);
+ cmd->can_use_one_scan = 1;
+ cmd->include_exported_vgs = 1;
- ret = process_each_vg(cmd, 0, NULL, vp.old_vgname, NULL, READ_FOR_UPDATE, 0, handle, _vgimportclone_vg_single);
+ vg = vg_read(cmd, vp.old_vgname, NULL, READ_WITHOUT_LOCK | READ_FOR_UPDATE, lockd_state, &error_flags, NULL);
+ if (!vg) {
+ log_error("Failed to read VG %s from devices being imported.", vp.old_vgname);
+ unlock_vg(cmd, NULL, vp.new_vgname);
+ goto out;
+ }
+
+ if (error_flags) {
+ log_error("Error reading VG %s from devices being imported.", vp.old_vgname);
+ release_vg(vg);
+ unlock_vg(cmd, NULL, vp.new_vgname);
+ goto out;
+ }
+
+ if (!_update_vg(cmd, vg, &vp)) {
+ log_error("Failed to update VG on devices being imported.");
+ release_vg(vg);
+ unlock_vg(cmd, NULL, vp.new_vgname);
+ goto out;
+ }
+ release_vg(vg);
unlock_vg(cmd, NULL, vp.new_vgname);
-out:
- internal_filter_clear();
- init_internal_filtering(0);
- destroy_processing_handle(cmd, handle);
+ /*
+ * Should we be using device_ids_validate to check/fix other
+ * devs in the devices file?
+ */
+ if (vp.import_devices) {
+ if (!device_ids_write(cmd)) {
+ log_error("Failed to write devices file.");
+ goto out;
+ }
+ }
+ ret = ECMD_PROCESSED;
+out:
+ unlock_devices_file(cmd);
return ret;
}
diff --git a/tools/vgimportdevices.c b/tools/vgimportdevices.c
new file mode 100644
index 000000000..2ba266cc2
--- /dev/null
+++ b/tools/vgimportdevices.c
@@ -0,0 +1,210 @@
+/*
+ * 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 %32s.",
+ 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;
+
+ /*
+ * Prepare devices file preemptively because the error path for this
+ * case from process_each is not as clean.
+ */
+ if (!setup_devices_file(cmd)) {
+ log_error("Failed to set up devices file.");
+ return ECMD_FAILED;
+ }
+ if (!devices_file_exists(cmd) && !devices_file_touch(cmd)) {
+ log_error("Failed to create 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);
+
+ 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 a case where we do not want to be limited by an existing
+ * devices file 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)
+ */
+ ret = process_each_vg(cmd, argc, argv, NULL, NULL, READ_FOR_UPDATE,
+ 0, handle, _vgimportdevices_single);
+ if (ret == ECMD_FAILED)
+ goto out;
+
+ 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;
+}
+