#!/bin/sh

TGTD_BIN="/usr/libexec/pstorage-iscsi/tgtd/bin"
ISCSI_ETC="/etc/pstorage/iscsi"
ISCSI_SHM="/dev/shm/pstorage-iscsi"
PSTORAGE_ETC="/etc/pstorage"
TGTADM="$TGTD_BIN/tgtadm"
PLOOP="/usr/sbin/ploop"
BRIDGE_NAME="pstorage-iscsi"
BRCTL="/usr/sbin/brctl"
ARPSEND="/usr/sbin/arpsend"
SHAMAN="/usr/sbin/shaman"
TGTD="$TGTD_BIN/tgtd"
TGTD_PORT=3260

function pcs_iscsi_update_arp {
        local addr="$1"
        if [ ! -x $ARPSEND ]; then
                echo "Can't find executable $ARPSEND" 1>&2
                return 1
        fi
	local dev=`/sbin/ip route get $addr | grep dev | sed "s/.* dev \(.*\)\ssrc.*$/\1/"`
        if [ -z "$dev" -o "lo" = $dev ]; then
                # use default interface
                dev=`/sbin/ip route list | grep default | sed "s/^default .*dev \(.*\)$/\1/"`
                if [ -z "$dev" ] ; then
                        echo "Unable get interface for default route" 1>&2
                        return 2
                fi
        fi
        $ARPSEND -c 1 -w 1 -U -i $addr $dev
        if [ $? -ne 0 ] ; then
                echo "Unable send update ARP request" 1>&2
                return 3
        fi

        return 0
}

function pcs_iscsi_is_registered_local {
	local target="$1"
	local owner=""
	local host_id=`cat $PSTORAGE_ETC/host_id`
	if [ -z "$host_id" ] ; then
		echo "Can't read host id" 1>&2
		return 2
	fi

	[ ! -x "$ISCSI_ETC/targets/$target" ] && return 1
	local path=`/bin/readlink $ISCSI_ETC/targets/$target 2>/dev/null`
	[ -z "$path" -o ! -d "$path" -o "$(dirname $path)" != "$ISCSI_ROOT" ] && continue

	owner=`cat $ISCSI_ETC/targets/$target/control/host`
	if [ $? -ne 0 ] ; then
		echo "Unable read file $target/control/host" 1>&2
		return 3
	fi

	[ -z "$owner" -o "$owner" != "$host_id" ] && return 1

	return 0
}

function get_id {
	[ ! -d $ISCSI_SHM ] && mkdir -p $ISCSI_SHM

       (
	flock -x 200
	last_tid=`ls $ISCSI_SHM 2>/dev/null | sort -g -r | head -n 1`
	if [ -z "$last_tid" ] ; then
		last_tid="10"
	else
		last_tid=$[ $last_tid + 1]
	fi
	touch $ISCSI_SHM/$last_tid /dev/null 2>&1
	echo $last_tid
       ) 200>$ISCSI_SHM/.lock
}

function pcs_iscsi_init_host {
	[ ! -d $ISCSI_SHM ] && mkdir -p $ISCSI_SHM >/dev/null 2>&1
	# make bridge
	bridge_exist=`$BRCTL show | grep $BRIDGE_NAME`
	if [ -z "$bridge_exist" ] ; then
		$BRCTL addbr $BRIDGE_NAME
		if [ $? -ne 0 ] ; then
			echo "Unable create bridge $BRIDGE_NAME" 1>&2
			return 1
		fi
	fi

	return 0
}

function put_id {
	local tid="$1"
	(
		flock -x 201
		rm -f $ISCSI_SHM/$tid
	) 201>$ISCSI_SHM/.lock
}

function pcs_iscsi_list_registered {
	ls $ISCSI_ETC/targets 2>/dev/null| while read target
	do
		link="$ISCSI_ETC/targets/$target"
		[ ! -h "$link" ] && continue
		path=`/bin/readlink $link 2>/dev/null`
		[ -z "$path" -o ! -d "$path" -o "$(dirname $path)" != "$ISCSI_ROOT" ] && continue
		pcs_iscsi_is_registered_local $target >/dev/null 2>&1
		[ $? -ne 0 ] && continue
		echo ""
		echo "iscsi-$target"
		echo "$path"
	done
}

