# -*-Shell-script-*-
# Steven Shiau <steven@nchc.org.tw>
# License: GPL
#
# functions	This file contains functions to be used by most or all
#		shell scripts in the DRBL environment
#
# cut the functions from drblsrv-rh, drblsrv-mdk, drbl.pl
# /opt/drbl/sbin/drbl-functions
# drbl-client-root-passwd 
# a lot of functions in drbl-login-switch ?
# mknic-nbi create_depmod_env modify
# resize_part check_input_hd()

# Do NOT set the locale by "export LC_ALL=C". It will make dialog distortation.

# Source DRBL setting
. /opt/drbl/conf/drbl.conf

check_distribution_name() {
 #8.0:/etc/issue or /etc/redhat-release: Red Hat Linux release 8.0 (Psyche)
 #9.0:/etc/issue or /etc/redhat-release: Red Hat Linux release 9 (Shrike)
 #FC1:/etc/fedora-release: Fedora Core release 1 (Yarrow)
 #FC2:/etc/fedora-release: Fedora Core release 2 (Tettnang)
 #MDK 9.2: /etc/mandrake-release: "Mandrake Linux release 9.2 (FiveStar) for i586" 
 #MDK 10.0 (Community): /etc/mandrake-release: "Mandrake Linux release 10.0 (Community) for i586" 
 #MDK 10.0 (Official): /etc/mandrake-release: "Mandrake Linux release 10.0 (Official) for i586" 
 #$OS_Version will be the index for rc.sysinit in drblsrv-rh, drblsrv-mdk and drblpush
  # RedHat
  grep -q "Red Hat Linux release 8.0 (Psyche)" /etc/redhat-release 2>/dev/null && OS_Version="RH8.0"
  grep -q "Red Hat Linux release 9 (Shrike)" /etc/redhat-release 2>/dev/null && OS_Version="RH9"
  grep -q "Fedora Core release 1 (Yarrow)" /etc/fedora-release 2>/dev/null && OS_Version="FC1"
  # Fedora Core
  grep -q "Fedora Core release 2 (Tettnang)" /etc/fedora-release 2>/dev/null && OS_Version="FC2"
  grep -q "Fedora Core release 3 (Heidelberg)" /etc/fedora-release 2>/dev/null && OS_Version="FC3"
  # Mandrake
  grep -q "Mandrake Linux release 9.2 (FiveStar) for i586" /etc/mandrake-release 2>/dev/null && OS_Version="MDK9.2"
  grep -q "Mandrake Linux release 10.0 (Community) for i586" /etc/mandrake-release 2> /dev/null && OS_Version="MDK10.0"
  grep -q "Mandrake Linux release 10.0 (Official) for i586" /etc/mandrake-release 2> /dev/null && OS_Version="MDK10.0"
  grep -q "Mandrakelinux release 10.1 (Official) for i586" /etc/mandrakelinux-release 2> /dev/null && OS_Version="MDK10.1"
  # RHEL
  #grep -q "Red Hat Enterprise Linux .* release 4 (Nahant)" /etc/redhat-release 2>/dev/null && OS_Version="RHEL4"
  # CentOS
  grep -q "CentOS release 4.0 (Final)" /etc/redhat-release 2>/dev/null && OS_Version="CO4"
  echo $OS_Version
}

# create the shared library program runtime env.
# ldd might give these 3 type results:
# 1. newer version (FC3):
# ldd /sbin/depmod
#         libc.so.6 => /lib/libc.so.6 (0x00a08000)
#         /lib/ld-linux.so.2 (0x009f1000)
# 2. older version (RH8/9,FC1/2):
# ldd /sbin/depmod
#         libc.so.6 => /lib/libc.so.6 (0x40025000)
#         /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
# 3. MDK10
#         linux-gate.so.1 =>  (0xffffe000)
#         libc.so.6 => /lib/tls/libc.so.6 (0x40028000)
#         /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
#RH9:
#libc.so.6 => /lib/libc.so.6 (0x40025000)
#/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
#FC1:
#libc.so.6 => /lib/libc.so.6 (0x00a35000)
#/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x00a20000)
#FC2:
#libc.so.6 => /lib/libc.so.6 (0x00b76000)
#linux.so.2 => /lib/ld-linux.so.2 (0x00b61000)
#FC3:
#libc.so.6 => /lib/tls/i686/libc.so.6 (0x007b3000)
#linux.so.2 (0x0079c000)
#mdk10:
#linux-gate.so.1 =>  (0xffffe000)
#libc.so.6 => /lib/tls/libc.so.6 (0x40028000)
#/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
 
