# Author: Steven Shiau <steven _at_ nchc org tw>
# License: GPL

###
### functions for drbl-ocs, ocs-sr and ocs-onthefly
###
#
min() {
  if [ -n "$1" ] && [ -n "$2"  ]; then
    if [ "$1" -lt  "$2" ]; then
      echo $1;
    else
      echo $2;
    fi
  fi
}
#
cciss_dev_map_if_necessary() {
  # Function to map the cciss dev (/dev/cciss/c0d0p1, /dev/cciss/c0d0p2...) as normal block dev (Ex, /dev/sda, /dev/sdb...)
  # Provided by Ron Kelley in June/2007
  # Modified by Steven Shiau in Aug/2008
  # This is special for Compaq's SMART Array Controllers (Driver for HP Controller SA5xxx SA6xxx), the driver is "cciss" (Ex. /lib/modules/2.6.17-11-generic/kernel/drivers/block/cciss.ko)
  # Related ref:
  # http://kbase.redhat.com/faq/FAQ_79_4929.shtm
  # https://sourceforge.net/forum/message.php?msg_id=4373795
  # https://sourceforge.net/forum/message.php?msg_id=4377537
  # https://sourceforge.net/forum/message.php?msg_id=4381338
  local part_tab="$1"
  local cciss_tmp line a b
  [ -z "$part_tab" ] || [ ! -e "$part_tab" ] && return 1
  # Check both kernel module loaded and the dev file
  if [ -d /proc/driver/cciss -a -d /dev/cciss ]; then
    echo "Mapping the device list for cciss dev..."
  else
    echo "No cciss drivers loaded.  Skip cciss related actions..."
    return 1
  fi
  cciss_tmp="$(mktemp /tmp/cciss_out.XXXXXX)"
  # Create the mapping table and link the device name in /dev
  create-cciss-mapping $cciss_tmp
  # The content of part_tab is like:
  #    Major Minor #blocks name 
  #    104 0 143367120 cciss/c0d0 
  #    104 1 104391 cciss/c0d0p1 
  #    104 2 143259637 cciss/c0d0p2 
  #    104 16 143367120 cciss/c0d1 
  #    104 17 143364028 cciss/c0d1p1 
  #
  # The content of $cciss_tmp is like:
  # cciss/c0d0: sdb
  # cciss/c0d0p1: sdb1
  # cciss/c0d0p2: sdb2
  # By using $cciss_tmp, we can convert the cciss/c0d0p1 in $part_tab to sdb1
  while read line; do 
    # get the mapping list
    a="$(echo $line | awk -F":" '{print $1}')"
    b="$(echo $line | awk -F":" '{print $2}')"
    perl -pi -e "s|$a\$|$b|g" $part_tab
  done < $cciss_tmp
  # clean the tmp file
  [ -e "$cciss_tmp" ] && rm -f "$cciss_tmp"
} # end of cciss_dev_map_if_necessary
#
get_random_time() {
  local TIME_LIMIT=$1
  local ran0 ran1 ran time
  [ -z "$TIME_LIMIT" ] && TIME_LIMIT="$NOTIFY_OCS_SERVER_TIME_LIMIT"
  # Found ip, ocs_server, mac, we can notify ocs server
  # To avoid all the clients notify at almost same time, we use random sleep before send info.
  # Ref: http://www.faqs.org/docs/abs/HTML/randomvar.html
  # We need extra uniq seed number, otherwise all the clients boot at same time, same boot sqeuence, will have same Unix time, PID, PPID... then the random will be the same. 
  # "ifconfig -a" will give the uniq value, since it contains uniq IP address and MAC address.
  # "$RANDOM" is bash built-in random number, Nominal range: 0 - 32767 (signed 16-bit integer).
  # NOTE: ran0: max 10 digits; ran1: max 5 digits, we want ran1 in the order 0-100, so /1000.0
  ran0="$(ifconfig -a | cksum | cut -f1 -d" ")"
  ran1="$(echo "$ran0 / $RANDOM / 1000.0" | bc -l)"
  ran="$(echo | awk "{ srand($ran1); print rand() }")"
  time="$(echo "scale=0; $TIME_LIMIT * $ran / 1" |bc -l)"
  # in case, if time is empty, give it a value.
  [ -z "$time" ] && time=$TIME_LIMIT
  echo $time
}
#
get_image_cat_zip_cmd() {
  #  cat_prog, zip_stdin_cmd and unzip_stdin_cmd are global variables.
  local imgf="$1"
  if [ ! -e "$imgf" ]; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
    echo "$imgf not found!!! $msg_program_stop!"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    exit 1
  elif [ -n "$(file -Ls $imgf | grep -i "gzip compressed data")" ]; then
    # Ex: hdb1.000: gzip compressed data, from Unix
    # Actually zcat is identical to gunzip -c
    cat_prog="zcat"
    zip_stdin_cmd="gzip -c $extra_gzip_opt"
    unzip_stdin_cmd="gunzip -c"
  elif [ -n "$(file -Ls $imgf | grep -i "bzip2 compressed data")" ]; then
    # Ex: hdb1.000: bzip2 compressed data, block size = 900k
    # Actually bzcat is identical to bzip2 -dc or bunzip2 -c
    cat_prog="bzcat"
    zip_stdin_cmd="bzip2 -c"
    unzip_stdin_cmd="bunzip2 -c"
  elif [ -n "$(file -Ls $imgf | grep -i "lzop compressed data")" ]; then
    # Ex: hdb1.ntfs-img: lzop compressed data - version 1.010, LZO1X-1, os: Unix
    cat_prog="lzop -dc"
    zip_stdin_cmd="lzop -c"
    unzip_stdin_cmd="lzop -dc"
  elif [ -n "$(file -Ls $imgf | grep -i "PartImage file .* not compressed")" ]; then
    # Ex: hdb1.000: PartImage file version 0.6.1 volume 0 type reiserfs-3.6 device /dev/hdb1, original filename hdb1, not compressed
    cat_prog="cat"
    zip_stdin_cmd="cat"
    unzip_stdin_cmd="cat"
  elif [ -n "$(echo $imgf | grep -iE "\.ntfs-img")" -a -n "$(file -Ls $imgf | grep -i ":.* data$")" ]; then
    # Ex: "hda1.ntfs-img: data"
    # This is ntfs-img, uncompressed.
    # Thanks to Guy Carpenter <guyc _at_ atgis com au> for reporting and identifying this bug.
    # If the image is on Samba server, the result is:
    # Ex: "hda1.ntfs-img: setgid sticky data"
    # Thanks to <ericj.tw _at_ gmail.com> for reporting this bug.
    cat_prog="cat"
    zip_stdin_cmd="cat"
    unzip_stdin_cmd="cat"
  elif [ -n "$(echo $imgf | grep -iE "\.dd-img")" ]; then
    # Ex: "hda1.dd-img"
    # This is dd-img, since the above testing all fails, we assume it's uncompressed.
    cat_prog="cat"
    zip_stdin_cmd="cat"
    unzip_stdin_cmd="cat"
  fi
  if [ -z "$cat_prog" ]; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
    echo "Unknown format for $imgf!!! $msg_program_stop!"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    exit 1
  fi
} # end of get_image_cat_zip_cmd
#
conv_return_code_to_human_read() {
  local rcd="$1"
  # clone_status is global variable
  if [ "$rcd" -eq 0 ]; then
    clone_status="success"
  else
    clone_status="***FAIL***"
  fi
} # end of conv_return_code_to_human_read
#
calculate_elapsed_time() {
  local start_t=$1
  local end_t=$2
  # time_elapsed and time_elapsed_in_min are global variable
  time_elapsed="$(LC_ALL=C echo "scale=2; ($end_t - $start_t)/(1*10^9)" | bc -l)"
  time_elapsed_in_min="$(LC_ALL=C echo "scale=3; $time_elapsed / 60 *1.0" | bc -l)"
}
# end of calculate_elapsed_time
#
disk_full_test() {
  local tgt_dir rc prompt_msg
  tgt_dir="$1"
  prompt_msg="$2"
  [ -z "$tgt_dir" ] && return 1
  diskfull_test="$tgt_dir/DISK_FULL"
  # Testing using 1MB, since small file maybe is accepted when disk is full.
  if ! dd if=/dev/zero of=$diskfull_test bs=1M count=1 &>/dev/null; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
    echo "$tgt_dir is full! No space left on device!"
    [ -n "$prompt_msg" ] && echo $prompt_msg
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo "$msg_program_stop!"
    [ -f "$diskfull_test" ] && rm -f $diskfull_test
    echo -n "Press Enter to exit..."
    read
    exit 1
  fi
  [ -f "$diskfull_test" ] && rm -f $diskfull_test
} # end of disk_full_test
#
trigger_dd_status_report() {
  local pt_dev="$1"
  local pt_s="$2"
  echo "dd status update interval: $dd_report_interval secs"
  while sleep $dd_report_interval; do
    [ -n "$pt_s" ] && echo "Partition $pt_dev size: $pt_s"
    pkill -SIGUSR1 dd
  done
}
#
output_swap_partition_uuid_label() {
  local swap_dev="$1"
  local output_swap_info="$2"
  # save the UUID/LABEL of swap partition so that we can create the same one in the future.
  # The result of blkid is like:
  # blkid /dev/hda5
  # /dev/hda5: UUID="92d422f0-3a17-4e69-93f0-f8fd1091e5eb" TYPE="swap"
  # or
  # blkid /dev/hda2
  # /dev/hda2: TYPE="swap" UUID="d366f065-7714-4b4c-92db-f4b12b3e1f6b
  # about LABEL (used in FC):
  # /dev/sda3: TYPE="swap" LABEL="SWAP-sda3"
  echo $msg_delimiter_star_line
  if type blkid &>/dev/null; then
    echo "Saving swap $swap_dev info in $output_swap_info..."
    blkinfo="$(mktemp /tmp/blkinfo.XXXXXX)"
    blkid $swap_dev | grep -o -E '\<UUID="[^[:space:]]*"($|[[:space:]]+)' > $blkinfo
    blkid $swap_dev | grep -o -E '\<LABEL="[^[:space:]]*"($|[[:space:]]+)' >> $blkinfo
    UUID=""
    LABEL=""
    . $blkinfo
    [ -z "$UUID" -a -z "$LABEL" ] && echo "Warning! UUID and LABLE of swap dev $swap_dev are nothing!"
    echo "UUID=\"$UUID\"" > $output_swap_info
    echo "LABEL=\"$LABEL\"" >> $output_swap_info
    [ -f "$blkinfo" ] && rm -f $blkinfo
  else
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "Program blkid is NOT found! Without it, we can not find the UUID of swap partition! In some GNU/Linux, if it's /etc/fstab is based on UUID, swap partition maybe not be on after it's cloned! Since the swap partition is recreated by mkswap!"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
  fi
  echo $msg_delimiter_star_line
} # end of output_swap_partition_uuid_label
#
image_save() {
  # source_dev is like /dev/hda1
  local source_dev="$1"
  local tgt_dir="$2"
  local tgt_file="$3"
  local image_name_
  local part_fs
  local compress_prog_prompt
  local dd_report_sig_pid part_size
  # #target_dir is dir path like "/home/partimag/sarge-base/"
  # $tgt_file here is the file name like "hda1"
  # if the report_msg is empty, put the initial one: image_name_
  image_name_="$(dirname $tgt_dir)"
  [ -z "$report_msg" ] && report_msg="Saved $image_name_,"
  # time_elapsed, time_elapsed_in_min and speed are global variables
  echo $msg_delimiter_star_line
  check_if_source_dev_busy_before_saving $source_dev $image_name_
  echo $msg_delimiter_star_line
  echo "Starting saving $source_dev as $tgt_dir/${tgt_file}.XXX..."
  # get the filesystem
  part_fs="$($DRBL_SCRIPT_PATH/sbin/get_part_info $source_dev filesystem)"
  echo "$source_dev filesystem: $part_fs."
  if [ "$USE_NTFSCLONE" = "yes" -a "$part_fs" = "ntfs" ]; then
    # case (1): ntfsclone is preferred
    compress_prog_opt="$IMG_CLONE_CMP"
    [ -z "$IMG_CLONE_CMP" ] && compress_prog_opt="gzip -c $extra_gzip_opt"
    compress_prog_prompt="$(echo "$compress_prog_opt" | sed -e "s/ -.*//g")"
    echo $msg_delimiter_star_line
    # Before saving, we check the ntfs integrity if ntfs_integrity_check is not "no"
    if [ "$ntfs_integrity_check" != "no" ]; then
      check_ntfs_partition_integrity $source_dev
    else
      echo "Assume the NTFS integrity is OK. Skip checking it!"
    fi
    echo "Checking the disk space... " && disk_full_test $image_name_
    # assign ntfsclone tmp file.
    case "$ntfsclone_progress" in
    "image_dir")
       IP="$($DRBL_SCRIPT_PATH/bin/get-ip-link-2-drbl-srv)"
       ntfs_img_info_tmp="${image_name_}/saving-${IP}-${source_dev##/*/}-`date +%Y%m%d%H%M`" ;;
    *)
       ntfs_img_info_tmp="$(mktemp /tmp/ntfs_info.XXXXXX)" ;;
    esac
    echo "Use ntfsclone with $compress_prog_prompt to save the image instead of partimage."
    case "$VOL_LIMIT" in
      [1-9]*)
         echo "Split image file with size limit $VOL_LIMIT MB." ;;
      *)
         echo "Do not split image." ;;
    esac
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "If this action fails or hangs, check:"
    echo "* Is the disk full ?" 
    [ "$(root_over_nfs)" = "yes" ] && echo "* Network connection and NFS service."
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo $msg_delimiter_star_line

    start_time="$(date +%s%N)"
    # $ntfsclone_extra_opt is global variable from check_ntfs_partition_integrity
    if [ -n "$ntfsclone_extra_opt" ]; then
      [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
      echo "WARNING!!! ntfsclone is run with extra option(s): $ntfsclone_extra_opt"
      [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    fi
    ( ntfsclone $ntfsclone_extra_opt --save-image --output - $source_dev | \
      $compress_prog_opt | \
      (
       case "$VOL_LIMIT" in
         [1-9]*)
	    # $tgt_dir/${tgt_file}.ntfs-img. is prefix, the last "." is necessary
	    # make the output file is like hda1.ntfs-img.aa, hda1.ntfs-img.ab.
	    # We do not add -d to make it like hda1.ntfs-img.00, hda1.ntfs-img.01, since it will confuse people that it looks like created by partimage (hda1.ntfs-img.000, hda1.ntfs-img.001)
            split -b ${VOL_LIMIT}m - $tgt_dir/${tgt_file}.ntfs-img.
            ;;
         *)
            cat - > $tgt_dir/${tgt_file}.ntfs-img
            ;;
       esac
      )
    ) 2>&1 | \
    tee $ntfs_img_info_tmp
    rc="$?"
    end_time="$(date +%s%N)"
    # UGLY! Since we use ntfsclone, then pipe to compression program, then IO redirect to file, the error control is week. ntfsclone wil not exit even if the disk is full. Therefore we check here.
    echo "Checking the disk space... " && disk_full_test $image_name_ "The saved image file $tgt_dir/${tgt_file}.ntfs-img is incomplete! You have to increase the disk space, then start clonezilla save mode again."
    get_ntfs_image_info $ntfs_img_info_tmp $start_time $end_time
    # no preparation time, it's accurate enough.
    echo ">>> Time elapsed: $time_elapsed secs (~ $time_elapsed_in_min mins), average speed: $speed"
    [ -f "$ntfs_img_info_tmp" ] && rm -f $ntfs_img_info_tmp
    # For better security
    chmod 600 $tgt_dir/${tgt_file}.ntfs-img* 2>/dev/null
    # prepare statistic report
    conv_return_code_to_human_read $rc
    report_msg="$report_msg $source_dev, $clone_status, $space_used, $time_elapsed_in_min mins, $speed;"
    echo $msg_delimiter_star_line
    echo "Finished saving $source_dev as $tgt_dir/${tgt_file}.ntfs-img"
  elif `is_partimage_support_fs $part_fs`; then
    # case (2): partimage supported filesystem, we can use partimage to save it.
    # Use partimage and stdout pipe to lzop, gzip, bzip2
    # Since partimage does not support lzo, we use pipe to lzop the image, and this is a good approach. So use it for gzip, bzip2, too. i.e. do not let partimage to do gzip/bzip2/lzop, use external program.
    # partiamge will use the NO-GUI mode. When using pipe stdout, --volume=0 is a must. Otherwise it will create a file like stdout.000...
    # The basic idea is to use partimage, but compression is made by other program with pipe, so use -z0 in partimage. -z0 and --volume=0 are already assigned in drbl-ocs.conf
    #
    # Check the Blocks per group of ext2/ext3 is 32768 or not. If not, partimage does not support. Exit. Ref: http://sourceforge.net/forum/forum.php?thread_id=1833628&forum_id=663168
    # Note! From partimage 0.6.7_beta1 with patch file from Thomas Tsai, it's not necessary to do this check check_blocks_per_group_of_ext2_3.
    # [ "$part_fs" = "ext2" -o "$part_fs" = "ext3" ] && check_blocks_per_group_of_ext2_3 $source_dev $tgt_dir
    #
    compress_prog_opt="$IMG_CLONE_CMP"
    # if something went wrong, we convert to gzip.
    if [ -z "$IMG_CLONE_CMP" ] || ! type lzop &>/dev/null; then
      compress_prog_opt="gzip -c $extra_gzip_opt"
    fi
    compress_prog_prompt="$(echo "$compress_prog_opt" | sed -e "s/ -.*//g")"
    # Before saving, we check the filesystem integrity
    check_partimage_partition_integrity $source_dev
    echo "Use $compress_prog_prompt to compress the image."
    case "$VOL_LIMIT" in
      [1-9]*)
         echo "Split image file with size limit $VOL_LIMIT MB." ;;
      *)
         echo "Do not split image." ;;
    esac
    # it is important to run partimage in save mode with volume=0 and -B gui=no
    # so that we can send the data to stdout
    start_time="$(date +%s%N)"
    partimage $DEFAULT_PARTIMAGE_SAVE_OPT $PARTIMAGE_SAVE_OPT -B gui=no save $source_dev stdout | $compress_prog_opt | \
    (
     case "$VOL_LIMIT" in
       [1-9]*)
          # "$tgt_dir/${tgt_file}." is prefix, the last "." is necessary
          # make the output file is like hda1.aa, hda1.ab.
          # We do not add -d to make it like hda1.00, hda1.01, since it will confuse people that it looks like created by partimage (hda1.000, hda1.001)
          split -b ${VOL_LIMIT}m - $tgt_dir/${tgt_file}.
          ;;
       *)
          cat - > $tgt_dir/${tgt_file}
          ;;
     esac
    )
    rc="$?"
    end_time="$(date +%s%N)"
    calculate_elapsed_time $start_time $end_time
    echo ">>> Time elapsed: $time_elapsed secs (~ $time_elapsed_in_min mins)"
    # For better security
    chmod 600 $tgt_dir/${tgt_file}* 2>/dev/null
    # prepare statistic report
    conv_return_code_to_human_read $rc
    report_msg="$report_msg $source_dev, $clone_status, $time_elapsed_in_min mins;"
    echo $msg_delimiter_star_line
    echo "Finished saving $source_dev as $tgt_dir/${tgt_file}.XXX"
  else
    # case (3): not supported filesystem, the last choice is to use dd to dump.
    compress_prog_opt="$IMG_CLONE_CMP"
    [ -z "$IMG_CLONE_CMP" ] && compress_prog_opt="gzip -c $extra_gzip_opt"
    compress_prog_prompt="$(echo "$compress_prog_opt" | sed -e "s/ -.*//g")"
    echo $msg_delimiter_star_line
    echo "Checking the disk space... " && disk_full_test $image_name_
    dd_img_info_tmp="$(mktemp /tmp/dd_info.XXXXXX)"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "$msg_fs_not_supported_by_partimage_ntfsclone: $part_fs"
    echo "$msg_use_this_method_to_save_img: dd + $compress_prog_prompt"
    echo "$msg_cons_for_dd_clone"
    echo "$msg_will_be_inefficent_and_slow..."
    echo "$msg_status_report_is_very_primitive..."
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo $msg_delimiter_star_line
    case "$VOL_LIMIT" in
      [1-9]*)
         echo "Split image file with size limit $VOL_LIMIT MB." ;;
      *)
         echo "Do not split image." ;;
    esac
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "If this action fails or hangs after several minutes, check:"
    echo "* Is the disk full ?" 
    [ "$(root_over_nfs)" = "yes" ] && echo "* Network connection and NFS service."
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo $msg_delimiter_star_line

    # since dd does not report status with any option, we have to send SIGUSR1 to tell dd to report every some secs...
    # dd report interval (secs) is loaded from drbl-ocs.conf
    part_size="$($DRBL_SCRIPT_PATH/sbin/get_part_info $source_dev size)"
    trigger_dd_status_report $source_dev $part_size &
    dd_report_sig_pid=$!
    start_time="$(date +%s%N)"
    #
    ( LC_ALL=C dd bs=1M if=$source_dev | \
      $compress_prog_opt | \
      (
       case "$VOL_LIMIT" in
         [1-9]*)
	    # $tgt_dir/${tgt_file}.dd-img. is prefix, the last "." is necessary
	    # make the output file is like hda1.dd-img.aa, hda1.dd-img.ab.
	    # We do not add -d to make it like hda1.dd-img.00, hda1.dd-img.01, since it will confuse people that it looks like created by partimage (hda1.dd-img.000, hda1.dd-img.001)
            split -b ${VOL_LIMIT}m - $tgt_dir/${tgt_file}.dd-img.
            ;;
         *)
            cat - > $tgt_dir/${tgt_file}.dd-img
            ;;
       esac
      )
    ) 2>&1 | \
    tee $dd_img_info_tmp
    rc="$?"
    end_time="$(date +%s%N)"
    kill -9 $dd_report_sig_pid &>/dev/null
    # save the size so that when we restore the image, partition size can be shown in the status report. This is better than to get partition info from *-pt.sf (which is shown in sectors, not absolute value) or uncompress *.dd-img then get the partition info in the loop device file (not efficient).
    echo "$part_size" > $tgt_dir/${tgt_file}-size
    # UGLY! Since we use dd, then pipe to compression program, then IO redirect to file, the error control is week. dd wil not exit even if the disk is full. Therefore we check here.
    echo "Checking the disk space... " && disk_full_test $image_name_ "The saved image file $tgt_dir/${tgt_file}.dd-img is incomplete! You have to increase the disk space, then start clonezilla save mode again."
    get_dd_image_info $dd_img_info_tmp $start_time $end_time
    # no preparation time, it's accurate enough.
    echo ">>> Time elapsed: $time_elapsed secs (~ $time_elapsed_in_min mins), average speed: $speed"
    [ -f "$dd_img_info_tmp" ] && rm -f $dd_img_info_tmp
    # For better security
    chmod 600 $tgt_dir/${tgt_file}.dd-img* 2>/dev/null
    # prepare statistic report
    conv_return_code_to_human_read $rc
    report_msg="$report_msg $source_dev, $clone_status, $space_used, $time_elapsed_in_min mins, $speed;"
    echo $msg_delimiter_star_line
    echo "Finished saving $source_dev as $tgt_dir/${tgt_file}.dd-img"
  fi
  echo $msg_delimiter_star_line
  if [ "$rc" -ne 0 ]; then
     [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
     echo "Failed to save partition $source_dev! $msg_press_enter_to_continue..."
     [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
     read
  fi
  return $rc
} # end of image_save

# check if the user input /dev/hda, /dev/hdb...
check_input_hd() {
    local target_hd="$*"
    [ -z "$target_hd" ] && echo "No device selected!" && exit 1
    for idisk in $target_hd; do
      case "$idisk" in
           [hs]d[a-z])
             true
             ;;
           *)
            [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
            echo "\"$idisk\" is unknown HD device! Known and supported HD device is like hda, hdb, hdc, hdd... $msg_program_stop!"
            [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
            echo -n "Press Enter to exit..."
            read
            exit 1
      esac
    done
}
check_input_partition() {
    local tgt_parts="$*"
    [ -z "$tgt_parts" ] && echo "No device selected!" && exit 1
    for ipart in $tgt_parts; do
      case "$ipart" in
           [hs]d[a-z][0-9]*)
             continue
             ;;
           *)
            [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
            echo "\"$target_part\" is unknown partition device! Known and supported partition device is like hda1, hda2, hdb1, hdc1... $msg_program_stop!"
            [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
            echo -n "Press Enter to exit..."
            read
            exit 1
      esac
    done
}
#
check_specify_hd_exists() {
  # the parameter is like hda, sda...
  local check_target_hd=$1
  local partition_table
  if [ -z "$check_target_hd" ]; then
    echo "You have to assign a device to be checked!"
    echo "Skip checking HD!!!"
    return 1
  fi
  # Check if the target HD exists or not
  # it's devfs compiled into kernel, we will use the conv_devfspart_to_tradpart to convert the devfs style partitions into traditional partitions
  partition_table="$(mktemp /tmp/parttable.XXXXXX)"
  if grep -Eq '(scsi/host|ide/host)' /proc/partitions 2>/dev/null; then
     # it's devfs compiled into kernel...
     conv_devfspart_to_tradpart $partition_table
  else
     # the devfs is not compiled into kernel, good!
     # We' better not use "cp -a /proc/partitions $partition_table", use cat to get the kernel param
     cat /proc/partitions > $partition_table
     cciss_dev_map_if_necessary $partition_table
  fi
  if [ -z "$(grep -E "$check_target_hd\>" $partition_table)" ]; then
     [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
     echo "Unable to find target HD \"$check_target_hd\"!!!"
     echo "Check if the HD $check_target_hd really exists, otherwise maybe the kernel is too old!"
     echo "$msg_program_stop!!!"
     [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
     echo -n "Press \"Enter\" to exit."
     read
     exit 1
  fi
  [ -f "$partition_table" ] && rm -f $partition_table
}
#
get_known_partition_sf_format() {
    # This function is used with the output format of sfdisk
    local partition_table="$1"
    local known_parts="" fuzzy_parts
    # Note! https://sourceforge.net/forum/message.php?msg_id=4289601
    # Thanks to Tom Ed (vi0lam0n) for identifying this bug.
    # In Ubuntu 6.10, the output sfdisk with 10 or more partitions is like:
    # -----------------------
    ## partition table of /dev/hda
    #unit: sectors
    #
    #/dev/hda1 : start=       63, size=   196497, Id=83
    #/dev/hda2 : start=   196560, size=   196560, Id=83
    #/dev/hda3 : start=   393120, size=   196560, Id=83
    #/dev/hda4 : start=   589680, size= 16186905, Id= 5
    #/dev/hda5 : start=   589743, size=    98217, Id=83
    #/dev/hda6 : start=   688023, size=   196497, Id=83
    #/dev/hda7 : start=   884583, size=   196497, Id=83
    #/dev/hda8 : start=  1081143, size=   196497, Id=83
    #/dev/hda9 : start=  1277703, size=   196497, Id=83
    #/dev/hda10: start=  1474263, size=   196497, Id=83
    #/dev/hda11: start=  1670823, size=   196497, Id=83
    #/dev/hda12: start=  1867383, size= 14909202, Id=83
    #
    # Therefore we have to remove : in the end also (for hda10:, hda11:...)
    [ -z "$partition_table" ] && return 1
    known_parts="$(cat $partition_table | grep "Id=" | awk -F, '{ if($3!=" Id= 0" && $3!=" Id= f" && $3!=" Id=82" && $3!=" Id= 5" && $3!=" Id=85") print $1; }' | cut -d" " -f1 | sed -e 's/\/dev\///g' -e 's/:$//g')"
    # since Id=82 is for swap and Solaris, we have to check it if it's really swap or not
    known_parts="$(echo $known_parts)"  # make it a line
    if grep -q "Id=82\>" $partition_table 2>/dev/null; then
       fuzzy_parts="$(grep Id="82\>" $partition_table | cut -d" " -f1 | sed -e 's/:$//g')"
       # fuzzy_parts is like /dev/sda2
       for i in $fuzzy_parts; do
         if [ "$(get_part_info $i fs)" != "swap" ]; then
           known_parts="$known_parts ${i/\/dev\/}"
         fi
       done
    fi
    echo "$known_parts"
} # end of get_known_partition_sf_format
get_swap_partition_sf_format() {
    local partition_table="$1"
    local known_parts=""
    [ -z "$partition_table" ] && return 1
    if grep -q "Id=82\>" $partition_table 2>/dev/null; then
       fuzzy_parts="$(grep Id="82\>" $partition_table | cut -d" " -f1 | sed -e 's/:$//g')"
       # fuzzy_parts is like /dev/sda2
       for i in $fuzzy_parts; do
         if [ "$(get_part_info $i fs)" = "swap" ]; then
           known_parts="$known_parts ${i/\/dev\/}"
         fi
       done
    fi
    echo "$known_parts"
} # end of get_swap_partition_sf_format
#
conv_uuid_mount_to_tradpart() {
  local mounted_table=$1
  if [ -z "$mounted_table" -o ! -e "$mounted_table" ]; then
    echo "No mounted table file exists! Program terminated!!!"
    exit 1
  fi
  uuid_list="$(grep -E "^/dev/disk/by-uuid/" $mounted_table | awk -F" " '{print $1}')"
  # Example:
  # rootfs / rootfs rw 0 0
  # none /sys sysfs rw,nosuid,nodev,noexec 0 0
  # none /proc proc rw,nosuid,nodev,noexec 0 0
  # udev /dev tmpfs rw 0 0
  # /dev/disk/by-uuid/f3460329-25d4-467e-bb59-8f40ce78554a / reiserfs rw 0 0
  # /dev/disk/by-uuid/f3460329-25d4-467e-bb59-8f40ce78554a /dev/.static/dev reiserfs rw 0 0
  # /dev/disk/by-uuid/a65083db-aa77-48cd-ba75-df96c0b08013 /home reiserfs rw 0 0
  
  for i in $uuid_list; do
    # Find the source partition by file
    # Ex:
    # file -hs /dev/disk/by-uuid/7fc9980a-88c6-4132-aa26-2aa05f288956
    # /dev/disk/by-uuid/7fc9980a-88c6-4132-aa26-2aa05f288956: symbolic link to `../../sda2'
    src_part="$(file -hs $i | sed -e "s/symbolic link to//g" -e "s/'//g" | awk -F":" '{print $2}')"
    src_part="$(basename $src_part)"
    LC_ALL=C perl -pi -e "s|^$i|/dev/$src_part|g" $mounted_table
  done
} # end of conv_uuid_mount_to_tradpart
#
get_known_partition_proc_format() {
    # input param, [harddisk|partition]
    # To specify it's hardisk or partition
    # return the hardisks or partitions

    # BACKUP_DEVS is global variable
    local chosen_disk="$1"   # chosen_disk is like: "sda", "hda"...
    local chosen_mode="$2"   # chosen_mode is like: data or swap
    local dev_list=""
    local partition_table=""
    local mounted_table=""
    local target_dev=""
    local dev_chosen_def=""
    local part_list hdtmp msg_not_mounted_dev_found TMP FILE
    local skip_lvm="no"

    if [ -z "$chosen_disk" ]; then
       echo "Variable chosen_disk is empty! Program terminated!!!"
       exit 1
    fi
    if [ -z "$chosen_mode" ]; then
       echo "Variable chosen_mode is empty! Program terminated!!!"
       exit 1
    fi

    # it's devfs compiled into kernel, we will use the conv_devfspart_to_tradpart to convert the devfs style partitions into traditional partitions
    partition_table="$(mktemp /tmp/parttable.XXXXXX)"
    if grep -Eq '(scsi/host|ide/host)' /proc/partitions 2>/dev/null; then
       # it's devfs compiled into kernel...
       cat /proc/partitions > $partition_table
       conv_devfspart_to_tradpart $partition_table
    else
       # the devfs is not compiled into kernel, good!
       # We' better not use "cp -a /proc/partitions $partition_table", use cat to get the kernel param
       cat /proc/partitions > $partition_table
       cciss_dev_map_if_necessary $partition_table
    fi
    # CDROM won't be listed in /proc/partitions, so we do not have to worry about that when cloning.
    # Leave only selected disk
    LC_ALL=C perl -i -ne "print if /^.*$chosen_disk/ .. /.*$/" $partition_table

    # Strip those busy/mounted partitions and disk.
    part_list="$(LC_ALL=C awk '/[hs]d[a-z][0-9]+($| )/ { print $4; }' $partition_table | sort)"
    echo "Excluding busy partition or disk..."
    # Convert by uuid to traditional list in /proc/mounts so it's easier for us to exclude.
    # The format of /proc/mounts with uuid is like:
    # rootfs / rootfs rw 0 0
    # none /sys sysfs rw,nosuid,nodev,noexec 0 0
    # none /proc proc rw,nosuid,nodev,noexec 0 0
    # udev /dev tmpfs rw 0 0
    # /dev/disk/by-uuid/f3460329-25d4-467e-bb59-8f40ce78554a / reiserfs rw 0 0
    # ...
    mounted_table="$(mktemp /tmp/mnt_table.XXXXXX)"
    if grep -Eq 'by-uuid' /proc/mounts 2>/dev/null; then
       # with uuid format in /proc/mounts, good!
       cat /proc/mounts > $mounted_table
       conv_uuid_mount_to_tradpart $mounted_table
    else
       # no uuid format in /proc/mounts, good!
       # We' better not use "cp -a /proc/partitions $mounted_table", use cat to get the kernel param
       cat /proc/mounts > $mounted_table
    fi

    for ipart in $part_list; do
      if grep -qEw "^/dev/$ipart" $mounted_table; then
        # hda1 -> hda
        hdtmp=${ipart:0:3}
	# strip disk
        LC_ALL=C perl -i -ne "print unless /^.*$hdtmp[[:space:]]+/ .. /.*$/" $partition_table
	# strip parititon
        LC_ALL=C perl -i -ne "print unless /^.*$ipart[[:space:]]+/ .. /.*$/" $partition_table
      fi
    done

    dev_list=$(LC_ALL=C awk '/[hs]d[a-z][0-9]+($| )/ { print $4; }' $partition_table | sort)
    dev_list="$(echo $dev_list)"   # convert to 1 line
    echo "Unmouted partitions (including extended or swap): $dev_list"

    # exclude some partitions
    BACKUP_DEVS=""
    for p in $dev_list; do
       # Skip swap and extended partition
       # We do not use the output of fdisk -l, it's type mabe like
       # Extended, Ext'd, it's not easy to tell... We use file to get the
       # partition type
       echo "Getting /dev/$p info..."
       part_type="$(LC_ALL=C file -Ls "/dev/$p")"
       rc=$?
       [ "$rc" -gt 0 ] && continue 
       case "$chosen_mode" in
       data)
         case "$part_type" in *[Ss][Ww][Aa][Pp]*|*extended*) continue ;; esac
         if [ "$skip_lvm" = "yes" ]; then
           case "$part_type" in *[Ll][Vv][Mm]*) continue ;; esac
         fi
         ;;
       swap)
           [ -z "$(echo "$part_type" | grep -i swap)" ] && continue
         ;;
       esac
       BACKUP_DEVS="$BACKUP_DEVS $p"
    done
    [ -f "$mounted_table" ] && rm -f $mounted_table
    [ -f "$partition_table" ] && rm -f $partition_table
} # end of get_known_partition_proc_format