function pcs_iscsi_register_target {
	local target="$1"
	local link="$ISCSI_ETC/targets/$target"

	if [ ! -f "$PSTORAGE_ETC/host_id" ]; then
		echo "Can't find file $PSTORAGE_ETC/host_id" 1>&2
		return 2
	fi

	mkdir -p "$ISCSI_ETC/targets"
	[ -h "$link" ] && rm $link
	ln -s "$ISCSI_ROOT/$target" "$link"
	if [ $? -ne 0 ] ; then
		echo "Unable create symlink $link to $ISCSI_ROOT/$target" 1>&2
		return 4
	fi
	
	# save host id in $ISCSI_ROOT/$target/control/host
	/bin/dd if="$PSTORAGE_ETC/host_id" of="$ISCSI_ROOT/$target/control/host" conv=fsync >/dev/null 2>&1
	if [ $? -ne 0 ]; then
		echo "Unable save host_id in $ISCSI_ROOT/$target/control/host" 1>&2
		rm -f "$link" 1>&2
		return 5
	fi

	return 0
}

function pcs_iscsi_up_lun {
	local target="$1"
	local ctl_port="$2"
	local lun="$3"
	local dev=""
	local msg=""
	local lun_id=`expr "$lun" : "^lun\([0-9]*\)" `

	if [ ! -d "$ISCSI_ROOT/$target/$lun" ] ; then
		echo "LUN $lun_id for target $target doesn't exist" 1>&2
		return 1
	fi

	$PLOOP mount "$ISCSI_ROOT/$target/$lun/DiskDescriptor.xml" >/dev/null
	if [ $? -ne 0 ] ; then
		echo "Can't mount ploop $ISCSI_ROOT/$target/$lun/ploop"
		return 2
	fi
	dev=`$PLOOP list | grep "$ISCSI_ROOT/$target/$lun/ploop" | cut -d ' ' -f 1`
	if [ -z "$dev" ] ; then
		echo "Unable get device for $ISCSI_ROOT/$target/$lun/ploop" 1>&2
		$PLOOP umount "$ISCSI_ROOT/$target/$lun/DiskDescriptor.xml" >/dev/null 2>&1
		return 2
	fi
	if [ ! -b "/dev/$dev" ] ; then
		echo "Unable find block device /dev/$dev" 2>&1
		$PLOOP umount "$ISCSI_ROOT/$target/$lun/DiskDescriptor.xml" >/dev/null 2>&1
		return 3
	fi

	$TGTADM -C $ctl_port --lld iscsi --mode logicalunit --op new --tid 1 --lun $lun_id -b "/dev/$dev" --blocksize 4096
	if [ $? -ne 0 ] ; then
		echo "Can't add LUN $lun_id for target $target" 1>&2
		$PLOOP umount "$ISCSI_ROOT/$target/$lun/ploop" >/dev/null 2>&1
		return 4
	fi

	return 0
}

function pcs_iscsi_remove_addr {
	while [ "${#}" -gt 0 ]; do
		local addr="$1"; shift
		local addr_exist=`/sbin/ip addr show $BRIDGE_NAME 2>/dev/null | grep "$addr"`
		[ -n "$addr_exist" ] && /sbin/ip addr del "$addr" dev "$BRIDGE_NAME" >/dev/null 2>&1
	done
}

function pcs_iscsi_add_addr {
	local addr="$1"
	local bridge_exist=`$BRCTL show | grep "$BRIDGE_NAME"`
	if [ -z "$bridge_exist" ] ; then
		echo "Bridge $BRIDGE_NAME doesn't exist on this host" 1>&2
		return 1
	fi
	addr_exist=`/sbin/ip addr show $BRIDGE_NAME 2>/dev/null | grep "$addr"`
	[ -n "$addr_exist" ] && echo "Address $addr already exist on bridge $BRIDGE_NAME" 1>&2
	/sbin/ip addr add "$addr" dev "$BRIDGE_NAME"
	if [ $? -ne 0 ] ; then
		echo "Failed to add address $addr to bridge $BRIDGE_NAME" 1>&2
                return 2
        fi

	pcs_iscsi_update_arp `echo $addr | cut -d '/' -f 1`
	[ $? -ne 0 ] && return 3
	return 0
}