create_depmod_env() {
  local newroot="$1"
  [ -z $newroot ] && exit 1
  cp --parents /sbin/depmod $newroot
  if ldd /sbin/depmod &>/dev/null; then
    depmod_lib_need=$(ldd /sbin/depmod | sed -e "s/.*=> //g" -e "s/(0x.*)//g" | awk '{print $1}')
    for imod in $depmod_lib_need; do
        cp --parents $imod $newroot
    done
  fi
}
clean_depmod_env() {
  local newroot="$1"
  [ -z $newroot ] && exit 1
  [ -f $newroot/sbin/depmod ] && rm -f $newroot/sbin/depmod
  if ldd /sbin/depmod &>/dev/null; then
    depmod_lib_need=$(ldd /sbin/depmod | sed -e "s/.*=> //g" -e "s/(0x.*)//g" | awk '{print $1}')
    for f2rm in $depmod_lib_need; do
      [ -f "$newroot/$f2rm" ] && rm -f $newroot/$f2rm
    done
  fi
}

# function to create chkconfig environment
create_chkconfig_env() {
  local newroot="$1"
  [ -z $newroot ] && exit 1
  cp --parents /sbin/chkconfig $newroot
  chkconfig_lib_need=$(ldd /sbin/chkconfig | sed -e "s/.*=> //g" -e "s/(0x.*)//g" | awk '{print $1}')
  for imod in $chkconfig_lib_need; do
    cp --parents $imod $newroot
  done
}

# function to clean chkconfig environment
clean_chkconfig_env() {
  local newroot="$1"
  [ -z $newroot ] && exit 1
  chkconfig_lib_need=$(ldd /sbin/chkconfig | sed -e "s/.*=> //g" -e "s/(0x.*)//g" | awk '{print $1}')
  [ -f $newroot/sbin/chkconfig ] && rm -f $newroot/sbin/chkconfig
  for f2rm in $chkconfig_lib_need; do
    [ -f $newroot/$f2rm ] && rm -f $newroot/$f2rm
  done
}

# Check if root or not
check_if_root() {
   if [ ! "$UID" = "0" ]; then
     echo
     echo "[$LOGNAME] You need to run this script \"`basename $0`\" as root."
     echo
     exit 1
   fi
}

# function to get the autologin_account
# input is the host dir, like /tftpboot/nodes/192.168.1.1, then
# return the auto_login ID
get_autologin_account() {
    local ihost="$1"
    # $auto_login_id and $echo_no_digit_0_1 are global variables
    # auto login username is set as hostname
    . $ihost/etc/sysconfig/network
    auto_login_id="$HOSTNAME"
    if grep -q $auto_login_id /etc/passwd ; then
      # account $auto_login_id exists, show it
      echo "$auto_login_id"
    fi
}

# check switch if it is input correct as on or off
check_switch_on_off() {
  local switch="$1"
  case "$switch" in
    on|ON|[oO]|[nN]|off|OFF|[oO][fF][fF])
         break
         ;;
     "")
         usage && exit 1
         ;;
     *)
         echo "You must specify \"on\" or \"off\" !!! Program stop!!!"
         exit 1
         ;;
  esac
}

#
create_authconfig_env() {
  local newroot="$1"
  [ -z $newroot ] && exit 1
   file_need="/usr/sbin/authconfig /usr/sbin/pwconv /usr/sbin/grpconv"
   cp --parents $file_need $newroot

   if ldd /usr/sbin/authconfig &>/dev/null; then
     authconfig_lib_need=$(ldd /usr/sbin/authconfig | sed -e "s/.*=> //g" -e "s/(0x.*)//g" | awk '{print $1}')
     for imod in $authconfig_lib_need; do
       cp --parents $imod $newroot
     done
   fi
   if ldd /usr/sbin/pwconf &>/dev/null; then
     pwconf_lib_need=$(ldd /usr/sbin/pwconf | sed -e "s/.*=> //g" -e "s/(0x.*)//g" | awk '{print $1}')
     for imod in $pwconf_lib_need; do
       cp --parents $imod $newroot
     done
   fi
   if ldd /usr/sbin/grpconv &>/dev/null; then
     grpconv_lib_need=$(ldd /usr/sbin/grpconv | sed -e "s/.*=> //g" -e "s/(0x.*)//g" | awk '{print $1}')
     for imod in $grpconv_lib_need; do
       cp --parents $imod $newroot
     done
   fi
}

