#!/bin/bash

CTL_TOOL="vstorage-iscsi"
TGTD_BIN="/usr/libexec/pstorage-iscsi/tgtd/bin"
ISCSI_ETC="/etc/vstorage/iscsi"
ISCSI_SHM="/dev/shm/vstorage-iscsi"
ISCSI_TMP="/dev/shm/vstorage-iscsi"
PSTORAGE_ETC="/etc/vstorage"
TGTADM="$TGTD_BIN/tgtadm"
PLOOP="/usr/sbin/ploop"
BRIDGE_NAME="vstorage-iscsi"
BRCTL="/usr/sbin/brctl"
ARPING="/sbin/arping"
SHAMAN="/usr/sbin/shaman"
TGTD="$TGTD_BIN/tgtd"
TGTD_PORT=3260
TGTD_IO_BACKEND="aio" # "rdwr"
IP_CMD="/sbin/ip"
VSTORAGE_BIN="/usr/bin/vstorage"
SYSTEMD_TOOL="/usr/bin/systemctl"
ISCSI_TGTD_IPC_PATH="/var/run/tgtd"
ISCSI_TGTD_LOG_PATH="/var/log/vstorage/iscsi"

# TGTD can send NOP-OUT probes to connected initiators to determine when
# an initiator is dead and then automatically clear and tear down the
# TCP connection.
TGTD_NOP_INTERVAL="5" # interval in seconds
TGTD_NOP_COUNT="6"

TARGET_NO_OWNER="unregistered"

iscsi_get_netdev()
{
# Get a list of interfaces, excluding ones with LOOPBACK NOARP or SLAVE flags
# Accept mask: eth*[0-9]|tr*[0-9]|wlan[0-9]|ath[0-9]|ip6tnl*[0-9]|mip6mnha*[0-9]|bond[0-9]
        NETDEVICES=`${IP_CMD} a l | awk '
/^[[:digit:]]+:/ && /\<UP\>/ && !/\<LOOPBACK\>/ && !/\<SLAVE\>/ && !/\<NOARP\>/{
        if ($2 ~ /^en.*|^eth.*[0-9]+|^virbr[0-9]+|^em.*[0-9]+|^p[0-9]+p[0-9]+|^tr.*[0-9]+|^wlan[0-9]+|^ath[0-9]+|^ip6tnl.*[0-9]+|^mip6mnha.*[0-9]+|^vlan.*|^bond.*[0-9]+|^ib[0-9]+/) print $2;
}' | sed -e 's/:$//' -e 's/@.*//'`

        local tmp_list=""
        # Open vSwitch mode - IP assigned to bridge over device
        if [ -d "/sys/module/openvswitch" ]; then
                for dev in $NETDEVICES; do
                        local tmp=`$OVSCTL_CMD iface-to-br $dev 2>/dev/null`
                        [ $? -ne 0 ] && tmp="$dev"
                        tmp_list="$tmp_list $tmp"
                done
                NETDEVICES="$tmp_list"
        fi

        # step 2 - filter out devices without IP address
        tmp_list=""
        for dev in $NETDEVICES; do
                local tmp=`${IP_CMD} a l $dev | awk -v dev=$dev \
                        '/^^[\ \t]+inet6? / { print(dev); exit; }'`
                [ -n "$tmp" ] && tmp_list="$tmp_list $tmp"
        done
        echo "$tmp_list"
}

function is_root_on_vstorage {
        for d in $(cat /proc/mounts | awk '/^[pv]storage:\/\//{ print $2}'); do
                [[ "$ISCSI_ROOT" == $d* ]] && return 1
        done
        return 0
}

function pcs_iscsi_set_storage_attr {
	local dir="$1"; shift
	$VSTORAGE_BIN "set-attr" "$dir" ${@} >/dev/null 2>&1
	if [ $? -ne 0 ]; then
		echo "Can't set storage attributes for $dir" 1>&2
		return 1
	fi
	return 0
}

