]> sourceware.org Git - lvm2.git/commitdiff
add device hints to reduce scanning
authorDavid Teigland <teigland@redhat.com>
Fri, 7 Dec 2018 20:35:22 +0000 (14:35 -0600)
committerDavid Teigland <teigland@redhat.com>
Tue, 15 Jan 2019 16:23:47 +0000 (10:23 -0600)
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.)

49 files changed:
lib/Makefile.in
lib/cache/lvmcache.c
lib/cache/lvmcache.h
lib/commands/toolcontext.c
lib/commands/toolcontext.h
lib/config/config_settings.h
lib/config/defaults.h
lib/device/dev-cache.c
lib/device/dev-cache.h
lib/device/dev-type.c
lib/device/dev-type.h
lib/device/device.h
lib/filters/filter-composite.c
lib/filters/filter-fwraid.c
lib/filters/filter-internal.c
lib/filters/filter-md.c
lib/filters/filter-mpath.c
lib/filters/filter-partitioned.c
lib/filters/filter-persistent.c
lib/filters/filter-regex.c
lib/filters/filter-signature.c
lib/filters/filter-sysfs.c
lib/filters/filter-type.c
lib/filters/filter-usable.c
lib/label/hints.c [new file with mode: 0644]
lib/label/hints.h [new file with mode: 0644]
lib/label/label.c
test/shell/hints.sh [new file with mode: 0644]
test/shell/process-each-duplicate-pvs.sh
tools/command.c
tools/commands.h
tools/lvmcmdline.c
tools/pvchange.c
tools/pvcreate.c
tools/pvdisplay.c
tools/pvremove.c
tools/pvscan.c
tools/reporter.c
tools/toollib.c
tools/tools.h
tools/vgcfgrestore.c
tools/vgcreate.c
tools/vgextend.c
tools/vgimportclone.c
tools/vgmerge.c
tools/vgreduce.c
tools/vgremove.c
tools/vgrename.c
tools/vgsplit.c

index bde66f97e50ee3f47e5c49e3a856f45622791d9d..654c322e23cbf31cf0bcded5995ea261330c7fac 100644 (file)
@@ -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 \
index da8b4d8d29e7d79b34931dfa222b9e05b087ffc1..0ffa604dfeaf2a46649db6db51ab99458eab69a7 100644 (file)
@@ -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;
+}
index ba60405522819ab978006e61a2dfc5d17aff8346..12f17dfa690c0283a06f6d969ce6166053caa6a9 100644 (file)
@@ -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_
index 63eafe85f7eddac47a7051d2573bf4be1ee78984..39ab3df192c6841a6ec29d4dc67dbecf1e3cb5a3 100644 (file)
@@ -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;
index 6396f6c334298094b86d64cee515dc737bfe01c2..e8ce312a4ea2294fdd8699d70e3dbc2b90ff1429 100644 (file)
@@ -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.
index f1d282645714ce87aeded873a295b7b1f4053b99..e15494d5b58ab523d3bfb948e8b79368df4aaee2 100644 (file)
@@ -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"
index b45324b091461143c3da3d138b8df306caf59f69..06a5ecf1600bffb14b701f9772d6b339128cc960 100644 (file)
 
 #define DEFAULT_SCAN_LVS 1
 
+#define DEFAULT_HINTS "all"
+
 #endif                         /* _LVM_DEFAULTS_H */