#
create_passwd_env() {
  local newroot="$1"
  [ -z $newroot ] && exit 1
  # /lib* includes /lib and /lib64
  PAM_need_files="/lib*/security/ /usr/lib*/libcrack*.so* /usr/lib*/cracklib_dict.* /lib*/libnss_files*.so* /lib*/libnsl*.so*"
  passwd_lib_need=$(ldd /usr/bin/passwd | sed -e "s/.*=> //g" -e "s/(0x.*)//g" | awk '{print $1}')
  for imod in $passwd_lib_need; do
    cp --parents $imod $newroot
  done
  cp --parents /usr/bin/passwd $newroot

  # /bin/echo is necessary for passwd --stdin
  cp --parents /bin/echo $newroot

  cp --parents -r $PAM_need_files $newroot
  # we need sh to run the script, borrow it from mkinitrd-net's busybox
  cp /usr/lib/mkinitrd-net/initrd-skel/bin/sh $newroot/bin
}

clean_passwd_env() {
  local newroot="$1"
  [ -z "$newroot" ] && exit 1
#  passwd_lib_need=$(ldd /usr/bin/passwd | cut -d" " -f3)
#  [ -f "$newroot/usr/bin/passwd" ] && rm -f $newroot/usr/bin/passwd
#  [ -f "$newroot/bin/echo" ] && rm -f $newroot/bin/echo
#  for f2rm in $passwd_lib_need; do
#    [ -f $newroot/$f2rm ] && rm -f $newroot/$f2rm
#  done
#  for f2rm in $PAM_need_files; do
#    [ -f $newroot/$f2rm ] && rm -f $newroot/$f2rm
#  done
#  [ -f "$newroot/bin/sh" ] && rm -f $newroot/bin/sh
  for d2rm in bin lib usr; do
    if [ -d "$newroot/$d2rm" ]; then
      [ -n "$verbose" ] && echo "Cleaning the unnecessary directory $newroot/$d2rm ..."
      rm -rf $newroot/$d2rm
    fi
  done
}

# check if the user input /dev/hda, /dev/hdb...
check_input_hd() {
    local target_hd="$1"
    case "$target_hd" in
	 [hs]d[a-z])
	   continue
	   ;;
	 *)
	  echo "Unknown HD device! Program stop!"
          Usage
	  exit 1
    esac
}

