#!/bin/bash 
#
# updatediskless is a script to configure the diskless boot image.
#   It takes three parameters:
#     1. The name of the directory where the "root" directory of the diskless 
#        environment resides.
#     2. The kernel version of the kernel to be used on the diskless clients.
#     3. The destination directory for the image file and kernel.
#
#   This script can be run multiple times and should be run everytime you 
#   update the kernel on this share.
#
# Copyright (C) 2003 Daniel Walsh <dwalsh@redhat.com>
# Copyright (C) 2005 Jason Vas Dias <jvdias@redhat.com>
#
# 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.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
#
TOP_DIR=`pwd`
if [ $# != 3 ]; then
	echo Usage: `basename $0` disklessDir kernelVersion destinationDir 
	exit 1 
fi
ROOT=$1
VERSION=$2
DESTDIR=$3
IMAGEFILE=$DESTDIR/initrd.img
MNTPOINT=/tmp/$$
/bin/rm -rf $MNTPOINT
/bin/mkdir -p $MNTPOINT
KERNEL=$ROOT/boot/vmlinuz-$VERSION 
MODULES=$ROOT/lib/modules/$VERSION
INSTALLDIR=/usr/share/system-config-netboot/diskless
export LC_ALL=C
unalias cp >/dev/null 2>&1
umask 0022
if [ -e "$KERNEL" ]; then
    if [ ! -e "$MODULES"  ]; then
	echo could not find modules in $MODULES
	exit -1 ;
    fi
else
    echo Could not find kernel $KERNEL
    exit -1 ;
fi
#  
#
#  Create the initrd first as a plain directory to determine size
#
/bin/mkdir -p $MNTPOINT || exit -1;
SIDEFFECTS=$MNTPOINT
CLEANUP=''
#
#
#  Exit function to clean up side effects:
die(){ r=$?; [ $# -gt 0 ] && r=$1; [ "$r" -eq 0 ] && r=1; [ -z "$r" ] && r=1; 
       cd /tmp; /bin/rm -rf $SIDEFFECTS; 
       [ -n "$CLEANUP" ] && $CLEANUP;
       exit $r; 
     }
#
#  Copy the diskless init script onto the image 
#
/bin/cp $INSTALLDIR/disklessrc $MNTPOINT/disklessrc || die;
#
#  create required directories on the image 
#
/bin/mkdir -p $MNTPOINT/proc/ || die;
/bin/mkdir -p $MNTPOINT/sbin/ || die;
/bin/mkdir -p $MNTPOINT/mnt || die;
/bin/mkdir -p $MNTPOINT/lib || die;
/bin/mkdir -p $MNTPOINT/tmp || die;
/bin/mkdir -p $MNTPOINT/dev || die;
/bin/mkdir -p $MNTPOINT/var/lib/nfs   || die;
/bin/mkdir -p $MNTPOINT/var/lib/dhcp  || die;
/bin/mkdir -p $MNTPOINT/usr/share/hwdata/  || die;
/bin/mkdir -p $MNTPOINT/etc/rc.d/init.d/   || die;
/bin/mkdir -p $MNTPOINT/etc/sysconfig/network-scripts  || die;
/bin/ln -s sbin $MNTPOINT/bin  || die;
# mknod'ing the devices instead of copying them works both with and
# without devfs...
/bin/mknod $MNTPOINT/dev/console c 5 1 || die;
/bin/mknod $MNTPOINT/dev/null c 1 3    || die;
/bin/mknod $MNTPOINT/dev/ram b 1 1     || die;
/bin/mknod $MNTPOINT/dev/systty c 4 0  || die;
for i in 1 2 3 4; do
    /bin/mknod $MNTPOINT/dev/tty$i c 4 $i || die;
done
#
#  copy required files on the image 
#
/bin/cp "$ROOT"/etc/rc.d/init.d/functions $MNTPOINT/etc/rc.d/init.d/ || die;
/bin/cp "$ROOT"/etc/sysconfig/network-scripts/network-functions $MNTPOINT/etc/sysconfig/network-scripts/ || die;
for ((i=0;i<10;i=i+1)); do
    /bin/cat << __EOF > $MNTPOINT/etc/sysconfig/network-scripts/ifcfg-eth$i
DEVICE=eth$i
BOOTPROTO=dhcp
ONBOOT=yes
TYPE=Ethernet
__EOF
    [ $? -ne 0 ] && die;
done;
#/bin/cp "$ROOT"/usr/share/hwdata/pcitable $MNTPOINT/usr/share/hwdata/ || die;
# disklessrc now uses modules.pcimap, not pcitable
BINS="/sbin/busybox.anaconda /sbin/insmod /sbin/modprobe /sbin/rmmod /sbin/dhclient /bin/bash /bin/mount /sbin/route /sbin/ip /usr/bin/expr /sbin/lspci /sbin/ifconfig /sbin/consoletype /sbin/pivot_root /bin/hostname /bin/domainname /usr/bin/host"
# Set up links to all the busybox functions -
# may be different for different versions of busybox!
if [ ! -e $ROOT/sbin/busybox.anaconda ]; then 
    echo "Required binary /sbin/busybox.anaconda is not found in client root $ROOT."
    echo "Install required packages in the client root and retry."
    die;
fi
cp -fp $ROOT/sbin/busybox.anaconda $MNTPOINT/sbin/busybox;
BBFs=(`LC_ALL=C /usr/sbin/chroot $ROOT /sbin/busybox.anaconda 2>&1 | (bbf=0; bbs=''; while read l; do if echo "$l" | grep -q 'functions:$'; then bbf=1; continue; fi; if [ "$bbf" -eq 1 ]; then bbs="$bbs $l"; fi; done; IFS=' 	,
' BBFs=($bbs); echo ${BBFs[@]};)`);
for bbf in ${BBFs[@]}; do
    # Create links to all the busybox functions:
    # do not install busybox emulations that do not work (eg. modprobe!) 
    # and others that may cause problems (eg. sh!)
    if [ "$bbf" != 'busybox' ] && [ "$bbf" != 'modprobe' ] && [ "$bbf" != 'insmod' ] && [ "$bbf" != 'rmmod' ] && [ "$bbf" != 'lsmod' ]  && [ "$bbf" != 'rmmod' ] &&  [ "$bbf" != 'pivot_root' ] && [ "$bbf" != 'sh' ] && [ "$bbf" != 'bash' ] && [ "$bbf" != 'mount' ] && [ "$bbf" != 'lspci' ] && [ "$bbf" != 'ifconfig' ] && [ "$bbf" != 'ip' ] && [ "$bbf" != 'route' ]; then
	ln -s busybox $MNTPOINT/sbin/$bbf;
    fi;
done
# Check that each "function" required by disklessrc / dhclient-script,
# which are in modern busybox.anaconda, are now linked; if not, add program to $BINS: 
RQFs=(awk basename cat cut df grep ln ls more rm cp mv echo tr which head tail tee test touch true uniq sed sleep sort umount uname mktemp readlink);
for rqf in ${RQFs[@]}; do
    if [ ! -e  $MNTPOINT/sbin/$rqf ]; then
	BINS="$BINS "`/usr/sbin/chroot $ROOT which $rqf`;
    fi;
done
SCRIPTS="/sbin/dhclient-script"
for bin in $BINS $SCRIPTS; do
    if [ ! -e $ROOT/$bin ]; then 
	echo "Required binary $bin is not found in client root $ROOT."
	echo "Install required packages in the client root and retry."
	die;
    fi
    if [ -e $MNTPOINT/sbin/${bin##*/} ]; then
       # a busybox function
       BINS=${BINS/$bin/};
       continue;
    fi;
    lddBin="`/usr/sbin/chroot $ROOT /usr/bin/ldd $bin`";
    missingLibs=`echo "$lddBin" | grep 'not found$'`;
    if [ -n "$missingLibs" ]; then
	echo "Required binary $bin has missing library dependencies:"
	echo "$missingLibs";
	die;
    fi;
    if [[ $lddBin =~ 'not\ ' ]]; then # 'not a dynamic executable'
	BINS=${BINS/$bin/};   
    fi;
    /bin/cp -fp $ROOT/$bin $MNTPOINT/sbin/;
    if [ $? -ne 0 ]; then
	echo "Copying required binary $bin to initrd failed.";
	die;
    fi;
done
/bin/mv -f $MNTPOINT/sbin/busybox.anaconda $MNTPOINT/sbin/busybox;
/bin/ln -sf bash $MNTPOINT/sbin/sh;
cd $MNTPOINT/sbin/;
cd ../etc;
/bin/touch fstab || die;
/bin/ln -sf /proc/mounts mtab || die;
( /usr/sbin/chroot $ROOT /usr/bin/ldd $BINS  ) | 
   /bin/grep '^[ 	]' | 
   /bin/sed 's/^[^>]*>[ ]//;s/^[ 	]*//;s/[( ].*$//;/^[ 	]*$/d' | /bin/sort | /usr/bin/uniq |
   while read f; do 
       d=${f%/*}
       if [ ! -d $MNTPOINT/$d ]; then
          /bin/mkdir -p $MNTPOINT/$d || die;
       fi
       L=`/usr/bin/readlink $ROOT/$f`; 
       if [ -n "$L" ]; then 
          /bin/cp -fp $ROOT/$d/$L $MNTPOINT/$d/$L || die;
          /bin/ln -s $L $MNTPOINT/$f || die;
       else 
          /bin/cp -fp $ROOT/$f $MNTPOINT/$f || die;
       fi; 
   done || { echo "Copying required libraries to initrd failed."; die; };
cd $TOP_DIR
# Copy required modules files to initrd:
if [ ! -e "$MODULES"/modules.dep ]; then
    echo "Required file $MODULES/modules.dep not found . Run 'depmod -a' in the client root and retry."
    die;
fi
NEWMODDIR=$MNTPOINT/lib/modules/$VERSION
/bin/mkdir -p $NEWMODDIR || die;
/bin/ls ${MODULES}/. | grep 'modules\.' | while read f; do /bin/cp -fp ${MODULES}/$f $NEWMODDIR || die; done
for d in kernel/drivers/net kernel/fs/nfs kernel/fs/lockd kernel/net/sunrpc kernel/net/ipv6 kernel/crypto;
do
    if [ ! -d "$MODULES"/$d ]; then
	echo "Required modules directory $MODULES/$d not found."
	echo "Install required modules directory in client root and retry."
	die;
    fi
    (cd $MODULES; /bin/tar -cpf - $d) | (cd $NEWMODDIR; /bin/tar -xpf -);
    r=$?
    if [ "$r" -ne 0 ]; then
	echo "Copy of required modules directory $d to initrd failed."
	die $r;
    fi;
done    
#
#   Find missing module dependencies in initrd:
#
moddeps()
{
    if [ ! -d "$1" ] || [ ! -d "$1/kernel" ] || [ ! -r "$1/modules.dep" ]; then 
	echo "moddeps: Expected name of initrd modules directory first argument - got $1"
	return 1;
    fi;
    m=$1
    r=${m%/*/*/*}    
    h=''
    t=`/bin/mktemp /tmp/XXXXXX`
    while read l; do
	if [[ "$l" = *\\ ]]; then
	    h="$h"${l/\\};
	elif [ -n "$h" ]; then
	    h="$h$l"
	    echo $h
	    h='';
	else
	    echo $l;
	fi;
    done < $m/modules.dep > $t;
    kv=${m##*/}
    kvr=(`echo $kv | /bin/sed 's/\([0-9]*\)\.\([0-9]*\)[^0-9].*$/\1 \2/'`);
    mo='.ko';
    if [ ${#kvr[@]} -eq 2 ] && [ ${kvr[0]} -le 2 ] && [ ${kvr[1]} -le 4 ]; then
	mo='.o';
    fi;
    find $m -name "*$mo" | while read o; do
	mos=(${o#${r}})
	while [ ${#mos[@]} -gt 0 ]; do
	    o=${mos[0]}
	    if [ ! -e "$r/$o" ]; then
		echo "$o";
	    fi;
	    mos[0]=''
	    mos=(${mos[@]} `/bin/egrep "^$o:" $t | /bin/sed 's/^.*://'`)
        done;
    done;
    /bin/rm -f $t;
    return 0;
}
moddeps "$NEWMODDIR" | sed 's#^/##' | 
        ( cd "$ROOT"; /bin/tar -cpf - -T -) |
	( cd "$MNTPOINT"; /bin/tar -xpf -);
if [ $? -ne 0 ]; then
    echo "Copying modules to initrd failed."
    die;
fi;
#
#  Create a new image file
#
duks=(`/usr/bin/du -ks $MNTPOINT 2>/dev/null || echo ''`)
dus=${duks[0]}
imagesize=16000;
if [ -n ${dus} ] && [ ${dus} -gt 1024 ]; then
    imagesize=`echo -e 'scale=2\nr=('$dus'*1.05)\nr*=1024\nscale=0\nif((r%1024)>0){(r/1024)+1}else{r/1024}\n' | /usr/bin/bc`
fi;
if [ -z ${imagesize} ] || ! { echo "$imagesize" | /bin/egrep -q '^[0-9]+$'; }; then
    imagesize=16000;
fi;
uncompressedimage=/tmp/image$$
TMPDIR=$MNTPOINT
MNTPOINT=/mnt/$$
/bin/mkdir -p $MNTPOINT || die;
rm -f $uncompressedimage;
/bin/dd if=/dev/zero of=$uncompressedimage bs=1k count=$imagesize 2> /dev/null || die;
chmod +rw $uncompressedimage;
img=$uncompressedimage
SIDEFFECTS="$MNTPOINT $uncompressedimage";
SELINUX=
SEMLS=
sedir=`while read d dir type r; do if [ "$type" = "selinuxfs" ]; then echo $dir; break; fi; done < /proc/mounts`
if [ -e $sedir/enforce ]; then
   SELINUX=1
#  SELinux is enabled; could be enforcing / permissive, doesn't matter
   if [ -e $sedir/mls ] && [ `cat $sedir/mls` -eq 1 ]; then
       SEMLS=':s0';
   fi;
fi
if [ "$SELINUX" -eq 1 ]; then
   /usr/bin/chcon -t fsadm_tmp_t $img >/dev/null 2>&1;
   # under new SELinux, mke2fs/tune2fs can only create filesystems in files with this context
fi
# We have to "echo y |" so that it doesn't complain about $IMAGE not
# being a block device
mke2fsOut=`echo y | /sbin/mke2fs $img 2>&1`;
if [ $? -ne 0 ]; then
   echo -e "mke2fs failed:\n$mke2fsOut";
   die;
fi;
/sbin/tune2fs -i0 $img >/dev/null 2>&1 || die;
if [ -n "$SELINUX" ]; then
    /usr/bin/chcon -t mount_tmp_t $img >/dev/null 2>&1;
#   new SELinux mount cannot mount files unless they have this context 
    /bin/mount -t ext2 -o loop,context=system_u:object_r:removable_t${SEMLS} $img $MNTPOINT || die;
#   do NOT enable creation of SELinux xattr labels in the initrd; new SELinux requires ':s0', old does not :-(
else
#  SELinux is disabled / not present; no labels will be created
    /bin/mount -t ext2 -o loop $img $MNTPOINT || die;
fi
CLEANUP="/bin/umount -f $MNTPOINT"
#
# We don't need this directory, so let's save space
/bin/rm -rf $MNTPOINT/'lost+found'
#
#  Copy the temp directory to the initrd:
#
if ! (cd $TMPDIR; /bin/tar -cpf - . || die ) | (cd $MNTPOINT; /bin/tar -xpf -) ; then
   echo "Creation of initrd failed."
   die
fi;
/bin/rm -rf $TMPDIR;
SIDEFFECTS=$uncompressedimage
# 
#  Unmount and compress image file to be ready to boot
#
/bin/sync
/bin/umount $MNTPOINT
CLEANUP=''
/bin/rmdir  $MNTPOINT
/bin/mkdir -p "$DESTDIR" ;
if [ ! -d "$DESTDIR" ]; then
   echo "Cannot create $DESTDIR directory.";
   die;
fi
if [ -e "$IMAGEFILE" ]; then
    /bin/mv -f "$IMAGEFILE" "$IMAGEFILE".backup;
fi
if ! /usr/bin/gzip -c -9 $uncompressedimage > "$IMAGEFILE"; then
   echo " gzip -c -9 $uncompressedimage > $IMAGEFILE failed.";
   die;
fi
/bin/rm -f $uncompressedimage
SIDEFFECTS=$IMAGEFILE
if ! /bin/cp -a "$KERNEL" "$DESTDIR"/vmlinuz; then
   echo "/bin/cp -a $KERNEL $DESTDIR/vmlinuz failed.";
   die;
fi
exit 0;
