From: Hannes Reinecke <hare@suse.de>
Date: Thu, 19 Nov 2009 13:54:56 +0100
Subject: Accept failed paths for multipath maps
References: bnc#458037,bnc#458393
Patch-Mainline: submitted to dm-devel 12/18/2013

The multipath kernel module is rejecting any map with an invalid
device. However, as the multipathd is processing the events serially
it will try to push a map with invalid devices if more than one
device failed at the same time.
So we can as well accept those maps and make sure to mark the
paths as down.

Signed-off-by: Hannes Reinecke <hare@suse.de>
---
 drivers/md/dm-mpath.c |   95 ++++++++++++++++++++++++++++++++++++++------------
 drivers/md/dm-mpath.h |    1 
 drivers/md/dm-table.c |    3 +
 3 files changed, 77 insertions(+), 22 deletions(-)

--- a/drivers/md/dm-mpath.c
+++ b/drivers/md/dm-mpath.c
@@ -348,6 +348,9 @@ static struct pgpath *choose_path_in_pg(
 
 	pgpath = path_to_pgpath(path);
 
+	if (!pgpath->path.dev)
+		return ERR_PTR(-ENODEV);
+
 	if (unlikely(lockless_dereference(m->current_pg) != pg)) {
 		/* Only update current_pgpath if pg changed */
 		spin_lock_irqsave(&m->lock, flags);
@@ -627,6 +630,7 @@ static struct pgpath *parse_path(struct
 {
 	int r;
 	struct pgpath *p;
+	const char *path;
 	struct multipath *m = ti->private;
 	struct request_queue *q = NULL;
 	const char *attached_handler_name;
@@ -641,17 +645,40 @@ static struct pgpath *parse_path(struct
 	if (!p)
 		return ERR_PTR(-ENOMEM);
 
-	r = dm_get_device(ti, dm_shift_arg(as), dm_table_get_mode(ti->table),
+	path = dm_shift_arg(as);
+	r = dm_get_device(ti, path, dm_table_get_mode(ti->table),
 			  &p->path.dev);
 	if (r) {
-		ti->error = "error getting device";
-		goto bad;
+		unsigned major, minor;
+
+		/* Try to add a failed device */
+		if (r == -ENXIO && sscanf(path, "%u:%u", &major, &minor) == 2) {
+			dev_t dev;
+
+			/* Extract the major/minor numbers */
+			dev = MKDEV(major, minor);
+			if (MAJOR(dev) != major || MINOR(dev) != minor) {
+				/* Nice try, didn't work */
+				DMWARN("Invalid device path %s", path);
+				ti->error = "error converting devnum";
+				goto bad;
+			}
+			DMWARN("adding disabled device %d:%d", major, minor);
+			p->path.dev = NULL;
+			format_dev_t(p->path.pdev, dev);
+			p->is_active = 0;
+		} else {
+			ti->error = "error getting device";
+			goto bad;
+		}
+	} else {
+		memcpy(p->path.pdev, p->path.dev->name, 16);
 	}
 
-	if (test_bit(MPATHF_RETAIN_ATTACHED_HW_HANDLER, &m->flags) || m->hw_handler_name)
+	if (p->path.dev)
 		q = bdev_get_queue(p->path.dev->bdev);
 
-	if (test_bit(MPATHF_RETAIN_ATTACHED_HW_HANDLER, &m->flags)) {
+	if (q && test_bit(MPATHF_RETAIN_ATTACHED_HW_HANDLER, &m->flags)) {
 retain:
 		attached_handler_name = scsi_dh_attached_handler_name(q, GFP_KERNEL);
 		if (attached_handler_name) {
@@ -671,7 +698,7 @@ retain:
 		}
 	}
 
-	if (m->hw_handler_name) {
+	if (q && m->hw_handler_name) {
 		r = scsi_dh_attach(q, m->hw_handler_name);
 		if (r == -EBUSY) {
 			char b[BDEVNAME_SIZE];
@@ -703,6 +730,11 @@ retain:
 		goto bad;
 	}
 
+	if (!p->is_active) {
+		ps->type->fail_path(ps, &p->path);
+		p->fail_count++;
+		atomic_dec(&m->nr_valid_paths);
+	}
 	return p;
 
  bad:
@@ -1024,10 +1056,10 @@ static int fail_path(struct pgpath *pgpa
 
 	spin_lock_irqsave(&m->lock, flags);
 
-	if (!pgpath->is_active)
+	if (!pgpath->path.dev || !pgpath->is_active)
 		goto out;
 
-	DMWARN("Failing path %s.", pgpath->path.dev->name);
+	DMWARN("Failing path %s.", pgpath->path.pdev);
 
 	pgpath->pg->ps.type->fail_path(&pgpath->pg->ps, &pgpath->path);
 	pgpath->is_active = false;
@@ -1039,7 +1071,7 @@ static int fail_path(struct pgpath *pgpa
 		m->current_pgpath = NULL;
 
 	dm_path_uevent(DM_UEVENT_PATH_FAILED, m->ti,
-		       pgpath->path.dev->name, atomic_read(&m->nr_valid_paths));
+		       pgpath->path.pdev, atomic_read(&m->nr_valid_paths));
 
 	schedule_work(&m->trigger_event);
 
@@ -1064,6 +1096,12 @@ static int reinstate_path(struct pgpath
 	if (pgpath->is_active)
 		goto out;
 
+	if (!pgpath->path.dev) {
+		DMWARN("Cannot reinstate disabled path %s", pgpath->path.pdev);
+		r = -ENODEV;
+		goto out;
+	}
+
 	DMWARN("Reinstating path %s.", pgpath->path.dev->name);
 
 	r = pgpath->pg->ps.type->reinstate_path(&pgpath->pg->ps, &pgpath->path);
@@ -1082,7 +1120,7 @@ static int reinstate_path(struct pgpath
 	}
 
 	dm_path_uevent(DM_UEVENT_PATH_REINSTATED, m->ti,
-		       pgpath->path.dev->name, nr_valid_paths);
+		       pgpath->path.pdev, nr_valid_paths);
 
 	schedule_work(&m->trigger_event);
 
@@ -1104,6 +1142,9 @@ static int action_dev(struct multipath *
 	struct pgpath *pgpath;
 	struct priority_group *pg;
 
+	if (!dev)
+		return 0;
+
 	list_for_each_entry(pg, &m->priority_groups, list) {
 		list_for_each_entry(pgpath, &pg->pgpaths, list) {
 			if (pgpath->path.dev == dev)
@@ -1229,7 +1270,7 @@ static void pg_init_done(void *data, int
 			break;
 		}
 		DMERR("Could not failover device %s: Handler scsi_dh_%s "
-		      "not attached.", pgpath->path.dev->name,
+		      "not attached.", pgpath->path.pdev,
 		      m->hw_handler_name);
 		/*
 		 * Fail path for now, so we do not ping pong
@@ -1246,7 +1287,7 @@ static void pg_init_done(void *data, int
 	case SCSI_DH_RETRY:
 		/* Wait before retrying. */
 		delay_retry = 1;
-		DMWARN("Device %s busy, retry.", pgpath->path.dev->name);
+		DMWARN("Device %s busy, retry.", pgpath->path.pdev);
 	case SCSI_DH_IMM_RETRY:
 	case SCSI_DH_RES_TEMP_UNAVAIL:
 		if (pg_init_limit_reached(m, pgpath))
@@ -1261,7 +1302,7 @@ static void pg_init_done(void *data, int
 		 * patches we can do more advanced handling.
 		 */
 		DMERR("Could not failover device %s: Handler scsi_dh_%s "
-		      "error %d.", pgpath->path.dev->name,
+		      "error %d.", pgpath->path.pdev,
 		      m->hw_handler_name, errors);
 		fail_path(pgpath);
 	}
@@ -1303,12 +1344,16 @@ static void activate_path(struct work_st
 {
 	struct pgpath *pgpath =
 		container_of(work, struct pgpath, activate_path.work);
-	struct request_queue *q = bdev_get_queue(pgpath->path.dev->bdev);
 
-	if (pgpath->is_active && !blk_queue_dying(q))
-		scsi_dh_activate(q, pg_init_done, pgpath);
-	else
-		pg_init_done(pgpath, SCSI_DH_DEV_OFFLINED);
+	if (pgpath->path.dev) {
+		struct request_queue *q = bdev_get_queue(pgpath->path.dev->bdev);
+
+		if (pgpath->is_active && !blk_queue_dying(q)) {
+			scsi_dh_activate(q, pg_init_done, pgpath);
+			return;
+		}
+	}
+	pg_init_done(pgpath, SCSI_DH_DEV_OFFLINED);
 }
 
 static int noretry_error(int error)
@@ -1516,7 +1561,7 @@ static void multipath_status(struct dm_t
 			       pg->ps.type->info_args);
 
 			list_for_each_entry(p, &pg->pgpaths, list) {
-				DMEMIT("%s %s %u ", p->path.dev->name,
+				DMEMIT("%s %s %u ", p->path.pdev,
 				       p->is_active ? "A" : "F",
 				       p->fail_count);
 				if (pg->ps.type->status)
@@ -1542,7 +1587,7 @@ static void multipath_status(struct dm_t
 			       pg->ps.type->table_args);
 
 			list_for_each_entry(p, &pg->pgpaths, list) {
-				DMEMIT("%s ", p->path.dev->name);
+				DMEMIT("%s ", p->path.pdev);
 				if (pg->ps.type->status)
 					sz += pg->ps.type->status(&pg->ps,
 					      &p->path, type, result + sz,
@@ -1629,7 +1674,7 @@ static int multipath_prepare_ioctl(struc
 	if (!current_pgpath)
 		current_pgpath = choose_pgpath(m, 0);
 
-	if (current_pgpath) {
+	if (current_pgpath && current_pgpath->path.dev) {
 		if (!test_bit(MPATHF_QUEUE_IO, &m->flags)) {
 			*bdev = current_pgpath->path.dev->bdev;
 			*mode = current_pgpath->path.dev->mode;
@@ -1674,6 +1719,8 @@ static int multipath_iterate_devices(str
 
 	list_for_each_entry(pg, &m->priority_groups, list) {
 		list_for_each_entry(p, &pg->pgpaths, list) {
+			if (!p->path.dev)
+				continue;
 			ret = fn(ti, p->path.dev, ti->begin, ti->len, data);
 			if (ret)
 				goto out;
@@ -1686,8 +1733,12 @@ out:
 
 static int pgpath_busy(struct pgpath *pgpath)
 {
-	struct request_queue *q = bdev_get_queue(pgpath->path.dev->bdev);
+	struct request_queue *q;
+
+	if (!pgpath->path.dev)
+		return 0;
 
+	q = bdev_get_queue(pgpath->path.dev->bdev);
 	return blk_lld_busy(q);
 }
 
--- a/drivers/md/dm-mpath.h
+++ b/drivers/md/dm-mpath.h
@@ -12,6 +12,7 @@
 struct dm_dev;
 
 struct dm_path {
+	char pdev[16];		/* Requested physical device */
 	struct dm_dev *dev;	/* Read-only */
 	void *pscontext;	/* For path-selector use */
 };
--- a/drivers/md/dm-table.c
+++ b/drivers/md/dm-table.c
@@ -464,6 +464,9 @@ void dm_put_device(struct dm_target *ti,
 	struct list_head *devices = &ti->table->devices;
 	struct dm_dev_internal *dd;
 
+	if (!d)
+		return;
+
 	list_for_each_entry(dd, devices, list) {
 		if (dd->dm_dev == d) {
 			found = 1;