# check grub partition
check_grub_partition() {
  local select_disk="$1"  # select_disk is like hda or sda
  local partition_list=""
  found_grub_partition=""
  local fs
  # found_grub_partition is a global variable
  # it's devfs compiled into kernel, we will use the conv_devfspart_to_tradpart to convert the devfs style partitions into traditional partitions
  partition_table="$(mktemp /tmp/parttable.XXXXXX)"
  if grep -Eq '(scsi/host|ide/host)' /proc/partitions 2>/dev/null; then
     # it's devfs compiled into kernel...
     conv_devfspart_to_tradpart $partition_table
  else
     # the devfs is not compiled into kernel, good!
     # We' better not use "cp -a /proc/partitions $partition_table", use cat to get the kernel param
     cat /proc/partitions > $partition_table
     cciss_dev_map_if_necessary $partition_table
  fi
  if [ -n "$select_disk" ]; then
    # we have target disk, parsing it.
    partition_list="$(LC_ALL=C awk "/${select_disk}[0-9]+/ { print \$4; }" $partition_table)"
  else
    partition_list=$(LC_ALL=C awk '/[hs]d[a-z][0-9]+/ { print $4; }' $partition_table)
  fi
  for ipartition in $partition_list; do
    grub_partition="/dev/$ipartition"
    # If the partition is ntfs, skip. Since normally grub files are not in ntfs
    fs="$(get_part_info $grub_partition filesystem)"
    [ -n "$(echo "$fs" | grep -i "ntfs")" ] && continue
    # process the boot loader
    hd_img=`mktemp -d /tmp/hd_img.XXXXXX`
    mount $grub_partition $hd_img >/dev/null 2>&1
    mrc=$?
    # check if /boot is in its own partition, if so, different mount point.
    # grub root-directory must have "/boot/"
    if [ -d "$hd_img/boot/grub" -o -d "$hd_img/grub/" ]; then
       # Found the partition...
       # remount it as /tmp/hd_img.XXXXXX/boot
       found_grub_partition="$grub_partition"
       [ $mrc -eq 0 ] && umount $grub_partition
       [ -d "$hd_img" ] && rm -rf $hd_img
       [ -d "$hd_img/boot" ] && rm -rf $hd_img/boot
       break;
    fi
    [ $mrc -eq 0 ] && umount $grub_partition
    [ -d "$hd_img" ] && rm -rf $hd_img
    [ -d "$hd_img/boot" ] && rm -rf $hd_img/boot
  done
  [ -f "$partition_table" ] && rm -f $partition_table
}
#
mount_root_partition_for_separate_boot_part() {
  local hd_img="$1"
  local partition_list="" root_part="" lvm_list ilv fname mrc
  [ -z "$hd_img" ] && echo "You must assign mounting point in function mount_root_partition_for_separate_boot_part" && exit 1
  # it's devfs compiled into kernel, we will use the conv_devfspart_to_tradpart to convert the devfs style partitions into traditional partitions
  partition_table="$(mktemp /tmp/parttable.XXXXXX)"
  if grep -Eq '(scsi/host|ide/host)' /proc/partitions 2>/dev/null; then
     # it's devfs compiled into kernel...
     conv_devfspart_to_tradpart $partition_table
  else
     # the devfs is not compiled into kernel, good!
     # We' better not use "cp -a /proc/partitions $partition_table", use cat to get the kernel param
     cat /proc/partitions > $partition_table
     cciss_dev_map_if_necessary $partition_table
  fi
  partition_list=$(LC_ALL=C awk '/[hs]d[a-z][0-9]+/ { print $4; }' $partition_table)
  for ipartition in $partition_list; do
    root_partition="/dev/$ipartition"
    # process the boot loader
    mount $root_partition $hd_img >/dev/null 2>&1
    mrc=$?
    # root partition must have /boot
    if [ -d "$hd_img/boot/" ]; then
       # Found the root partition
       found_root_partition="$root_partition"
       break;
    fi
    [ $mrc -eq 0 ] && umount $root_partition
  done
  # Try LVM2 if no root partition is found
  if [ -z "$found_root_partition" ]; then
    lvm_list="$(LC_ALL=C awk '/dm-[0-9]+/ { print $4; }' $partition_table)"
    if [ -n "$lvm_list" ]; then
       for ilv in /dev/mapper/*; do
	 [ "$fname" = "/dev/mapper/contronl" ] && continue
         # process the boot loader
         mount $ilv $hd_img >/dev/null 2>&1
         mrc=$?
         # root partition must have /boot
         if [ -d "$hd_img/boot/" ]; then
            # Found the root partition
            found_root_partition="$ilv"
            break;
         fi
         [ $mrc -eq 0 ] && umount $ilv
       done
    fi
  fi
  [ -f "$partition_table" ] && rm -f $partition_table
  if [ -n "$found_root_partition" ]; then
    echo "$found_root_partition is mounted as root partition for grub-install..."
  else
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "No root partition is mounted! You'd better to make sure the running kernel does support the file system of root partition!"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
  fi
} # end of mount_root_partition_for_separate_boot_part

# check input_device
check_if_input_device_exist() {
  ANS_TMP=$1
  shift
  local input_dev="$*"
  local partition_table
  [ -z "$input_dev" ] && echo "No input device!" && exit 1
  # it's devfs compiled into kernel, we will use the conv_devfspart_to_tradpart to convert the devfs style partitions into traditional partitions
  partition_table="$(mktemp /tmp/parttable.XXXXXX)"
  if grep -Eq '(scsi/host|ide/host)' /proc/partitions 2>/dev/null; then
     # it's devfs compiled into kernel...
     conv_devfspart_to_tradpart $partition_table
  else
     # the devfs is not compiled into kernel, good!
     # We' better not use "cp -a /proc/partitions $partition_table", use cat to get the kernel param
     cat /proc/partitions > $partition_table
     cciss_dev_map_if_necessary $partition_table
  fi

  ret_dev="$input_dev"
  for idev in $input_dev; do
    if [ -z "$(awk -F" " '{print $4}' $partition_table | grep -Ew "$idev")" ]; then
      [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
      echo "The input device [$idev] does NOT exist in this machine!"
      echo "We will not save this device [$idev]!"
      [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
      # remove non-existing device.
      ret_dev="$(echo $ret_dev | sed -e "s/\<$idev\>//g")"
      echo "Press enter to continue!"
      read
    else
      echo "Selected device [$idev] found!"
    fi
  done
  [ -f "$partition_table" ] && rm -f $partition_table
  if [ -n "$ret_dev" ]; then
    echo $ret_dev > $ANS_TMP
    # strip the unnecessary quotation mark "
    LC_ALL=C perl -pi -e "s/\"//g" $ANS_TMP
    echo "The selected devices: $ret_dev"
  else
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
    echo "No inputted devices found!"
    echo "$msg_program_stop!!!"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    exit 1
  fi
}
#
check_ntfs_partition_integrity() {
    # Ref: http://mlf.linux.rulez.org/mlf/ezaz/ntfsresize.html#troubleshoot
    local src_dev="$1"
    local ntfsfix_ans
    ntfsclone_extra_opt=""
    echo -n "Checking NTFS integrity in $src_dev... "
    if ! ntfsresize --info --force $src_dev &>/dev/null; then
       [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
       echo "$msg_ntfs_in_this_part_is_corrupt: $src_dev"
       echo "$msg_two_options_for_you_now:"
       echo "(1) $msg_boot_win_run_chkdsk"
       echo "(2) $msg_run_ntfsfix_then_force_save"
       [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
       echo -n "[1] "
       read ntfsfix_ans
       case "$ntfsfix_ans" in
         2) 
	   # force to save the ntfs filesystem
	   # ntfsclone_extra_opt is global variable
	   ntfsclone_extra_opt="--force"
	   ntfsfix $src_dev
	   ;;
	 *)
           echo "$msg_program_stop!"
           echo -n "$msg_press_enter_to_continue..."
           read
           exit 1
	   ;;
       esac
    fi
    echo "done!"
} # end of check_ntfs_partition_integrity
check_partimage_partition_integrity() {
    local src_dev="$1"
    echo -n "Checking file system integrity in $src_dev... "
    # file (file-4.19 in FC6) will give the results like:
    # /dev/hda2: Linux rev 1.0 ext3 filesystem data (needs journal recovery)
    # /dev/mapper/vg2-lv2: ReiserFS V3.6 block size 4096 (mounted or unclean) num blocks 90112 r5 hash
    if [ -n "$(file -Ls "$src_dev" | grep -iE "(needs journal recovery|mounted or unclean)")" ]; then
       [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
       echo
       echo "The file system in $src_dev is unclean!"
       echo "It's recommended to boot the template machine into GNU/Linux to let it be fixed automatically or use fsck (such as fsck.ext3, fsck.reiserfs...) to fix $src_dev! Clonezilla still can save the image, but it is not recommended to continue. If you want to quit now, press Ctrl-C."
       [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
       if [ "$batch_mode" != "on" ]; then
         echo -n "$msg_press_enter_to_continue..."
         read
       fi
    fi
    echo "done!"
} # end of check_partimage_partition_integrity
# re-install grub
install_grub_hd() {
    local grub_partition
    local selected_hd
    while [ $# -gt 0 ]; do
      case "$1" in
        -s|--selected-hd)
           shift
           if [ -z "$(echo $1 |grep ^-.)" ]; then
             # skip the -xx option, in case 
             selected_hd="$1"
             shift
           fi
           [ -z "$selected_hd" ] && echo "-s is used, but no selected_hd assigned." && exit 1
           ;;
        -*)     echo "${0}: ${1}: invalid option" >&2
                USAGE >& 2
                exit 2 ;;
        *)      break ;;
      esac
    done
    grub_partition="$1"
    # if grub_partition is not set, set default
    if [ "$grub_partition" = "auto" ]; then
       check_grub_partition $selected_hd
       grub_partition="$found_grub_partition"
       if [ -n "$grub_partition" ]; then
         echo "Found grub partition: $grub_partition... Trying to run grub-install now... "
       else
         echo "The grub directory is NOT found. Maybe it does not exist (so other boot manager exists) or the file system is not supported in the kernel. Skip grub-install." 
	 return 1
       fi
    fi
    # grub_partition is like "/dev/hda1" or "/dev/sda1"
    # So grub_hd is like "hda" or "sda"
    grub_hd="${grub_partition:5:3}"
    # active this device
    fdisk -l $grub_hd &>/dev/null
    # process the boot loader
    hd_img=`mktemp -d /tmp/hd_img.XXXXXX`
    mount $grub_partition $hd_img
    # check if --no-floppy is supported.
    grub_no_floopy_opt=""
    if [ -n "$(grub-install --help | grep "\--no-floppy")" ]; then
      grub_no_floopy_opt="--no-floppy"
    fi
    # check if /boot is in its own partition, if so, different mount point.
    # grub root-directory must have "/boot/"
    if [ ! -d "$hd_img/boot/" ]; then
       # In this case, /boot must exist in its own partition, 
       # not same partition with / (root partition).

       # remount it as /tmp/hd_img.XXXXXX/boot
       umount $hd_img
       [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
       echo "There is a separate boot partition in target device. Trying to mount root partition for grub-install to work with that..."
       [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
       # Important! We have to mount the root dir first. Otherwise in FC6, CentOS 5 or later, grub-install will refuse to do that without root device. It will show the error message like: "Could not find device for /mnt"... For Debian or Ubuntu, no such problem. 
       # Actually we can modify /sbin/grub-install (grub-0.97-13) in FC6 or Centos5:
       # -----------
       # ...
       # # Get the root drive.
       # root_device=`find_device ${rootdir}` || exit 1
       # ...
       # -----------
       # as 
       # root_device=`find_device ${rootdir}`
       # i.e. remove "|| exit 1". However, it's better to follow the way distribution does. Therefore we modify ocs-function instead of modifying grub-install.
       mount_root_partition_for_separate_boot_part $hd_img
       # just in case no /boot in root partition
       mkdir -p $hd_img/boot
       mount $grub_partition $hd_img/boot/
       # if device.map exists, remove it to avoid some problem.
       [ -f "$hd_img/boot/grub/device.map" ] && rm -f $hd_img/boot/grub/device.map
       grub-install $grub_no_floopy_opt --root-directory=$hd_img /dev/$grub_hd
       rc=$?
    else
       # if device.map exists, remove it to avoid some problem.
       [ -f "$hd_img/boot/grub/device.map" ] && rm -f $hd_img/boot/grub/device.map
       grub-install $grub_no_floopy_opt --root-directory=$hd_img /dev/$grub_hd
       rc=$?
    fi

    # Check if grub config exists or not
    [ ! -f "$hd_img/boot/grub/menu.lst" -a ! -f "$hd_img/boot/grub/grub.conf" ] && echo "$msg_uppercase_Warning!!! Can NOT find the grub config file \"menu.lst\" or \"grub.conf\" in the system cloned!!!"

    if [ $rc -gt 0 ]; then
        [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
	echo "Failed to install grub!!!"
        [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
	[ "$debug_mode" = "on" ] && sulogin
    fi
    # if it's VFAT, grub-install will fail when try to link menu.lst as grub.conf 
    # we just copy it.
    if [ ! -e "$hd_img/boot/grub/grub.conf" ]; then
      cp -f $hd_img/boot/grub/menu.lst $hd_img/boot/grub/grub.conf
    fi
    umount $grub_partition 2>/dev/null
    umount $hd_img 2>/dev/null
    [ -d "$hd_img" ] && rm -rf $hd_img
    [ -d "$hd_img/boot" ] && rm -rf $hd_img/boot
    echo "done!"
}

# output the CHS values of a HD
output_HD_CHS() {
   local DEV=$1
   local target_d=$2
   [ -z "$DEV" -o -z "$target_d" ] && return 1
   cylinders="$(sfdisk -g /dev/$DEV |grep -oEi "[[:digit:]]+ cylinders" | awk -F" " '{print $1}')"
   heads="$(sfdisk -g /dev/$DEV |grep -oEi "[[:digit:]]+ heads" | awk -F" " '{print $1}')"
   sectors="$(sfdisk -g /dev/$DEV |grep -oEi "[[:digit:]]+ sectors" | awk -F" " '{print $1}')"
   [ -f "$target_d/${DEV}-chs.sf" ] && rm -f $target_d/${DEV}-chs.sf
   for i in cylinders heads sectors; do
     eval num=\$$i
     echo "$i=$num" >> $target_d/${DEV}-chs.sf
   done
}

# sfdisk failed process
sfdisk_fail_process() {
    echo "sfdisk failed, abort!"
    echo "Please make sure your HD exist or the size of HD is big enough!"
    echo "We can NOT go on! Press \"c\" to enter command prompt or any other key to quit the program..." 
    read fail_answer
    case "$fail_answer" in
      [cC])
	  sulogin
          ;;
         *)
          exit 1
    esac
}
#
load_HD_geometry() {
  local target_d="$1"
  local DEV="$2"
  no_CHS_data_warning() {
      [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
      echo "Unable to find the previous saved file $target_d/${DEV}-chs.sf!"
      echo "Maybe the image is created by old verson clonezilla."
      echo "We will not force to use the specified HD geometry."
      [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
  }

  [ -z "$target_d" ] && return 1
  [ -z "$DEV" ] && return 1
  if [ -f "$target_d/${DEV}-chs.sf" ]; then
    . $target_d/${DEV}-chs.sf
    [ -z "$cylinders" -o -z "$heads" -o -z "$sectors" ] && no_CHS_data_warning
    sfdisk_opt="$sfdisk_opt -C $cylinders -H $heads -S $sectors"
  else 
    no_CHS_data_warning
  fi
}
#
clean_cylinder_boundary_warning() {
  # We have to clean this two warnings, otherwise sfdisk will refuce to write
  # even if we run something like:
  # sfdisk --force -C# -H# -S# /dev/hda < hda-pt.sf...
  local part_file=$1
  if [ -f "$part_file" ]; then
    LC_ALL=C perl -p -i -e "s/^Warning: extended partition does not start at a cylinder boundary.//gi" $part_file
    LC_ALL=C perl -p -i -e "s/^DOS and Linux will interpret the contents differently.//gi" $part_file
  fi
}
#
turn_off_swap() {
  # turn off the swap partition and file to unlock the busy partition
  if [ -e /etc/debian_version -o -e /etc/SuSE-release ]; then
    # Debian or SUSE
    # just stop it!
    [ -x /etc/init.d/mkswapfile ] && /etc/init.d/mkswapfile stop
  else
    # RH-like
    if [ -f /var/lock/subsys/mkswapfile -a -x /etc/init.d/mkswapfile ]; then
      /etc/init.d/mkswapfile stop
    fi
  fi
  swapoff -a
} # end of turn_off_swap
#
turn_off_swap_and_LVM2() {
  turn_off_swap
  # Stop LVM so that the HD will not be occupied, we use script comes from
  # gentoo, but actually just run "vgchange -an" will be ok.
  lvm2-stop.sh
} # end of turn_off_swap_and_LVM2
#
check_integrity_of_partition_table_in_disk() {
  # This function is used to check the integrity of partition table in disk. In Clonezilla, we use parted to parse the filesystem in partition table, if the partition table is weird, we have to show warning messages.
  # disk_file is like /dev/hda, /dev/sda...
  local disk_file="$1"
  local dsk_chk_tmp="$(mktemp /tmp/dsk_chk_tmp.XXXXXX)"
  local disk_name continue_confirm_ans
  if [ -z "$disk_file" ]; then
    echo "To check partition table, you have to assign disk!"
    exit 1
  fi
  # use parted to check if the partition table is ok or not.
  echo "Checking the integrity of partition table in the disk $disk_file... "
  parted -s $disk_file print &> $dsk_chk_tmp
  rc=$?
  if [ "$rc" -gt 0 ]; then
    disk_name="$(basename $disk_file)"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
    echo "$msg_the_partition_table_in_disk_is_illegal: $disk_file"
    echo "$msg_parted_detect_as_wrong"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    if [ -e "$dsk_chk_tmp" ]; then
      echo "$msg_error_messages_from_parted_are:"
      echo $msg_delimiter_star_line
      cat $dsk_chk_tmp
      echo $msg_delimiter_star_line
      rm -f $dsk_chk_tmp
    fi
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "$msg_continue_with_weried_partition_table"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo -n "$msg_are_u_sure_u_want_to_continue ? (y/N) "
    read continue_confirm_ans
    case "$continue_confirm_ans" in
         y|Y|[yY][eE][sS])
	    echo "Very bad choice!"
            echo "$msg_ok_let_do_it!"
            ;;
         *)
	    echo "Smart choice!"
            echo "$msg_program_stop!"
            exit 1
    esac
  fi
} # end of check_integrity_of_partition_table_in_disk
#
check_created_partition_table() {
  # This function is used to check the created partition table by "sfdisk -f $dev < $partition_table_file"
  # It's because we always use -f/--force with sfdisk when creating partition in clonezilla, and "sfdisk --force" will always return value "0", no matter the partition table config file is good or not. This is why here we have to use parted to check it again. 
  # TODO: Maybe use parted to create the partition table ?
  #
  # disk_file is like /dev/hda, /dev/sda...
  local disk_file="$1"
  local img_name="$2"
  local dsk_chk_tmp="$(mktemp /tmp/dsk_chk_tmp.XXXXXX)"
  local disk_name
  if [ -z "$disk_file" ]; then
    echo "To check partition table, you have to assign disk!"
    exit 1
  fi
  # use parted to check if the partition table is ok or not.
  echo "Checking the integrity of partition table in the disk $disk_file... "
  parted -s $disk_file print &> $dsk_chk_tmp
  rc=$?
  if [ "$rc" -gt 0 ]; then
    disk_name="$(basename $disk_file)"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
    echo "$msg_the_partition_table_in_disk_is_illegal: $disk_file"
    echo "$msg_does_this_part_table_file_fit_disk: $img_name/${disk_name}-pt.sf ?"
    echo "$msg_is_this_disk_too_small: $disk_file ?"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    if [ -e "$dsk_chk_tmp" ]; then
      echo "$msg_error_messages_from_parted_are:"
      echo $msg_delimiter_star_line
      cat $dsk_chk_tmp
      echo $msg_delimiter_star_line
      rm -f $dsk_chk_tmp
    fi
    echo "$msg_program_stop!"
    exit 1
  fi
} # end of check_created_partition_table
#
check_blocks_per_group_of_ext2_3(){
  # This function is used to check the Blocks per group of ext2/ext3 is 32768 or not. If not, partimage does not support. Exit. Ref: http://sourceforge.net/forum/forum.php?thread_id=1833628&forum_id=663168
  local part_="$1"
  local img_name="$2"
  local blocks_per_group
  if [ -z "$ $part_" ]; then
    echo "To check the blocks per group of ext2/ext3, you have to assign the partition!"
    echo "$msg_program_stop!"
    exit 1
  fi
  blocks_per_group="$(dumpe2fs $part_ 2>/dev/null | grep -i "^Blocks per group:" | awk -F":" '{print $2}' | sed -e "s/^[[:space:]]*//g")"
  if [ "$blocks_per_group" != "32768" ]; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
    echo "The 'Blocks per group' of ext2/ext3 in this partition $part_ is \"$blocks_per_group\", NOT 32768." | tee $tgt_dir/THIS_IMAGE_DIR_IS_BROKEN.txt
    echo "$msg_partimage_not_support_block_per_group_non_32768" | tee -a $tgt_dir/THIS_IMAGE_DIR_IS_BROKEN.txt
    echo "$msg_for_more_info_check: http://sourceforge.net/forum/forum.php?thread_id=1833628&forum_id=663168" | tee -a $tgt_dir/THIS_IMAGE_DIR_IS_BROKEN.txt
    echo "$msg_Warning! $msg_this_image_dir_is_not_complete: $img_name"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo "$msg_program_stop!"
    echo -n "$msg_press_enter_to_continue..."
    read
    exit 1
  fi
} # end of check_blocks_per_group_of_ext2_3
# create partition from file
create_partition() {
  # tgt_hd_file: /dev/hda, /dev/hdb...
  # $create_par is global variable
  local tgt_hd_file="$1"
  local tgt_dir="$2"
  local sf_opt="$3"
  local tgt_hd_name="$(basename $tgt_hd_file)"

  check_input_hd $tgt_hd_name

  turn_off_swap_and_LVM2

  # The following actions must be after turn_off_swap_and_LVM2, since if swap and LVM2 is not off, partimage or ntfsclone will not be able to write the busy partitions. 

  echo "Creating partition in $tgt_hd_file..."
  [ "$load_HD_CHS" = "yes" ] && load_HD_geometry $tgt_dir $tgt_hd_name
  check_if_source_dev_busy_before_create_partition $tgt_hd_file

  # create partition from file
  # Before create partition, clean the warning message in ${tgt_hd_name}-pt.sf so that sfdisk will not refuse to do that.
  echo $msg_delimiter_star_line
  date | tee $RESTORE_SFDISK_LOG
  echo "Writinging the partition table..." | tee -a $RESTORE_SFDISK_LOG
  # dump partition table only without running sfdisk
  # ref: http://www.nautilus.com.br/~rei/material/artigos/sistema-de-boot.html
  case "$create_part_by_sfdisk" in
    n|N|[nN][oO])
      dd if=$tgt_dir/${tgt_hd_name}-mbr of=$tgt_hd_file skip=446 seek=446 bs=1 count=66
      RETVAL="$?"
      if [ "$RETVAL" -eq 0 ]; then
        echo -n "Making kernel re-read the partition table of $tgt_hd_file... "
        sfdisk -R $tgt_hd_file
        echo "done!"
        echo "The partition table of $tgt_hd_file is:"
        fdisk -l $tgt_hd_file
      else
        [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
        echo "Unable to use dd to dump the partition table in $tgt_hd_file!"
        echo "Something weng wrong! The clone maybe won't work!"
        [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
      fi
      ;;
    y|Y|[yY][eE][sS])
      # create partition table by running sfdisk
      echo "Running sfdisk $sf_opt $tgt_hd_file < $tgt_dir/${tgt_hd_name}-pt.sf" | tee -a $RESTORE_SFDISK_LOG
      sfdisk $sf_opt $tgt_hd_file < $tgt_dir/${tgt_hd_name}-pt.sf | tee -a $RESTORE_SFDISK_LOG
      RETVAL="$?"
      echo "This is done by sfdisk $sf_opt $tgt_hd_file < $tgt_dir/${tgt_hd_name}-pt.sf" | tee -a $RESTORE_SFDISK_LOG
      # This is useless if $sf_opt (--force is used)! Since we always use --force now, so the return value is "0"
      if [ $RETVAL -ne 0 ]; then
        sfdisk_fail_process
        exit 1
      fi
      # add another checking mechanism by parted, since sfdisk with -f won't return correct code.
      check_created_partition_table $tgt_hd_file $tgt_dir
      ;;
  esac
} # end of create_partition
#
countdown_or_confirm_before_save() {
  local save_img_name="$1"
  local save_dev="$2"
  # wait for some commands if specified by user
  if [ -n "$TIME_to_wait" ]; then
    n="$TIME_to_wait"
    echo $msg_delimiter_star_line
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "$msg_the_following_step_is_to_save_the_disk_part_as_img: \"$save_dev\" -> \"$save_img_name\"."
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    countdown $TIME_to_wait
  fi
  if [ "$confirm_before_clone" = "yes" ]; then
    echo $msg_delimiter_star_line
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "$msg_the_following_step_is_to_save_the_disk_part_as_img: \"$save_dev\" -> \"$save_img_name\"."
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo -n "$msg_are_u_sure_u_want_to_continue ? (y/N) "
    read save_confirm_ans
    case "$save_confirm_ans" in
         y|Y|[yY][eE][sS])
            echo "$msg_ok_let_do_it!"
            ;;
         *)
            echo "$msg_program_stop!"
            exit 1
    esac
  fi
} # end of countdown_or_confirm_before_save
#
warning_about_run_parts_in_debian() {
  local run_dir="$1"
  if [ -e /etc/debian_version ]; then
     [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
     echo "///NOTE/// The files in $run_dir are run by run-parts. In Debian-like distribution, those file names must consist entirely of upper and lower case letters, digits, underscores, and hyphens. Therefore something like 'myrun.sh' will NOT be run by run-parts since it containing illegal character dot '.'!"
     [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
     echo The scripts in $run_dir will be run are:
     echo $msg_delimiter_star_line
     run-parts --test $run_dir
     echo $msg_delimiter_star_line
  fi
} # end of warning_about_run_parts_in_debian
#
countdown_or_confirm_before_restore() {
  local restore_img_name="$1"
  local restore_dev="$2"
  # wait for some commands if specified by user
  if [ -n "$TIME_to_wait" ]; then
    n="$TIME_to_wait"
    echo $msg_delimiter_star_line
    echo "$msg_the_following_step_is_to_restore_img_to_disk_part: \"$restore_img_name\" -> \"$restore_dev\""
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "$msg_uppercase_Warning!!! $msg_uppercase_Warning!!! $msg_uppercase_Warning!!!"
    echo "$msg_uppercase_Warning! $msg_all_data_in_dev_will_be_overwritten: $restore_dev"
    if [ "$ocs_sr_type" = "restoreparts" -a "$create_part" = "yes" ]; then
      echo "$msg_uppercase_Warning! $msg_option_k_is_not_chosen_part_table_will_be_recreated"
    fi
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo "Last chance to quit in $TIME_to_wait seconds... Press Shutdown button in this machine to avoid continuing."
    countdown $TIME_to_wait
  fi
  if [ "$confirm_before_clone" = "yes" ]; then
    echo $msg_delimiter_star_line
    echo "$msg_the_following_step_is_to_restore_img_to_disk_part: \"$restore_img_name\" -> \"$restore_dev\""
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "$msg_uppercase_Warning!!! $msg_uppercase_Warning!!! $msg_uppercase_Warning!!!"
    echo "$msg_uppercase_Warning! $msg_all_data_in_dev_will_be_overwritten: $restore_dev"
    if [ "$ocs_sr_type" = "restoreparts" -a "$create_part" = "yes" ]; then
      echo "$msg_uppercase_Warning! $msg_option_k_is_not_chosen_part_table_will_be_recreated"
    fi
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo "$msg_are_u_sure_u_want_to_continue ?"
    echo -n "[y/N] "
    read restore_confirm_ans
    case "$restore_confirm_ans" in
         y|Y|[yY][eE][sS])
            echo "$msg_ok_let_do_it!"
            ;;
         *)
            echo "$msg_program_stop!"
            exit 1
   esac
  fi
} # end of countdown_or_confirm_before_restore
#
task_preprocessing() {
  local mode="$1"
  local server_tmp=""
  local mntpnt_tmp=""

  # check if the kernel cmdline wants this client to abort or not...
  check_boot_kernel_arg_cmdline

  # load the specified modules
  if [ -n "$module_to_load" ]; then
    for imod in $module_to_load; do
      echo "Loading module $imod..."
      modprobe $imod
    done
  fi

  # mount_point
  if [ -n "$mount_point" ]; then
    if [ -n "$(echo "$mount_point" | grep ":")" ]; then
      server_tmp="$(echo "$mount_point" | awk -F":" '{print $1}')"  
      mntpnt_tmp="$(echo "$mount_point" | awk -F":" '{print $2}')"
    else
      server_tmp=$(awk '$2 ~ /\/$/ {print $1}' /etc/fstab | awk -F ":" '{print $1}')
      mntpnt_tmp=$mount_point
    fi
    mount -t nfs $server_tmp:$mntpnt_tmp $ocsroot
    if [ $? -ne 0 ]; then
      echo "The mount point you specified ($mntpnt_tmp) is not exported by server ($server)"
      echo "We can NOT go on! Press \"c\" to enter command prompt or any other key to quit the program..." 
      read fail_answer
      case "$fail_answer" in
        [cC]) /sbin/sulogin ;;
         *) task_postprocessing ;;
      esac
    fi
  fi

  if [ "$debug_mode" = "on" ]; then
    echo "Enter shell...Use \"exit\" to back to the original program."
    sulogin
  fi
}
#
run_post_cmd_when_clone_end() {
  local post_run="$1"
  case "$post_run" in
    poweroff)
      echo -n 'Will poweroff... '; countdown 5
      poweroff $HALT_REBOOT_OPT
      ;;
    command)
      echo
      ;;
    reboot)
      echo -n 'Will reboot... '; countdown 5
      reboot $HALT_REBOOT_OPT
      ;;
    choose)
      echo "$msg_clone_finished_choose_to:"
      echo "(0) $msg_poweroff"
      echo "(1) $msg_reboot"
      echo "(2) $msg_enter_cml"
      echo -n "[2] "
      read final_action
      case "$final_action" in
        0) echo -n 'Will poweroff... '; countdown 5; poweroff $HALT_REBOOT_OPT ;;
        1) echo -n 'Will reboot... '; countdown 5; reboot $HALT_REBOOT_OPT ;;
        *) echo ;;
      esac
      ;;
    *)
      # if post_run is echo true, do not show it
      [ "$post_run" != "true" ] && echo -n "Running $post_run..."
      $post_run 
      echo
      ;;
  esac
}

