#!/bin/bash

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

##################################################
CMD=$(basename $0)

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

usage() {
cat <<EOF
Usage: $CMD <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
  stop (k) [options] SESSION [SESSION]...      stop cereal session(s)
    -a (--all)                                   stop all sessions
  restart (r) [options] SESSION [SESSION]...   restart cereal session(s)
    -a (--all)                                   restart all sessions
    -r (--running)                               restart only running 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 (( $# < 5 )) ; then
	failure "Not enough input arguments.
Type '$CMD help' for more info."
    fi

    SESSION="$1"
    TTY="$2"
    BAUD="$3"
    SUSER="$4"
    SGROUP=$(ls -l "$TTY" | awk '{ print $4 }')
    LOGUSER='cereal'
    LOGGROUP="$5"

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

    mkdir -p "$SESSIONDIR/$SESSION"

    # create run script
    ln -s "$SHAREDIR/mainrun" "$SESSIONDIR/$SESSION/run"

    # 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 script
    mkdir -p -m 0750 "$SESSIONDIR/$SESSION/log/main"
    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"

    # 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"
    ln -s "$SHAREDIR/logrun" "$SESSIONDIR/$SESSION/log/run" 

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

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

# start_check
start_check() {
    check_tty_rw "$USER" "$GROUP" "$TTY"
}
export -f start_check

# start_session SESSION
start_session() {
    local SESSION="$1"
    ln -s "$SESSIONDIR/$SESSION" "$SERVICE.$SESSION"
    log_write "$SESSION" "session '$SESSION' started."
}
export -f start_session

# start session
start() {
    if [ -z "$1" ] ; then
	failure "Not enough input arguments.
Type '$CMD 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_linked "$SESSION" ; then
	    error "Session '$SESSION' is already running." 1
	elif [ ! -w "$SERVICEDIR" ] ; then
	    error "You do not have permission to start session '$SESSION'." 2
	elif ! chpst -e "$SESSIONDIR/$SESSION/env/" bash -c start_check ; then
	    error "Session '$SESSION' not properly configured." 2
	else
	    if start_session "$SESSION" ; then
		echo "Started session '$SESSION'."
	    else
		error "Session '$SESSION' could not be started." 2
	    fi
	fi
    done
}

# stop_session SESSION
stop_session() {
    local SESSION="$1"
    log_write "$SESSION" "stopping session '$SESSION'..."
    sv exit "$SERVICE.$SESSION"
    rm "$SERVICE.$SESSION"
}
export -f stop_session

# stop session
stop() {
    if [ -z "$1" ] ; then
	failure "Not enough input arguments.
Type '$CMD 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_linked "$SESSION" ; then
	    error "Session '$SESSION' not linked." 1
	elif ! is_controllable "$SESSION" ; then
	    error "You do not have permission to stop session '$SESSION'." 2
	else
	    if stop_session "$SESSION" ; then
		echo "Stopped session '$SESSION'."
	    else
		error "Session '$SESSION' could not be stopped." 2
	    fi
	fi
    done
}

# restart_session SESSION
restart_session() {
    local SESSION="$1"
    log_write "$SESSION" "restarting session '$SESSION'..."
    sv restart "$SERVICE.$SESSION"
}
export -f restart_session

# restart session
restart() {
    if [ -z "$1" ] ; then
	failure "Not enough input arguments.
Type '$CMD 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
	    echo "Session '$SESSION' not found."
	elif ! is_linked "$SESSION" ; then
	    start "$SESSION"
	elif ! is_controllable "$SESSION" ; then
	    error "You do not have permission to restart session '$SESSION'." 2
	else
	    if restart_session "$SESSION" ; then
		echo "Restarted session '$SESSION'."
	    else
		error "Session '$SESSION' could not be restarted." 2
	    fi
	fi
    done
}

# destroy_session SESSION
destroy_session() {
    rm -rf "$SESSIONDIR/$1"
}
export -f destroy_session

# destroy session
destroy() {
    if [ -z "$1" ] ; then
	failure "Not enough input arguments.
Type '$CMD 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_linked "$SESSION" ; then
	    echo "Session '$SESSION' is currently linked."
	    read -p "Really stop and destroy session? [Y|n]: " OK
	    if [ -z "$OK" -o "${OK/y/Y}" = 'Y' ] ; then
		if stop_session "$SESSION" ; then
		    if destroy_session "$SESSION" ; then
			echo "Stopped and destroyed session '$SESSION'."
		    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
	    read -p "Really destroy session '$SESSION'? [Y|n]: " OK
	    if [ -z "$OK" -o "${OK/y/Y}" = 'Y' ] ; then
		if destroy_session "$SESSION" ; then
		    echo "Destroyed session '$SESSION'."
		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 '$CMD 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 '$CMD help' for usage."
	;;
esac

exit "$ERR"
