#!/bin/tcsh -f
# JLdL 08Dec13.
#
# Copyright (C) 2005-2013 by Jorge L. deLyra <delyra@fma.if.usp.br>.
# This program may be copied and/or distributed freely. See the
# _ terms and conditions in /usr/share/doc/<package>/copyright.
#
# This program is meant for making the directory structure of
# _ a new remote-boot node within the cluster filesystems.
#
# Note: this script installs the kernel links in the tftpboot
# _ directory; it expects the corresponding links within the
# _ boot directory of the nodes to have names structured as
# _ vmlinuz-n<node_number>; it also assumes that the tftpboot
# _ directory is a subdirectory of the cluster root.
#
# Store the name this script was called with.
set name = `basename $0`
#
# Initialize variables for the configuration file.
set conflag = 0
set confile = "/etc/cluster.conf"
#
# Initialize the variables for the source and target node directories.
set source = ""
set target = ""
#
# Initialize a variable for the list of cluster filesystems.
set clfsdirs = ""
#
# We need a variable with the value '$'.
set dollar = '$'
#
# Process the command-line arguments.
foreach cla ( $* )
    #
    # Detect options.
    if ( "`echo -n $cla | cut -c 1`" == "-" ) then
	#
	# If we got here with the argument flag up, there is an error.
	if ( $conflag == 1 ) then
	    echo "${name}: ERROR: option -C requires an argument"
	    exit 1
	endif
	#
	# Now process the options.
	switch ( $cla )
	case "-h":
	case "--help":
	    #
	    # Print a usage message.
	    echo "usage: $name [-C <config>] [<source>[<target>]] [<dir> <dir> ...|all]"
	    echo "       -C: use alternate configuration file <config>"
	    echo "       clone the system directory structure for a new node;"
	    echo "       '<source>' is the name of the source node directory;"
	    echo "       '<target>' is the name of the target node directory;"
	    echo "       each '<dir>' must be the mount point of a cluster"
	    echo "       filesystem; you may use the keyword 'all' to do"
	    echo "       all of them; to get all the details run the"
	    echo "       command 'man $name'"
	    exit 0
	    breaksw
	case "-C":
	case "--Config-file":
	    #
	    # Raise the flag.
	    set conflag = 1
	    breaksw
	default:
	    #
	    # Print an error message.
	    echo "${name}: ERROR: unknown option $cla; try -h to get help"
	    exit 1
	    breaksw
	endsw
    #
    # Process non-option arguments.
    else
	#
	# Get the arguments of options.
	if ( $conflag == 1 ) then
	    #
	    # Set the configuration file.
	    set confile = $cla
	    #
	    # Lower the flag.
	    set conflag = 0
	#
	# Detect arguments consisting of digits and assume that
	# _ they are the source and target node directories.
	else if ( "`echo -n $cla | egrep '^[0-9]+$dollar'`" != "" ) then
	    if ( "$source" == "" ) then
		set source = $cla
	    else if ( "$target" == "" ) then
		set target = $cla
	    else
		#
		# Print an error message.
		echo "${name}: ERROR: too many node directory arguments"
		exit 1
	    endif
	else
	    #
	    # Get and accumulate the list of filesystems from the command line
	    # _ arguments; arguments are filesystem directories to act on.
	    set clfsdirs = ( $clfsdirs $cla )
	endif
    endif
end
#
# If we got here with the argument flag up, there is an error.
if ( $conflag == 1 ) then
    echo "${name}: ERROR: option -C requires an argument"
    exit 1
endif
#
# The default value of the list of cluster filesystems
# _ is the current working directory.
if ( "$clfsdirs" == "" ) then
    set clfsdirs = .
endif
#
# Source the configuration file; this must define the following variables:
# _ nick_name; virt_node; cluster_root; mount_points; node_address; lan_domain.
if ( -r $confile ) then
    source $confile
else
    echo "${name}: ERROR: cannot read configuration file $confile"
    exit 1
endif
#
# Do some simple error detection: check that the necessary
# _ variables are defined in the configuration file.
if ( ! $?nick_name ) then
    echo "${name}: ERROR: nick_name not defined in configuration file"
    exit 1
endif
if ( ! $?virt_node ) then
    echo "${name}: ERROR: virt_node not defined in configuration file"
    exit 1
