#!/bin/sh

# vblade-persist: this script sets up a permanent vblade instance under
#                 runit supervision

# Author: Daniel Kahn Gillmor <dkg@fifthhorseman.net>
# Copyright: November 2007
# License: GPLv3 or later

# Where are the servicedirs stored?
VDIR=/var/lib/vblade-persist/vblades

# Where do the vblade-{run,finish,logrun} scripts live?
SDIR=/usr/share/vblade-persist

usage() {
    echo "usage: vblade-persist ls [--no-header]"
    echo "       vblade-persist start|stop|restart|auto|noauto SHELF SLOT"
    echo "       vblade-persist start|stop|restart|auto|noauto all"
    echo "       vblade-persist setup SHELF SLOT NETIF SOURCE"
    echo "       vblade-persist destroy SHELF SLOT"
    echo "       vblade-persist mac SHELF SLOT ls"
    echo "       vblade-persist mac SHELF SLOT clear"
    echo "       vblade-persist mac SHELF SLOT add [MAC ...] "
    echo "       vblade-persist mac SHELF SLOT del [MAC ...] "
}

error() {
    echo "vblade-persist: $1" >&2
}

fatal() {
    error "$1"
    exit ${2:-'1'}
}

require_sv() {
    [ -x /usr/bin/sv ] || \
	fatal "/usr/bin/sv not available.  Please install runit."
}

# FIXME: make this test a little more robust.
verify_vblade_persist_path() {
    [ -d "$1" ]
}

verify_mac_address() {
    echo "$1" | tr -d ':' | egrep -q '^[[:xdigit:]]{12}$'
}

# shelf identifiers should be integers:
# FIXME: check that they're in a certain range? 
verify_shelf() {
    expr match "$1" '[0-9][0-9]*$' >/dev/null 
}

# slot identifiers should be integers:
# FIXME: check that they're in a certain range? 
verify_slot() {
    expr match "$1" '[0-9][0-9]*$' >/dev/null 
}

# FIXME: if there's no ip and no ifconfig, what do we do?
verify_interface() {
    if [ -x /bin/ip ] ; then
	/bin/ip link show "$1" >/dev/null
    elif [ -x /sbin/ifconfig ] ; then
	/sbin/ifconfig -s "$1" > /dev/null
    else
	error "Don't know how to test interface.  Assuming '$1' is acceptable."
	true
    fi
}

# the device must be specified by an absolute path, and it shoud be
# either a block device, a link to a block device, a regular file, or
# a link to a regular file:
verify_source() {
    expr match "$1" '/' >/dev/null && \
	( [ -b `readlink -f "$1"` ] || [ -f `readlink -f "$1"` ] )
}

setup() {
    local SHELF
    local SLOT
    local NETIF
    local SOURCE
    SHELF="$1"
    SLOT="$2"
    NETIF="$3"
    SOURCE="$4"

    verify_shelf "$SHELF" || fatal "not a valid shelf: '$SHELF'"
    verify_slot "$SLOT" || fatal "not a valid shelf: '$SLOT'"
    verify_interface "$NETIF" || fatal "not a valid interface: '$NETIF'"
    verify_source "$SOURCE" || fatal "not a valid source: '$SOURCE'"

    local DNAME
    DNAME="e$SHELF.$SLOT"
    
    mkdir "$VDIR/$DNAME" || \
	fatal "Maybe you are already exporting a device as shelf $SHELF, slot $SLOT?"
    mkdir "$VDIR/$DNAME/env"
    mkdir "$VDIR/$DNAME/log"
    mkdir "$VDIR/$DNAME/macs"
    
    # start it off idle (so that the admin can decide when to start):
    touch "$VDIR/$DNAME/down"

    ln -s "$SDIR/vblade-run" "$VDIR/$DNAME/run"
    ln -s "$SDIR/vblade-finish" "$VDIR/$DNAME/finish"
    ln -s "$SDIR/vblade-logrun" "$VDIR/$DNAME/log/run"

    echo "$SHELF" > "$VDIR/$DNAME/env/SHELF"
    echo "$SLOT" > "$VDIR/$DNAME/env/SLOT"
    echo "$NETIF" > "$VDIR/$DNAME/env/NETIF"
    echo "$SOURCE" > "$VDIR/$DNAME/env/SOURCE"

    # tell runit to supervise this vblade:
    update-service --add "$VDIR/$DNAME" vblade-"$DNAME" || \
	fatal "Could not set up supervision for this vblade"
}

destroy() {
    local SHELF
    local SLOT
    SHELF="$1"
    SLOT="$2"
    verify_shelf "$SHELF" || fatal "Not a valid shelf: '$SHELF'"
    verify_slot "$SLOT" || fatal "Not a valid slot: '$SLOT'"

    local DNAME
    local DPATH
    DNAME="e$SHELF.$SLOT"
    DPATH="$VDIR/$DNAME"
    verify_vblade_persist_path "$DPATH" || \
	fatal "'`basename $DPATH`' is not a vblade-persist-managed export."
    
    update-service --remove "$DPATH" vblade-"$DNAME"
    sv exit "$DPATH"
    rm -rf "$DPATH"
}