## description:
##   convert devfs names into normal short ones, written by Tom Rini.
fixdevfs() {
    ## get partition number, if any
    local PARTNUM="${1##*[a-z]}"
    ## Find the bus type.
    local TYPE="$(v=${1#/dev/} ; echo ${v%/host*})"
    ## Find the host number.
    local HOST="$(v=${1#/dev/*/host} ; echo ${v%/bus*})"
    ## Find the bus number.
    local BUS="$(v=${1#/dev/*/bus} ; echo ${v%/tar*})"
    ## Find the target.
    local TARGET="$(v=${1#/dev/*/target} ; echo ${v%/lun*})"

    case "$TYPE" in
	ide)
	case "$HOST" in
	    0)
	    case "$TARGET" in
		0)
		local DEV=hda
		;;
		1)
		local DEV=hdb
		;;
	    esac
	    ;;
	    1)
	    case "$TARGET" in
		0)
	        local DEV=hdc
	        ;;
		1)
		local DEV=hdd
		;;
	    esac
	    ;;
	    2)
	    case "$TARGET" in
		0)
	        local DEV=hde
	        ;;
		1)
		local DEV=hdf
		;;
	    esac
	    ;;
	    3)
	    case "$TARGET" in
		0)
	        local DEV=hdg
	        ;;
		1)
		local DEV=hdh
		;;
	    esac
	    ;;
	    *)
		echo "${1#/dev/}"
		#echo "Unable to translate this device, try again without devfs."
		return 1
	esac
	local DEV="${DEV}${PARTNUM}"
	echo "$DEV"
	return 0
	;;
	scsi)
	local LUN="$(v=${1#/dev/*/lun} ; echo ${v%/*})"

	## In this case, we need to figure out what number our device is
	local DEVCOUNT=0

	## copy scsi file into a variable removing "Attached Devices"
	## which is the first line. this avoids a lot of
	## [incmopatible] crap later, and improves readability.

	## find number of lines once and recycle that number, to save
	## some time (linecount is a bit slow). subtract one line
	## to scrap Attached Devices:

	local SCSILINES="$(($(wc -l /proc/scsi/scsi | cut -d' ' -f1) - 1))"
	local PROCSCSI="$(cat /proc/scsi/scsi | tail -n $SCSILINES)"

	for i in $(seq $(($SCSILINES / 3))) ; do

	    ## put every scsi device into one single line
	    local DEVINFO="$(echo "$PROCSCSI" | head -n $(($i * 3)) | tail -n 3)"
	    [ "$DEBUG" = 1 ] && echo 1>&2 "$PRG: DEBUG: fixdevfs: DEVINFO=$DEVINFO"

	    ## cut the type field, expect "Direct-Access" later.
	    local DEVTYPE="$(v=$(echo ${DEVINFO##*Type: }) ; echo ${v%% *})"
	    [ "$DEBUG" = 1 ] && echo 1>&2 "$PRG: DEBUG: fixdevfs: DEVTYPE=$DEVTYPE"

	    if [ "$DEVTYPE" = "Direct-Access" ] ; then
		## Lets find out some more information
		## get the device id.
		local DEVID="$(v=$(echo ${DEVINFO##*Id: }) ; n=$(echo ${v%% *}) ; echo ${n#*0})"
		[ "$DEBUG" = 1 ] && echo 1>&2 "$PRG: DEBUG: fixdevfs: DEVID=$DEVID"

		## get the device lun.
		local DEVLUN="$(v=$(echo ${DEVINFO##*Lun: }) ; n=$(echo ${v%% *}) ; echo ${n#*0})"
		[ "$DEBUG" = 1 ] && echo 1>&2 "$PRG: DEBUG: fixdevfs: DEVLUN=$DEVLUN"

		## get the device channel.
		local DEVCHAN="$(v=$(echo ${DEVINFO##*Channel: }) ; n=$(echo ${v%% *}) ; echo ${n#*0})"
		[ "$DEBUG" = 1 ] && echo 1>&2 "$PRG: DEBUG: fixdevfs: DEVCHAN=$DEVCHAN"

		## get the scsi host id.
		local DEVHOST="$(v=$(echo ${DEVINFO##*Host: scsi}) ; echo ${v%% *})"
		[ "$DEBUG" = 1 ] && echo 1>&2 "$PRG: DEBUG: fixdevfs: DEVHOST=$DEVHOST"

		local DEVCOUNT="$(($DEVCOUNT + 1))"
		[ "$DEBUG" = 1 ] && echo 1>&2 "$PRG: DEBUG: fixdevfs: DEVCOUNT=$DEVCOUNT"
		if [ "$DEVHOST" = "$HOST" -a "$DEVCHAN" = "$BUS" -a \
		    "$DEVID" = "$TARGET" -a "$DEVLUN" = "$LUN" ] ; then
		    local DEV="sd$(smalltr $DEVCOUNT)${PARTNUM}"
		    echo "$DEV"
		    return 0
		fi
	    fi
	done
	echo "${1#/dev/}"
	#echo "Unable to translate this device, try again without devfs."
	return 1
	;;
	*)
	echo "${1#/dev/}"
	#echo "Unknown bus"
	return 1
	;;
    esac
    ## we should never get here
    return 1
}

conv_devfspart_to_tradpart() {
   # function to convert devfs partitions to traditional partitions
   # fix the disk/partition, we do not want the devfs style for clonezilla
   TARGET_FILE="$1"
   [ -z "$TARGET_FILE" ] && echo "No output filename! Program stop!!!" && return 1
   [ -f "$TARGET_FILE" ] && rm -f $TARGET_FILE
   while read major minor block p x; do
     p="$(fixdevfs "/dev/$p")"
     echo "$major $minor $block $p" >> $TARGET_FILE
   done < /proc/partitions
}