function pcs_iscsi_update_arp {
        local addr="$1"
        if [ ! -x $ARPING ]; then
                echo "Can't find executable $ARPING" 1>&2
                return 1
        fi

	net_dev=`iscsi_get_netdev 2>/dev/null`
	if [ -z "$net_dev" ]; then
		echo "No active network interfaces" 1>&2
		return 2
	fi

	for d in $net_dev; do
		$ARPING -c 1 -w 1 -q -D -A -I $d $addr
	done

        return 0
}

function get_host_id {
	cat $PSTORAGE_ETC/host_id
	rc=$?
	if [ $? -ne 0 ] ; then
		echo "Can't read host id" 1>&2
		return $rc
	fi
	return 0
}

function pcs_iscsi_is_registered_local {
	local target="$1"
	local host_id="$2"
	local owner=""
	if [ -z "$host_id" ] ; then
		host_id=`get_host_id`
		[ $? -ne 0 ] && return 2
	fi

	owner=`cat $ISCSI_ROOT/$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/.running >/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"
	local target="$2"
	(
		flock -x 201
		rm -f $ISCSI_SHM/$tid
	) 201>$ISCSI_SHM/.lock
	[ -n "$target" ] && rm -f "$ISCSI_SHM/.running/$target"
}

function pcs_iscsi_list_registered {

	host_id=`get_host_id`
	[ $? -ne 0 ] && return 1

	ls $ISCSI_ROOT 2>/dev/null | grep "^iqn*" 2>/dev/null | while read target
	do
		[ ! -d  "$ISCSI_ROOT/$target/control" ] && continue
		
		pcs_iscsi_is_registered_local $target $host_id >/dev/null 2>&1
		[ $? -ne 0 ] && continue
		echo ""
		echo "iscsi-$target"
		echo "$ISCSI_ROOT/$target"
	done
}

function pcs_iscsi_target_has_owner {
	local target="$1"
	local owner=""
	if [ -f "$ISCSI_ROOT/$target/control/host" ]; then
		owner=`cat $ISCSI_ROOT/$target/control/host`
		if [ $? -ne 0 ] ; then
			echo "Unable read file $ISCSI_ROOT/$target/control/host" 1>&2
			return 3
		fi
	fi

	[ -n "$owner" -a "$owner" != "$TARGET_NO_OWNER" ] && return 1
	return 0
}

function pcs_iscsi_bind_account {
	local target="$1"
	local ctl_port="$2"
	local account=""
	[ ! -L "$ISCSI_ROOT/$target/control/account" ] && return
	account=`/bin/readlink "$ISCSI_ROOT/$target/control/account"`
	if [ -n "$account" ] ; then
		local user=`basename $account`
		local passw=`cat "$account/auth" `
		$TGTADM -C $ctl_port --lld iscsi --op new --mode account --user $user --password $passw
		$TGTADM -C $ctl_port --lld iscsi --op bind --mode account --tid 1 --user $user
		return $?
	fi
	return 0
}

function pcs_iscsi_unregister_target_ha {
	local target="$1"
	msg=`echo "$TARGET_NO_OWNER" | dd of="$ISCSI_ROOT/$target/control/host" conv=fsync 2>&1`
	if [ $? -ne 0 ]; then
		echo "$msg" 1>&2
		echo "Can't write in $ISCSI_ROOT/$target/control/host" 1>&2
		return 2
	fi
	return 0
}

function pcs_iscsi_unregister_target {
	local target="$1"

	host_id=`get_host_id`
	[ $? -ne 0 ] && return 1

	prev_owner=`cat "$ISCSI_ROOT/$target/control/host" 2>/dev/null`

	pcs_iscsi_unregister_target_ha "$target"
	[ $? -ne 0 ] && return 3

	[ "$prev_owner" = "$host_id" ] && $SHAMAN del "iscsi-$target" >/dev/null 2>&1
	return 0
}

function pcs_iscsi_register_target_ha {
	local target="$1"
	local host_id=`get_host_id`
	[ $? -ne 0 ] && return 1

	# save host id in $ISCSI_ROOT/$target/control/host
	msg=`echo $host_id | /bin/dd of="$ISCSI_ROOT/$target/control/host" conv=fsync 2>&1`
	if [ $? -ne 0 ]; then
		echo "$msg" 1>&2
		echo "Unable save host_id in $ISCSI_ROOT/$target/control/host" 1>&2
		return 2
	fi

	# reset running state after registration
	rm -f "$ISCSI_ROOT/$target/control/.running"
	return 0
}

function pcs_iscsi_register_target {
	local target="$1"
	local prev_owner=""

	if [ -f "$ISCSI_ROOT/$target/control/host" ]; then
		prev_owner=`cat "$ISCSI_ROOT/$target/control/host"`
		if [ $? -ne 0 ] ; then
			echo "Unable read $ISCSI_ROOT/$target/control/host" 1>&2
			return 1
		fi
	fi

	pcs_iscsi_register_target_ha "$target"
	[ $? -ne 0 ] && return $?

	if [ -z "$prev_owner" -o "$prev_owner" = "$TARGET_NO_OWNER" ]; then
		$SHAMAN -i add "iscsi-$target" -P $ISCSI_ROOT/$target >/dev/null
		if [ $? -ne 0 ] ; then
			echo "Can't register target '$target' as shaman's resource" 1>&2
			echo "Leave target in unregistered state." 1>&2
			pcs_iscsi_unregister_target "$target" >/dev/null 2>&1
			return 2
		fi
	else
		$SHAMAN move-from "iscsi-$target" "md.$prev_owner" >/dev/null
		if [ $? -ne 0 ] ; then
			echo "Can't register target '$target' as shaman's resource" 1>&2
			echo "Leave target in unregistered state." 1>&2
			pcs_iscsi_unregister_target "$target" >/dev/null 2>&1
			return 3
		fi
	fi

	return 0
}

function pcs_iscsi_attach_lun {
	local ctl_port="$1"
	local lun_id="$2"
	local ploop_dev="$3"
	local serial_sn="$4"

	$TGTADM -C $ctl_port --lld iscsi --mode logicalunit --op new --tid 1 --lun $lun_id -b "$ploop_dev" --blocksize 512 --bstype $TGTD_IO_BACKEND
	if [ $? -ne 0 ] ; then
		echo "Can't attach LUN $lun_id for target" 1>&2
		return 1
	fi

	if [ -n "$serial_sn" ] ; then
		$TGTADM -C $ctl_port --lld iscsi --mode logicalunit --op update --tid 1 --lun $lun_id --params scsi_sn="$serial_sn",scsi_id="$serial_sn"
		if [ $? -ne 0 ] ; then
			echo "Can't set serial_sn '$serial_sn' for new LUN $lun_id" 1>&2
			return 2
		fi
	fi

	return 0
}

function pcs_iscsi_get_ploop_dev {
	local target="$1"
	local lun="$2"
	local lun_id=`expr "$lun" : "^lun\([0-9]*\)" `
	local canon_path=$(readlink -f "$ISCSI_ROOT/$target/$lun/ploop")
	local ploop_dev=$( $PLOOP list | grep "$canon_path" | cut -d ' ' -f 1)
	if [ -z "$ploop_dev" ] ; then
		echo "Unable find device for $ISCSI_ROOT/$target/$lun/ploop" 1>&2
		return 1
	fi
			
	if [ ! -b "/dev/$ploop_dev" ] ; then
		echo "Unable find block device /dev/$ploop_dev" 1>&2
		return 2
	fi
	echo "/dev/$ploop_dev"
	return 0
}

function pcs_iscsi_grow_lun {
	local target="$1"
	local lun="$2"
	local new_size="$3"
	local lun_id=`expr "$lun" : "^lun\([0-9]*\)" `
	local serial_sn=""
	local ploop_dev=""
	local ctl_port=""

	if [ ! -d "$ISCSI_ROOT/$target/$lun" ] ; then
		echo "LUN $lun_id for target $target doesn't exist" 1>&2
		return 1
	fi
	
	ctl_port=`pcs_iscsi_get_ctl_port $target`
	if [ -n "$ctl_port" ] ; then
		ploop_dev=`pcs_iscsi_get_ploop_dev $target $lun`
		[ $? -ne 0 ] && return 2
	fi

	# grow ploop
	msg=`$PLOOP grow -s $new_size "$ISCSI_ROOT/$target/$lun/DiskDescriptor.xml" 2>&1`
	if [ $? -ne 0 ] ; then
                echo "$msg" 1>&2
                echo "Can't grow ploop $ISCSI_ROOT/$target/$lun/ploop" 1>&2
		return 3
	fi

	if [ -n "$ploop_dev" ] ; then
		# detach LUN from target
		$TGTADM -C $ctl_port --lld iscsi --mode logicalunit --op delete --tid 1 --lun $lun_id >/dev/null 2>&1

		# and attach it back
		[ -f "$ISCSI_ROOT/$target/$lun/serial_sn" ] && serial_sn=`cat "$ISCSI_ROOT/$target/$lun/serial_sn" 2>/dev/null`
		pcs_iscsi_attach_lun $ctl_port $lun_id $ploop_dev "$serial_sn"
		return $?
	fi

	return 0
}

function pcs_iscsi_up_lun {
	local target="$1"
	local ctl_port="$2"
	local lun="$3"
	local dev=""
	local msg=""
	local serial_sn=""
	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

	msg=`$PLOOP mount "$ISCSI_ROOT/$target/$lun/DiskDescriptor.xml" 2>&1`
	if [ $? -ne 0 ] ; then
		echo "$msg" 2>&1
		echo "Can't mount ploop $ISCSI_ROOT/$target/$lun/ploop" 1>&2
		return 2
	fi

	dev=`pcs_iscsi_get_ploop_dev $target $lun`
	[ $? -ne 0 ] && return 3
	tgtd_chown_nogrp "$dev"

	[ -f "$ISCSI_ROOT/$target/$lun/serial_sn" ] && serial_sn=`cat "$ISCSI_ROOT/$target/$lun/serial_sn" 2>/dev/null`
	pcs_iscsi_attach_lun $ctl_port $lun_id $dev "$serial_sn"
	if [ $? -ne 0 ] ; then
		$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"`
	if [ -n "$addr_exist" ] ; then 
	    echo "Address $addr already exist on bridge $BRIDGE_NAME" 1>&2
	else
	    /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
	fi
	pcs_iscsi_update_arp `echo $addr | cut -d '/' -f 1`
	[ $? -ne 0 ] && return 3
	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=""
	local forceoption=""
	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 $rc
	fi
	if [ -z "$ctl_port" ]; then
		if [ ! -f "$ISCSI_SHM/.running/$target" ] ; then
			echo "Target $target not running" 1>&2
			return 0
		fi
		ctl_port=`cat $ISCSI_SHM/.running/$target`
	fi

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

	if [ -n "$force" ] ; then
		# passing to --op delete --mode target, so it works
		# despite existing connections, closing them during
		# target removal. It can fail even then, due to active
		# scsi commands in a target's LUN.
		forceoption="--force";
	else
		# try failing early with a diagnostics on non-forced
		# stop, leaving the target online if there are active
		# connections.
		pcs_iscsi_target_can_stop_nonforcibly "$ctl_port" "$target"
		rc=$?
		if [ $rc -ne 0 ] ; then
			return $rc
		fi
	fi

	# Offline everything first on forced stop.
	[ -n "$force" ] && $TGTADM -C $ctl_port --mode sys --op update \
				   --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 [ "$?" -ne 107 ] ; then
		if [ -n "$force" ]; then
			$TGTADM -C $ctl_port --mode target --op update --tid 1 \
				-n state -v offline >/dev/null 2>&1
			[ $? -ne 0 -a $? -ne 107 ] && return 2
		fi

		attempts=10
		while true; do
			msg=$($TGTADM -C $ctl_port --mode target --op delete --tid 1 $forceoption 2>&1)
			rc=$?

			if [ "$rc" -ne 22 -o -z "$force" ] ; then break; fi
			case "$msg" in
				*'logical unit is still active'*)
					# retry (active scsi commands
					# or pending prstore_open)
					;;
				*)
					# no point in retrying for any
					# other reason
					break
					;;
			esac
			attempts=$(($attempts-1))
			if [ $attempts -eq 0 ] ; then
				break
			fi
			sleep 1
		done

		if [ "$rc" -ne 0 -a "$rc" -ne 107 ]; then
			case "$msg" in
				*'this target is still active'*)
					# calling for side-effect
					# (connection list output)
					pcs_iscsi_target_can_stop_nonforcibly "$ctl_port" "$target"
					rc=2
					;;
				*"can't find the target"*)
					# if a previous stop command
					# was interrupted after target
					# had been removed, go ahead
					# with address removal and
					# ploop unmounting
					rc=0
					;;
				*)
					# unanticipated error message
					# from tgtadm is forwarded to
					# stderr
					echo "$msg" 1>&2
					rc=2
					;;
			esac
			if [ "$rc" -ne 0 ] ; then return $rc ; fi
		fi

		# tgtd will exit if all targets were removed
		if [ "$rc" -ne 107 ] ; then
			$TGTADM -C $ctl_port --op delete --mode system >/dev/null 2>&1
		fi
	fi

	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 $target
	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 tgtd_uidparm {
	if [ -n "$ISCSI_USER" ] ; then
		echo -u "$ISCSI_USER"
	fi
}