list() {
    [ "$1" = "--no-header" ] || echo '#shelf slot netif source auto? stat'
    for dev in `ls "$VDIR"` ; do
	# if we can't figure out the state, put "unsupervised" in the stat column
	local STAT
	STAT=unsupervised
	[ -e "$VDIR/$dev/supervise/stat" ] && STAT=$(cat "$VDIR/$dev/supervise/stat")
        echo $(cat "$VDIR/$dev/env/SHELF" \
	    "$VDIR/$dev/env/SLOT" \
	    "$VDIR/$dev/env/NETIF" \
	    "$VDIR/$dev/env/SOURCE") \
	    $( [ -e "$VDIR/$dev/down" ] && echo 'no')auto \
	    "$STAT"
    done
}

svcommand() {
    require_sv
    local CMD
    local SVDIR
    CMD="$1"
    SVDIR="$2"
    verify_vblade_persist_path "$SVDIR" || \
	fatal "'`basename $DPATH`' is not a vblade-persist-managed export"
    sv "$CMD" "$SVDIR"
}


auto() {
    verify_vblade_persist_path "$2" || \
	fatal "'`basename $DPATH`' is not a vblade-persist-managed export"
    if [ "$1" = "auto" ] ; then
	rm -f "$2/down"
    else
	touch "$2/down"
    fi
}

autoall() {
    local AUTO
    AUTO="$1"
    for dev in `ls "$VDIR"` ; do
	auto "$AUTO" "$VDIR/$dev"
    done
}

svall() {
    require_sv
    local CMD
    CMD="$1"
    for dev in `ls "$VDIR"` ; do
	svcommand "$CMD" "$VDIR/$dev"
    done
}

mac() {
    local SHELF
    SHELF="$1"
    shift
    local SLOT
    SLOT="$1"
    shift

    verify_shelf "$SHELF" || fatal "Not a valid shelf: '$SHELF'"
    verify_slot "$SLOT" || fatal "Not a valid slot: '$SLOT'"
    
    local DPATH
    DPATH="$VDIR/e$SHELF.$SLOT"
    verify_vblade_persist_path "$DPATH" || \
	fatal "'`basename $DPATH`' is not a vblade-persist-managed export."

    local CHANGES
    CHANGES=no

    local CMD
    CMD="$1"
    case "$CMD" in
	clear)
	    if (ls "$DPATH/macs/" | grep -q ^.) ; then
		rm -f "$DPATH"/macs/*
		CHANGES=yes
	    fi
	    ;;
	add)
	    shift
	    for mac in "$@"; do 
		if ( ! verify_mac_address "$mac" ) ; then
		    error "Not a valid MAC address: '$mac'"
		elif [ ! -e "$DPATH/macs/$mac" ] ; then
		    touch "$DPATH/macs/$mac"
		    CHANGES=yes
		fi
	    done
	    ;;
	del)
	    shift
	    for mac in "$@"; do 
		if [ -e "$DPATH/macs/$mac" ] ; then
		    rm -f "$DPATH/macs/$mac"
		    CHANGES=yes
		fi
	    done
	    ;;
	ls)
	    ls -1 "$DPATH/macs/"
	    ;;
	*)
	    usage
	    exit 1;
	    ;;
    esac

    # restart any running vblade upon change
    if [ "$CHANGES" = "yes" ] && \
	[ -e "$DPATH/supervise/stat" ] && \
	[ `cat "$DPATH/supervise/stat"` = "run" ] ; then
	sv restart "$DPATH"
    fi
}

case "$1" in
    setup)
	setup "$2" "$3" "$4" "$5"
	;;
    destroy)
	destroy "$2" "$3"
	;;
    ls)
	list "$2"
	;;
    start|stop|restart)
	if [ "all" = "$2" ] ; then
	    svall "$1"
	else
	    verify_shelf "$2" || fatal "Not a valid shelf: '$2'"
	    verify_slot "$3" || fatal "Not a valid slot: '$3'"
	    svcommand "$1" "$VDIR/e$2.$3"
	fi
	;;
    auto|noauto)
	if [ "all" = "$2" ] ; then
	    autoall "$1"
	else
	    verify_shelf "$2" || fatal "Not a valid shelf: '$2'"
	    verify_slot "$3" || fatal "Not a valid slot: '$3'"
	    auto "$1" "$VDIR/e$2.$3"
	fi
	;;
    mac)
	shift
	mac "$@"
	;;
    *)
	usage
	exit 1
	;;
esac