# get dhcpd interface
get_dhcpd_interface() {
  local INTERFACES= INTERFACES_TMP=
  # get the setting of dhcp server
  . /etc/sysconfig/dhcpd
  # collect those interface, filter out other options, such as -p 60
  INTERFACES=""
  for ieth in $DHCPDARGS; do
     INTERFACES_TMP="$(echo $ieth |grep -o eth.*)"
     INTERFACES="$INTERFACES $INTERFACES_TMP"
  done
  echo $INTERFACES
}

# HD dma status
check_hd_dma() {
   # input example: /dev/hda
   # return 0/1, 0: DMA off, 1: DMA on
   local hd_dev=$1
   DMA_status=$(/sbin/hdparm -d $hd_dev |grep using_dma |awk  '{print $3}')
   echo "$DMA_status"
}

# turn on hd dma
turn_on_hd_dma() {
    local hd_dev="$1"
    if [ -n "$hd_dev" ]; then
      if [ "$(check_hd_dma $hd_dev)" = "0" ]; then
        # turn on DMA if it's off (0)
        /sbin/hdparm -d1m1c1 $hd_dev
      fi
      # check the result
      DMA=`check_hd_dma $hd_dev`
      if [ -n "$DMA" ]; then
         # This is IDE device, we can get DMA status
         [ "$DMA" -eq 0 ] && echo "Warning!!! Failed to turn on harddisk \"$hd_dev\" DMA... The clone performance might be NOT good..." && sleep 5
      else
         # This is SCSI device, we can not get DMA status
         echo "No HD DMA information, maybe this not a IDE device!"
      fi
    else
         echo "To turn on HD dma, you have to specify the HD dev!"
    fi
}

# do not let screen be blank
screen_not_blank() {
    /usr/bin/setterm -blank 0
}

# set passwd login for single user mode
set_clients_rc1_passwd() {
  local rc1_sulogin="~~:S:wait:/sbin/sulogin"
  IP_LIST=$*
  # make a flag "LIST_HOST"
  [ -n "$IP_LIST" ] && LIST_HOST="on"

  for ihost in $drblroot/*; do
     # skip those IP not listed in the $IP_LIST
     if [ "$LIST_HOST" = "on" ]; then
       [ -z "$(echo $IP_LIST | grep ${ihost##/*/})" ] && continue
     fi

    if ! grep -q ^$rc1_sulogin $ihost/etc/inittab ; then
      echo "Set the single user password for client ${ihost##/*/}, this will be safer..."
      cat <<-EOF >> $ihost/etc/inittab

# Single User Mode Password
$rc1_sulogin
EOF
    fi
  done
}

# function to active the /proc/partitions. For SCSI devices, even when device
# modules is loaded, the device is not shown in /proc/partitions if no program
# like fdisk or sg_map runs... We also do it for /dev/hdx.
active_proc_partitions() {
   echo -n "Activating the partition info in /proc..."
   for ihd in /dev/[hs]d[a-z]; do
      fdisk -l $ihd &> /dev/null
   done
   echo "done!"
}
#
get_lang_index() {
   local lang=$1
   local language
   case "$lang" in
      "1")
           language="tw.BIG5"
           ;;
      "2")
           language="tw.UTF-8"
           ;;
       *)
           language="en"
           ;;
   esac
   echo $language
}

check_kernel_nfsd_tcp_config() {
  # note the kernel should be the one is using in the drbl server
  kernel_ver=$(uname -r)
  # check if /boot/config-$kernel_ver exists
  # kernel config is either in /boot/ 
  kernel_config="/boot/config-$kernel_ver"
  echo "Checking the kernel config \"$kernel_config...\""
  if [ ! -f $kernel_config ]; then 
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "$kernel_config does NOT exist!"
    echo "I can not judge wheither NFS over TCP is supported in the kernel you are using!!!"
    echo "We will assume that the NFS server use UDP protocol!"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    protocol="udp"
  else
    # we assume the the priority is higher for EXT2, i.e. EXT2 option will overwrite the CRAMFS.
    if [ -n "$(grep "^CONFIG_NFSD_TCP=y" $kernel_config)" ]; then
      protocol="tcp"
    else
      [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
      echo "$kernel_config is found but NFS over TCP is not supported in the kernel you are using for the DRBL clients!!!"
      echo "We will use NFS over UDP !"
      [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
      protocol="udp"
    fi
  fi
  case "$protocol" in
    [tT][cC][pP])
       rc=0 
       ;;
    [uU][dD][pP])
       rc=1 
       ;;
  esac
  return $rc
}

