#!/bin/bash
# initrd builder for network booting
#
# Modified by Steven Shiau <steven _at_ nchc org tw> and Blake Huang <klhaung _at_ nchc org tw>.
#
# License: GPL
# Utility function to determine whether or not a filesystem is usable for
# loopback mounts.  Lifted verbatim from Erik Troan's mkinitrd script.
# mkpxeinitrd-net program is derived from mkinitrd-net, which is developed by Michael Brown from Fen Systems Ltd.

# Load DRBL setting and functions
DRBL_SCRIPT_PATH="${DRBL_SCRIPT_PATH:-/opt/drbl/}"

. $DRBL_SCRIPT_PATH/sbin/drbl-conf-functions

# Default settings (some can be overridden by command-line options)
include_modules=include-modules
initrd_skel=/usr/lib/mkpxeinitrd-net/initrd-skel
kernel_ver=`uname -r`
use_sudo=y
keep=n
output_dir=/tftpboot/nbi_img
make_link=y
quiet=
vmlinuz_suffix="pxe"
initrd_suffix="pxe"

# No need to use sudo if we are root
if [ $UID -eq 0 ]; then
  use_sudo=n
fi

USAGE="Usage: $0 [-k|--kernel <kernel_ver>] [-n|--nolink] [-q|--quiet] [-l|--local] [--nosudo] [-nu|--no-usb-modules] [-t|--initfs-type ext2|cramfs|initramfs]--keep] [--help]"

# default setting
use_usb_keyboard_modules="yes"

# Parse command-line options
while [ $# -gt 0 ]; do
  case "$1" in
    -l|--local)
		shift
		use_local=y ;;
    -k|--kernel)
		shift
		kernel_ver=$1
		shift ;;
    -t|--initfs-type)
		shift
		initfs_type=$1
		shift ;;
    --nosudo)   shift ; use_sudo=n ;;
    -nu|--no-usb-modules)   shift ; use_usb_keyboard_modules="no" ;;
    --keep)     shift ; keep=y ;;
    --n|--nolink)
		shift ; make_link=n ;;
    -q|--quiet) shift ; quiet=-q ;;
    -v|--verbose) shift ; verbose="-v" ;;
    --help)	shift ; do_help=y ;;
    --)		shift ; break ;;
    -*)		echo "${0}: ${1}: invalid option" >&2
		echo $USAGE >& 2
		exit 2 ;;
    *)		break ;;
  esac
done


# Build list of requested modules
#modules="$*"
#requested_modules="$modules"
#modules="$modules lockd" # Need this one..., otherwise, module load procedure will have some problem, ie, we want sunrpc -> lockd -> nfs
#modules="$modules nfs" # Always require nfs for nfs mount
#modules="$modules af_packet" # Always require af_packet for udhcpc if it's a module

# --help => Print help message
if [ "$do_help" == "y" ]; then
  echo $USAGE
  echo "  -k, --kernel   Specify kernel version"
  echo "  -n, --nolink   Do not create a matching symbolic link"
  echo "  -l, --local    Run locally from CVS (for developers only)"
  echo "  --nosudo       Do not use sudo (i.e. must run as root instead)"
  echo "  --keep         Keep temporary files instead of deleting them"
  exit 0;
fi

# --local => we are running directly from CVS, rather than
# from an installed copy, so use local files and directories
if [ "$use_local" == "y" ]; then
  include_modules=./include-modules
  initrd_skel=initrd-skel
  output_dir=tftpboot
fi

# If use_sudo is set, check that sudo exists
sudo=/usr/bin/sudo
if [ "$use_sudo" == "y" ]; then
  if [ ! -x $sudo ]; then
    use_sudo=n
    echo "WARNING: --nosudo not specified but $sudo not found"
  fi
fi
if [ "$use_sudo" == "n" ]; then
  sudo=
fi

# Create temporary working files
initrd=`mktemp -d ${tmpdir}/initrd.XXXXXX`
initrdimg=`mktemp ${tmpdir}/initrd.img.XXXXXX`
initrdmnt=`mktemp -d ${tmpdir}/initrd.mnt.XXXXXX`

