#!/bin/sh
# geninitrd mod: dm-multipath
USE_MULTIPATH=${USE_MULTIPATH:-yes}

# if we should init dm-multipath at boot
have_multipath=no

# dm-multipath wwid which is used for rootfs
MPATH_WWID=

# setup geninitrd module
# @access	public
setup_mod_multipath() {
	if [ ! -x /sbin/multipath ]; then
		USE_MULTIPATH=no
	fi
}

# return true if node is multipath controlled
# @param	string $node device node to be examined
# @access	public
is_multipath() {
	local devpath="$1"

	# multipath disabled
	if is_no "$USE_MULTIPATH"; then
		return 1
	fi

	# multipath nodes are under device mapper
	if [[ "$devpath" != /dev/mapper/* ]]; then
		return 1
	fi

	DM_NAME=
	eval $(dm_export "$devpath")
	if [ -z "$DM_NAME" ]; then
		die "Couldn't extract DM_NAME from $devpath"
	fi

	local MPATH_WWID=${DM_UUID##*-}

	# just check if it is valid.
	local info=$(multipath -l $MPATH_WWID)
	if [ -z "$info" ]; then
		return 1
	fi

	return 0
}

# find modules for $devpath
# @param	$devpath	device to be examined
# @access	public
# find dm-multipath modules for $devpath
# returns false if $devpath is not dm-multipath
find_modules_multipath() {
	local devpath="$1"

	DM_NAME=
	eval $(dm_export "$devpath")
	if [ -z "$DM_NAME" ]; then
		die "Couldn't extract DM_NAME from $devpath"
	fi

	# Partition:
	#  DM_NAME=LUN-28p1
	#  DM_UUID=part1-mpath-36006016002c11800a0aa05fbfae0db11
	# Disk:
	#  DM_NAME=LUN-28
	#  DM_UUID=mpath-36006016002c11800a0aa05fbfae0db11
	MPATH_WWID=${DM_UUID##*-}

	local info=$(multipath -l $MPATH_WWID)
	if [ -z "$info" ]; then
		return 1
	fi

	verbose "Finding modules for dm-multipath (WWID=$MPATH_WWID)"
	have_multipath=yes

	local p list
	list=$(mp_parse_devs "$info")
	for p in $list; do
		find_modules_for_devpath $p
		lvm_ignore_devices="$lvm_ignore_devices $p"
	done

	list=$(mp_parse_hwhandler "$info")
	for p in $list; do
		find_module "$p"
	done

	list=$(mp_parse_policy "$info")
	for p in $list; do
		find_module "dm-$p"
	done

	find_module "dm-mod"
	return 0
}

# generate initrd fragment
# @access	public
initrd_gen_multipath() {
	if ! is_yes "$have_multipath"; then
		return
	fi

	inst_d /sbin /lib/udev /etc/multipath
	inst_exec /sbin/kpartx /sbin
	inst_exec /sbin/multipath /sbin

	# for udev callouts
	local scsi_id=$(find_tool $(get_libdir /LIBDIR/udev/scsi_id) /lib/udev/scsi_id /sbin/scsi_id)
	inst_exec $scsi_id /lib/udev

	local installed=0
	for _lib in $(get_libdir LIBDIR); do
		if [ -d /$_lib/multipath ]; then
			inst_d /$_lib/multipath
			inst_exec /$_lib/multipath/* /$_lib/multipath
			installed=1
			break
		fi
	done
	if [ "$installed" -eq 0 ]; then
		inst_exec /sbin/mpath* /sbin
	fi

	grep -Ev '^([ 	]*$|#)' /etc/multipath.conf > $DESTDIR/etc/multipath.conf

	if [ -f /etc/multipath/bindings ]; then
		grep -Ev '^([ 	]*$|#)' /etc/multipath/bindings > $DESTDIR/etc/multipath/bindings
	else
		touch $DESTDIR/etc/multipath/bindings
	fi

	mount_dev
	initrd_gen_devices

	mount_sys
	echo "export MPATH_WWID=$MPATH_WWID" | add_linuxrc
	add_linuxrc <<-'EOF'
		# parse mpath_wwid= from kernel commandline
		for arg in $CMDLINE; do
			if [ "${arg##mpath_wwid=}" != "${arg}" ]; then
				MPATH_WWID=${arg##mpath_wwid=}
				if [ "$MPATH_WWID" = "*" ]; then
					# '*' would mean activate all WWID-s
					MPATH_WWID=
					echo "multipath: Activating all WWID-s"
				else
					echo "multipath: Activating WWID=$WWID"
				fi
			fi
		done

		debugshell
		/sbin/multipath -v 0 $MPATH_WWID

		for a in /dev/mapper/*; do
			[ $a = /dev/mapper/control ] && continue
			/sbin/kpartx -a -p p $a
		done
		debugshell
	EOF
}


# PRIVATE METHODS
# export info from dmsetup
# param can be:
# - MAJOR:MINOR
# - /dev/dm-MINOR
# - /dev/mapper/DM_NAME
dm_export() {
	local arg="$1"

	case "$arg" in
	*:*)
		local maj=${arg%:*} min=${arg#*:}
		dmsetup -j $maj -m $min export
		;;
	/dev/dm-*)
		local min=${arg#*dm-}
		local maj=$(awk '$2 == "device-mapper" {print $1}' /proc/devices)
		dm_export $maj:$min
		;;
	/dev/mapper/*)
		local dm_name=${arg#/dev/mapper/}
		dmsetup export $dm_name
		;;
	*)
		die "dm_export: unexpected $arg"
		;;
	esac
}

# parse blockdevices behind multipath device
# takes 'multipath -l' output as input
mp_parse_devs() {
	local info="$1"

	# parse "0:0:1:0 sdf" -> /dev/sdf
	#
	# multipath-tools-0.4.8-0.12.amd64
	# LUN-02 (36006016002c11800ce520d27c6ebda11) dm-0 DGC     ,RAID 10
	# [size=12G][features=1 queue_if_no_path][hwhandler=1 emc]
	# \_ round-robin 0 [prio=0][active]
	#  \_ 0:0:0:0 sda 8:0   [active][undef]
	# \_ round-robin 0 [prio=0][enabled]
	#  \_ 0:0:1:0 sdf 8:80  [active][undef]
	#
	# multipath-tools-0.4.8-9.x86_64
	# LUN-14 (36006016002c118006f4f8bccc7fada11) dm-3 ,
	# size=7.0G features='0' hwhandler='0' wp=rw
	# |-+- policy='round-robin 0' prio=-1 status=enabled
	# | `- #:#:#:# sde 8:64 failed undef running
	# `-+- policy='round-robin 0' prio=-1 status=active
	#   `- #:#:#:# sdb 8:16 active undef running

	echo "$info" | awk '{
		if (match($0, /[#0-9]+:[#0-9]+:[#0-9]+:[#0-9]+ [^ ]+ [0-9]+:[0-9]/)) {
			# take whole matched part into "l" variable
			l = substr($0, RSTART, RLENGTH);
			split(l, a, " ");
			printf("/dev/%s\n", a[2])
		}
	}'
}

# parse policy output for each device
# takes 'multipath -l' output as input
mp_parse_policy() {
	local info="$1"

	# multipath-tools-0.4.8-0.12.amd64
	# LUN-02 (36006016002c11800ce520d27c6ebda11) dm-0 DGC     ,RAID 10
	# [size=12G][features=1 queue_if_no_path][hwhandler=1 emc]
	# \_ round-robin 0 [prio=0][active]
	#  \_ 0:0:0:0 sda 8:0   [active][undef]
	# \_ round-robin 0 [prio=0][enabled]
	#  \_ 0:0:1:0 sdf 8:80  [active][undef]
	#
	# multipath-tools-0.4.8-9.x86_64
	# LUN-14 (36006016002c118006f4f8bccc7fada11) dm-3 ,
	# size=7.0G features='0' hwhandler='0' wp=rw
	# |-+- policy='round-robin 0' prio=-1 status=enabled
	# | `- #:#:#:# sde 8:64 failed undef running
	# `-+- policy='round-robin 0' prio=-1 status=active
	#   `- #:#:#:# sdb 8:16 active undef running

	echo "$info" | awk '
		# multipath-tools-0.4.8-0.12.amd64
		/\[prio=/{
			print $2
		}
		# multipath-tools-0.4.8-9.x86_64
		/policy=/{
			if (match($0, /policy=[^ ]+/)) {
				# take whole matched part into "l" variable
				l = substr($0, RSTART, RLENGTH);
				# remove policy= and single quote,
				# which we can not use in this awk inline script, therefore the %c hack
				sub(sprintf("^policy=%c?", 39), "", l);
				print l
			}
		}
	' | sort -u
}

# parse hwhandler from multipath output
# takes 'multipath -l' output as input
mp_parse_hwhandler() {
	local info="$1"

	# TODO: actually the dm-emc vs scsi-dh-emc is dependant on kernel version
	# not version of the tools installed

	# multipath-tools-0.4.8-0.12.amd64
	# [size=12G][features=1 queue_if_no_path][hwhandler=1 emc]
	#
	# multipath-tools-0.4.8-11.x86_64
	# size=7.0G features='0' hwhandler='0' wp=rw
	# size=2.0G features='1 queue_if_no_path' hwhandler='1 emc' wp=rw
	echo "$info" | sed -ne "
		# multipath-tools-0.4.8-0.12.amd64
		/\[hwhandler=1/{
			s,^.*\[hwhandler=1 \([^]]*\)\].*$,dm-\1,
			p
		}
		# multipath-tools-0.4.8-11.x86_64
		/hwhandler='1/{
			s,^.*hwhandler='1 \([^']*\)'.*$,scsi-dh-\1,
			p
		}
	" | sort -u
}