switch_clients_init() {
   # input: client_init IP_LIST
   # if IP_LIST is none, this function will process all the clients.
   local CLIENT_INIT
   local IP_LIST
   CLIENT_INIT=$1
   shift
   IP_LIST=$*
   # make a flag "LIST_HOST"
   [ -n "$IP_LIST" ] && LIST_HOST="on"
   [ -z "$CLIENT_INIT" ] && echo "No specified mode for client inittab!!! Program stop!!!" && exit 1
   for host in $drblroot/*; do
     # skip those IP not listed in the $IP_LIST
     if [ "$LIST_HOST" = "on" ]; then
       [ -z "$(echo $IP_LIST | grep ${host##/*/})" ] && continue
     fi

     echo "Setting the graphic mode for node IP = $(basename $host)..."
     # set the init to 5, i.e. default is X
     /usr/bin/perl -p -i -e "s/^id:[1-5]:initdefault:/id:$CLIENT_INIT:initdefault:/g" $host/etc/inittab
   done
}

# This "NEWER" program is borrowed from http://staff.washington.edu/corey/new-patches.cgi
# The original author:
# Corey Satten, corey @ cac.washington.edu, 06/26/03, release 1.8
# For the latest version visit: http://staff.washington.edu/corey/tools.html
#
# Modified by Blake Huang and Steven Shiau to use in DRBL
NEWER='
    # Function newer:
    # Return 1 if second arg is "newer" than the first arg, else return 0.
    #
    # Because extra dotted fields before a hyphen are more significant
    # than those after a hyphen, first split on hyphens, then loop over
    # dotted fields passing the hard alphanumerics to function "compare"
    # for further splitting and comparing.
    #
    # eg.  older bind-utils-8.2.2_P5-9     and  older gzip-1.2.4-14
    #      newer bind-utils-8.2.2_P7-0.6.2 and  newer gzip-1.2.4a-2
    #
    #      older apmd-3.0beta9-3           and  older rmt-0.4b4-11 
    #      newer apmd-3.0final-2           and  newer rmt-0.4b19-5.6x

    function newer(a, b,    na1, nb1, na, nb, minn, i, j) {
	#printf("newer called with %s %s\n", a, b)
	if ('"${EFLAG-0}"') return a!=b
	if (O) {na=a; a=b; b=na}
	na1 = split(a, A1, /-/)
	nb1 = split(b, B1, /-/)
	if (na1 != nb1) {
	  #printf "unsure about %s and %s\n", a, b > "/dev/stderr"
	  return 1 }
	for (j=1; j<=na1; ++j) {
	  na = split(A1[j], A, /\./)
	  nb = split(B1[j], B, /\./)
	  minn = na < nb ? na : nb
	  for (i=1; i<=minn; ++i) {
	    if ('"${DEBUG-0}"') \
	      printf(" newer%d comparing %s %s\n", i, A[i], B[i])>"/dev/stderr"
	    if ((A[i] B[i]) ~ /^[0-9]+$/) {
	      if (A[i]+0 < B[i]+0) return 1
	      if (A[i]+0 > B[i]+0) return 0 }
	    else if (A[i] "" != B[i] "") return compare(A[i], B[i])
	    }
	  if (nb > na) return 1
	  if (nb < na) return 0
	  }
	return 0
	}

    # Function compare (called only by function newer):
    # Return 1 if second arg is "newer" than the first arg, else return 0.
    #
    # This is harder than it looks: consider "v9" vs "v10a", etc.
    # split out and compare alternating fields of numeric and non-numeric

    function compare (a, b,    xa, xb) {
	#printf(" compare called with %s %s\n", a, b)
	while (length(a) && length(b)) {
	  if (a ~ /^[0-9]/) {
	    match(a, /^[0-9]+/)
	    xa = substr(a, 1, RLENGTH); a = substr(a, RLENGTH+1) }
	  else {
	    match(a, /^[^0-9]+/)
	    xa = substr(a, 1, RLENGTH); a = substr(a, RLENGTH+1) }
	  if (b ~ /^[0-9]/) {
	    match(b, /^[0-9]+/)
	    xb = substr(b, 1, RLENGTH); b = substr(b, RLENGTH+1) }
	  else {
	    match(b, /^[^0-9]+/)
	    xb = substr(b, 1, RLENGTH); b = substr(b, RLENGTH+1) }
	    #printf("  compare2 %s %s <%s> <%s>\n", xa, xb, a, b)
	  if ( (xa xb) ~ /^[0-9]+$/) {
	    if (xa+0 < xb+0) return 1
	    if (xa+0 > xb+0) return 0 }
	  else {
	    if (xa "" < xb "") return 1
	    if (xa "" > xb "") return 0 }
	  }
	if (length(b)) return 1
	else return 0
	}
    '