function tgtd_close_sessions {
	ctl_port="$1"
	target="$2"
        sids=`$TGTADM -C $ctl_port --mode target --op show | grep "I_T nexus: " | awk '{ print $3 }'`
        [ -z "$sids" ] && return 0

        max_sid=`echo "$sids" | tr ' ' '\n' | sort -nr | head -n1`
        if [ -z "$max_sid" ] ; then
                echo "Unable get maximum nexus ID for target $target" 1>&2
                return 1
        fi

        nexuses=`$TGTADM -C $ctl_port --mode target --op show | grep -v "IP Address" | tr '\n' '!' | \
                sed "s/Target.*\(nexus:.*\)LUN information.*/\1nexus: $[max_sid+1] /"`
        for sid in $sids; do
                next_sid=$[$sid+1]
		initiator=`echo $nexuses | sed "s/.*\(nexus: $sid.*\)nexus: $next_sid/\1/" | tr '!' '\n' | grep "Initiator"`
		echo "Closing nexus $sid, $initiator" 1>&2
                conn=`echo $nexuses | sed "s/.*\(nexus: $sid.*\)nexus: $next_sid/\1/" | tr '!' '\n' | grep "Connection" | \
                        awk '{ print $2 } '`
                if [ -z "$conn" ] ; then
                        echo "Unable get connections for nexus $sid" 1>&2
                        continue
                fi
                for cid in $conn; do
			echo " closing connection $cid" 1>&2
			$TGTADM -C $ctl_port --mode conn --op delete --tid 1 --sid $sid --cid $cid >/dev/null
			rc=$?
			[ $rc -ne 0 ] && return $rc
                done
        done

	return 0
}

# stop running target
function pcs_iscsi_down_target {
	local target="$1"
	local ctl_port="$2"
	local force="$3"
	local tid=""
	local luns=""
	local owner=""

	pcs_iscsi_is_registered_local "$target"
	rc=$?
	if [ $rc -ne 0 ] ; then
		[ $rc -eq 1 ] && echo "Target $target not registered on current host" 1>&2
		return 1
	fi
	
	if [ -z "$ctl_port" ]; then
		if [ ! -f "$ISCSI_ROOT/$target/control/.running" ]; then
			echo "Target $target not running" 1>&2
			return 0
		fi
		ctl_port=`cat $ISCSI_ROOT/$target/control/.running`
	fi

	if [ -z "$ctl_port" ]; then
		echo "Control port is unknown" 1>&2
		return 1
	fi

	# Offline everything first on forced stop.
	[ -n "$force" ] && $TGTADM -C $ctl_port --mode sys --name State -v offline >/dev/null 2>&1

	$TGTADM -C $ctl_port -mode target --op unbind --tid 1 -I ALL >/dev/null 2>&1
	if [ "$?" -eq 107 ] ; then
		echo "tgtd not running with control port $ctl_port" 1>&2
		return 1
	fi

	if [ -n "$force" ]; then
		$TGTADM -C $ctl_port --mode target --op update --tid=1 -n state -v offline >/dev/null 2>&1
		tgtd_close_sessions $ctl_port $target
		[ $? -ne 0 ] && return 2
	fi

	$TGTADM -C $ctl_port --mode target --op delete --tid 1 >/dev/null 2>&1
	rc=$?
	if [ "$rc" -eq 107 ] ; then
		echo "tgtd not running with control port $ctl_port" 1>&2
		return 1
	fi

	if [ "$rc" -ne 0 ] ; then
		echo "initiators still connected" 1>&2
		$TGTADM -C $ctl_port --mode target --op show | grep "Initiator: " | \
			awk '{ printf " Initiator:   %s (%s)\n", $2,$4}'
		return 2
	fi

	# tgtd will exit if all targets were removed
	$TGTADM -C $ctl_port --op delete --mode system >/dev/null 2>&1
	if [ -f "$ISCSI_ROOT/$target/control/address" ] ; then
		addresses=`cat "$ISCSI_ROOT/$target/control/address"`
		pcs_iscsi_remove_addr $addresses
	fi

	luns=`ls -d "$ISCSI_ROOT/$target"/lun[0-9]* 2>/dev/null`
	if [ -n "$luns" ] ; then
		for l in $luns; do
			$PLOOP umount "$ISCSI_ROOT/$target/$(basename $l)/ploop" >/dev/null 2>&1
		done
	fi
	rm -f "$ISCSI_ROOT/$target/control/.running"
	put_id $ctl_port
	return 0
}

function pcs_iscsi_down_lun {
	local target="$1"
	local lun_id="$2"
	local ctl_port="$3"
              
	if [ -z "$ctl_port" ] ; then
		echo "Unknown control port"
		return 1
	fi

        $TGTADM -C $ctl_port --lld iscsi --mode logicalunit --op delete --tid 1 --lun "$lun_id"
        if [ $? -ne 0 ] ; then
                echo "Can't delete LUN $lun_id for target $target" 1>&2
                return 2
        fi

	$PLOOP umount "$ISCSI_ROOT/$target/lun${lun_id}/ploop" >/dev/null
	ret=$?
	if [ $ret -ne 0 ] ; then
		echo "Failed to umount ploop image $ISCSI_ROOT/$target/lun${lun_id}/ploop" 1>&2
		return 3
	fi

	return 0
}

