#!/bin/sh

# cereal-admin: manage cereal sessions (runit service directory structure
# and runit run directory).
#
# The cereal scripts were written by
# Jameson Graef Rollins <jrollins@finestructure.net>
# and
# Daniel Kahn Gillmor <dkg@fifthhorseman.net>.
#
# They are Copyright 2007, and are all released under the GPL, version 3
# or later.

##################################################
SHAREDIR=${SHAREDIR:-"/usr/share/cereal"}
export SHAREDIR
. "$SHAREDIR/common"
[ -r "$ETC/cereal-admin.conf" ] && . "$ETC/cereal-admin.conf"
##################################################

usage() {
cat <<EOF
Usage: cereal-admin <subcommand> [options] [args]
Cereal session management program.

subcommands:
  create (c) SESSION TTY BAUD USER LOGGROUP    create cereal session
  start (s) [options] SESSION [SESSION]...     start cereal session(s)
    -a (--all)                                   start all sessions
  restart (r) [options] SESSION [SESSION]...   restart cereal session(s)
    -a (--all)                                   restart all sessions
    -r (--running)                               restart only running sessions
  stop (k) [options] SESSION [SESSION]...      stop cereal session(s)
    -a (--all)                                   stop all sessions
  destroy (d) [options] SESSION [SESSION]...   destroy cereal session(s)
    -a (--all)                                   destroy all sessions
  list (l) [options] [SESSION]...              list session(s)
    -n (--names)                                 list just session names
  help (h,?)                                   this help

EOF
}