#
set_specific_host_pxe_conf() {
  local HOSTS="$*"
  for ih in $HOSTS; do
    case "$ih" in
      *.*.*.*)
        # list by IP
	# the pxecfg will like "C0A80001"
	 pxecfg="$(/usr/bin/gethostip $ih | cut -d" " -f3)"
         ;;
      *:*:*:*:*:*)
        # list by MAC
	# for an Ethernet (ARP type 1) with address 88:99:AA:BB:CC:DD it would search for the filename 01-88-99-aa-bb-cc-dd (lowercase)
	 pxecfg="$(echo $ih | tr "[A-Z]" "[a-z]" | tr ":" "-")"
	 # append "01-" in the beginning
	 pxecfg="01-$pxecfg"
         ;;
    esac
    echo -n "Generate the PXE config file for host $ih ... "
    cp -f $PXELINUX_DIR/default_skeleton $PXELINUX_DIR/$pxecfg
    echo "done!"
  done
}

# function to get the pxecfg image block line number
get_pxecfg_image_block() {
     local IMG_NAME="$1"
     local PXE_CONF_TMP="$2"
     [ -z "$IMG_NAME" ] && exit 1
     # By using
     # grep -Ei -n '^[[:space:]]*label[[:space:]]+.*([[:space:]]|$)+' /tftpboot/nbi_img/pxelinux.cfg/default | grep -A1 -E "label[[:space:]]+drbl([[:space:]]|$)+"
     # 10:label drbl
     # 17:label local
     # so we know we can replace the one between line no. 10 and 17
     between_lines=$(grep -Ei -n "^[[:space:]]*label[[:space:]]+.*([[:space:]]|$)+" $PXE_CONF_TMP | grep -E -A1 "label[[:space:]]+$IMG_NAME([[:space:]]|$)+" | cut -d":" -f1)
     begin_line=$(echo $between_lines | awk -F" " '{print $1}')
     end_line=$(echo $between_lines | awk -F" " '{print $2}')
     # if end_line is nothing, it must be the last block, i.e. we can not find the next [.*]
     if [ -z "$end_line" ]; then
       end_line=$(wc -l $PXE_CONF_TMP | awk -F" " '{print $1}')
     else
       # if not nothing, backword one line
       end_line=$(($end_line - 1))
     fi
     echo "$begin_line $end_line"
}