# task for post processing
task_postprocessing() {
  #
  echo "*****************************************************"
  case "$change_win_hostname" in
     By_IP)
          echo "Changing the hostname of M$ windows based on IP address..."
          /opt/drbl/sbin/drbl-chnthn -b -v IP -p $win_hostname_prefix
          ;;
     By_MAC)
          echo "Changing the hostname of M$ windows based on MAC address..."
          /opt/drbl/sbin/drbl-chnthn -b -v MAC -p $win_hostname_prefix
          ;;
  esac
  echo "*****************************************************"
  # Restore the inittab so that if ocsmgrd fails to get the message, the client
  # will not clone again.
  if [ "$always_restore" != "yes" ]; then
    [ -f "/etc/inittab.ocs" ] && mv -f /etc/inittab.ocs /etc/inittab
  fi
  if [ "$run_postrun_dir" = "yes" ]; then
     echo $msg_delimiter_star_line
     echo "Start to run the program(s) in $OCS_POSTRUN_DIR..."
     warning_about_run_parts_in_debian $OCS_POSTRUN_DIR
     run-parts $OCS_POSTRUN_DIR
     echo $msg_delimiter_star_line
  fi

  # option_mount_point
  [ -n "$mount_point" ] && umount $ocsroot
  # option debug
  if [ "$debug_mode" = "on" ]; then
    echo "Enter shell...Use \"exit\" to back to the original program."
    sulogin
  fi
  # notify the server if necessary
  task_notify_localboot

  #
  echo "Now syncing - flush filesystem buffers..." 
  sync;sync;sync

  # reboot/poweroff/choose...
  run_post_cmd_when_clone_end "$postrun"
} # end of task_postprocessing
#
ask_time_or_clients_to_wait() {
      local n_clients_tmp
      local time_opt_tmp
      # Note!!! We must use common tmp file to pass the result, we can not
      # just echo result in the end of this function. Otherwise in newer
      # dialog (FC3), the script will wait for the end of function, then it 
      # shows the result. 
      # There is nothing in the screen when function is called if we just 
      # use echo the result to stdout.
      ANS_TMP="$1"
      local mcast_cln_no="$2"
      TMP=`mktemp /tmp/ocs.XXXXXX`
      trap "[ -f "$TMP" ] && rm -f $TMP" HUP INT QUIT TERM EXIT
      n_clients_tmp=0
      time_opt_tmp=0
      if [ -z "$mcast_cln_no" ]; then
        n_clients_tmp_default="$($DRBL_SCRIPT_PATH/bin/get-client-ip-list | wc -l | sed -e "s/ //g")"
      else
        n_clients_tmp_default="$mcast_cln_no"
      fi
      $DIA --backtitle "$msg_nchc_free_software_labs" --title "$msg_nchc_clonezilla" \
      --menu "$msg_choose_the_multicast_method:" 0 0 0 \
      "clients+time-to-wait" "$msg_clients_time_to_wait" \
      "time-to-wait" "$msg_time_to_wait" \
      "clients-to-wait" "$msg_clients_to_wait" \
      2> $TMP
      M="$(cat $TMP)"
      case "$M" in
        "time-to-wait") 
           $DIA --backtitle "$msg_nchc_free_software_labs" --title "$msg_nchc_clonezilla" \
           --inputbox "$msg_time_to_wait_sec ?" 0 0 "$TIME_TO_WAIT_DEFAULT" 2> $TMP
           time_opt_tmp="$(cat $TMP)"
           [ -z "$time_opt_tmp" ] && echo "You must specify the time! $msg_program_stop!" && exit 1
           [ $time_opt_tmp -le 0 ] && echo "Time number can NOT <= 0! $msg_program_stop!!!" && exit 1
           echo "time_to_wait=$time_opt_tmp" > $ANS_TMP
           ;;
        "clients-to-wait")
           ## ask how many clients to restore
           $DIA --backtitle "$msg_nchc_free_software_labs" --title "$msg_nchc_clonezilla" \
           --inputbox "$msg_how_many_clients_to_restore ?" 0 0 "$n_clients_tmp_default" 2> $TMP
           n_clients_tmp=$(cat $TMP)
           # check client no.
           # Steven TODO, better to re-ask if n_client <=0
           [ -z "$n_clients_tmp" ] && echo "You must specify the client number! $msg_program_stop!" && exit 1
           [ $n_clients_tmp -le 0 ] && echo "Client number can NOT <= 0! $msg_program_stop!!!" && exit 1
           echo "clients_to_wait=$n_clients_tmp" > $ANS_TMP
           ;;
        "clients+time-to-wait")
           ## ask how many clients to restore
           $DIA --backtitle "$msg_nchc_free_software_labs" --title "$msg_nchc_clonezilla" \
           --inputbox "$msg_how_many_clients_to_restore ?" 0 0 "$n_clients_tmp_default" 2> $TMP
           n_clients_tmp=$(cat $TMP)
           # check client no.
           # Steven TODO, better to re-ask if n_client <=0
           [ -z "$n_clients_tmp" ] && echo "You must specify the client number! $msg_program_stop!" && exit 1
           [ $n_clients_tmp -le 0 ] && echo "Client number can NOT <= 0! $msg_program_stop!!!" && exit 1
           # ask time
           $DIA --backtitle "$msg_nchc_free_software_labs" --title "$msg_nchc_clonezilla" \
           --inputbox "$msg_max_time_to_wait_sec" 0 0 "$MAX_TIME_TO_WAIT_DEFAULT" 2> $TMP
           max_time_opt_tmp="$(cat $TMP)"
           [ -z "$max_time_opt_tmp" ] && echo "You must specify the time! $msg_program_stop!" && exit 1
           [ $max_time_opt_tmp -le 0 ] && echo "Time number can NOT <= 0! $msg_program_stop!!!" && exit 1
           echo "clients_to_wait=$n_clients_tmp" > $ANS_TMP
           echo "max_time_to_wait=$max_time_opt_tmp" >> $ANS_TMP
           ;;
        *)
           echo "You must specify the mode! $msg_program_stop!" && exit 1
      esac 
      [ -f "$TMP" ] && rm -f $TMP
}

#
check_existing_target_image() {
  local tgt_image="$1"
  if [ "$tgt_image" = "$ocsroot" -o -z "$tgt_image" ]; then
    echo "Something went wrong! Contact DRBL developers to fix this problem!"
    echo "$msg_program_stop!!!"
    exit 1
  else
    [ -f "$tgt_image" ] && rm -f $tgt_image
    [ -d "$tgt_image" ] && rm -rf $tgt_image
  fi
}
check_input_target_image() {
  local tgt_dir="$1"
  if [ ! -d "$tgt_dir" ]; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
    echo "The directory $tgt_dir for the inputed name do NOT exist!"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo "$msg_program_stop!!!"
    exit 1
  fi
}

#
get_input_image_name() {
    # parameters: [/home/partimag|...] [hda1|hda2...]
    # return the image name
    # Note!!! We must use common tmp file to pass the result, we can not
    # just echo result in the end of this function. Otherwise in newer
    # dialog (FC3), the script will wait for the end of function, then it 
    # shows the result. 
    # There is nothing in the screen when function is called if we just 
    # use echo the result to stdout.
    ANS_TMP="$1"
    local tgt_name_default
    local ASK_IMGNAME=1
    TMP=`mktemp /tmp/ocs.XXXXXX`
    trap "[ -f "$TMP" ] && rm -f $TMP" HUP INT QUIT TERM EXIT
    while [ "$ASK_IMGNAME" -ne 0 ]; do
      tgt_name_default="$(date +%F-%H-img)"
      $DIA --backtitle "$msg_nchc_free_software_labs" --title "$msg_nchc_clonezilla" \
         --inputbox "$msg_input_name_to_save_the_img $tgt_name" 0 0 "$tgt_name_default" 2> $TMP
      tgt_img_name="$(cat $TMP)"
      if [ -n "$(echo $tgt_img_name | grep -Eo "[^.[:alnum:]_-]")" ]; then
        # we just accept those image name is alpha, digit, _, - and dot (.)
        $DIA --backtitle "$msg_nchc_free_software_labs" --title "$msg_nchc_clonezilla" \
        --msgbox "$msg_you_must_input_legal_filename!\n$msg_please_do_it_again!" 0 0 
      elif [ -n "$(echo $tgt_img_name | grep -E "(^|[[:space:]]+)([0-9ab]|-b|single|s)($|[[:space:]]+)")" ]; then
        # 0-9, a, b, -b, single, s will confuse init, which could be rc[0-9], a, b, -b, single, s mode
        $DIA --backtitle "$msg_nchc_free_software_labs" --title "$msg_nchc_clonezilla" \
        --msgbox "$msg_1_6_a_b_is_reserved!\n$msg_please_do_it_again!" 0 0 
      elif [ -n "$(echo $tgt_img_name | grep -Ew "(\.ntfs-img|\.dd-img)")" ]; then
        # .ntfs-img and .dd-img is reserved for image name type and format use. 
        $DIA --backtitle "$msg_nchc_free_software_labs" --title "$msg_nchc_clonezilla" \
        --msgbox "$msg_name_ntfs_dd_img_is_reserved!\n$msg_please_do_it_again!" 0 0 
      elif [ -z "$tgt_img_name" ]; then
        $DIA --backtitle "$msg_nchc_free_software_labs" --title "$msg_nchc_clonezilla" \
        --msgbox "$msg_you_must_input_filename!\n$msg_please_do_it_again!" 0 0 
      elif [ -d "$ocsroot/$tgt_img_name" ]; then
        $DIA --backtitle "$msg_nchc_free_software_labs" --title "$msg_nchc_clonezilla" \
        --yesno "$msg_the_image \"$tgt_img_name\" $msg_was_saved_before!\n\n$msg_do_you_want_to_replace ?" 0 0 2> $TMP
        ASK_IMGNAME=$?
      else
        ASK_IMGNAME=0
      fi
    done
    [ -f "$TMP" ] && rm -f $TMP
    # return the valule
    echo $tgt_img_name > $ANS_TMP
} # end of get_input_image_name
#
get_input_dev_name() {
    # input param, [harddisk|partition]
    # To specify it's hardisk or partition
    # return the hardisks or partitions
    # Note!!! We must use common tmp file to pass the result, we can not
    # just echo result in the end of this function. Otherwise in newer
    # dialog (FC3), the script will wait for the end of function, then it 
    # shows the result. 
    # There is nothing in the screen when function is called if we just 
    # use echo the result to stdout.
    ANS_TMP="$1"
    local dev_type="$2"
    local selection="$3"
    local skip_lvm="$4"
    local msg_choose_dev_to_what="$5"
    local dev_to_be_exclude="$6"
    local dev_list=""
    local HARDDEVS=""
    local partition_table=""
    local mounted_table=""
    local target_dev=""
    local dev_chosen_def=""
    local NUMDEV=0
    local part_list hdtmp msg_not_mounted_dev_found TMP FILE

    [ -z "$dev_type" ] && echo "You must specify the type get be entered!!! $msg_program_stop!" && exit 1
    [ -z "$selection" ] && selection="checklist"
    [ -z "$skip_lvm" ] && skip_lvm="no"

    # it's devfs compiled into kernel, we will use the conv_devfspart_to_tradpart to convert the devfs style partitions into traditional partitions
    partition_table="$(mktemp /tmp/parttable.XXXXXX)"
    if grep -Eq '(scsi/host|ide/host)' /proc/partitions 2>/dev/null; then
       # it's devfs compiled into kernel...
       conv_devfspart_to_tradpart $partition_table
    else
       # the devfs is not compiled into kernel, good!
       # We' better not use "cp -a /proc/partitions $partition_table", use cat to get the kernel param
       cat /proc/partitions > $partition_table
       cciss_dev_map_if_necessary $partition_table
    fi
    # CDROM won't be listed in /proc/partitions, so we do not have to worry about that when cloning.
    # strip those busy/mounted partitions and disk.
    part_list="$(LC_ALL=C awk '/[hs]d[a-z][0-9]+($| )/ { print $4; }' $partition_table | sort)"
    echo "Excluding busy partition or disk..."
    # Convert by uuid to traditional list in /proc/mounts so it's easier for us to exclude.
    # The format of /proc/mounts with uuid is like:
    # rootfs / rootfs rw 0 0
    # none /sys sysfs rw,nosuid,nodev,noexec 0 0
    # none /proc proc rw,nosuid,nodev,noexec 0 0
    # udev /dev tmpfs rw 0 0
    # /dev/disk/by-uuid/f3460329-25d4-467e-bb59-8f40ce78554a / reiserfs rw 0 0
    # ...
    mounted_table="$(mktemp /tmp/mnt_table.XXXXXX)"
    if grep -Eq 'by-uuid' /proc/mounts 2>/dev/null; then
       # with uuid format in /proc/mounts, good!
       cat /proc/mounts > $mounted_table
       conv_uuid_mount_to_tradpart $mounted_table
    else
       # no uuid format in /proc/mounts, good!
       # We' better not use "cp -a /proc/partitions $mounted_table", use cat to get the kernel param
       cat /proc/mounts > $mounted_table
    fi

    for ipart in $part_list; do
      if grep -qEw "^/dev/$ipart" $mounted_table; then
        # hda1 -> hda
        hdtmp=${ipart:0:3}
	# strip disk
        LC_ALL=C perl -i -ne "print unless /^.*$hdtmp[[:space:]]+/ .. /.*$/" $partition_table
	# strip parititon
        LC_ALL=C perl -i -ne "print unless /^.*$ipart[[:space:]]+/ .. /.*$/" $partition_table
      fi
    done

    # Strip those excluding devs
    for ipart in $dev_to_be_exclude; do
      LC_ALL=C perl -i -ne "print unless /^.*$ipart[[:space:]]+/ .. /.*$/" $partition_table
    done

    case "$dev_type" in
      harddisk)
        # now when input harddisk, not only one selection
	[ -z "$msg_choose_dev_to_what" ] && msg_choose_dev_to_what="$msg_choose_disks_to_save"
	msg_not_mounted_dev_found="$msg_no_umounted_disks_found"
        dev_list=$(LC_ALL=C awk '/[hs]d[a-z]($| )/ { print $4; }' $partition_table | sort)
        NUMDEV="$(echo $dev_list | wc -w)"
        ;;
      partition)
        # file -Ls /dev/hdax output example:
        # /dev/hda5: Linux/i386 swap file (new style) 1 (4K pages) size 113895 pages
        # /dev/hda2: x86 boot sector, extended partition table
        # when input partitions, not only one selection
	[ -z "$msg_choose_dev_to_what" ] && msg_choose_dev_to_what="$msg_choose_parts_to_save $msg_linux_parts_MS_mapping"
	msg_not_mounted_dev_found="$msg_no_umounted_parts_found"
        dev_list=$(LC_ALL=C awk '/[hs]d[a-z][0-9]+($| )/ { print $4; }' $partition_table | sort)
        for p in $dev_list; do
          # skip swap and extended partition
          #FILE="$(LC_ALL=C file -Ls "/dev/$p" | cut -d: -f2 | tr ' ' _ | sed -e "s/^_//g")"
          FILE="$(LC_ALL=C file -Ls "/dev/$p")"
          rc=$?
          [ "$rc" = "0" ] && case "$FILE" in *[Ss][Ww][Aa][Pp]*|*extended*) continue ;; esac
	  if [ "$skip_lvm" = "yes" ]; then
            [ "$rc" = "0" ] && case "$FILE" in *[Ll][Vv][Mm]*) continue ;; esac
	  fi
          NUMDEV=$[NUMDEV+1]
        done
        ;;
    esac
    TMP=`mktemp /tmp/ocs.XXXXXX`
    trap "[ -f "$TMP" ] && rm -f $TMP" HUP INT QUIT TERM EXIT
    # check if any harddisk is found ?
    if [ $NUMDEV -le 0 ]; then
       [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
       echo "$msg_error! $msg_not_mounted_dev_found"
       echo -n "$msg_press_enter_to_exit"
       read
       [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
       exit 1
    fi

    # prepare the dialog menu info
    for p in $dev_list; do
       DEV_MODEL=""
       case "$p" in
          hd[a-z])
            if [ "`cat /proc/ide/$p/media`" = "disk" ] ; then
              DEV_MODEL=`tr ' ' _ </proc/ide/$p/model`
            fi
            ;;
          sd[a-z])
            x="$(cat /sys/block/$p/device/model | tr ' ' _ 2>/dev/null)"
            # the result is like "WDC WD1600JS-60M"
	    # Note! The above method only works in kernel 2.6. For kernel 2.4, it not supported, and it's shown as "No_description" later.
            x=${x#*\"}
            x=${x%\"*}
            DEV_MODEL=${x}
	    # just in case if nothing found
            [ -z "$DEV_MODEL" ] && DEV_MODEL="No_description"
            ;;
          [hs]d[a-z][0-9]*)
            # Skip swap and extended partition
            # We do not use the output of fdisk -l, it's type mabe like
            # Extended, Ext'd, it's not easy to tell... We use file to get the
            # partition type
	    echo "Getting /dev/$p info..."
            part_type="$(LC_ALL=C file -Ls "/dev/$p")"
            rc=$?
            [ "$rc" -gt 0 ] && continue 
            case "$part_type" in *[Ss][Ww][Aa][Pp]*|*extended*) continue ;; esac
	    if [ "$skip_lvm" = "yes" ]; then
              case "$part_type" in *[Ll][Vv][Mm]*) continue ;; esac
	    fi
            DEV_MODEL="$($DRBL_SCRIPT_PATH/sbin/get_part_info /dev/$p filesystem)"
            ;;
        esac
        [ -z "$DEV_MODEL" ] && DEV_MODEL="No_description"
	# If only 1 device, make it on as default, else let user choose (off).
	if [ "$NUMDEV" = 1 ]; then
	  dev_chosen_def="on"
	else
	  dev_chosen_def="off"
	fi
        HARDDEVS="$HARDDEVS $p $DEV_MODEL $dev_chosen_def"
    done
    $DIA --backtitle "$msg_nchc_free_software_labs" --title "$msg_nchc_clonezilla" \
      --$selection "$msg_choose_dev_to_what:" 0 0 $NUMDEV $HARDDEVS \
    2> $TMP
    target_dev="$(cat $TMP)"
    [ -f "$TMP" ] && rm -f $TMP 
    [ -f "$mounted_table" ] && rm -f $mounted_table
    [ -f "$partition_table" ] && rm -f $partition_table

    # return the value
    echo $target_dev > $ANS_TMP
    # strip the unnecessary quotation mark "
    LC_ALL=C perl -pi -e "s/\"//g" $ANS_TMP
} # end of get_input_dev_name