function pcs_iscsi_start_tgtd {
	local ctl_port="$1"; shift
	local portal="$(echo "$1" | cut -d '/' -f1):$TGTD_PORT"; shift
	$TGTD -C $ctl_port --iscsi portal="$portal" >/dev/null 2>&1
	if [ $? -ne 0 ]; then
		echo "Unable start tgtd daemon with control port $ctl_port" 1>&2
		return 1
	fi

	while [ "${#}" -gt 0 ]; do
		portal="$(echo "$1" | cut -d '/' -f1):$TGTD_PORT"
		shift
		$TGTADM -C $ctl_port --lld iscsi --op new --mode portal --param portal="$portal"
		if [ $? -ne 0 ]; then
			echo "Can't add portal $portal, control port=$ctl_port" 1>&2
			# tgtd will exit if all targets were removed
			$TGTADM -C $ctl_port --op delete --mode system >/dev/null 2>&1
			return 2
		fi
	done
	# put tgtd into "offline" state until all the targets are configured.
	$TGTADM -C $ctl_port --op update --mode sys --name State -v offline >/dev/null 2>&1
	return 0
}

function tgtd_setup_target_params {
	local ctl_port="$1"
	$TGTADM -C $ctl_port --lld iscsi --mode target --op update --tid 1 --name MaxXmitDataSegmentLength --value 131072
	$TGTADM -C $ctl_port --lld iscsi --mode target --op update --tid 1 --name FirstBurstLength --value 131072
}

# start existing and registered target
function pcs_iscsi_up_target {
	local target="$1"
	local luns=""
	local ctl_port=""
	local addresses=""
	if [ ! -d  "$ISCSI_ROOT/$target" ] ; then
		echo "Target $target doesn't exist on $ISCSI_ROOT" 1>&2
		return 1
	fi

        pcs_iscsi_is_registered_local "$target"
        rc=$?
        if [ $rc -ne 0 ] ; then
                [ $rc -eq 1 ] && echo "Target $target not registered on current host" 1>&2
                return 2
        fi

        if [ -f "$ISCSI_ROOT/$target/control/address" ] ; then
		addresses=`cat "$ISCSI_ROOT/$target/control/address"`
		if [ -z "$addresses" ] ; then
			echo "Unknown address for target $target" 1>&2
			return 3
		fi
		for addr in $addresses; do
			pcs_iscsi_add_addr $addr
			[ $? -ne 0 ] && return 4
		done
	else
		echo "Unknown address for target $target" 1>&2
		return 3
        fi

	# add target
	ctl_port=`get_id`
	pcs_iscsi_start_tgtd $ctl_port $addresses
	if [ $? -ne 0 ] ; then
		put_id $ctl_port
		return 4
	fi

	$TGTADM -C $ctl_port --lld iscsi --mode target --op new --tid 1 --targetname "$target"
	if [ $? -ne 0 ] ; then
		echo "Unable add target $target" 1>&2
		pcs_iscsi_down_target $target $ctl_port
		return 5
	fi
	
	mkdir -p "$ISCSI_ROOT/$target/pr"
	$TGTADM -C $ctl_port --mode target --op update --tid 1 --name=pr_dir --value="$ISCSI_ROOT/$target/pr" >/dev/null 2>&1

        # FIXME: Add IP wildcard to allow all initiators (this is insecure!).
        # FIXME: We must read allowed initiators from $ISCSI_ROOT/$target/control/allowed
        $TGTADM -C $ctl_port --mode target --op bind --tid 1 -I ALL
        if [ $? -ne 0 ]; then
                echo "Failed to bind allowed initiators" 1>&2
		pcs_iscsi_down_target $target $ctl_port
                return 7
        fi

	tgtd_setup_target_params $ctl_port

	luns=`ls -d "$ISCSI_ROOT/$target/"lun[0-9]* 2>/dev/null`

	if [ -n "$luns" ] ; then
		for l in $luns; do
			pcs_iscsi_up_lun $target $ctl_port `basename $l`
			if [ $? -ne 0 ] ; then
				pcs_iscsi_down_target $target $ctl_port
				return 6
			fi
		done
	fi
	echo $ctl_port | dd of="$ISCSI_ROOT/$target/control/.running" conv=fsync >/dev/null 2>&1
	if [ $? -ne 0 ] ; then
		echo "Can't store file $ISCSI_ROOT/$target/control/.running" 1>&2
		pcs_iscsi_down_target $target $ctl_port
		return 7
	fi

	# Put tgtd into "ready" state.
	$TGTADM -C $ctl_port --op update --mode sys --name State -v ready
}