endif
if ( ! $?cluster_root ) then
    echo "${name}: ERROR: cluster_root not defined in configuration file"
    exit 1
endif
if ( ! $?mount_points ) then
    echo "${name}: ERROR: mount_points not defined in configuration file"
    exit 1
endif
if ( ! $?node_address ) then
    echo "${name}: ERROR: node_address not defined in configuration file"
    exit 1
endif
if ( ! $?lan_domain ) then
    echo "${name}: ERROR: lan_domain not defined in configuration file"
    exit 1
endif
#
# Give the default value to the optional configuration variable.
if ( ! $?address_offset ) then
    set address_offset = 0
endif
#
# Get the number of digits in the node numbers.
set ndig = `echo -n $virt_node | wc -c`
#
# Build the regular expression for the node directories.
set node_digs = "[0-9]"
set idig = 1
while ( $idig < $ndig )
    set node_digs = "${node_digs}[0-9]"
    @ idig = $idig + 1
end
set node_targ = "^$node_digs"'$'
#
# Make a list including only the mount points that contain node
# _ directories; test them all, including the cluster root.
\ls -1d $cluster_root/$node_digs >& /dev/null
if ( $status == 0 ) then
    set mpts = $cluster_root
else
    echo "${name}: ERROR: no node directories within $cluster_root"
    exit 1
endif
foreach mpt ( $mount_points )
    \ls -1d $cluster_root/$mpt/$node_digs >& /dev/null
    if ( $status == 0 ) then
	set mpts = ( $mpts $cluster_root/$mpt )
    else
	if ( $mpt == tmp || $mpt == var ) then
	    echo "${name}: ERROR: no node directories within $cluster_root/$mpt"
	    exit 1
	else
	    echo "${name}: WARNING : no node directories within $cluster_root/$mpt"
	    echo "    NOTE: if you are using a completely shared filesystem,"
	    echo "          you may have some problems with a few packages."
	endif
    endif
end
#
# Map the keyword 'all' onto the appropriate set of node filesystems.
if ( "$clfsdirs" == "all" ) then
    set clfsdirs = ( $mpts )
endif
#
# Define a separator line.
set sep = "--------------------------------------------------------------------------------"
#
# Define the location of the library.
set libdir = /usr/lib/cluster
#
# Define the default source node.
set dsn = $virt_node
#
# Define the default target node.
set dtn = `echo -n $virt_node | tr "0" "9"`
#
# Check and acquire the source directory.
if ( "$source" == "" ) then
    echo -n "Enter source node [$dsn]: "
    set source = $<
    if ( "$source" == "" ) set source = $dsn
endif
#
# Check that the source is correct.
set scheck = `echo $source | grep "$node_targ"`
if ( "$scheck" == "" ) then
    echo "${name}: ERROR: source directory must consist of $ndig digits"
    exit 1
endif
#
# Check and acquire the target directory.
if ( "$target" == "" ) then
    echo -n "Enter target node [$dtn]: "
    set target = $<
    if ( "$target" == "" ) set target = $dtn
endif
#
# Check that the target is correct.
set tcheck = `echo $target | grep "$node_targ"`
if ( "$tcheck" == "" ) then
    echo "${name}: ERROR: target directory must consist of $ndig digits"
    exit 1