check_img_in_pxe_cfg() {
     local IMG="$1"
     local PXE_CONF_TMP="$2"
     if [ -z "$IMG" ]; then
        [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
        echo "You must specify the image name! Program terminated!!!"
        [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
        exit 1
     fi
     if ! grep -Eiq "^[[:space:]]*label[[:space:]]+$IMG" $PXE_CONF_TMP; then
        [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
        echo "Unable to find the image ($IMG) label! Make sure the $IMG is labeled in $PXE_CONF_TMP! Program terminated!!!"
        [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
        exit 1
     fi
}

sub_default_pxe_img() {
     local IMG="$1"
     local PXE_CONF_TMP="$2"
     local MENU_LABEL="$3"
     check_img_in_pxe_cfg $IMG $PXE_CONF_TMP
     # turn off all MENU DEFAULT
     echo "Turn off all MENU DEFAULT... "
     perl -pi -e "s/^(#|[[:space:]])*MENU DEFAULT.*/  # MENU DEFAULT/i" $PXE_CONF_TMP
     # turn on MENU HIDE except some
     all_label="$(awk '/^[[:space:]]*label[[:space:]]+.*([[:space:]]|$)+/ {print $2}' $PXE_CONF_TMP)"
     for i in $all_label; do
       if [ -z "$(echo $IMG_SHOW_IN_MENU | grep -i $i)" ]; then
         [ -n "$VERBOSE" ] && echo "Hide image $i"
         hide_reveal_pxe_img $i hide $PXE_CONF_TMP
       fi
     done

     # turn on MENU DEFAULT & turn off MENU HIDE for the specified image
     lines=$(get_pxecfg_image_block $IMG $PXE_CONF_TMP)
     begin_line=$(echo $lines | awk -F" " '{print $1}')
     end_line=$(echo $lines | awk -F" " '{print $2}')
     echo "Make \"$IMG\" as default label in $PXE_CONF_TMP."
     sub_def_cmd="if ($begin_line..$end_line) {s/^(#|[[:space:]])*MENU DEFAULT.*/  MENU DEFAULT/i}"
     sub_hide_cmd="if ($begin_line..$end_line) {s/^(#|[[:space:]])*MENU HIDE.*/  # MENU HIDE/i}"
     perl -pi -e "$sub_def_cmd" $PXE_CONF_TMP
     perl -pi -e "$sub_hide_cmd" $PXE_CONF_TMP

     if [ -n "$MENU_LABEL" ]; then
       echo The MENU LABEL is \"$MENU_LABEL\"
       sub_menu_label_cmd="if ($begin_line..$end_line) {s/^[[:space:]]*MENU LABEL.*/  MENU LABEL $MENU_LABEL/i}"
       perl -pi -e "$sub_menu_label_cmd" $PXE_CONF_TMP
     fi
}

hide_reveal_pxe_img() {
     local IMG="$1"
     local ACT="$2"
     local PXE_CONF_TMP="$3"
     check_img_in_pxe_cfg $IMG $PXE_CONF_TMP
     # turn off MENU DEFAULT & turn on MENU HIDE for the specified image
     lines=$(get_pxecfg_image_block $IMG $PXE_CONF_TMP)
     begin_line=$(echo $lines | awk -F" " '{print $1}')
     end_line=$(echo $lines | awk -F" " '{print $2}')
     case "$ACT" in
       "hide")
         [ -n "$VERBOSE" ] && echo "Hide $IMG in $PXE_CONF_TMP... "
         sub_act_cmd="if ($begin_line..$end_line) {s/^(#|[[:space:]])*MENU HIDE.*/  MENU HIDE/i}"
         ;;
       "reveal")
         [ -n "$VERBOSE" ] && echo "Unhide $IMG in $PXE_CONF_TMP... "
         sub_act_cmd="if ($begin_line..$end_line) {s/^(#|[[:space:]])*MENU HIDE.*/  # MENU HIDE/i}"
         ;;
     esac
     perl -pi -e "$sub_act_cmd" $PXE_CONF_TMP
}

#
ask_and_load_lang_set() {
  local language_opt="$1"
  # lang will be the global variable
  # get the language
  if [ -z "$language_opt" ]; then
    # Language
    echo "Language | y| 語言 ?".
    echo "[0]: English"
    echo "[1]: Chinese Traditional (Big5) - Taiwan |  (jX) - xW".
    echo "[2]: Chinese Traditional (UTF-8, Unicode) - Taiwan | 中文 (萬國標準碼) - 台灣"
    echo -n "[0] "
    read lang_answer
  else
    lang_answer="$language_opt"
  fi
  case "$lang_answer" in
     1)
        lang="tw.BIG5"
        ;;
     2)
        lang="tw.UTF-8"
        ;;
     tw.*)
        lang="$lang_answer"
        ;;
     *)
        lang="en"
  esac
  
  # get the l10n message
  if [ -f "$DRBL_SCRIPT_PATH/lang/bash/$lang" ] ; then
    . $DRBL_SCRIPT_PATH/lang/bash/$lang
  else
    echo "Not such language option!!!"
    exit 1
  fi
} # end of ask_and_load_lang_set

#
language_help_prompt_by_idx_no() {
  echo "-l, --language  Set the language to be shown by index number"
  echo "                [0]: English, [1]: Chinese Traditional (Big5) - Taiwan, [2] Chinese Traditional (UTF-8, Unicode) - Taiwan"
}
#
language_help_prompt_by_idx_name() {
  echo "-ln             Set the language to be shown by index name, not number, such as en, tw.Big5, tw.UTF-8"
}
