# $Id: subroutines,v 1.97 2002/04/16 09:31:54 lange Exp $
#*********************************************************************
#
# subroutines -- usefull subroutines for FAI
#
# This script is part of FAI (Fully Automatic Installation)
# (c) 2000-2002 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
    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
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
islinux() {

    # test is we are running on Linux
#    [ X$OS_TYPE = X ] && OS_TYPE=`uname -s | tr '[A-Z]' '[a-z]'
    if [ X$OS_TYPE = Xlinux ]; then
       return 0
    else
       return 1
    fi
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ifclass() {

    # test, if a class is defined
    local cl
    for cl in $classes; do
	[ x$cl = x$1 ] && return 0
    done
    return 1
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
create_resolv_conf() {

    # create a resolv.conf using the DHCP or BOOTP information
    if [ "$DNSSRVS" ]; then
	[ "$DOMAIN" ] && echo "domain $DOMAIN" >/tmp/etc/resolv.conf
	for dnshost in $DNSSRVS ; do
	    echo "nameserver $dnshost" >>/tmp/etc/resolv.conf
	done
    fi
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
list_disks() {

    # print only every second entry; used by disk_info
    local i ent list
    list="$@"
    i=0

    for ent in $list; do
	if [ $i -eq 0 ]; then
	    echo $ent
	    i=1
	else
	    i=0
	fi
    done
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
mount_local_disks() {

    # try to mount all local disk partitions
    local mountoption=ro
    local disk partition partitions
    fstabcount=0
    [ "$1" = "rw" ] && mountoption=$1

    for disk in $disklist; do
	partitions=`LC_ALL=C file -s /dev/$disk?* | \
	    egrep -v " empty$|  data$| extended partition table" | \
	    perl -ne 'print "$1\n" if m#^/dev/(\S+):\s#'`
	for partition in $partitions; do
	    mkdir -p $FAI_ROOT/$partition
	    mount -o $mountoption /dev/$partition $FAI_ROOT/$partition
	    # \ && echo $partition mounted successfully
	    if [ -f $FAI_ROOT/$partition/etc/$fstab ]; then
		echo "/etc/$fstab found in $partition"
		fstabpart=$partition   # used in fstab_mount
		fstablist="$fstablist $partition"
		fstabcount=$((fstabcount+1))
	    fi
	done
    done
    mount | grep $FAI_ROOT
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
umount_local_disks() {

    local part
    test -d $FAI_ROOT || return
    for part in `grep $FAI_ROOT /proc/mounts | cut -d ' ' -f 2| sort -r`; do
	umount $part
    done
    test -d $FAI_ROOT/ida && rmdir $FAI_ROOT/ida/*
    test -d $FAI_ROOT/rd && rmdir $FAI_ROOT/rd/*
    rmdir $FAI_ROOT/*
    umount $FAI_ROOT
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
rwmount() {

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

    if [ $fstabcount -eq 1 ]; then
        # mount the root partition; then mount the rest according to fstab found
	umount_local_disks
	mount -o ro /dev/$fstabpart $FAI_ROOT
	mount2dir $FAI_ROOT $FAI_ROOT/etc/$fstab
	df
    fi
    [ $fstabcount -eq 0 ] && echo "No /etc/$fstab found"
    [ $fstabcount -ge 2 ] && echo -n "Found multiple /etc/$fstab files in : $fstablist.\nUse mount2dir for mounting."
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
save_log_local() {

    # save log files on local disk
    local logbase=$FAI_ROOT/var/log/fai
    local thislog=$logbase/$HOSTNAME/$FAI_ACTION-$RUNDATE
    find $LOGDIR -size 0 | xargs -r rm
    mkdir -p $thislog
    cp -p $LOGDIR/* $thislog
    ln -sf $HOSTNAME $logbase/localhost
    ln -sf $FAI_ACTION-$RUNDATE $logbase/$HOSTNAME/last-$FAI_ACTION
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
save_log_remote() {

    # save log files to $LOGUSER/$HOSTNAME/.. on $SERVER
    # also create a link last-$FAI_ACTION the the directory of the
    # last action. The name of the log directory contains date and
    # time of the action performed
    [ "$LOGUSER" ] || return

    echo "Saving log files remote to $LOGUSER@$SERVER $HOSTNAME/$FAI_ACTION-$RUNDATE"
    local thislog=$HOSTNAME/$FAI_ACTION-$RUNDATE
    find $LOGDIR -size 0 | xargs -r rm
    $FAI_REMOTESH -l $LOGUSER $SERVER "mkdir -p $thislog; cd $HOSTNAME; \
        rm -f last-$FAI_ACTION; ln -sf $FAI_ACTION-$RUNDATE last-$FAI_ACTION"
    $FAI_REMOTECP -p $LOGDIR/* $LOGUSER@$SERVER:$thislog
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
save_dmesg() {

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

    # wait for running (background) jobs to finish (eg. 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() {

    local taskname=$1
    if [ -f $LOGDIR/skip.$taskname ]; then
        # skip whole task and hook
	rm $LOGDIR/skip.$taskname
	[ "$verbose" ] && echo "Skiping task_$taskname"
    else
	echo "Calling task_$taskname"
	call_hook $taskname
	task_$taskname
    fi
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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 "Call hook: $hook.$cl"
	    # execute the hook
	    $hfile $dflag
	    # is that really good ? Makes sense, if hostname would
	    # be the first hook
	    # execute only one hook if return value is 42
	    # [ $? -eq 42 ] && return
	fi
	if [ -x $hfile.source ]; then
	    echo "Source hook: $hook.$cl.source"
	    # source this hook
	    . $hfile.source $dflag
	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
       [ "$debug" ] && echo "Warning $0: \$FAI_LOCATION not defined."
       get_fai_cvs
    else
        mount $romountopt $FAI_LOCATION $FAI &&
	    echo "$FAI mounted from $FAI_LOCATION"
    fi
    # source user specific subroutines
    [ -f $FAI/hooks/subroutines ] && . $FAI/hooks/subroutines
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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=""
	echo "Checking out CVS"
	[ -n "$FAI_CVSTAG" ] && TAG="-r $FAI_CVSTAG"
        export FAI_CONFIG_AREA=$FAI
        export FAI=/tmp/$(basename $FAI_CONFIG_AREA)
        
	[ "$debug" ] && echo "\$FAI now points to $FAI"
        
	if [ -d "$FAI_CONFIG_AREA" ] ; then
	   echo "Config found at $FAI_CONFIG_AREA: Copying"
	   cp -a $FAI_CONFIG_AREA $FAI 
	fi
	cd /tmp
	cvs -q -d"$FAI_CVSROOT" co -P -d $(basename "$FAI") \
	  $TAG $FAI_CVSMODULE > ${THISLOG}/cvs.log
    else
	echo "Warning $0: Neither \$FAI_LOCATION nor \$FAI_CVSROOT are defined."
    fi
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
define_fai_flags() {

    # FAI_FLAGS are now comma separated, define all flags
    FAI_FLAGS=`echo $FAI_FLAGS | sed -e 's/,/ /g'` 
    for flag in $FAI_FLAGS; do
	# define this flag as 1
	eval "$flag=1"
	[ "$verbose" ] && echo "FAI_FLAGS: $flag=1"
    done
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
task_setup() {

    local flag
    # set the system time and date using rdate or/and ntpdate
    [ "$TIMESRVS_1" ] && rdate $TIMESRVS_1
    [ "$NTPSRVS_1" ]  && ntpdate -b -v $NTPSRVS

    define_fai_flags

    DNSDOMAIN=$DOMAIN     # cfengine 1.5.3 can't use $DOMAIN
    devnull=/dev/null
    [ "$debug" ] && devnull=/dev/console

#    not yet used, maybe use a hook for that
#    if [ "$FAI_EXTRA_MOUNT" ]; then
#	mount $romountopt -n $FAI_EXTRA_MOUNT && echo "$FAI_EXTRA_MOUNT mounted"
#    fi

    [ "$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
    disk_info # get and define all information of local disks

    # when did FAI start, using localtime
    RUNDATE=$(date +'%Y%m%d_%H%M%S')

    cat >> $rcsfaivar <<-EOM
	RUNDATE=$RUNDATE
	FAI_ACTION=$FAI_ACTION
	LOGDIR=$LOGDIR
EOM
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
task_action() {

    echo "FAI_ACTION: $FAI_ACTION"
    case $FAI_ACTION in

	install)
	    echo Performing FAI installation. All data may be overwritten !
	    task install
	    ;;
	sysinfo)
	    echo Showing system information.
	    task sysinfo
	    die Now you have a shell.
	    ;;
	backup)
	    echo Doing backup of multiple partitions.
	    task backup
	    ;;
	*)
	    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."
		task_faiend
	    fi
	    ;;
	esac
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
task_defclass() {

    local newclasses addclasses class f

    cd $FAI/class || die "No directory $FAI/class found. No configuration in $FAI_LOCATION on the install server."
    echo "Defining classes"

    [ -f $HOSTNAME ] && newclasses=`grep -v '^#' $HOSTNAME`
    [ "$debug" ] && echo "newclasses= $newclasses"
    classes="DEFAULT $newclasses"

    # alphabetical sort is important
    for f in `ls S[0-9]*.{sh,pl,source}` ; do
	if [ -x $f -a -f $f ]; then
	[ "$verbose" ] && echo "Executing $f"
	case $f in
	    *.pl) newclasses=`perl $f </dev/null | grep -v '^#'` ;;
	    *.sh) newclasses=`sh $f </dev/null | grep -v '^#'` ;;
	    *.source) . $f ;; # this script can define $newclasses !
	esac
	[ "$debug" ] && echo "newclasses= $newclasses"
	classes="$classes $newclasses"
	fi
    done

    # scripts can also write additional classes to a file, if they
    # can't print them to stdout. Now read this file and define the classes
    [ -f $LOGDIR/additional-classes ] && addclasses=`grep -v '^#' $LOGDIR/additional-classes`
    [ "$debug" ] && echo "additonal classes: $addclasses"

    # now add the hostname (the only class in lowercase) and LAST to
    # the list of classes
    classes="$classes $addclasses $HOSTNAME LAST"

    # also define all classes in reverse order ($revclasses)
    for class in $classes ; do
	echo $class >> $LOGDIR/FAI_CLASSES
	revclasses="$class $revclasses"
    done

    # 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() {

    local class
    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
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
task_mountdisks() {

    [ ! -f $LOGDIR/$fstab ] && die "No $LOGDIR/$fstab created."
    mount2dir $FAI_ROOT $LOGDIR/$fstab
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
task_configure() {

    # execute all scripts that match the name of a class,
    # if class is a directory, execute all $class/S[0-9]* scripts in it
    local class f

    cd $FAI/scripts
    for class in $classes ; do
	[ -x $class -a -f $class ] && do_script $class
	if [ -d $class ]; then
	   for f in `ls $class/S[0-9]*` ; do
	       [ -x $f -a -f $f ] && do_script $f
	   done
	fi
    done
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
do_script() {

    # execute scripts and save their outpu in log files
    # cfengine, shell, perl and expect scripts are known types
    local shelldebug file filetype

    file=$1

    cd $FAI/scripts
    filetype=`file $file`
    shelldebug=
    case $filetype in
	*"Bourne shell script"*)
	    [ "$debug" ] && shelldebug="sh -x" ;;
	*"Bourne-Again shell script"*)
	    [ "$debug" ] && shelldebug="bash -x" ;;
    esac

    case $filetype in

	*"executable shell script"*|*"/bash script"*|*"Bourne shell script"*|*"Bourne-Again shell script"*)
	    echo "Executing $shelldebug shell: $file"
	    echo "=====   shell: $file   =====" >> $LOGDIR/shell.log 2>&1
	    $shelldebug ./$file >> $LOGDIR/shell.log 2>&1
	;;

	*"cfengine script"*)
	    echo "Executing cfengine: $file"
	    echo "=====   cfengine: $file   =====" >> $LOGDIR/cfengine.log 2>&1
	    ./$file --no-lock -v -f $file -D${cfclasses} >> $LOGDIR/cfengine.log 2>&1
	;;

	*"perl script"*)
	    echo "Executing perl: $file"
	    echo "=====   perl: $file   =====" >> $LOGDIR/perl.log 2>&1
	    ./$file >> $LOGDIR/perl.log 2>&1
	;;

	*"expect script"*)
	    echo "Executing expect: $file"
	    echo "=====   expect: $file   =====" >> $LOGDIR/expect.log 2>&1
	    ./$file >> $LOGDIR/expect.log 2>&1
	;;

	*) echo "File $file has unsupported type $filetype." ;;
    esac
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
task_savelog() {

    save_log_local
    save_log_remote
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
task_faiend() {

    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"
    cd /
    sync
    umount -ar
    exec reboot -dfi
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
task_backup() {

# NOT YET USED. IT'S ONLY AN IDEA

# FAI_BACKUP_LIST contains backup options
# proposed format: "hda1 hda3 hda12 sda2 '0 0 1 1'"
#                  list of devices  list of backup levels
# this subroutine is not yet tested, not yet used
# It's only an idea how a backup could be made
    die "Task backup not yet used. But you can use the hook backup."
    local partition
    mount_local_disks
    for partition in $SAVE_DEVICES ; do
	# tar cf - $FAI_ROOT/$partition | $FAI_REMOTESH -l $LOGUSER $SERVER dd if= of=$HOSTNAME.$partition.tar
	dump 0f $LOGUSER@$SERVER:backup/$HOSTNAME/$partition.dmp $FAI_ROOT/$partition
    done
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
task_install() {

    > $stamp

    save_dmesg
    load_keymap_consolechars

    task partition
    task mountdisks
    task extrbase
    task mirror
    task updatebase
    task instsoft
    task configure
    task finish
    date
    echo -e "FAI finished.\a"
    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."
	die "Please look at the log files in $LOGDIR for errors."
    fi
    task faiend
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