function tgtd_chown {
	local colon=$1; shift
	if [ -n "$ISCSI_USER" ] ; then
		chown "$ISCSI_USER"$colon "$@"
	fi
}

function tgtd_chown_grp {
	tgtd_chown : "$@"
}

function tgtd_chown_nogrp {
	tgtd_chown "" "$@"
}

function pcs_iscsi_start_tgtd {
	local target="$1"; shift
	local ctl_port="$1"; shift

	local portal="$(echo "$1" | cut -d '/' -f1):$TGTD_PORT"; shift
	local ipc_path="$ISCSI_TGTD_IPC_PATH"
	local log_dir="$ISCSI_TGTD_LOG_PATH/$target"

	[ -n "$TGTD_NOP_INTERVAL" ] && portal="$portal,nop_interval=$TGTD_NOP_INTERVAL,nop_count=$TGTD_NOP_COUNT"
	mkdir -p -m0755 "$ipc_path"
	tgtd_chown_grp "$ipc_path"
	mkdir -p -m0755 "$log_dir"
	tgtd_chown_grp "$log_dir"

	TGT_LOG_FILE="$log_dir/tgtd.log.gz" \
		    $TGTD $(tgtd_uidparm) -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 MaxRecvDataSegmentLength --value 131072
	$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
}

