#! /bin/bash

# Usage: block-dmmd [add args | remove args]
#     
#  the xm config file should have something like:
#   dmmd:md;/dev/md0;md;/dev/md1;lvm;/dev/vg1/lv1
#  or
#   dmmd:lvm;/dev/vg1/lv1;lvm;/dev/vg1/lv2;md;/dev/md0
#  note the last device will be used for VM

# History:
#  2009-06-09, mh@novell.com:
#	Emit debugging messages into a temporary file; if no longer needed,
#	  just comment the exec I/O redirection below
#	Make variables used in functions local to avoid global overridings
#	Use vgscan and vgchange where required
#	Use the C locale to avoid dealing with localized messages
#	Assign output from assembling an MD device to a variable to aid debugging

# We do not want to deal with localized messages:
LANG=C
LC_MESSAGES=C
export LANG LC_MESSAGES

dir=$(dirname "$0")
. "$dir/block-common.sh"

#exec >> /tmp/block-dmmd-`date +%F_%T.%N`.log 2>&1
#echo shell-flags: $-

command=$1

# We check for errors ourselves:
set +e

function run_mdadm()
{
    local mdadm_cmd=$1
    local msg
    local rc

    msg="`/sbin/mdadm $mdadm_cmd 2>&1`"
    rc=$?
    case "$msg" in
	*"has been started"* | *"already active"* )
	    return 0
	    ;;
	*"is already in use"* )
	    # hmm, might be used by another device in this domU
	    # leave it to upper layers to detect a real error
	    return 2
	    ;;
	* )
	    return $rc
	    ;;
    esac
    return 1
}

function activate_md()
{
    local par=$1
    local already_active=0 cfg dev rc t

    if [ ${par} = ${par%%(*} ]; then 
        # No configuration file specified:
	dev=$par
	cfg=
    else
	dev=${par%%(*}
	t=${par#*(}
	cfg="-c ${t%%)*}"
    fi
    if /sbin/mdadm -Q -D $dev; then
	already_active=1
    fi
    run_mdadm "-A $dev $cfg"
    rc=$?
    if [ $already_active -eq 1 ] && [ $rc -eq 2 ]; then
	return 0
    fi
    return $rc
}

function deactivate_md()
{
    local par=$1	# Make it explicitly local

    ## We need the device name only while deactivating
    /sbin/mdadm -S ${par%%(*}
    return $?
}

function activate_lvm()
{
    local run_timeout=90
    local end_time

    end_time=$(($(date +%s)+${run_timeout}))
    while true; do
	/sbin/lvchange -aey $1
	if [ $? -eq 0 -a -e $1 ]; then
	    return 0
	fi

	sleep 0.1
	if [ $(date +%s) -ge ${end_time} ]; then
	    log err "Failed to activate $1 within ${run_timeout} seconds"
	    return 1
	fi
    done
    return 1
}

function deactivate_lvm()
{
    /sbin/lvchange -aen $1
    if [ $? -eq 0 ]; then
	# We may have to deactivate the VG now, but can ignore errors:
#        /sbin/vgchange -an ${1%/*} || :
        # Maybe we need to cleanup the LVM cache:
#        /sbin/vgscan --mknodes || :
	return 0
    fi
    return 1
}

BP=100
SP=$BP
VBD=

declare -a stack
function push()
{
    if [ -z "$1" ]; then
        return
    fi
    let "SP -= 1"
    stack[$SP]="${1}"
}

function pop()
{
    VBD=

    if [ "$SP" -eq "$BP" ]; then
        return
    fi

    VBD=${stack[$SP]}
    let "SP += 1"
}

function activate_dmmd()
{
    case $1 in
        md)
            activate_md $2
            return
            ;;
        lvm)
            activate_lvm $2
            return
            ;;
    esac
}

function deactivate_dmmd()
{
    case "$1" in
        md)
            deactivate_md $2
            return
            ;;
        lvm)
            deactivate_lvm $2
            return
            ;;
    esac
}

function cleanup_stack()
{
    while [ 1 ]; do
        pop
        if [ -z "$VBD" ]; then
            break
        fi
        deactivate_dmmd $VBD
    done
}

function parse_par()
{
    local ac par rc s t		# Make these explicitly local vars

    ac=$1
    par="$2"

    par="$par;"
    while [ 1 ]; do
        t=${par%%;*}
        if [ -z "$t" ]; then
            return 0
        fi
        par=${par#*;}

        s=${par%%;*}
        if [ -z "$s" ]; then
            return 1
        fi
        par=${par#*;}

        if [ "$ac" = "activate" ]; then
            activate_dmmd $t $s
            rc=$?
            if [ $rc -ne 0 ]; then
               return 1
            fi
        fi
        push "$t $s"

    done
}


case "$command" in
    add)
	p=`xenstore-read $XENBUS_PATH/params` || true
	claim_lock "dmmd"
	dmmd=$p
	parse_par activate "$dmmd"
	rc=$?
	if [ $rc -ne 0 ]; then
	    cleanup_stack
	    release_lock "dmmd"
	    exit 1
	fi
	lastparam=${dmmd##*;}
	usedevice=${lastparam%(*}
	    xenstore-write $XENBUS_PATH/node "$usedevice"
	    write_dev "$usedevice"
	    release_lock "dmmd"
	    exit 0
	    ;;

    remove)
	p=`xenstore-read $XENBUS_PATH/params` || true
	claim_lock "dmmd"
	dmmd=$p
	parse_par noactivate "$dmmd"
	cleanup_stack
	release_lock "dmmd"
	exit 0
	;;
esac