[lvm-devel] master - add device hints to reduce scanning

David Teigland teigland at sourceware.org
Tue Jan 15 16:31:11 UTC 2019


Gitweb:        https://sourceware.org/git/?p=lvm2.git;a=commitdiff;h=6620dc9475d55207bfcd5e666f0379bcdf11831a
Commit:        6620dc9475d55207bfcd5e666f0379bcdf11831a
Parent:        81b3b71dae7775c93adebdea60308e11057a4ee8
Author:        David Teigland <teigland at redhat.com>
AuthorDate:    Fri Dec 7 14:35:22 2018 -0600
Committer:     David Teigland <teigland at redhat.com>
CommitterDate: Tue Jan 15 10:23:47 2019 -0600

add device hints to reduce scanning

Save the list of PVs in /run/lvm/hints.  These hints
are used to reduce scanning in a number of commands
to only the PVs on the system, or only the PVs in a
requested VG (rather than all devices on the system.)
---
 lib/Makefile.in                          |    1 +
 lib/cache/lvmcache.c                     |   18 +
 lib/cache/lvmcache.h                     |    3 +
 lib/commands/toolcontext.c               |    1 +
 lib/commands/toolcontext.h               |    7 +-
 lib/config/config_settings.h             |   14 +
 lib/config/defaults.h                    |    2 +
 lib/device/dev-cache.c                   |    6 +-
 lib/device/dev-cache.h                   |    3 +-
 lib/device/dev-type.c                    |   30 +
 lib/device/dev-type.h                    |    2 +
 lib/device/device.h                      |    1 +
 lib/filters/filter-composite.c           |   11 +-
 lib/filters/filter-fwraid.c              |    3 +-
 lib/filters/filter-internal.c            |    3 +-
 lib/filters/filter-md.c                  |    3 +-
 lib/filters/filter-mpath.c               |    3 +-
 lib/filters/filter-partitioned.c         |    3 +-
 lib/filters/filter-persistent.c          |    8 +-
 lib/filters/filter-regex.c               |    3 +-
 lib/filters/filter-signature.c           |    3 +-
 lib/filters/filter-sysfs.c               |    3 +-
 lib/filters/filter-type.c                |    3 +-
 lib/filters/filter-usable.c              |    3 +-
 lib/label/hints.c                        | 1241 ++++++++++++++++++++++++++++++
 lib/label/hints.h                        |   37 +
 lib/label/label.c                        |   71 ++-
 test/shell/hints.sh                      |  377 +++++++++
 test/shell/process-each-duplicate-pvs.sh |   18 +-
 tools/command.c                          |    2 +
 tools/commands.h                         |   34 +-
 tools/lvmcmdline.c                       |   30 +
 tools/pvchange.c                         |    2 +
 tools/pvcreate.c                         |    2 +
 tools/pvdisplay.c                        |    7 +
 tools/pvremove.c                         |    2 +
 tools/pvscan.c                           |    9 +
 tools/reporter.c                         |    7 +
 tools/toollib.c                          |   33 +-
 tools/tools.h                            |    3 +
 tools/vgcfgrestore.c                     |    2 +
 tools/vgcreate.c                         |    2 +
 tools/vgextend.c                         |    2 +
 tools/vgimportclone.c                    |    2 +
 tools/vgmerge.c                          |    2 +
 tools/vgreduce.c                         |    2 +
 tools/vgremove.c                         |    2 +
 tools/vgrename.c                         |    2 +
 tools/vgsplit.c                          |    2 +
 49 files changed, 1979 insertions(+), 51 deletions(-)

diff --git a/lib/Makefile.in b/lib/Makefile.in
index bde66f9..654c322 100644
--- a/lib/Makefile.in
+++ b/lib/Makefile.in
@@ -61,6 +61,7 @@ SOURCES =\
 	format_text/text_label.c \
 	freeseg/freeseg.c \
 	label/label.c \
+	label/hints.c \
 	locking/file_locking.c \
 	locking/locking.c \
 	log/log.c \
diff --git a/lib/cache/lvmcache.c b/lib/cache/lvmcache.c
index da8b4d8..0ffa604 100644
--- a/lib/cache/lvmcache.c
+++ b/lib/cache/lvmcache.c
@@ -69,6 +69,7 @@ static DM_LIST_INIT(_unused_duplicate_devs);
 static int _scanning_in_progress = 0;
 static int _vgs_locked = 0;
 static int _found_duplicate_pvs = 0;	/* If we never see a duplicate PV we can skip checking for them later. */
+static int _found_duplicate_vgnames = 0;
 
 int lvmcache_init(struct cmd_context *cmd)
 {
@@ -131,6 +132,11 @@ int lvmcache_found_duplicate_pvs(void)
 	return _found_duplicate_pvs;
 }
 
+int lvmcache_found_duplicate_vgnames(void)
+{
+	return _found_duplicate_vgnames;
+}
+
 int lvmcache_get_unused_duplicate_devs(struct cmd_context *cmd, struct dm_list *head)
 {
 	struct device_list *devl, *devl2;
@@ -1225,6 +1231,8 @@ static int _insert_vginfo(struct lvmcache_vginfo *new_vginfo, const char *vgid,
 				     sizeof(uuid_primary)))
 			return_0;
 
+		_found_duplicate_vgnames = 1;
+
 		/*
 		 * vginfo is kept for each VG with the same name.
 		 * They are saved with the vginfo->next list.
@@ -2278,3 +2286,13 @@ int lvmcache_scan_mismatch(struct cmd_context *cmd, const char *vgname, const ch
 	return 1;
 }
 
+int lvmcache_vginfo_has_pvid(struct lvmcache_vginfo *vginfo, char *pvid)
+{
+	struct lvmcache_info *info;
+
+	dm_list_iterate_items(info, &vginfo->infos) {
+		if (!strcmp(info->dev->pvid, pvid))
+			return 1;
+	}
+	return 0;
+}
diff --git a/lib/cache/lvmcache.h b/lib/cache/lvmcache.h
index ba60405..12f17df 100644
--- a/lib/cache/lvmcache.h
+++ b/lib/cache/lvmcache.h
@@ -169,6 +169,7 @@ int lvmcache_vgid_is_cached(const char *vgid);
 uint64_t lvmcache_smallest_mda_size(struct lvmcache_info *info);
 
 int lvmcache_found_duplicate_pvs(void);
+int lvmcache_found_duplicate_vgnames(void);
 
 void lvmcache_pvscan_duplicate_check(struct cmd_context *cmd);
 
@@ -198,6 +199,8 @@ void lvmcache_set_independent_location(const char *vgname);
 
 int lvmcache_scan_mismatch(struct cmd_context *cmd, const char *vgname, const char *vgid);
 
+int lvmcache_vginfo_has_pvid(struct lvmcache_vginfo *vginfo, char *pvid);
+
 /*
  * These are clvmd-specific functions and are not related to lvmcache.
  * FIXME: rename these with a clvm_ prefix in place of lvmcache_
diff --git a/lib/commands/toolcontext.c b/lib/commands/toolcontext.c
index 63eafe8..39ab3df 100644
--- a/lib/commands/toolcontext.c
+++ b/lib/commands/toolcontext.c
@@ -1484,6 +1484,7 @@ struct cmd_context *create_config_context(void)
 
 	dm_list_init(&cmd->config_files);
 	dm_list_init(&cmd->tags);
+	dm_list_init(&cmd->hints);
 
 	if (!_init_lvm_conf(cmd))
 		goto_out;
diff --git a/lib/commands/toolcontext.h b/lib/commands/toolcontext.h
index 6396f6c..e8ce312 100644
--- a/lib/commands/toolcontext.h
+++ b/lib/commands/toolcontext.h
@@ -172,11 +172,16 @@ struct cmd_context {
 	unsigned is_clvmd:1;
 	unsigned use_full_md_check:1;
 	unsigned is_activating:1;
+	unsigned enable_hints:1;		/* hints are enabled for cmds in general */
+	unsigned use_hints:1;			/* if hints are enabled this cmd can use them */
+	unsigned pvscan_recreate_hints:1;	/* enable special case hint handling for pvscan --cache */
+	unsigned scan_lvs:1;
 
 	/*
-	 * Filtering.
+	 * Devices and filtering.
 	 */
 	struct dev_filter *filter;
+	struct dm_list hints;
 
 	/*
 	 * Configuration.
diff --git a/lib/config/config_settings.h b/lib/config/config_settings.h
index f1d2826..e15494d 100644
--- a/lib/config/config_settings.h
+++ b/lib/config/config_settings.h
@@ -255,6 +255,20 @@ cfg(devices_external_device_info_source_CFG, "external_device_info_source", devi
 	"    compiled with udev support.\n"
 	"#\n")
 
+cfg(devices_hints_CFG, "hints", devices_CFG_SECTION, 0, CFG_TYPE_STRING, DEFAULT_HINTS, vsn(2, 3, 2), NULL, 0, NULL,
+	"Use a local file to remember which devices have PVs on them.\n"
+	"Some commands will use this as an optimization to reduce device\n"
+	"scanning, and will only scan the listed PVs. Removing the hint file\n"
+	"will cause lvm to generate a new one. Disable hints if PVs will\n"
+	"be copied onto devices using non-lvm commands, like dd.\n"
+	"#\n"
+	"Accepted values:\n"
+	"  all\n"
+	"    Use all hints.\n"
+	"  none\n"
+	"    Use no hints.\n"
+	"#\n")
+
 cfg_array(devices_preferred_names_CFG, "preferred_names", devices_CFG_SECTION, CFG_ALLOW_EMPTY | CFG_DEFAULT_UNDEFINED , CFG_TYPE_STRING, NULL, vsn(1, 2, 19), NULL, 0, NULL,
 	"Select which path name to display for a block device.\n"
 	"If multiple path names exist for a block device, and LVM needs to\n"
diff --git a/lib/config/defaults.h b/lib/config/defaults.h
index b45324b..06a5ecf 100644
--- a/lib/config/defaults.h
+++ b/lib/config/defaults.h
@@ -312,4 +312,6 @@
 
 #define DEFAULT_SCAN_LVS 1
 
+#define DEFAULT_HINTS "all"
+
 #endif				/* _LVM_DEFAULTS_H */
