#! /bin/bash

# $Id: subroutines 3027 2005-11-10 22:37:16Z lange $
#*********************************************************************
#
# subroutines -- useful subroutines for FAI
#
# This script is part of FAI (Fully Automatic Installation)
# (c) 2000-2005 by Thomas Lange, lange@informatik.uni-koeln.de
# Universitaet zu Koeln
#
#*********************************************************************
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
# 
# A copy of the GNU General Public License is available as
# `/usr/share/common-licences/GPL' in the Debian GNU/Linux distribution
# or on the World Wide Web at http://www.gnu.org/copyleft/gpl.html.  You
# can also obtain it by writing to the Free Software Foundation, Inc.,
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#*********************************************************************

# source this file, then you have these function available in the shell

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
die() {

    # echo comment and exit installation
    task_savelog
    echo "$@"
    exec bash
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
defnop() {

    # define given list of subroutine names as dummy function;
    # this will fake unknown commands

    local name
    for name in "$@";do
        eval "$name () { :;}"
    done
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ifclass() {

    [ "$debug" ] && echo "Test if class $1 is in $classes"
    # test if a class is defined
    local cl
    local ret=1

    for cl in $classes; do
	[ x$cl = x$1 ] && ret=0 && break
    done
    [ "$debug" ] && echo "ifclass returns $ret"
    return $ret
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
rwmount() {

    # remount partition read/write
    mount -o rw,remount $1
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
save_dmesg() {

    dmesg > $LOGDIR/dmesg.log
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
wait_for_jobs() {

    # can be an extern script
    # wait for running (background) jobs to finish (e.g. update-auctex-elisp)
    local i=0
    while jobsrunning; do
	[ $(($i % 3)) -eq 0 ] && echo "Waiting for background jobs to finish."
	i=$(($i+1))
	sleep 10
    done
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
task() {

    # hooks are called before a task is called
    # if a task is skipped, also its hooks are skipped
    # a hook can set the flag, so the accociated task is skipped

    local taskname=$1

    [ -f $LOGDIR/skip.$taskname ] || call_hook $taskname

    if [ -f $LOGDIR/skip.$taskname ]; then
        # skip task
	rm $LOGDIR/skip.$taskname
	[ "$verbose" ] && echo "Skiping task_$taskname"
	sndmon "TASKSKIP $taskname"
    else
	echo "Calling task_$taskname"
	sndmon "TASKBEGIN $taskname"
	terror=0   # task can set this variable to indicate an error
	task_$taskname
	sndmon "TASKEND $taskname $terror"
    fi
    # since the subroutine is not needed any more, we can undefine it
    unset task_$taskname
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
call_hook() {

    local hook=$1
    local cl dflag hfile
    [ "$debug" ] && dflag="-d"

    for cl in $classes; do
	hfile=$FAI/hooks/$hook.$cl
	if [ -x $hfile ]; then
	    echo "Calling hook: $hook.$cl"
	    sndmon "HOOK $hook.$cl"
	    # execute the hook
	    $hfile $dflag
	    check_status $hook.$cl $?
	fi
	if [ -x $hfile.source ]; then
	    echo "Source hook: $hook.$cl.source"
	    sndmon "HOOK $hook.$cl.source"
            # source this hook
	    . $hfile.source $dflag
	    check_status $hook.$cl.source $?
	fi
     done
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
skiptask() {

    # mark all given tasks, so they will be skipped
    local tasklist="$@"
    local task

    for task in $tasklist; do
	> $LOGDIR/skip.$task
    done
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
get_fai_dir() {

    # get /fai directory; mount it or get it from a cvs repository
    if [ -z "$FAI_LOCATION" ]; then
       get_fai_cvs
    else
        mount $romountopt $FAI_LOCATION $FAI &&
	    echo "Configuration space $FAI mounted from $FAI_LOCATION"
    fi
    ln -s $FAI $rundir/current_config
    if [ ! -d $FAI/class ]; then
	echo "WARNING: directory $FAI/class not found."
    fi
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
get_fai_cvs() {

    # subroutine which gets $FAI (/fai) configuration directory from
    # a cvs repository. You can redefine this subroutine if you need
    # access via ftp, http, or from a database

    if [ "$FAI_CVSROOT" ] ; then
        local TAG=""
	[ -n "$FAI_CVSTAG" ] && TAG="-r $FAI_CVSTAG"
        export FAI_CONFIG_AREA=$FAI_ROOT$FAI
        export FAI=$(mktemp -t -d fai-config.XXXXXX)
        
	[ "$debug" ] && echo "\$FAI now points to $FAI"
        
	if [ -d "$FAI_CONFIG_AREA/CVS" -a -z "$FORCE" ] ; then
	   echo "Config found at $FAI_CONFIG_AREA: Copying"
	   cp -a $FAI_CONFIG_AREA/. $FAI
	   echo "Updating CVS"
	   cd $FAI
           cvs -q -d"$FAI_CVSROOT" up -P $TAG -d -C > $LOGDIR/cvs.log
	else 
	   echo "Checking out CVS"
	   cd /tmp
	   cvs -q -d"$FAI_CVSROOT" co -P -d $(basename "$FAI") \
	     $TAG $FAI_CVSMODULE > $LOGDIR/cvs.log
	fi
    else
	echo "Warning $0: Neither \$FAI_LOCATION nor \$FAI_CVSROOT are defined."
    fi
    cd /
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
define_fai_flags() {

    local flag
    # FAI_FLAGS are comma separated, define all flags
    FAI_FLAGS=${FAI_FLAGS//,/ }
	[ "$verbose" ] && echo "FAI_FLAGS: $FAI_FLAGS"
    for flag in $FAI_FLAGS; do
	# define this flag as 1
	eval "$flag=1"
    done
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
task_setup() {

    # source user specific subroutines
    [ -f $FAI/hooks/subroutines ] && . $FAI/hooks/subroutines
    if [ $DO_INIT_TASKS -eq 1 ]; then
        # set the system time and date using rdate or/and ntpdate
        [ "$TIMESRVS_1" ] && rdate $TIMESRVS_1
        [ "$NTPSRVS_1" ]  && ntpdate -b -v $NTPSRVS
    fi

    define_fai_flags
    DNSDOMAIN=$DOMAIN     # cfengine 1.5.3 can't use $DOMAIN

    if [ $DO_INIT_TASKS -eq 1 ] ; then
        [ "$createvt" ] && {
	    # create two virtual terminals; acces via alt-F2 and alt-F3
	    echo "Press ctrl-c to interrupt FAI and to get a shell"
	    openvt -c2 /bin/bash ; openvt -c3 /bin/bash
	    trap 'echo "You can reboot with faireboot";bash' INT QUIT
        }
    
        # start secure shell daemon for remote access
	[ "$sshd" -a -x /usr/sbin/sshd ] && /usr/sbin/sshd
    fi

    # when did FAI start, using localtime
    FAI_RUNDATE=$(date +'%Y%m%d_%H%M%S')
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
task_action() {

    if [ -z "$FAI_ACTION" ]; then
	echo "No action in \$FAI_ACTION defined."
	sndmon "TASKERROR action 21"
	task_faiend
	exit 
    fi
    echo "FAI_ACTION: $FAI_ACTION"
    case $FAI_ACTION in

	install)
	    if [ $DO_INIT_TASKS -eq 0 ]; then
		echo "Cowardly refusing to run a FAI installation on a running system."
		return
	    fi
	    echo Performing FAI installation. All data may be overwritten!
	    echo -ne "\a"; sleep 1
	    echo -ne "\a"; sleep 1
	    echo  -e "\a"; sleep 5
	    task install
	    task faiend
	    ;;
	softupdate)
	    echo Performing FAI system update. All data may be overwritten!
	    task softupdate
	    ;;
	sysinfo)
	    echo Showing system information.
	    task sysinfo
	    task_faiend
	    die Now you have a shell.
	    ;;
	*)
	    if [ -f $FAI/hooks/$FAI_ACTION ]; then
		echo "Calling user defined action: $FAI_ACTION"
		$FAI/hooks/$FAI_ACTION
	    else
		echo "ERROR: User defined action $FAI/hooks/$FAI_ACTION not found."
		sndmon "TASKERROR action 22"
		task_faiend
	    fi
	    ;;
	esac
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
task_defclass() {

    if [ ! -d $FAI/class ]; then
	sndmon "TASKERROR defclass 21"
	echo "Directory $FAI/class not found. Following subdirectories are found:"
	find $FAI -type d -maxdepth 1 -printf "%p\n"
	die "Aborting."
    fi

    # new script for defining classes; variables imported: $LOGDIR, $verbose, $debug
    if [ $renewclass -eq 1 ]; then
	# reevaluate new list of classes
	fai-class -T $FAI/class $LOGDIR/FAI_CLASSES
	classes=$(< $LOGDIR/FAI_CLASSES)
    else
	# use classes defined at installation time
	if [ ! -f /var/log/fai/FAI_CLASSES ]; then
	    die "Try to read classes from /var/log/fai/FAI_CLASSES. Failed. Aborting."
	fi
	classes=$(< /var/log/fai/FAI_CLASSES)
    fi

    # define classes as: a.b.c.d for cfengine -D
    # this doesn't work without echo
    cfclasses=$(echo $classes)
    cfclasses=${cfclasses// /.}
    [ "$debug" ] && echo "cfclasses: $cfclasses"
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
task_defvar() {

    cd $FAI/class
    for class in $classes ; do
	if [ -f $class.var ]; then
	    [ "$verbose" ] && echo "Executing $class.var"
	    [ "$debug" ] && set -vx
	    . $class.var </dev/null
	    [ "$debug" ] && set +vx
	fi
    done

    # /fai/class/S* scripts or hooks can write variable definitions
    # to additonal.var. now source these definitions
    [ "$debug" ] && set -vx
    [ -f $LOGDIR/additional.var ] && . $LOGDIR/additional.var
    [ "$debug" ] && set +vx
    unset class
    # now all variables are defined. Dump them to variables.sh
    set | perl -ne 'print if /^\w\w+=/' | egrep -v "^BASH_VERSINFO|^EUID|^PPID|^SHELLOPTS|^UID|^rootpw|^HOME|^PWD" > $LOGDIR/variables.sh
    cd /
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
task_mountdisks() {

    [ ! -f $LOGDIR/$fstab ] && die "No $LOGDIR/$fstab created."
    # mount swap space
    local sd
    for sd in $SWAPLIST; do
	swapon $sd && [ "$verbose" ] && echo "Enable swap device $sd"
    done
    mount2dir $FAI_ROOT $LOGDIR/$fstab
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
task_configure() {

    fai-do-scripts $FAI/scripts
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
task_savelog() {

    [ -d $target/var/log/fai ] || mkdir -p $target/var/log/fai
    [ -d $target/var/log/fai ] && fai-savelog -l
    cd $LOGDIR && cp -p FAI_CLASSES variables.sh $diskvar $target/var/log/fai
    fai-savelog -r
    cd /
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
task_faiend() {

    [ $DO_INIT_TASKS -eq 0 ] && exit 0
    wait_for_jobs
    echo "Press <RETURN> to reboot or ctrl-c to execute a shell"
    # reboot without prompting if FAI_FLAG reboot is set
    [ -z $reboot ] && read
    echo "Rebooting $HOSTNAME now"
    sndmon REBOOT
    cd /
    sync

    case $(uname -s) in
	Linux)
	    killall -q sshd
	    umount $target/proc
	    umount -ar
	    exec reboot -dfi
	    ;;
    esac
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
task_backup() {

    die "Task backup not yet used. But you can use the hook backup."
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
task_install() {

    > $stamp

    save_dmesg

    task partition
    task mountdisks
    task extrbase
    task mirror
    task debconf
    task prepareapt
    task updatebase
    task instsoft
    task configure
    task finish
    task chboot

    rm -f $stamp
    # save again, because new messages could be created
    save_dmesg
    task savelog

    if [ -f $stamp ]; then
	echo "Error while executing commands in subshell."
	echo "$stamp was not removed."
	sndmon "TASKERROR install 21"
	die "Please look at the log files in $LOGDIR for errors."
    fi
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
task_softupdate() {

    > $stamp

    save_dmesg

    local start_seconds=$(cut -d . -f 1 /proc/uptime)
    task mirror
    task debconf
    task prepareapt
    task updatebase
    task instsoft
    task configure
    date
    echo "The update took $[$(cut -d . -f 1 /proc/uptime)-$start_seconds] seconds."

    rm -f $stamp
    # save again, because new messages could be created
    save_dmesg
    task savelog

    if [ -f $stamp ]; then
	echo "Error while executing commands in subshell."
	echo "$stamp was not removed."
	sndmon "TASKERROR softupdate 21"
	die "Please look at the log files in $LOGDIR for errors."
    fi
    umount $FAI_ROOT/fai
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

catnc() {
    # cat but no comment lines
    egrep -v "^#" $@
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