# Copy skeleton into temporary area
cp -a $initrd_skel/* $initrd/
mkdir -p $initrd/lib/modules/$kernel_ver

# put 2nd search pci table from kernel
cp -f $drbl_common_root/lib/modules/$kernel_ver/modules.pcimap $initrd/etc

# if we can find the modules in the common_root, use it first
if [ -d $drbl_common_root/lib/modules/$kernel_ver ]; then
   drbl_kernel_mod_path="$drbl_common_root/"
else
   drbl_kernel_mod_path=""
fi
echo "Use kernel modules from $drbl_kernel_mod_path/lib/modules/$kernel_ver."

# check if /boot/config-$kernel_ver exists
# By Blake Huang, modified by Steven Shiau.
# kernel config is either in /boot/ or /tftpboot/node_root/boot
kernel_config="$drbl_kernel_mod_path/lib/modules/../../boot/config-$kernel_ver"

# decided the initfs type
if [ -n "$initfs_type" ]; then
  # initfs_type is assigned
  # format the initfs_type
  case "$initfs_type" in
   ext2|EXT2) 
     if [ -z "$(grep "^CONFIG_EXT2_FS=y" $kernel_config)" ]; then
       [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
       echo "EXT2 is NOT builtin (maybe module) in the kernel $kernel_ver (Check $kernel_config for more details)!"
       [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
       echo "Program terminated!!!"
       exit 1
     fi
     initfs_type="EXT2" ;;
   cramfs|CRAMFS) 
     if [ -z "$(grep "^CONFIG_CRAMFS=y" $kernel_config)" ]; then
       [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
       echo "CRAMFS is NOT builtin (maybe module) in the kernel $kernel_ver (Check $kernel_config for more details)!"
       [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
       echo "Program terminated!!!"
       exit 1
     fi
     initfs_type="CRAMFS" ;;
   initramfs|INITRAMFS) 
     if [ -z "$(echo "$kernel_ver" | grep -E "^2\.6\.")" ]; then
       [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
       echo "initramfs only works for kernel 2.6!"
       [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
       echo "Program terminated!!!"
       exit 1
     fi
     initfs_type="INITRAMFS" ;;
   *) echo $USAGE && exit 2;;
  esac
else
  # initfs_type is not assigned, try to find it
  if [ ! -f $kernel_config ]; then 
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "$kernel_config does NOT exist!"
    echo "I can not judge either filesystem CRAMFS or EXT2 is builtin (NOT module) in the kernel you are using!!!"
    echo "We will assume that the kernel you are using can use initramfs!"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    if [ -z "$(echo "$kernel_ver" | grep -E "^2\.6\.")" ]; then
      [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
      echo "initramfs only works for kernel 2.6!"
      [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
      echo "Program terminated!!!"
      exit 1
    fi
    initfs_type="INITRAMFS"
  else
    # we assume the the priority is higher for EXT2, i.e. EXT2 option will overwrite the CRAMFS.
    [ -n "$(grep "^CONFIG_CRAMFS=y" $kernel_config)" ] && initfs_type="CRAMFS"
    [ -n "$(grep "^CONFIG_EXT2_FS=y" $kernel_config)" ] && initfs_type="EXT2"
    if [ -z "$initfs_type" ]; then
      if [ -z "$(echo "$kernel_ver" | grep -E "^2\.6\.")" ]; then
        [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
        echo "initramfs only works for kernel 2.6!"
        [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
        echo "Program terminated!!!"
        exit 1
      fi
      [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
      echo "$kernel_config is found but either CRAMFS or EXT2 filesystem is NOT builtin in the kernel. We will use initramfs."
      [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
      initfs_type="INITRAMFS"
    fi
  fi
fi

# copy the modules for the requested kernel version like:
# cp -a /lib/modules/$kernel_ver $initrd/lib/modules/
# or
# cp --parents -f -a /lib/modules/$kernel_ver/modules.* /lib/modules/$kernel_ver/kernel/fs/nfs /lib/modules/$kernel_ver/kernel/fs/lockd /lib/modules/$kernel_ver/kernel/drivers/net $initrd/
# mkpxeinitrd-net result in redhat.
# nfs.o sunrpc.o lockd.o
# mkinitrd-net said we need
# lockd nfs af_packet
# mandrake etherboot nbi includes
# af_packet.o  nfsd.o
# like:
# /lib/modules/2.4.22-10mdk/kernel/fs/nfsd/nfsd.o.gz
# /lib/modules/2.4.22-10mdk/kernel/net/packet/af_packet.o.gz
# it seems that RH do not have af_packet, while Mandrake/Debian has it.
# so in redhat, we need:
# nfs.o sunrpc.o lockd.o
# for mandrake, we need
# af_packet.o  nfsd.o

# But we can have a better method:
# In kernel 2.4.26 or later, we need the lib/mldules/$kernel_ver/kernel/lib also
if [ -n "$(echo $kernel_ver | grep "^2\.4\.")" ]; then
  # for kernel 2.4.x, we use this ugly method, since tar has "--exclude" option
  (cd $drbl_kernel_mod_path/lib/modules/$kernel_ver && tar cplf - modules.*  kernel/net/sunrpc* kernel/fs/nfs* kernel/fs/lockd* kernel/net/packet kernel/drivers/net kernel/drivers/base kernel/lib --exclude appletalk --exclude fc --exclude hamradio --exclude irda --exclude pcmcia --exclude tokenring --exclude wan --exclude wireless --exclude wireless_old 2>/dev/null) | ( cd $initrd/lib/modules/$kernel_ver && tar xpf -)
elif [ -n "$(echo $kernel_ver | grep "^2\.6\.")" ]; then
  # for kernel 2.6.x, we have better method	 
  (
   cd $drbl_kernel_mod_path/lib/modules/$kernel_ver/
   # We need some usb modules so that we can use usb keyboard if it hang in initrd
   if [ "$use_usb_keyboard_modules" = "yes" ]; then
     usb_related_modules="kernel/drivers/usb/core/usbcore.* \
                          kernel/drivers/usb/host/ehci-hcd.* \
                          kernel/drivers/usb/host/ohci-hcd.* \
                          kernel/drivers/usb/host/uhci-hcd.* \
                          kernel/drivers/usb/input/usbhid.* \
                          kernel/drivers/usb/input/usbkbd.*"
   fi
   # (a). For modules.*, kernel/net/packet/af_packet (for debian based, RH already builtin) and USB related modules
   # Always require af_packet for udhcpc if af_packet is not builtin.
   # Normally, for RedHat-like, it's builtin. For Debian-like, it's a module.
   cp -a --parents modules.* kernel/net/packet $usb_related_modules $initrd/lib/modules/$kernel_ver/ 2>/dev/null

   # (b). For nfs.ko modules
   # From kernel 2.6.17 to 2.6.18 in FC5.92 to FC6, cacaefs is used. Hence
   # modprobe --show-depends nfs
   # install /sbin/modprobe --first-time --ignore-install sunrpc && { /bin/mount -t rpc_pipefs sunrpc /var/lib/nfs/rpc_pipefs > /dev/null 2>&1 || :; }
   # insmod /lib/modules/2.6.17-1.2630.fc6/kernel/fs/nfs_common/nfs_acl.ko
   # insmod /lib/modules/2.6.17-1.2630.fc6/kernel/fs/fscache/fscache.ko
   # insmod /lib/modules/2.6.17-1.2630.fc6/kernel/fs/lockd/lockd.ko
   # insmod /lib/modules/2.6.17-1.2630.fc6/kernel/fs/nfs/nfs.ko
   nfs_mods="$(parse-nfs-mod -p "$drbl_kernel_mod_path" -k $kernel_ver)"
   cp -a --parents $nfs_mods $initrd/lib/modules/$kernel_ver/

   # (c). For network device modules
   # Get the relative path of those NIC kernel modules
   # Ex: kernel/drivers/net/8139too.ko
   net_mods="$(parse-net-mod -p "$drbl_kernel_mod_path" -k $kernel_ver)"
   # To avoid net_mods is too long, we use for loop here
   for i in $net_mods; do
     cp -a --parents $i $initrd/lib/modules/$kernel_ver/
   done

   # Deal with firmware!
   # The following is borrowed from Debian's /usr/share/initramfs-tools/hook-functions
   # Variable DESTDIR is required for copy_exec use later
   export DESTDIR="$initrd"

   firmware_use_flag="no"
   echo "Trying to include network card firmwares if they exist in /lib/firmware/..."
   for i in $net_mods; do
     mod="$initrd/lib/modules/$kernel_ver/$i"
     firmwares="$(modinfo -F firmware ${mod} 2>/dev/null)"
     # firmwares e.g.:
     #   bnx2/bnx2-rv2p-09-4.6.15.fw
     #   bnx2/bnx2-mips-09-4.6.17.fw
     #   bnx2/bnx2-rv2p-06-4.6.16.fw
     #   bnx2/bnx2-mips-06-4.6.16.fw
     #   e100/d102e_ucode.bin
     #   e100/d101s_ucode.bin
     #   e100/d101m_ucode.bin
     [ -z "$firmwares" ] && continue
     firmware_use_flag="yes"
     for firmware in $firmwares; do
       if [ -e "${initrd}/lib/firmware/${firmware}" ] \
       || [ -e "${initrd}/lib/firmware/${kernel_ver}/${firmware}" ]; then
       	continue
       fi
       
       # Only print warning for missing fw of loaded module
       # or forced loaded module
       if [ ! -e "$drbl_kernel_mod_path/lib/firmware/${firmware}" ] \
       && [ ! -e "$drbl_kernel_mod_path/lib/firmware/${kernel_ver}/${firmware}" ]; then
        [ "$verbose" = "-v" ] && echo "W: Possible missing firmware /lib/firmware/${firmware} for module $(basename ${mod} .ko)" >&2
       	continue
       fi
       
       if [ ! -e "${initrd}/lib/udev/firmware.agent" ] \
       && [ -e "/lib/udev/firmware.agent" ]; then
       	copy_exec /lib/udev/firmware.agent
       fi
       
       [ "$verbose" = "-v" ] && echo "Adding firmware ${firmware}"
       if [ -e "/lib/firmware/${kernel_ver}/${firmware}" ]; then
       	copy_exec "/lib/firmware/${kernel_ver}/${firmware}"
       else
       	copy_exec "/lib/firmware/${firmware}"
       fi
     done
   done

   # (d). For Ubuntu 7.10 or later.
   # Extra network drivers are located in dir like: /lib/modules/2.6.22-14-generic/ubuntu/net
   if [ -n "$(unalias ls &>/dev/null; ls ubuntu/net 2>/dev/null)" ]; then
     mkdir -p $initrd/lib/modules/$kernel_ver/ubuntu/
     # Note! Now the working dir is in:
     # $drbl_kernel_mod_path/lib/modules/$kernel_ver/
     cp -a --parents ubuntu/net $initrd/lib/modules/$kernel_ver/ubuntu/
   fi
   # (e). For Debian Lenny or later
   # Extra network drivers are located in dir like: /lib/modules/kernel/2.6.26-1-686/extra/atl2/atl2.ko
   # Since we can not tell which driver is for network or not, we put them all.
   if [ -n "$(unalias ls &>/dev/null; ls extra/* 2>/dev/null)" ]; then
     mkdir -p $initrd/lib/modules/$kernel_ver/extra/
     # Note! Now the working dir is in:
     # $drbl_kernel_mod_path/lib/modules/$kernel_ver/
     cp -a --parents extra/* $initrd/lib/modules/$kernel_ver/extra/
   fi

   # If firmware_use_flag is "yes", we have to include udev, since without udev, firmware loading won't work.
   if [ "$firmware_use_flag" = "yes" ]; then
     # The copy_exec in hooks/udev will honor variable DESTDIR
     echo "Calling hook udev..."
     /usr/lib/mkpxeinitrd-net/hooks/udev
   fi
  )
else 
  echo "Kernel $kernel_ver is not supported! Program terminated!"
  exit 1
fi

# before gzipping the initrd, rename or unzip the gzipped modules if they exist,
# so that modprobe from busybox can use that. This is specially for Mandrake
# for kernel 2.6, the modules is *.ko.gz, while for kernel 2.4, it's *.o.gz
find $initrd/lib/modules/$kernel_ver/ -name "*.o.gz" -exec gunzip {} \;
find $initrd/lib/modules/$kernel_ver/ -name "*.ko.gz" -exec gunzip {} \;

# rename the module names in modules.dep from *.o.gz to *.o, 
# This is specially for Mandrake
perl -pi -e 's/(.[k]*o).gz/$1/g' $initrd/lib/modules/$kernel_ver/modules.dep

# copy the kernel to output_dir first.
cp -f $verbose $drbl_kernel_mod_path/boot/vmlinuz-$kernel_ver $output_dir/
# To create initrd with CRAMFS or EXT2
case "$initfs_type" in
 "CRAMFS")
   # CRAMFS
   echo "Creating the CRAMFS initrd..."
   $sudo mknod $initrd/dev/console c 5 1
   $sudo mknod $initrd/dev/null c 1 3
   $sudo mknod $initrd/dev/ram b 1 1
   $sudo mknod $initrd/dev/systty c 4 0
   $sudo mknod $initrd/dev/random c 1 8
   $sudo mknod $initrd/dev/urandom c 1 9
   for i in 1 2 3 4; do $sudo mknod $initrd/dev/tty$i c 4 $i; done
   
   # initrd image looks for /linuxrc instead of /init
   mv -f $initrd/linuxrc-or-init $initrd/linuxrc
   # create initrd using cramfs
   mkcramfs $initrd $output_dir/initrd-$initrd_suffix.$kernel_ver.img
   [ "$verbose" = "-v" ] && echo "The output initrd is $output_dir/initrd-$initrd_suffix.$kernel_ver.img" 
   ;;
 "EXT2")
   # EXT2
   # Create empty ext2fs image file
   echo "Creating the EXT2 initrd..."
   dd if=/dev/zero bs=1k of=$initrdimg count=$((`du -sk $initrd | cut -f1` * 7 / 6)) 2> /dev/null
   /sbin/mke2fs -q -F $initrdimg 2> /dev/null
   
   # Mount image file, copy files on, create /dev entries, display free space, umount
   $sudo mount -o loop $initrdimg $initrdmnt
   cp -a $initrd/* $initrdmnt/
   # initrd image looks for /linuxrc instead of /init
   mv -f $initrdmnt/linuxrc-or-init $initrdmnt/linuxrc
   $sudo mknod $initrdmnt/dev/console c 5 1
   $sudo mknod $initrdmnt/dev/null c 1 3
   $sudo mknod $initrdmnt/dev/ram b 1 1
   $sudo mknod $initrdmnt/dev/systty c 4 0
   $sudo mknod $initrdmnt/dev/random c 1 8
   $sudo mknod $initrdmnt/dev/urandom c 1 9
   for i in 1 2 3 4; do $sudo mknod $initrdmnt/dev/tty$i c 4 $i; done

   if [ "$quiet" == "n" ]; then
     df -h $initrdmnt
   fi
   $sudo umount $initrdmnt
   
   # Create output file
   gzip -9 -n -c $initrdimg > $output_dir/initrd-$initrd_suffix.$kernel_ver.img
   [ "$verbose" = "-v" ] && echo "The output initrd is $output_dir/initrd-$initrd_suffix-$kernel_ver.img" 
   ;;
 "INITRAMFS")
   # initramfs
   echo "Creating the initRAMFS image..."
   $sudo mknod $initrd/dev/console c 5 1
   $sudo mknod $initrd/dev/null c 1 3
   $sudo mknod $initrd/dev/ram b 1 1
   $sudo mknod $initrd/dev/systty c 4 0
   $sudo mknod $initrd/dev/random c 1 8
   $sudo mknod $initrd/dev/urandom c 1 9
   for i in 1 2 3 4; do $sudo mknod $initrd/dev/tty$i c 4 $i; done

   # create initrd using initramfs
   ( cd $initrd
     # initRAMFS image looks for /init instead of /linuxrc
     mv -f linuxrc-or-init init
     find . | cpio --quiet -o -H newc | gzip -9 > $output_dir/initrd-$initrd_suffix.$kernel_ver.img )
   [ "$verbose" = "-v" ] && echo "The output initrd is $output_dir/initrd-$initrd_suffix.$kernel_ver.img" 
   ;;
esac
# Create symlink
if [ "$make_link" == "y" ]; then
  [ "$verbose" = "-v" ] && echo "Creating the softlink vmlinuz and initrd..."
  # vmlinuz
  vmlinuz_link=$output_dir/vmlinuz-$vmlinuz_suffix
  [ -L $vmlinuz_link ] && rm -f $verbose $vmlinuz_link
  ln -fs $verbose vmlinuz-$kernel_ver $vmlinuz_link
  #initrd
  initrd_link=$output_dir/initrd-$initrd_suffix.img
  [ -L $initrd_link ] && rm -f $verbose $initrd_link
  ln -fs $verbose initrd-$initrd_suffix.$kernel_ver.img $initrd_link
fi

# Remove temporary files
if [ "$keep" == "n" ]; then
  rm -rf $initrd
  rm -f $initrdimg
  rmdir $initrdmnt
fi

exit 0
