Subject: [PATCH] [BZ 145897] zipl: Fix installation on multipath target with failed paths
From: Stefan Haberland <sth@linux.vnet.ibm.com>

Description:  zipl: Fix installation on multipath target with failed paths
Symptom:      When zipl is used to install an IPL record on a multipath target
              where the first path is in failed state, zipl will abort with the
              following error message:
                Run /lib/s390-tools/zipl_helper.device-mapper /boot
                Use of uninitialized value $blocksize in scalar chomp at
                      /lib/s390-tools/zipl_helper.device-mapper line 518.
                Error: Could not get block size for sdX
                Error: Script could not determine target parameters
Problem:      The first path of a multipath target is used to determine the 
              base device of a logical device.
Solution:     Fix this by skipping failed paths of a multipath target when 
              determining the actual base device of a logical device.
              Parse output of "dmsetup status" as generated by
              linux/drivers/md/dm-mpath.c:multipath_status routine.
Reproduction: Run zipl on a multipath target with first path in failed state.
Upstream-ID:  -
Problem-ID:   145897

Signed-off-by: Stefan Haberland <sth@linux.vnet.ibm.com>
---
 zipl/src/zipl_helper.device-mapper |   80 ++++++++++++++++++++++++++++++++++++-
 1 file changed, 79 insertions(+), 1 deletion(-)

--- a/zipl/src/zipl_helper.device-mapper
+++ b/zipl/src/zipl_helper.device-mapper
@@ -79,6 +79,7 @@ sub get_major_minor($);
 sub get_table($$);
 sub get_linear_data($$);
 sub get_mirror_data($$);
+sub get_multipath_status($);
 sub get_multipath_data($$);
 sub filter_table($$$);
 sub get_target_start($);
@@ -364,11 +365,88 @@ out_error:
 	    "'$dev_name'\n");
 }
 
+# get_multipath_status(device)
+# Return map of nodes to F/A flags (i.e. $map{$node} is either "A" or "F").
+# See linux/drivers/md/dm-mpath.c:multipath_status for details.
+sub get_multipath_status($)
+{
+	my $dev = shift();
+	my %map;
+	my $str;
+	my $failed = 0;
+
+	open(my $fh, "$dmsetup status /dev/$dev 2>/dev/null|") or return undef;
+	while ($str = <$fh>) {
+		# sample output (single line):
+		# 0 67108864 multipath \
+		# 	2 0 0 \
+		#	0 \
+		#	2 2 \
+		#		E 0 \
+		#		2 2 \
+		#			8:16 F 1 \
+		#				0 1 \
+		#			8:0 F 1 \
+		#				0 1 \
+		#		A 0 \
+		#		2 2 \
+		#			8:32 A 0 \
+		#				0 1 \
+		#			8:48 A 0 \
+		#				0 1
+		my @line = split(/\s+/, $str);
+		next if !@line;
+
+		my ($start, $length, $type) = splice(@line, 0, 3);
+		next if $type ne "multipath";
+
+		my $cnt = shift(@line); # Remove #multipath_feature_args +
+		splice(@line, 0, $cnt) if $cnt > 0; # multipath_feature_args
+
+		$cnt = shift(@line); # Remove #handler_status_args +
+		splice(@line, 0, $cnt) if $cnt > 0; # handler_status_args
+
+		# num_groups init_group_number ...
+		my ($ngr, $ign) = splice(@line, 0, 2);
+		for (my $g = 0; $g < $ngr; $g++) {
+			# Remove group_state + #ps_status_args
+			# group_state: D(isabled), A(ctive), or E(nabled)
+			my ($state, $cnt) = splice(@line, 0, 2);
+			# Remove ps_status_args*
+			splice(@line, 0, $cnt) if $cnt > 0;
+			# Remove #paths + #selector_args
+			my ($paths, $nsa) = splice(@line, 0, 2);
+			for (my $p = 0; $p < $paths; $p++) {
+				# Fetch single path description
+				my ($node, $active, $fail_cnt)
+					= splice(@line, 0, 3);
+				# active: A(ctive) or F(ailed)
+				$map{$node} = $active;
+				$failed++ if $active ne "A";
+				# Remove selector_args*
+				splice(@line, 0, $nsa) if $nsa > 0;
+			}
+		}
+	}
+	close($fh);
+
+	die ("Error: No paths found for '$dev'\n") if scalar(keys %map) == 0;
+	if ($failed) {
+		die ("Error: All paths for '$dev' failed\n")
+			if $failed == scalar(keys %map);
+
+		print(STDERR "Warning: There are one or more failed paths for"
+			." device '$dev'\n");
+	}
+	return \%map;
+}
+
 # get_multipath_data(dev_name, args)
 # Returns [[major1, minor1], [major2, minor2], ..]
 sub get_multipath_data($$)
 {
 	my ($dev_name, $args) = @_;
+	my $status = get_multipath_status($dev_name);
 	my @argv = split(/\s+/, $args);
 	my @data;
 
@@ -401,7 +479,7 @@ sub get_multipath_data($$)
 			if (!($argv[0] =~ /(\d+):(\d+)/)) {
 				goto out_error;
 			}
-			push(@data, [$1, $2]);
+			push(@data, [$1, $2]) if $status->{$argv[0]} eq "A";
 			# Remove device + deviceargs
 			splice(@argv, 0, 1 + $num_path_args);
 		}
