mrc0mmand / rpms / lvm2

Forked from rpms/lvm2 2 years ago
Clone

Blame SOURCES/lvm2-2_03_12-devs-remove-invalid-path-name-aliases.patch

abb29f
 lib/device/dev-cache.c    | 161 ++++++++++++++++++++++++++++++++++++----------
abb29f
 test/shell/dev-aliases.sh |  53 +++++++++++++++
abb29f
 2 files changed, 179 insertions(+), 35 deletions(-)
abb29f
 create mode 100644 test/shell/dev-aliases.sh
abb29f
abb29f
diff --git a/lib/device/dev-cache.c b/lib/device/dev-cache.c
abb29f
index d5f18ff..8082efa 100644
abb29f
--- a/lib/device/dev-cache.c
abb29f
+++ b/lib/device/dev-cache.c
abb29f
@@ -1428,60 +1428,151 @@ struct device *dev_hash_get(const char *name)
abb29f
 	return (struct device *) dm_hash_lookup(_cache.names, name);
abb29f
 }
abb29f
 
abb29f
+static void _remove_alias(struct device *dev, const char *name)
abb29f
+{
abb29f
+	struct dm_str_list *strl;
abb29f
+
abb29f
+	dm_list_iterate_items(strl, &dev->aliases) {
abb29f
+		if (!strcmp(strl->str, name)) {
abb29f
+			dm_list_del(&strl->list);
abb29f
+			return;
abb29f
+		}
abb29f
+	}
abb29f
+}
abb29f
+
abb29f
+/*
abb29f
+ * Check that paths for this dev still refer to the same dev_t.  This is known
abb29f
+ * to drop invalid paths in the case where lvm deactivates an LV, which causes
abb29f
+ * that LV path to go away, but that LV path is not removed from dev-cache (it
abb29f
+ * probably should be).  Later a new path to a different LV is added to
abb29f
+ * dev-cache, where the new LV has the same major:minor as the previously
abb29f
+ * deactivated LV.  The new LV will find the existing struct dev, and that
abb29f
+ * struct dev will have dev->aliases entries that refer to the name of the old
abb29f
+ * deactivated LV.  Those old paths are all invalid and are dropped here.
abb29f
+ */
abb29f
+
abb29f
+static void _verify_aliases(struct device *dev, const char *newname)
abb29f
+{
abb29f
+	struct dm_str_list *strl, *strl2;
abb29f
+	struct stat st;
abb29f
+
abb29f
+	dm_list_iterate_items_safe(strl, strl2, &dev->aliases) {
abb29f
+		/* newname was just stat'd and added by caller */
abb29f
+		if (newname && !strcmp(strl->str, newname))
abb29f
+			continue;
abb29f
+
abb29f
+		if (stat(strl->str, &st) || (st.st_rdev != dev->dev)) {
abb29f
+			log_debug("Drop invalid path %s for %d:%d (new path %s).",
abb29f
+				  strl->str, (int)MAJOR(dev->dev), (int)MINOR(dev->dev), newname ?: "");
abb29f
+			dm_hash_remove(_cache.names, strl->str);
abb29f
+			dm_list_del(&strl->list);
abb29f
+		}
abb29f
+	}
abb29f
+}
abb29f
+
abb29f
 struct device *dev_cache_get(struct cmd_context *cmd, const char *name, struct dev_filter *f)
