#!/bin/bash

# this script is run by at startup on the nfs_root system, and runs the ka-deploy client 
# it also updates the 'step file' on the tftp server

# $Revision$
# $Author$
# $Date$
# $Header$
# $Id$
# $Log$
# Revision 1.4  2005/08/28 21:38:32  oblin
# ka support (initially from Antoine Ginies and Erwan Velu)
#
# Revision 1.1.2.5  2003/06/17 06:34:33  erwan
# Removing remaining dchp cache for KA
#
# Revision 1.1.2.4  2003/06/11 18:04:28  erwan
# Fixing mkreiserfs call
#
# Revision 1.1.2.3  2002/11/07 15:10:52  erwan
# SCSI support now activated
#
# Revision 1.1.2.2  2002/11/05 15:49:13  erwan
# added some files
#
# Revision 1.1.2.1  2002/11/05 11:16:54  erwan
# added ka tools in rescue
#
# Revision 1.6  2001/12/03 16:28:02  sderr
# Completely new install script
#
# Revision 1.5  2001/10/10 13:55:04  sderr
# Updates in documentation
#
# Revision 1.4  2001/06/29 09:31:45  sderr
# *** empty log message ***
#
# Revision 1.3  2001/05/31 08:51:43  sderr
# scripts/doc update to match new command-line syntax
#
# Revision 1.2  2001/05/03 12:34:41  sderr
# Added CVS Keywords to most files. Mostly useless.
#
# $State$

# This script is provided as an exmaple and should probably not be run as is


unset LANG
unset LANGUAGE

# needed for some for i in foo* loops
shopt -s nullglob

bash < /dev/tty2 >/dev/tty2 2>&1 &

# IDEA : maybe this option could be overriden by a kaopt= in the kernel command line ?
KA_SESSION_BASE="-s kainstall"

ka_call_num=0

inc_ka_session()
{
	(( ka_call_num++ ))
	cur_ka_session=$KA_SESSION_BASE$ka_call_num
}


# testing ? -- NOT FULLY IMPLEMENTED !!!!!!!!!!!!!!!!!!!!
#DONTWRITE=yes
DONTWRITE=no

# Let's find out what our IP is
ip=`/sbin/ifconfig | grep -v 127.0.0.1 | grep "inet addr" | sed 's/^.*inet addr:\([^ ]*\) .*$/\1/g' | head -n 1`

# the file tftpserver should contain the name of the .. tftpserver
server=`cat tftpserver`


# reverse a file
tac()
{
	awk '{ x[NR] = $0 } END { for (i = NR; i >= 1; i--) print x[i] }'
}

# run a command, hide its output and print OK if it suceeds, print FAILED and show the output otherwise.
runcom()
{
		echo -n "$1..." 1>&2
		shift;
		out=`"$@" 2>&1` 
		ret=$?
		if [ $ret -eq 0 ]; then
				echo $C_S"OK"$C_N 1>&2
		else
				echo $C_F"Failed"$C_N 1>&2
				echo $C_W"$out"$C_N 1>&2
		fi
		return $ret

}

# 5 4 3 2 1 zero ignition
countdown()
{
        t=$1
        while [ "$t" -ne 0 ]; do
                echo -n "$t "
                sleep 1
		# move the cursor back
		# I use now tr instead of sed because busybox's sed adds a non-wanted carriage return
		# busybox's tr does not seem to know about the [: :] stuff (?)
                echo -n "$t " | tr " 0-9" $'\x08'
		(( t-- ))
        done
	# backspace only moves the cursor back, so at this point there's still a "1" to erase
	if [ "$1" -ne 0 ] ;then
        	echo -n "  "$'\x08\x08'
	fi
}


int_shell()
{
	echo $C_H"starting an interactive shell"$C_N
	exec /bin/bash
}

fail()
{
		echo $*
		echo $C_F"--- The installation program FAILED ---"$C_N
		echo "Check your configuration -- try to read previous error messages"
		echo "This machine is going to reboot (Ctrl-S to block it) (Ctrl-C for an interactive shell)"
		trap int_shell SIGINT
		countdown 30
		do_reboot
}

do_reboot()
{
		reboot
		sleep 1234567 # do not continue the install script (/sbin/reboot does not block)
}


	
# ahem this LILO function should be fixed someday
# right now this function assumes there is a properly configured lilo on the duplicated linux system
do_lilo()
{		

	
chroot /mnt/disk << EOF
lilo
EOF


}		