function tgtd_check_portals {
	local target="$1"
	local ctl_port="$2"

	[ ! -f "$ISCSI_ROOT/$target/control/address" ] && return 0
	addresses=`cat "$ISCSI_ROOT/$target/control/address" 2>/dev/null`
	for addr in `cat "$ISCSI_ROOT/$target/control/address" 2>/dev/null`; do
		local portal="$(echo "$addr" | cut -d '/' -f1):$TGTD_PORT";
		$TGTADM -C $ctl_port --mode portal --op show 2>/dev/null | grep "$portal" 2>/dev/null
		[ $? -ne 0 ] && return 1
	done
	return 0
}

function pcs_iscsi_get_ctl_port {
	local target="$1"
	cat "$ISCSI_SHM/.running/$target" 2>/dev/null
	return $?
}

function pcs_iscsi_get_status {
	local target="$1"
        if [ ! -f "$ISCSI_SHM/.running/$target" ] ; then
                echo "stopped"
		return 0
        fi

	ctl_port=`cat "$ISCSI_SHM/.running/$target" 2>/dev/null`
	if [ -z "$ctl_port" ] ; then
		echo "service failed"
		return 1
	fi

	msg=`$TGTADM -C $ctl_port --mode target --op show 2>/dev/null`
	if [ $? -eq 107 ] ; then
		echo "service failed"
		return 1
	fi

	echo "$msg" | grep  "^Target 1: $target$" >/dev/null 2>&1
	if [ $? -ne 0 ] ; then
		echo "not running"
		return 1
	fi

	echo "running"
	return 0
}