diff --git a/lib/device/dev-cache.c b/lib/device/dev-cache.c
index fb48f1a..8afebfe 100644
--- a/lib/device/dev-cache.c
+++ b/lib/device/dev-cache.c
@@ -1473,7 +1473,7 @@ struct device *dev_cache_get(struct cmd_context *cmd, const char *name, struct d
 		return d;
 
 	if (f && !(d->flags & DEV_REGULAR)) {
-		ret = f->passes_filter(cmd, f, d);
+		ret = f->passes_filter(cmd, f, d, NULL);
 
 		if (ret == -EAGAIN) {
 			log_debug_devs("get device by name defer filter %s", dev_name(d));
@@ -1546,7 +1546,7 @@ struct device *dev_cache_get_by_devt(struct cmd_context *cmd, dev_t dev, struct
 	if (!f)
 		return d;
 
-	ret = f->passes_filter(cmd, f, d);
+	ret = f->passes_filter(cmd, f, d, NULL);
 
 	if (ret == -EAGAIN) {
 		log_debug_devs("get device by number defer filter %s", dev_name(d));
@@ -1603,7 +1603,7 @@ struct device *dev_iter_get(struct cmd_context *cmd, struct dev_iter *iter)
 		f = iter->filter;
 
 		if (f && !(d->flags & DEV_REGULAR)) {
-			ret = f->passes_filter(cmd, f, d);
+			ret = f->passes_filter(cmd, f, d, NULL);
 
 			if (ret == -EAGAIN) {
 				log_debug_devs("get device by iter defer filter %s", dev_name(d));
diff --git a/lib/device/dev-cache.h b/lib/device/dev-cache.h
index 41c4a9c..9233c52 100644
--- a/lib/device/dev-cache.h
+++ b/lib/device/dev-cache.h
@@ -25,11 +25,12 @@ struct cmd_context;
  * predicate for devices.
  */
 struct dev_filter {
-	int (*passes_filter) (struct cmd_context *cmd, struct dev_filter *f, struct device *dev);
+	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 *private;
 	unsigned use_count;
+	const char *name;
 };
 
 int dev_cache_index_devs(void);
diff --git a/lib/device/dev-type.c b/lib/device/dev-type.c
index 638f4b2..9278b51 100644
--- a/lib/device/dev-type.c
+++ b/lib/device/dev-type.c
@@ -79,6 +79,36 @@ int dev_is_pmem(struct device *dev)
 	return 0;
 }
 
+int dev_is_lv(struct device *dev)
+{
+	FILE *fp;
+	char path[PATH_MAX];
+	char buffer[64];
+
+	if (dm_snprintf(path, sizeof(path), "%sdev/block/%d:%d/dm/uuid",
+			dm_sysfs_dir(),
+			(int) MAJOR(dev->dev),
+			(int) MINOR(dev->dev)) < 0) {
+		log_warn("Sysfs dm uuid path for %s is too long.", dev_name(dev));
+		return 0;
+	}
+
+	if (!(fp = fopen(path, "r")))
+		return 0;
+
+	if (!fgets(buffer, sizeof(buffer), fp)) {
+		log_warn("Failed to read %s.", path);
+		fclose(fp);
+		return 0;
+	}
+
+	fclose(fp);
+
+	if (!strncmp(buffer, "LVM-", 4))
+		return 1;
+	return 0;
+}
+
 struct dev_types *create_dev_types(const char *proc_dir,
 				   const struct dm_config_node *cn)
 {
diff --git a/lib/device/dev-type.h b/lib/device/dev-type.h
index 75539e8..d349905 100644
--- a/lib/device/dev-type.h
+++ b/lib/device/dev-type.h
@@ -95,4 +95,6 @@ int dev_is_rotational(struct dev_types *dt, struct device *dev);
 
 int dev_is_pmem(struct device *dev);
 
+int dev_is_lv(struct device *dev);
+
 #endif
diff --git a/lib/device/device.h b/lib/device/device.h
index e879dbb..fa7e738 100644
--- a/lib/device/device.h
+++ b/lib/device/device.h
@@ -36,6 +36,7 @@
 #define DEV_FILTER_AFTER_SCAN	0x00002000	/* apply filter after bcache has data */
 #define DEV_FILTER_OUT_SCAN	0x00004000	/* filtered out during label scan */
 #define DEV_BCACHE_WRITE	0x00008000      /* bcache_fd is open with RDWR */
+#define DEV_SCAN_FOUND_LABEL	0x00010000      /* label scan read dev and found label */
 
 /*
  * Support for external device info.
diff --git a/lib/filters/filter-composite.c b/lib/filters/filter-composite.c
index a9374ab..b0063f1 100644
--- a/lib/filters/filter-composite.c
+++ b/lib/filters/filter-composite.c
@@ -18,13 +18,15 @@
 #include "lib/filters/filter.h"
 #include "lib/device/device.h"
 
-static int _and_p(struct cmd_context *cmd, struct dev_filter *f, struct device *dev)
+static int _and_p(struct cmd_context *cmd, struct dev_filter *f, struct device *dev, const char *use_filter_name)
 {
 	struct dev_filter **filters;
 	int ret;
 
 	for (filters = (struct dev_filter **) f->private; *filters; ++filters) {
-		ret = (*filters)->passes_filter(cmd, *filters, dev);
+		if (use_filter_name && strcmp((*filters)->name, use_filter_name))
+			continue;
+		ret = (*filters)->passes_filter(cmd, *filters, dev, use_filter_name);
 
 		if (!ret)
 			return 0;	/* No 'stack': a filter, not an error. */
@@ -33,12 +35,12 @@ static int _and_p(struct cmd_context *cmd, struct dev_filter *f, struct device *
 	return 1;
 }
 
-static int _and_p_with_dev_ext_info(struct cmd_context *cmd, struct dev_filter *f, struct device *dev)
+static int _and_p_with_dev_ext_info(struct cmd_context *cmd, struct dev_filter *f, struct device *dev, const char *use_filter_name)
 {
 	int r;
 
 	dev_ext_enable(dev, external_device_info_source());
-	r = _and_p(cmd, f, dev);
+	r = _and_p(cmd, f, dev, use_filter_name);
 	dev_ext_disable(dev);
 
 	return r;
@@ -93,6 +95,7 @@ struct dev_filter *composite_filter_create(int n, int use_dev_ext_info, struct d
 	cft->wipe = _wipe;
 	cft->use_count = 0;
 	cft->private = filters_copy;
+	cft->name = "composite";
 
 	log_debug_devs("Composite filter initialised.");
 
diff --git a/lib/filters/filter-fwraid.c b/lib/filters/filter-fwraid.c
index 6f47692..992eba8 100644
--- a/lib/filters/filter-fwraid.c
+++ b/lib/filters/filter-fwraid.c
@@ -65,7 +65,7 @@ static int _dev_is_fwraid(struct device *dev)
 #define MSG_SKIPPING "%s: Skipping firmware RAID component device"
 
 static int _ignore_fwraid(struct cmd_context *cmd, struct dev_filter *f __attribute__((unused)),
-			   struct device *dev)
+			   struct device *dev, const char *use_filter_name)
 {
 	int ret;
 
@@ -113,6 +113,7 @@ struct dev_filter *fwraid_filter_create(struct dev_types *dt __attribute__((unus
 	f->destroy = _destroy;
 	f->use_count = 0;
 	f->private = NULL;
+	f->name = "fwraid";
 
 	log_debug_devs("Firmware RAID filter initialised.");
 
diff --git a/lib/filters/filter-internal.c b/lib/filters/filter-internal.c
index 8cc0011..c10e810 100644
--- a/lib/filters/filter-internal.c
+++ b/lib/filters/filter-internal.c
@@ -38,7 +38,7 @@ void internal_filter_clear(void)
 }
 
 static int _passes_internal(struct cmd_context *cmd, struct dev_filter *f __attribute__((unused)),
-			    struct device *dev)
+			    struct device *dev, const char *use_filter_name)
 {
 	struct device_list *devl;
 
@@ -74,6 +74,7 @@ struct dev_filter *internal_filter_create(void)
 	f->passes_filter = _passes_internal;
 	f->destroy = _destroy;
 	f->use_count = 0;
+	f->name = "internal";
 
 	log_debug_devs("Internal filter initialised.");
 
diff --git a/lib/filters/filter-md.c b/lib/filters/filter-md.c
index 9cc1a06..d6dd28c 100644
--- a/lib/filters/filter-md.c
+++ b/lib/filters/filter-md.c
@@ -82,7 +82,7 @@
  * that will not pass.
  */
 
-static int _passes_md_filter(struct cmd_context *cmd, struct dev_filter *f __attribute__((unused)), struct device *dev)
+static int _passes_md_filter(struct cmd_context *cmd, struct dev_filter *f __attribute__((unused)), struct device *dev, const char *use_filter_name)
 {
 	int ret;
 
@@ -145,6 +145,7 @@ struct dev_filter *md_filter_create(struct cmd_context *cmd, struct dev_types *d
 	f->destroy = _destroy;
 	f->use_count = 0;
 	f->private = dt;
+	f->name = "md";
 
 	log_debug_devs("MD filter initialised.");
 
diff --git a/lib/filters/filter-mpath.c b/lib/filters/filter-mpath.c
index bcd1e52..f0374b4 100644
--- a/lib/filters/filter-mpath.c
+++ b/lib/filters/filter-mpath.c
@@ -247,7 +247,7 @@ static int _dev_is_mpath(struct dev_filter *f, struct device *dev)
 
 #define MSG_SKIPPING "%s: Skipping mpath component device"
 
-static int _ignore_mpath(struct cmd_context *cmd, struct dev_filter *f, struct device *dev)
+static int _ignore_mpath(struct cmd_context *cmd, struct dev_filter *f, struct device *dev, const char *use_filter_name)
 {
 	if (_dev_is_mpath(f, dev) == 1) {
 		if (dev->ext.src == DEV_EXT_NONE)
@@ -288,6 +288,7 @@ struct dev_filter *mpath_filter_create(struct dev_types *dt)
 	f->destroy = _destroy;
 	f->use_count = 0;
 	f->private = dt;
+	f->name = "mpath";
 
 	log_debug_devs("mpath filter initialised.");
 
diff --git a/lib/filters/filter-partitioned.c b/lib/filters/filter-partitioned.c
index 6418cdf..1a70054 100644
--- a/lib/filters/filter-partitioned.c
+++ b/lib/filters/filter-partitioned.c
@@ -19,7 +19,7 @@
 
 #define MSG_SKIPPING "%s: Skipping: Partition table signature found"
 
-static int _passes_partitioned_filter(struct cmd_context *cmd, struct dev_filter *f, struct device *dev)
+static int _passes_partitioned_filter(struct cmd_context *cmd, struct dev_filter *f, struct device *dev, const char *use_filter_name)
 {
 	struct dev_types *dt = (struct dev_types *) f->private;
 	int ret;
@@ -66,6 +66,7 @@ struct dev_filter *partitioned_filter_create(struct dev_types *dt)
 	f->destroy = _partitioned_filter_destroy;
 	f->use_count = 0;
 	f->private = dt;
+	f->name = "partitioned";
 
 	log_debug_devs("Partitioned filter initialised.");
 
diff --git a/lib/filters/filter-persistent.c b/lib/filters/filter-persistent.c
index 130b1e5..afa32d4 100644
--- a/lib/filters/filter-persistent.c
+++ b/lib/filters/filter-persistent.c
@@ -71,13 +71,16 @@ static void _persistent_filter_wipe(struct dev_filter *f)
 	dm_hash_wipe(pf->devices);
 }
 
-static int _lookup_p(struct cmd_context *cmd, struct dev_filter *f, struct device *dev)
+static int _lookup_p(struct cmd_context *cmd, struct dev_filter *f, struct device *dev, const char *use_filter_name)
 {
 	struct pfilter *pf = (struct pfilter *) f->private;
 	void *l;
 	struct dm_str_list *sl;
 	int pass = 1;
 
+	if (use_filter_name && strcmp(f->name, use_filter_name))
+		return pf->real->passes_filter(cmd, pf->real, dev, use_filter_name);
+
 	if (dm_list_empty(&dev->aliases)) {
 		log_debug_devs("%d:%d: filter cache skipping (no name)",
 				(int)MAJOR(dev->dev), (int)MINOR(dev->dev));
@@ -102,7 +105,7 @@ static int _lookup_p(struct cmd_context *cmd, struct dev_filter *f, struct devic
 	if (!l) {
 		dev->flags &= ~DEV_FILTER_AFTER_SCAN;
 
-		pass = pf->real->passes_filter(cmd, pf->real, dev);
+		pass = pf->real->passes_filter(cmd, pf->real, dev, use_filter_name);
 
 		if (!pass) {
 			/*
@@ -182,6 +185,7 @@ struct dev_filter *persistent_filter_create(struct dev_types *dt, struct dev_fil
 	f->use_count = 0;
 	f->private = pf;
 	f->wipe = _persistent_filter_wipe;
+	f->name = "persistent";
 
 	log_debug_devs("Persistent filter initialised.");
 
diff --git a/lib/filters/filter-regex.c b/lib/filters/filter-regex.c
index 1a8e8a2..e439b36 100644
--- a/lib/filters/filter-regex.c
+++ b/lib/filters/filter-regex.c
@@ -145,7 +145,7 @@ static int _build_matcher(struct rfilter *rf, const struct dm_config_value *val)
 	return r;
 }
 
-static int _accept_p(struct cmd_context *cmd, struct dev_filter *f, struct device *dev)
+static int _accept_p(struct cmd_context *cmd, struct dev_filter *f, struct device *dev, const char *use_filter_name)
 {
 	int m, first = 1, rejected = 0;
 	struct rfilter *rf = (struct rfilter *) f->private;
@@ -212,6 +212,7 @@ struct dev_filter *regex_filter_create(const struct dm_config_value *patterns)
 	f->destroy = _regex_destroy;
 	f->use_count = 0;
 	f->private = rf;
+	f->name = "regex";
 
 	log_debug_devs("Regex filter initialised.");
 
diff --git a/lib/filters/filter-signature.c b/lib/filters/filter-signature.c
index 5c5796f..6a81203 100644
--- a/lib/filters/filter-signature.c
+++ b/lib/filters/filter-signature.c
@@ -22,7 +22,7 @@
 #define BUFSIZE 4096
 
 static int _ignore_signature(struct cmd_context *cmd, struct dev_filter *f __attribute__((unused)),
-		      struct device *dev)
+		      struct device *dev, const char *use_filter_name)
 {
 	char buf[BUFSIZE];
 	int ret = 0;
@@ -81,6 +81,7 @@ struct dev_filter *signature_filter_create(struct dev_types *dt)
 	f->destroy = _destroy;
 	f->use_count = 0;
 	f->private = dt;
+	f->name = "signature";
 
 	log_debug_devs("signature filter initialised.");
 
diff --git a/lib/filters/filter-sysfs.c b/lib/filters/filter-sysfs.c
index c77c4a6..ebca808 100644
--- a/lib/filters/filter-sysfs.c
+++ b/lib/filters/filter-sysfs.c
@@ -260,7 +260,7 @@ static int _init_devs(struct dev_set *ds)
 }
 
 
-static int _accept_p(struct cmd_context *cmd, struct dev_filter *f, struct device *dev)
+static int _accept_p(struct cmd_context *cmd, struct dev_filter *f, struct device *dev, const char *use_filter_name)
 {
 	struct dev_set *ds = (struct dev_set *) f->private;
 
@@ -323,6 +323,7 @@ struct dev_filter *sysfs_filter_create(void)
 	f->destroy = _destroy;
 	f->use_count = 0;
 	f->private = ds;
+	f->name = "sysfs";
 
 	log_debug_devs("Sysfs filter initialised.");
 
diff --git a/lib/filters/filter-type.c b/lib/filters/filter-type.c
index 3b0a644..1d08370 100644
--- a/lib/filters/filter-type.c
+++ b/lib/filters/filter-type.c
@@ -17,7 +17,7 @@
 #include "lib/misc/lib.h"
 #include "lib/filters/filter.h"
 
-static int _passes_lvm_type_device_filter(struct cmd_context *cmd, struct dev_filter *f, struct device *dev)
+static int _passes_lvm_type_device_filter(struct cmd_context *cmd, struct dev_filter *f, struct device *dev, const char *use_filter_name)
 {
 	struct dev_types *dt = (struct dev_types *) f->private;
 	const char *name = dev_name(dev);
@@ -53,6 +53,7 @@ struct dev_filter *lvm_type_filter_create(struct dev_types *dt)
 	f->destroy = _lvm_type_filter_destroy;
 	f->use_count = 0;
 	f->private = dt;
+	f->name = "type";
 
 	log_debug_devs("LVM type filter initialised.");
 
diff --git a/lib/filters/filter-usable.c b/lib/filters/filter-usable.c
index 6997368..b3ff650 100644
--- a/lib/filters/filter-usable.c
+++ b/lib/filters/filter-usable.c
@@ -105,7 +105,7 @@ static int _check_pv_min_size(struct device *dev)
 	return 0;
 }
 
-static int _passes_usable_filter(struct cmd_context *cmd, struct dev_filter *f, struct device *dev)
+static int _passes_usable_filter(struct cmd_context *cmd, struct dev_filter *f, struct device *dev, const char *use_filter_name)
 {
 	struct filter_data *data = f->private;
 	filter_mode_t mode = data->mode;
@@ -185,6 +185,7 @@ struct dev_filter *usable_filter_create(struct cmd_context *cmd, struct dev_type
 	f->passes_filter = _passes_usable_filter;
 	f->destroy = _usable_filter_destroy;
 	f->use_count = 0;
+	f->name = "usable";
 
 	if (!(data = zalloc(sizeof(struct filter_data)))) {
 		log_error("Usable device filter mode allocation failed");
diff --git a/lib/label/hints.c b/lib/label/hints.c
new file mode 100644
index 0000000..bdd70a9
--- /dev/null
+++ b/lib/label/hints.c
@@ -0,0 +1,1241 @@
+/*
+ * Copyright (C) 2018 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
+ */
+
+/*
+ * There are four different ways that commands handle hints:
+ *
+ * 1. Commands that use hints to reduce scanning, and create new
+ *    hints when needed:
+ *
+ *    fullreport, lvchange, lvcreate, lvdisplay, lvremove, lvresize,
+ *    lvs, pvdisplay, lvpoll, pvs, vgchange, vgck, vgdisplay, vgs,
+ *    lvextend, lvreduce, lvrename
+ *
+ * 2. Commands that just remove existing hints:
+ *
+ *    pvcreate, pvremove, vgcreate, vgremove, vgextend, vgreduce,
+ *    vgcfgrestore, vgimportclone, vgmerge, vgsplit, pvchange
+ *
+ * 3. Commands that ignore hints:
+ *
+ *    lvconvert, lvmdiskscan, lvscan, pvresize, pvck, pvmove, pvscan,
+ *    vgcfgbackup, vgexport, vgimport, vgscan, pvs -a, pvdisplay -a
+ *
+ * 4. Command that removes existing hints and creates new hints:
+ *
+ *    pvscan --cache
+ *
+ *
+ * For 1, hints are used to reduce scanning by:
+ * . get the list of all devices on the system from dev_cache_scan()
+ * . remove devices from that list which are not listed in hints
+ * . do scan the remaining list of devices
+ *
+ * label_scan() is where those steps are implemented:
+ *      . dev_cache_scan() produces all_devs list
+ *      . get_hints(all_devs, scan_devs, &newhints)
+ *        moves some devs from all_devs to scan_devs list (or sets newhints
+ *        if no hints are applied, and a new hints file should be created)
+ *      . _scan_list(scan_devs) does the label scan
+ *      . if newhints was set, call write_hint_file() to create new hints
+ *        based on which devs _scan_list saw an lvm label on
+ *
+ * For 2, commands that change "global state" remove existing hints.
+ * The hints become incorrect as a result of the changes the command
+ * is making. "global state" is lvm state that is not isolated within a VG.
+ * (This is basically: which devices are PVs, and which VG names are used.)
+ *
+ * Commands that change global state do not create new hints because
+ * it's much simpler to create hints based solely on the result of a
+ * full standard label scan, i.e. which devices had an lvm label.
+ * (It's much more complicated to create hints based on making specific
+ * changes to existing hints based on what the command has changed.)
+ *
+ * For 3, these commands are a combination of: uncommon commands that
+ * don't need optimization, commands where the purpose is to read all
+ * devices, commands dealing with global state where it's important to
+ * not miss anything, commands where it's safer to know everything.
+ *
+ * For 4, this is the traditional way of forcing any locally cached
+ * state to be cleared and regenerated.  This would be used to reset
+ * hints after doing something that invalidates the hints in a way
+ * that lvm couldn't detect itself, e.g. using dd to copy a PV to
+ * a non-PV device.  (A user could also just rm /run/lvm/hints in
+ * place of running pvscan --cache.)
+ *
+ *
+ * Creating hints:
+ *
+ * A command in list 1 above calls get_hints() to try to read the
+ * hints file.  get_hints() will sometimes not return any hints, in
+ * which case the label_scan will scan all devices.  This happens if:
+ *
+ * a. the /run/lvm/hints file does not exist *
+ * b. the /run/lvm/hints file is empty *
+ * c. the /run/lvm/hints file content is not applicable *
+ * d. the /run/lvm/newhints file exists *
+ * e. the /run/lvm/nohints file exists
+ * f. a shared nonblocking flock on /run/lvm/hints fails
+ *
+ * When get_hints(all_devs, scan_devs, &newhints) does not find hints to use,
+ * it will sometimes set "newhints" so that the command will create a new
+ * hints file after scanning all the devs.  [* These commands create a
+ * new hint file after scanning.]
+ *
+ * After scanning a dev list that was reduced by applying hints, label_scan
+ * calls validate_hints() to check if the hints were consistent with what
+ * the scan saw on the devs.  Sometimes it's not, in which case the command
+ * then scans the remaining devs, and creates /run/lvm/newhints to signal
+ * to the next command that it should create new hints.
+ *
+ * Causes of each case above:
+ * a) First command run, or a user removed the file
+ * b) A command from list 2 cleared the hint file
+ * c) See below
+ * d) Another command from list 1 found invalid hints after scanning.
+ *    A command from list 2 also creates a newhints file in addition
+ *    to clearing the hint file.
+ * e) A command from list 2 is blocking other commands from using
+ *    hints while it makes global changes.
+ * f) A command from list 2 is holding the ex flock to block
+ *    other commands from using hints while it makes global changes.
+ *
+ * The content of the hint file is ignored and invalidated in get_hints if:
+ *
+ * . The lvm.conf filters or scan_lvs setting used by the command that
+ *   created the hints do not match the settings used by this command.
+ *   When these settings change, different PVs can become visible,
+ *   making previous hints invalid.
+ *
+ * . The list of devices on the system changes.  When a new device
+ *   appears on the system, it may have a PV that was not not around
+ *   when the hints were created, and it needs to be scanned.
+ *   (A hash of all dev names on the system is used to detect when
+ *   the list of devices changes and hints need to be recreated.)
+ *
+ * The hint file is invalidated in validate_hints if:
+ *
+ * . The devs in the hint file have a different PVID or VG name
+ *   than what was seen during the scan.
+ *
+ * . Duplicate PVs were seen in the scan.
+ *
+ * . Others may be added.
+ *
+ */
+
+#include "base/memory/zalloc.h"
+#include "lib/misc/lib.h"
+#include "lib/label/label.h"
+#include "lib/misc/crc.h"
+#include "lib/mm/xlate.h"
+#include "lib/cache/lvmcache.h"
+#include "lib/device/bcache.h"
+#include "lib/commands/toolcontext.h"
+#include "lib/activate/activate.h"
+#include "lib/label/hints.h"
+#include "lib/device/dev-type.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>
+
+static const char *_hints_file = DEFAULT_RUN_DIR "/hints";
+static const char *_nohints_file = DEFAULT_RUN_DIR "/nohints";
+static const char *_newhints_file = DEFAULT_RUN_DIR "/newhints";
+
+/*
+ * Format of hints file.  Increase the major number when
+ * making a change to the hint file format that older lvm
+ * versions can't use.  Older lvm versions will not try to
+ * use the hint file if the major number in it is larger
+ * 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.
+ */
+#define HINTS_VERSION_MAJOR 1
+#define HINTS_VERSION_MINOR 1
+
+#define HINT_LINE_LEN (PATH_MAX + NAME_LEN + ID_LEN + 64)
+#define HINT_LINE_WORDS 4
+static char _hint_line[HINT_LINE_LEN];
+
+static int _hints_fd = -1;
+
+#define NONBLOCK 1
+
+#define NEWHINTS_NONE     0
+#define NEWHINTS_FILE     1
+#define NEWHINTS_INIT     2
+#define NEWHINTS_REFRESH  3
+#define NEWHINTS_EMPTY    4
+
+static int _hints_exists(void)
+{
+	struct stat buf;
+
+	if (!stat(_hints_file, &buf))
+		return 1;
+	if (errno != ENOENT)
+		log_debug("hints_exist errno %d", errno);
+	return 0;
+}
+
+static int _nohints_exists(void)
+{
+	struct stat buf;
+
+	if (!stat(_nohints_file, &buf))
+		return 1;
+	if (errno != ENOENT)
+		log_debug("nohints_exist errno %d", errno);
+	return 0;
+}
+
+static int _newhints_exists(void)
+{
+	struct stat buf;
+
+	if (!stat(_newhints_file, &buf))
+		return 1;
+	if (errno != ENOENT)
+		log_debug("newhints_exist errno %d", errno);
+	return 0;
+}
+
+static int _touch_newhints(void)
+{
+	FILE *fp;
+
+	if (!(fp = fopen(_newhints_file, "w")))
+		return_0;
+	if (fclose(fp))
+		stack;
+	return 1;
+}
+
+static int _touch_nohints(void)
+{
+	FILE *fp;
+
+	if (!(fp = fopen(_nohints_file, "w")))
+		return_0;
+	if (fclose(fp))
+		stack;
+	return 1;
+}
+
+static int _touch_hints(void)
+{
+	FILE *fp;
+
+	if (!(fp = fopen(_hints_file, "w")))
+		return_0;
+	if (fclose(fp))
+		stack;
+	return 1;
+}
+
+static void _unlink_nohints(void)
+{
+	if (unlink(_nohints_file))
+		log_debug("unlink_nohints errno %d", errno);
+}
+
+static void _unlink_hints(void)
+{
+	if (unlink(_hints_file))
+		log_debug("unlink_hints errno %d", errno);
+}
+
+static void _unlink_newhints(void)
+{
+	if (unlink(_newhints_file))
+		log_debug("unlink_newhints errno %d", errno);
+}
+
+static int _clear_hints(struct cmd_context *cmd)
+{
+	FILE *fp;
+	time_t t;
+
+	if (!(fp = fopen(_hints_file, "w"))) {
+		log_warn("Failed to clear hint file.");
+		/* shouldn't happen, but try to unlink in case */
+		_unlink_hints();
+		return 0;
+	}
+
+	t = time(NULL);
+
+	fprintf(fp, "# Created empty by %s pid %d %s", cmd->name, getpid(), ctime(&t));
+
+	if (fflush(fp))
+		log_debug("clear_hints flush errno %d", errno);
+
+	if (fclose(fp))
+		log_debug("clear_hints close errno %d", errno);
+
+	return 1;
+}
+
+static int _lock_hints(int mode, int nonblock)
+{
+	int fd;
+	int op = mode;
+	int ret;
+
+	if (nonblock)
+		op |= LOCK_NB;
+
+	if (_hints_fd != -1) {
+		log_warn("lock_hints existing fd %d", _hints_fd);
+		return 0;
+	}
+
+	fd = open(_hints_file, O_RDWR);
+	if (fd < 0) {
+		log_debug("lock_hints open errno %d", errno);
+		return 0;
+	}
+
+
+	ret = flock(fd, op);
+	if (!ret) {
+		_hints_fd = fd;
+		return 1;
+	}
+
+	if (close(fd))
+		stack;
+	return 0;
+}
+
+static void _unlock_hints(void)
+{
+	int ret;
+
+	if (_hints_fd == -1) {
+		log_warn("unlock_hints no existing fd");
+		return;
+	}
+
+	ret = flock(_hints_fd, LOCK_UN);
+	if (ret)
+		log_warn("unlock_hints flock errno %d", errno);
+
+	if (close(_hints_fd))
+		stack;
+	_hints_fd = -1;
+}
+
+static struct hint *_find_hint_name(struct dm_list *hints, const char *name)
+{
+	struct hint *hint;
+
+	dm_list_iterate_items(hint, hints) {
+		if (!strcmp(hint->name, name))
+			return hint;
+	}
+	return NULL;
+}
+
+/*
+ * Decide if a given device name should be included in the hint hash.
+ * If it is, then the hash changes if the device is added or removed
+ * from the system, which causes the hints to be regenerated.
+ * If it is not, then the device being added/removed from the system
+ * does not change the hint hash, which means hints remain unchanged.
+ *
+ * If we know that lvm does not want to scan this device, then it should
+ * be excluded from the hint hash.  If a dev is excluded by the regex
+ * filter or by scan_lvs setting, then we know lvm doesn't want to scan
+ * it, so when it is added/removed the scanning results won't change, and
+ * we don't want to regenerate hints.
+ *
+ * One effect of this is that the regex filter and scan_lvs setting also
+ * need to be saved in the hint file, since if those settings change,
+ * it may impact what devs lvm wants to scan, and therefore change what
+ * the hints are.
+ *
+ * We do not need or want to apply all filters to a device here.  The full
+ * filters still determine if a device is scanned and used.  This is simply
+ * used to decide if the device name should be included in the hash,
+ * where the changing hash triggers hints to be recreated.  So, by
+ * including a device here which is excluded by the real filters, the result is
+ * simply that we could end up recreating hints more often than necessary,
+ * which is not a problem.  Not recreating hints when we should is a bigger
+ * problem, so it's best to include devices here if we're unsure.
+ *
+ * Any filter used here obviously cannot rely on reading the device, since
+ * the whole point of the hints is to avoid reading the device.
+ *
+ * It's common for the system to include a device path for a disconnected
+ * device and report zero size for it (e.g. a loop device).  When the
+ * device is connected, a new device name doesn't appear, but the dev size
+ * for the existing device is now reported as non-zero.  So, if a device
+ * is connected/disconnected, changing the size from/to zero, it is
+ * included/excluded in the hint hash.
+ */
+
+static int _dev_in_hint_hash(struct cmd_context *cmd, struct device *dev)
+{
+	uint64_t devsize = 0;
+
+	if (!cmd->filter->passes_filter(cmd, cmd->filter, dev, "regex"))
+		return 0;
+
+	if (!cmd->filter->passes_filter(cmd, cmd->filter, dev, "type"))
+		return 0;
+
+	/* exclude LVs from hint accounting when scan_lvs is 0 */
+	if (!cmd->scan_lvs && dm_is_dm_major(MAJOR(dev->dev)) && dev_is_lv(dev))
+		return 0;
+
+	if (dev_get_size(dev, &devsize) && !devsize)
+		return 0;
+
+	return 1;
+}
+
+/*
+ * Hints were used to reduce devs that were scanned.  After the reduced
+ * scanning is done, this is called to check if the hints may have been
+ * incorrect or insufficient, in which case we want to continue scanning all
+ * the other (unhinted) devices, as would be done when no hints are used.
+ * This should not generally happen, but is done in an attempt to catch
+ * any unusual situations where the hints become incorrect from something
+ * unexpected.
+ */
+int validate_hints(struct cmd_context *cmd, struct dm_list *hints)
+{
+	struct hint *hint;
+	struct dev_iter *iter;
+	struct device *dev;
+	int ret = 1;
+
+	/* No commands are using hints. */
+	if (!cmd->enable_hints)
+		return 0;
+
+	/* This command does not use hints. */
+	if (!cmd->use_hints && !cmd->pvscan_recreate_hints)
+		return 0;
+
+	if (lvmcache_found_duplicate_pvs()) {
+		log_debug("Hints not used with duplicate pvs");
+		ret = 0;
+		goto out;
+	}
+
+	if (lvmcache_found_duplicate_vgnames()) {
+		log_debug("Hints not used with duplicate vg names");
+		ret = 0;
+		goto out;
+	}
+
+	/*
+	 * Check that the PVID saved in the hint for each device matches the
+	 * PVID that the scan found on the device.  If not, then the hints
+	 * became stale somehow (e.g. manually copying devices with dd) and
+	 * need to be refreshed.
+	 */
+	if (!(iter = dev_iter_create(NULL, 0)))
+		return 0;
+	while ((dev = dev_iter_get(cmd, iter))) {
+		if (!(hint = _find_hint_name(hints, dev_name(dev))))
+			continue;
+
+		/* The cmd hasn't needed this hint's dev so it's not been scanned. */
+		if (!hint->chosen)
+			continue;
+
+		if (strcmp(dev->pvid, hint->pvid)) {
+			log_debug("Invalid hint device %d:%d %s pvid %s had hint pvid %s",
+				  major(hint->devt), minor(hint->devt), dev_name(dev),
+				  dev->pvid, hint->pvid);
+			ret = 0;
+		}
+	}
+	dev_iter_destroy(iter);
+
+	/*
+	 * Check in lvmcache to see if the scan noticed any missing PVs
+	 * which might mean the hints left out a device that we should
+	 * have scanned.
+	 *
+	 * FIXME: the scan cannot currently detect missing PVs.
+	 * They are only detected in vg_read when the PVIDs listed
+	 * in the metadata are looked for and not found.  This could
+	 * be addressed by at least saving the number of expected PVs
+	 * during the scan (in the summary), and then comparing that
+	 * number with the number of PVs found in the hints listing
+	 * that VG name.
+	 */
+
+	/*
+	 * The scan placed a summary of each VG (vginfo) and PV (info)
+	 * into lvmcache lists.  Check in lvmcache to see if the VG name
+	 * for each PV matches the vgname saved in the hint for the PV.
+	 */
+	dm_list_iterate_items(hint, hints) {
+		struct lvmcache_vginfo *vginfo;
+
+		/* The cmd hasn't needed this hint's dev so it's not been scanned. */
+		if (!hint->chosen)
+			continue;
+
+		if (!hint->vgname[0] || (hint->vgname[0] == '-'))
+			continue;
+
+		if (!(vginfo = lvmcache_vginfo_from_vgname(hint->vgname, NULL))) {
+			log_debug("Invalid hint device %d:%d %s pvid %s had vgname %s no VG info.",
+				  major(hint->devt), minor(hint->devt), hint->name,
+				  hint->pvid, hint->vgname);
+			ret = 0;
+			continue;
+		}
+
+		if (!lvmcache_vginfo_has_pvid(vginfo, hint->pvid)) {
+			log_debug("Invalid hint device %d:%d %s pvid %s had vgname %s no PV info.",
+				  major(hint->devt), minor(hint->devt), hint->name,
+				  hint->pvid, hint->vgname);
+			ret = 0;
+			continue;
+		}
+	}
+
+out:
+	if (!ret) {
+		/*
+		 * Force next cmd to recreate hints.  If we can't
+		 * create newhints, the next cmd should get here
+		 * like we have.  We don't use _clear_hints because
+		 * we don't want to take an ex lock here.
+		 */
+		if (!_touch_newhints())
+			stack;
+	}
+
+	return ret;
+}
+
+/*
+ * For devs that match entries in hints, move them from devs_in to devs_out.
+ */
+static void _apply_hints(struct cmd_context *cmd, struct dm_list *hints,
+		char *vgname, struct dm_list *devs_in, struct dm_list *devs_out)
+{
+	struct hint *hint;
+	struct device_list *devl, *devl2;
+	struct dm_list *name_list;
+	struct dm_str_list *name_sl;
+
+	dm_list_iterate_items_safe(devl, devl2, devs_in) {
+		if (!(name_list = dm_list_first(&devl->dev->aliases)))
+			continue;
+		name_sl = dm_list_item(name_list, struct dm_str_list);
+
+		if (!(hint = _find_hint_name(hints, name_sl->str)))
+			continue;
+
+		/* if vgname is set, pick hints with matching vgname */
+		if (vgname && hint->vgname[0] && (hint->vgname[0] != '-')) {
+			if (strcmp(vgname, hint->vgname))
+				continue;
+		}
+
+		dm_list_del(&devl->list);
+		dm_list_add(devs_out, &devl->list);
+		hint->chosen = 1;
+	}
+}
+
+/*
+ * Return 1 and needs_refresh 0: the hints can be used
+ * Return 1 and needs_refresh 1: the hints can't be used and should be updated
+ * Return 0: the hints can't be used
+ *
+ * recreate is set if hint file should be refreshed/recreated
+ */
+static int _read_hint_file(struct cmd_context *cmd, struct dm_list *hints, int *needs_refresh)
+{
+	char devpath[PATH_MAX];
+	FILE *fp;
+	const struct dm_config_node *cn;
+	struct dev_iter *iter;
+	struct hint *hint;
+	struct device *dev;
+	char *split[HINT_LINE_WORDS];
+	char *name, *pvid, *devn, *vgname, *p;
+	uint32_t read_hash = 0;
+	uint32_t calc_hash = INITIAL_CRC;
+	uint32_t read_count = 0;
+	uint32_t calc_count = 0;
+	int found = 0;
+	int keylen;
+	int hv_major, hv_minor;
+	int major, minor;
+	int ret = 1;
+	int i;
+
+	if (!(fp = fopen(_hints_file, "r")))
+		return 0;
+
+	for (i = 0; i < HINT_LINE_WORDS; i++)
+		split[i] = NULL;
+
+	while (fgets(_hint_line, sizeof(_hint_line), fp)) {
+		if (!(hint = zalloc(sizeof(struct hint)))) {
+			ret = 0;
+			break;
+		}
+
+		if (_hint_line[0] == '#')
+			continue;
+
+		if ((p = strchr(_hint_line, '\n')))
+			*p = '\0';
+
+		/*
+		 * Data in the hint file cannot be used if:
+		 * - the hints file major version is larger than used by this cmd
+		 * - filters used for hints don't match filters used by this cmd
+		 * - scan_lvs setting used when creating hints doesn't match the
+		 *   scan_lvs setting used by this cmd
+		 * - the list of devs used when creating hints does not match the
+		 *   list of devs used by this cmd
+		 */
+
+		keylen = strlen("hints_version:");
+		if (!strncmp(_hint_line, "hints_version:", keylen)) {
+			if (sscanf(_hint_line + keylen, "%d.%d", &hv_major, &hv_minor) != 2) {
+				log_debug("ignore hints with unknown version %d.%d", hv_major, hv_minor);
+				*needs_refresh = 1;
+				break;
+			}
+
+			if (hv_major > HINTS_VERSION_MAJOR) {
+				log_debug("ignore hints with newer major version %d.%d", hv_major, hv_minor);
+				*needs_refresh = 1;
+				break;
+			}
+			continue;
+		}
+
+		keylen = strlen("global_filter:");
+		if (!strncmp(_hint_line, "global_filter:", keylen)) {
+			cn = find_config_tree_array(cmd, devices_global_filter_CFG, NULL);
+			if (strcmp(cn->v->v.str, _hint_line + keylen)) {
+				log_debug("ignore hints with different global_filter");
+				*needs_refresh = 1;
+				break;
+			}
+			continue;
+		}
+
+		keylen = strlen("filter:");
+		if (!strncmp(_hint_line, "filter:", keylen)) {
+			cn = find_config_tree_array(cmd, devices_filter_CFG, NULL);
+			if (strcmp(cn->v->v.str, _hint_line + keylen)) {
+				log_debug("ignore hints with different filter");
+				*needs_refresh = 1;
+				break;
+			}
+			continue;
+		}
+
+		keylen = strlen("scan_lvs:");
+		if (!strncmp(_hint_line, "scan_lvs:", keylen)) {
+			int scan_lvs = 0;
+			sscanf(_hint_line + keylen, "%u", &scan_lvs);
+
+			if (scan_lvs != cmd->scan_lvs) {
+				log_debug("ignore hints with different scan_lvs");
+				*needs_refresh = 1;
+				break;
+			}
+			continue;
+		}
+
+		keylen = strlen("devs_hash:");
+		if (!strncmp(_hint_line, "devs_hash:", keylen)) {
+			sscanf(_hint_line + keylen, "%u %u", &read_hash, &read_count);
+			continue;
+		}
+
+		/*
+		 * Ignore any other line prefixes that we don't recognize.
+		 */
+		keylen = strlen("scan:");
+		if (strncmp(_hint_line, "scan:", keylen))
+			continue;
+
+		if (dm_split_words(_hint_line, HINT_LINE_WORDS, 0, split) < 1)
+			continue;
+
+		name = split[0];
+		pvid = split[1];
+		devn = split[2];
+		vgname = split[3];
+
+		if (name && !strncmp(name, "scan:", 5))
+			strncpy(hint->name, name+5, PATH_MAX);
+
+		if (pvid && !strncmp(pvid, "pvid:", 5))
+			strncpy(hint->pvid, pvid+5, ID_LEN);
+
+		if (devn && sscanf(devn, "devn:%d:%d", &major, &minor) == 2)
+			hint->devt = makedev(major, minor);
+
+		if (vgname && (strlen(vgname) > 3) && (vgname[4] != '-'))
+			strncpy(hint->vgname, vgname+3, NAME_LEN);
+
+		log_debug("add hint %s %s %d:%d %s", hint->name, hint->pvid, major, minor, vgname);
+		dm_list_add(hints, &hint->list);
+		found++;
+	}
+
+	if (fclose(fp))
+		stack;
+
+	if (!ret)
+		return 0;
+
+	if (!found)
+		return 1;
+
+	if (*needs_refresh)
+		return 1;
+
+	/*
+	 * Calculate and compare hash of devices that may be scanned.
+	 */
+	if (!(iter = dev_iter_create(NULL, 0)))
+		return 0;
+	while ((dev = dev_iter_get(cmd, iter))) {
+		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));
+		calc_count++;
+	}
+	dev_iter_destroy(iter);
+
+	if (read_hash && (read_hash != calc_hash)) {
+		/* The count is just informational. */
+		log_debug("ignore hints with read_hash %u count %u calc_hash %u count %u",
+			  read_hash, read_count, calc_hash, calc_count);
+		*needs_refresh = 1;
+		return 1;
+	}
+
+	log_debug("accept hints found %d", dm_list_size(hints));
+	return 1;
+}
+
+/*
+ * Include any device in the hints that label_scan saw which had an lvm label
+ * header. label_scan set DEV_SCAN_FOUND_LABEL on the dev if it saw an lvm
+ * header.  We only create new hints here after a complete label_scan at the
+ * start of the command.  (It makes things far simpler to always just recreate
+ * hints from a clean, full scan, than to try to make granular updates to the
+ * content of an existing hint file.)
+ *
+ * Hints are not valid from one command to the next if the commands are using
+ * different filters or different scan_lvs settings.  These differences would
+ * cause the two commands to consider different devices for scanning.
+ *
+ * If the set of devices on the system changes from one cmd to the next
+ * (excluding those skipped by filters or scan_lvs), the hints are ignored
+ * since there may be a new device that is now present that should be scanned
+ * that was not present when the hints were created.  The change in the set of
+ * devices is detected by creating a hash of all dev names.  When a device is
+ * added or removed from this system, this hash changes triggering hints to be
+ * recreated.
+ *
+ * (This hash detection depends on the two commands iterating through dev names
+ * in the same order, which happens because the devs are inserted into the
+ * btree using devno.  If the btree implementation changes, then we need
+ * to sort the dev names here before iterating through them.)
+ *
+ * N.B. the config setting pv_min_size should technically be included in
+ * the hint file like the filter and scan_lvs setting, since increasing
+ * pv_min_size can cause new devices to be scanned that were not before.
+ * It is left out since it is not often changed, but could be easily added.
+ */
+
+int write_hint_file(struct cmd_context *cmd, int newhints)
+{
+	char devpath[PATH_MAX];
+	FILE *fp;
+	const struct dm_config_node *cn;
+	struct lvmcache_info *info;
+	struct dev_iter *iter;
+	struct device *dev;
+	const char *vgname;
+	uint32_t hash = INITIAL_CRC;
+	uint32_t count = 0;
+	time_t t;
+	int ret = 1;
+
+	/* This function should not be called if !enable_hints or !use_hints. */
+
+	/* No commands are using hints. */
+	if (!cmd->enable_hints)
+		return 0;
+
+	/* This command does not use hints. */
+	if (!cmd->use_hints && !cmd->pvscan_recreate_hints)
+		return 0;
+
+	if (lvmcache_found_duplicate_pvs() || lvmcache_found_duplicate_vgnames()) {
+		/*
+		 * When newhints is EMPTY, it means get_hints() found an empty
+		 * hint file.  So we scanned all devs and found duplicate pvids
+		 * or duplicate vgnames (which is probably why the hints were
+		 * empty.)  Since the hint file is already empty, we don't need
+		 * to recreate an empty file.
+		 */
+		if (newhints == NEWHINTS_EMPTY)
+			return 1;
+	}
+
+	log_debug("Writing hint file %d", newhints);
+
+	if (!(fp = fopen(_hints_file, "w"))) {
+		ret = 0;
+		goto out_unlock;
+	}
+
+	t = time(NULL);
+
+	if (lvmcache_found_duplicate_pvs() || lvmcache_found_duplicate_vgnames()) {
+		fprintf(fp, "# Created empty by %s pid %d %s", cmd->name, getpid(), ctime(&t));
+
+		/* leave a comment about why it's empty in case someone is curious */
+		if (lvmcache_found_duplicate_pvs())
+			fprintf(fp, "# info: duplicate_pvs\n");
+		if (lvmcache_found_duplicate_vgnames())
+			fprintf(fp, "# info: duplicate_vgnames\n");
+		goto out_flush;
+	}
+
+	fprintf(fp, "# Created by %s pid %d %s", cmd->name, getpid(), ctime(&t));
+	fprintf(fp, "hints_version: %d.%d\n", HINTS_VERSION_MAJOR, HINTS_VERSION_MINOR);
+
+	cn = find_config_tree_array(cmd, devices_global_filter_CFG, NULL);
+	fprintf(fp, "global_filter:%s\n", cn->v->v.str);
+
+	cn = find_config_tree_array(cmd, devices_filter_CFG, NULL);
+	fprintf(fp, "filter:%s\n", cn->v->v.str);
+
+	fprintf(fp, "scan_lvs:%d\n", cmd->scan_lvs);
+
+	/* 
+	 * iterate through all devs and write a line for each
+	 * dev flagged DEV_SCAN_FOUND_LABEL
+	 */
+
+	if (!(iter = dev_iter_create(NULL, 0))) {
+		ret = 0;
+		goto out_close;
+	}
+
+	/*
+	 * This loop does two different things (for clarity this should be
+	 * two separate dev_iter loops, but one is used for efficiency).
+	 * 1. compute the hint hash from all relevant devs
+	 * 2. add PVs to the hint file
+	 */
+	while ((dev = dev_iter_get(cmd, iter))) {
+		if (!_dev_in_hint_hash(cmd, dev)) {
+			if (dev->flags & DEV_SCAN_FOUND_LABEL) {
+				/* should never happen */
+				log_error("skip hint hash but found label %s", dev_name(dev));
+			}
+			continue;
+		}
+
+		/*
+		 * Create a hash of all device names on the system so we can
+		 * detect when the devices on the system change, which
+		 * invalidates the existing hints.
+		 */
+		memset(devpath, 0, sizeof(devpath));
+		strncpy(devpath, dev_name(dev), PATH_MAX);
+		hash = calc_crc(hash, (const uint8_t *)devpath, strlen(devpath));
+		count++;
+
+		if (!(dev->flags & DEV_SCAN_FOUND_LABEL))
+			continue;
+
+		/*
+		 * No vgname will be found here for a PV with no mdas,
+		 * in which case the vgname hint will be incomplete.
+		 * (The label scan cannot associate nomda-pvs with the
+		 * correct vg in lvmcache; that is only done by vg_read.)
+		 * When using vgname hint we would always want to also
+		 * scan any PVs missing a vgname hint in case they are
+		 * part of the vg we are looking for.
+		 */
+		if ((info = lvmcache_info_from_pvid(dev->pvid, dev, 0)))
+			vgname = lvmcache_vgname_from_info(info);
+		else
+			vgname = NULL;
+
+		if (vgname && is_orphan_vg(vgname))
+			vgname = NULL;
+
+		fprintf(fp, "scan:%s pvid:%s devn:%d:%d vg:%s\n",
+			dev_name(dev),
+			dev->pvid,
+			major(dev->dev), minor(dev->dev),
+			vgname ?: "-");
+	}
+
+	fprintf(fp, "devs_hash: %u %u\n", hash, count);
+	dev_iter_destroy(iter);
+
+ out_flush:
+	if (fflush(fp))
+		stack;
+
+	log_debug("Wrote hint file with devs_hash %u count %u", hash, count);
+
+	/*
+	 * We are writing refreshed hints because another command told us to by
+	 * touching newhints, so unlink the newhints file.
+	 */
+	if (newhints == NEWHINTS_FILE)
+		_unlink_newhints();
+
+ out_close:
+	if (fclose(fp))
+		stack;
+
+ out_unlock:
+	/* get_hints() took ex lock before returning with newhints set */
+	_unlock_hints();
+
+	return ret;
+}
+
+/*
+ * Commands that do things that would change existing hints (i.e. create or
+ * remove PVs) call this function before they start to get rid of the existing
+ * hints.  This function clears the content of the hint file so that subsequent
+ * commands will recreate it.  These commands do not try to recreate hints when
+ * they are done (this keeps hint creation simple, always done in one way from
+ * one place.)  While this command runs, it holds an ex lock on the hint file.
+ * This causes any other command that tries to use the hints to ignore the
+ * hints by failing in _lock_hints(SH).  We do not want another command to
+ * be creating new hints at the same time that this command is changing things
+ * that would invalidate them, so we block new hints from being created until
+ * we are done with the changes.
+ *
+ * This is the only place that makes a blocking lock request on the hints file.
+ * It does this so that it won't clear the hint file while a previous command
+ * is still reading it, and to ensure we are holding the hints lock before we
+ * begin changing things.  (In place of a blocking request we could add a retry
+ * loop around nonblocking requests, which would allow us to better handle
+ * instances where a bad/stuck lock is blocking this for a long time.)
+ *
+ * To handle cases of indefinite postponement (repeated commands taking sh lock
+ * on the hints file, preventing us from ever getting the ex lock), we touch
+ * the nohints file first.  The nohints file causes all other commands to
+ * ignore hints.  This means we should only have to block waiting for
+ * pre-existing commands that have locked the hints file.
+ *
+ * (If the command were to crash or be SIGKILLed between touch_nohints
+ * and unlink_nohints, it could leave the nohints file in place.  This
+ * is not a huge deal - it would be cleared by the next command like
+ * this that doesn't crash, or by a reboot, or manually.  If it's still
+ * an issue we could easily write the pid in the nohints file, and
+ * others could check if the pid is still around before obeying it.)
+ *
+ * The intention is to call this function after the global ex lock has been
+ * taken, which is the official lock serializing commands changing which
+ * devs are PVs or not.  This means that a command should never block in
+ * this function due to another command that has used this function --
+ * they would be serialized by the official global lock first.
+ * e.g. two pvcreates should never block each other from the hint lock,
+ * but rather from the global lock...
+ *
+ * Unfortunately, the global(orphan) lock is not used consistently so it's not
+ * quite doing its job right and needs some cleanup.  Until that's done,
+ * concurrent commands like pvcreate may block each other on the hint lock.
+ */
+
+void clear_hint_file(struct cmd_context *cmd)
+{
+	/* No commands are using hints. */
+	if (!cmd->enable_hints)
+		return;
+
+	/*
+	 * This function runs even when cmd->use_hints is 0,
+	 * which means this command does not use hints, but
+	 * others do, so we are clearing the hints for them.
+	 */
+
+	/* limit potential delay blocking on hints lock next */
+	if (!_touch_nohints())
+		stack;
+
+	/*
+	 * We are relying on the command exit to release this flock,
+	 * we should probably add an explicit unlock_hints call.
+	 */
+
+	if (!_lock_hints(LOCK_EX, 0))
+		stack;
+
+	_unlink_nohints();
+
+	if (!_clear_hints(cmd))
+		stack;
+
+	/*
+	 * Creating a newhints file here is not necessary, since
+	 * get_hints would see an empty hints file, but get_hints
+	 * is more efficient if it sees a newhints file first.
+	 */
+	if (!_touch_newhints())
+		stack;
+}
+
+/*
+ * Currently, all the commands using hints (ALLOW_HINTS) take an optional or
+ * required first position arg of a VG name or LV name.  If some other command
+ * began using hints which took some other kind of position arg, we would
+ * probably want to exclude that command from attempting this optimization,
+ * because it would be difficult to know what VG that command wanted to use.
+ */
+static void _get_single_vgname_cmd_arg(struct cmd_context *cmd,
+				       struct dm_list *hints, char **vgname)
+{
+	struct hint *hint;
+	char namebuf[NAME_LEN];
+	char *name = NULL;
+	char *arg, *st, *p;
+	int i = 0;
+	
+	memset(namebuf, 0, sizeof(namebuf));
+
+	if (cmd->position_argc != 1)
+		return;
+
+	if (!cmd->position_argv[0])
+		return;
+
+	arg = cmd->position_argv[0];
+
+	/* tag */
+	if (arg[0] == '@')
+		return;
+
+	/* /dev/path - strip chars before vgname */
+	if (arg[0] == '/') {
+#if 0
+		/* skip_dev_dir only available in tools layer */
+		const char *strip;
+		if (!(strip = skip_dev_dir(cmd, (const char *)arg, NULL)))
+			return;
+		arg = (char *)strip;
+#endif
+		return;
+	}
+
+	if (!(st = strchr(arg, '/'))) {
+		/* simple vgname */
+		name = strdup(arg);
+		goto check;
+	}
+
+	/* take vgname from vgname/lvname */
+	for (p = arg; p < st; p++)
+		namebuf[i++] = *p;
+
+	name = strdup(namebuf);
+
+check:
+	/*
+	 * Only use this vgname hint if there are hints that contain this
+	 * vgname.  This might happen if we aren't able to properly extract the
+	 * vgname from the command args (could happen in some odd cases, e.g.
+	 * only LV name is specified without VG name).
+	 */
+	dm_list_iterate_items(hint, hints) {
+		if (!strcmp(hint->vgname, name)) {
+			*vgname = name;
+			return;
+		}
+	}
+}
+
+/*
+ * Returns 0: no hints are used.
+ *  . newhints is set if this command should create new hints after scan
+ *    for subsequent commands to use.
+ *
+ * Returns 1: use hints that are returned in hints list.
+ */
+
+int get_hints(struct cmd_context *cmd, struct dm_list *hints, int *newhints,
+	      struct dm_list *devs_in, struct dm_list *devs_out)
+{
+	int needs_refresh = 0;
+	char *vgname = NULL;
+
+	/* Decide below if the caller should create new hints. */
+	*newhints = NEWHINTS_NONE;
+
+	/* No commands are using hints. */
+	if (!cmd->enable_hints)
+		return 0;
+
+	/*
+	 * Special case for 'pvscan --cache' which removes hints,
+	 * and then creates new hints.  pvscan does not use hints,
+	 * so this has to be checked before the cmd->use_hints check.
+	 */
+	if (cmd->pvscan_recreate_hints) {
+		/* clear_hint_file already locked hints ex */
+		/* create new hints after scan */
+		log_debug("get_hints: pvscan recreate");
+		*newhints = NEWHINTS_FILE;
+		return 0;
+	}
+
+	/* This command does not use hints. */
+	if (!cmd->use_hints)
+		return 0;
+
+	/*
+	 * Check if another command created the nohints file to prevent us from
+	 * using hints.
+	 */
+	if (_nohints_exists()) {
+		log_debug("get_hints: nohints file");
+		return 0;
+	}
+
+	/*
+	 * Check if another command created the newhints file to cause us to
+	 * ignore current hints and recreate new ones.  We'll unlink_newhints
+	 * to remove newhints file after writing refreshed hints file.
+	 */
+	if (_newhints_exists()) {
+		log_debug("get_hints: newhints file");
+		if (!_hints_exists())
+			_touch_hints();
+		if (!_lock_hints(LOCK_EX, NONBLOCK))
+			return 0;
+		/* create new hints after scan */
+		*newhints = NEWHINTS_FILE;
+		return 0;
+	}
+
+	/*
+	 * no hints file exists, a normal case
+	 */
+	if (!_hints_exists()) {
+		log_debug("get_hints: no file");
+		if (!_touch_hints())
+			return 0;
+		if (!_lock_hints(LOCK_EX, NONBLOCK))
+			return 0;
+		/* create new hints after scan */
+		*newhints = NEWHINTS_INIT;
+		return 0;
+	}
+
+	/*
+	 * hints are locked by a command modifying things, just skip using
+	 * hints this time since they aren't accurate while things change.
+	 * We hold a sh lock on the hints file while reading it to prevent
+	 * another command from clearing it while we're reading
+	 */
+	if (!_lock_hints(LOCK_SH, NONBLOCK)) {
+		log_debug("get_hints: lock fail");
+		return 0;
+	}
+
+	/*
+	 * couln't read file for some reason, not normal, just skip using hints
+	 */
+	if (!_read_hint_file(cmd, hints, &needs_refresh)) {
+		log_debug("get_hints: read fail");
+		_unlock_hints();
+		return 0;
+	}
+
+	_unlock_hints();
+
+	/*
+	 * The content of the hint file is invalid and should be refreshed,
+	 * so we'll scan everything and then recreate the hints.
+	 */
+	if (needs_refresh) {
+		log_debug("get_hints: needs refresh");
+
+		if (!_lock_hints(LOCK_EX, NONBLOCK))
+			return 0;
+
+		/* create new hints after scan */
+		*newhints = NEWHINTS_REFRESH;
+		return 0;
+
+	}
+	
+	/*
+	 * A command that changes global state clears the content
+	 * of the hints file so it will be recreated, and we must
+	 * be following that since we found no hints.
+	 */
+	if (dm_list_empty(hints)) {
+		log_debug("get_hints: no entries");
+
+		if (!_lock_hints(LOCK_EX, NONBLOCK))
+			return 0;
+
+		/* create new hints after scan */
+		*newhints = NEWHINTS_EMPTY;
+		return 0;
+	}
+
+	/*
+	 * If the command specifies a single VG (alone or as part of a single
+	 * LV), then we can set vgname to further reduce scanning by only
+	 * scanning the hints for the given vgname.
+	 *
+	 * (This is a further optimization beyond the basic hints that tell
+	 * us which devs are PVs. We might want to enable this optimization
+	 * separately.)
+	 */
+	_get_single_vgname_cmd_arg(cmd, hints, &vgname);
+
+	_apply_hints(cmd, hints, vgname, devs_in, devs_out);
+
+	log_debug("get_hints: applied using %d other %d",
+		  dm_list_size(devs_out), dm_list_size(devs_in));
+	return 1;
+}
+
diff --git a/lib/label/hints.h b/lib/label/hints.h
new file mode 100644
index 0000000..d80016e
--- /dev/null
+++ b/lib/label/hints.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2004-2018 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_HINTS_H
+#define _LVM_HINTS_H
+
+struct hint {
+	struct dm_list list;
+	char name[PATH_MAX];
+	char pvid[ID_LEN + 1];
+	char vgname[NAME_LEN];
+	dev_t devt;
+	unsigned chosen:1; /* this hint's dev was chosen for scanning */
+};
+
+int write_hint_file(struct cmd_context *cmd, int newhints);
+
+void clear_hint_file(struct cmd_context *cmd);
+
+int get_hints(struct cmd_context *cmd, struct dm_list *hints, int *newhints,
+              struct dm_list *devs_in, struct dm_list *devs_out);
+
+int validate_hints(struct cmd_context *cmd, struct dm_list *hints);
+
+#endif
+
diff --git a/lib/label/label.c b/lib/label/label.c
index 6fe1e41..7d5073e 100644
--- a/lib/label/label.c
+++ b/lib/label/label.c
@@ -22,6 +22,7 @@
 #include "lib/device/bcache.h"
 #include "lib/commands/toolcontext.h"
 #include "lib/activate/activate.h"
+#include "lib/label/hints.h"
 
 #include <sys/stat.h>
 #include <fcntl.h>
@@ -358,6 +359,8 @@ static int _process_block(struct cmd_context *cmd, struct dev_filter *f,
 	int ret = 0;
 	int pass;
 
+	dev->flags &= ~DEV_SCAN_FOUND_LABEL;
+
 	/*
 	 * The device may have signatures that exclude it from being processed.
 	 * If filters were applied before bcache data was available, some
@@ -370,7 +373,7 @@ static int _process_block(struct cmd_context *cmd, struct dev_filter *f,
 
 		log_debug_devs("Scan filtering %s", dev_name(dev));
 		
-		pass = f->passes_filter(cmd, f, dev);
+		pass = f->passes_filter(cmd, f, dev, NULL);
 
 		if ((pass == -EAGAIN) || (dev->flags & DEV_FILTER_AFTER_SCAN)) {
 			/* Shouldn't happen */
@@ -412,6 +415,7 @@ static int _process_block(struct cmd_context *cmd, struct dev_filter *f,
 		goto_out;
 	}
 
+	dev->flags |= DEV_SCAN_FOUND_LABEL;
 	*is_lvm_device = 1;
 
 	/*
@@ -827,6 +831,16 @@ static int _setup_bcache(int cache_blocks)
 	return 1;
 }
 
+static void _free_hints(struct dm_list *hints)
+{
+	struct hint *hint, *hint2;
+
+	dm_list_iterate_items_safe(hint, hint2, hints) {
+		dm_list_del(&hint->list);
+		free(hint);
+	}
+}
+
 /*
  * Scan and cache lvm data from all devices on the system.
  * The cache should be empty/reset before calling this.
@@ -835,13 +849,18 @@ static int _setup_bcache(int cache_blocks)
 int label_scan(struct cmd_context *cmd)
 {
 	struct dm_list all_devs;
+	struct dm_list scan_devs;
+	struct dm_list hints;
 	struct dev_iter *iter;
 	struct device_list *devl, *devl2;
 	struct device *dev;
+	int newhints = 0;
 
 	log_debug_devs("Finding devices to scan");
 
 	dm_list_init(&all_devs);
+	dm_list_init(&scan_devs);
+	dm_list_init(&hints);
 
 	/*
 	 * Iterate through all the devices in dev-cache (block devs that appear
@@ -889,20 +908,64 @@ int label_scan(struct cmd_context *cmd)
 	};
 	dev_iter_destroy(iter);
 
-	log_debug_devs("Found %d devices to scan", dm_list_size(&all_devs));
-
 	if (!scan_bcache) {
 		if (!_setup_bcache(dm_list_size(&all_devs)))
 			return 0;
 	}
 
-	_scan_list(cmd, cmd->filter, &all_devs, NULL);
+	/*
+	 * In some common cases we can avoid scanning all devices.
+	 *
+	 * TODO: if the command is using hints and a single vgname
+	 * arg, we can also take the vg lock here, prior to scanning.
+	 * This means we would not need to rescan the PVs in the VG
+	 * in vg_read (skip lvmcache_label_rescan_vg) after the
+	 * vg lock is usually taken.  (Some commands are already
+	 * able to avoid rescan in vg_read, but locking early would
+	 * apply to more cases.)
+	 */
+	if (!get_hints(cmd, &hints, &newhints, &all_devs, &scan_devs))
+		dm_list_splice(&scan_devs, &all_devs);
+
+	log_debug("Will scan %d devices skip %d", dm_list_size(&scan_devs), dm_list_size(&all_devs));
+
+	/*
+	 * Do the main scan.
+	 */
+	_scan_list(cmd, cmd->filter, &scan_devs, NULL);
+
+	dm_list_init(&cmd->hints);
+
+	if (!dm_list_empty(&hints)) {
+		if (!validate_hints(cmd, &hints)) {
+			/*
+			 * We scanned a subset of all devices based on hints.
+			 * With the results from the scan we may decide that
+			 * the hints are not valid, so scan all others.
+			 */
+			log_debug("Will scan %d remaining devices", dm_list_size(&all_devs));
+			_scan_list(cmd, cmd->filter, &all_devs, NULL);
+			_free_hints(&hints);
+			newhints = 0;
+		} else {
+			/* The hints may be used by another device iteration. */
+			dm_list_splice(&cmd->hints, &hints);
+		}
+	}
 
 	dm_list_iterate_items_safe(devl, devl2, &all_devs) {
 		dm_list_del(&devl->list);
 		free(devl);
 	}
 
+	dm_list_iterate_items_safe(devl, devl2, &scan_devs) {
+		dm_list_del(&devl->list);
+		free(devl);
+	}
+
+	if (newhints)
+		write_hint_file(cmd, newhints);
+
 	return 1;
 }
 
diff --git a/test/shell/hints.sh b/test/shell/hints.sh
new file mode 100644
index 0000000..bdaf3be
--- /dev/null
+++ b/test/shell/hints.sh
@@ -0,0 +1,377 @@
+#!/usr/bin/env bash
+
+# Copyright (C) 2012 Red Hat, Inc. All rights reserved.
+#
+# 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 General Public License v.2.
+#
+# You should have received a copy of the GNU 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
+
+SKIP_WITH_LVMPOLLD=1
+
+/* hints are currently disabled with lvmlockd */
+SKIP_WITH_LVMLOCKD=1
+
+RUNDIR="/run"
+test -d "$RUNDIR" || RUNDIR="/var/run"
+HINTS="$RUNDIR/lvm/hints"
+NOHINTS="$RUNDIR/lvm/nohints"
+NEWHINTS="$RUNDIR/lvm/newhints"
+PREV="$RUNDIR/lvm/prev-hints"
+
+. lib/inittest
+
+# TODO:
+# Test commands that ignore hints
+# Test flock
+
+
+aux lvmconf 'devices/scan_lvs = 0'
+
+aux prepare_devs 6
+
+# no PVs yet so hints should have no devs
+pvs
+not grep scan: $HINTS
+
+#
+# vg1 uses dev1,dev2
+#
+# Test basics that PVs are in hints, not non-PV devs,
+# and that only PVs are scanned when using hints.
+#
+
+vgcreate $vg1 "$dev1" "$dev2"
+lvcreate -n $lv1 -l 4 $vg1
+
+# test that only the two PVs are in hints
+pvs
+grep -v -E "$dev1|$dev2" $HINTS > tmptest
+not grep scan: tmptest
+
+# test that 'pvs' submits only two reads, one for each PV in hints
+strace -e io_submit pvs 2>&1|tee tmptest
+test "$(grep io_submit tmptest | wc -l)" -eq 2
+
+# test that 'pvs -a' submits six reads, one for each device
+strace -e io_submit pvs -a 2>&1|tee tmptest
+test "$(grep io_submit tmptest | wc -l)" -eq 6
+
+#
+# vg2 uses dev3,dev4
+#
+# Test common commands that cause hints to be refreshed:
+# pvcreate/vgcreate/vgextend/vgreduce/vgremove/pvremove
+#
+
+not pvs "$dev3"
+not grep "$dev3" $HINTS
+cp $HINTS $PREV
+pvcreate "$dev3"
+grep "# Created empty" $HINTS
+cat $NEWHINTS
+# next cmd recreates hints
+pvs "$dev3"
+grep "$dev3" $HINTS
+not diff $HINTS $PREV
+not cat $NEWHINTS
+
+not vgs $vg2
+cp $HINTS $PREV
+vgcreate $vg2 "$dev3"
+grep "# Created empty" $HINTS
+cat $NEWHINTS
+# next cmd recreates hints
+vgs $vg2
+grep $vg2 $HINTS
+not diff $HINTS $PREV
+not cat $NEWHINTS
+
+cp $HINTS $PREV
+vgextend $vg2 "$dev4"
+grep "# Created empty" $HINTS
+cat $NEWHINTS
+# next cmd recreates hints
+vgs $vg2
+grep "$dev4" $HINTS
+not diff $HINTS $PREV
+not cat $NEWHINTS
+
+cp $HINTS $PREV
+vgreduce $vg2 "$dev4"
+grep "# Created empty" $HINTS
+cat $NEWHINTS
+# next cmd recreates hints
+vgs $vg2
+grep "$dev4" $HINTS
+not diff $HINTS $PREV
+not cat $NEWHINTS
+
+cp $HINTS $PREV
+vgremove $vg2
+grep "# Created empty" $HINTS
+cat $NEWHINTS
+# next cmd recreates hints
+not vgs $vg2
+not grep $vg2 $HINTS
+not diff $HINTS $PREV
+not cat $NEWHINTS
+
+cp $HINTS $PREV
+pvremove "$dev3" "$dev4"
+grep "# Created empty" $HINTS
+cat $NEWHINTS
+# next cmd recreates hints
+not pvs "$dev3"
+not pvs "$dev4"
+not grep "$dev3" $HINTS
+not grep "$dev4" $HINTS
+not diff $HINTS $PREV
+not cat $NEWHINTS
+
+#
+# Test that adding a new device and removing a device
+# causes hints to be recreated.
+#
+
+not pvs "$dev5"
+
+# create a new temp device that will cause hint hash to change
+DEVNAME=${PREFIX}pv99
+echo "0 `blockdev --getsize $dev5` linear $dev5 0" | dmsetup create $DEVNAME
+dmsetup status $DEVNAME
+
+cp $HINTS $PREV
+# pvs ignores current hints because of different dev hash and refreshes new hints
+pvs
+# devs listed in hints before and after are the same
+grep scan: $PREV > scan1
+grep scan: $HINTS > scan2
+diff scan1 scan2
+# hash listed before and after are different
+cat $PREV
+cat $HINTS
+grep devs_hash $PREV > devs_hash1
+grep devs_hash $HINTS > devs_hash2
+not diff devs_hash1 devs_hash2
+
+# hints are stable/unchanging
+cp $HINTS $PREV
+pvs
+diff $HINTS $PREV
+
+# remove the temp device which will cause hint hash to change again
+dmsetup remove $DEVNAME
+
+cp $HINTS $PREV
+# pvs ignores current hints because of different dev hash and refreshes new hints
+pvs
+# devs listed in hints before and after are the same
+grep scan: $PREV > scan1
+grep scan: $HINTS > scan2
+diff scan1 scan2
+# hash listed before and after are different
+grep devs_hash $PREV > devs_hash1
+grep devs_hash $HINTS > devs_hash2
+not diff devs_hash1 devs_hash2
+
+#
+# Test that hints don't change from a bunch of commands
+# that use hints and shouldn't change it.
+#
+
+# first create some more metadata using vg2
+pvcreate "$dev3" "$dev4"
+vgcreate $vg2 "$dev3"
+lvcreate -n $lv1 -l1 $vg2
+lvcreate -n $lv2 -l1 $vg2
+
+cp $HINTS $PREV
+lvm fullreport
+lvchange -ay $vg1
+lvchange -an $vg1
+lvcreate -l1 -n $lv2 $vg1
+lvcreate -l1 -an -n $lv3 $vg1
+lvchange -an $vg1
+lvremove $vg1/$lv3
+lvresize -l+1 $vg1/$lv2
+lvresize -l-1 $vg1/$lv2
+lvdisplay
+pvdisplay
+vgdisplay
+lvs
+pvs
+vgs
+vgchange -ay $vg2
+vgchange -an $vg2
+vgck $vg2
+lvrename $vg1 $lv2 $lv3
+# no change in hints after all that
+diff $HINTS $PREV
+
+#
+# Test that changing the filter will cause hint refresh
+#
+
+rm $HINTS $PREV
+vgs
+cp $HINTS $PREV
+# this changes the filter to exclude dev5 which is not a PV
+aux hide_dev "$dev5"
+# next cmd sees different filter, ignores hints, creates new hints
+pvs
+not diff $HINTS $PREV
+# run cmds using new filter
+pvs
+cp $HINTS $PREV
+vgs
+# hints are stable once refreshed
+diff $HINTS $PREV
+# this changes the filter to include dev5
+aux unhide_dev "$dev5"
+# next cmd sees different filter, ignores hints, creates new hints
+pvs
+not diff $HINTS $PREV
+# hints are stable
+cp $HINTS $PREV
+vgs
+diff $HINTS $PREV
+
+#
+# Test that changing scan_lvs will cause hint refresh
+# 
+
+rm $HINTS $PREV
+vgs
+cp $HINTS $PREV
+# change lvm.conf
+aux lvmconf 'devices/scan_lvs = 1'
+# next cmd sees new setting, ignores hints, creates new hints
+pvs
+not diff $HINTS $PREV
+# run cmds using new filter
+pvs
+cp $HINTS $PREV
+vgs
+# hints are stable once refreshed
+diff $HINTS $PREV
+# change lvm.conf back
+aux lvmconf 'devices/scan_lvs = 0'
+# next cmd sees different scan_lvs, ignores hints, creates new hints
+pvs
+not diff $HINTS $PREV
+# hints are stable once refreshed
+cp $HINTS $PREV
+pvs
+diff $HINTS $PREV
+
+#
+# Test pvscan --cache to force hints refresh
+#
+
+# pvs (no change), pvscan (hints are new), pvs (no change)
+pvs
+cp $HINTS $PREV
+diff $HINTS $PREV
+cp $HINTS $PREV
+pvscan --cache
+not diff $HINTS $PREV
+cp $HINTS $PREV
+pvs
+diff $HINTS $PREV
+grep 'Created by pvscan' $HINTS
+# dev4 is a PV not used by a VG, dev5 is not a PV
+# using dd to copy skirts hint tracking so dev5 won't be seen
+dd if="$dev4" of="$dev5" bs=1M
+# this pvs won't see dev5
+pvs > foo
+cat foo
+grep "$dev4" foo
+not grep "$dev5" foo
+# no hints have changed after dd and pvs since dd cannot be detected
+diff $HINTS $PREV
+# force hints refresh, will see duplicate now
+pvscan --cache
+not diff $HINTS $PREV
+cat $HINTS
+pvs -a > foo
+# after force refresh, both devs (dups) appear in output
+cat foo
+grep "$dev4" foo
+grep "$dev5" foo
+# clear PV from dev5
+dd if=/dev/zero of="$dev5" bs=1M count=1
+# this pvs won't use hints because of duplicate PVs,
+# and will create new hints
+cp $HINTS $PREV
+pvs > foo
+not diff $HINTS $PREV
+grep "$dev4" foo
+not grep "$dev5" foo
+grep "$dev4" $HINTS
+not grep "$dev5" $HINTS
+
+
+#
+# Test incorrect dev-to-pvid info in hints is detected
+# dev4 is a PV not in a VG
+#
+
+pvs
+cp $HINTS tmp-old
+# this pvchange will invalidate current hints
+pvchange -u "$dev4"
+grep "# Created empty" $HINTS
+cat $NEWHINTS
+# this next pvs will create new hints with the new uuid
+pvs
+grep "$dev4" $HINTS > tmp-newuuid
+cp $HINTS tmp-new
+not diff tmp-old tmp-new
+# hints are stable
+pvs
+diff $HINTS tmp-new
+# replace the current hints with the old hints with the old uuid
+cp tmp-old $HINTS
+# this next pvs will see wrong dev-to-pvid mapping and invalidate hints
+pvs
+cat $HINTS
+cat $NEWHINTS
+# this next pvs will create new hints with the new uuid
+pvs
+cat $HINTS
+grep -f tmp-newuuid $HINTS
+rm tmp-old tmp-new tmp-newuuid
+
+
+#
+# Test incorrent pvid-to-vgname info in hints is detected
+#
+
+# this vgcreate invalidates current hints
+vgcreate $vg3 $dev4
+# this pvs creates new hints
+pvs
+cp $HINTS tmp-old
+# this vgrename will invalidate current hints
+vgrename $vg3 $vg4
+# this pvs will create new hints with the new vg name
+pvs
+cp $HINTS tmp-new
+not diff tmp-old tmp-new
+# replace the current hints with the old hints with the old vg name
+cp tmp-old $HINTS
+# this pvs will see wrong pvid-to-vgname mapping and invalidate hints
+pvs
+cat $NEWHINTS
+# this pvs will create new hints with the new vg name
+pvs
+grep $vg4 $HINTS
+
+vgremove -y $vg4
+vgremove -y $vg2
+vgremove -y $vg1
+
diff --git a/test/shell/process-each-duplicate-pvs.sh b/test/shell/process-each-duplicate-pvs.sh
index b8a8774..ffd085a 100644
--- a/test/shell/process-each-duplicate-pvs.sh
+++ b/test/shell/process-each-duplicate-pvs.sh
@@ -55,7 +55,7 @@ check pv_field "$dev2" dev_size "$SIZE2"
 
 # Copy dev1 over dev2.
 dd if="$dev1" of="$dev2" bs=1M iflag=direct oflag=direct,sync
-pvscan --cache
+#pvscan --cache
 
 # The single preferred dev is shown from 'pvs'.
 pvs -o+uuid,duplicate 2>&1 | tee out
@@ -292,7 +292,7 @@ grep "$dev3" out
 grep "$dev4" out
 
 dd if="$dev3" of="$dev4" bs=1M iflag=direct oflag=direct,sync
-pvscan --cache
+#pvscan --cache
 
 # One appears with 'pvs'
 
@@ -375,7 +375,7 @@ check pv_field "$dev4" dev_size "$SIZE4"
 
 dd if=/dev/zero of="$dev3" bs=1M oflag=direct,sync || true
 dd if=/dev/zero of="$dev4" bs=1M oflag=direct,sync || true
-pvscan --cache
+#pvscan --cache
 
 # The previous steps prevent us from nicely cleaning up
 # the vg lockspace in lvmlockd, so just restart it;
@@ -392,6 +392,9 @@ lvcreate -l1 -n $lv2 $vg2 "$dev4"
 
 dd if="$dev3" of="$dev5" bs=1M iflag=direct oflag=direct,sync
 dd if="$dev4" of="$dev6" bs=1M iflag=direct oflag=direct,sync
+# dev5/dev6 not pvs so dd'ing pv onto them causes invalid hints
+# that won't be detected, so 5/6 won't be scanned unless we
+# force hint recreation
 pvscan --cache
 
 pvs -o+uuid,duplicate 2>&1 | tee out
@@ -450,7 +453,7 @@ not grep "prefers device $dev6" warn
 
 dd if=/dev/zero of="$dev5" bs=1M oflag=direct,sync || true
 dd if=/dev/zero of="$dev6" bs=1M oflag=direct,sync || true
-pvscan --cache
+#pvscan --cache
 
 lvremove -y $vg2/$lv1
 lvremove -y $vg2/$lv2
@@ -460,7 +463,7 @@ pvremove -ff -y "$dev4"
 
 dd if=/dev/zero of="$dev3" bs=1M oflag=direct,sync || true
 dd if=/dev/zero of="$dev4" bs=1M oflag=direct,sync || true
-pvscan --cache
+#pvscan --cache
 
 # Reverse devs in the previous in case dev3/dev4 would be
 # preferred even without an active LV using them.
@@ -471,6 +474,9 @@ lvcreate -l1 -n $lv2 $vg2 "$dev6"
 
 dd if="$dev5" of="$dev3" bs=1M iflag=direct oflag=direct,sync
 dd if="$dev6" of="$dev4" bs=1M iflag=direct oflag=direct,sync
+# dev3/dev4 are not pvs (zeroed above) so dd'ing pv onto them causes
+# invalid hints that won't be detected, so 3/4 won't be scanned
+# unless we force hint recreation
 pvscan --cache
 
 pvs -o+uuid,duplicate 2>&1 | tee out
@@ -506,7 +512,7 @@ not grep "prefers device $dev4" warn
 
 dd if=/dev/zero of="$dev3" bs=1M oflag=direct,sync || true
 dd if=/dev/zero of="$dev4" bs=1M oflag=direct,sync || true
-pvscan --cache
+#pvscan --cache
 
 lvremove -y $vg2/$lv1
 lvremove -y $vg2/$lv2
diff --git a/tools/command.c b/tools/command.c
index 6931e44..bf2879f 100644
--- a/tools/command.c
+++ b/tools/command.c
@@ -136,6 +136,8 @@ static inline int configtype_arg(struct cmd_context *cmd __attribute__((unused))
 #define DISALLOW_TAG_ARGS        0x00000800
 #define GET_VGNAME_FROM_OPTIONS  0x00001000
 #define CAN_USE_ONE_SCAN	 0x00002000
+#define ALLOW_HINTS              0x00004000
+
 
 /* create foo_CMD enums for command def ID's in command-lines.in */
 
diff --git a/tools/commands.h b/tools/commands.h
index 8c653e6..83e50e7 100644
--- a/tools/commands.h
+++ b/tools/commands.h
@@ -35,7 +35,7 @@ xx(help,
 
 xx(fullreport,
    "Display full report",
-   PERMITTED_READ_ONLY | ALL_VGS_IS_DEFAULT | LOCKD_VG_SH)
+   PERMITTED_READ_ONLY | ALL_VGS_IS_DEFAULT | LOCKD_VG_SH | ALLOW_HINTS)
 
 xx(lastlog,
    "Display last command's log report",
@@ -43,7 +43,7 @@ xx(lastlog,
 
 xx(lvchange,
    "Change the attributes of logical volume(s)",
-   PERMITTED_READ_ONLY)
+   PERMITTED_READ_ONLY | ALLOW_HINTS)
 
 xx(lvconvert,
    "Change logical volume layout",
@@ -51,15 +51,15 @@ xx(lvconvert,
 
 xx(lvcreate,
    "Create a logical volume",
-   0)
+   ALLOW_HINTS)
 
 xx(lvdisplay,
    "Display information about a logical volume",
-   PERMITTED_READ_ONLY | ALL_VGS_IS_DEFAULT | LOCKD_VG_SH | CAN_USE_ONE_SCAN)
+   PERMITTED_READ_ONLY | ALL_VGS_IS_DEFAULT | LOCKD_VG_SH | CAN_USE_ONE_SCAN | ALLOW_HINTS)
 
 xx(lvextend,
    "Add space to a logical volume",
-   0)
+   ALLOW_HINTS)
 
 xx(lvmchange,
    "With the device mapper, this is obsolete and does nothing.",
@@ -83,23 +83,23 @@ xx(lvmsar,
 
 xx(lvreduce,
    "Reduce the size of a logical volume",
-   0)
+   ALLOW_HINTS)
 
 xx(lvremove,
    "Remove logical volume(s) from the system",
-   ALL_VGS_IS_DEFAULT) /* all VGs only with --select */
+   ALL_VGS_IS_DEFAULT | ALLOW_HINTS) /* all VGs only with --select */
 
 xx(lvrename,
    "Rename a logical volume",
-   0)
+   ALLOW_HINTS)
 
 xx(lvresize,
    "Resize a logical volume",
-   0)
+   ALLOW_HINTS)
 
 xx(lvs,
    "Display information about logical volumes",
-   PERMITTED_READ_ONLY | ALL_VGS_IS_DEFAULT | LOCKD_VG_SH | CAN_USE_ONE_SCAN)
+   PERMITTED_READ_ONLY | ALL_VGS_IS_DEFAULT | LOCKD_VG_SH | CAN_USE_ONE_SCAN | ALLOW_HINTS)
 
 xx(lvscan,
    "List all logical volumes in all volume groups",
@@ -127,7 +127,7 @@ xx(pvdata,
 
 xx(pvdisplay,
    "Display various attributes of physical volume(s)",
-   PERMITTED_READ_ONLY | ENABLE_ALL_DEVS | ENABLE_DUPLICATE_DEVS | LOCKD_VG_SH | CAN_USE_ONE_SCAN)
+   PERMITTED_READ_ONLY | ENABLE_ALL_DEVS | ENABLE_DUPLICATE_DEVS | LOCKD_VG_SH | CAN_USE_ONE_SCAN | ALLOW_HINTS)
 
 /* ALL_VGS_IS_DEFAULT is for polldaemon to find pvmoves in-progress using process_each_vg. */
 
@@ -137,7 +137,7 @@ xx(pvmove,
 
 xx(lvpoll,
    "Continue already initiated poll operation on a logical volume",
-   0)
+   ALLOW_HINTS)
 
 xx(pvremove,
    "Remove LVM label(s) from physical volume(s)",
@@ -145,7 +145,7 @@ xx(pvremove,
 
 xx(pvs,
    "Display information about physical volumes",
-   PERMITTED_READ_ONLY | ALL_VGS_IS_DEFAULT | ENABLE_ALL_DEVS | ENABLE_DUPLICATE_DEVS | LOCKD_VG_SH | CAN_USE_ONE_SCAN)
+   PERMITTED_READ_ONLY | ALL_VGS_IS_DEFAULT | ENABLE_ALL_DEVS | ENABLE_DUPLICATE_DEVS | LOCKD_VG_SH | CAN_USE_ONE_SCAN | ALLOW_HINTS)
 
 xx(pvscan,
    "List all physical volumes",
@@ -173,11 +173,11 @@ xx(vgcfgrestore,
 
 xx(vgchange,
    "Change volume group attributes",
-   PERMITTED_READ_ONLY | ALL_VGS_IS_DEFAULT)
+   PERMITTED_READ_ONLY | ALL_VGS_IS_DEFAULT | ALLOW_HINTS)
 
 xx(vgck,
    "Check the consistency of volume group(s)",
-   ALL_VGS_IS_DEFAULT | LOCKD_VG_SH)
+   ALL_VGS_IS_DEFAULT | LOCKD_VG_SH | ALLOW_HINTS)
 
 xx(vgconvert,
    "Change volume group metadata format",
@@ -189,7 +189,7 @@ xx(vgcreate,
 
 xx(vgdisplay,
    "Display volume group information",
-   PERMITTED_READ_ONLY | ALL_VGS_IS_DEFAULT | LOCKD_VG_SH | CAN_USE_ONE_SCAN)
+   PERMITTED_READ_ONLY | ALL_VGS_IS_DEFAULT | LOCKD_VG_SH | CAN_USE_ONE_SCAN | ALLOW_HINTS)
 
 xx(vgexport,
    "Unregister volume group(s) from the system",
@@ -228,7 +228,7 @@ xx(vgrename,
 
 xx(vgs,
    "Display information about volume groups",
-   PERMITTED_READ_ONLY | ALL_VGS_IS_DEFAULT | LOCKD_VG_SH | CAN_USE_ONE_SCAN)
+   PERMITTED_READ_ONLY | ALL_VGS_IS_DEFAULT | LOCKD_VG_SH | CAN_USE_ONE_SCAN | ALLOW_HINTS)
 
 xx(vgscan,
    "Search for all volume groups",
diff --git a/tools/lvmcmdline.c b/tools/lvmcmdline.c
index 9c0476e..55a068f 100644
--- a/tools/lvmcmdline.c
+++ b/tools/lvmcmdline.c
@@ -2286,6 +2286,7 @@ static void _apply_current_output_settings(struct cmd_context *cmd)
 static int _get_current_settings(struct cmd_context *cmd)
 {
 	const char *activation_mode;
+	const char *hint_mode;
 
 	_get_current_output_settings_from_args(cmd);
 
@@ -2313,6 +2314,29 @@ static int _get_current_settings(struct cmd_context *cmd)
 	if (cmd->cname->flags & CAN_USE_ONE_SCAN)
 		cmd->can_use_one_scan = 1;
 
+	cmd->scan_lvs = find_config_tree_bool(cmd, devices_scan_lvs_CFG, NULL);
+
+	/*
+	 * 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.
+	 * 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.
+	 *
+	 * enable_hints=0 means no commands are using hints, so this
+	 * command would not need to invalidate hints for other cmds.
+	 */
+	cmd->enable_hints = 1;
+
+	/* Only certain commands need to be optimized by using hints. */
+	if (cmd->cname->flags & ALLOW_HINTS)
+		cmd->use_hints = 1;
+
+	if ((hint_mode = find_config_tree_str(cmd, devices_hints_CFG, NULL))) {
+		if (!strcmp(hint_mode, "none"))
+			cmd->enable_hints = 0;
+	}
+
 	cmd->partial_activation = 0;
 	cmd->degraded_activation = 0;
 	activation_mode = find_config_tree_str(cmd, activation_mode_CFG, NULL);
@@ -2688,6 +2712,12 @@ static int _init_lvmlockd(struct cmd_context *cmd)
 	const char *lvmlockd_socket;
 	int use_lvmlockd = find_config_tree_bool(cmd, global_use_lvmlockd_CFG, NULL);
 
+	/*
+	 * Think about when/how to enable hints with lvmlockd.
+	 */
+	if (use_lvmlockd)
+		cmd->enable_hints = 0;
+
 	if (use_lvmlockd && arg_is_set(cmd, nolocking_ARG)) {
 		/* --nolocking is only allowed with vgs/lvs/pvs commands */
 		cmd->lockd_gl_disable = 1;
diff --git a/tools/pvchange.c b/tools/pvchange.c
index 8c19268..696dab4 100644
--- a/tools/pvchange.c
+++ b/tools/pvchange.c
@@ -252,6 +252,8 @@ int pvchange(struct cmd_context *cmd, int argc, char **argv)
 
 	set_pv_notify(cmd);
 
+	clear_hint_file(cmd);
+
 	ret = process_each_pv(cmd, argc, argv, NULL, 0, READ_FOR_UPDATE | READ_ALLOW_EXPORTED, handle, _pvchange_single);
 
 	if (!argc)
diff --git a/tools/pvcreate.c b/tools/pvcreate.c
index 4ffb12f..c244a1f 100644
--- a/tools/pvcreate.c
+++ b/tools/pvcreate.c
@@ -148,6 +148,8 @@ int pvcreate(struct cmd_context *cmd, int argc, char **argv)
 		return_ECMD_FAILED;
 	cmd->lockd_gl_disable = 1;
 
+	clear_hint_file(cmd);
+
 	if (!(handle = init_processing_handle(cmd, NULL))) {
 		log_error("Failed to initialize processing handle.");
 		return ECMD_FAILED;
diff --git a/tools/pvdisplay.c b/tools/pvdisplay.c
index 3c97a7f..11f38eb 100644
--- a/tools/pvdisplay.c
+++ b/tools/pvdisplay.c
@@ -93,6 +93,13 @@ int pvdisplay(struct cmd_context *cmd, int argc, char **argv)
 		return EINVALID_CMD_LINE;
 	}
 
+	/*
+	 * Without -a, command only looks at PVs and can use hints,
+	 * with -a, the command looks at all (non-hinted) devices.
+	 */
+	if (arg_is_set(cmd, all_ARG))
+		cmd->use_hints = 0;
+
 	ret = process_each_pv(cmd, argc, argv, NULL,
 			      arg_is_set(cmd, all_ARG), 0,
 			      NULL, _pvdisplay_single);
diff --git a/tools/pvremove.c b/tools/pvremove.c
index 06a7e73..5f76de6 100644
--- a/tools/pvremove.c
+++ b/tools/pvremove.c
@@ -48,6 +48,8 @@ int pvremove(struct cmd_context *cmd, int argc, char **argv)
 	}
 	cmd->lockd_gl_disable = 1;
 
+	clear_hint_file(cmd);
+
 	/* When forcibly clearing a PV we don't care about a VG lock. */
 	if (pp.force == DONT_PROMPT_OVERRIDE)
 		cmd->lockd_vg_disable = 1;
diff --git a/tools/pvscan.c b/tools/pvscan.c
index 3f3c745..da1e435 100644
--- a/tools/pvscan.c
+++ b/tools/pvscan.c
@@ -656,6 +656,15 @@ int pvscan_cache_cmd(struct cmd_context *cmd, int argc, char **argv)
 	 * Scan all devices when no args are given.
 	 */
 	if (!argc && !devno_args) {
+		/*
+		 * pvscan --cache removes existing hints and recreates new ones.
+		 * We begin by clearing hints at the start of the command like
+		 * vgcreate would do.  The pvscan_recreate_hints flag is used
+		 * to enable the special case hint recreation in label_scan.
+		 */
+		cmd->pvscan_recreate_hints = 1;
+		clear_hint_file(cmd);
+
 		log_verbose("pvscan all devices.");
 		_online_pvid_files_remove();
 		_online_pvscan_all_devs(cmd, NULL, NULL);
diff --git a/tools/reporter.c b/tools/reporter.c
index c321d21..ee38db3 100644
--- a/tools/reporter.c
+++ b/tools/reporter.c
@@ -1456,6 +1456,13 @@ int pvs(struct cmd_context *cmd, int argc, char **argv)
 {
 	report_type_t type;
 
+	/*
+	 * Without -a, command only looks at PVs and can use hints,
+	 * with -a, the command looks at all (non-hinted) devices.
+	 */
+	if (arg_is_set(cmd, all_ARG))
+		cmd->use_hints = 0;
+
 	if (arg_is_set(cmd, segments_ARG))
 		type = PVSEGS;
 	else
diff --git a/tools/toollib.c b/tools/toollib.c
index d8394a3..5206e26 100644
--- a/tools/toollib.c
+++ b/tools/toollib.c
@@ -15,6 +15,7 @@
 
 #include "tools.h"
 #include "lib/format_text/format-text.h"
+#include "lib/label/hints.h"
 
 #include <sys/stat.h>
 #include <signal.h>
@@ -3908,14 +3909,40 @@ static int _get_arg_devices(struct cmd_context *cmd,
 	return ret_max;
 }
 
-static int _get_all_devices(struct cmd_context *cmd, struct dm_list *all_devices)
+static int _get_all_devices(struct cmd_context *cmd,
+			    int process_all_devices,
+			    struct dm_list *all_devices)
 {
 	struct dev_iter *iter;
 	struct device *dev;
 	struct device_id_list *dil;
+	struct hint *hint;
 	int r = ECMD_FAILED;
 
-	log_debug("Getting list of all devices");
+	/*
+	 * If command is using hints and is only looking for PVs
+	 * (not all devices), then we can use only devs from hints.
+	 */
+	if (!process_all_devices && !dm_list_empty(&cmd->hints)) {
+		log_debug("Getting list of all devices from hints");
+
+		dm_list_iterate_items(hint, &cmd->hints) {
+			if (!(dev = dev_cache_get(cmd, hint->name, NULL)))
+				continue;
+
+			if (!(dil = dm_pool_alloc(cmd->mem, sizeof(*dil)))) {
+				log_error("device_id_list alloc failed.");
+				goto out;
+			}
+
+			strncpy(dil->pvid, hint->pvid, ID_LEN);
+			dil->dev = dev;
+			dm_list_add(all_devices, &dil->list);
+		}
+		return 1;
+	}
+
+	log_debug("Getting list of all devices from system");
 
 	if (!(iter = dev_iter_create(cmd->filter, 1))) {
 		log_error("dev_iter creation failed.");
@@ -4488,7 +4515,7 @@ int process_each_pv(struct cmd_context *cmd,
 	 * from all VGs are processed first, removing them from all_devices.  Then
 	 * any devs remaining in all_devices are processed.
 	 */
-	if ((ret = _get_all_devices(cmd, &all_devices)) != ECMD_PROCESSED) {
+	if ((ret = _get_all_devices(cmd, process_all_devices, &all_devices)) != ECMD_PROCESSED) {
 		ret_max = ret;
 		goto_out;
 	}
diff --git a/tools/tools.h b/tools/tools.h
index 5e0cd30..ab9503e 100644
--- a/tools/tools.h
+++ b/tools/tools.h
@@ -42,6 +42,7 @@
 #include "lib/commands/toolcontext.h"
 #include "toollib.h"
 #include "lib/notify/lvmnotify.h"
+#include "lib/label/hints.h"
 
 #include <ctype.h>
 #include <sys/types.h>
@@ -133,6 +134,8 @@ struct arg_value_group_list {
 #define GET_VGNAME_FROM_OPTIONS  0x00001000
 /* The data read from disk by label scan can be used for vg_read. */
 #define CAN_USE_ONE_SCAN	 0x00002000
+/* Command can use hints file */
+#define ALLOW_HINTS		 0x00004000
 
 
 void usage(const char *name);
diff --git a/tools/vgcfgrestore.c b/tools/vgcfgrestore.c
index 84032f1..2302f0a 100644
--- a/tools/vgcfgrestore.c
+++ b/tools/vgcfgrestore.c
@@ -130,6 +130,8 @@ int vgcfgrestore(struct cmd_context *cmd, int argc, char **argv)
 		return ECMD_FAILED;
 	}
 
+	clear_hint_file(cmd);
+
 	lvmcache_label_scan(cmd);
 
 	cmd->handles_unknown_segments = 1;
diff --git a/tools/vgcreate.c b/tools/vgcreate.c
index 2a40bc7..c146ab7 100644
--- a/tools/vgcreate.c
+++ b/tools/vgcreate.c
@@ -64,6 +64,8 @@ int vgcreate(struct cmd_context *cmd, int argc, char **argv)
 		return_ECMD_FAILED;
 	cmd->lockd_gl_disable = 1;
 
+	clear_hint_file(cmd);
+
 	/* Check for old md signatures at the end of devices. */
 	cmd->use_full_md_check = 1;
 
diff --git a/tools/vgextend.c b/tools/vgextend.c
index 5287a36..c727d75 100644
--- a/tools/vgextend.c
+++ b/tools/vgextend.c
@@ -166,6 +166,8 @@ int vgextend(struct cmd_context *cmd, int argc, char **argv)
 		return_ECMD_FAILED;
 	cmd->lockd_gl_disable = 1;
 
+	clear_hint_file(cmd);
+
 	if (!(handle = init_processing_handle(cmd, NULL))) {
 		log_error("Failed to initialize processing handle.");
 		return ECMD_FAILED;
diff --git a/tools/vgimportclone.c b/tools/vgimportclone.c
index b7fae61..34c211c 100644
--- a/tools/vgimportclone.c
+++ b/tools/vgimportclone.c
@@ -345,6 +345,8 @@ retry_name:
 	 */
 	cmd->lockd_vg_disable = 1;
 
+	clear_hint_file(cmd);
+
 	ret = process_each_vg(cmd, 0, NULL, vp.old_vgname, NULL, READ_FOR_UPDATE | READ_ALLOW_EXPORTED, 0, handle, _vgimportclone_vg_single);
 
 	unlock_vg(cmd, NULL, vp.new_vgname);
diff --git a/tools/vgmerge.c b/tools/vgmerge.c
index f2c785b..8c8f2a2 100644
--- a/tools/vgmerge.c
+++ b/tools/vgmerge.c
@@ -210,6 +210,8 @@ int vgmerge(struct cmd_context *cmd, int argc, char **argv)
 	if (!lockd_gl(cmd, "ex", LDGL_UPDATE_NAMES))
 		return ECMD_FAILED;
 
+	clear_hint_file(cmd);
+
 	vg_name_to = skip_dev_dir(cmd, argv[0], NULL);
 	argc--;
 	argv++;
diff --git a/tools/vgreduce.c b/tools/vgreduce.c
index ce3d155..1bca33f 100644
--- a/tools/vgreduce.c
+++ b/tools/vgreduce.c
@@ -224,6 +224,8 @@ int vgreduce(struct cmd_context *cmd, int argc, char **argv)
 		return_ECMD_FAILED;
 	cmd->lockd_gl_disable = 1;
 
+	clear_hint_file(cmd);
+
 	if (!(handle = init_processing_handle(cmd, NULL))) {
 		log_error("Failed to initialize processing handle.");
 		return ECMD_FAILED;
diff --git a/tools/vgremove.c b/tools/vgremove.c
index 5010e7d..2120858 100644
--- a/tools/vgremove.c
+++ b/tools/vgremove.c
@@ -101,6 +101,8 @@ int vgremove(struct cmd_context *cmd, int argc, char **argv)
 	if (!lockd_gl(cmd, "ex", LDGL_UPDATE_NAMES))
 		return ECMD_FAILED;
 
+	clear_hint_file(cmd);
+
 	/*
 	 * This is a special case: if vgremove is given a tag, it causes
 	 * process_each_vg to do lockd_gl(sh) when getting a list of all
diff --git a/tools/vgrename.c b/tools/vgrename.c
index 5e386cc..1286ed9 100644
--- a/tools/vgrename.c
+++ b/tools/vgrename.c
@@ -195,6 +195,8 @@ int vgrename(struct cmd_context *cmd, int argc, char **argv)
 	if (!lockd_gl(cmd, "ex", LDGL_UPDATE_NAMES))
 		return_ECMD_FAILED;
 
+	clear_hint_file(cmd);
+
 	/*
 	 * Special case where vg_name_old may be a UUID:
 	 * If vg_name_old is a UUID, then process_each may
diff --git a/tools/vgsplit.c b/tools/vgsplit.c
index fc99d2e..87f48df 100644
--- a/tools/vgsplit.c
+++ b/tools/vgsplit.c
@@ -569,6 +569,8 @@ int vgsplit(struct cmd_context *cmd, int argc, char **argv)
 	if (!lockd_gl(cmd, "ex", LDGL_UPDATE_NAMES))
 		return_ECMD_FAILED;
 
+	clear_hint_file(cmd);
+
 	if (arg_is_set(cmd, name_ARG))
 		lv_name = arg_value(cmd, name_ARG);
 	else




More information about the lvm-devel mailing list