endif
#
# Loop over the filesystem arguments.
foreach clfsdir ( $clfsdirs )
    #
    # Go to the root of the filesystem.
    cd $clfsdir
    #
    # Print a separator.
    echo $sep
    echo "Working within the $cwd directory..."
    #
    # Do some simple error detection: check that this is being executed within
    # _ the mount-point of a cluster filesystem containing node directories.
    set clflag = 0
    foreach mpt ( $mpts )
	if ( "$cwd" == "$mpt" ) then
	    set clflag = 1
	endif
    end
    if ( "$clflag" == 0 ) then
	echo "${name}: ERROR: $cwd is not an appropriate cluster filesystem"
	exit 1
    endif
    #
    # Check the status of the source directory.
    if ( ! -d "$source" ) then
	echo "${name}: ERROR: source directory does not exist"
	exit 1
    endif
    #
    # Check the status of the target directory.
    if ( -d "$target" ) then
	echo "${name}: ERROR: target directory already exists"
	exit 1
    endif
    #
    # Call a central function to clone the node directory.
    $libdir/make-new-netboot-node.sub $cluster_root $libdir $source $target
    #
    # Do on the new node whatever configurations are necessary and possible.
    #
    # In the case of the tmp filesystem, just fix
    # _ the mode of the directory.
    if ( "$cwd" == $cluster_root/tmp ) then
	echo "Fixing the mode of the directory..."
	chmod 1777 $target
    #
    # In the case of the root filesystem, do
    # _ a series of configurations.
    else if ( "$cwd" == $cluster_root ) then
	#
	# Fix the node-specific kernel files.
	echo "Fixing the node-specific kernel files..."
	foreach sfile ( `cd $target/boot/ ; \ls -1 vmlinuz-n$node_digs*` )
	    set tfile = `echo -n $sfile | sed -e "s|n$source|n$target|g"`
	    \rm -f $target/boot/$sfile
	    \cp -p $source/boot/$sfile $target/boot/$tfile
	    #
	    # Make sure the tftpboot directory is there.
	    mkdir -p $cluster_root/tftpboot/
	    #
	    # Make the links to the tftpboot directory.
	    \ln -f $target/boot/$tfile $cluster_root/tftpboot/
	    echo "    boot/$tfile..."
	end
	#
	# Verify whether any Etherboot-NBI kernel images are present.
	set nbi = `cd $target/boot/ ; \ls -1 vmlinuz* | grep vmlinuz-nbi-n`
	#
	# Do the Etherboot-NBI files too, if they are present.
	if ( "$nbi" != "" ) then
	    foreach sfile ( `cd $target/boot/ ; \ls -1 vmlinuz-nbi-n$node_digs*` )
		set tfile = `echo -n $sfile | sed -e "s|n$source|n$target|g"`
		\rm -f $target/boot/$sfile
		\cp -p $source/boot/$sfile $target/boot/$tfile
		#
		# Make sure the tftpboot directory is there.
		mkdir -p $cluster_root/tftpboot/
		#
		# Make the links to the tftpboot directory.
		\ln -f $target/boot/$tfile $cluster_root/tftpboot/
		echo "    boot/$tfile..."
	    end
	endif
	#
	# Edit automatically a few files.
	echo "Editing a few files with sed:"
	#
	# ********* etc/hostname *********
	#
	set edtfile = etc/hostname
	echo "    $edtfile..."
	#
	# Remove the old file.
	rm -f $target/$edtfile
	#
	# Create the new file.
	cat $source/$edtfile | \
	    sed -e "s|$source|$target|g" \
		> $target/$edtfile
	#
	# ********** etc/fstab ***********
	#
	set edtfile = etc/fstab
	echo "    $edtfile..."
	#
	# Define a variable with the tab character.
	set tab = "`echo -n '\t'`"
	#
	# Remove the old file.
	rm -f $target/$edtfile
	#
	# Create the new file.
	cat $source/$edtfile | \
	    sed -e "s|/$source\([ $tab]\)|/$target\1|g" \
		> $target/$edtfile
	#
	# **** etc/network/interfaces ****
	#
	set edtfile = etc/network/interfaces
	echo "    $edtfile..."
	#
	# Extract the numbers from the base address.
	set bip0 = `echo -n $node_address | cut -d. -f4`
	set bip1 = `echo -n $node_address | cut -d. -f3`
	set bip2 = `echo -n $node_address | cut -d. -f2`
	set bip3 = `echo -n $node_address | cut -d. -f1`
	#
	# Calculate the address of the source node.
	if ( $source == $virt_node ) then
	    #
	    # For the virtual node there is no offset.
	    set sadd = $node_address
	else
	    #
	    # For all other nodes we must add the offset.
	    @ node = $source + $address_offset
	    #
	    # This is an addition in base 256.
	    @ rat0 = ( $node + $bip0 ) / 256
	    @ sip0 = ( $node + $bip0 ) - ( 256 * $rat0 )
	    @ rat1 = ( $rat0 + $bip1 ) / 256
	    @ sip1 = ( $rat0 + $bip1 ) - ( 256 * $rat1 )
	    @ rat2 = ( $rat1 + $bip2 ) / 256
	    @ sip2 = ( $rat1 + $bip2 ) - ( 256 * $rat2 )
	    @ rat3 = ( $rat2 + $bip3 ) / 256
	    @ sip3 = ( $rat2 + $bip3 ) - ( 256 * $rat3 )
	    set sadd = $sip3.$sip2.$sip1.$sip0
	endif
	#
	# Check whether the source-node file is correct.
	grep -q "address[ $tab]*$sadd" $source/$edtfile
	if ( $status != 0 ) then
	    echo "${name}: WARNING: the address in $source/$edtfile"
	    echo "    does not match the value of node_address in the"
	    echo "    configuration file; will NOT edit this file."
	else
	    #
	    # Calculate the address of the target node.
	    if ( $target == $virt_node ) then
		#
		# For the virtual node there is no offset.
		set tadd = $node_address
	    else
		#
		# For all other nodes we must add the offset.
		@ node = $target + $address_offset
		#
		# This is an addition in base 256.
		@ rat0 = ( $node + $bip0 ) / 256
		@ tip0 = ( $node + $bip0 ) - ( 256 * $rat0 )
		@ rat1 = ( $rat0 + $bip1 ) / 256
		@ tip1 = ( $rat0 + $bip1 ) - ( 256 * $rat1 )
		@ rat2 = ( $rat1 + $bip2 ) / 256
		@ tip2 = ( $rat1 + $bip2 ) - ( 256 * $rat2 )
		@ rat3 = ( $rat2 + $bip3 ) / 256
		@ tip3 = ( $rat2 + $bip3 ) - ( 256 * $rat3 )
		set tadd = $tip3.$tip2.$tip1.$tip0
	    endif
	    #
	    # Remove the old file.
	    rm -f $target/$edtfile
	    #
	    # Create the new file.
	    cat $source/$edtfile | \
		sed -e "s|address[ $tab]*$sadd|address $tadd|g" \
		    > $target/$edtfile
	endif
	#
	# ********** etc/hosts ***********
	#
	set edtfile = etc/hosts
	echo "    $edtfile..."
	#
	# Define the fully qualified domain name of the source node.
	set shost = $nick_name$source.$lan_domain
	#
	# Check whether the source-node file is correct.
	grep -q "^[ $tab]*${sadd}[ $tab]*$shost" $source/$edtfile
	if ( $status != 0 ) then
	    echo "${name}: WARNING: the address in $source/$edtfile"
	    echo "    does not match the value of node_address in the"
	    echo "    configuration file; will NOT edit this file."
	else
	    #
	    # Calculate the address of the target node.
	    if ( $target == $virt_node ) then
		#
		# For the virtual node there is no offset.
		set tadd = $node_address
	    else
		#
		# For all other nodes we must add the offset.
		@ node = $target + $address_offset
		#
		# This is an addition in base 256.
		@ rat0 = ( $node + $bip0 ) / 256
		@ tip0 = ( $node + $bip0 ) - ( 256 * $rat0 )
		@ rat1 = ( $rat0 + $bip1 ) / 256
		@ tip1 = ( $rat0 + $bip1 ) - ( 256 * $rat1 )
		@ rat2 = ( $rat1 + $bip2 ) / 256
		@ tip2 = ( $rat1 + $bip2 ) - ( 256 * $rat2 )
		@ rat3 = ( $rat2 + $bip3 ) / 256
		@ tip3 = ( $rat2 + $bip3 ) - ( 256 * $rat3 )
		set tadd = $tip3.$tip2.$tip1.$tip0
	    endif
	    #
	    # Define the fully qualified domain name of the target node.
	    set thost = $nick_name$target.$lan_domain
	    #
	    # Check that the line does not already exist in the target file,
	    # _ in order to avoid creating duplicated lines in it; this is
	    # _ to detect the case in which the hosts file is the same in
	    # _ all nodes and already contains lines for all the nodes.
	    grep -q "^[ $tab]*${tadd}[ $tab]*$thost" $target/$edtfile
	    if ( $status != 0 ) then
		#
		# Remove the old file.
		rm -f $target/$edtfile
		#
		# Create the new file.
		cat $source/$edtfile | \
		    sed -e "s|$sadd|$tadd|g" \
			-e "s|$nick_name$source|$nick_name$target|g" \
			> $target/$edtfile
	    endif
	endif
	echo "done."
    endif
    #
    # Go back to the original directory.
    cd -
end
#
# Print a final separator.
echo $sep