# 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

	[ ! -d "$ISCSI_SHM/.running" ] && mkdir -p "$ISCSI_SHM/.running"

	if [ -f "$ISCSI_SHM/.running/$target" ]; then
		echo "Target already running." 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 $target $ctl_port $addresses
	if [ $? -ne 0 ] ; then
		pcs_iscsi_remove_addr $addresses
		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

	pcs_iscsi_bind_account $target $ctl_port
	if [ $? -ne 0 ] ; then
		echo "Failed to bind CHAP account to target" 1>&2
		pcs_iscsi_down_target $target $ctl_port
		return 7
	fi

	tgtd_setup_target_params $ctl_port

        scsi_id=`echo $target | md5sum | cut -d ' ' -f1`
        $TGTADM -C $ctl_port --lld iscsi --mode logicalunit --op update --tid 1 --lun 0 --params scsi_id="$scsi_id"
        if [ $? -ne 0 ]; then
                echo "Unable to set scsi_id for LUN 0" 1>&2
                pcs_iscsi_down_target $target $ctl_port
                return 8
        fi

	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

	echo $ctl_port > $ISCSI_SHM/.running/$target
	# 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

	pcs_iscsi_is_registered_local "$target"
	rc=$?
	[ $rc -gt 1 ] && return $rc # pcs_iscsi_is_registered_local failed
	if [ $rc -eq 0 ]; then
		# target registered on current host
		if [ -f "$ISCSI_SHM/.running/$target" ] ; then
			echo "Can't delete running target" 1>&2
			return 2
		fi
		pcs_iscsi_unregister_target "$target"
	elif [ -z "$force" ]; then
		pcs_iscsi_target_has_owner $target
		rc=$?
		[ $rc -gt 1 ] && return $rc

		if [ $rc -eq 1 ] ; then
			echo "Target $target registered on another host" 1>&2
			return 3
		fi
	fi

	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
}