# ********************
# NOTE! This get_existing_hda1_image function is abandoned since clonezilla 1.4.0-1, since
# ********************
get_existing_hda1_image() {
   # NOTE! This function is abandoned since clonezilla 1.4.0-1, since
   # we can use partitions to save and restore, no more specific hda1.
   # show existing images so that user can choose then return chosen to file ANS_TMP
   local ANS_TMP=$1
   local tgt_file=
   local TMP=`mktemp /tmp/ocs.XXXXXX`
   trap "[ -f "$TMP" ] && rm -f $TMP" HUP INT QUIT TERM EXIT
   local OLDVERTMP=`mktemp /tmp/oldocs.XXXXXX`
   trap "[ -f "$OLDVERTMP" ] && rm -f $OLDVERTMP" HUP INT QUIT TERM EXIT
   filelist=""
   numfiles=0
   if [ -z "$(ls -d $imagedir/*.000 2> /dev/null)" ]; then
     echo "No image in $imagedir .. (abort)"; exit 0
   fi
   for file in $imagedir/*.000; do               
     [ "$file" = "$imagedir/ocsmgrd" ] && continue
     [ "$file" = "$imagedir/size" ] && continue
   
     ## Blake, 2005/01/20, backward compability ..
     if [ ! -d $file ]; then
       imgf="${file/\.000/}"
       for imgff in $imgf.*; do
         sub="${imgff##*\.}"
         echo "$imgff $file/hda1.$sub" >> $OLDVERTMP
       done
       fileinfo=`ls -lh $file | awk '{ print $6"_"$7"_"$5; }'`
     else
       fileinfo=`ls -lh $file/hda1.000 | awk '{ print $6"_"$7"_"$5; }'`
     fi
     filename=${file##/*/}
     filename=${file##*/}
     filelist="$filelist $filename $fileinfo"
     echo "$numfiles. $filename $fileinfo" >> $TMP
     numfiles=`expr $numfiles + 1`
   done
   ## Blake, 2005/01/20, backward compability
   if [ "$(wc -l $OLDVERTMP | awk -F " " '{print $1}')" != "0" ]; then
     echo "To make it compatble with the new clonezilla,"
     echo "we have to move some of the old partimage files into directories."
     echo "The following partimage files are moving:"
     while read from to; do echo "  $from -> $to"; done < $OLDVERTMP
     echo "Press any key to continue..."
     read anykey
     while read from to; do
       filename="${from##*/}"
       dirname="${filename/\.[0-9][0-9][0-9]/.000}"
       size="$(grep "^$filename" $imagedir/size | cut -d: -f2)"
       mv $from $imagedir/$filename.$$
       [ ! -d $imagedir/$dirname ] && mkdir -p $imagedir/$dirname
       mv $imagedir/$filename.$$ $to
       filename="${to##*/}"
       echo "$filename:$size" >> $imagedir/$dirname/size
     done < $OLDVERTMP
   fi
   
   # Choose tgt_file
   if [ $numfiles -gt 0 ]; then
     if [ $numfiles -lt $MAX_DIALOG_HEIGHT ]; then
       height=$numfiles
     else
       height="$MAX_DIALOG_HEIGHT"
     fi
     $DIA \
       --backtitle "$msg_nchc_free_software_labs" \
       --title "$msg_nchc_clonezilla" \
       --menu "$msg_choose_the_image_to_restore:" 0 $RESTORE_DIA_WIDTH \
       $height $filelist 2> $TMP
     tgt_file=$(cat $TMP)
   fi
   [ -f "$TMP" ] && rm -f $TMP
   [ -f "$OLDVERTMP" ] && rm -f $OLDVERTMP
   # return the valule
   echo $tgt_file > $ANS_TMP
}
#
get_existing_disk_image() {
   # show existing images so that user can choose then return chosen to file ANS_TMP
   local ANS_TMP=$1
   local tgt_file=
   local ASK_IMGNAME=1
   local TMP=`mktemp /tmp/ocs.XXXXXX`
   trap "[ -f "$TMP" ] && rm -f $TMP" HUP INT QUIT TERM EXIT
   numfiles=`ls $imagedir 2> /dev/null | wc -l`
   numfiles=`expr $numfiles + 0`
   # list the previous saved images
   # the list of images can be put in one page
   filelist=""
   numfiles=0
   for file in `ls $imagedir 2> /dev/null`; do
     # only directory ..., not file
     [ ! -d "$imagedir/$file" ] && continue
     [ ! -f "$imagedir/$file/disk" ] && continue
     fileinfo=`ls -lh $imagedir/$file/disk 2>/dev/null| awk '{ print $6"_"$7; }'`
     fileinfo=$fileinfo"_"$(tr ' ' _ < $imagedir/$file/disk 2>/dev/null)
     filelist="$filelist $file $fileinfo"
     numfiles=`expr $numfiles + 1`
   done

   if [ $numfiles -gt 0 ]; then
     if [ $numfiles -lt $MAX_DIALOG_HEIGHT ]; then
       height="$numfiles"
     else
       height="$MAX_DIALOG_HEIGHT"
     fi
     while [ "$ASK_IMGNAME" -ne 0 ]; do
       $DIA \
         --backtitle "$msg_nchc_free_software_labs" \
         --title "$msg_nchc_clonezilla" \
         --menu "$msg_choose_the_image_to_restore:" 0 $RESTORE_DIA_WIDTH \
         $height $filelist 2> $TMP
       tgt_file="$(cat $TMP)"
       if [ -z "$tgt_file" ]; then
         $DIA --backtitle "$msg_nchc_free_software_labs" --title "$msg_nchc_clonezilla" \
        --msgbox "$msg_you_must_input_filename!\n$msg_please_do_it_again!" 0 0 
       else
         ASK_IMGNAME=0
       fi
     done
   else
     [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
     echo "No disk image is found in $imagedir.. Make sure you already saved an image! $msg_program_stop!!!"
     [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
     exit 1
   fi
   [ -f "$TMP" ] && rm -f $TMP
   # return the valule
   echo $tgt_file > $ANS_TMP
} # end of get_existing_disk_image

#
get_existing_parts_image() {
   # show existing partitions images so that user can choose then return chosen to file ANS_TMP
   local ANS_TMP=$1
   local tgt_file
   local ASK_IMGNAME=1
   local TMP=`mktemp /tmp/ocs.XXXXXX`
   trap "[ -f "$TMP" ] && rm -f $TMP" HUP INT QUIT TERM EXIT
   numfiles=`ls $imagedir 2> /dev/null | wc -l`
   numfiles=`expr $numfiles + 0`
   filelist=""
   numfiles=0
   for file in `ls $imagedir 2> /dev/null`; do
     # only directory ..., not file
     [ ! -d "$imagedir/$file" ] && continue
     [ ! -f "$imagedir/$file/parts" -a ! -f "$imagedir/$file/disk" ] && continue
     fileinfo=`ls -lhd $imagedir/$file | awk '{ print $6"_"$7; }'`
     if [ -f "$imagedir/$file/parts" ]; then 
       fileinfo=$fileinfo"_"$(tr ' ' _ < $imagedir/$file/parts)
     elif [ -f "$imagedir/$file/disk" ]; then 
       fileinfo=$fileinfo"_"$(tr ' ' _ < $imagedir/$file/disk)
     fi
     filelist="$filelist $file $fileinfo"
     numfiles=`expr $numfiles + 1`
   done
   if [ $numfiles -gt 0 ]; then
     if [ $numfiles -lt $MAX_DIALOG_HEIGHT ]; then
       height="$numfiles"
     else
       height="$MAX_DIALOG_HEIGHT"
     fi
     while [ "$ASK_IMGNAME" -ne 0 ]; do
       $DIA \
       --backtitle "$msg_nchc_free_software_labs" \
       --title "$msg_nchc_clonezilla" \
       --menu "$msg_choose_the_image_to_restore:" 0 $RESTORE_DIA_WIDTH \
       $height $filelist 2> $TMP
       tgt_file="$(cat $TMP)"
       if [ -z "$tgt_file" ]; then
         $DIA --backtitle "$msg_nchc_free_software_labs" --title "$msg_nchc_clonezilla" \
        --msgbox "$msg_you_must_input_filename!\n$msg_please_do_it_again!" 0 0 
       else
         ASK_IMGNAME=0
       fi
     done
   else
     [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
     echo "No disk or partitions image directory is found in $imagedir! $msg_program_stop!!!"
     [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
     exit 1
   fi
   [ -f "$TMP" ] && rm -f $TMP
   # return the valule
   echo $tgt_file > $ANS_TMP
} # end of get_existing_parts_image
#
get_existing_disks_from_img() {
   local ANS_TMP=$1
   local tgt_file=$2
   local tgt_disks
   local ASK_IMGNAME=1
   local pt_tmp=""
   #
   NUMDISKS="$(wc -w $tgt_file/disk 2>/dev/null | awk '{print $1}')"
   if [ -n "$NUMDISKS" -a "$NUMDISKS" -gt 0 ]; then
     if [ $NUMDISKS -lt $MAX_DIALOG_HEIGHT ]; then
       height="$NUMDISKS"
     else
       height="$MAX_DIALOG_HEIGHT"
     fi
     TMP=`mktemp /tmp/ocs.XXXXXX`
     trap "[ -f "$TMP" ] && rm -f $TMP" HUP INT QUIT TERM EXIT
     PROMPT="$(for p in `cat $tgt_file/disk` ; do echo "$p" "disk("${p:0:2}")_disk("${p:2}")" off ; done)"
     if [ "$NUMDISKS" = "1" ]; then
       # If only one dev, turn on it.
       PROMPT="$(echo $PROMPT | sed -e "s/off$/on/g")"
     fi
     while [ "$ASK_IMGNAME" -ne 0 ]; do
       $DIA --backtitle "$msg_nchc_free_software_labs" --title  \
       "$msg_nchc_clonezilla" --checklist "$msg_choose_the_disks_to_restore:" \
       0 0 $height $PROMPT 2> $TMP
       tgt_disks="$(cat $TMP)"
       if [ -z "$tgt_disks" ]; then
         $DIA --backtitle "$msg_nchc_free_software_labs" --title "$msg_nchc_clonezilla" \
        --msgbox "$msg_you_must_input_partition!\n$msg_please_do_it_again!" 0 0 
       else
         ASK_IMGNAME=0
       fi
     done
   else
     [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
     echo "No disk image file is found in $tgt_file! $msg_program_stop!!!"
     [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
     [ -f "$TMP" ] && rm -f $TMP
     exit 1
   fi
   [ -f "$TMP" ] && rm -f $TMP
   # return the valule
   echo $tgt_disks > $ANS_TMP
   # strip the unnecessary quotation mark "
   LC_ALL=C perl -pi -e "s/\"//g" $ANS_TMP
} # end of get_existing_disks_from_img

#
get_existing_partitions_from_img() {
   local ANS_TMP="$1"
   local tgt_file="$2"
   local skip_mounted_part="$3"
   local tgt_parts
   local ASK_IMGNAME=1
   local pt_tmp=""

   # skip_mounted_part is the switch to skip the mounted partition if this is running in clonezilla client. i.e. the selected partition will write to the partition in this machine.
   # On the other hand, if this function is called by DRBL server, it's not necessary to skip the mounted partition, since it's nothing to do with the mounted partition in the server.
   # By default, we assume it's running in the client (Clonezilla live mode)
   [ -z "$skip_mounted_part" ] && skip_mounted_part="yes"
   # If the image is created by savedisk, convert it to partitions lists
   if [ ! -f "$tgt_file/parts" ]; then
     for ipt in $tgt_file/*pt.sf; do
       for part in `get_known_partition_sf_format $ipt`; do
         pt_tmp="$pt_tmp $part"
       done
     done
     echo "$pt_tmp" > $tgt_file/parts
   fi
   #
   NUMPARTS="$(wc -w $tgt_file/parts 2>/dev/null | awk '{print $1}')"
   if [ -n "$NUMPARTS" -a "$NUMPARTS" -gt 0 ]; then
     if [ $NUMPARTS -lt $MAX_DIALOG_HEIGHT ]; then
       height="$NUMPARTS"
     else
       height="$MAX_DIALOG_HEIGHT"
     fi
     TMP=`mktemp /tmp/ocs.XXXXXX`
     trap "[ -f "$TMP" ] && rm -f $TMP" HUP INT QUIT TERM EXIT
     PROMPT="$(for p in `cat $tgt_file/parts` ; do 
                 # skip the mounted partition if this is running in clonezilla client. i.e. the selected partition will write to the partition in this machine.
		 # On the other hand, if this function is called by DRBL server, it's not necessary to skip the mounted partition, since it's nothing to do with the mounted partition in the server.
		 if [ "$skip_mounted_part" = "yes" ]; then
                   [ -n "$(mount | grep -Ew "^/dev/$p")" ] && continue
		 fi
                 echo "$p" "disk("${p:0:3}")_partition("${p:3}")" off
	       done)"
     if [ "$NUMPARTS" = "1" ]; then
       # If only one dev, turn on it.
       PROMPT="$(echo $PROMPT | sed -e "s/off$/on/g")"
     fi
     if [ -z "$PROMPT" ]; then
       [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
       echo "No prompt for dialog! Something went wrong!"
       [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
       echo "$msg_program_stop!"
       exit 1
     fi
     while [ "$ASK_IMGNAME" -ne 0 ]; do
       $DIA --backtitle "$msg_nchc_free_software_labs" --title  \
       "$msg_nchc_clonezilla" --checklist "$msg_choose_the_parts_to_restore $msg_linux_parts_MS_mapping" \
       0 0 $height $PROMPT 2> $TMP
       tgt_parts="$(cat $TMP)"
       if [ -z "$tgt_parts" ]; then
         $DIA --backtitle "$msg_nchc_free_software_labs" --title "$msg_nchc_clonezilla" \
        --msgbox "$msg_you_must_input_partition!\n$msg_please_do_it_again!" 0 0 
       else
         ASK_IMGNAME=0
       fi
     done
   else
     [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
     echo "No partition image file is found in $tgt_file! $msg_program_stop!!!"
     [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
     [ -f "$TMP" ] && rm -f $TMP
     exit 1
   fi
   [ -f "$TMP" ] && rm -f $TMP
   # return the valule
   echo $tgt_parts > $ANS_TMP
   # strip the unnecessary quotation mark "
   LC_ALL=C perl -pi -e "s/\"//g" $ANS_TMP
} # end of get_existing_partitions_from_img

#
check_dhcpd_config() {
    ## check dhcp server
    if [ ! -e $DHCPDCONF_DIR/dhcpd.conf ]; then
      [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
      echo "You must setup DRBL first. Check http://drbl.nchc.org.tw or http://drbl.sf.net for more details!"
      [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
      echo "$msg_program_stop!!!"
      exit 1
    fi
}
check_dhcpd_if_range() {
  # Warning if range option is used in /etc/dhcpd.conf
  grep "^[ +]*[^#][ *]range" $DHCPDCONF_DIR/dhcpd.conf > /dev/null 2>&1 
  RETVAL=$?
  if [ $RETVAL -eq 0 ]; then
    # found the range option, which is not comment
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "$msg_range_found_in_dhcpd_conf"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    if [ "$batch_mode" != "on" ]; then
      echo -n "$msg_press_enter_to_continue..."
      read
    fi
  fi
}
#
force_pxe_clients_boot_label() {
  local label="$1"
  local menu_label="$2"
  [ -z "$label" ] && echo "No label in force_pxe_clients_boot_label!" && exit 1
  if [ -n "$menu_label" ]; then
    echo "Setting the PXE clients to DRBL mode with label \"$menu_label\"..."
    $DRBL_SCRIPT_PATH/sbin/set-default-pxe-img -i "$label" -c $PXE_CONF -l "$menu_label"
  else
    echo "Setting the PXE clients to DRBL mode, keep orig menu label..."
    $DRBL_SCRIPT_PATH/sbin/set-default-pxe-img -i "$label" -c $PXE_CONF
  fi
  if [ "$label" = "local" ]; then
    # force to make local boot without PXE passwd checking if it's local
    # which means user should have the right to use local OS without password
    # after client's local OS is restored by clonezilla.
    lines=$(get_pxecfg_image_block local $PXE_CONF)
    begin_line=$(echo $lines | awk -F" " '{print $1}')
    end_line=$(echo $lines | awk -F" " '{print $2}')
    sub_cmd="if ($begin_line..$end_line) {s/^[[:space:]]*[#]*[[:space:]]*(MENU PASSWD.*)/  # \$1/gi}"
    LC_ALL=C perl -pi -e "$sub_cmd" $PXE_CONF
  fi
  # specify the nodes if assigned by user
  [ "$LIST_HOST" = "on" ] && set_specific_host_pxe_conf $IP_LIST
} # end of force_pxe_clients_boot_label
#
# This function is already replaced by force_pxe_clients_boot_label
force_pxe_clients_boot_local() {
  # force to make local boot without PXE passwd checking
  lines=$(get_pxecfg_image_block local $PXE_CONF)
  begin_line=$(echo $lines | awk -F" " '{print $1}')
  end_line=$(echo $lines | awk -F" " '{print $2}')
  sub_cmd="if ($begin_line..$end_line) {s/^[[:space:]]*[#]*[[:space:]]*(MENU PASSWD.*)/  # \$1/gi}"
  LC_ALL=C perl -pi -e "$sub_cmd" $PXE_CONF
  echo "Setting the PXE clients to local boot..."
  # make it use default one
  $DRBL_SCRIPT_PATH/sbin/set-default-pxe-img -i local -c $PXE_CONF
  # specify the nodes if assigned by user
  [ "$LIST_HOST" = "on" ] && set_specific_host_pxe_conf $IP_LIST
}
#
do_nfs_restart() {
      [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
      echo "Restart nfs server to make sure the stalled NFS is cleaned..."
      [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
      /etc/init.d/$NFS_SRV_NAME restart
}
#
find_multicast_ethernet_port() {
    echo -n "Finding the multicast seed ethernet port... "
    for eth in `get_dhcpd_interface`; do
      # keep the port for multicast to be echoed in screen
      eth_for_multicast="$eth"
      break
    done
    echo "done."
    echo "Will use ethernet port $eth_for_multicast for multicast seed in this clonezilla server."
} # end of find_multicast_ethernet_port
#
clean_filesystem_header_in_partition() {
  # function to clean the filesystem hearder in partition, normally 1MB is enough. For ext3 it's 65536 bytes.
  # If we do not clean it, for some filesystem, partimage or clone program might not overwrite that part, and blkid, parted or partimage might give wrong info.
  local part_="$1"
  if [ -n "$part_" -a -e "$part_" ]; then
    echo "Clean filesystem hearder in device $part_..."
    dd if=/dev/zero of="$part_" bs=1M count=1 &>/dev/null
  else
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "$part_ NOT found!"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
  fi
} # end of clean_filesystem_header_in_partition
# get ntfs restore statistics
get_ntfs_image_info() {
  local report_info=$1
  local start_t=$2
  local end_t=$3
  local space_used_number space_used_unit
  # time_elapsed, time_elapsed_in_min, space_used and speed are global variables
  [ ! -e "$report_info" -o -z "$start_t" -o -z "$end_t" ] && return 1
  # The report of ntfsclone is like:
  # Space in use       : 7 MB (0.5%)
  calculate_elapsed_time $start_t $end_t
  space_used="$(awk -F":" '/^Space in use/ {print $2}' $report_info | sed -e "s/[[:space:]]*([[:digit:]]*.*)[[:space:]]*//g" | sed -e "s/^[[:space:]]*//g")"
  space_used_number="$(echo $space_used | awk -F" " '{print $1}')"
  space_used_unit="$(echo $space_used | awk -F" " '{print $2}')"
  speed="$(LC_ALL=C echo "scale=1; $space_used_number / $time_elapsed *60.0" | bc -l)"
  # show it with unit
  speed="$speed $space_used_unit/min"
  return 0
}
# Since there is a bug in partimage for split image files,
# http://www.partimage.org/forums/viewtopic.php?t=363
# Use stdin input for restoring is a good solution.
do_unicast_stdin_restore() {
  # part is like: /dev/hda1
  local target_d="$1"
  local img_file="$2"
  local part="$3"  # part is like /dev/hda1
  local start_time end_time image_name_ split_by dd_report_sig_pid part_size
  # if the report_msg is empty, put the initial one: image_name_
  image_name_="$(basename $target_d)"
  [ -z "$report_msg" ] && report_msg="Unicast restored $image_name_,"
  echo $msg_delimiter_star_line
  check_if_target_dev_busy_before_restoring $part
  clean_filesystem_header_in_partition $part
  echo $msg_delimiter_star_line
  # time_elapsed, time_elapsed_in_min and speed are global variables
  # get the cat program: cat, zcat or bzcat
  if [ -f "$target_d/$img_file.000" -o \
       -f "$target_d/$img_file.aa" -o \
       -f "$target_d/$img_file" ]; then
    # The saved image is from partimage
    if [ -f "$target_d/$img_file.000" ]; then
      get_image_cat_zip_cmd $target_d/$img_file.000
      # The files are split by partimage, like hda1.000, hda1.001, so we have to add "."
      img_file_prefix="$img_file.*"
      split_by="partimage"
    elif [ -f "$target_d/$img_file.aa" ]; then
      get_image_cat_zip_cmd $target_d/$img_file.aa
      # The files are split by split, like hda1.aa, hda1.ab, so we have to add "."
      img_file_prefix="$img_file.*"
      split_by="split"
    else
      get_image_cat_zip_cmd $target_d/$img_file
      # The file is NOT split, so the file name is just like "hda1" only, no "."
      img_file_prefix="$img_file"
      # although it's not split, we still sort it as split_by="partimage", since it the same model with partimage
      split_by="partimage"
    fi
    echo $msg_delimiter_star_line
    echo "Starting unicast restoring image ${target_d##*/} to $part..."
    start_time="$(date +%s%N)"
    case "$split_by" in
      partimage)
        toskip=0
        # partimage will put header in 2nd and later volumes, so we have to uncompress it, then strip it before pipe them to partimage
        ( for img in $target_d/$img_file_prefix; do
            $cat_prog $img | dd bs=512 skip=$toskip 2> /dev/null
            toskip=1
          done
        ) \
        | partimage $DEFAULT_PARTIMAGE_RESTORE_OPT $PARTIMAGE_RESTORE_OPT -o -d restore $part stdin
        ;;
      split)
        # ntfsclone+split will NOT put header in 2nd and later volumes, so just cat them
        ( for img in $target_d/$img_file_prefix; do
            cat $img
          done
        ) \
        | $unzip_stdin_cmd \
        | partimage $DEFAULT_PARTIMAGE_RESTORE_OPT $PARTIMAGE_RESTORE_OPT -o -d restore $part stdin
        ;;
    esac
    # TODO
    # partimage will return 1 no matter it finishes or not when we use stdin and
    # other options to suppress the warning message... 
    # So just return 0.
    rc=0
    end_time="$(date +%s%N)"
    calculate_elapsed_time $start_time $end_time
    # prepare statistic report
    conv_return_code_to_human_read $rc
    report_msg="$report_msg $part, $clone_status, $time_elapsed_in_min mins;"
  elif [ -f "$target_d/$img_file.ntfs-img" -o -f "$target_d/$img_file.ntfs-img.aa" ]; then
    # The saved image is from ntfsclone
    if [ -f "$target_d/$img_file.ntfs-img.aa" ]; then
      get_image_cat_zip_cmd $target_d/$img_file.ntfs-img.aa
      # The files are split, like hda1.00, hda1.01, so we have to add "."
      img_file_prefix="$img_file.ntfs-img.*"
    else
      get_image_cat_zip_cmd $target_d/$img_file.ntfs-img
      # The file is NOT split, so the file name is just like "hda1" only, no "."
      img_file_prefix="$img_file.ntfs-img"
    fi
    # assign ntfsclone tmp file.
    case "$ntfsclone_progress" in
    "image_dir")
       IP="$($DRBL_SCRIPT_PATH/bin/get-ip-link-2-drbl-srv)"
       ntfs_img_info_tmp="$target_d/restoring-${IP}-${img_file}-`date +%Y%m%d%H%M`" ;;
    *)
       ntfs_img_info_tmp="$(mktemp /tmp/ntfs_info.XXXXXX)" ;;
    esac
    echo $msg_delimiter_star_line
    echo "Starting unicast restoring image ${target_d##*/} to $part..."
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "If this action fails or hangs, check:"
    echo "* Is the saved image $target_d/$img_file.ntfs-img* corrupted ?" 
    [ "$(root_over_nfs)" = "yes" ] && echo "* Network connection and NFS service."
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo $msg_delimiter_star_line

    start_time="$(date +%s%N)"
    # ntfsclone does not put any header, so just cat them
    ( for img in $target_d/$img_file_prefix; do
        cat $img
      done
    ) | \
    $unzip_stdin_cmd | \
    ntfsclone --restore-image --overwrite $part - | tee $ntfs_img_info_tmp
    rc="$?"
    end_time="$(date +%s%N)"
    get_ntfs_image_info $ntfs_img_info_tmp $start_time $end_time
    # For unicast, no preparation time, it's accurate enough.
    echo ">>> Time elapsed: $time_elapsed secs (~ $time_elapsed_in_min mins), average speed: $speed"
    [ -f "$ntfs_img_info_tmp" ] && rm -f $ntfs_img_info_tmp
    # prepare statistic report
    conv_return_code_to_human_read $rc
    report_msg="$report_msg $part, $clone_status, $space_used, $time_elapsed_in_min mins, $speed;"
  elif [ -f "$target_d/$img_file.dd-img" -o -f "$target_d/$img_file.dd-img.aa" ]; then
    # The saved image is from dd
    if [ -f "$target_d/$img_file.dd-img.aa" ]; then
      get_image_cat_zip_cmd $target_d/$img_file.dd-img.aa
      # The files are split, like hda1.00, hda1.01, so we have to add "."
      img_file_prefix="$img_file.dd-img.*"
    else
      get_image_cat_zip_cmd $target_d/$img_file.dd-img
      # The file is NOT split, so the file name is just like "hda1" only, no "."
      img_file_prefix="$img_file.dd-img"
    fi
    dd_img_info_tmp="$(mktemp /tmp/dd_info.XXXXXX)"
    echo $msg_delimiter_star_line
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "$msg_image_saved_from_dd"
    echo "$msg_cons_for_dd_clone"
    echo "$msg_will_be_inefficent_and_slow..."
    echo "$msg_status_report_is_very_primitive..."
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo $msg_delimiter_star_line
    echo "Starting unicast restoring image ${target_d##*/} to $part..."
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "If this action fails or hangs after several minutes, check:"
    echo "* Is the saved image $target_d/$img_file.dd-img* corrupted ?" 
    [ "$(root_over_nfs)" = "yes" ] && echo "* Network connection and NFS service."
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL

    # since dd does not report status with any option, we have to send SIGUSR1 to tell dd to report every some secs...
    # dd report interval (secs) is loaded from drbl-ocs.conf
    [ -e "$target_d/${img_file}-size" ] && part_size="$(cat $target_d/${img_file}-size)"
    trigger_dd_status_report $part $part_size &
    dd_report_sig_pid=$!
    echo $msg_delimiter_star_line

    start_time="$(date +%s%N)"
    # dd does not put any header, so just cat them
    ( for img in $target_d/$img_file_prefix; do
        cat $img
      done
    ) | \
    $unzip_stdin_cmd | \
    LC_ALL=C dd bs=1M of=$part 2>&1 | tee $dd_img_info_tmp
    rc="$?"
    end_time="$(date +%s%N)"
    kill -9 $dd_report_sig_pid &>/dev/null
    get_dd_image_info $dd_img_info_tmp $start_time $end_time
    # For unicast, no preparation time, it's accurate enough.
    echo ">>> Time elapsed: $time_elapsed secs (~ $time_elapsed_in_min mins), average speed: $speed"
    [ -f "$dd_img_info_tmp" ] && rm -f $dd_img_info_tmp
    # prepare statistic report
    conv_return_code_to_human_read $rc
    report_msg="$report_msg $part, $clone_status, $space_used, $time_elapsed_in_min mins, $speed;"
  fi
  echo "Finished unicast restoring image ${target_d##*/} to $part."
  echo $msg_delimiter_star_line
  return $rc
} # end of do_unicast_stdin_restore
#
do_multicast_udpcast_restore() {
  # part is like: /dev/hda1
  # port is global variable
  # time_elapsed, time_elapsed_in_min and speed are global variables
  local target_d="$1"
  local img_file="$2"
  local part="$3"
  local start_time end_time time_limit time image_name_ dd_report_sig_pid part_size
  # if the report_msg is empty, put the initial one: image_name_
  image_name_="$(basename $target_d)"
  [ -z "$report_msg" ] && report_msg="Multicast restored $image_name_,"
  udpcast_rec_cmd="udp-receiver --nokbd --mcast-all-addr $MULTICAST_ALL_ADDR --portbase $port $TIME_TO_LIVE_OPT" 
  clean_filesystem_header_in_partition $part
  echo $msg_delimiter_star_line
  echo "Starting multicast restoring image ${target_d##*/} to $part..."
  if [ -f "$target_d/$img_file.000" -o \
       -f "$target_d/$img_file.aa" -o \
       -f "$target_d/$img_file" ]; then
    # The saved image is from partimage
    # get $unzip_stdin_cmd from image file
    if [ -f "$target_d/$img_file.000" ]; then
      get_image_cat_zip_cmd $target_d/$img_file.000
    elif [ -f "$target_d/$img_file.aa" ]; then
      get_image_cat_zip_cmd $target_d/$img_file.aa
    else
      get_image_cat_zip_cmd $target_d/$img_file
    fi
    echo $msg_delimiter_star_line

    start_time="$(date +%s%N)"
    $udpcast_rec_cmd 2>$udpcast_stderr | $unzip_stdin_cmd | partimage \
    $DEFAULT_PARTIMAGE_RESTORE_OPT $PARTIMAGE_RESTORE_OPT restore $part stdin 
    # TODO
    # partimage will return 1 no matter it finishes or not when we use stdin and
    # other options to suppress the warning message... 
    # So just return 0.
    rc=0
    end_time="$(date +%s%N)"
    calculate_elapsed_time $start_time $end_time
    # prepare statistic report
    conv_return_code_to_human_read $rc
    report_msg="$report_msg $part, $clone_status, $time_elapsed_in_min mins;"
  elif [ -f "$target_d/$img_file.ntfs-img" -o -f "$target_d/$img_file.ntfs-img.aa" ]; then
    # The saved image is from ntfsclone
    # get $unzip_stdin_cmd from image file
    if [ -f "$target_d/$img_file.ntfs-img.aa" ]; then
      get_image_cat_zip_cmd $target_d/$img_file.ntfs-img.aa
    else
      get_image_cat_zip_cmd $target_d/$img_file.ntfs-img
    fi
    # assign ntfsclone tmp file.
    case "$ntfsclone_progress" in
    "image_dir")
       IP="$($DRBL_SCRIPT_PATH/bin/get-ip-link-2-drbl-srv)"
       ntfs_img_info_tmp="$target_d/restoring-${IP}-${img_file}-`date +%Y%m%d%H%M`" ;;
    *)
       ntfs_img_info_tmp="$(mktemp /tmp/ntfs_info.XXXXXX)" ;;
    esac
    echo $msg_delimiter_star_line
    echo "Waiting for multicast clone to start..."
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "If this action fails or hangs, check:"
    echo "* Is the saved image $target_d/$img_file.ntfs-img* corrupted ?" 
    [ "$(root_over_nfs)" = "yes" ] && echo "* Network connection or switch ? Did you forget to link those network switches if you have more than 1 ? Does your network switch block multicast packet ?"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo $msg_delimiter_star_line

    start_time="$(date +%s%N)"
    $udpcast_rec_cmd 2>$udpcast_stderr | $unzip_stdin_cmd | \
    ntfsclone --restore-image --overwrite $part - | tee $ntfs_img_info_tmp
    rc="$?"
    end_time="$(date +%s%N)"
    get_ntfs_image_info $ntfs_img_info_tmp $start_time $end_time
    echo ">>> Time elapsed: $time_elapsed secs (~ $time_elapsed_in_min mins), average speed: $speed"
    # For multicast, with preparation time, it's no so accurate.
    echo ">>> NOTE: The elapsed time may include some preparation time, so the speed is not very accurate."
    [ -f "$ntfs_img_info_tmp" ] && rm -f $ntfs_img_info_tmp
    # prepare statistic report
    conv_return_code_to_human_read $rc
    report_msg="$report_msg $part, $clone_status, $space_used, $time_elapsed_in_min mins, $speed;"
  elif [ -f "$target_d/$img_file.dd-img" -o -f "$target_d/$img_file.dd-img.aa" ]; then
    # The saved image is from dd
    # get $unzip_stdin_cmd from image file
    if [ -f "$target_d/$img_file.dd-img.aa" ]; then
      get_image_cat_zip_cmd $target_d/$img_file.dd-img.aa
    else
      get_image_cat_zip_cmd $target_d/$img_file.dd-img
    fi
    # assign dd tmp file.
    dd_img_info_tmp="$(mktemp /tmp/dd_info.XXXXXX)"
    echo $msg_delimiter_star_line
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "$msg_image_saved_from_dd"
    echo "$msg_cons_for_dd_clone"
    echo "$msg_will_be_inefficent_and_slow..."
    echo "$msg_status_report_is_very_primitive..."
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo $msg_delimiter_star_line
    echo "Waiting for multicast clone to start..."
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "If this action fails or hangs after several minutes, check:"
    echo "* Is the saved image $target_d/$img_file.dd-img* corrupted ?" 
    [ "$(root_over_nfs)" = "yes" ] && echo "* Network connection or switch ? Did you forget to link those network switches if you have more than 1 ? Does your network switch block multicast packet ?"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo $msg_delimiter_star_line

    # since dd does not report status with any option, we have to send SIGUSR1 to tell dd to report every some secs...
    # dd report interval (secs) is loaded from drbl-ocs.conf
    [ -e "$target_d/${img_file}-size" ] && part_size="$(cat $target_d/${img_file}-size)"
    trigger_dd_status_report $part $part_size &
    dd_report_sig_pid=$!
    start_time="$(date +%s%N)"
    $udpcast_rec_cmd 2>$udpcast_stderr | $unzip_stdin_cmd | \
    LC_ALL=C dd bs=1M of=$part 2>&1 | tee $dd_img_info_tmp
    rc="$?"
    end_time="$(date +%s%N)"
    kill -9 $dd_report_sig_pid &>/dev/null
    get_dd_image_info $dd_img_info_tmp $start_time $end_time
    echo ">>> Time elapsed: $time_elapsed secs (~ $time_elapsed_in_min mins), average speed: $speed"
    # For multicast, with preparation time, it's no so accurate.
    echo ">>> NOTE: The elapsed time may include some preparation time, so the speed is not very accurate."
    [ -f "$dd_img_info_tmp" ] && rm -f $dd_img_info_tmp
    # prepare statistic report
    conv_return_code_to_human_read $rc
    report_msg="$report_msg $part, $clone_status, $space_used, $time_elapsed_in_min mins, $speed;"
  fi
  echo "Finished multicast restoring image ${target_d##*/} to $part."
  echo $msg_delimiter_star_line
  
  # note! We must sleep a while to let the udp-receiver to finish their jobs then start the next ones.
  echo -n "Preparing the next... "
  # we sleep at least 3 secs.
  sleep 3
  # To avoid all the clients notify at almost same time, we use random sleep before joining multicast.
  # The max time can not be smaller than mcast_max_wait_time
  if [ -n "$mcast_max_wait_time" ]; then
    # -2 secs to make the program have time to run until udpcast request
    time_limit="$(min $SLEEP_TIME_AFTER_PART_CLONED $((mcast_max_wait_time-2)))"
  else
    time_limit="$SLEEP_TIME_AFTER_PART_CLONED"
  fi
  # if < 0, set it as 0
  [ "$time_limit" -le 0 ] && time_limit=0
  [ "$verbose" = "on" ] && echo -n "Max wait time: $time_limit. "
  time="$(get_random_time $time_limit)"
  countdown $time
  echo
  echo $msg_delimiter_star_line
  port="$((port+2))"

  return $rc
} # end of do_multicast_udpcast_restore
#
udp_send_part_img() {
  # part is like: hda1 or lv0l0
  # imagedir, port and ipart are global variable
  local target_d="$1"
  local img_file="$2"
  local part="$3"
  local n split_by
  # for the 2nd and other partition, we should shorten the preparation time
  [ -n "$mcast_wait_time" -a $ipart -gt 1 ] && udpcast_hold_opt1="--min-wait $PART_PREPARE_TIME" 
  [ -n "$mcast_max_wait_time" -a $ipart -gt 1 ] && udpcast_hold_opt3="--max-wait $PART_PREPARE_TIME" 
  udpcast_send_cmd="udp-sender $udp_sender_extra_opt $udpcast_hold_opt1 $udpcast_hold_opt2 $udpcast_hold_opt3 --interface $eth_for_multicast --nokbd --mcast-all-addr $MULTICAST_ALL_ADDR --portbase $port $TIME_TO_LIVE_OPT"
  if [ -f "$imagedir/$target_d/$img_file.000" -o \
       -f "$imagedir/$target_d/$img_file.aa" -o \
       -f "$imagedir/$target_d/$img_file" ]; then
    # The saved image is from partimage
    # get the cat program: cat/zcat/bzcat and zip_stdin_cmd (gzip -c/bzip2 -c/cat)
    if [ -f "$imagedir/$target_d/$img_file.000" ]; then
      get_image_cat_zip_cmd $imagedir/$target_d/$img_file.000
      # The files are split by partimage, like hda1.000, hda1.001, so we have to add "."
      img_file_prefix="$img_file.*"
      split_by="partimage"
    elif [ -f "$imagedir/$target_d/$img_file.aa" ]; then
      get_image_cat_zip_cmd $imagedir/$target_d/$img_file.aa
      # The files are split by split, like hda1.aa, hda1.ab, so we have to add "."
      img_file_prefix="$img_file.*"
      split_by="split"
    else
      get_image_cat_zip_cmd $imagedir/$target_d/$img_file
      # The file is NOT split, so the file name is just like "hda1" only, no "."
      img_file_prefix="$img_file"
      # although it's not split, we still sort it as split_by="partimage", since it the same model with partimage
      split_by="partimage"
    fi
    [ -n "$verbose" ] && echo $msg_delimiter_star_line
    [ -n "$verbose" ] && echo "Feeding multicast restoring image $target_d to client's /dev/$part... "
   (
    case "$split_by" in
      partimage)
        # partimage+(split inside partimage) will put header in 2nd and later volumes, so we have to something different. not just cat them.
        # The reason we do not use the same skill as do_unicast_stdin_restore
        # is that we want compressed image to be transferred in the network, not
        # the uncompress one, which need a lot of bandwidth.
        # Therefore we uncompress it, strip the header in 2nd/3rd... volume, then compress it again before send it
        n=0
        ( for img in $imagedir/$target_d/$img_file_prefix; do
            n=$((n + 1))
            if [ $n -eq 1 ]; then
             # By separating this, do not compress and uncompress the 1st image.
             # If the VOL_LIMIT is large enough, there is no split images.
             # uncompress | dd and strip header | compress!!!
             cat $img
            else
              $cat_prog $img | dd skip=1 bs=512 2>/dev/null | $zip_stdin_cmd
            fi
          done
        )
        ;;
      split)
        # partimage+split will NOT put header in 2nd and later volumes, so just cat them
        ( for img in $imagedir/$target_d/$img_file_prefix; do
            cat $img
          done
        )
	;;
    esac
   ) \
   | $udpcast_send_cmd 2>$udpcast_stderr
  elif [ -f "$imagedir/$target_d/$img_file.ntfs-img" -o -f "$imagedir/$target_d/$img_file.ntfs-img.aa" ]; then
    # The saved image is from ntfsclone
    # here we do not have to run get_image_cat_zip_cmd since ntfsclone image does not contain its own special header, so we do not have to uncompress and strip that.
    if [ -f "$imagedir/$target_d/$img_file.ntfs-img.aa" ]; then
      # The files are split, like hda1.00, hda1.01, so we have to add "."
      img_file_prefix="$img_file.ntfs-img.*"
    else
      # The file is NOT split, so the file name is just like "hda1" only, no "."
      img_file_prefix="$img_file.ntfs-img"
    fi
    # we use the original format (do not uncompress) to save the bandwidth
    # ntfsclone does not put any header, so just cat them
    ( for img in $imagedir/$target_d/$img_file_prefix; do
        cat $img
      done
    ) | \
    $udpcast_send_cmd 2>$udpcast_stderr
  elif [ -f "$imagedir/$target_d/$img_file.dd-img" -o -f "$imagedir/$target_d/$img_file.dd-img.aa" ]; then
    # The saved image is from dd
    # here we do not have to run get_image_cat_zip_cmd since dd image does not contain its own special header, so we do not have to uncompress and strip that.
    if [ -f "$imagedir/$target_d/$img_file.dd-img.aa" ]; then
      # The files are split, like hda1.00, hda1.01, so we have to add "."
      img_file_prefix="$img_file.dd-img.*"
    else
      # The file is NOT split, so the file name is just like "hda1" only, no "."
      img_file_prefix="$img_file.dd-img"
    fi
    # we use the original format (do not uncompress) to save the bandwidth
    # dd does not put any header, so just cat them
    ( for img in $imagedir/$target_d/$img_file_prefix; do
        cat $img
      done
    ) | \
    $udpcast_send_cmd 2>$udpcast_stderr
  fi
  # prepare parameters for the next 
  ipart="$((ipart+1))"
  port="$((port+2))"
  [ -n "$verbose" ] && echo "done!"
} # end of udp_send_part_img
#
mail_clonezilla_log_to_root() {
  local mail_cli="$1"
  # backup the log file so that it won't be overwritten, since we will run this function in background.
  ocs_job_tmp="$(mktemp /tmp/ocs_job.XXXXXX)"
  cp -f /var/log/clonezilla-jobs.log $ocs_job_tmp
  # mail the clonezilla.log to root.
  $mail_cli -s "Clonezilla report at `date +%F-%R`" root < $ocs_job_tmp
  [ -f "$ocs_job_tmp" ] && rm -f $ocs_job_tmp
}
# function to feed multicast for restoring partitions
feed_multicast_restoreparts() {
  # This is for None-LVM parts
  local tgt_dir="$1"
  local tgt_parts="$2"
  local time_to_pause_before_mail mail_client=""
  # prepare the mail client
  if type mutt &>/dev/null; then
    mail_client="mutt"
  elif type mail &>/dev/null; then
    mail_client="mail"
  elif type mailx &>/dev/null; then
    mail_client="mailx"
  fi
  # Set the initial value for ipart and port.
  ipart=1
  port=$MULTICAST_PORT
  do_LVM_restore_feed="no"
  for partition in $tgt_parts; do
    # hda1 -> hda
    hd_tmp=${partition:0:3}
    if [ -n "$(grep -Ew "^/dev/$partition" $imagedir/$tgt_dir/${hd_tmp}-pt.sf | grep -i "Id=8e")" ]; then
      # do not process LVM partition here, we will process LVM partition and its LV together, later
      do_LVM_restore_feed="yes"
      continue
    fi
    udp_send_part_img $tgt_dir $partition $partition
  done
  # We have to do restore LVM (PV/VG/LV) together, not follow every partition
  if [ "$do_LVM_restore_feed" = "yes" ]; then
    # LVM exists, feed images to restore PV/VG/LV.
    feed_LVM_multicast "$tgt_parts" $port
  fi
  # spawn a background program to wait and mail the clonezilla-jogs.log to root
  (
   # if we can find mail/mutt program then we do it, otherwise skip.
   if [ -n "$mail_client" ]; then
     # When the udp-sender finishes, clonezilla client need to do more:
     # 0. sync the disk
     # 1. prepare the next step (time necessary: $SLEEP_TIME_AFTER_PART_CLONED)
     # 2. restore MBR (time necessary: < 1 sec)
     # 3. run grub-install if assigned (time necessary: maybe 5 secs or more)
     # 4. notify ocsmgrd its job is done (time necessary: $NOTIFY_OCS_SERVER_TIME_LIMIT secs)
     # For 0-3, we estimate it as 30 secs totally.
     time_to_pause_before_mail=$((30+$SLEEP_TIME_AFTER_PART_CLONED+$NOTIFY_OCS_SERVER_TIME_LIMIT))
     sleep $time_to_pause_before_mail
     # Now all clients should finish all their jobs, we can mail now.
     mail_clonezilla_log_to_root $mail_client
   fi
  ) &
  # If always restore, i.e. multicast loop, respawn itself
  if [ "$always_restore" = "yes" ]; then
    mcast_loop="$(( mcast_loop + 1 ))"
    # If it's clonezilla_box_mode, clean dhcpd leases
    echo $msg_delimiter_star_line
    echo "Start the next multicast service (#$mcast_loop)..."
    if [ "$clonezilla_mode" = "clonezilla_box_mode" ]; then
      if [ "$clean_dhcpd_lease_in_clone_box_mode" = "yes" ]; then
        echo "Clean the dhcpd leases..."
        $DRBL_SCRIPT_PATH/sbin/clean-dhcpd-lease &>/dev/null
      fi
    fi
    echo $msg_delimiter_star_line
    feed_multicast_restoreparts "$tgt_dir" "$tgt_parts"
  fi
} # end of feed_multicast_restoreparts