run_chroot_command()
{		

	/usr/sbin/chroot /mnt/disk $*

}		


log()
{
	echo $* 1>&2
	echo $* >> /tmp/ginstlog
}

# version for the standard tftp client
std_tftp_put_file()
{
	remotef=$2
	localf=$1
	err=`echo put $localf $remotef | tftp $server 2>&1`
	err=`echo $err | grep Sent`
	if [ -z "$err" ]; then
		log tftp error: could not get/put file 
		return 1
	fi
	return 0
}

# version for the tftp client built in busybox
busybox_tftp_put_file()
{
	remotef=$2
	localf=$1
	err=`tftp -p -l $localf -r $remotef $server 2>&1`
	if [ $? -ne 0 ]; then
		log tftp error: could not get/put file $err
		return 1
	fi
	return 0
}


busybox_tftp_get_file()
{
	remotef=$1
	localf=$2
	err=`tftp -g -l $localf -r $remotef $server 2>&1`
	if [ $? -ne 0 ]; then
		log tftp error: could not get/put file $err
		return 1
	fi
	return 0
}


std_tftp_get_file()
{
	remotef=$1
	localf=$2
	err=`echo get $remotef $localf | tftp $server 2>&1`
	err=`echo $err | grep Received`
	if [ -z "$err" ]; then
		echo tftp error: could not get/put file 
		return 1
	fi
	return 0
}

tftp_get_file()
{
		busybox_tftp_get_file "$@"
}

tftp_put_file()
{
		busybox_tftp_put_file "$@"
}

# write a string ($2) in a remote file ($1)
tftp_put_in()
{
	echo "$2" > $temp
	err=`echo put $temp "$1" | tftp $server 2>&1`
	rm -f $temp
	err=`echo $err | grep Sent`
	if [ -z "$err" ]; then
		log tftp error: could not get/put file 
		return 1
	fi
	return 0
}

get_var_bis()
{
		while read a; do
			echo "$a" | grep -s -q "^ *#.*"
			if [ $? -eq 0 ]; then 
					continue 
			fi
			val=`echo "$a" | sed 's/[^"]*"\(.*[^\\]\)".*/\1/'`
			var=`echo "$a" | sed 's/[^"]*".*[^\\]" *\$\([^ ]*\)/\1/'`
			if [ "$var" = "$1" ]; then
					echo $val
					return 0
			fi
		done
		return 1
}

# fetch variable $2 from file $1
get_var()
{
		(cat $1; echo) | get_var_bis $2
}

# find the current step in the kernel command line
get_step()
{
	step=install
#	step=`cat /proc/cmdline | sed 's/.*kastep=\([^ ]*\).*/\1/'`
	if [ "$step" ]; then
		echo $step > /tmp/step
		return 0
	fi
	return 1
}


# write a new file on the tftp server
# this file is a pxelinux config file
# do this by getting the 'template file' and adding a DEFAULT at the beginning
set_step()
{
	step=$1
	
	
	runcom "Getting template file" tftp_get_file "ka/pxelinux.cfg/template" /tmp/template || return 1
	
	echo DEFAULT $step > /tmp/newcfg
	cat /tmp/template >> /tmp/newcfg
	
	runcom "Sending back new pxelinux config file" tftp_put_file /tmp/newcfg "ka/pxelinux.cfg/IP/$ip" || return 1
	
	return 0
}



# the mount_partition calls must be done in the same shell (NOT a subshell) because the global variable below has to be updated
# idea : maybe use a file instead of this variable (and since the tac function now exists, why not ?)
mounted_fs=""

# mount a partition UNDER /disk !!! (/disk is prepended to $2)
mount_partition()
{
	dev=$1
	point=$2
	
	echo -n "Mounting $C_H$1$C_N as /mnt/disk$C_H$point$C_N" 
	mkdir -p /mnt/disk$point
	test -d /mnt/disk$point || return 1
	runcom "..." mount $dev /mnt/disk/$point || return 1
	mounted_fs="$dev $mounted_fs"
	return 0
}

# umount all mounted partitions under /disk, in the reverse order
umount_partitions()
{
	for dev in $mounted_fs; do
		retries=0
		while ! runcom "Umounting $dev" umount $dev ; do
			sleep 3
			(( retries++ ))
			if [ $retries -gt 3 ]; then
				echo Failed too many times. giving up.
				break
			fi
		done
	done
}