function pcs_iscsi_set_chap_account {
    local target="$1"
    local user_name="$2"
    local dir="$3"

    [ "$user_name" == "-" ] && user_name=""

    if [ -n "$user_name" ]; then
        if [ -d "$ISCSI_ROOT/accounts/$user_name" ]; then
	    rm -f "$dir/control/account"
        ln -sf "$ISCSI_ROOT/accounts/$user_name" "$dir/control/account"
	    if [ $? -ne 0 ] ; then
		echo "Unable create link to account '$user_name'." 1>&2
		return 1
	    fi
	else
	    echo "Can't find CHAP account '$user_name'" 1>&2
	    return 1
        fi
    elif [ -L "$dir/control/account" ]; then  
       rm -f  "$dir/control/account"
    fi

    return 0
}

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

	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 2
	fi

	tmp=`mktemp -d ${ISCSI_ROOT}/tmp/new-$target.XXXXXXXX`
	if [ $? -ne 0 ] ; then
		echo "Can't create temporary directory." 1>&2
		return 3
	fi

	is_root_on_vstorage || stor_attr="mds-storage=1"

	for dir in "$tmp/control" "$tmp/pr"; do
		mkdir -p "$dir" >/dev/null
		if [ $? -ne 0 ] ; then
			echo "Can't create directory $dir" 1>&2
			rm -rf $tmp
			return 4
		fi

		if [ -n "$stor_attr" ]; then
			pcs_iscsi_set_storage_attr "$dir" "$stor_attr"
			if [ $? -ne 0 ]; then
				rm -rf $tmp
				return 4
			fi
		fi
	done

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

	if [ ! -z "$user_name" ]; then 
	    pcs_iscsi_set_chap_account "$target" "$user_name" "$tmp"
	    if [ $? -ne 0 ]; then
		rm -rf $tmp
		return 5
	    fi
	fi

	mv "$tmp" "${ISCSI_ROOT}/$target" >/dev/null
	if [ $? -ne 0 ] ; then
		echo "Failed to create directory." 1>&2
		rm -rf $tmp
		return 6
	fi

	pcs_iscsi_register_target $target
	if [ $? -ne 0 ] ; then
		echo "Unable register target $target on this host" 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" 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_target_can_stop_nonforcibly {
	local ctl_port="$1"; shift
	local target="$1"

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

	local initiators=$($TGTADM -C $ctl_port --mode target --op show | grep "Initiator: " | \
			      awk '{ printf " Initiator:   %s (%s)\n", $2,$4}')

	if [ -n "$initiators" ]; then
		echo "initiators still connected" 1>&2
		echo "$initiators" 1>&2
		return 2
	fi
	return 0
}

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