# Check if LVM
check_LVM_partition() {
  # This function is to check existing LVM partition
  # part is like "/dev/hda1"
  local part="$1"
  [ -z "$part" ] && return 1
  # Check if the partition is LVM, if so, return 0, else 1
  # Ceasar suggested to use get_part_info:
  # if [ -n "$(file -Ls $part | grep -i "LVM" 2>/dev/null)" ]; then
  if [ -n "$($DRBL_SCRIPT_PATH/sbin/get_part_info $part filesystem | grep -i "LVM" 2>/dev/null)" ]; then
    return 0
  else 
    return 1
  fi
}

# Save LVM: PV/VG/LV data
# Part of these codes are from http://www.trickytools.com/php/clonesys.php
# Thanks to Jerome Delamarche (jd@inodes-fr.com)
save_logv() {
  local DEV
  local VG
  local UUID
  local LOGV
  local image_name_
  local target="$1"
  PV_PARSE_CONF="$target_dir/lvm_vg_dev.list"
  LOGV_PARSE_CONF="$target_dir/lvm_logv.list"
  # if the report_msg is empty, put the initial one: image_name_
  image_name_="$(basename $target_dir)"
  [ -z "$report_msg" ] && report_msg="Saved $image_name_,"
  rm -f $PV_PARSE_CONF $LOGV_PARSE_CONF
  $DRBL_SCRIPT_PATH/sbin/lvm2-start.sh
  echo "Parsing LVM layout..."
  pvscan | grep lvm2 | while read LINE; do
    DEV=`echo $LINE | tr -s ' ' | cut -d' ' -f2`
    VG=`echo $LINE | tr -s ' ' | cut -d' ' -f4`
    # DEV is like /dev/hda2
    # We just want to keep the chosen target
    # The results in $PV_PARSE_CONF is like:
    # ----------------------------------------------------
    # vg /dev/hda2 qdlt6U-M4bo-xExy-XG9Q-U7pg-bOXN-n54i5Y
    # ----------------------------------------------------
    # Ex: target: hda1 hda2 hda3, DEV maybe: /dev/hda2
    if [ -n "$(echo "$target" | grep -E "\<${DEV##*/}\>")" ]; then
      UUID="$(pvdisplay $DEV | grep "PV UUID" | awk -F" " '{print $3}')"
      echo "$VG $DEV $UUID" | tee -a $PV_PARSE_CONF
    fi
  done
  
  # We just want the LV in chosen target
  echo "Parsing logical volumes..."
  # lvscan results are like:
  # ACTIVE            '/dev/vg3/lvol0' [1.20 GB] inherit
  # ACTIVE            '/dev/vg3/lvol1' [648.00 MB] inherit
  # ACTIVE            '/dev/vg/lvol0' [1.50 GB] inherit
  # ACTIVE            '/dev/vg/lvol1' [500.00 MB] inherit
  #                         ^^ The VG
  
  # find LV for chosen PV.
  lvscan | grep "/.*/" | while read LINE; do
    LOGV=`echo $LINE |tr -s ' ' | cut -d' ' -f2 | tr -d "'"`
    # LOGV is like: /dev/vg3/lvol0
    while read vg dev uuid; do
     # only keep LOGV is in the chosen vg
     if [ -n "$(echo $LOGV | grep -E "/dev/$vg/" 2>/dev/null)" ]; then
      file_system="$(file -Ls $LOGV | awk -F":" '{print $2}')"
      echo "$LOGV $file_system" | tee -a $LOGV_PARSE_CONF
      break
     fi 
    done < $PV_PARSE_CONF
  done
  
  # Backup the vg conf
  echo "Saving the VG config... "
  while read vg dev uuid; do
    vgcfgbackup -f $target_dir/lvm_$vg.conf $vg 2>/dev/null
  done < $PV_PARSE_CONF
  echo "done!"
  [ ! -f "$PV_PARSE_CONF" ] && exit 1
  [ ! -f "$LOGV_PARSE_CONF" ] && exit 1
  while read lv fs; do
   [ ! -e "$lv" ] && continue
   fn="$(echo $lv | sed -e "s|^/dev/||" -e "s|/|-|g")"
   echo "Saving $lv as filename: $fn. Filesystem: $fs"
   # skip swap or extended partition
   case "$fs" in 
     *[Ss][Ww][Aa][Pp]*)
       output_swap_partition_uuid_label $lv $target_dir/swappt-${fn}.info
       continue ;;
     *extended*) 
       continue ;;
   esac
   image_save $lv $target_dir $fn
   echo $msg_delimiter_star_line
  done < $LOGV_PARSE_CONF
} # end of save_logv

# Restore LVM: PV/VG/LV data
# Part of these codes are from http://www.trickytools.com/php/clonesys.php
# Thanks to Jerome Delamarche (jd@inodes-fr.com)
restore_logv() {
  # mode is unicast or multicast
  local tgt_parts="$1"  # tgt_parts is like: hda1 hda2 hda5
  local mode="$2"
  local port="$3"
  local volg is_in_chosen_partition
  if [ -z "$mode" ]; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
    echo "To restore LVM, you must specify it's unicast or multicast!!!"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo "$msg_program_stop!!!"
    exit 1
  fi
  PV_PARSE_CONF="$target_dir/lvm_vg_dev.list"
  LOGV_PARSE_CONF="$target_dir/lvm_logv.list"
  [ ! -f "$PV_PARSE_CONF" ] && exit 1
  [ ! -f "$LOGV_PARSE_CONF" ] && exit 1
  #
  $DRBL_SCRIPT_PATH/sbin/lvm2-stop.sh
  # Clean the filesystem header in partition where PV exists
  # This is an insurance, since later when we use partimage/ntfsclone to clone, it will not overwrite file header in that partition, it will only overwrite the data in LV. Then parted, blkid will give wrong info.
  while read lv fs; do
   # we process the real data partition, only those in the chosen partitions
   # Ex:
   # /dev/vg3/lvol0  Linux rev 1.0 ext3 filesystem data (large files)
   # Then lvol0 is belong to VG vg3
   volg="$(echo "$lv" | awk -F"/" '{print $3}')"
   # Find if the LV is in the chosen partition (via VG, we can determine that)
   # EX: tgt_parts: hda1, hda3, hda5...
   #     vg3 /dev/hda3 nPMQQ0-D2yN-YRHL-9fBM-0cUm-vgcw-DCUTri
   for ipt in $tgt_parts; do
     if [ -n "$(grep -E "[[:space:]]+/dev/$ipt[[:space:]]+" $PV_PARSE_CONF | grep -E "\<$volg\>")" ]; then
       # Found the chosen partitions is in the VG
       clean_filesystem_header_in_partition /dev/$ipt
     fi
   done
  done < $LOGV_PARSE_CONF
  # create PV first
  echo "Creating the PV... "
  while read vg dev uuid; do
    pvcreate -ff --yes --uuid $uuid $dev
  done < $PV_PARSE_CONF
  echo "done!"
  
  # Restore the vg conf
  echo "Restoring the VG config... "
  while read vg dev uuid; do
    vgcfgrestore -f $target_dir/lvm_$vg.conf $vg 2>/dev/null
  done < $PV_PARSE_CONF
  echo "done!"
  #
  $DRBL_SCRIPT_PATH/sbin/lvm2-start.sh
  
  while read lv fs; do
   [ ! -e $lv ] && continue
   # Then we process the real data partition, only those in the chosen partitions
   # Ex:
   # /dev/vg3/lvol0  Linux rev 1.0 ext3 filesystem data (large files)
   # Then lvol0 is belong to VG vg3
   volg="$(echo "$lv" | awk -F"/" '{print $3}')"
   # Find if the LV is in the chosen partition (via VG, we can determine that)
   # EX: tgt_parts: hda1, hda3, hda5...
   #     vg3 /dev/hda3 nPMQQ0-D2yN-YRHL-9fBM-0cUm-vgcw-DCUTri
   is_in_chosen_partition="no"
   for ipt in $tgt_parts; do
     if [ -n "$(grep -E "[[:space:]]+/dev/$ipt[[:space:]]+" $PV_PARSE_CONF | grep -E "\<$volg\>")" ]; then
       # Found the chosen partitions is in the VG
       is_in_chosen_partition="yes"
       break
     fi
   done
   # If not in the chosen partition, skip this, continue with the next.
   [ "$is_in_chosen_partition" = "no" ] && continue
   fn="$(echo $lv | sed -e "s|^/dev/||" -e "s|/|-|g")"
   # create the swap if it's swap partition
   case "$fs" in 
     *[Ss][Ww][Aa][Pp]*)
        echo $msg_delimiter_star_line
        echo "Found the swap partition $lv, create it by:"
        # read LABEL, UUID info for $partition if swappt-${fn}.info exists
        uuid_opt=""
        label_opt=""
        if [ -e "$target_dir/swappt-${fn}.info" ]; then
          UUID=""
          LABEL=""
          . "$target_dir/swappt-${fn}.info"
          [ -n "$UUID" ] && uuid_opt="-U $UUID"
          [ -n "$LABEL" ] && label_opt="-L $LABEL"
        fi
        echo "mkswap-uuid $label_opt $uuid_opt $lv"
        mkswap-uuid $label_opt $uuid_opt $lv
        echo $msg_delimiter_star_line
	# then skip the rest.
        continue;; 
   esac
   echo "Restoring device $fn..."
   case "$mode" in
     unicast)
       do_unicast_stdin_restore $target_dir $fn $lv
       ;;
     multicast)
       do_multicast_udpcast_restore $target_dir $fn $lv
       ;;
   esac
  done < $LOGV_PARSE_CONF
} # end of restore_logv

# Multicast feeding to Restore LVM: PV/VG/LV data
# Part of these codes are from http://www.trickytools.com/php/clonesys.php
# Thanks to Jerome Delamarche (jd@inodes-fr.com)
feed_LVM_multicast() {
  # This is for LVM parts
  # imagedir, port and ipart are global variable
  local tgt_parts="$1"
  local port="$2"
  local volg n
  if [ -z "$port" ]; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
    echo "To feed multicast LVM, you must specify the port!!!"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo "$msg_program_stop!!!"
    exit 1
  fi
  # Note! With a leading $imagedir before /$target_dir
  PV_PARSE_CONF="$imagedir/$target_dir/lvm_vg_dev.list"
  LOGV_PARSE_CONF="$imagedir/$target_dir/lvm_logv.list"
  [ ! -f "$PV_PARSE_CONF" ] && exit 1
  [ ! -f "$LOGV_PARSE_CONF" ] && exit 1
  
  while read lv fs; do
   # Process the real data partition, only those in the chosen partitions
   # Ex:
   # /dev/vg3/lvol0  Linux rev 1.0 ext3 filesystem data (large files)
   # Then lvol0 is belong to VG vg3
   volg="$(echo "$lv" | awk -F"/" '{print $3}')"
   # Find if the LV is in the chosen partition (via VG, we can determine that)
   # EX: tgt_parts: hda1, hda3, hda5...
   #     vg3 /dev/hda3 nPMQQ0-D2yN-YRHL-9fBM-0cUm-vgcw-DCUTri
   is_in_chosen_partition="no"
   for ipt in $tgt_parts; do
     if [ -n "$(grep -E "[[:space:]]+/dev/$ipt[[:space:]]+" $PV_PARSE_CONF | grep -E "\<$volg\>")" ]; then
       # Found the chosen partitions is in the VG
       is_in_chosen_partition="yes"
       break
     fi
   done
   [ "$is_in_chosen_partition" = "no" ] && continue
   fn="$(echo $lv | sed -e "s|^/dev/||" -e "s|/|-|g")"
   [ ! -e "$imagedir/$target_dir/$fn.000" -a \
     ! -e "$imagedir/$target_dir/$fn.aa" -a \
     ! -e "$imagedir/$target_dir/$fn" ] && continue
   udp_send_part_img $target_dir $fn $lv
  done < $LOGV_PARSE_CONF
}
#
check_target_hd() {
  local disk_file="$1"
  local tgt_hd="$2"
  if [ -z "$tgt_hd" ]; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
    echo "The target harddisk you assigned is nothing!"
    echo "$disk_file is empty ?"
    echo "Maybe diskfull when you saved image ?"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo "$msg_program_stop!"
    exit 1
  fi
}
#
check_target_parts() {
  local parts_file="$1"
  local tgt_parts="$2"
  if [ -z "$tgt_parts" ]; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
    echo "The target partitions you assigned is nothing!"
    echo "$parts_file is empty ?"
    echo "Maybe diskfull when you saved image ?"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo "$msg_program_stop!"
    exit 1
  fi
}
#
stop_ocs_service() {
  local to_gen_ssi_files="$1"
  local IP ihost
  # check if "ocsmgrd" is running ?
  if [ ! -e "$ocs_lock_dir/clonezilla.lock" ]; then 
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
    echo "OCS is not started!"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    exit 0
  fi

  # HOST_OPTION_MODIFY: 
  # Now clonezilla can not be multi-image, so it will be a mess if just
  # stop some clients. Therefore if "$LIST_HOST" = "on", we still stop
  # all the clonezilla process.
  # if [ "$LIST_HOST" != "on" ]; then
    # $LIST_HOST !=on means we are setting all the clients.
    # affect the whole clients, BEGIN
    # From clonezilla 2.0, the ocs-srv-reset is almost useless. Comment it.
    #[ "$clean_client_all_ocs_related_srv" = "yes" ] && rm -f $RCX_ROOTDIR/rc[06].d/S05ocs-srv-reset
    
    # kill drbl-ocs, ocsmgrd and udpcast daemon
    kill_ps_by_kill_9 drbl-ocs
    kill_ps_by_killall_9 ocsmgrd
    kill_ps_by_killall_9 udp-sender
    
    # clean the tag
    rm -f $ocs_lock_dir/clonezilla.lock
    # affect the whole clients, END

    # restart nfs to make sure stalled NFS is cleaned.
    [ "$nfs_restart" != "no" ] && do_nfs_restart
  # fi

  # HOST_OPTION_MODIFY: modified since we specify hosts.

  # when remove ocs_opt etc in pxelinux cfg, PXE_CONF is global variable.
  remove_ocs_opt_etc_in_pxelinux_cfg_block clonezilla
  # prepare the HOSTNAME-IP-MAC table
  OCS_TMP=`mktemp /tmp/ocs_clean_tmp.XXXXXX`
  trap "[ -f "$OCS_TMP" ] && rm -f $OCS_TMP" HUP INT QUIT TERM EXIT
  $DRBL_SCRIPT_PATH/bin/parse_dhcpd_conf $OCS_TMP
  # Note! We can not use "for ihost in $drblroot/*; do" since we have to create every pxelinux config for client. If we use that, it will fail in DRBL SSI and Clonezilla box.
  #for ihost in $drblroot/*; do
  for IP in `get-client-ip-list`; do
    ihost="$drblroot/$IP"
    # if the LIST_HOST is on, skip those IP not listed in the $IP_LIST
    if [ "$LIST_HOST" = "on" ]; then
      [ -z "$(echo $IP_LIST | grep -Ew "$IP")" ] && continue
    fi
    echo "Stop OCS mode for node $IP, no matter it's in OCS mode or not."
    [ -f "$ihost/etc/inittab.ocs" ] && mv -f $ihost/etc/inittab.ocs $ihost/etc/inittab
    # remove the pxe config file in $pxecfg_pd/tftpboot/pxelinux.cfg (IP-based)
    pxecfg="$($DRBL_SCRIPT_PATH/bin/gethostip.pl $IP)"
    [ -f "$PXELINUX_DIR/$pxecfg" ] && rm -f $PXELINUX_DIR/$pxecfg

    # HOST_OPTION_MODIFY: maybe it's not IP based, it's MAC based.
    # These files look like: 01-MAC address (with ":" -> "-"),
    pxecfg_MAC="01-$(grep ${ihost##*/} $OCS_TMP | awk -F" " '{print $3}' | tr ":" "-")"
    [ -f "$PXELINUX_DIR/$pxecfg_MAC" ] && rm -f $PXELINUX_DIR/$pxecfg_MAC

    # remove the stalled ocs-run.param.
    rm -f $drblroot/$IP/etc/ocs/ocs-run.param
    if [ "$clean_client_all_ocs_related_srv" = "yes" ]; then
      # clean the services in rc1.d which are specially for drbl-ocs
      echo "Removing OCS related services in node IP add. = $IP"
      $DRBL_SCRIPT_PATH/sbin/ocs-related-srv -n $IP remove
    fi
  done
  [ -f "$OCS_TMP" ] && rm -f $OCS_TMP

  # update the ssi image if it's in clonezilla box mode.
  # Loading the mode when drblpush, we need to know if it's clonezilla_box_mode
  # This only necessary in clonezilla server, since /etc/drbl/drbl_deploy.conf does not exist in client.
  . /etc/drbl/drbl_deploy.conf

  # To avoid, clientdir=node_root is missing.. we check here.
  # If it's clonezilla_box_mode, clean dhcpd leases, too.
  if [ "$clonezilla_mode" = "clonezilla_box_mode" ]; then
    echo $msg_delimiter_star_line
    if [ "$to_gen_ssi_files" != "no" ]; then
      # use any one in the IP list
      if [ "$LIST_HOST" = "on" ]; then
        # if the list is MAC, actually dcs will convert it to IP address, but how about if user just run drbl-ocs ?
        ssi_ip="$(echo $IP_LIST | awk -F" " '{print $1}')"
      fi
      $DRBL_SCRIPT_PATH/sbin/gen_ssi_files -t $ssi_ip
    fi
    if [ "$clean_dhcpd_lease_in_clone_box_mode" = "yes" ]; then
      echo "Clean the dhcpd leases..."
      $DRBL_SCRIPT_PATH/sbin/clean-dhcpd-lease &>/dev/null
    fi
    echo $msg_delimiter_star_line
  fi

} # end of stop_ocs_servic