# recreate excluded directories
# read stdin like this : u=rwx g=rwx o=rwx uid gid filename
recreate_dirs()
{
	while read line; do
		declare -a fields
		fields=( $line )
		file=/mnt/disk/${fields[5]}
#		echo $file
# note : it is possible that the directory exists already, if it was a mount point
# we need to set the permissions/users anyway
		mkdir -p $file
#		echo chmod ${fields[0]},${fields[1]},${fields[2]}  $file
		chmod ${fields[0]},${fields[1]},${fields[2]}  $file
		# argl !! chmod o+t does not work with busybox's chmod !
		# we have to handle it alone
		if echo ${fields[2]} | grep -q t; then
			chmod +t $file
		fi
		chown ${fields[3]}.${fields[4]} $file
	done
}

make_partitions()
{
	# we must be in the partfiles directory
	for file in partition_tab*; do
		drive=`echo $file | sed 's/partition_tab//'`
		cat $file | runcom "Writing partition table for $drive using sfdisk" /sbin/sfdisk /dev/$drive -uS --force || fail "error with sfdisk"
	done

	for file in fdisk_commands*; do
		drive=`echo $file | sed 's/fdisk_commands//'`
		runcom "Cleaning hard drive" dd if=/dev/zero of=/dev/$drive bs=1M count=5 || fail "Can t clean drive$drive"
		cat $file | runcom "Writing partition table for $drive using fdisk" fdisk /dev/$drive || fail "error with fdisk"
	done

}
checkDevEntries()
{ 
	if ! test -r /mnt/disk/dev/hda ; then
  		(cd /dev && tar c *) | (cd /mnt/disk/dev && tar x)
	fi
}

write_MBRs()
{
# we must be in the partfiles directory also
	for file in MBR*; do
		drive=`echo $file | sed 's/MBR//'`
		runcom "Writing new MBR for $drive" dd if=$file of=/dev/$drive bs=1 count=446
	done
}


# Colors
# Success
C_S=$'\033[1;32m'
# Failure
C_F=$'\033[1;31m'
# Warning
C_W=$'\033[1;33m'
# Normal
C_N=$'\033[0;39m'
# Hilight
C_H=$'\033[1;39m'


# Clear screen, fancy startup message.
echo $'\033'[2J$'\033'[H
echo "------| $C_H"Ka"$C_N |---- Install starting..."

temp=/tmp/ginst

# activate dma ? -- obsolete stuff I think		
# runcom "Setting HD optimizations" hdparm -c1 -d1 -K1 $HD	
		
delay=0

if ! runcom "Getting step name" get_step; then
		echo "Error: Could not get current step "
		fail
else
		step=`cat /tmp/step`
fi

echo Next Server is `cat /ka/tftpserver`

echo Current step for $ip is : \"$C_H$step$C_N\"

echo -n "Finding install type :  "
case $step in
	shell)
		echo No install, but interactive shell
		## drop the user to an interactive shell 
		exec /bin/bash
		;;
	install) 
		install_type=install
		nextstep=ready
		echo Install Linux
		;;
	test_install) 
		install_type=test_install
		nextstep=ready
		echo TEST TEST TEST
		countdown 10
		echo Install Linux TEST
		;;
esac


if [ -z "$install_type" ]; then
		echo FATAL : Could not recognize this step name
		echo "Aborting... "
		fail
fi



# receive the partition table, fstab, etc from the source node
mkdir /tmp/partfiles
inc_ka_session
echo Current session is $cur_ka_session
runcom "Receiving partitions information" /ka/ka-d-client -w $cur_ka_session -e "( cd /tmp/partfiles && tar xvf - )"	|| fail




cd /tmp/partfiles 
make_partitions


test -f /tmp/partfiles/streams || fail "Missing streams file"
first_stream=`cat /tmp/partfiles/streams | head -n 1`

if [ "$first_stream" = linux ]; then
	rcv_linux=yes
else
	rcv_linux=no
fi