# create session
create() {
    if [ $# -lt 5 ] ; then
	failure "Not enough input arguments.
Type 'cereal-admin help' for more info."
    fi

    SESSION="$1"
    TTY="$2"
    BAUD="$3"
    SUSER="$4"
    SGROUP=$(stat --dereference --printf="%G" "$TTY")
    LOGUSER='cereal'
    LOGGROUP="$5"

    is_session "$SESSION" && failure "A session named '$SESSION' already exists."
    check_tty "$TTY"
    check_session_tty "$TTY"
    check_user "$SUSER"
    check_group "$SGROUP"
    check_user "$LOGUSER"
    check_group "$LOGGROUP"
    check_tty_rw "$SUSER" "$SGROUP" "$TTY"

    # create service directory
    mkdir -p "$SESSIONDIR/$SESSION"
    ln -s "$SHAREDIR/mainrun" "$SESSIONDIR/$SESSION/run"
    ln -s "$SHAREDIR/finish" "$SESSIONDIR/$SESSION/finish"
    touch "$SESSIONDIR/$SESSION/down"

    # store environment variables
    mkdir -p "$SESSIONDIR/$SESSION/env"
    echo "$SESSION" > "$SESSIONDIR/$SESSION/env/SESSION"
    echo "$TTY" > "$SESSIONDIR/$SESSION/env/TTY"
    echo "$BAUD" > "$SESSIONDIR/$SESSION/env/BAUD" 
    echo "$SUSER" > "$SESSIONDIR/$SESSION/env/USER"
    echo "$SGROUP" > "$SESSIONDIR/$SESSION/env/GROUP"
    echo "$LOGUSER" > "$SESSIONDIR/$SESSION/env/LOGUSER"
    echo "$LOGGROUP" > "$SESSIONDIR/$SESSION/env/LOGGROUP"

    # create logging infrastructure
    mkdir -p -m 0750 "$SESSIONDIR/$SESSION/log/main"
    ln -s "$SHAREDIR/logrun" "$SESSIONDIR/$SESSION/log/run"
    touch "$SESSIONDIR/$SESSION/log/main/current"
    chmod 0640 "$SESSIONDIR/$SESSION/log/main/current"
    chown -R "$LOGUSER" "$SESSIONDIR/$SESSION/log/main"
    chgrp -R "$LOGGROUP" "$SESSIONDIR/$SESSION/log"

    # make supervise directory world accessible if requested
    if [ "$SUPERVISE_WORLD_ACCESSIBLE" = 'yes' ] ; then
	mkdir -p -m 0755 "$SESSIONDIR/$SESSION/supervise"
	mkdir -p -m 0755 "$SESSIONDIR/$SESSION/log/supervise"
    fi

    # create socket for screen, since it can't log to stdout
    mkfifo "$SESSIONDIR/$SESSION/socket"
    chown "$SUSER:$LOGGROUP" "$SESSIONDIR/$SESSION/socket"
    chmod 0640 "$SESSIONDIR/$SESSION/socket"

    echo "Created session '$SESSION':"
    display_session "$SESSION"

    update-service --add "$SESSIONDIR/$SESSION" "cereal.$SESSION"
}

# start_session SESSION
start_session() {
    local SESSION
    SESSION="$1"

    chpst -e "$SESSIONDIR/$SESSION/env/" sh -c ". $SHAREDIR/common && start_check" || return 1
    sv start "$SESSIONDIR/$SESSION" || return 1
    rm -r "$SESSIONDIR/$SESSION/down"
    log_write "$SESSION" "session '$SESSION' started."
}

# start session
start() {
    if [ -z "$1" ] ; then
	failure "Not enough input arguments.
Type 'cereal-admin help' for more info."
    elif [ "$1" = '--all' -o "$1" = '-a' ] ; then
	SESSIONS=$(list -n) || failure "There are no sessions." 1
    else
	SESSIONS="$@"
    fi

    for SESSION in $SESSIONS ; do
	if ! is_session "$SESSION" ; then
	    error "Session '$SESSION' not found." 1
	elif is_running "$SESSION" ; then
	    echo "Session '$SESSION' is already running."
	elif is_locked "$SESSION" ; then
	    error "Session tty device appears to be locked."
	elif start_session "$SESSION" ; then
	    echo "Session '$SESSION' started."
	else
	    error "Session '$SESSION' could not be started." 2
	fi
    done
}

# restart_session SESSION
restart_session(){
    local SESSION
    SESSION="$1"
    sv restart "$SESSIONDIR/$SESSION" || return 1
    log_write "$SESSION" "session '$SESSION' restarted."
}

# restart session
restart() {
    if [ -z "$1" ] ; then
	failure "Not enough input arguments.
Type 'cereal-admin help' for more info."
    elif [ "$1" = '--all' -o "$1" = '-a' ] ; then
	SESSIONS=$(list -n) || failure "There are no sessions." 1
    elif [ "$1" = '--running' -o "$1" = '-r' ] ; then
	SESSIONS=$(list | grep '^+' | cut -d ' ' -f 2)
	[ "$SESSIONS" ] || failure "There are no running sessions." 0
    else
	SESSIONS="$@"
    fi
    
    for SESSION in $SESSIONS ; do
	if ! is_session "$SESSION" ; then
	    error "Session '$SESSION' not found." 1
	elif ! is_running "$SESSION" ; then
	    if start_session "$SESSION" ; then
		echo "Session '$SESSION' started."
	    else
		error "Session '$SESSION' could not be started." 2
	    fi
	elif restart_session "$SESSION" ; then
	    echo "Session '$SESSION' restarted."
	else
	    error "Session could not be restarted." 2
	fi
    done
}

# stop_session SESSION
stop_session() {
    local SESSION
    SESSION="$1"
    sv stop "$SESSIONDIR/$SESSION" || return 1
    touch "$SESSIONDIR/$SESSION/down"
    log_write "$SESSION" "session '$SESSION' stopped."
}

# stop session
stop() {
    if [ -z "$1" ] ; then
	failure "Not enough input arguments.
Type 'cereal-admin help' for more info."
    elif [ "$1" = '--all' -o "$1" = '-a' ] ; then
	SESSIONS=$(list -n) || failure "There are no sessions." 1
    else
	SESSIONS="$@"
    fi
    
    for SESSION in $SESSIONS ; do
	if ! is_session "$SESSION" ; then
	    error "Session '$SESSION' not found." 1
	elif stop_session "$SESSION" ; then
	    echo "Session '$SESSION' stopped."
	else
	    error "Session '$SESSION' could not be stopped." 2
	fi
    done
}

# destroy_session SESSION
destroy_session() {
    local SESSION
    SESSION="$1"
    update-service --remove "$SESSIONDIR/$SESSION" "cereal.$SESSION"
    rm -rf "$SESSIONDIR/$SESSION"
}

# destroy session
destroy() {
    if [ -z "$1" ] ; then
	failure "Not enough input arguments.
Type 'cereal-admin help' for more info."
    elif [ "$1" = '--all' -o "$1" = '-a' ] ; then
	SESSIONS=$(list -n) || failure "There are no sessions." 1
    else
	SESSIONS="$@"
    fi

    for SESSION in $SESSIONS ; do
	if ! is_session "$SESSION" ; then
	    error "Session '$SESSION' not found." 1
        elif [ ! -w "$SESSIONDIR/$SESSION" ] ; then
            error "You do not have permission to destroy session '$SESSION'." 2
	elif is_running "$SESSION" ; then
	    echo "Session '$SESSION' is currently running."
	    printf "Really stop and destroy session? [Y|n]: "
	    read OK; OK=${OK:=Y}
	    if [ "$OK" = 'y' -o "$OK" = 'Y' ] ; then
		if stop_session "$SESSION" ; then
		    if destroy_session "$SESSION" ; then
			echo "Session '$SESSION' stopped and destroyed."
		    else
			error "Session '$SESSION' could not be destroyed." 2
		    fi
		else
		    error "Session '$SESSION' could not be stopped." 2
		fi
	    else
		error "Session '$SESSION' not stopped." 1
	    fi
	else
	    printf "Really destroy session '%s'? [Y|n]: " "$SESSION"
	    read OK; OK=${OK:=Y}
	    if [ "$OK" = 'y' -o "$OK" = 'Y' ] ; then
		if destroy_session "$SESSION" ; then
		    echo "Session '$SESSION' destroyed."
		else
		    error "Session '$SESSION' could not be destroyed." 2
		fi
	    else
		error "Session '$SESSION' not destroyed." 1
	    fi
	fi
    done
}

###############################################################
### MAIN

COMMAND="$1"
[ "$COMMAND" ] || failure "Type 'cereal-admin help' for usage."
shift

case $COMMAND in
    'create'|'c')
	create "$@"
	;;
    'start'|'s')
	start "$@"
	;;
    'restart'|'r')
	restart "$@"
	;;
    'stop'|'k')
	stop "$@"
	;;
    'destroy'|'d')
	destroy "$@"
	;;
    'list'|'l')
	list "$@" || failure "There are no sessions." 1
	;;
    'help'|'h'|'?')
	usage
	;;
    *)
	failure "Unknown command: '$COMMAND'
Type 'cereal-admin help' for usage."
	;;
esac

exit "$ERR"