# delete existing target, force flag allow delete target registered
# on another host
function pcs_iscsi_delete_target {
	local target="$1"
	local force="$2"
	if [ -z "$ISCSI_ROOT" ] ; then
		echo "ISCSI_ROOT must be specified" 1>&2
		return 1
	fi

	[ ! -d "$ISCSI_ROOT/$target" ] && return 0

	if [ -f "$ISCSI_ROOT/$target/control/.running" ] ; then
		echo "Can't delete running target" 1>&2
		return 3
	fi

	if [ -z "$force" ] ; then
		pcs_iscsi_is_registered_local "$target"
        	rc=$?
        	if [ $rc -ne 0 ] ; then
                	[ $rc -eq 1 ] && echo "Target $target not registered on current host" 1>&2
                	return 3
        	fi
	fi

        $SHAMAN del "iscsi-$target" >/dev/null 2>&1

	mv "$ISCSI_ROOT/$target" "$ISCSI_ROOT/tmp/$target"
	if [ $? -ne 0 ] ; then
		echo "Can't move directory $ISCSI_ROOT/$target to $ISCSI_ROOT/tmp/$target"
		return 4
	fi

	rm -rf "$ISCSI_ETC/targets/$target" $ISCSI_ROOT/tmp/$target
	return 0
}

# create and register target
function pcs_iscsi_make_target {
	local target="$1"
	local addr="$2"

	if [ -z "$ISCSI_ROOT" ] ; then
		echo "ISCSI_ROOT must be specified" 1>&2
		return 1
	fi

	if [ -d "$ISCSI_ROOT/$target" ] ; then
		echo "Target $target already exist in $ISCSI_ROOT" 1>&2
		return 3
	fi
	
	mkdir -p "$ISCSI_ROOT/$target/control"
	if [ $? -ne 0 ] ; then
		echo "Unable create directory $ISCSI_ROOT/$target/control" 1>&2
		return 4
	fi

	echo "$addr" | dd of="$ISCSI_ROOT/$target/control/address" conv=fsync >/dev/null 2>&1
	if [ $? -ne 0 ] ; then
		echo "Can't store address in file $ISCSI_ROOT/$target/control/address" 1>&2
		return 5
	fi

	pcs_iscsi_register_target $target
	if [ $? -ne 0 ] ; then
		echo "Unable register target $target on this host" 1>&2
		return 6
	fi

	$SHAMAN -i add "iscsi-$target" -P $ISCSI_ROOT/$target >/dev/null
	if [ $? -ne 0 ] ; then
		echo "Can't register new target as shaman's resource" 1>&2
		return 7
	fi
	
	return 0
}

function pcs_iscsi_check_root {
	local create_dir="$1"
	if [ -z "$ISCSI_ROOT" ] ; then
		echo "Unable to find directory with iSCSI targets, please check 'ISCSI_ROOT'" 1>&2
		echo "in $ISCSI_ETC/config, or use -r,--root option." 1>&2
	        exit 1
	fi

	if [ ! -d "$ISCSI_ROOT" ]; then
		if [ "$create_dir" != "yes" ] ; then
			echo "Directory $ISCSI_ROOT does not exist" 1>&2
			exit 2
		fi
		mkdir -p $ISCSI_ROOT
		if [ $? -ne 0 ] ; then
			echo "Unable create directory $ISCSI_ROOT" 1>&2
			exit 3
		fi
	fi

	[ ! -d "$ISCSI_ROOT/tmp" ] && mkdir -p "$ISCSI_ROOT/tmp"
}

function pcs_iscsi_check_target {
        local target="$1"
        if [ ! -d  "$ISCSI_ROOT/$target" ] ; then
                echo "Target $target doesn't exist on $ISCSI_ROOT" 1>&2
                exit 1
        fi

        pcs_iscsi_is_registered_local "$target"
        rc=$?
        if [ $rc -ne 0 ] ; then
                [ $rc -eq 1 ] && echo "Target $target not registered on current host" 1>&2
                exit 2
        fi
}

function pcs_iscsi_lock_exec {
	local cmd="$1" ; shift
	local target="$1"
	local ret=""
	[ ! -d $ISCSI_SHM ] && mkdir -p $ISCSI_SHM
	(
	        flock -w 30 -x 222 || return 10
		# '222<&-' explicitely closes fd 222 for childs, especially tgtd
		(${cmd} "${@}") 222<&-
		ret=$?
	) 222>"$ISCSI_SHM/.lock_$target"
	return $ret
}
