#!/bin/bash
_version="20151222_0201"

_dl_path="repo.digiconcept.net/drbddiff/master"
_sig="0xA9621CC5B2A8E1BA"
_ssh() {
	ssh -n -oPasswordAuthentication=no $@ 
}
_scp() {
	scp -oPasswordAuthentication=no $@
}

_info() {
	echo -e "\t[INFO]\t$1"
}
_error() {
	echo -e "\t[ERROR]\t $1"
}
_fatal() {
	echo -e "\t[FATAL]\t$1"
	echo -e "\t[FATAL]\tquitting"
	exit 1
}
_debug() {
	if [[ $_verbosity -ge "1" ]] ; then
		echo -e "\t[DEBUG]\t$1"
	fi
}


_disconnect_peer() {
	if [[ $_online -eq "0" && $_disconnect -eq "1" ]] ; then
                drbdadm disconnect $_resource || _fatal "could not disconnect $_resource" 
		_info "successfully disconnected $_resource"
		_off="$_off$_resource;"
	fi
}
_reconnect_peer() {
	if [[ $_online -eq "0" && $_reconnect -eq "1" ]] ; then
		IFS=";"
		for _connect in $_off ; do
                	drbdadm connect $_connect || echo -e "[ERROR] could not reconnect $_connect"
			_info "successfully reconnected $_connect"
		done
	fi
}

_set_disk() {
	if [[ ! -b $_device ]] ; then _fatal "$_device (local) is not a block device" ; fi
	_resource="$(drbdsetup show $_device | grep resource | sed -r 's/resource\ (.+)\ \{/\1/g')" # need resource for drbdadm connect/disconnect
	_disk="$(drbdsetup show $_device | grep -P "disk.+\".+\"\;$" | sed -r 's/.+\"(.+)\";$/\1/g')" # need disk for dd'ing
	#if [[ -z $_remote_disk ]] ; then
		_remote_disk=$( _ssh $_remote_host drbdsetup show $_device | grep -P "disk.+\"" | sed -r 's/.+\"(.+)\"\;$/\1/g') || _fatal "could not determine remote disk (maybe use --remote-disk switch)"
		_ssh $_remote_host "test -b $_remote_disk" || _fatal "$_remote_disk on $_remote_host not a valid block device"
	#fi
	_debug "found out-of-sync statement in logfile: device:$_device, disk:$_disk, remote_disk:$_remote_disk, resource:$_resource, block:$_offset, count:$_count"
}

_scan() {
	if [[ $_scanmode -eq "1" ]] ; then
                _counter=0
		_info "starting scan..."
		_oos_count=$(grep -cP "block\ (drbd[0-9]+):\ Out\ of\ sync:\ start=([0-9]+),\ size=([0-9]+)" $_logfile) || _fatal "no out-of-sync statement found in logfile"
		while read _line ; do
                        if [[ ${_line} =~ block\ (drbd[0-9]+):\ Out\ of\ sync:\ start=([0-9]+),\ size=([0-9]+) ]] ; then
				_device="/dev/${BASH_REMATCH[1]}"
				_offset=${BASH_REMATCH[2]}
                                _count=${BASH_REMATCH[3]}
				_set_disk
                        else
				continue 1
			fi
        		if [[ $_counter -eq "0" || ! $_off =~ "${_resource};" ]] ; then 
				_disconnect="1" 
			else
				_disconnect="0" 
			fi
			_run
			_counter=$(( $_counter + 1))
	        done < ${_logfile}
	_reconnect="1"
	_reconnect_peer
        fi
}

_prepare_tmp() {
	if [[ -z $_tmpdir ]] ; then _tmpdir="/tmp/drbddiff/" ; fi
	if [[ ! -d $_tmpdir ]] ; then 
		mkdir $_tmpdir || _fatal "could not create $_tmpdir" 
	fi
	_ssh $_remote_host test -d $_tmpdir  || { _ssh $_remote_host mkdir $_tmpdir || _fatal "could not create $_tmpdir on $_remote_host" ; }
	_debug "$_tmpdir prepared"
}

_run() {
	_tmp_local="$(mktemp --tmpdir=${_tmpdir} --suffix=-${_resource}-${_offset}+${_count}@${HOSTNAME}.drbd_debug)" || _fatal "could not create local tempfile"
	_tmp_remote="$(_ssh $_remote_host "mktemp --tmpdir=${_tmpdir} --suffix=-${_resource}-${_offset}+${_count}@${_remote_host}.drbd_debug")" || _fatal "could not create remote tempfile"
	dd if=$_disk of=$_tmp_local iflag=direct bs=512 skip=$_offset count=$_count > /dev/null 2>&1 || _fatal "dd'ing locally failed"
	_a_local+=($_tmp_local)
	_disconnect_peer
	_ssh $_remote_host "dd if=$_remote_disk of=$_tmp_remote iflag=direct bs=512 skip=$_offset count=$_count" > /dev/null 2>&1 || _fatal "dd'ing remotely failed"
	_a_remote+=($_tmp_remote)
	_debug "finished dd'ing sector $_offset"
	_reconnect_peer
}

_scp_files() {
	_debug "echo $_a_remote"
	for _r_file in "${_a_remote[@]}" ; do
		_info "fetching remote file $_r_file"
		_print=$( _scp $_remote_host:$_r_file $_r_file 2>&1 || _fatal "could not fetch $_r_file" )
		_debug "\t$_print"
	done
}

_dcfname() {
	echo "$1" | sed -r "s/.*\/?tmp\.[A-Za-z0-9]+-([A-Za-z0-9@]+)-([0-9]+)\+([0-9]+).*/\1 \2 \3/g"
}