#
# start ocs services
# parameter: 
#   task 
#      "save" for saving the data to server
#      "restore" for restoring the data from server  
#   option
#      "$target_image" for save/restore the partition /dev/hda1
#      "$target_dir $target_hd" for save/restore the disk
#   n_clients
#      how many clients will be connected (for multicast only)
start_ocs_service() {
  local task
  local option
  local n_clients
  local node_ip
  local tgt_img=""
  local tgt_dev=""
  local regen_opt
  while [ $# -gt 0 ]; do
    case "$1" in
      -t|--task)
         shift
         if [ -z "$(echo $1 |grep ^-.)" ]; then
           # skip the -xx option, in case 
           task="$1"
           shift
         fi
         [ -z "$task" ] && echo "-t is used, but no task assigned." && exit 1
         ;;
      -o|--option)
         shift
         if [ -z "$(echo $1 |grep ^-.)" ]; then
           # skip the -xx option, in case 
           option="$1"
           shift
         fi
         [ -z "$option" ] && echo "-o is used, but no option assigned." && exit 1
         ;;
      -n|--n-clients)
         shift
         if [ -z "$(echo $1 |grep ^-.)" ]; then
           # skip the -xx option, in case 
           n_clients="$1"
           shift
         fi
         [ -z "$n_clients" ] && echo "-n is used, but no n_clients assigned." && exit 1
         ;;
    esac
  done
  # The option="$target_image $target_dev", we can get tgt_img & target_dev
  tgt_img_menu="$(echo $option | awk -F" " '{print $1}')"
  tgt_dev_menu="$(echo $option | awk '{ for (i=2; i<=NF; i++) printf "%s ", $i; printf "\n"; }' | sed -e "s/^[[:space:]]*//" -e "s/[[:space:]]*$//")"
  # maybe it will be ask_user, if so, change it as choose later
  tgt_img_menu="$(echo "$tgt_img_menu" | sed -e "s/ask_user/(choose later)/g")"
  tgt_dev_menu="$(echo "$tgt_dev_menu" | sed -e "s/ask_user/(choose later)/g")"
  

  echo "Clonezilla image dir: $ocs_lock_dir"
  # Create the ocs_lock_dir if it does not exist
  [ ! -d "$ocs_lock_dir" ] && mkdir -p $ocs_lock_dir

  # For old version, we have to remove the old tag, now we use $ocs_lock_dir/clonezilla.lock instead of $ocsroot/clonezilla.lock
  rm -f $ocsroot/clonezilla.lock

  # Is "ocsmgrd" running ?
  if [ -e "$ocs_lock_dir/clonezilla.lock" ]; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "Your system is already in clonezilla mode... we will stop it first if necessary, then start it again..."
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    # We do not have to generate SSI files in stop_ocs_service, since later the files will be created.
    stop_ocs_service no
  fi

  if [ ! -e "$SYSCONF_PATH/$DHCP_SRV_NAME" ]; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
    echo "You must setup DRBL first. Check http://drbl.sf.net or http://drbl.nchc.org.tw for more details."
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
  fi

  # Warning if range option is used in /etc/dhcpd.conf
  check_dhcpd_if_range

  # restart nfs to make sure stalled NFS is cleaned.
  [ "$nfs_restart" != "no" ] && do_nfs_restart

  # Loading the mode when drblpush, we need to know if it's clonezilla_box_mode
  # This only necessary in clonezilla server, since /etc/drbl/drbl_deploy.conf does not exist in client.
  . /etc/drbl/drbl_deploy.conf

  # From clonezilla 2.0, the ocs-srv-reset is almost useless. Comment it.
  # make sure next time when server boots, it is NOT in ocs mode
  #( cd $RCX_ROOTDIR/rc0.d/; ln -fs $RCX_REL_INITD/ocs-srv-reset S05ocs-srv-reset )
  #( cd $RCX_ROOTDIR/rc6.d/; ln -fs $RCX_REL_INITD/ocs-srv-reset S05ocs-srv-reset )

  # (1) set the UDP port for multicast
  # (2) get the multicast ethernet port $eth_for_multicast
  if [ -n "$(echo "$task" | grep -e "^multicast_restore")" ]; then
    # (1)
    # OCS_OPT is global variable from ocs-sr
    OCS_OPT="$OCS_OPT --mcast-port $MULTICAST_PORT"
    # (2)
    [ -z "$eth_for_multicast" ] && find_multicast_ethernet_port
    # echo notes for network environment in multicast clonezilla
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "$msg_you_are_using_multicast_clonezilla"
    echo "$msg_ethernet_port_is_up_confirm: $eth_for_multicast "
    echo "$msg_more_NIC_connect_each_other"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    if [ "$batch_mode" != "on" ]; then
      echo -n "$msg_press_enter_to_continue..."
      read
    fi
  fi

  # HOST_OPTION_MODIFY: modified since we specify hosts.
  # Note! We can not use "for ihost in $drblroot/*; do" since we have to create every pxelinux config for client. If we use that, it will fail in DRBL SSI and Clonezilla box.
  #for ihost in $drblroot/*; do
  for node_ip in `get-client-ip-list`; do
    # if the LIST_HOST is on, skip those IP not listed in the $IP_LIST
    if [ "$LIST_HOST" = "on" ]; then
      [ -z "$(echo $IP_LIST | grep -Ew "$node_ip")" ] && continue
    fi
    echo "Starting the OCS service for node IP add. = $node_ip"
    prep_client_inittab_rc1_srv $node_ip $task "$option"
  done

  # Set the single user mode password if not setting for client...This will be safer..."
  set_clients_rc1_passwd $IP_LIST

  # HOST_OPTION_MODIFY: 
  # before start the ocsmgrd, kill the stale one
  kill_ps_by_killall_9 ocsmgrd

  # make client to boot in drbl mode with menu label specified.
  case "$task" in
    "saveparts")
       ocs_menu_label="save partitions $tgt_dev_menu as image $tgt_img_menu"
       ;;
    "savedisk")
       ocs_menu_label="save disk $tgt_dev_menu as image $tgt_img_menu"
       ;;
    "restoreparts")
       ocs_menu_label="unicast restore $tgt_img_menu to part $tgt_dev_menu"
       ;;
    "restoredisk")
       ocs_menu_label="unicast restore $tgt_img_menu to disk $tgt_dev_menu"
       ;;
    "multicast_restoredisk")
       ocs_menu_label="multicast restore $tgt_img_menu to disk $tgt_dev_menu"
       ;;
    "multicast_restoreparts")
       ocs_menu_label="multicast restore $tgt_img_menu to part $tgt_dev_menu"
       ;;
     *)
  esac

  # First, set the pxe menu to Clonezilla:..., since maybe it's in drbl mode. If -y0 is set, later we will switch the default menu to local.
  force_pxe_clients_boot_label clonezilla "Clonezilla: $ocs_menu_label"
  # set runlevel 1 to kernel parameter in pxelinux config
  add_runlevel_1_in_pxelinux_cfg_block clonezilla

  # If the mode is in always_restore, and PXE default menu is assigned to local (-y0), we will set local boot as default. Since the always_restore mode is only useful with local OS exists. But if in some scenario, such as production clone flow, maybe pxe_menu_default_mode=clone is useful, since a lot of hardisks will be replaced one by one.
  if [ "$always_restore" = "yes" ]; then
   case "$pxe_menu_default_mode" in
     "local")
         force_pxe_clients_boot_label local "$local_os_menu_label"
         ;;
     "drbl")
         force_pxe_clients_boot_label drbl
         ;;
   esac
  fi

  # start the ocsmgrd daemon
  # the $ocs_lock_dir/clonezilla.lock is a tag for ocs is running,
  # after start/stop cloning, we will remove $ocs_lock_dir/clonezilla.lock
  touch $ocs_lock_dir/clonezilla.lock
  # UDPCAST MODIFY
  $DRBL_SCRIPT_PATH/sbin/ocsmgrd &
  ocs_log_file="$(grep "joblogfile =>" /opt/drbl/sbin/ocsmgrd | awk -F" " '{print $3}')"
  echo "$msg_client_job_are_logged_in $ocs_log_file"
  if [ -n "$(echo $task | grep -i "restore")" ]; then
    # if the mode restore, show the log prompt
    echo "$msg_client_sfdisk_log_are_in $RESTORE_SFDISK_LOG"
  fi

  # Multicast part
  # set udpcast holding option
  if [ -n "$(echo "$task" | grep -e "^multicast_restore")" ]; then
    # prepare the multicast option
    [ -n "$mcast_wait_time" ] && udpcast_hold_opt1="--min-wait $mcast_wait_time" 
    [ -n "$n_clients" ] && udpcast_hold_opt2="--min-clients $n_clients"
    [ -n "$mcast_max_wait_time" ] && udpcast_hold_opt3="--max-wait $mcast_max_wait_time" 
    [ -z "$udpcast_hold_opt1" -a -z "$udpcast_hold_opt2" -a -z "$udpcast_hold_opt3" ] && echo "Someting went wrong! I can not find time_to_wait, clients_to_wait or clients+time_to_wait option! $msg_program_stop!" && exit 1
  fi

  #
  trap "exit 1" HUP INT QUIT TERM EXIT
  target_dir="$(echo "$option" | awk -F' ' '{print $1}')"
  case "$task" in
    "multicast_restoredisk")
      # find the available partitions
      target_parts=""
      # Note! $target_dir is not absolute path, because when task_saveparts do the real job later, it will add $imagedir, so we have to put $imagedir here.
      for ihd in $target_hd; do
        for partition in `get_known_partition_sf_format $imagedir/$target_dir/${ihd}-pt.sf`; do
          target_parts="$target_parts $partition"
        done
      done
      # Let feed_multicast_restoreparts do the real job
      # target_dir will be got from $option when run feed_multicast_restoreparts
      feed_multicast_restoreparts "$target_dir" "$target_parts" &
      ;;

    "multicast_restoreparts")
      # Let feed_multicast_restoreparts do the real job
      #feed_multicast_restoreparts "$option" &
      feed_multicast_restoreparts "$target_dir" "$target_parts" &
      ;;
  esac

  # To avoid, clientdir=node_root is missing.. we check here.
  # If it's clonezilla_box_mode, clean dhcpd leases, too.
  if [ "$clonezilla_mode" = "clonezilla_box_mode" ]; then
    echo $msg_delimiter_star_line
    echo "You are in clonezilla box mode!"
    [ "$regen_drbl_ssi_template_tarball" = "no" ] && regen_opt="--no-create-ssi-template"
    # use any one in the IP list
    if [ "$LIST_HOST" = "on" ]; then
      # if the list is MAC, actually dcs will convert it to IP address, but how about if user just run drbl-ocs ?
      ssi_ip="$(echo $IP_LIST | awk -F" " '{print $1}')"
      $DRBL_SCRIPT_PATH/sbin/tune-clientdir-opt -l en --no-stop-ocs -t $ssi_ip -z yes $regen_opt
    else
      $DRBL_SCRIPT_PATH/sbin/tune-clientdir-opt -l en --no-stop-ocs -z yes $regen_opt
    fi
    if [ "$clean_dhcpd_lease_in_clone_box_mode" = "yes" ]; then
      echo "Clean the dhcpd leases..."
      $DRBL_SCRIPT_PATH/sbin/clean-dhcpd-lease &>/dev/null
    fi
    echo $msg_delimiter_star_line
  fi

  # We do not have to run /opt/drbl/sbin/gen_ssi_files in the end of start_ocs_service is because we will always update the /etc/rc1.d for client (mapping to /tftpboot/node_root/drbl_ssi/rc1.d in the server) when a client boots in SSI mode.
}
# end of start_ocs_service

