#!/bin/bash

TGTD_BIN="/usr/libexec/pstorage-iscsi/tgtd/bin"
TGTD="$TGTD_BIN/tgtd"
TGTD_IO_BACKEND="aio" # "rdwr"
TGTADM="$TGTD_BIN/tgtadm"
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"

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 tgtd_add_portal() {
	local $*
	echo "ip = $ip_address, port = $port" 1>&2
	portal="$ip_address:$port"
	$TGTADM -C $ctl_port --lld iscsi --op new --mode portal --param portal="$portal"
	return $?
}

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

	local ip_address="$(echo "$1" | cut -d '/' -f1)"; shift
	local port=$TGTD_PORT
	local portal="$ip_address:$port"
	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"

	echo "ip = $ip_address, port = $port" 1>&2
	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
		ip_address="$(echo "$1" | cut -d '/' -f1)"
		shift
		tgtd_add_portal ctl_port=$ctl_port ip_address=$ip_address port=$port
		if [ $? -ne 0 ]; then
			echo "Can't add portal $ip_address:$port, 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
	return 0
}

function tgtd_create_target() {
	local "$@"

	tgtd_daemon_start $target $ctl_port $addresses
	if [ $? -ne 0 ]; then
		echo "Failed to start tgtd" 2>&1
		return 1
	fi

	tgtd_disable_target target=$target ctl_port=$ctl_port
	if [ $? -ne 0 ]; then
		echo "Failed to disable target" 2>&1
		return 2
	fi

	$TGTADM -C $ctl_port --lld iscsi --mode target --op new --tid 1 --targetname "$target"
	[ $? -ne 0 ] && return 3

	_tgtd_enable_persistent_reservation target=$target ctl_port=$ctl_port
	if [ $? -ne 0 ]; then
		echo "Failed to enable persistent reservation" 1>&2
		# not critical
	fi

	return 0
}

function tgtd_delete_target() {
	local $*
	local forceoption=""

	if [ -n "$force" ]; then
		# Offline everything first on forced stop.
		tgtd_disable_target target=$target ctl_port=$ctl_port

		# 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";
	fi

	$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

	return 0
}

function tgtd_configure_target {
	local $*

	$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

	# LUN 0 must have for each target, it's used for service purposes
	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"
	rc=$?
	if [ $rc -ne 0 ]; then
		echo "Unable to set scsi_id for LUN 0" 1>&2
	fi

	return $rc
}

function tgtd_enable_target() {
	local $*
	# Put tgtd into "ready" state.
	$TGTADM -C $ctl_port --op update --mode sys --name State -v ready
	return $?
}

function tgtd_disable_target() {
	local $*
	# Put tgtd into "offline" state.
	$TGTADM -C $ctl_port --op update --mode sys --name State -v offline >/dev/null 2>&1
	return $?
}

function tgtd_disable_authentication() {
	local $*
	$TGTADM -C $ctl_port --mode target --op bind --tid 1 -I ALL
	return $?
}

function tgtd_add_account() {
	local $*
	$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 $?
}

function _tgtd_enable_persistent_reservation() {
	local $*
	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
	return $?
}

function tgtd_get_target_status() {
	local $*

	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
}

function tgtd_show_target_portals() {
	local $*
	$TGTADM -C $ctl_port --mode portal --op show | cut -d ' ' -f2 | cut -d ',' -f1 2>/dev/null
}

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

function tgtd_attach_lun() {
	local $*

	tgtd_chown_nogrp "$dev"

	$TGTADM -C $ctl_port --lld iscsi --mode logicalunit --op new --tid 1 --lun $lun_id -b "$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 tgtd_detach_lun() {
	local $*
	$TGTADM -C $ctl_port --lld iscsi --mode logicalunit --op delete --tid 1 --lun "$lun_id" >/dev/null 2>&1
	return $?
}

function _get_lun_info {
	local ctl_port="$1"
	local lun_id="$2"
	local max_lun_id=`$TGTADM -C $ctl_port --mode target --op show 2>/dev/null | grep "LUN:" | awk '{ print $2 }' | sort -nr | head -n 1`
	[ -z "$max_lun_id" ] && return
	max_lun_id=$[ $max_lun_id + 1]
	$TGTADM -C $ctl_port --mode target --op show | tr '\n' '_' | sed "s/Target.*\(LUN: 0.*\)Account information:.*/\1 LUN: ${max_lun_id=}/" | sed "s/.*\(LUN: ${lun_id}.*\)LUN: $[$lun_id + 1].*/\1/" | tr '_' '\n'
}

function tgtd_is_lun_online() {
	local $*
	online=`_get_lun_info $ctl_port $lun_id | grep "Online:" | tr '\n' ' ' | awk '{ print $2 }'`
	echo $online
}

function tgtd_detach_dev() {
	return 0
}

function tgtd_show_lun_stats() {
	local $*
	$TGTADM -C $ctl_port --lld iscsi --mode logicalunit --op stat --tid 1 --lun $lun_id
}

function tgtd_set_limit() {
	local $*
	$TGTADM -C $ctl_port --mode target --op update --tid 1 -n $type -v $limit  >/dev/null 2>&1
}

function tgtd_rescan_lun() {
	local $*

	# detach LUN from target
	tgtd_detach_lun ctl_port=$ctl_port lun_id=$lun_id
	[ $? -ne 0 ] && return 1

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

	return $?
}