_diff() {
	for ((n=0; n < ${#_a_local[@]} && n < ${#_a_remote[@]}; n++)) ; do
		_sha256sum_local=$(sha256sum ${_a_local[n]}| cut -f1 -d' ')
		_sha256sum_remote=$(sha256sum ${_a_remote[n]} | cut -f1 -d' ' )
		_info_local="$( _dcfname ${_a_local[n]} )"
		_info_remote="$( _dcfname ${_a_remote[n]} )"
		if [[ ${_sha256sum_local} == ${_sha256sum_remote}  ]] ; then
			_info "FALSE POSITIVE DETECTED:"
			echo -e "\t$_info_local"
			if [[ $_verbosity -ge 1 ]] ; then
				echo -e "\t${_a_local[n]}:  $_sha256sum_local"
				echo -e "\t${_a_remote[n]}: $_sha256sum_remote"
			fi
			echo -e ""
		else
			_a_deg_local+=(${_info_local})
			_info "DEGRADATION CONFIRMED:"
			echo -e "\t$_info_local"
			echo -e "\t${_a_local[n]}:  $_sha256sum_local"
			echo -e "\t${_a_remote[n]}: $_sha256sum_remote"
			echo -e ""
			if [[ $_verbosity -ge 1 ]] ; then
				xxd ${_a_local[n]} > ${_a_local[n]}.xxd
				xxd ${_a_remote[n]} > ${_a_remote[n]}.xxd
				echo -e ""
				_debug "diff\'ing"
				echo -e ""
				diff ${_a_local[n]}.xxd ${_a_remote[n]}.xxd
				echo -e ""
			fi
		fi
	done

	_info "all detected degradations [locally]"
	echo -e ""
	for ((t=0; t < ${#_a_deg_local[@]}; t++)) ; do
#		if (( t%3 == 0 )) ; then echo -en "\t" ; fi
		echo -n "${_a_deg_local[t]} "
		if (( t%3 == 2 )) ; then echo -e "" ; fi
	done
}

_chk_scanmode() {
	if [[ $_scanmode -eq "1" ]] ; then
		_fatal "cannot combine $1 with scanmode\nexiting"
	fi
}

_update() {
if [[ $(which curl) && ( $(which gpg) || $(which gpg2) )  ]] ; then
	_dl_tmp=$(mktemp)
	curl https://$_dl_path > ${_dl_tmp} 2> /dev/null || _fatal "downloading $_dl_path failed"
	curl https://${_dl_path}.asc > ${_dl_tmp}.asc 2> /dev/null || _fatal "downloading ${_dl_path}.asc failed"
	if [[ ! $(gpg --list-key $_sig > /dev/null 2>&1) ]] ; then
		gpg --recv-key $_sig > /dev/null 2>&1 || _fatal "key $_sig not found"
	fi
	gpg --verify ${_dl_tmp}.asc  || _fatal "could not verify integrity of ${_dl_tmp}"

	cp $0 $0.bak || _fatal "while making backup of $0"
	mv ${_dl_tmp} $0 || _fatal "while replacing $0" 
	_info "upgrade complete"
	chmod 750 ./$0
	./$0 -V
else
	_fatal "no curl and/or gpg/gpg2 in \$PATH"
fi
}

_online="0"
_reconnect="0"
_disconnect="0"
_verbosity="0"
_scanmode="0"
_dryrun="0"

while [[ $# > 0 ]] ; do
	key="$1"
	case "$key" in
		-r|--resouce)
		shift
		_resource="$1"
		_chk_scanmode "-r|--resource"
		shift
		;;
		-o|--offset)
		shift
		_offset="$1"
		_chk_scanmode "-o|--offset"
		shift
		;;
		-c|--count)
		shift
		_count="$1"
		_chk_scanmode "-c|--count"
		shift
		;;
		-h|--remote_host)
		shift
		_remote_host="$1"
		shift
		;;
		--remote-disk)
		shift
		_remote_disk="$1"
		shift
		;;	
		-s|--scanfile)
		shift
		_scanmode="1"
		if [[ -f $1 ]] ; then
			 _logfile="$1"
			shift
		else
			echo "no file found at ${1}\nexiting"
			exit 1
		fi	
		;;
		--online)
		shift
		_online="1"
		;;
		--R|-reconnect)
		shift
		_chk_scanmode "-R|--reconnect"
		_reconnect="1"
		;;
		-D|--disconnect)
		shift
		_chk_scanmode "-D|--disconnect"
		_disconnect="1"
		;;
		--tmpdir)
		shift
		_tmpdir="$1"
		shift
		;;
		-v|--verbose)
		shift
		_verbosity=$(( $_verbosity + 1 ))
		;;
		-n|--dry-run)
		shift
		_dryrun="1"
		;;
		-V|--version)
		echo "$0 $_version"
		echo "hash: $(sha256sum $0)"
		exit 0
		;;
		--update)
		_update
		exit 0
		;;
		--help)
			_fatal "not implemented as of yet"
		;;
		*)
                _fatal "command $1 unknown\nexiting"
                exit 1
                ;;
	esac
done


if [[ ! "_ssh $_remote_host true" ]] ; then _fatal "could not connect to remote host: ${_remote_host:-NONE SPECIFIED}" ; fi

if [[ $_scanmode -eq "1" ]] ; then
	_prepare_tmp
	_scan
	_scp_files
	_diff
else
	#_disk="$(drbdadm sh-md-dev $_resource)" || exit 1
        #_remote_disk="$($_ssh $_remote_host "drbdadm sh-md-dev $_resource")" || exit 1
	_set_disk
	_prepare_tmp
	_run
fi