index fb48f1a8075a42006ba3218a853bdb62d1034390..8afebfe93010d296932f2722f9463693af73c21e 100644 (file)
@@ -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));
index 41c4a9cc7b9b35fbbcae36f525f14557f960f7e8..9233c5266f04aa90219667e6cfc478fd29f0ea05 100644 (file)
@@ -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);
index 638f4b285df40ee7f72fc8ff5e00898a4798f3d0..9278b51ab0a7c1098cf01145023c91f499e8f608 100644 (file)
@@ -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)
 {
index 75539e82ee4847d1ab0ffd87cbdc8b3590f1ee8f..d34990551adf21d9b553fc76a795cb388e74fe90 100644 (file)
@@ -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
index e879dbb2ecf8f1d2fd5e9de08c7590de56485fb8..fa7e738e0cb9b8b8d2770eb02f9a1a8fec2d20cd 100644 (file)
@@ -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.
index a9374abaa819f19e5fb9fb319d85cdd601d69704..b0063f1490d3814f508e4c1ab1f500599117b68c 100644 (file)
 #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.");
 
index 6f476921b988433464841d64ef64a07f02b8debe..992eba8b84ff6c900e3463948494dd28b39f3779 100644 (file)
@@ -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.");
 
index 8cc0011b19197cf1110b306713d89c7ecfedb450..c10e8105f2fc3f04027fed50b18ed7e0da1b079d 100644 (file)
@@ -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.");
 
index 9cc1a0641398c64cf9e117e12b703dfb191970c5..d6dd28cfb41d816fb1b3d633d2bdbe23da1e8612 100644 (file)
@@ -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.");
 
index bcd1e52eec6a81c439042b14075bd6155e97831e..f0374b4d0d382b17126999045eaad4abd1522ba7 100644 (file)
@@ -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.");
 
index 6418cdf062de9de48423c77dff73b5d46b364c03..1a700543c8e58415edd8514b50b5e310c2e63339 100644 (file)
@@ -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.");
 
index 130b1e51787c394688ad16fe5ab003bacee26a54..afa32d4b9a03e71f7a201aa0db19a3dda77607e6 100644 (file)
@@ -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.");
 
index 1a8e8a2efd5b0c3e537d9183d83db5dd6d174907..e439b36b53f148b959709873df1632b7262562e7 100644 (file)
@@ -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.");
 
index 5c5796f9c1c82559d27754715328c2f405728267..6a81203a9f01dbbcec22f3c52efa373736a457da 100644 (file)
@@ -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.");
 
index c77c4a6b00c5f1b956c62d1fe074805589650be1..ebca8087c60fd2e332fbf451924194a4b2af53f1 100644 (file)
@@ -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.");
 
index 3b0a644dfea4a62f4afb15a98f9ee7eaf6b67730..1d083707f1d5bcbbcabdd0fe26f731bc1fcaa999 100644 (file)
@@ -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.");
 
index 699736893d93a029718e29b3b44c152318c48449..b3ff650753c830ba8001265ee8b2b4be19f96f9d 100644 (file)
@@ -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 (file)
index 0000000..bdd70a9
--- /dev/null
@@ -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 (file)
index 0000000..d80016e
--- /dev/null
@@ -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
+
index 6fe1e41ecec8829cf866d406823e0ddc9e5b00c4..7d5073ef5b8fe632992dd3eae5f2af4135300a0b 100644 (file)
@@ -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 (file)
index 0000000..bdaf3be
--- /dev/null
@@ -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
+
index b8a87746efefc072cbfdf014083fe265980988a1..ffd085a9a6fa1bd15f76934cc17c0f672e6b6d57 100644 (file)
@@ -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
index 6931e44d4b95cfb8f18ef8820f24d176d7b4e51b..bf2879fa329b03fd7bc1762ab494002c7d67cf24 100644 (file)
@@ -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 */
 
index 8c653e63fef560deb643a8967455cdd36cb3d4bd..83e50e7f035dcefa834e3b55771f9306dd2b7f69 100644 (file)
@@ -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",
index 9c0476e713835bdcabce752312e8213dee112dc9..55a068f7ff52287792fd07a3a06a19f5b0f84ab5 100644 (file)
@@ -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;
index 8c192680e89cbb12104a82625b0dd8fe940745c1..696dab4acffb8889393acdce15e96ced9fbdec4b 100644 (file)
@@ -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)
index 4ffb12fb7f321afdcaa6b0c6293580858cee6664..c244a1f025bd205b93e818bff41d1b269d6f3214 100644 (file)
@@ -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;
index 3c97a7f2980853500192566f6d76483c3663eaf4..11f38ebd22c336e0b81af2fa99e10ee235606f4b 100644 (file)
@@ -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);
index 06a7e730717216cc93e06dbc627bbf9b72bb4bac..5f76de64c7726c13f68a5ad2aec63ac1eeaed755 100644 (file)
@@ -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;
index 3f3c74513ab4e30cf9dabaa15adeca357fa4ec08..da1e4354b5520115e3705329b531d948fbfaae68 100644 (file)
@@ -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);
index c321d21538736c245792ddf71e7fd94668bfce9e..ee38db3b10485bbd1bddecc0cb41bb4e97d673c9 100644 (file)
@@ -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
index d8394a37d9d93b6138549ae200ab396a113a1d10..5206e26d38f52e9f6271b81490febda4baebd096 100644 (file)
@@ -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;
        }
index 5e0cd3046e783fcadc8f310b67c030a531962b24..ab9503e2b319acb68b51a711a8cfb7313082bc09 100644 (file)
@@ -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);
index 84032f133abbb974dec85266f0ac56d70e13d612..2302f0ad26ddd6cdd1b3260c75af185c2eb42568 100644 (file)
@@ -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;
index 2a40bc718c2e94b8e2f3b5b7967a65f602eb0430..c146ab776be644970ea7a389ce93efb9872a202d 100644 (file)
@@ -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;
 
index 5287a364ee81fb038a038bc2e2dbe2f96f8914eb..c727d75bb5175ab023e1f518a56da2cf667979d7 100644 (file)
@@ -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;
index b7fae61fac40a9e9283d3abb45565b518e0a59cf..34c211c72eeb7109cbbf4c9cdc5d7e3181aa918d 100644 (file)
@@ -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);
index f2c785ba7a23395442d552a80cefa63c12f9259c..8c8f2a2a07ac97fed2ae79023b1f571313ba7114 100644 (file)
@@ -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++;
index ce3d155c768eb6ad2f7e774b075475c106a957bd..1bca33fb37cbf20ff13fff98574c8c84c50cc329 100644 (file)
@@ -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;
index 5010e7d6bc4bdadb74564c7385c09d2d416be0fe..2120858691715fa39a911572463349594da0f45f 100644 (file)
@@ -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
index 5e386cc7464ba028c325d6272306d673cde3837d..1286ed9670f8a76a96ceba5de9177986df0f1085 100644 (file)
@@ -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
index fc99d2ee583fbea2a3259e5245b235a46f6282b0..87f48df8a889301fefbeb052d19bc0731bbba555 100644 (file)
@@ -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
This page took 0.157877 seconds and 5 git commands to generate.