# -*-shell-script-*-

# Shared bash functions for cereal
#
# 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.

##################################################
# managed directories
ETC="/etc/cereal"
export ETC
SESSIONDIR="/var/lib/cereal/sessions"
export SESSIONDIR
ERR=0
export ERR
##################################################

error() {
    echo "$1" >&2
    ERR=${2:-'1'}
}

failure() {
    echo "$1" >&2
    exit ${2:-'2'}
}

# check if TTY is valid tty
# check_is_tty TTY
check_tty() {
    [ -c "$1" ] || failure "'$1' is not a valid tty."
}

# check is tty is already being used in another session
# check_is_session_tty TTY
check_session_tty() {
    local SESSION
    local TTY
    TTY="$1"

    for SESSION in $(ls "$SESSIONDIR") ; do
	if grep -q "^$TTY$" "$SESSIONDIR/$SESSION/env/TTY" ; then
	    failure "TTY '$TTY' is already being monitored by session '$SESSION'."
	fi
    done
}	

# check if USER is valid
# check_user USER
check_user() {
    getent passwd "$1" > /dev/null || failure "'$1' is not a valid user."
}

# check if GROUP is valid
# check_group GROUP
check_group() {
    getent group "$1" > /dev/null || failure "'$1' is not a valid group."
}

# check if the user can read/write to a TTY
is_tty_rw() {
    chpst -u "$1:$2" bash -c "test -r $3 && test -w $3"
}

# check_tty_rw USER GROUP TTY
check_tty_rw() {
    is_tty_rw "$@" || failure "User '$1' does not have read/write access to tty '$3', tty is not g+rw, or you do not have permission to change user."
}

# start_check
# this is called with "chpst -e" with a session environment
start_check() {
    is_tty_rw "$USER" "$GROUP" "$TTY"
}

# check if session exists
# is_session SESSION
is_session() {
    test -d "$SESSIONDIR/$1"
}

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

    STAT_FILE="$SESSIONDIR/$SESSION/supervise/stat"

    if [ -r "$SESSIONDIR/$SESSION/supervise" ] ; then
	if [ -e "$STAT_FILE" ] ; then
	    if [ $(cat "$STAT_FILE") = 'run' ] ; then
		# return 0 if the service is running
		return 0
	    else
		# return 1 if the service is *not* running
		return 1
	    fi
	else
	    # if the stat file doesn't exist, assume it's not running
	    return 1
	fi
    else
        # return 2 if we can't read the stat file and don't know the status
	return 2
    fi
}

# check if sessions tty is locked
# is_locked SESSION
is_locked() {
    local TTY
    TTY=$(cat "$SESSIONDIR/$1/env/TTY")
    test -e /var/lock/LCK..${TTY##/dev/}
}

# can_attach SESSION [USER]
can_attach() {
    local USER
    USER=${2:-"$USER"}
    [ "$USER" = $(cat "$SESSIONDIR/$1/env/USER") ]
}

# in_group USER GROUP
in_group() {
    groups "$1" | cut -d ':' -f 2 | tr ' ' '\n' | grep -q "^$2$"
}

# can_follow SESSION [USER]
can_follow() {
    local LOGUSER
    local LOGGROUP
    local USER
    LOGUSER=$(cat "$SESSIONDIR/$1/env/LOGUSER")
    LOGGROUP=$(cat "$SESSIONDIR/$1/env/LOGGROUP")
    USER=${2:-"$USER"}
    [ "$USER" = "$LOGUSER" ] || in_group "$USER" "$LOGGROUP" || [ $(id -u "$USER") = '0' ]
}

# write to the log of a session
#log_write SESSION STATEMENT
log_write() {
    printf "\ncereal: %s\n" "$2" >> "$SESSIONDIR/$1/socket"
}

# display_session SESSION [USER]
display_session() {
    local SESSION
    local SFLAG
    local AFLAG
    local FFLAG

    SESSION="$1"
    USER=${2:-"$USER"}

    # set state flag
    # last flag works only for users that can read supervise/stat
    is_running "$SESSION"
    case $? in
	0)
	    SFLAG='+' # running
	    ;;
	1)
	    SFLAG='-' # stopped
	    ;;
	2)
	    SFLAG='?' # unknown
	    ;;
    esac
    # set attach flag
    if can_attach "$SESSION" "$USER" ; then
	AFLAG='a'
    else
	AFLAG='-'
    fi    
    # set follow flag

    if can_follow "$SESSION" "$USER" ; then
	FFLAG='f'
    else
	FFLAG='-'
    fi

    cd "$SESSIONDIR/$SESSION/env"
    echo "${SFLAG}${AFLAG}${FFLAG} $SESSION $(cat TTY) $(cat BAUD) $(cat USER) $(cat LOGGROUP)"
}

# list [-n] SESSION [SESSION...]
list() {
    local SESSION
    local SESSIONS
    local DISP
    DISP=0

    # flag to just output session names (otherwise display full info)
    if [ "$1" = '--names' -o "$1" = '-n' ] ; then
	unset DISP
	shift 1
    fi

    # list of session to display
    if [ "$1" ] ; then
	SESSIONS="$@"
    else
	SESSIONS=$(ls -1 "$SESSIONDIR" 2> /dev/null)
	[ "$SESSIONS" ] || return 1
    fi

    for SESSION in $SESSIONS ; do
	if ! is_session "$SESSION" ; then
	    error "Session '$SESSION' not found." 1
	elif [ "$DISP" ] ; then
	    display_session "$SESSION"
	else
	    echo "$SESSION"
	fi
    done
}

# function called by the session service run script to actually start screen
# takes no arguments, since it is called with a "chpst -e"
mainrun() {
    check_tty "$TTY"
    check_user "$USER"
    check_group "$GROUP"
    check_group "$LOGGROUP"
    check_tty_rw "$USER" "$GROUP" "$TTY"
    chown "$USER:$LOGGROUP" ./socket || failure "Can not properly set ownership of socket."
    chmod 0640 ./socket || failure "Can not properly set permissions on socket."

    echo "starting screen session..."
    LOCKFILE=/var/lock/LCK..${TTY##/dev/}
    lockfile -r0 "$LOCKFILE"
    LOCK_EXIT_CODE=$?
    if [ "$LOCK_EXIT_CODE" != 0 ] ; then
        exit "$LOCK_EXIT_CODE"
    fi
    chmod u+w "$LOCKFILE"
    printf '%9s\n' $$ >> "$LOCKFILE"
    exec chpst -u "$USER:$GROUP" /usr/bin/screen -D -m -L -c "$SCREENRC" -s /bin/false -S "cereal:$SESSION" -t "$SESSION" "$TTY" "$BAUD"
}

# function called by the session service finish script to remove the
# serial device lockfile
# takes no arguments, since it is called with a "chpst -e"
remove_lock() {
    LOCKFILE=/var/lock/LCK..${TTY##/dev/}
    rm -r "$LOCKFILE"
}