# create inittab for ocs
prep_client_inittab_rc1_srv() {
  # Usage:
  # prep_client_inittab_rc1_srv $node_ip $task "$option"
  # Then it will run like this:
  # /sbin/drbl-ocs restoredisk /home/partimag/woody_base hda
  local node_ip=$1
  local rc1_task=$2
  local rc1_opt=$3
  local hw_det_opt

  # change the mode to rc1.d
  case "$modify_client_etc_inittab" in
  yes)
    # backup inittab as inittab.ocs
    if [ ! -e "$drblroot/$node_ip/etc/inittab.ocs" ]; then
      # backup the original one
      cp -f $drblroot/$node_ip/etc/inittab $drblroot/$node_ip/etc/inittab.ocs
    fi
    LC_ALL=C perl -p -i -e "s/^id:[1-5]:initdefault:/id:1:initdefault:/g" $drblroot/$node_ip/etc/inittab
    ;;
  no)
    [ "$verbose" = "on" ] && echo "Do not modify client's /etc/inittab, just put \"1\" in bootparam."
    ;;
  esac

  if [ "$re_put_ocs_related_srv_in_client_rc1d" = "yes" ]; then
    # Prepare some ocs related services in client's rc1.d/
    # We need kudzu/harddrake in client... so that for example, SCSI devices, the driver can be loaded auto.
    # use != off to default let it on even if the variable hw_detect is empty.
    if [ "$hw_detect" != "off" ]; then
      hw_det_opt="-d on"
    else
      hw_det_opt="-d off"
    fi
    $DRBL_SCRIPT_PATH/sbin/ocs-related-srv $hw_det_opt -n $node_ip put
    # create the ocs-run service to run save or restore when client boots.
    ( cd $drblroot/$node_ip/$RCX_ROOTDIR/rc1.d/; ln -fs $RCX_REL_INITD/ocs-run S19ocs-run )
  fi

  if [ "$rc1_task" = "select_in_client" ]; then
     # reset these variables, we just want to run /opt/drbl/sbin/ocs only
     # skip prep ocsroot (clonezilla iamge home), since it's in DRBL environment, the ocsroot is mounted by NFS already.
     # Note! OCS_OPT should not use -s/-S/-a/-b/-z/0-6
     # because: /sbin/init [ -a ] [ -s ] [ -b ] [ -z xxx ] [ 0123456Ss ]
     OCS_OPT="-k"
     rc1_opt=""
  fi
  case "$ocs_client_trig_type" in
    ocs-run.param)
      # NOTE! This mode will not work in Clonezilla box, only for full 
      # clonezilla mode. since we will not regen the SSI template 
      # after drbl-ocs is run. We always set ocs_client_trig_type=both 
      # in drbl-ocs.conf.
      # The reason we keep this mode is that it's easier for us to debug
      # in full drbl/clonezilla mode.
      # Remove the stalled one in pxelinux config file, just in case.
      remove_ocs_opt_etc_in_pxelinux_cfg_block clonezilla
      if [ -d "$drblroot/$node_ip/etc/" ]; then
	# We can not just mkdir -p $drblroot/$node_ip/etc/ocs/, otherwise it will make DRBL SSI template fail... since if it's created, the etc tarball will just contains one file /etc/ocs/ocs-run.param. We will only test if $drblroot/$node_ip/etc/ exists, if so, it's full drbl/clonezilla mode. we can create the dir $drblroot/$node_ip/etc/ocs/, and put the param.
        mkdir -p $drblroot/$node_ip/etc/ocs/
        # Note! Just put it in one line, it will be run by ocs-run as parameter.
        echo --language en $OCS_OPT $rc1_task $rc1_opt > $drblroot/$node_ip/etc/ocs/ocs-run.param
      fi
      ;;
    proc-cmdline)
      # remove the stalled ocs-run.param, just in case.
      rm -f $drblroot/$node_ip/etc/ocs/ocs-run.param
      add_ocs_opt_etc_in_pxelinux_cfg_block clonezilla "--language en $OCS_OPT $rc1_task $rc1_opt"
      ;;
    both)
      if [ -d "$drblroot/$node_ip/etc/" ]; then
	# We can not just mkdir -p $drblroot/$node_ip/etc/ocs/, otherwise it will make DRBL SSI template fail... since if it's created, the etc tarball will just contains one file /etc/ocs/ocs-run.param. We will only test if $drblroot/$node_ip/etc/ exists, if so, it's full drbl/clonezilla mode. we can create the dir $drblroot/$node_ip/etc/ocs/, and put the param.
        mkdir -p $drblroot/$node_ip/etc/ocs/
        # Note! Just put it in one line, it will be run by ocs-run as parameter.
        echo --language en $OCS_OPT $rc1_task $rc1_opt > $drblroot/$node_ip/etc/ocs/ocs-run.param
      fi
      add_ocs_opt_etc_in_pxelinux_cfg_block clonezilla "--language en $OCS_OPT $rc1_task $rc1_opt"
      ;;
    *)
      [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
      echo "Unknown mode for ocs_client_trig_type in $DRBL_SCRIPT_PATH/conf/drbl-ocs.conf!"
      [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
      echo "$msg_program_stop!"
      exit 1
  esac
} # end of prep_client_inittab_rc1_srv

# task_notify_localboot
# ask for localboot next time
task_notify_localboot() {
  if [ "$always_restore" = "yes" ]; then
     echo "It's in always-restore clonezilla mode!"
     echo "Do not notify clonezilla server to switch to local boot mode."
     echo $msg_delimiter_star_line
     echo "Finished!"
     return 2
  fi

  # check if it's spawned by drbl-ocs.
  is_spawned_by_drbl_ocs $ocs_ppid
  rc=$?
  if [ "$rc" -gt 0 ]; then
    echo "This program is not started by Clonezilla server, so skip notifying it the job is done."
    echo "Finished!"
    return 2
  fi

  echo $msg_delimiter_star_line
  echo -n "Notifying clonezilla server my job is done... "
  netdevices="$($DRBL_SCRIPT_PATH/bin/get-nic-devs)"
  for device in $netdevices; do
    ip="$($DRBL_SCRIPT_PATH/bin/get_ip $device)"
    [ -z "$ip" ] && continue
    ocs_server="$($DRBL_SCRIPT_PATH/bin/get_nfsserver $ip)"
    # If ocsserver is not found, then this NIC is not for DRBL, try another one.
    [ -z "$ocs_server" ] && continue
    mac="$($DRBL_SCRIPT_PATH/bin/get_mac $device)"
    break
  done
  # Found ip, ocs_server, mac, we can notify ocs server
  # To avoid all the clients notify at almost same time, we use random sleep before send info.
  time="$(get_random_time $NOTIFY_OCS_SERVER_TIME_LIMIT)"
  countdown $time
  echo
  echo -n "Sending info \"$ip $mac $report_msg\" to $ocs_server:$OCSMGRD_PORT... "
  $DRBL_SCRIPT_PATH/bin/socket.pl -m "$ip $mac $report_msg" -h $ocs_server -p $OCSMGRD_PORT
  echo "done!"
  echo $msg_delimiter_star_line
  echo "Finished!"
} # end of task_notify_localboot
#
show_warning_about_write_failed() {
  local img_dir=$1
  [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
  echo "Something went wrong! The disk or partition information can NOT be written in target directory $img_dir!"
  echo "Maybe the disk or partition is full in Clonezilla server ?"
  echo "$msg_press_enter_to_continue..."
  [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
  read
} # show_warning_about_write_failed

# task "savedisk"
# parameter:
#  $1 target_dir
#  $2 target_hd
task_savedisk() {
  local target_dir=$1
  local target_hd=$2
  local target_parts=$2
  local target_swap_parts

  screen_not_blank
  active_proc_partitions
  #######################################################
  # 2003/05/12: ask user about target_dir and target_hd #
  #######################################################
  if [ "$target_dir" = "ask_user" ]; then
    ANS_TMP=`mktemp /tmp/ocs_ans.XXXXXX`
    local ASK_IMG_NAME=1
    trap "[ -f "$ANS_TMP" ] && rm -f $ANS_TMP" HUP INT QUIT TERM EXIT
    while [ "$ASK_IMG_NAME" -ne 0 ]; do
      get_input_image_name $ANS_TMP
      target_dir="$(cat $ANS_TMP)"
      if [ "$target_dir" = "ask_user" ]; then
         $DIA --backtitle "$msg_nchc_free_software_labs" --title "$msg_nchc_clonezilla" \
        --msgbox "$msg_ask_user_is_reserved_for_save_mode\n$msg_please_do_it_again!!!" 0 0 
      else
	 ASK_IMG_NAME=0
      fi
    done
    [ -f "$ANS_TMP" ] && rm -f $ANS_TMP
    # Note! We won't add leading $ocsroot in $target_dir, because when task_saveparts do the real job later, it will add that.
  fi
   
  check_existing_target_image "$ocsroot/$target_dir"

  if [ "$target_hd" = "ask_user" ]; then
    ANS_TMP=`mktemp /tmp/ocs_ans.XXXXXX`
    local ASK_DEV_NAME=1
    trap "[ -f "$ANS_TMP" ] && rm -f $ANS_TMP" HUP INT QUIT TERM EXIT
    while [ "$ASK_DEV_NAME" -ne 0 ]; do
      get_input_dev_name $ANS_TMP harddisk
      target_hd="$(cat $ANS_TMP | tr -d \")"
      [ -n "$target_hd" ] && ASK_DEV_NAME=0
    done
    [ -f "$ANS_TMP" ] && rm -f $ANS_TMP
    check_input_hd $target_hd
  fi

  # check if the device exists
  ANS_TMP=`mktemp /tmp/ocs_chkdev.XXXXXX`
  trap "[ -f "$ANS_TMP" ] && rm -f $ANS_TMP" HUP INT QUIT TERM EXIT
  check_if_input_device_exist $ANS_TMP $target_hd 
  target_hd="$(cat $ANS_TMP | tr -d \")"
  [ -f "$ANS_TMP" ] && rm -f $ANS_TMP

  # We need the partition table to conver the selected HD to partitions.
  target_parts=""
  target_swap_parts=""
  for idisk in $target_hd; do
    # Since sfdisk does not work with EFI/GPT, we no more use sfdisk to get the partition list. Instead, we use /proc/partitions from kenrel to list them. Thanks to Justin Fitzhugh from mozilla.com.
    # pt_tmp="$(mktemp /tmp/pt_tmp.XXXXXX)"
    # sfdisk -d /dev/$idisk > $pt_tmp
    # # find the available partitions
    # for partition in `get_known_partition_sf_format $pt_tmp`; do
    #   target_parts="$target_parts $partition"
    # done
    # for partition in `get_swap_partition_sf_format $pt_tmp`; do
    #   target_swap_parts="$target_swap_parts $partition"
    # done
    # [ -f "$pt_tmp" ] && rm -f $pt_tmp
    BACKUP_DEVS=""
    get_known_partition_proc_format $idisk data
    target_parts="$BACKUP_DEVS"
    BACKUP_DEVS=""
    get_known_partition_proc_format $idisk swap
    target_swap_parts="$BACKUP_DEVS"
  done

  [ -n "$target_parts" ] && echo "The data partition to be saved: $target_parts"
  [ -n "$target_swap_parts" ] && echo "The swap partition to be saved: $target_swap_parts"
  # Let task_saveparts do the real job
  task_saveparts $target_dir "$target_parts"

  # save swap partition UUID/LABEL if it exists.
  for isp in $target_swap_parts; do
    echo "Saving swap partition $isp info in $ocsroot/$target_dir/swappt-${isp}.info if it exists..."
    output_swap_partition_uuid_label /dev/$isp $ocsroot/$target_dir/swappt-${isp}.info
  done

  # The target_dir is local variable, so we put leading $ocsroot to save the disk
  # disk is an important tag to mark that image is for entire disk, and its content consists of the save disk device name (hda, hdb...)
  echo "$target_hd" > $ocsroot/$target_dir/disk
  rc=$?
  [ "$rc" -gt 0 ] && show_warning_about_write_failed $ocsroot/$target_dir
}

# task "saveparts"
# parameter:
#  $1 target_dir
#  $2 target_parts
task_saveparts() {
  local target_dir=$1
  local target_parts=$2

  # target_hd will be extract from $target_parts, maybe we will have one more
  local target_hd=""
  local hd_tmp=""

  screen_not_blank
  active_proc_partitions
  #######################################################
  # ask user about target_dir and target_parts #
  #######################################################
  if [ "$target_dir" = "ask_user" ]; then
    ANS_TMP=`mktemp /tmp/ocs_ans.XXXXXX`
    local ASK_IMG_NAME=1
    trap "[ -f "$ANS_TMP" ] && rm -f $ANS_TMP" HUP INT QUIT TERM EXIT
    while [ "$ASK_IMG_NAME" -ne 0 ]; do
      get_input_image_name $ANS_TMP
      target_dir="$(cat $ANS_TMP)"
      if [ "$target_dir" = "ask_user" ]; then
         $DIA --backtitle "$msg_nchc_free_software_labs" --title "$msg_nchc_clonezilla" \
        --msgbox "$msg_ask_user_is_reserved_for_save_mode\n$msg_please_do_it_again!!!" 0 0 
      else
	 ASK_IMG_NAME=0
      fi
    done
    [ -f "$ANS_TMP" ] && rm -f $ANS_TMP
  fi
   
  # set $target_dir as the absolute path, i.e. put leading $ocsroot.
  target_dir="$ocsroot/$target_dir"
  check_existing_target_image $target_dir

  if [ "$target_parts" = "ask_user" ]; then
    ANS_TMP=`mktemp /tmp/ocs_ans.XXXXXX`
    trap "[ -f "$ANS_TMP" ] && rm -f $ANS_TMP" HUP INT QUIT TERM EXIT
    get_input_dev_name $ANS_TMP partition
    # we have to remove " (comes with checklist in dialog) so that for loop
    # will work (Specially for FC3/4...)
    target_parts="$(cat $ANS_TMP | tr -d \")"
    [ -f "$ANS_TMP" ] && rm -f $ANS_TMP
    check_input_partition $target_parts
  fi

  # check if the device exists
  ANS_TMP=`mktemp /tmp/ocs_chkdev.XXXXXX`
  trap "[ -f "$ANS_TMP" ] && rm -f $ANS_TMP" HUP INT QUIT TERM EXIT
  check_if_input_device_exist $ANS_TMP $target_parts
  # we have to remove " (comes with checklist in dialog) so that for loop
  # will work (Specially for FC3/4...)
  target_parts="$(cat $ANS_TMP | tr -d \")"
  [ -f "$ANS_TMP" ] && rm -f $ANS_TMP

  # find the target hd
  # maybe we will have one more hd (like hda1, hda2, hdb1, hdb3 -> hda, hdb)
  for ipart in $target_parts; do
    hd_tmp=${ipart:0:3}
    if [ -z "$target_hd" ]; then
	    target_hd="$hd_tmp"
    elif [ -z "$(echo $target_hd | grep -E "$hd_tmp" 2>/dev/null)" ]; then
	    target_hd="$target_hd $hd_tmp"
    fi
  done

  # countdown or confirm.
  countdown_or_confirm_before_save "$target_dir" "$target_hd ($target_parts)"

  if [ "$run_prerun_dir" = "yes" ]; then
     echo $msg_delimiter_star_line
     echo "Start to run the program(s) in $OCS_PRERUN_DIR..."
     warning_about_run_parts_in_debian $OCS_PRERUN_DIR
     run-parts $OCS_PRERUN_DIR
     echo $msg_delimiter_star_line
  fi

  # turn on hd dma 
  for ihd in $target_hd; do
    check_specify_hd_exists $ihd
    [ "$force_dma_on" = "yes" ] && turn_on_hd_dma /dev/$ihd
  done
  # Turn off swap and LVM2, unlock the busy partitions.
  turn_off_swap_and_LVM2

  # create the image directory
  mkdir -p $target_dir

  # save partition table
  for ihd in $target_hd; do
    check_integrity_of_partition_table_in_disk /dev/$ihd
    echo -n "Reading the partition table for /dev/$ihd..."
    sfdisk -d /dev/$ihd > $target_dir/${ihd}-pt.sf
    RETVAL="$?"
    echo "RETVAL=$RETVAL"
    clean_cylinder_boundary_warning $target_dir/${ihd}-pt.sf
    output_HD_CHS $ihd $target_dir
    if [ "$RETVAL" -ne 0 ]; then
      sfdisk_fail_process
      exit 1
    fi
  done
  echo "done!"

  # save the mbr 
  for ihd in $target_hd; do
    # MBR is the first 446 Bytes, the rest part is partition table
    echo "Saving the MBR and partition info for $ihd..."
    dd if=/dev/$ihd of=$target_dir/${ihd}-mbr count=1 bs=512
  done

  do_LVM_save="no"
  for partition in $target_parts; do
    check_LVM_partition /dev/$partition
    if [ "$?" -eq 0 ]; then
      # LVM
      # We have to do save LVM (PV/VG/LV) together, not follow every partition
      do_LVM_save="yes"
      continue
    fi
    # not LVM, normal partition
    # From partimage 0.6.6, batch mode won't stop if checkInodeForDevice is not run. Therefore comment it.
    # checkInodeForDevice /dev/$partition 
    image_save /dev/$partition $target_dir $partition
  done
  # We have to do save LVM (PV/VG/LV) together, not follow every partition
  if [ "$do_LVM_save" = "yes" ]; then
    save_logv "$target_parts"
  fi
  echo "$target_parts" > $target_dir/parts
  rc=$?
  [ "$rc" -gt 0 ] && show_warning_about_write_failed $target_dir
}

# task "restoredisk" 
# parameter:
#   $1 target_image
#   $2 target_hd
task_restoredisk() {
  local target_dir="$1"
  local target_hd="$2"
  local port="$3"

  case "$target_dir" in
    "ask_user")
        ANS_TMP=`mktemp /tmp/ocs_ans.XXXXXX`
        trap "[ -f "$ANS_TMP" ] && rm -f $ANS_TMP" HUP INT QUIT TERM EXIT
	get_existing_disk_image $ANS_TMP
        # the return name will be only one image name.
        # Note! We won't add leading $ocsroot in $target_dir, because when task_saveparts do the real job later, it will add that.
        target_dir="$(cat $ANS_TMP)"
        [ -f "$ANS_TMP" ] && rm -f $ANS_TMP
        # conver the clonezilla format for 1.x to 2.x if necessary
        convert_ocs_format_from_1.5_to_2.0_or_newer $ocsroot/$target_dir/
        ;;
  esac
  check_input_target_image "$ocsroot/$target_dir"

  case "$target_hd" in
    "ask_user")
        ANS_TMP=`mktemp /tmp/ocs_ans.XXXXXX`
        trap "[ -f "$ANS_TMP" ] && rm -f $ANS_TMP" HUP INT QUIT TERM EXIT
        get_existing_disks_from_img $ANS_TMP $ocsroot/$target_dir
        # we have to remove " (comes with checklist in dialog) so that for loop
        # will work (Specially for FC3/4...)
        target_hd="$(cat $ANS_TMP | tr -d \")"
        [ -f "$ANS_TMP" ] && rm -f $ANS_TMP
        # target name exists, but file "parts" is empty ?
        check_target_hd $ocsroot/$target_dir/disk "$target_hd"
        ;;
  esac

  # find the available partitions
  target_parts=""
  # Note! $target_dir is not absolute path, because when task_saveparts do the real job later, it will add $ocsroot, so we have to put $ocsroot here.
  for ihd in $target_hd; do
    for partition in `get_known_partition_sf_format $ocsroot/$target_dir/${ihd}-pt.sf`; do
      target_parts="$target_parts $partition"
    done
  done

  # Let task_restoreparts do the real job
  task_restoreparts $target_dir "$target_parts" $port
}

# task "multicast_restoredisk" 
# parameter:
#   $1 port
#   $2 target_dir
#   $3 target_hd
task_multicast_restoredisk() {
  local target_dir="$1"
  local target_hd="$2"
  local port=$3
  # To do backward compatability, we still keep task_multicast_restoredisk function, but let task_restoredisk do the real job.
  task_restoredisk $target_dir "$target_hd" $port
}

# task "restoreparts" 
# parameter:
#   $1 target_image
#   $2 target_parts (hda1, hda2...)
#   $3 port (if exists, it's multicast mode).
task_restoreparts() {
  local target_dir="$1"
  local target_parts="$2"
  # we use the port to identify it's unicast or multicast.
  local port="$3"
  if [ -z "$target_dir" ]; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
    echo "target_dir is NOT assigned in function task_restoreparts!!!"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo "$msg_program_stop!"
    exit 1
  fi
  if [ -z "$target_parts" ]; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
    echo "target_parts is NOT assigned in function task_restoreparts!!!"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo "$msg_program_stop!"
    exit 1
  fi

  case "$target_dir" in
    "ask_user")
       ANS_TMP=`mktemp /tmp/ocs_ans.XXXXXX`
       trap "[ -f "$ANS_TMP" ] && rm -f $ANS_TMP" HUP INT QUIT TERM EXIT
       # get_existing_parts_image will search $imagedir
       get_existing_parts_image $ANS_TMP
       # the return name will be only one image name.
       target_dir="$(cat $ANS_TMP)"
       # target_image is an important global variable, it's related to the PXE menu tag, and some other important thing.
       target_image="$target_dir"
       [ -f "$ANS_TMP" ] && rm -f $ANS_TMP
       # conver the clonezilla format for 1.x to 2.x if necessary
       convert_ocs_format_from_1.5_to_2.0_or_newer $ocsroot/$target_dir/
       ;;
  esac
  # set $target_dir as the absolute path, i.e. put leading $ocsroot.
  target_dir="$ocsroot/$target_dir"
  check_input_target_image "$target_dir"

  case "$target_parts" in
    "ask_user")
       ANS_TMP=`mktemp /tmp/ocs_ans.XXXXXX`
       trap "[ -f "$ANS_TMP" ] && rm -f $ANS_TMP" HUP INT QUIT TERM EXIT
       get_existing_partitions_from_img $ANS_TMP $imagedir/$target_image yes
       # we have to remove " (comes with checklist in dialog) so that for loop
       # will work (Specially for FC3/4...)
       target_parts="$(cat $ANS_TMP | tr -d \")"
       [ -f "$ANS_TMP" ] && rm -f $ANS_TMP
       # target name exists, but file "parts" is empty ?
       check_target_parts $imagedir/$target_image/parts "$target_parts"
       ;;
  esac

  # we use the port to identify it's unicast or multicast.
  if [ -n "$port" ]; then
    net_mode="multicast"
  else
    net_mode="unicast"
  fi

  # target_hd will be extract from $target_parts, maybe we will have one more
  local target_hd=""
  local hd_tmp=""

  # find the target hd
  # maybe we will have one more hd (like hda1, hda2, hdb1, hdb3 -> hda, hdb)
  for ipart in $target_parts; do
    hd_tmp=${ipart:0:3}
    if [ -z "$target_hd" ]; then
      target_hd="$hd_tmp"
    elif [ -z "$(echo $target_hd | grep -E "$hd_tmp" 2>/dev/null)" ]; then
      target_hd="$target_hd $hd_tmp"
    fi
  done

  # turn on hd dma 
  for ihd in $target_hd; do
    check_specify_hd_exists $ihd
    [ "$force_dma_on" = "yes" ] && turn_on_hd_dma /dev/$ihd
  done
  screen_not_blank
  active_proc_partitions

  # if $create_part (global variable) is no, only some preparations 
  # in create_partition, it won't run sfdisk in fact.
  
  # strip the leading spaces
  target_parts="$(echo $target_parts | sed -e "s/^[[:space:]]*//g")"
  # countdown or wait for confirm
  countdown_or_confirm_before_restore "$target_dir" "$target_hd ($target_parts)"

  # check if it's spawned by clonezilla server 
  is_spawned_by_drbl_ocs $ocs_ppid
  rc=$?
  if [ "$rc" -gt 0 -a "$batch_mode" != "on" ]; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "This program is not started by clonezilla server. You are using this command to restore image \"$target_dir\" to local disk(s) \"$target_hd\" in this machine."
    echo "$msg_uppercase_Warning!!! $msg_uppercase_Warning!!! $msg_uppercase_Warning!!!"
    echo "$msg_uppercase_Warning! $msg_all_data_in_dev_will_be_overwritten: $target_hd ($target_parts)"
    if [ "$ocs_sr_type" = "restoreparts" -a "$create_part" = "yes" ]; then
      echo "$msg_uppercase_Warning! $msg_option_k_is_not_chosen_part_table_will_be_recreated"
    fi
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo "$msg_let_me_ask_you_again, $msg_are_u_sure_u_want_to_continue ?"
    echo -n "[y/N] "
    read continue_restore
    case "$continue_restore" in
     y|Y|[yY][eE][sS])
       echo "OK, let's continue!"
       ;;
     *)
       echo "Abort!"
       exit 1
       ;;
    esac
  fi

  if [ "$run_prerun_dir" = "yes" ]; then
     echo $msg_delimiter_star_line
     echo "Start to run the program(s) in $OCS_PRERUN_DIR..."
     warning_about_run_parts_in_debian $OCS_PRERUN_DIR
     run-parts $OCS_PRERUN_DIR
     echo $msg_delimiter_star_line
  fi

  if [ "$create_part" = "yes" ]; then
    for ihd in $target_hd; do
      create_partition /dev/$ihd $target_dir "$sfdisk_opt"
    done
  fi

  do_LVM_restore="no"
  for partition in $target_parts; do
    # hda1 -> hda
    hd_tmp=${partition:0:3}
    if [ -n "$(grep -Ew "^/dev/$partition" $target_dir/${hd_tmp}-pt.sf | grep -i "Id=8e")" ]; then
      # We have to do restore LVM (PV/VG/LV) together, not follow every partition
      do_LVM_restore="yes"
      continue
    fi
    # From partimage 0.6.6, batch mode won't stop if checkInodeForDevice is not run. Therefore comment it.
    # checkInodeForDevice /dev/$partition
    case "$net_mode" in 
     "unicast")
        do_unicast_stdin_restore $target_dir $partition /dev/$partition
        rc="$?"
        ;;
     "multicast")
        do_multicast_udpcast_restore $target_dir $partition /dev/$partition
        rc="$?"
        ;;
    esac
    if [ $rc -gt 0 ]; then
       [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
       echo "Failed to restore partition image file $target_dir/${partition}* to /dev/$partition! Maybe this image is corrupt! $msg_press_enter_to_continue..."
       [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
       read
    fi
  done

  # swap partition
  for ihd in $target_hd; do
    for partition in `cat $target_dir/${ihd}-pt.sf | grep "Id=82" | cut -d" " -f1 | sed -e 's/\/dev\///g'`
    do
      echo "Found the swap partition /dev/$partition, create it by:"
      # read LABEL, UUID info for $partition if swappt-$partition.info exists
      uuid_opt=""
      label_opt=""
      if [ -e "$target_dir/swappt-$partition.info" ]; then
        UUID=""
	LABEL=""
        . "$target_dir/swappt-$partition.info"
	[ -n "$UUID" ] && uuid_opt="-U $UUID"
	[ -n "$LABEL" ] && label_opt="-L $LABEL"
      fi
      echo "mkswap-uuid $label_opt $uuid_opt /dev/$partition"
      mkswap-uuid $label_opt $uuid_opt /dev/$partition
      echo $msg_delimiter_star_line
    done
  done

  # We have to do restore LVM (PV/VG/LV) together, not follow every partition
  if [ "$do_LVM_restore" = "yes" ]; then
    # LVM exists, restore PV/VG/LV.
    restore_logv "$target_parts" $net_mode $port
    echo $msg_delimiter_star_line
  fi

  # re-install MBR
  for ihd in $target_hd; do
    if [ "$restore_mbr" = "yes" ]; then
      echo -n "Restoring the MBR data for $ihd... "
      # MBR is the first 446 Bytes.
      dd if=$target_dir/${ihd}-mbr of=/dev/$ihd bs=446 count=1 &>/dev/null
      echo "done!"
      echo $msg_delimiter_star_line
    fi
  done

  # re-install grub here
  if [ "$install_grub" = "on" ]; then
    install_grub_hd $grub_partition
    echo $msg_delimiter_star_line
  fi

  # resize partition if necessary
  if [ "$resize_partition" = "on" ]; then
    for partition in $target_parts; do
      # hda1 -> hd_tmp, part_no_tmp ,i.e => hda, 1 
      hd_tmp="${partition:0:3}"
      part_no_tmp="${partition:3}"
      echo "Now resize the partition for $hd_tmp$part_no_tmp"
      $DRBL_SCRIPT_PATH/sbin/resize_part $hd_tmp $part_no_tmp
      echo $msg_delimiter_star_line
    done
  fi
} # end of task_restoreparts

# task "multicast_restoreparts" 
# parameter:
#   $1 port
#   $2 target_dir
#   $3 target_hd
task_multicast_restoreparts() {
  local target_dir="$1"
  local target_parts="$2"
  local port="$3"
  # To do backward compatability, we still keep task_multicast_restoreparts function, but let task_restoreparts do the real job.
  task_restoreparts $target_dir "$target_parts" $port
}

check_boot_kernel_arg_cmdline() {
  # The mode assigned when client boots, if this is assigned, 
  # it will overide the mode assigned by server
  if grep -i -q "drbl_ocs=disallow" /proc/cmdline; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
    echo "Clonezilla server assigned drbl_ocs=\"disallow\" for this client, so the clonezilla execution in this machine is aborted!"
    echo -n "$msg_press_enter_to_continue..."
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    read
    exit 0
  fi
}

kill_ps_by_kill_9() {
    # kill drbl-ocs or ocs-sr process
    # This is better than killall -9, more exactly
    local prog="$1"
    local pids
    pids="$(ps -e -o pid,args | grep -E "$prog" | grep -v "grep" | awk -F" " '{print $1}')"
    if [ -n "$pids" ]; then
      for p in $pids; do
        [ "$p" != "$ocs_myself_id" ] && kill -9 $p &> /dev/null
      done
    fi
}   
#
kill_ps_by_killall_9() {
    local prog="$1"
    local pid=""
    [ -z "$prog" ] && echo "prog to be killed is necessary! Abort!" && exit 1
    # kill the prog daemon
    pid=`ps -e | grep -Ew "$prog" | grep -v "grep"`
    [ -n "$pid" ] && killall -9 $prog
}   

#
do_startdisk() {
    # target_image
    if [ -z "$target_image" ]; then
      # ask user to choose the target_image
      case "$task" in
        "save")
           target_image="ask_user"
	   ;;
        "restore")
	   if [ "$select_img_in_client" = "yes" ]; then
             # if user want to select the image name in client
             target_image="ask_user"
           else
             ANS_TMP=`mktemp /tmp/ocs_ans.XXXXXX`
             trap "[ -f "$ANS_TMP" ] && rm -f $ANS_TMP" HUP INT QUIT TERM EXIT
             # get_existing_disk_image will search $imagedir
	     get_existing_disk_image $ANS_TMP
             # the return name will be only one image name.
             target_image="$(cat $ANS_TMP)"
             [ -f "$ANS_TMP" ] && rm -f $ANS_TMP
           fi
           ;;
        "multicast_restore")
	   if [ "$select_img_in_client" = "yes" ]; then
             # if user want to select the image name in client
             echo "You can not choose the image name in the client when using multicast restore! You must choose now."
           fi
           ANS_TMP=`mktemp /tmp/ocs_ans.XXXXXX`
           trap "[ -f "$ANS_TMP" ] && rm -f $ANS_TMP" HUP INT QUIT TERM EXIT
           # get_existing_disk_image will search $imagedir
	   get_existing_disk_image $ANS_TMP
           # the return name will be only one image name.
           target_image="$(cat $ANS_TMP)"
           [ -f "$ANS_TMP" ] && rm -f $ANS_TMP
           ;;
         *)
           echo "Usage: $ocs_file startdisk {save|restore|multicast_restore} target_image target_hd"
           exit 0
           ;;
      esac
    fi

    # target_hd
    if [ -z "$target_hd" ]; then
      # ask user to choose the target_hd
      case "$task" in
        "save")
           target_hd="ask_user"
           ;;
        "restore")
	   if [ "$select_img_in_client" = "yes" ]; then
             # if user want to select the image name in client
             target_hd="ask_user"
           else
             ANS_TMP=`mktemp /tmp/ocs_ans.XXXXXX`
             trap "[ -f "$ANS_TMP" ] && rm -f $ANS_TMP" HUP INT QUIT TERM EXIT
	     get_existing_disks_from_img $ANS_TMP $imagedir/$target_image
             target_hd="$(cat $ANS_TMP | tr -d \")"
             [ -f "$ANS_TMP" ] && rm -f $ANS_TMP
             # target name exists, but file "disk" is empty ?
             check_target_hd $imagedir/$target_image/disk "$target_hd"
           fi
           ;;
        "multicast_restore")
	   if [ "$select_img_in_client" = "yes" ]; then
             # if user want to select the image name in client
             echo "You can not input the target harddrive in the client when using multicast restore! You must choose now."
           fi
           ANS_TMP=`mktemp /tmp/ocs_ans.XXXXXX`
           trap "[ -f "$ANS_TMP" ] && rm -f $ANS_TMP" HUP INT QUIT TERM EXIT
	   get_existing_disks_from_img $ANS_TMP $imagedir/$target_image
           target_hd="$(cat $ANS_TMP | tr -d \")"
           [ -f "$ANS_TMP" ] && rm -f $ANS_TMP
           # target name exists, but file "disk" is empty ?
           check_target_hd $imagedir/$target_image/disk "$target_hd"
           ;;
      esac
    fi

    # start
    if [ -z "$target_image" -o "$target_image" = "$imagedir/" -o -z "$target_hd" ]; then 
      [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
      echo "You didn't specify which file or disk to $task"
      [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
      exit 1 
    fi

    # if it's restore, we have to conver the old format to newer
    case "$task" in
      "restore"|"multicast_restore")
        if [ "$target_image" != "ask_user" ]; then
          convert_ocs_format_from_1.5_to_2.0_or_newer $imagedir/$target_image/
        fi
        ;;
    esac

    get_multicast_restore_mode_if_mcast

    #
    [ -n "$n_clients" ] && mcast_client_no_opt="-n $n_clients"
    echo start_ocs_service $mcast_client_no_opt -t $task"disk" -o "$target_image $target_hd"
    start_ocs_service $mcast_client_no_opt -t $task"disk" -o "$target_image $target_hd" 
} # end of do_startdisk

#
do_startparts () {
    # target_image
    if [ -z "$target_image" ]; then
      # ask user to choose the target_image
      case "$task" in
        "save")
           target_image="ask_user"
	   ;;
        "restore")
	   if [ "$select_img_in_client" = "yes" ]; then
             # if user want to select the image name in client
             target_image="ask_user"
	   else
             ANS_TMP=`mktemp /tmp/ocs_ans.XXXXXX`
             trap "[ -f "$ANS_TMP" ] && rm -f $ANS_TMP" HUP INT QUIT TERM EXIT
	     # get_existing_parts_image will search $imagedir
	     get_existing_parts_image $ANS_TMP
             # the return name will be only one image name.
             target_image="$(cat $ANS_TMP)"
             [ -f "$ANS_TMP" ] && rm -f $ANS_TMP
           fi
           ;;
        "multicast_restore")
	   if [ "$select_img_in_client" = "yes" ]; then
             # if user want to select the image name in client
             echo "You can not choose the image name in the client when using multicast restore! You must choose now."
           fi
           ANS_TMP=`mktemp /tmp/ocs_ans.XXXXXX`
           trap "[ -f "$ANS_TMP" ] && rm -f $ANS_TMP" HUP INT QUIT TERM EXIT
	   # get_existing_parts_image will search $imagedir
	   get_existing_parts_image $ANS_TMP
           # the return name will be only one image name.
           target_image="$(cat $ANS_TMP)"
           [ -f "$ANS_TMP" ] && rm -f $ANS_TMP
           ;;
         *)
           echo "Usage: $ocs_file startparts {save|restore|multicast_restore} target_image target_parts"
           exit 0
           ;;
      esac
    fi

    # target_parts
    if [ -z "$target_parts" ]; then
      # ask user to choose the target_parts
      case "$task" in
        "save")
           target_parts="ask_user"
           ;;
        "restore")
	   if [ "$select_img_in_client" = "yes" ]; then
             # if user want to select the image name in client
             target_parts="ask_user"
           else
             ANS_TMP=`mktemp /tmp/ocs_ans.XXXXXX`
             trap "[ -f "$ANS_TMP" ] && rm -f $ANS_TMP" HUP INT QUIT TERM EXIT
             get_existing_partitions_from_img $ANS_TMP $imagedir/$target_image no
             # we have to remove " (comes with checklist in dialog) 
             # so that for loop will work (Specially for FC3/4...)
             target_parts="$(cat $ANS_TMP | tr -d \")"
             [ -f "$ANS_TMP" ] && rm -f $ANS_TMP
             # target name exists, but file "parts" is empty ?
             check_target_parts $imagedir/$target_image/parts "$target_parts"
           fi
           ;;
        "multicast_restore")
	   if [ "$select_img_in_client" = "yes" ]; then
             # if user want to select the image name in client
             echo "You can not choose the image name in the client when using multicast restore! You must choose now."
           fi
           ANS_TMP=`mktemp /tmp/ocs_ans.XXXXXX`
           trap "[ -f "$ANS_TMP" ] && rm -f $ANS_TMP" HUP INT QUIT TERM EXIT
           get_existing_partitions_from_img $ANS_TMP $imagedir/$target_image no
           # we have to remove " (comes with checklist in dialog) 
           # so that for loop will work (Specially for FC3/4...)
           target_parts="$(cat $ANS_TMP | tr -d \")"
           [ -f "$ANS_TMP" ] && rm -f $ANS_TMP
           # target name exists, but file "parts" is empty ?
           check_target_parts $imagedir/$target_image/parts "$target_parts"
           ;;
      esac
    fi

    #
    if [ -z "$target_image" -o "$target_image" = "$imagedir/" -o -z "$target_parts" ]; then 
      [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
      echo "You didn't specify which file or partitions to $task"
      [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
      exit 1 
    fi

    # if it's restore, we have to conver the old format to newer
    case "$task" in
      "restore"|"multicast_restore")
        if [ "$target_image" != "ask_user" ]; then
          convert_ocs_format_from_1.5_to_2.0_or_newer $imagedir/$target_image/
        fi
        ;;
    esac

    get_multicast_restore_mode_if_mcast

    #
    [ -n "$n_clients" ] && mcast_client_no_opt="-n $n_clients"
    echo start_ocs_service $mcast_client_no_opt -t $task"parts" -o "$target_image $target_parts" 
    start_ocs_service $mcast_client_no_opt -t $task"parts" -o "$target_image $target_parts" 
} # end of do_startparts

#
do_select_in_client() {
  # Note! We can not use "for ihost in $drblroot/*; do" since we have to create every pxelinux config for client. If we use that, it will fail in DRBL SSI and Clonezilla box.
  #for ihost in $drblroot/*; do
  for node_ip in `get-client-ip-list`; do
    # if the LIST_HOST is on, skip those IP not listed in the $IP_LIST
    if [ "$LIST_HOST" = "on" ]; then
      [ -z "$(echo $IP_LIST | grep -Ew "$node_ip")" ] && continue
    fi
    echo "Starting the OCS service for node IP add. = $node_ip"
    # inittab for ocs, we do not want client to reboot.
    prep_client_inittab_rc1_srv $node_ip select_in_client
  done

  # First, set the pxe menu to Clonezilla:..., since maybe it's in drbl mode. If -y0 is set, later we will switch the default menu to local.
  force_pxe_clients_boot_label clonezilla "Clonezilla: choose save or restore later"
  # set runlevel 1 to kernel parameter in pxelinux config
  add_runlevel_1_in_pxelinux_cfg_block clonezilla
  # Set the single user mode password if not setting for client...This will be safer..."
  set_clients_rc1_passwd $IP_LIST

  # If the mode is in always_restore, and PXE default menu is assigned to local (-y0), we will set local boot as default. Since the always_restore mode is only useful with local OS exists. But if in some scenario, such as production clone flow, maybe pxe_menu_default_mode=clone is useful, since a lot of hardisks will be replaced one by one.
  if [ "$always_restore" = "yes" ]; then
   case "$pxe_menu_default_mode" in
     "local")
         force_pxe_clients_boot_label local "$local_os_menu_label"
         ;;
     "drbl")
         force_pxe_clients_boot_label drbl
         ;;
   esac
  fi

  # start the ocsmgrd daemon
  # the $ocs_lock_dir/clonezilla.lock is a tag for ocs is running,
  # after start/stop cloning, we will remove $ocs_lock_dir/clonezilla.lock
  touch $ocs_lock_dir/clonezilla.lock
  # UDPCAST MODIFY
  $DRBL_SCRIPT_PATH/sbin/ocsmgrd &
  ocs_log_file="$(grep "joblogfile =>" /opt/drbl/sbin/ocsmgrd | awk -F" " '{print $3}')"
  echo "$msg_client_job_are_logged_in $ocs_log_file"
} # end of do_select_in_client() {