#if we must receive a linux system, we need to format and mount the partitions
if [ $rcv_linux = yes ]; then	
	# format partitions
	format_partitions()
	{
		while read line; do
			declare -a fields
			fields=( $line )

			case ${fields[2]} in
				reiserfs )
					runcom "Formatting ${fields[0]} as reiserfs" mkreiserfs -f ${fields[0]} || fail
					;;
				jfs )
					runcom "Formatting ${fields[0]} as jfs" mkfs.jfs ${fields[0]} || fail
					;;
				
				xfs )
					runcom "Formatting ${fields[0]}	as xfs" mkfs.xfs -f ${fields[0]} || fail
					;;
				ext3 )
					runcom "Formatting ${fields[0]}	as ext3" mkfs.ext2 -j ${fields[0]} || fail
					;;
				ext2 )
					runcom "Formatting ${fields[0]} as ext2" mkfs.ext2 ${fields[0]} || fail
					;;
				swap ) 
					runcom "Formatting ${fields[0]} as swap" mkswap ${fields[0]} || fail
					;;
			esac
		done
	}

	format_partitions < /tmp/partfiles/pfstab	
		

	# mount the partitions

	mount_partitions()
	{
		while read line; do
			declare -a fields
			fields=( $line )

			case ${fields[2]} in
				reiserfs )
					mount_partition ${fields[0]} ${fields[1]} || fail
					;;
				xfs )
					mount_partition ${fields[0]} ${fields[1]} || fail
					;;
				jfs )
					mount_partition ${fields[0]} ${fields[1]} || fail
					;;
				ext3 )
					mount_partition ${fields[0]} ${fields[1]} || fail
					;;
				ext2 )
					mount_partition ${fields[0]} ${fields[1]} || fail
					;;
			esac
		done
	}

	# NOTE
	# I replaced cat truc | mount_partitions by mount_partitions < truc
	# because in the former case mount_partitions runs in a subshell and the $mounted_fs value is lost
	mount_partitions < /tmp/partfiles/pfstab

	echo ++++++++++++++++++++++++++
	mount
	echo ++++++++++++++++++++++++++

	delay=0
else
	delay=10
fi

if [ $DONTWRITE != yes ]; then
	for stream in `cat /tmp/partfiles/streams`; do
		if [ "$stream" = "linux" ]; then
			# partitions already formatted/mounted, just copy now
			# untar data from the master 'on the fly'
			echo -n "Linux copy is about to start "
			countdown $delay
			echo
			inc_ka_session
			/ka/ka-d-client -w $cur_ka_session -e "(cd /mnt/disk; tar --extract  --read-full-records --same-permissions --numeric-owner --sparse --file - ) 2>/dev/null" || fail

			runcom "Syncing disks" sync 
			echo Linux copy done.
			echo Creating excluded directories
			cat /tmp/partfiles/excluded | recreate_dirs
			#echo Setting up networking
			#/ka/setup_network.sh
			#delay=10
		else
			# maybe receive some raw partition dumps
			echo Raw copy of $stream is about to start
			countdown $delay
			inc_ka_session
			/ka/ka-d-client -w $cur_ka_session -e "dd of=$stream bs=65536" || fail
			delay=10
		fi
	done

	echo "Removing computing interfaces"
	rm -f /mnt/disk/etc/sysconfig/network-scripts/ifcfg-eth1 >/dev/null 2>&1

	echo "Removing duplicated dhcp cache"
	rm -f /mnt/disk/etc/dhcpc/* >/dev/null 2>&1

	echo "Writing modules.conf" 
	/usr/bin/perl /ka/gen_modules_conf.pl >/mnt/disk/etc/modules.conf

	echo "Writing modprobe.conf"
	chroot /mnt/disk/ /sbin/generate-modprobe.conf >/mnt/disk/etc/modprobe.conf
	
	echo "Running mkinitrd"
	/ka/make_initrd

	cd /tmp/partfiles
	write_MBRs

	
	if test -f /tmp/partfiles/command; then
		checkDevEntries
		command_to_run=`cat /tmp/partfiles/command`
		runcom "Running $command_to_run" run_chroot_command "$command_to_run"
	fi

else
	echo " I would run ka-deploy(s) and then lilo/mbr  "
	sleep 1
fi

# maybe there is a last dummy ka-deploy for synchronization
if test -f /tmp/partfiles/delay; then
	sleep 1
	inc_ka_session
	runcom "Waiting source node signal to end installation"	/ka/ka-d-client -w $cur_ka_session -e "cat" || fail
fi

umount_partitions

	
# Update the step file on the tftp server
#runcom 'Sending back new $step' set_step $nextstep || fail

echo -n Rebooting...
countdown 3
do_reboot