abb29f
 {
abb29f
-	struct stat buf;
abb29f
-	struct device *d = (struct device *) dm_hash_lookup(_cache.names, name);
abb29f
-	int info_available = 0;
abb29f
-	int ret = 1;
abb29f
+	struct device *dev = (struct device *) dm_hash_lookup(_cache.names, name);
abb29f
+	struct stat st;
abb29f
+	int ret;
abb29f
 
abb29f
-	if (d && (d->flags & DEV_REGULAR))
abb29f
-		return d;
abb29f
+	/*
abb29f
+	 * DEV_REGULAR means that is "dev" is actually a file, not a device.
abb29f
+	 * FIXME: I don't think dev-cache is used for files any more and this
abb29f
+	 * can be dropped?
abb29f
+	 */
abb29f
+	if (dev && (dev->flags & DEV_REGULAR))
abb29f
+		return dev;
abb29f
+
abb29f
+	/*
abb29f
+	 * The requested path is invalid, remove any dev-cache
abb29f
+	 * info for it.
abb29f
+	 */
abb29f
+	if (stat(name, &st)) {
abb29f
+		if (dev) {
abb29f
+			log_print("Device path %s is invalid for %d:%d %s.",
abb29f
+				  name, (int)MAJOR(dev->dev), (int)MINOR(dev->dev), dev_name(dev));
abb29f
 
abb29f
-	/* If the entry's wrong, remove it */
abb29f
-	if (stat(name, &buf) < 0) {
abb29f
-		if (d)
abb29f
 			dm_hash_remove(_cache.names, name);
abb29f
-		log_sys_very_verbose("stat", name);
abb29f
-		d = NULL;
abb29f
-	} else
abb29f
-		info_available = 1;
abb29f
 
abb29f
-	if (d && (buf.st_rdev != d->dev)) {
abb29f
-		dm_hash_remove(_cache.names, name);
abb29f
-		d = NULL;
abb29f
-	}
abb29f
+			_remove_alias(dev, name);
abb29f
 
abb29f
-	if (!d) {
abb29f
-		_insert(name, info_available ? &buf : NULL, 0, obtain_device_list_from_udev());
abb29f
-		d = (struct device *) dm_hash_lookup(_cache.names, name);
abb29f
-		if (!d) {
abb29f
-			log_debug_devs("Device name not found in dev_cache repeat dev_cache_scan for %s", name);
abb29f
-			dev_cache_scan();
abb29f
-			d = (struct device *) dm_hash_lookup(_cache.names, name);
abb29f
+			/* Remove any other names in dev->aliases that are incorrect. */
abb29f
+			_verify_aliases(dev, NULL);
abb29f
 		}
abb29f
+		return NULL;
abb29f
 	}
abb29f
 
abb29f
-	if (!d)
abb29f
+	if (!S_ISBLK(st.st_mode)) {
abb29f
+		log_debug("Not a block device %s.", name);
abb29f
 		return NULL;
abb29f
+	}
abb29f
 
abb29f
-	if (d && (d->flags & DEV_REGULAR))
abb29f
-		return d;
abb29f
+	/*
abb29f
+	 * dev-cache has incorrect info for the requested path.
abb29f
+	 * Remove incorrect info and then add new dev-cache entry.
abb29f
+	 */
abb29f
+	if (dev && (st.st_rdev != dev->dev)) {
abb29f
+		log_print("Device path %s does not match %d:%d %s.",
abb29f
+			  name, (int)MAJOR(dev->dev), (int)MINOR(dev->dev), dev_name(dev));
abb29f
+
abb29f
+		dm_hash_remove(_cache.names, name);
abb29f
+
abb29f
+		_remove_alias(dev, name);
abb29f
+
abb29f
+		/* Remove any other names in dev->aliases that are incorrect. */
abb29f
+		_verify_aliases(dev, NULL);
abb29f
+
abb29f
+		/* Add new dev-cache entry next. */
abb29f
+		dev = NULL;
abb29f
+	}
abb29f
+
abb29f
+	/*
abb29f
+	 * Either add a new struct dev for st_rdev and name,
abb29f
+	 * or add name as a new alias for an existing struct dev
abb29f
+	 * for st_rdev.
abb29f
+	 */
abb29f
+	if (!dev) {
abb29f
+		_insert_dev(name, st.st_rdev);
abb29f
 
abb29f
-	if (f && !(d->flags & DEV_REGULAR)) {
abb29f
-		ret = f->passes_filter(cmd, f, d, NULL);
abb29f
+		/* Get the struct dev that was just added. */
abb29f
+		dev = (struct device *) dm_hash_lookup(_cache.names, name);
abb29f
 
abb29f
-		if (ret == -EAGAIN) {
abb29f
-			log_debug_devs("get device by name defer filter %s", dev_name(d));
abb29f
-			d->flags |= DEV_FILTER_AFTER_SCAN;
abb29f
-			ret = 1;
abb29f
+		if (!dev) {
abb29f
+			log_error("Failed to get device %s", name);
abb29f
+			return NULL;
abb29f
 		}
abb29f
+
abb29f
+		_verify_aliases(dev, name);
abb29f
 	}
abb29f
 
abb29f
-	if (f && !(d->flags & DEV_REGULAR) && !ret)
abb29f
+	/*
abb29f
+	 * The caller passed a filter if they only want the dev if it
abb29f
+	 * passes filters.
abb29f
+	 */
abb29f
+
abb29f
+	if (!f)
abb29f
+		return dev;
abb29f
+
abb29f
+	ret = f->passes_filter(cmd, f, dev, NULL);
abb29f
+
abb29f
+	/*
abb29f
+	 * This might happen if this function is called before
abb29f
+	 * filters can do i/o.  I don't think this will happen
abb29f
+	 * any longer and this EAGAIN case can be removed.
abb29f
+	 */
abb29f
+	if (ret == -EAGAIN) {
abb29f
+		log_debug_devs("dev_cache_get filter deferred %s", dev_name(dev));
abb29f
+		dev->flags |= DEV_FILTER_AFTER_SCAN;
abb29f
+		ret = 1;
abb29f
+	}
abb29f
+
abb29f
+	if (!ret) {
abb29f
+		log_debug_devs("dev_cache_get filter excludes %s", dev_name(dev));
abb29f
 		return NULL;
abb29f
+	}
abb29f
 
abb29f
-	return d;
abb29f
+	return dev;
abb29f
 }
abb29f
 
abb29f
 static struct device *_dev_cache_seek_devt(dev_t dev)
abb29f
diff --git a/test/shell/dev-aliases.sh b/test/shell/dev-aliases.sh
abb29f
new file mode 100644
abb29f
index 0000000..c97cd5d
abb29f
--- /dev/null
abb29f
+++ b/test/shell/dev-aliases.sh
abb29f
@@ -0,0 +1,53 @@
abb29f
+#!/usr/bin/env bash
abb29f
+
abb29f
+# Copyright (C) 2012 Red Hat, Inc. All rights reserved.
abb29f
+#
abb29f
+# This copyrighted material is made available to anyone wishing to use,
abb29f
+# modify, copy, or redistribute it subject to the terms and conditions
abb29f
+# of the GNU General Public License v.2.
abb29f
+#
abb29f
+# You should have received a copy of the GNU General Public License
abb29f
+# along with this program; if not, write to the Free Software Foundation,
abb29f
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
abb29f
+
abb29f
+SKIP_WITH_LVMPOLLD=1
abb29f
+
abb29f
+. lib/inittest
abb29f
+
abb29f
+aux prepare_devs 3
abb29f
+
abb29f
+vgcreate $vg $dev1 $dev2 $dev3
abb29f
+
abb29f
+#
abb29f
+# This lvconvert command will deactivate LV1, then internally create a new
abb29f
+# lv, lvol0, as a poolmetadataspare, then activate lvol0 to zero it.
abb29f
+# lvol0 will get the same major:minor that LV1 had.  When the code gets
abb29f
+# the struct dev for lvol0, the new path to lvol0 is added to the
abb29f
+# dev-cache with it's major:minor.  That major:minor already exists in
abb29f
+# dev-cache and has the stale LV1 as an alias.  So the path to lvol0 is
abb29f
+# added as an alias to the existing struct dev (with the correct
abb29f
+# major:minor), but that struct dev has the stale LV1 path on its aliases
abb29f
+# list.  The code will now validate all the aliases before returning the
abb29f
+# dev for lvol0, and will find that the LV1 path is stale and remove it
abb29f
+# from the aliases.  That will prevent the stale path from being used for
abb29f
+# the dev in place of the new path.
abb29f
+#
abb29f
+# The preferred_name is set to /dev/mapper so that if the stale path still
abb29f
+# exists, that stale path would be used as the name for the dev, and the
abb29f
+# wiping code would fail to open that stale name.
abb29f
+#
abb29f
+
abb29f
+lvcreate -n $lv1 -L32M $vg $dev1
abb29f
+lvcreate -n $lv2 -L16M $vg $dev2
abb29f
+lvconvert -y --type cache-pool --poolmetadata $lv2 --cachemode writeback $vg/$lv1 --config='devices { preferred_names=["/dev/mapper/"] }' 
abb29f
+lvremove -y $vg/$lv1
abb29f
+
abb29f
+lvcreate -n $lv1 -L32M $vg $dev1
abb29f
+lvcreate -n $lv2 -L16M $vg $dev2
abb29f
+lvconvert -y --type cache-pool --poolmetadata $lv2 $vg/$lv1
abb29f
+lvremove -y $vg/$lv1
abb29f
+
abb29f
+# TODO: add more validation of dev aliases being specified as command
abb29f
+# args in combination with various preferred_names settings.
abb29f
+
abb29f
+vgremove -ff  $vg