#
prompt_to_turn_on_client() {
    local type=$1
    case "$type" in
      "save")
         turn_on_client_msg="$msg_turn_on_client_to_make_template"
         ;;
      "restore"|"multicast_restore")
         turn_on_client_msg="$msg_turn_on_client_to_clone"
         ;;
      "select_in_client")
         turn_on_client_msg="$msg_turn_on_client_to_select_clone_type"
         ;;
    esac
    echo $msg_delimiter_star_line
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "$turn_on_client_msg"
    echo "$msg_note! (1) $msg_etherboot_5_4_is_required (2) $msg_win_fail_with_Missing_OS"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
} # end of prompt_to_turn_on_client
#
get_multicast_restore_mode_if_mcast() {
    # n_clients (for multicast only)
    if [ -z "$n_clients" -a "$task" = "multicast_restore" ]; then
      if [ -z "$mcast_wait_time" -a -z "$n_clients" -a -z "$mcast_max_wait_time" ] ; then
       ANS_TMP=`mktemp /tmp/ocs_ans.XXXXXX`
       trap "[ -f "$ANS_TMP" ] && rm -f $ANS_TMP" HUP INT QUIT TERM EXIT
       ask_time_or_clients_to_wait $ANS_TMP
       . $ANS_TMP
       # we will get time_to_wait or clients_to_wait.
       [ -n "$time_to_wait" ] && mcast_wait_time="$time_to_wait"
       [ -n "$clients_to_wait" ] && n_clients="$clients_to_wait"
       [ -n "$max_time_to_wait" ] && mcast_max_wait_time="$max_time_to_wait"
       [ -f "$ANS_TMP" ] && rm -f $ANS_TMP
      fi
    fi
} # end of get_multicast_restore_mode_if_mcast
#
add_runlevel_1_in_pxelinux_cfg_block() {
  local label="$1"
  [ -z "$label" ] && echo "No label in add_runlevel_1_in_pxelinux_cfg_block!" && exit 1
  lines=$(get_pxecfg_image_block $label $PXE_CONF)
  begin_line=$(echo $lines | awk -F" " '{print $1}')
  end_line=$(echo $lines | awk -F" " '{print $2}')
  tag_found="$(head -n $end_line $PXE_CONF | tail -n $(($end_line-$begin_line)) | grep -Ei "^[[:space:]]*append[[:space:]]*.*[[:space:]]+\<1\>([[:space:]]+|$)")"
  if [ -z "$tag_found" ]; then
    sub_menu_label_cmd="if ($begin_line..$end_line) {s/(^[[:space:]]*append[[:space:]]+.*)/\$1 1/i}"
    LC_ALL=C perl -pi -e "$sub_menu_label_cmd" $PXE_CONF
  fi
} # end of add_runlevel_1_in_pxelinux_cfg_block
#
remove_runlevel_1_in_pxelinux_cfg_block() {
  local label="$1"
  [ -z "$label" ] && echo "No label in remove_runlevel_1_in_pxelinux_cfg_block!" && exit 1
  # remove 1 from the append .... like this in pxelinux config:
  # append initrd=initrd-pxe.img ramdisk_size=12288  devfs=nomount drblthincli=off selinux=0 1
  lines=$(get_pxecfg_image_block $label $PXE_CONF)
  begin_line=$(echo $lines | awk -F" " '{print $1}')
  end_line=$(echo $lines | awk -F" " '{print $2}')
  sub_menu_label_cmd="if ($begin_line..$end_line) {s/(^[[:space:]]*append[[:space:]]+.*)[[:space:]]+1([[:space:]]+.*)/\$1\$2/i}"
  LC_ALL=C perl -pi -e "$sub_menu_label_cmd" $PXE_CONF
} # end of remove_runlevel_1_in_pxelinux_cfg_block()
#
add_ocs_opt_etc_in_pxelinux_cfg_block() {
  # add something like: ocs_opt="--language 0  -g auto -p true restoredisk 2disks hda hdb" in pxelinux config file
  local label="$1"
  local ocs_opt="$2"
  [ -z "$label" ] && echo "No label in add_ocs_opt_etc_in_pxelinux_cfg_block!" && exit 1
  [ -z "$ocs_opt" ] && echo "ocs_opt NOT specified in add_ocs_opt_etc_in_pxelinux_cfg_block! Abort!" && exit 1
  lines=$(get_pxecfg_image_block $label $PXE_CONF)
  begin_line=$(echo $lines | awk -F" " '{print $1}')
  end_line=$(echo $lines | awk -F" " '{print $2}')
  tag_found="$(head -n $end_line $PXE_CONF | tail -n $(($end_line-$begin_line)) | grep -Eiw "^[[:space:]]*append[[:space:]]*.*[[:space:]]+ocs_opt=.*([[:space:]]+|$)")"
  if [ -z "$tag_found" ]; then
    # append ocs_opt=... in the end of append kernel parameter.
    sub_menu_label_cmd="if ($begin_line..$end_line) {s/(^[[:space:]]*append[[:space:]]+.*)/\$1 ocs_opt=\"$ocs_opt\"/i}"
    LC_ALL=C perl -pi -e "$sub_menu_label_cmd" $PXE_CONF
  else
    # overwrite existing ocs_opt=...
    # ocs_opt must be the last parameters in default append for pxelinux.
    sub_menu_label_cmd="if ($begin_line..$end_line) {s/(^[[:space:]]*append[[:space:]]*.*)([[:space:]]+ocs_opt=.*)/\$1 ocs_opt=\"$ocs_opt\"/i}"
    LC_ALL=C perl -pi -e "$sub_menu_label_cmd" $PXE_CONF
  fi
} # end of add_ocs_opt_etc_in_pxelinux_cfg_block
#
remove_ocs_opt_etc_in_pxelinux_cfg_block() {
  local label="$1"
  [ -z "$label" ] && echo "No label in remove_ocs_opt_etc_in_pxelinux_cfg_block!" && exit 1
  # remove something like ocs_opt=... from the append .... like this in pxelinux config:
  # append ... ocs_opt="--language 0  -g auto -p true restoredisk 2disks hda hdb"...
  lines=$(get_pxecfg_image_block $label $PXE_CONF)
  begin_line=$(echo $lines | awk -F" " '{print $1}')
  end_line=$(echo $lines | awk -F" " '{print $2}')
  sub_menu_label_cmd="if ($begin_line..$end_line) {s/(^[[:space:]]*append[[:space:]]+.*)[[:space:]]+ocs_opt=\".*\"([[:space:]]+.*)/\$1\$2/i}"
  LC_ALL=C perl -pi -e "$sub_menu_label_cmd" $PXE_CONF
} # end of remove_ocs_opt_etc_in_pxelinux_cfg_block()
#
check_ocs_input_params(){
# This function is used to check drbl-ocs and ocs-sr input parameters
if [ -n "$(echo $VOL_LIMIT | grep -E "[^0-9]" 2>/dev/null)" ]; then
   [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
   echo "Image file size must be digits, not [$VOL_LIMIT]! $msg_program_stop!"
   [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
   exit 1
fi
# convert 05/006... to 5, 6, also make it as integer
VOL_LIMIT="$(echo "scale=0; $VOL_LIMIT / 1" |bc -l)"

if [ "$USE_NTFSCLONE" = "yes" ] && ! type lzop &>/dev/null; then
   [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
   echo "lzop is NOT found in this server, we will use the default compression program (gzip)."
  [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
   IMG_CLONE_CMP="gzip -c $extra_gzip_opt"
fi

if [ -n "$TIME_to_wait" -a -z "`echo "$TIME_to_wait" | grep "[0-9].*"`" ]; then
   [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
   echo "\"$TIME_to_wait\" is NOT a valid time for wait-time! $msg_program_stop!"
   [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
   exit 1
fi

# check grub_partition if we want to install grub
if [ "$install_grub" = "on" ]; then
  if [ -z "`echo $grub_partition | grep "/dev/[sh]d[a-z][1-9]*"`" -a "$grub_partition" != "auto" ]; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
    echo "\"$grub_partition\" is NOT a valid grub root partition! $msg_program_stop!!!"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    exit 1
  fi
fi
} # end of check_ocs_input_params

#
USAGE_common_restore(){
    # Common restore usage help messages for both drbl-ocs and ocs-sr
    echo " -g, --grub-install GRUB_PARTITION     Install grub in hda with root grub directory in GRUB_PARTITION when restoration finishs, GRUB_PARTITION can be one of \"/dev/hda1\", \"/dev/hda2\"... or \"auto\" (\"auto\" will clonezilla detects the grub root partition automatically)"
    echo " -r, --resize-partition   Resize the partition when restoration finishes, this will try to fix the problem when small partition image is restored to larger partition. Now support partition with filesystem reiserfs, fat, ext2 and ext3. Warning!!! Use this carefully... Backup your data first"
    echo " -k, --no-fdisk, --no-create-partition   Do NOT create partition in target harddisk. If this option is set, you must make sure there is an existing partition table in the current restored harddisk. Default is Yes"
    echo " -t, --no-restore-mbr Do NOT restore the MBR (Mater Boot Record) when restoring image. If this option is set, you must make sure there is an existing MBR in the current restored harddisk. Default is Yes"
    echo " -u, --select-img-in-client  Input the image name in clients"
    echo " -e, --load-geometry  Force to use the saved CHS (cylinders, heads, sectors) when using sfdisk"
    echo " -y, -y0, --always-restore, --always-restore-default-local  Let Clonezilla server as restore server, i.e. client will always has restore mode to choose (However default mode in PXE menu is local boot)"
    echo " -y1, --always-restore-default-clone Let Clonezilla server as restore server, i.e. client will always has restore mode to choose (The default mode in PXE menu is clone, so if client boots, it will enter clone always, i.e. clone forever)"
    echo " -j, --create-part-by-sfdisk Use sfdisk to create partition table instead of using dd to dump the partition table from saved image (This is default)"
    echo " -j0, --create-part-by-dd  Use dd to dump the partition table from saved image instead of sfdisk. ///Note/// This does NOT work when logical drives exist."
    echo " -hn0 PREFIX     Change the hostname of M$ Windows based on the combination of hostname prefix and IP address, i.e. PREFIX-IP"
    echo " -hn1 PREFIX     Change the hostname of M$ Windows based on the combination of hostname prefix and NIC MAC address, i.e. PREFIX-MAC"
    # This --max-time-to-wait exists both in drbl-ocs and ocs-sr is because
    # ocs-sr need to know the --max-time-to-wait so that when sleeping
    # between partitions restoring clone won't timeout.
    echo " --max-time-to-wait TIME   When not enough clients have connected (but at least one), start anyways when TIME seconds since first client connection have pased. This option is used with --clients-to-wait"
} # end of USAGE_common_restore
#
USAGE_common_save(){
    # Common save usage help messages for both drbl-ocs and ocs-sr
    echo " -z0, --no-compress       Don't compress when saving: very fast but very big image file (NOT compatible with multicast restoring!!!)"
    echo " -z1, --gzip-compress     Compress using gzip when saving: fast and small image file (default)"
    echo " -z2, --bz2-compress      Compress using bzip2 when saving: slow but smallest image file"
    echo " -z3, --lzo-compress      Compress using lzop when saving: similar to the size by gzip, but faster than gzip."
    echo " -ntfs-ok, --ntfs-ok      Assume the NTFS integrity is OK, do NOT check again (for ntfsclone only)"
    echo " -q, --use-ntfsclone      If the partition to be saved is NTFS, use program ntfsclone instead of partimage"
} # end of USAGE_common_save
#
USAGE_common_general() {
    # Common save and restore usage help messages for both drbl-ocs and ocs-sr
    language_help_prompt_by_idx_no
    echo " -b, --batch              Run $ocs in batch mode, i.e. without any prompt or wait to press enter"
    echo " -c, --confirm            Wait for confirmation before saving or restoring"
    echo " -d, --debug-mode         Enter command mode to debug before saving/restoring"
    echo " --debug=LEVEL            Output the partimage debug log in directory /var/log/ with debug LEVEL (0,1,2... default=0)"
    echo " -m, --module  MODULE     Force to load kernel module MODULE, this is useful when some SCSI device is not detected. NOTE! Use only one module, more than one may cause parsing problem."
    echo " -o0, --run-prerun-dir    Run the script in the direcoty $OCS_POSTRUN_DIR before clone is started. The command will be run before MBR is created or saved."
    echo " -o1, -o, --run-postrun-dir    Run the script in the direcoty $OCS_POSTRUN_DIR when clone is finished. The command will be run before that assigned in -p or --postaction."
    echo " -w, --wait-time TIME     Wait for TIME secs before saving/restoring"
    echo " --nogui                  Do not show GUI of partimage, use text only"
    echo " -a, --no-force-dma-on    Do not force to turn on HD DMA"
    echo " -mp, --mount-point MOUNT_POINT Use NFS to mount MOUNT_POINT as directory ocsroot (ocsroot is assigned in drbl.conf)"
    echo " -or, --ocsroot DIR       Specify DIR (absolute path) as directory ocsroot (i.e. overwrite the ocsroot assigned in drbl.conf)"
    echo " -p, --postaction [choose|poweroff|reboot|command|CMD]     When save/restoration finishs, choose action in the client, poweroff, reboot (default), in command prompt or run CMD"
    echo " -ns, --ntfs-progress-in-image-dir Save the ntfsclone progress tmp file in the image dir so that if cloning is in DRBL client, the progress can be check in the server (Default in to be put in local /tmp/, which is local tmpfs)."
    echo " -v, --verbose            Prints verbose information"
} # end of USAGE_common_general
#
check_if_source_dev_busy_before_saving() {
  local src_dev=$1
  local img_name=$2
  [ -z "$src_dev" ] && return 1
  if [ -n "$(mount | grep -Ew "^$src_dev")" ]; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
    echo "$src_dev is mounted! You have to unmount the $src_dev!!!"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo "Clean the incomplete image $img_name... "
    [ -n "$img_name" -a -d "$img_name" ] && rm -rfv $img_name
    echo "done!"
    echo "$msg_program_stop!"
    exit 1
  fi
} # end of check_if_source_dev_busy_before_saving
#
check_if_target_dev_busy_before_restoring() {
  local tgt_dev=$1
  [ -z "$tgt_dev" ] && return 1
  if [ -n "$(mount | grep -Ew "^$tgt_dev")" ]; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
    echo "$tgt_dev is mounted! You have to unmount the $tgt_dev!!!"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo "$msg_program_stop!"
    exit 1
  fi
} # end of check_if_target_dev_busy_before_restoring
#
check_if_source_dev_busy_before_create_partition() {
  local src_dev=$1
  [ -z "$src_dev" ] && return 1
  mounted_parts="$(mount | grep -Ew "^${src_dev}[0-9]*")"
  if [ -n "$mounted_parts" ]; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
    echo "$src_dev is busy! Some partition is mounted!"
    echo $msg_delimiter_star_line
    echo "$mounted_parts"
    echo $msg_delimiter_star_line
    echo "You have to unmount them first!!! Or you can choose '-k' to skip partition recreation when you start clonezilla!"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo "$msg_program_stop!"
    exit 1
  fi
} # end of check_if_source_dev_busy_before_create_partition
#
is_spawned_by_drbl_ocs() {
  local ppid_="$1" 
  local rc
  if [ -z "$ppid_" ]; then
    echo "No PID is assigned! Unable to decide that!"
    return 1
  fi
  ocs_pproc="$(ps -eo pid,ucmd | grep -Ew "^[[:space:]]*$ppid_" | awk -F" " '{print $2}')"
  rc=1
  if [ -n "$(echo "$ocs_pproc" | grep -Ew "S[0-9][0-9]ocs-run$")" ]; then
    echo "$ocs_file is spawned by $ocs_pproc"
    rc=0
  fi
  return $rc
} # end of is_spawned_by_drbl_ocs
#
convert_ocs_format_from_1.5_to_2.0_or_newer() {
  # img_path is $ocsroot/$target_dir/
  local img_path="$1"
  local hd_dev=""
  local tgt_hd_tmp
  if [ -f "$img_path/mbr" -a -f "$img_path/pt.sf" ]; then
    # Note! for very old clonezilla 1.x, no chs.sf, so we do not check that, but will convert it if it exists
    echo $msg_delimiter_star_line
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "Converting $img_path, which is clonezilla image format 1.5 or older, to newer format..."
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    # check the format, just in case
    # For clonezilla 1.5 or order, only one harddisk are supported, files are:
    # mbr, pt.sf, chs.sf
    # For clonezilla 1.6 or later, multiple harddisks are supported, files are:
    # hda-mbr, hda-pt.sf, hda-chs.sf, sda-mbr, sda-pt.sf, sda-chs.sf...
    #
    if [ -f "$img_path/disk" ]; then
      # This is for image from savedisk
      hd_dev="$(cat $img_path/disk 2>/dev/null)"
      if [ "$(echo $hd_dev | wc -w)" -gt 1 ]; then
        # clonezilla 1.x format, should only for one disk, no multiple disks.
        [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
        echo "Unknown clonezilla image format! The format is in a mess ?"
        [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
        echo "$msg_program_stop!!!"
        exit 1
      fi
    elif [ -f "$img_path/parts" ]; then
      # This is for image from saveparts
      # we need get disk name: hda1 hda2 hda3 -> hda
      # we choose the first one in partitions to convert, since in old format, when image of partitions exists, 
      tgt_hd_tmp="$(cat $img_path/parts | awk -F" " '{print $1}')"
      hd_dev="${tgt_hd_tmp:0:3}"
    fi
    for ifile in mbr pt.sf chs.sf; do
      # chs.sf maybe not exists for very old clonezilla 1.x
      if [ -f "$img_path/$ifile" ]; then
        mv -fv $img_path/$ifile $img_path/${hd_dev}-${ifile}
      fi
    done
    if [ "$batch_mode" != "on" ]; then
      echo -n "$msg_press_enter_to_continue..."
      read
    fi
    echo $msg_delimiter_star_line
  fi
} # end of convert_ocs_format_from_1.5_to_2.0_or_newer
#
create_live_media_opt_drbl_tarball() {
  local workdir=$1
  local exc_list
  # Prepare the directories: $OCS_PRERUN_DIR and $OCS_POSTRUN_DIR, so that user can use that in the future.
  mkdir -p $OCS_PRERUN_DIR $OCS_POSTRUN_DIR
  # To avoid confusion, we exclude drbl-ocs and dcs (it's useless in Live media, only works in drbl/clonezilla server).
  #exc_list="opt/drbl/doc opt/drbl/setup drbl-ocs dcs drbl-client-switch share/locale share/man opt/drbl/etc"
  exc_list="drbl-ocs dcs drbl-client-switch share/locale share/man"
  exc_list_all=""
  for i in $exc_list; do
    exc_list_all="$exc_list_all --exclude $i "
  done
  tar $exc_list_all -czf $workdir/opt_drbl.tgz $DRBL_SCRIPT_PATH/ 2>/dev/null
}
#
set_boot_loader() {
  # output_dev is the partition filename, like /dev/sda1
  local output_dev="$1"
  local fs
  [ -z "$output_dev" ] && echo "output_dev must be assigned in set_boot_loader!" && exit 1
  echo -n "Finding the filesystem in $output_dev... "
  fs="$($DRBL_SCRIPT_PATH/sbin/get_part_info $output_dev filesystem)"
  case "$fs" in
  fat*|vfat*|FAT*|VFAT*)
     echo "FAT, use syslinux as boot loader."
     boot_loader=syslinux
     ;;
  *) 
     echo "Not FAT, us grub as boot loader."
     boot_loader=grub
     ;;
  esac
}
#
ab2dig() {
  local tomatch=$1
  local dits alphabets
  dits=`seq 0 25`
  alphabets=(a b c d e f g h i j k l m n o p q r s t u v w x y z)
  for i in $dits; do
    if [ "${alphabets[$i]}" = "$tomatch" ]; then
      base=$i
      break
    fi
  done
  echo $base
}
#
ask_language_if_supported_with_bterm(){
 # read the setting if exists
 [ -e "/etc/ocs/ocs-live.conf" ] && . /etc/ocs/ocs-live.conf
 if [ -z "$ocs_lang" ]; then
   if [ "$TERM" = "bterm" ] && ([ -e /dev/fb/0 ] || [ -e /dev/fb0 ]); then 
     TMP="$(mktemp /tmp/lang.XXXXXX)"
     /opt/drbl/bin/langchooser $TMP
     ocs_lang="$(cat $TMP)"
     [ -f "$TMP" ] && rm -f $TMP
   fi
 fi
}
#
show-general-ocs-live-prompt() {
  local prog_to_show="$1"
  [ -z "$prog_to_show" ] && prog_to_show="ocs-live"
  echo $msg_delimiter_star_line
  echo "$msg_if_you_want_to_use_ocs_again:"
  [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
  echo "(1) $msg_stay_in_this_console_1"
  echo "(2) $msg_run_cmd_exit"
  [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
  echo $msg_delimiter_star_line
  [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
  echo "$msg_remember_poweroff_reboot_when_ocs_sr_is_done"
  [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
  echo $msg_delimiter_star_line
  # we have to add this wait, otherwise if it's bterm, this help message will just finish quickly and back to command line, nothing left in the screen.
  echo -n "$msg_press_enter_to_continue"
  read
} # end of show-general-ocs-live-prompt
#
is_partimage_support_fs() {
  # function to check if the input filesystem is supported by partimage or not
  # partimage_support_fs is global variable in drbl-ocs.conf
  local input_fs="$1"
  local rc
  # ufs, hfs is beta support in partimage 0.6.5
  # ntfs support is experimental in partimage 0.6.5
  # Use 'grep -Fiw "$input_fs"' instead of 'grep -Eiw "$input_fs"', otherwise
  # echo "hfs" | grep -Eiw "hfs+" will shown. It's not what we want.
  if [ -n "$(echo "$partimage_support_fs" | grep -Fiw "$input_fs")" ]; then
    rc=0
  else
    rc=1
  fi
  return $rc
} # end of is_partimage_support_fs
# get dd save/restore statistics
get_dd_image_info() {
  local report_info=$1
  local start_t=$2
  local end_t=$3
  # time_elapsed, time_elapsed_in_min, space_used and speed are global variables
  [ ! -e "$report_info" -o -z "$start_t" -o -z "$end_t" ] && return 1
  # The report of dd is like:
  # 52+0 records out
  # 54525952 bytes (55 MB) copied, 3.29675 seconds, 16.5 MB/s <-- This only for newer dd
  calculate_elapsed_time $start_t $end_t
  # show it with unit
  space_used="$(tail -n 1 "$report_info" | grep -oE "\(.*\)" | sed -e "s/[()]//g")"
  speed="$(tail -n 1 "$report_info" | awk -F"," '{print $3}')"
  # for old version dd, no speed report. Do not let speed be nothing 
  [ -z "$speed" ] && speed="Not available"
  return 0
}
#
check_if_tty1_and_continue() {
  # This function is run in clonezilla live, only run in tty1
  local TMP ocslivemode
  # CURRENT_TTY is environment variable from S30ocs-live-menu
  if [ "$CURRENT_TTY" != "/dev/tty1" ]; then
    echo "$0 only works in environment variable 'CURRENT_TTY' = '/dev/tty1'"
    exit 3
  fi
  
  TMP=$(mktemp /tmp/ocslivemode.XXXXXX)
  trap "[ -f "$TMP" ] && rm -f $TMP" HUP INT QUIT TERM EXIT
  $DIA --backtitle "$msg_nchc_free_software_labs" --title  \
  "$msg_start_clonezilla" --menu "$msg_start_clonezilla_or_enter_shell\n$msg_choose_mode:" \
  0 0 0 $DIA_ESC \
  "Start_Clonezilla" "$msg_start_clonezilla" \
  "Enter_shell"      "$msg_enter_cml" \
  2> $TMP
  ocslivemode="$(cat $TMP)"
  echo "ocslivemode is $ocslivemode"
  [ -f "$TMP" ] && rm -f $TMP
  
  case "$ocslivemode" in
    "Start_Clonezilla") 
      echo "Start Clonezilla now..." ;;
    "Enter_shell") exit 1 ;;
  esac
} # end of check_if_tty1_and_continue
#
get_harddisk_list(){
    local partition_table disk_list
    # function to get the harddisk list.
    # CDROM won't be listed in /proc/partitions, so we do not have to worry about that when cloning.
    # it's devfs compiled into kernel, we will use the conv_devfspart_to_tradpart to convert the devfs style partitions into traditional partitions
    partition_table="$(mktemp /tmp/parttable.XXXXXX)"
    if grep -Eq '(scsi/host|ide/host)' /proc/partitions 2>/dev/null; then
       # it's devfs compiled into kernel...
       cat /proc/partitions > $partition_table
       conv_devfspart_to_tradpart $partition_table
    else
       # the devfs is not compiled into kernel, good!
       # We' better not use "cp -a /proc/partitions $partition_table", use cat to get the kernel param
       cat /proc/partitions > $partition_table
       cciss_dev_map_if_necessary $partition_table
    fi
    disk_list="$(LC_ALL=C awk '/[hs]d[a-z]($| )/ { print $4; }' $partition_table | sort)"
    [ -f "$partition_table" ] && rm -f $partition_table
    echo "$disk_list"
} # end of get_harddisk_list
#
get_partition_list(){
    local partition_table part_list
    # function to get the harddisk list.
    # CDROM won't be listed in /proc/partitions, so we do not have to worry about that when cloning.
    # it's devfs compiled into kernel, we will use the conv_devfspart_to_tradpart to convert the devfs style partitions into traditional partitions
    partition_table="$(mktemp /tmp/parttable.XXXXXX)"
    if grep -Eq '(scsi/host|ide/host)' /proc/partitions 2>/dev/null; then
       # it's devfs compiled into kernel...
       conv_devfspart_to_tradpart $partition_table
    else
       # the devfs is not compiled into kernel, good!
       # We' better not use "cp -a /proc/partitions $partition_table", use cat to get the kernel param
       cat /proc/partitions > $partition_table
       cciss_dev_map_if_necessary $partition_table
    fi
    part_list="$(LC_ALL=C awk '/[hs]d[a-z][0-9]+($| )/ { print $4; }' $partition_table | sort)"
    [ -f "$partition_table" ] && rm -f $partition_table
    echo "$part_list"
} # end of get_partition_list
#
ocs-live-env-prepare(){
  # set the flag to show color output
  BOOTUP=color
  # some parameters for color output.
  [ -z "$SETCOLOR_SUCCESS" ] && SETCOLOR_SUCCESS="echo -en \\033[1;32m"
  [ -z "$SETCOLOR_FAILURE" ] && SETCOLOR_FAILURE="echo -en \\033[1;31m"
  [ -z "$SETCOLOR_WARNING" ] && SETCOLOR_WARNING="echo -en \\033[1;33m"
  [ -z "$SETCOLOR_NORMAL"  ] && SETCOLOR_NORMAL="echo -en \\033[0;39m"
  
  # Append the PATH in system.
  echo "export PATH=/opt/drbl/sbin:/opt/drbl/bin:\$PATH" >> /etc/profile
  echo "export PATH=/opt/drbl/sbin:/opt/drbl/bin:\$PATH" >> /etc/bash.bashrc
  # Clean /etc/motd to avoid confusion.
  echo -n "" > /etc/motd
  # Try to force to remount /live_media as rw, since it's rw device, like USB disk, we can try to save clonezilla image.
  mount -o remount,rw /live_media
  
  # Prepare default ocsroot.
  if mkdir -p /live_media/$ocsroot 2>/dev/null; then
    # if /live_media is writable, default to make $ocsroot link to /live_media/$ocsroot.
    mkdir -p `dirname $ocsroot`
    ln -fs /live_media/$ocsroot $ocsroot
  else
    # mkdir a mount point to be used later.
    mkdir -p $ocsroot
  fi
  
  ask_language_if_supported_with_bterm
  [ -z "$ocs_lang" ] && ocs_lang=en
  ask_and_load_lang_set $ocs_lang
  
  # run the main program
  [ "$ocs_live_batch" = "no" ] && check_if_tty1_and_continue
} # end of ocs-live-env-prepare
#
network_config_if_necessary() {
  local configured_ip run_net_cfg run_again_ans
  configured_ip="$(get-all-nic-ip --all-ip-address)"
  if [ -z "$configured_ip" ]; then
    echo "$msg_network_is_not_configured: ocs-live-netcfg"
    echo -n "$msg_press_enter_to_continue..."
    read
    run_net_cfg="yes"
    while [ "$run_net_cfg" = "yes" ]; do
      ocs-live-netcfg
      configured_ip="$(get-all-nic-ip --all-ip-address)"
      if [ -z "$configured_ip" ]; then
        echo $msg_delimiter_star_line
        [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
        echo "$msg_no_network_card_is_configured_do_it_again ?"
        [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
	echo -n "[Y/n] "
	read run_again_ans
	case "$run_again_ans" in
          n|N|[nN][oO])
	     run_net_cfg="no"
	     ;;
	  *)
	     run_net_cfg="yes"
	     ;;
	esac
      else
	run_net_cfg="no"
      fi
    done
  else
    echo "$msg_network_is_already_configured: $configured_ip"
  fi
} # end of network_config_if_necessary
#
run_post_cmd_when_clonezilla_live_end() {
  local tty1_bash_id
  echo "$msg_clone_finished_choose_to:"
  echo "(0) $msg_poweroff"
  echo "(1) $msg_reboot"
  echo "(2) $msg_enter_cml"
  echo "(3) $msg_run_clonezilla_live_again"
  echo -n "[2] "
  read final_action
  case "$final_action" in
    0) echo -n 'Will poweroff... '; countdown 5; poweroff $HALT_REBOOT_OPT ;;
    1) echo -n 'Will reboot... '; countdown 5; reboot $HALT_REBOOT_OPT ;;
    3) echo -n 'Run Clonezilla live again... '
       # umount the clonezilla home dir
       umount $ocsroot
       # since we are not in login shell, it's useless to use "exit" or "logout" to force logout bash. Use kill to terminate the login shell in tty1. The clonezilla live main menu will be run again.
       tty1_bash_id="$(ps -t tty1 |grep bash | awk -F" " '{print $1}')"
       kill -9 $tty1_bash_id
       ;;
    *) echo ;;
  esac
} # end of run_post_cmd_when_clonezilla_live_end