function pcs_iscsi_save_snapshot_info {
	local target="$1"
	local lun_id="$2"
	local uuid="$3"
	local descr="$4"
        mkdir -p "$ISCSI_ROOT/snapshots" >/dev/null 2>&1
        echo "SNAP_CREATE_DATE='$(date +"%F %T")'; SNAP_DESCRIPTION='${descr}'; SNAP_IQN='$target'; SNAP_LUN='${lun_id}'" \
                | dd of="$ISCSI_ROOT/snapshots/$uuid" conv=fsync >/dev/null 2>&1
        if [ $? -ne 0 ] ; then
                echo "Unable save description for new snapshot" 1>&2
                return 3
        fi
	return 0
}

function pcs_iscsi_delete_snapshot_info {
	local lun_dir="$1"
        $PLOOP snapshot-list -H -o UUID "$lun_dir/DiskDescriptor.xml" | \
        while read snap_uuid; do
                snap_uuid=`expr "$snap_uuid" : "^\{\(.*\)\}$"`
                rm -f "$ISCSI_ROOT/snapshots/$snap_uuid"
        done
}

function pcs_iscsi_get_root() {
        local root="$1"
        if [ -n "$root" ] ; then
                echo "$root"
                return
        fi

        root="$ISCSI_ROOT"
        if [ -z "$root" ] ; then
                # ask shaman for ISCSI root
                cluster=`/usr/sbin/shaman stat 2>/dev/null | grep Cluster | tr "'" " " | awk '{ print $2 }'`
                [ -z "$cluster" ] && return
                mnt=`grep "[pv]storage://$cluster" /proc/mounts | awk '{ print $2 }'`
                [ -z "$mnt" ] && return
                root="$mnt/iscsi"
        fi
        echo "$root"
}

function pcs_iscsi_validate_addr {
        tmp_addr="$1"
        out=""
        for addr in $tmp_addr; do
                # validate addresses
                addr=`echo $addr | cut -d '/' -f 1`
                /bin/ipcalc -c $addr
                if [ $? -ne 0 ] ; then
			echo "Address '$addr' is wrong." 1>&2
			return
                fi
                /bin/ipcalc -c -6 $addr >/dev/null 2>&1
                if [ $? -ne 0 ] ; then
                        /bin/ipcalc -c -4 $addr >/dev/null 2>&1
                        if [ $? -ne 0 ] ; then
				echo "Address '$addr' is wrong." 1>&2
				return
                        fi
                        out="$addr/32 $out"
                else
                        out="$addr/64 $out"
                fi
        done
	echo "$out"
}

