# 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
}
#
confirm_continue_or_default_quit() {
  local ans_continue
  echo "$msg_are_u_sure_u_want_to_continue ?"
  echo -n "[y/N] "
  read ans_continue
  case "$ans_continue" in
       y|Y|[yY][eE][sS])
          echo "$msg_ok_let_do_it!"
          ;;
       *)
          echo "$msg_program_stop!"
          exit 1
  esac
} # end of confirm_continue_or_default_quit
#
unmount_wait_and_try() {
  local mnt_dev_pnt="$1"
  # We have to wait for umount successfully. Since for NTFS, looks like the mount will be delayed on some system. Time limit set to be 5 secs
  for i in `seq 1 25`; do
    sleep 0.2
    if umount $mnt_dev_pnt 2>/dev/null; then
      break
    fi
  done
} # end of unmount_wait_and_try
#
get_ntfsclone_progress_opt() {
  if type ntfsclone &>/dev/null; then
    # Decide ntfsclone supports -V or not. Thomas has added an option "-V" for ntfsclone to show more info when cloning. However, the patch we sent to ntfsprogs project did not get any response... we have to do this here. In the future, we will switch to use partclone as default.
    ntfsclone_progress_option=""
    if [ -n "$(ntfsclone -h 2>&1 | grep -Ewo -- "-V")" ]; then
      ntfsclone_progress_option="-V"
    fi
  fi
} # end of get_ntfsclone_progress_opt
#
gen_md5_sha1_sums() {
  local img_dir="$1"  # absolute path
  # Generate MD5SUMS, SHA1SUMS
  if [ "$gen_md5sum" = "yes" ]; then
    echo "Generating MD5SUMS for image $target_dir. This might take a while..."
    (cd $img_dir; time md5sum * > MD5SUMS)
  fi
  if [ "$gen_sha1sum" = "yes" ]; then
    echo "Generating SHA1SUMS for image $target_dir. This might take a while..."
    (cd $img_dir; time sha1sum * > SHA1SUMS)
  fi
} # end of gen_md5_sha1_sums
#
check_md5_sha1_sums() {
  local img_dir="$1"  # absolute path
  local rc ans_continue
  # Check MD5SUMS, SHA1SUMS
  if [ "$check_md5sum" = "yes" ]; then
    if [ -e "$img_dir/MD5SUMS" ]; then
      echo $msg_delimiter_star_line
      echo "Checking MD5SUMS for image $img_dir. This might take a while..."
      (cd $img_dir; LC_ALL=C md5sum -c MD5SUMS)
      rc=$?
      if [ "$rc" -gt 0 ]; then
        [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
        echo "$msg_MD5SUMS_check_failed"
        [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
	confirm_continue_or_default_quit
      fi
      echo $msg_delimiter_star_line
    else
      [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
      echo "MD5SUMS was not found in image $img_dir. Skip checking MD5 checksums."
      [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    fi
  fi
  if [ "$check_sha1sum" = "yes" ]; then
    if [ -e "$img_dir/SHA1SUMS" ]; then
      echo $msg_delimiter_star_line
      echo "Checking SHA1SUMS for image $img_dir. This might take a while..."
      (cd $img_dir; LC_ALL=C sha1sum -c SHA1SUMS)
      rc=$?
      if [ "$rc" -gt 0 ]; then
        [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
        echo "$msg_SHA1SUMS_check_failed"
	confirm_continue_or_default_quit
        [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
      fi
      echo $msg_delimiter_star_line
    else
      [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
      echo "SHA1SUMS was not found in image $img_dir. Skip checking SHA1 checksums."
      [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    fi
  fi
} # end of check_md5_sha1_sums
#
get_disk_from_part() {
  # Function to get disk name from partition name, e.g. /dev/sda1 -> /dev/sda
  local ipart=$1  # ipart is like /dev/sda1
  local parttmp hdtmp
  parttmp="$(basename $ipart)"
  hdtmp="${parttmp:0:3}"
  hdtmp=/dev/$hdtmp
  echo "$hdtmp"
} # end of get_disk_from_part
#
get_partition_table_type_from_disk() {
  local tdisk=$1  # e.g. /dev/sda
  local ptype
  if [ -n "$(LC_ALL=C parted -s $tdisk print 2>/dev/null | grep -iE "^Partition Table:" | grep -iE "gpt")" ]; then
    ptype="gpt"
  else
    ptype="mbr"
  fi
  # If no ptype was found, force to be mbr.
  [ -z "$ptype" ] && ptype="mbr"
  echo "$ptype"
} # get_partition_table_type_from_disk
#
inform_kernel_partition_table_changed() {
  # Function to inform kernel the partition table has changed
  local ptype=$1  # e.g. mbr, gpt
  local tdisk=$2  # e.g. /dev/sda
  if [ -z "$ptype" -o -z "$tdisk" ]; then
    echo "No ptype or tdisk parameter was assigned in function inform_kernel_partition_table_changed! Program terminated!"
    exit 1
  fi
  # if tdisk is not the form /dev/[hsv]d[a-z] (e.g. LVM), skip.
  [ -z "$(echo $tdisk | grep -E -- "/dev/[hsv]d[a-z]")" ] && return 3
  echo -n "Informing the kernel the file system has been changed..."
  case "$ptype" in
  mbr)
    # //NOTE// Put "sfdisk -R $tdisk" as the last action, since for BSD slices, partprobe does not know, and if it is the last one, kernel info (/proc/partitions) won't show any slices. "sleep 1" is also required, otherwise we might see the error message: "BLKRRPART: Device or resource busy"
    LC_ALL=C partprobe $tdisk
    echo -n "."
    sleep 1
    LC_ALL=C kpartx $tdisk &>/dev/null
    echo -n "."
    sleep 1
    LC_ALL=C sfdisk -R $tdisk
    echo -n "."
    sleep 1
  ;;
  gpt)
    # Since sfdisk does not work for gpt, skip it.
    LC_ALL=C partprobe $tdisk
    echo -n "."
    sleep 1
    LC_ALL=C kpartx $tdisk &>/dev/null
    echo -n "."
    sleep 1
  ;;
  esac
  echo " done!"
} # end of inform_kernel_partition_table_changed
sync_and_active_exec_files() {
  # This is a workaround to avoid "sed: command not found" error for DRBL live
  # Maybe it's because aufs or some unknown issues...
  # Ref: https://sourceforge.net/projects/drbl/forums/forum/675794/topic/3707138
  # //NOTE// This is for DRBL client machine to run, not on server, therefore we can not grep "live" or "config"
  [ -z "$(LC_ALL=C grep -Ewo "clientdir=node_root" /proc/cmdline)" ] && return 0
  sync; sync; sync
  find /bin /sbin -print > /dev/null
} # end of sync_and_active_exec_files
#
prepare_lshw_disk_info_for_sdx() {
  # Prepare lshw disk info, this is specially for sdx disk, since the serial number we need can only be found by lshw, and lshw scan will take a while, we run it once and keep the output in a temp file.
  gen_proc_partitions_map_file
  if [ -n "$(grep -E "sd[a-z]" $partition_table)" ]; then
    lshw_disk_info="$(mktemp /tmp/lshw-disk-info.XXXXXX)"
    trap "[ -f "$lshw_disk_info" ] && rm -f $lshw_disk_info" HUP INT QUIT TERM EXIT
    echo "Collecting the disk info in this machine..."
    LC_ALL=C lshw -class disk > $lshw_disk_info 2>/dev/null
  fi
} # end of prepare_lshw_disk_info_for_sdx
#
output_ocs_related_pkgs() {
  local img_d="$1"
  local output_f="$2"
  local pkg_to_query installed_pkgs
  # PKG_FROM_DRBL is loaded from drbl.conf, we append "drbl"
  pkg_to_query="drbl $PKG_FROM_DRBL"
  #
  installed_pkgs=""
  # //Note// It's possible on Debian system, rpm package is installed, maybe due to alien or something similar. Therefore we have to check dpkg first.
  if type dpkg &>/dev/null; then
    # Debian-based
    for ipkg in $pkg_to_query; do
      ipkg_ver=""
      ipkg_ver="$(LC_ALL=C dpkg -l $ipkg 2>/dev/null | tail -n 1 | awk -F" " '{print $2"-"$3}')"
      [ -n "$ipkg_ver" ] && installed_pkgs="$installed_pkgs $ipkg_ver"
    done
  else
    # Redhat/RPM based
    for ipkg in $pkg_to_query; do
      ipkg_ver=""
      ipkg_ver="$(LC_ALL=C rpm -q $ipkg 2>/dev/null)"
      [ -n "$ipkg_ver" ] && installed_pkgs="$installed_pkgs $ipkg_ver"
    done
  fi
  echo "$installed_pkgs" >> $img_d/$output_f
} # end of output_ocs_related_pkgs
#
dump_hardware_software_info() {
  local img_dir="$1" 
  local save_time pkg_to_query installed_pkgs
  [ -z "$img_dir" ] && return 1
  save_time="$(LC_ALL=C date "+%r, %Y/%b/%d")"
  # lshw does not exist in SuSE, but hwinfo does.
  if type lshw &>/dev/null; then
    echo "Saving hardware info by lshw..."
    echo "This image was saved from this machine with hardware info at $save_time:" > $img_dir/Info-lshw.txt
    LC_ALL=C lshw >> $img_dir/Info-lshw.txt
  elif type hwinfo &>/dev/null; then
    echo "Saving hardware info by hwinfo..."
    echo "This image was saved from this machine with hardware info at $save_time:" > $img_dir/Info-hwinfo.txt
    LC_ALL=C hwinfo >> $img_dir/Info-hwinfo.txt
  fi
  if type dmidecode &>/dev/null; then
    echo "Saving DMI info..."
    echo "This image was saved from this machine with DMI info at $save_time:" > $img_dir/Info-dmi.txt
    LC_ALL=C dmidecode >> $img_dir/Info-dmi.txt
  fi
  if type lspci &>/dev/null; then
    echo "Saving PCI info..."
    echo "This image was saved from this machine with PCI info at $save_time:" > $img_dir/Info-lspci.txt
    echo "'lspci' results:" >> $img_dir/Info-lspci.txt
    LC_ALL=C lspci >> $img_dir/Info-lspci.txt
    echo $msg_delimiter_star_line >> $img_dir/Info-lspci.txt
    echo "'lspci -n' results:" >> $img_dir/Info-lspci.txt
    LC_ALL=C lspci -n >> $img_dir/Info-lspci.txt
  fi
  #
  echo "Saving package info..."
  if grep -qE "clientdir=" /proc/cmdline; then
    # DRBL SSI or Clonezilla box mode. In this mode, since the /var/lib/rpm is not the original one. If we use "rpm -q drbl", the return value is nothing even if drbl is installed in server. Therefore we have to use the prepared info.
    echo "Image was saved by these Clonezilla-related packages:" > $img_dir/Info-packages.txt
    cat /drbl_ssi/DRBL-Clonezilla-related-pkgs.txt >> $img_dir/Info-packages.txt
  else
    # Full DRBL or Clonezilla box mode or in Clonezilla live. We can query it by "rpm -q" or "dpkg -l" since this is more accurate.
    echo "Image was saved by these Clonezilla-related packages:" > $img_dir/Info-packages.txt
    output_ocs_related_pkgs $img_dir Info-packages.txt
  fi
} # end of dump_hardware_software_info
#
save_hidden_data_after_MBR() {
# This function is used to get the data after the 1st sector, before the first partition. 
  # Ref: 
  # (1) https://sourceforge.net/forum/message.php?msg_id=5538109
  # (2) http://www-307.ibm.com/pc/support/site.wss/document.do?sitestyle=lenovo&lndocid=MIGR-57590

  # A normal partition table output by :parted -s /dev/sda unit s print":
  # Number  Start       End         Size        Type     File system  Flags
  # 1      63s         117194174s  117194112s  primary  reiserfs     boot
  # 2      117194175s  125001764s  7807590s    primary  linux-swap
  # 3      125001765s  312576704s  187574940s  primary  reiserfs

  # While for some model of machines, e.g. 
  # (1) IBM Thinkpad X61 with backup/restory utility from IBM called Rescue & Recovery installed
  # (2) and or Toshiba
  # Number  Start      End         Size        Type     File system  Flags
  # 1      2048s      12095487s   12093440s   primary  ntfs
  # 2      12095488s  312581719s  300486232s  primary  ntfs         boot
  # 
  local src_dsk="$1"  # e.g. sda
  local img_s="$2"    # img_s in the image name with absolute path
  local real_i start_s hidden_data_size
  for i in src_dsk img_s; do
    eval real_i=\$$i
    [ -z "$real_i" ] && echo "You have to assign $i in function save_hidden_data_after_MBR!" && exit 1
  done

  # WRONG! WRONG! Do not do this!
  #first_p="/dev/${src_dsk}1"   # /dev/sda -> /dev/sda1
  #start_s="$(ocs-get-part-info -u s $first_p start)"
  #start_s="$(echo $start_s | sed -e "s/s$//g")"  # Strip the last "s"
  # The above is WRONG!
  # The partition may not be in order, e.g.
  # -------------------------------------------
  # Disk /dev/hda: 16777215s
  # Sector size (logical/physical): 512B/512B
  # Partition Table: msdos
  # 
  # Number  Start    End       Size     Type     File system  Flags
  #  2      63s      586844s   586782s  primary  ntfs              
  #  1      944055s  1530899s  586845s  primary                    
  # -------------------------------------------
  # Therefore we can not just use sda1 or hda1 to find the start sector.
  start_s="$(LC_ALL=C parted -s /dev/${src_dsk} unit s print | grep -iE -A 100000 "[[:space:]]*Start[[:space:]]*End[[:space:]]*" | sed -e "s/^.*[[:space:]]*Start[[:space:]]*End[[:space:]]*.*$//g" | sed "/^$/d" | awk -F" " '{print $2}' | sed -e "s/s$//g" | sort -n | head -n 1)"
  echo "The first partition of disk /dev/${src_dsk} starts at $start_s."
  hidden_data_size="$(($start_s -1))"
  if [ -z "$hidden_data_size" ]; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
    echo "Something went wrong! The hidden data size can NOT be found!"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo "$msg_program_stop!"
    exit 1
  fi
  if [ "$hidden_data_size" -le 0 ]; then
    echo "Skip saving hidden data between MBR and 1st partition since it's not found."
    return 3
  fi
  echo "Saving the hidden data between MBR (1st sector, i.e. 512 bytes) and 1st partition, which might be useful for some recovery tool, by:"
  echo dd if=/dev/${src_dsk} of=${img_s} skip=1 bs=512 count=$hidden_data_size
  dd if=/dev/${src_dsk} of=${img_s} skip=1 bs=512 count=$hidden_data_size

} # end of save_hidden_data_after_MBR
#
restore_hidden_data_after_MBR() {
  local tgt_dsk="$1"  # e.g. sda
  local img_r="$2"    # img_r in the image name with absolute path
  local real_i data_size start_s hidden_space_size
  for i in tgt_dsk img_r; do
    eval real_i=\$$i
    [ -z "$real_i" ] && echo "You have to assign $i in function restore_hidden_data_after_MBR!" && exit 1
  done
  if [ ! -e "$img_r" ]; then
    echo "No hidden data (between MBR and 1st partition) image was found. Skip restoring it."
    return 3
  fi

  # WRONG! WRONG! Do not do this!
  #first_p="/dev/${tgt_dsk}1"   # /dev/sda -> /dev/sda1
  ## The result is like: 63s, 2048s
  #start_s="$(ocs-get-part-info -u s $first_p start)"
  #start_s="$(echo $start_s | sed -e "s/s$//g")"  # Strip the last "s"
  # The above is WRONG!
  # The partition may not be in order, e.g.
  # -------------------------------------------
  # Disk /dev/hda: 16777215s
  # Sector size (logical/physical): 512B/512B
  # Partition Table: msdos
  # 
  # Number  Start    End       Size     Type     File system  Flags
  #  2      63s      586844s   586782s  primary  ntfs              
  #  1      944055s  1530899s  586845s  primary                    
  # -------------------------------------------
  # Therefore we can not just use sda1 or hda1 to find the start sector.
  start_s="$(LC_ALL=C parted -s /dev/${tgt_dsk} unit s print | grep -iE -A 100000 "[[:space:]]*Start[[:space:]]*End[[:space:]]*" | sed -e "s/^.*[[:space:]]*Start[[:space:]]*End[[:space:]]*.*$//g" | sed "/^$/d" | awk -F" " '{print $2}' | sed -e "s/s$//g" | sort -n | head -n 1)"
  echo "The first partition of disk /dev/${tgt_dsk} starts at $start_s."
  hidden_space_size="$(($start_s -1))"

  # Check the size. We have to make sure there is enough space to restore.
  data_size="$(LC_ALL=C stat -c "%s" $img_r)"
  data_size="$(LC_ALL=C echo "scale=0; $data_size /512 / 1" | bc -l)" # covert to sectors

  if [ "$data_size" -gt "$hidden_space_size" ]; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "The hidden space between MBR and 1st partition is \"$hidden_space_size\" sectors, which is smaller than the hidden data $img_r) size \"$data_size\" sectors."
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo "Skip restoring the hidden data between MBR and 1st partition."
    return 1
  fi

  echo "Restoring the hidden data between MBR (1st sector, i.e. 512 bytes) and 1st partition, which might be useful for some recovery tool, by:"
  echo dd if=${img_r} of=/dev/${tgt_dsk} seek=1 bs=512 count=$data_size
  dd if=${img_r} of=/dev/${tgt_dsk} seek=1 bs=512 count=$data_size
} # end of restore_hidden_data_after_MBR
#
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 ] || \
     [ -d /proc/driver/cpqarray -a -d /dev/ida ] || \
     [ -d /proc/driver/rd -a -d /dev/ra ]; then 
    echo "Mapping the device list for cciss dev..."
  else
    [ "$verbose" = "on" ] && echo "No cciss related disk was detected. 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
#
gen_proc_partitions_map_file() {
  # partition_table is a global variable
  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, we will use the conv_devfspart_to_tradpart to convert the devfs style partitions into traditional partitions
     conv_devfspart_to_tradpart $partition_table
  else
     # The devfs is not compiled into kernel, good!
     # We'd 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
} # end of gen_proc_partitions_map_file
#
get_dev_model_shown() {
  # dev_model_shown is a global variable
  local dev_list_="$1"
  local DEV_MODEL i machine_name
  gen_proc_partitions_map_file
  dev_model_shown=""

  # Shown the machine product name
  machine_name="$(dmidecode -s system-product-name 2>/dev/null | head -n 1)"
  if [ -z "$machine_name" -o "$machine_name" = "To Be Filled By O.E.M." ]; then
    dev_model_shown="Machine: Unknown product name\n"
  else
    dev_model_shown="Machine: $machine_name\n"
  fi
  for i in $dev_list_; do
    DEV_MODEL=""
    # If only the disk or partition exists, we show the warning about the disk/partition will be overwritten.
    if [ -n "$(grep -Ew "$i" $partition_table)" ]; then
      get_disk_or_part_hardware_info "$i"
      dev_model_shown="$dev_model_shown$i ($DEV_MODEL)\n"
    fi
  done
  [ -f "$partition_table" ] && rm -f $partition_table
  # Remove the \n in the end
  dev_model_shown="$(echo $dev_model_shown | sed -e "s|\\\n$||g")"
} # end of get_dev_model_shown
#
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"
  local comp_prog_ref comp_prog cpu_no
  # There are 2 ways to get the compression info
  # (1) By file name. The format is like ${tgt_file}.${fs_pre}-img.${comp_suf} (new format for partclone), or user might choose partimage + lzma (TODO)
  # (2) By file magic, i.e. use command "file"
  
  # (1)
  comp_prog_ref="$(echo "$imgf" | grep -Eo -- ".ptcl-img.(gz|bz2|lzo|lzma|xz|lzip|uncomp)?")"
  cpu_no="$(LC_ALL=C grep -iE "^processor" /proc/cpuinfo | wc -l)" 
  if [ -n "$comp_prog_ref" ]; then
    comp_prog="$(echo "$comp_prog_ref" | sed -e "s/.ptcl-img.//g")" 
    case "$comp_prog" in
      uncomp) cat_prog="cat"
              zip_stdin_cmd="cat"
              unzip_stdin_cmd="cat"
              ;;
      gz)     
              if [ "$cpu_no" -gt 1 ] && \
                 [ "$use_parallel_decompression" = "yes" ] && \
                 type pigz &>/dev/null; then
                cat_prog="pigz -d -c"
                zip_stdin_cmd="pigz -c $extra_gzip_opt"
                unzip_stdin_cmd="pigz -d -c"
              else
                cat_prog="zcat"
                zip_stdin_cmd="gzip -c $extra_gzip_opt"
                unzip_stdin_cmd="gunzip -c"
              fi
              ;;
      bz2)    
              if [ "$cpu_no" -gt 1 -a "$use_parallel_decompression" = "yes" ]; then
                if [ "$parallel_bzip2_prog" = "pbzip2" ] && type pbzip2 &>/dev/null; then
                  cat_prog="pbzip2 -d -c"
                  zip_stdin_cmd="pbzip2 -c"
                  unzip_stdin_cmd="pbzip2 -d -c"
                elif [ "$parallel_bzip2_prog" = "lbzip2" ] && type lbzip2 &>/dev/null; then
                  cat_prog="lbzip2 -d -c"
                  zip_stdin_cmd="lbzip2 -c"
                  unzip_stdin_cmd="lbzip2 -d -c"
                else
                  cat_prog="bzcat"
                  zip_stdin_cmd="bzip2 -c"
                  unzip_stdin_cmd="bunzip2 -c"
                fi
              else
                cat_prog="bzcat"
                zip_stdin_cmd="bzip2 -c"
                unzip_stdin_cmd="bunzip2 -c"
              fi
              ;;
      lzo)    cat_prog="lzop -dc"
              zip_stdin_cmd="lzop -c"
              unzip_stdin_cmd="lzop -dc"
              ;;
      lzma)   cat_prog="lzcat"
              zip_stdin_cmd="lzma -c $extra_lzma_opt"
              unzip_stdin_cmd="unlzma -c"
              ;;
      xz)     
              if [ "$cpu_no" -gt 1 ] && \
                 [ "$use_parallel_decompression" = "yes" ] && \
                 type pxz &>/dev/null; then
                cat_prog="pxz -d -c"
                zip_stdin_cmd="pxz -c $extra_pxz_opt"
                unzip_stdin_cmd="pxz -d -c"
              else
                cat_prog="xzcat"
                zip_stdin_cmd="xz -c $extra_xz_opt"
                unzip_stdin_cmd="unxz -c"
              fi
              ;;
      lzip)     
              if [ "$cpu_no" -gt 1 ] && \
                 [ "$use_parallel_decompression" = "yes" ] && \
                 type plzip &>/dev/null; then
                cat_prog="plzip -d -c"
                zip_stdin_cmd="plzip -c $extra_plzip_opt"
                unzip_stdin_cmd="plzip -d -c"
              else
                cat_prog="lzip -d -c"
                zip_stdin_cmd="lzip -c $extra_lzip_opt"
                unzip_stdin_cmd="lzip -d -c"
              fi
              ;;
    esac
  fi
  if [ -z "$comp_prog" ]; then
    # (2) Old format. Get compression info by file magic, i.e. use command "file"
    if [ ! -e "$imgf" ]; then
      [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
      echo "$imgf not found!!! $msg_program_stop!"
      [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
      exit 1
    fi
    if [ -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
      if [ "$cpu_no" -gt 1 ] && \
         [ "$use_parallel_decompression" = "yes" ] && \
         type pigz &>/dev/null; then
        cat_prog="pigz -d -c"
        zip_stdin_cmd="pigz -c $extra_gzip_opt"
        unzip_stdin_cmd="pigz -d -c"
      else
        cat_prog="zcat"
        zip_stdin_cmd="gzip -c $extra_gzip_opt"
        unzip_stdin_cmd="gunzip -c"
      fi
    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
      if [ "$cpu_no" -gt 1 -a "$use_parallel_decompression" = "yes" ]; then
        if [ "$parallel_bzip2_prog" = "pbzip2" ] && type pbzip2 &>/dev/null; then
          cat_prog="pbzip2 -d -c"
          zip_stdin_cmd="pbzip2 -c"
          unzip_stdin_cmd="pbzip2 -d -c"
        elif [ "$parallel_bzip2_prog" = "lbzip2" ] && type lbzip2 &>/dev/null; then
          cat_prog="lbzip2 -d -c"
          zip_stdin_cmd="lbzip2 -c"
          unzip_stdin_cmd="lbzip2 -d -c"
        else
          cat_prog="bzcat"
          zip_stdin_cmd="bzip2 -c"
          unzip_stdin_cmd="bunzip2 -c"
        fi
      else
        cat_prog="bzcat"
        zip_stdin_cmd="bzip2 -c"
        unzip_stdin_cmd="bunzip2 -c"
      fi
    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 "lzip compressed data")" ]; then
      # Ex: sda1.ntfs-img.aa: lzip compressed data - version 1, os: Unix
      if [ "$cpu_no" -gt 1 ] && \
         [ "$use_parallel_decompression" = "yes" ] && \
         type plzip &>/dev/null; then
        cat_prog="plzip -d -c"
        zip_stdin_cmd="plzip -c $extra_plzip_opt"
        unzip_stdin_cmd="plzip -d -c"
      else
        cat_prog="lzip -d -c"
        zip_stdin_cmd="lzip -c $extra_lzip_opt"
        unzip_stdin_cmd="lzip -d -c"
      fi
    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"
    elif [ -n "$(echo $imgf | grep -iE "\..*-img")" -a -n "$(file -Ls $imgf | grep -i ":.* data$")" ]; then
      # Ex: "sda1.fat16-img: data"
      # This is partclone image. Since this function is called after function is_partclone_image, we do not have to parse the image name again.
      # Todo: Submit the format of partclone image so that in the future, command "file -Ls" can identify the image format 
      cat_prog="cat"
      zip_stdin_cmd="cat"
      unzip_stdin_cmd="cat"
    fi
  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
#
ocs-get-comp-suffix() {
  local comp_cmd="$1"
  local suffix
  [ -z "$comp_cmd" ] && exit 1
  case $comp_cmd in
    cat*)                   suffix="uncomp";;
    gzip*|pigz*)            suffix="gz";;
    bzip2*|pbzip2*|lbzip2*) suffix="bz2";;
    lzop*)                  suffix="lzo";;
    lzma*)                  suffix="lzma";;
    xz*|pxz*)               suffix="xz";;
    lzip*|plzip*)           suffix="lzip";;
  esac
  if [ -n "$suffix" ]; then
    echo "$suffix"
  else
    exit 1
  fi
} # end of ocs-get-comp-suffix
#
conv_return_code_to_human_read() {
  local rcd="$1"
  # clone_status is a 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 "$msg_press_enter_to_continue..."
    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 "Disk/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"
  if type blkid &>/dev/null; then
    echo "Saving swap $swap_dev info in $output_swap_info..."
    blkinfo="$(mktemp /tmp/blkinfo.XXXXXX)"
    LC_ALL=C blkid -c /dev/null $swap_dev | grep -o -E '\<UUID="[^[:space:]]*"($|[[:space:]]+)' > $blkinfo
    LC_ALL=C blkid -c /dev/null $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
#
save_part_by_ntfsclone() {
  # 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!"
    ntfsclone_save_extra_opt="--force --rescue"
  fi
  echo "Checking the disk space... " && disk_full_test $image_name_
  # assign ntfsclone tmp file.
  case "$ntfsclone_progress" in
  "image_dir")
     IP="$(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."
  case "$VOL_LIMIT" in
    [1-9]*)
       echo "Image file will be split with size limit $VOL_LIMIT MB." ;;
    *)
       echo "Image file will not be split." ;;
  esac
  echo $msg_delimiter_star_line
  [ "$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_save_extra_opt is a global variable from check_ntfs_partition_integrity
  if [ -n "$ntfsclone_save_extra_opt" ]; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "WARNING!!! ntfsclone is run with extra option(s): $ntfsclone_save_extra_opt"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
  fi
  ( ntfsclone $ntfsclone_progress_option $ntfsclone_save_extra_opt_def $ntfsclone_save_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 weak. 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"
} #  end of save_part_by_ntfsclone
#
is_partclone_image() {
  local i j
  # The basic idea to to search the image with something like this:
  # if [ -n "$(unalias ls &>/dev/null; ls $target_d/$img_file.{ext2,ext3,hfs+,reiser4,reiserfs}-img* 2>/dev/null)" ]; then
  # since we will save partclone image in clonezilla as name like:
  # (1) Old format (before -q2 is default option)
  #   sda2.hfs+-img
  #   sda2.reiser4-img
  #   sda2.xfs-img
  #   sda2.reiserfs-img
  #   sda2.ext2-img
  #   sda2.ext3-img
  #   sda2.fat12-img
  #   sda2.fat16-img
  #   sda2.fat32-img
  #   sda2.vfat-img
  #   Or with .aa, .ab.. in the end like /home/partimag/pt2/sda2.vfat-img.aa...
  # (2) New format (after -q2 is the default option)
  #   ${tgt_file}.${fs_pre}-img.${comp_suf}
  #   e.g. sda1.ext4-ptcl-img.gz
  local tgt_d=$1 img_f=$2 rc=1
  if [ -z "$tgt_d" -o -z "$img_f" ]; then
    echo "You must assign tgt_d or img_f in function is_partclone_image! Program terminated!"
    exit 1
  fi
  # Check if new format first
  for i in $partclone_support_fs; do
    if [ -n "$(unalias ls &>/dev/null; ls ${tgt_d}/${img_f}.${i}-ptcl-img* 2>/dev/null)" ]; then
      rc=0
      break
    fi
  done
  # if rc=0, return now
  [ "$rc" -eq "0" ] && return $rc
  # If rc is still 1, maybe it's old format, check it.
  for i in $partclone_support_fs; do
    if [ -n "$(unalias ls &>/dev/null; ls ${tgt_d}/${img_f}.${i}-img* 2>/dev/null)" ]; then
      rc=0
      break
    fi
  done
  return $rc
} # end of is_partclone_image
#
save_part_by_partclone() {
  # partclone is preferred
  local fs_save partclone_img_info_tmp IP fs_p fs_pre comp_suf
  fs_save="$1"
  if [ -z "$fs_save" ]; then
    echo "///ERROR/// The filesystem must be assigned in function save_part_by_partclone!!!"
    echo "$msg_program_stop!"
    exit 1
  fi

  # Now the image file for partclone is like:
  # sda1.ext4-ptcl-img.gz
  # ${tgt_file}.${fs_pre}-img.${comp_suf}
  # To distinguish with the one saved by ntfsclone (ntfs-img), for partclone, we use "ntfs-ptcl" for file name, and $fs_p is for used with partclone.{$fs_p} program only
  fs_p="${fs_save}"  # variable to run partclone, i.e. partclone.${fs_p}
  fs_pre="${fs_save}-ptcl"  # image name prefix, e.g. sda1."ext4-ptcl"-img.gz

  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")"
  comp_suf="$(ocs-get-comp-suffix $compress_prog_opt)"

  echo $msg_delimiter_star_line
  echo "Checking the disk space... " && disk_full_test $image_name_
  # assign partclone tmp file.
  # TODO: partclone_progress is not set actually
  case "$partclone_progress" in
    "image_dir")
       IP="$(get-ip-link-2-drbl-srv)"
       partclone_img_info_tmp="${image_name_}/saving-${IP}-${source_dev##/*/}-`date +%Y%m%d%H%M`" ;;
    *)
       partclone_img_info_tmp="/var/log/partclone.log" ;;
  esac
  [ -e "$partclone_img_info_tmp" ] && rm -f $partclone_img_info_tmp
  echo $msg_delimiter_star_line
  echo "Use partclone with $compress_prog_prompt to save the image."
  case "$VOL_LIMIT" in
    [1-9]*)
       echo "Image file will be split with size limit $VOL_LIMIT MB." ;;
    *)
       echo "Image file will not be split." ;;
  esac
  echo $msg_delimiter_star_line
  [ "$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)"
  # //NOTE// Here we force to use LC_ALL=C for partclone since we need to use get_partclone_image_info to parse the log file to get the rate. Only the keyword in English is supported in get_partclone_image_info.
  ( LC_ALL=C partclone.${fs_p} $PARTCLONE_SAVE_OPT -L $partclone_img_info_tmp \
    -c -s $source_dev --output - | $compress_prog_opt | \
    (
     case "$VOL_LIMIT" in
       [1-9]*)
          # $tgt_dir/${tgt_file}.${fs_pre}-img. is prefix, the last "." is necessary make the output file is like hda1.${fs_pre}-img.aa, hda1.${fs_pre}-img.ab. We do not add -d to make it like hda1.${fs_pre}-img.00, hda1.${fs_pre}-img.01, since it will confuse people that it looks like created by partimage (hda1.${fs_pre}-img.000, hda1.${fs_pre}-img.001)
          split -b ${VOL_LIMIT}m - $tgt_dir/${tgt_file}.${fs_pre}-img.${comp_suf}.
          ;;
       *)
          cat - > $tgt_dir/${tgt_file}.${fs_pre}-img.${comp_suf}
          ;;
     esac
    )
  )
  rc="$?"
  end_time="$(date +%s%N)"
  sync_and_active_exec_files
  # UGLY! Since we use partclone, then pipe to compression program, then IO redirect to file, the error control is weak. partclone 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}.${fs_pre}-img.${comp_suf} is incomplete! You have to increase the disk space, then start clonezilla save mode again."
  get_partclone_image_info $partclone_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)"
  # For better security
  chmod 600 $tgt_dir/${tgt_file}.${fs_pre}-img.${comp_suf}* 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;"
  echo $msg_delimiter_star_line
  echo "Finished saving $source_dev as $tgt_dir/${tgt_file}.${fs_pre}-img.${comp_suf}"
} #  end of save_part_by_partclone
#
save_part_by_partimage() {
  # 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
  echo $msg_delimiter_star_line
  check_partimage_partition_integrity $source_dev
  echo "Use $compress_prog_prompt to compress the image."
  case "$VOL_LIMIT" in
    [1-9]*)
       echo "Image file will be split with size limit $VOL_LIMIT MB." ;;
    *)
       echo "Image will not be split." ;;
  esac
  echo $msg_delimiter_star_line
  # 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"
} # end of save_part_by_partimage
#
save_part_by_dd() {
  local partclone_img_info_tmp IP
  # case (3): not supported filesystem, the last choice is to use dd to dump.
  # //NOTE// For dd, we will force to use gzip only, no other compression will be used (See function check_ocs_input_params). This will make program easier.
  # The image file name is like sda1.dd-img.aa, not sda1.dd-img.gz.aa
  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_
  [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
  echo "$msg_fs_not_supported_by_partimage_ntfsclone: $part_fs"
  echo "$msg_cons_for_dd_clone"
  echo "$msg_will_be_inefficent_and_slow..."
  case "$S2S_IMAGE_PROG_IN_OCS" in
  dd)
    echo "$msg_use_this_method_to_save_img: dd + $compress_prog_prompt"
    echo "$msg_status_report_is_very_primitive..."
    ;;
  partclone)
    echo "$msg_use_this_method_to_save_img: partclone.dd + $compress_prog_prompt"
    ;;
  esac
  [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
  case "$VOL_LIMIT" in
    [1-9]*)
       echo "Image file will be split with size limit $VOL_LIMIT MB." ;;
    *)
       echo "Image file will not be split." ;;
  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

  case "$S2S_IMAGE_PROG_IN_OCS" in
  dd)
    dd_img_info_tmp="$(mktemp /tmp/dd_info.XXXXXX)"
    part_size="$(ocs-get-part-info $source_dev size)"
    echo "Partition size: $part_size"
    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
    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 weak. 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
    [ -f "$dd_img_info_tmp" ] && rm -f $dd_img_info_tmp
    ;;
  partclone)
    # assign partclone_img_info tmp file.
    # TODO: partclone_progress
    case "$partclone_progress" in
      "image_dir")
         IP="$(get-ip-link-2-drbl-srv)"
         partclone_img_info_tmp="$target_d/restoring-${IP}-${img_file}-`date +%Y%m%d%H%M`" ;;
      *)
         partclone_img_info_tmp="/var/log/partclone.log" ;;
    esac
    [ -f "$partclone_img_info_tmp" ] && rm -f $partclone_img_info_tmp
    start_time="$(date +%s%N)"
    # //NOTE// Here we force to use LC_ALL=C for partclone since we need to use get_partclone_image_info to parse the log file to get the rate. Only the keyword in English is supported in get_partclone_image_info.
    ( LC_ALL=C partclone.dd $PARTCLONE_SAVE_OPT -L $partclone_img_info_tmp \
      -c -s $source_dev --output - | $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
      )
    )
    rc="$?"
    end_time="$(date +%s%N)"
    sync_and_active_exec_files
    # UGLY! Since we use partclone, then pipe to compression program, then IO redirect to file, the error control is weak. partclone 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_partclone_image_info $partclone_img_info_tmp $start_time $end_time
    ;;
  esac

  # no preparation time, it's accurate enough.
  echo ">>> Time elapsed: $time_elapsed secs (~ $time_elapsed_in_min mins)"
  # 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;"
  echo $msg_delimiter_star_line
  echo "Finished saving $source_dev as $tgt_dir/${tgt_file}.dd-img"
} # end of save_part_by_dd
#
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 part_fs_shown
  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
  # The format for image dir and file: $tgt_dir/${tgt_file}.${fs_pre}-img.${comp_suf}.
  check_if_source_dev_busy_before_saving "$source_dev" "$tgt_dir"
  echo $msg_delimiter_star_line
  
  # get the filesystem
  part_fs="$(ocs-get-part-info $source_dev filesystem)"

  if [ "$rm_win_swap_hib" = "yes" ]; then
    # page and hibernation files should only exist in fat or ntfs partition.
    case "$part_fs" in
      fat16|fat32|vfat|ntfs) ocs-rm-win-swap-hib $source_dev;;
    esac
    echo $msg_delimiter_star_line
  fi

  if [ "$fsck_source_part" = "yes" ]; then
    # Available fsck:
    # fsck.cramfs    fsck.ext4      fsck.hfsplus   fsck.msdos     fsck.reiserfs
    # fsck.ext2      fsck.ext4dev   fsck.jfs       fsck.nfs       fsck.vfat
    # fsck.ext3      fsck.hfs       fsck.minix     fsck.reiser4   fsck.xfs
    fsck_partition $part_fs $source_dev
  fi

  echo "Starting saving $source_dev as $tgt_dir/${tgt_file}.XXX..."
  if [ -z "$part_fs" ]; then
    part_fs_shown="Unknown or unsupported"
  else
    part_fs_shown="$part_fs"
  fi
  [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
  echo "$source_dev filesystem: $part_fs_shown."
  [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
  if [ "$FORCE_TO_USE_DD" = "yes" ]; then
    # case (0): force to use dd
    save_part_by_dd
  elif [ "$USE_NTFSCLONE" = "yes" -a "$part_fs" = "ntfs" ]; then
    # case (1): ntfsclone is preferred
    save_part_by_ntfsclone
  elif [ "$USE_PARTCLONE" = "yes" ] && `is_partclone_support_fs $part_fs`; then
    # case (2): partclone is preferred
    save_part_by_partclone $part_fs
  elif `is_partimage_support_fs $part_fs`; then
    # case (3): partimage supported filesystem, we can use partimage to save it.
    save_part_by_partimage
  else
    # case (4): not supported filesystem, the last choice is to use dd to dump.
    save_part_by_dd
  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
           [hsv]d[a-z])
             true
             ;;
           *)
            [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
            echo "\"$idisk\" is unknown HD device! Known and supported HD device is like hda, hdb, sda, sdb... $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
           [hsv]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 target_hd_2_be_checked=$1
  local partition_table
  if [ -z "$target_hd_2_be_checked" ]; 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
  gen_proc_partitions_map_file
  if [ -z "$(grep -E "$target_hd_2_be_checked\>" $partition_table)" ]; then
     [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
     echo "$msg_unable_to_find_target_hd: \"$target_hd_2_be_checked\"!!!"
     echo "$msg_check_if_this_hd_exists_and_more: $target_hd_2_be_checked"
     [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
     echo "$msg_the_partition_in_the_system_now:"
     [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
     echo $msg_delimiter_star_line
     cat /proc/partitions
     echo $msg_delimiter_star_line
     echo "$msg_program_stop!!!"
     [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
     echo -n "$msg_press_enter_to_continue..."
     read
     exit 1
  fi
  [ -f "$partition_table" ] && rm -f $partition_table
} # end of check_specify_hd_exists
check_specify_part_exists() {
  # the parameter is like hda1, sda1...
  local check_target_part=$1
  local partition_table
  if [ -z "$check_target_part" ]; then
    echo "You have to assign a device to be checked!"
    echo "Skip checking HD!!!"
    return 1
  fi
  # Check if the target part 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
  gen_proc_partitions_map_file
  if [ -z "$(LC_ALL=C grep -Ew "$check_target_part\>" $partition_table)" ]; then
     [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
     echo "Unable to find target partition \"$check_target_part\"!!!"
     [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
     echo $msg_delimiter_star_line
     echo "The disk and partition in this system:"
     cat $partition_table
     echo $msg_delimiter_star_line
     echo "Check if the partition $check_target_part really exists, otherwise maybe the kernel is too old!"
     echo "$msg_program_stop!!!"
     echo -n "$msg_press_enter_to_continue..."
     read
     exit 1
  fi
  [ -f "$partition_table" ] && rm -f $partition_table
} # end of check_specify_part_exists
#
get_known_partition_sf_format() {
    # This function is used with the output format of sfdisk
    local partition_table="$1"
    local known_parts="" fuzzy_parts workdir dsk_pt index part_fs
    # 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="$(LC_ALL=C 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. Here we can use the table from parted (ex: sda-pt.parted). Maybe in the future version, we all use the output from parted (REMEMBER! If we do so, the old clonezilla image format won't work!)
    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
       workdir="$(dirname $partition_table)"
       for i in $fuzzy_parts; do
         # /dev/sda2 -> sda2 -> 2
	 # dsk_pt is like /home/partimag/sarge-r8/sda-pt.parted
         dsk_pt="$workdir/$(basename $i | sed -e "s/[[:digit:]]*$//g")-pt.parted"
         index="$(basename $i | sed -e "s/[hsv]d[a-z]*//g")" # index is like 2
	 if [ -e "${dsk_pt}" ]; then
	   part_fs="$(grep -Ew "^[[:space:]]*$index" ${dsk_pt} | grep -i swap)"
           if [ -z "$part_fs" ]; then  # No swap keyword in parted table for $index
             known_parts="$known_parts ${i/\/dev\/}"
           fi
	 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="" fuzzy_parts workdir dsk_pt index part_fs
    [ -z "$partition_table" ] && return 1
    if LC_ALL=C grep -qi "Id=82\>" $partition_table 2>/dev/null; then
       fuzzy_parts="$(LC_ALL=C grep -i Id="82\>" $partition_table | cut -d" " -f1 | sed -e 's/:$//g')"
       # fuzzy_parts is like /dev/sda2
       workdir="$(LC_ALL=C dirname $partition_table)"
       # Double check by the file *-pt.parted.
       for i in $fuzzy_parts; do
         # /dev/sda2 -> sda2 -> 2
	 # dsk_pt is like /home/partimag/sarge-r8/sda-pt.parted
         # For ocs-onthefly, the file to be checked is tgt_pt.parted
         if [ -z "$(LC_ALL=C basename "$partition_table" | grep -Ei "tgt_pt.sf")" ]; then
           # normal case, i.e. the file name is like sda-pt.sf
           dsk_pt="$workdir/$(LC_ALL=C basename $i | sed -e "s/[[:digit:]]*$//g")-pt.parted"
         else
           # Special case for ocs-onthefly, the file name is like tgt_pt.sf
           dsk_pt="$workdir/tgt_pt.parted"
         fi
         index="$(LC_ALL=C basename $i | sed -e "s/[hsv]d[a-z]*//g")" # index is like 2
	 if [ -e "${dsk_pt}" ]; then
	   part_fs="$(LC_ALL=C grep -Ew "^[[:space:]]*$index" ${dsk_pt} | grep -i swap)"
           if [ -n "$part_fs" ]; then  # With swap keyword in parted table for $index
             known_parts="$known_parts ${i/\/dev\/}"
           fi
	 fi
       done
    fi
    echo "$known_parts"
} # end of get_swap_partition_sf_format
#
get_swap_partition_parted_format() {
    local partition_table="$1"
    local known_parts="" swparts swdisk
    [ -z "$partition_table" ] && return 1
    # The gpt partition table is like:
    # Model: LSI MR9260-4i (scsi)
    # Disk /dev/sda: 21462876160s
    # Sector size (logical/physical): 512B/512B
    # Partition Table: gpt
    # 
    # Number  Start      End           Size          File system     Name  Flags
    #  1      2048s      46874623s     46872576s     linux-swap(v1)
    #  2      46874624s  76171263s     29296640s     ext4            ROOT  boot
    #  3      76171264s  95703039s     19531776s     ext4            LOG
    #  4      95703040s  21462874111s  21367171072s  ext4            VAR
    if LC_ALL=C grep -qiEw "linux-swap" $partition_table 2>/dev/null; then
       # swparts is like 1
       swparts="$(LC_ALL=C grep -iEw "linux-swap" $partition_table | awk -F" " '{print $1}')"
       if [ -n "$(LC_ALL=C echo $swparts | grep -Eiv "[[:digit:]]")" ]; then
	 return 1
       fi
       # swdisk is like sda
       swdisk="$(LC_ALL=C grep -E "^Disk /dev/[sh]d[a-z]+" $partition_table | awk -F " " '{print $2}' | sed -r -e "s|^[[:space:]]*||g" -e "s|/dev/||g" -e "s|:||g")"

       for i in $swparts; do
         # sda + 2 -> sda2
         known_parts="$known_parts ${swdisk}${i}"
       done
    fi
    # The return known_parts is like: sda1 sda2
    echo "$known_parts"
} # end of get_swap_partition_parted_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 a 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

    gen_proc_partitions_map_file

    # 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 '/[hsv]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 '/[hsv]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=""
    echo -n "Collecting info."
    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 do not use file to get the partition type, either, since in newer version (e.g. in Debian lenny) it won't show anything about extended.
       echo -n "."
       #part_type="$(LC_ALL=C file -Ls "/dev/$p" 2>/dev/null)"
       part_type="$(LC_ALL=C ocs-get-part-info "/dev/$p" type filesystem 2>/dev/null | sort)"
       rc=$?
       [ "$rc" -gt 0 ] && continue 
       case "$chosen_mode" in
       data)
         # We want data partition to be saved, so skip extended or swap. While LVM will be processed especially.
         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
         # For some version of file (e.g. 4.25-1 in Debian lenny), file -Ls will give extended partiton as something like: /dev/hda2: x86 boot sector; partition 1: ID=0x82, starthead 1, startsector 63, 819252 sectors
         # i.e. No "extended" keyword.
         # Therefore here we use parted to check it again.
         # Ex: parted -s /dev/hda print | grep -i -A10000 "^Number" | grep -iEw "^[[:space:]]*2" | grep -io 'extended'
         [ -n "$(LC_ALL=C parted -s /dev/${p:0:3} print | grep -i -A10000 "^Number" | grep -iEw "^[[:space:]]*${p:3}" | grep -ioE 'extended')" ] && continue
         ;;
       swap)
         # We want swap partition only, so only include swap.
         [ -z "$(echo "$part_type" | grep -i swap)" ] && continue
         # For some version of file (e.g. 4.25-1 in Debian lenny), file -Ls will give extended partiton as something like: /dev/hda2: x86 boot sector; partition 1: ID=0x82, starthead 1, startsector 63, 819252 sectors
         # i.e. No "extended" keyword.
         # Therefore here we use parted to check it again.
         # Ex: parted -s /dev/hda print | grep -i -A10000 "^Number" | grep -iEw "^[[:space:]]*2" | grep -io 'extended'
         [ -z "$(LC_ALL=C parted -s /dev/${p:0:3} print | grep -i -A10000 "^Number" | grep -iEw "^[[:space:]]*${p:3}" | grep -ioE 'swap')" ] && continue
         ;;
       esac
       BACKUP_DEVS="$BACKUP_DEVS $p"
    done
    echo " 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=""
  local fs grub_p
  # found_grub_partition is a global variable
  gen_proc_partitions_map_file
  partition_list=""
  if [ -n "$select_disk" ]; then
    # Search only on the select_disk
    for i in $select_disk; do
      if [ -n "$i" ]; then
        # we have target disk, parsing it.
        partition_list="$partition_list $(LC_ALL=C awk "/${i}[0-9]+/ { print \$4; }" $partition_table | sort)"
      fi
    done
  else
    # Search whole system
    partition_list="$(LC_ALL=C awk '/[hsv]d[a-z][0-9]+/ { print $4; }' $partition_table | sort)"
  fi
  found_grub_partition=""
  for ipartition in $partition_list; do
    grub_p="/dev/$ipartition"
    # //NOTE// 2009/Sep/06 grub2 supports NTFS, so we can not do it like this way anymore.
    # If the partition is ntfs, skip. Since normally grub files are not in ntfs
    #fs="$(LC_ALL=C ocs-get-part-info $grub_p filesystem)"
    #[ -n "$(echo "$fs" | grep -i "ntfs")" ] && continue
    # process the boot loader
    hd_img="$(mktemp -d /tmp/hd_img.XXXXXX)"
    mount -o ro $grub_p $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...
      # later we will remount it as /tmp/hd_img.XXXXXX/boot
      found_grub_partition="$found_grub_partition $grub_p"
    fi
    [ "$mrc" -eq 0 ] && unmount_wait_and_try $grub_p
    [ -d "$hd_img/boot" ] && rmdir $hd_img/boot
    [ -d "$hd_img" -a -n "$hd_img" ] && rmdir $hd_img
  done
  [ -f "$partition_table" ] && rm -f $partition_table
} # end of check_grub_partition
#
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
  gen_proc_partitions_map_file
  partition_list="$(LC_ALL=C awk '/[hsv]d[a-z][0-9]+/ { print $4; }' $partition_table | sort)"
  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 ] && unmount_wait_and_try $root_partition
  done
  # Try LVM2 if no root partition is found
  if [ -z "$found_root_partition" ]; then
    # Froce to start LVM devices so that they will be shown in /proc/partitions ad dm-*
    ocs-lvm2-start
    lvm_list="$(LC_ALL=C awk '/dm-[0-9]+/ { print $4; }' $partition_table | sort)"
    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 ] && unmount_wait_and_try $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
  if [ -z "$input_dev" ]; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
    echo "No input device!"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    exit 1
  fi
  gen_proc_partitions_map_file

  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
} # end of check_if_input_device_exist
#
check_ntfs_partition_integrity() {
    # Ref: http://mlf.linux.rulez.org/mlf/ezaz/ntfsresize.html#troubleshoot
    local src_dev="$1"
    local ntfsfix_ans
    ntfsclone_save_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_save_extra_opt is a global variable
           ntfsclone_save_extra_opt="--force --rescue"
	   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 [ "$ocs_batch_mode" != "on" ]; then
         echo -n "$msg_press_enter_to_continue..."
         read
       fi
    fi
    echo "done!"
} # end of check_partimage_partition_integrity
get_grub_pkg_name_in_live() {
  # grub1_pkg_name and grub2_pkg_name are global variable name.
  # When creating Clonezilla live, we will put grub*.deb in $DRBL_SCRIPT_PATH/pkg/grub, and a packages list file "$DRBL_SCRIPT_PATH/pkg/grub/grub-pkgs-list.txt" will be created. Refer to /opt/drbl/setup/files/ocs/live-hook/ocs-live-hook-functions function "download_grub_1_2_deb_for_later_use".
  if [ -e "$DRBL_SCRIPT_PATH/pkg/grub/grub-pkgs-list.txt" ]; then
    grub1_pkg_name="$(LC_ALL=C awk -F":" '/grub1/ {print $2}' $DRBL_SCRIPT_PATH/pkg/grub/grub-pkgs-list.txt | sed -e "s/^[[:space:]]*//g")"
    grub2_pkg_name="$(LC_ALL=C awk -F":" '/grub2/ {print $2}' $DRBL_SCRIPT_PATH/pkg/grub/grub-pkgs-list.txt | sed -e "s/^[[:space:]]*//g")"
  fi
} # end of get_grub_pkg_name_in_live
prepare_grub1_files_if_required() {
  local grub_1_installed grub_1_installed
  # If it's Clonezilla live, and the installed grub is grub 2, we install grub 1
  # Decide where is the $LIVE_MEDIA
  get_live_media_mnt_point &>/dev/null
  [ -z "$LIVE_MEDIA" ] && return 3
  #
  get_grub_pkg_name_in_live
  [ -z "$grub1_pkg_name" ] && echo "No grub 1 package name was found! Exit!"  && return 3
  [ -z "$grub2_pkg_name" ] && echo "No grub 2 package name was found! Exit!"  && return 3
  # Check if grub 1 installed
  if dpkg -L ${grub1_pkg_name} &>/dev/null; then
    grub_1_installed="yes"
  fi
  if dpkg -L ${grub2_pkg_name} &>/dev/null; then
    grub_2_installed="yes"
  fi
  if [ "$grub_2_installed" = "yes" ]; then
    echo "Grub 2 is installed. We need grub 1. Removing grub 2..."
    LC_ALL=C dpkg --purge ${grub2_pkg_name} 
  fi
  if [ -z "$grub_1_installed" ]; then
    echo "Installing grub 1 now..."
    for i in $DRBL_SCRIPT_PATH/pkg/grub/${grub1_pkg_name}*.deb; do
      # Here we use --unpack instead of --install since we do not want it's to be configured, otherwise the installation might fail due to no /boot/grub/. We just need the exe file.
      [ -f "$i" ] && LC_ALL=C dpkg --unpack $i
    done
  fi
} # end of prepare_grub1_files_if_required
prepare_grub2_files_if_required() {
  local grub_1_installed grub_1_installed
  # If it's Clonezilla live, and the installed grub is grub 1, we install grub 2
  # Decide where is the $LIVE_MEDIA
  get_live_media_mnt_point &>/dev/null
  [ -z "$LIVE_MEDIA" ] && return 3
  get_grub_pkg_name_in_live
  [ -z "$grub1_pkg_name" ] && echo "No grub 1 package name was found! Exit!"  && return 3
  [ -z "$grub2_pkg_name" ] && echo "No grub 2 package name was found! Exit!"  && return 3
  # Check if grub 1 installed
  if dpkg -L ${grub1_pkg_name} &>/dev/null; then
    grub_1_installed="yes"
  fi
  if dpkg -L ${grub2_pkg_name} &>/dev/null; then
    grub_2_installed="yes"
  fi
  if [ "$grub_1_installed" = "yes" ]; then
    echo "Grub 1 was installed. We need grub 2. Removing grub 1..."
    LC_ALL=C dpkg --purge ${grub1_pkg_name}
  fi
  if [ -z "$grub_2_installed" ]; then
    echo "Installiing grub 2 now..."
    for i in $DRBL_SCRIPT_PATH/pkg/grub/${grub2_pkg_name}*.deb; do
      # Here we use --unpack instead of --install since we do not want it's to be configured, otherwise the installation might fail due to no /boot/grub/. We just need the exe file.
      [ -f "$i" ] && LC_ALL=C dpkg --unpack $i
    done
  fi
} # end of prepare_grub2_files_if_required
#
check_grub_install_version() {
  local required_ver="$1"
  local grub_version rc
  echo "Checking grub-install version..."
  if [ -n "$(LC_ALL=C $grub_install_exec --version 2>&1 | grep -iE "^grub-install \(GNU GRUB" | grep -Eo "[[:space:]]0.")" ]; then
    grub_version="1"
  else
    grub_version="2"
  fi
  if [ "$required_ver" -ne "$grub_version" ]; then
   rc=1
  else
   rc=0
  fi
  return $rc
} # end of check_grub_install_version
#
test_run_grub2_from_restored_os(){
  # The run_grub2_from_restored_os_mode we have to decide to run grub2 from the restored OS. Possible values are "yes" or "no". If "yes", Clonezilla will chroot and run grub-install. If "no", use the grub in the running system to run grub-install --root-directory=....
  # //NOTE// The OS arch in the running environment must be the same as that in the restored OS, otherwise, e.g. running OS is i386, and restored OS is x86-64, if this option is "yes", you will get error messages when run grub2 install: 
  # "cannot run command" "Exec format error"
  # Ref: http://sourceforge.net/mailarchive/message.php?msg_name=59c9195a0911211504v7f500c6arf264ec4755d7a1fd%40mail.gmail.com
  # Here we assume if we can "chroot $mount_dir ls", we can run it.
  # run_grub2_from_restored_os_mode is global variable
  local root_prt="$1"   # e.g. /dev/sda2, the partition where / exists
  local rc_
  if [ -z "$root_prt" ]; then
    echo "Variable root_prt not assigned in function test_run_grub2_from_restored_os!"
    echo "Program terminated!"
    exit 1
  fi
  mnt_pnt="$(mktemp -d /tmp/root_reinst.XXXXXX)"
  mount $root_prt $mnt_pnt
  rc_=$?
  if [ "$rc_" -gt 0 ]; then
    echo "Failed to mount $root_prt! Maybe it does not exist or the file system is not supported in the kernel. Skip running grub2 reinstallation!" 
    return 1
  fi
  echo "Test if we can chroot the restored OS partition $root_prt..."
  # Actually we'd better to test /bin/bash instead of ls since we will run a shell script to for grub-install. However, if we test /bin/bash, we will enter the shell if it's successful.
  LC_ALL=C chroot $mnt_pnt ls &>/dev/null
  rc_=$?
  if [ "$rc_" -eq 0 ]; then
    echo "Yes, we are able to chroot the restored OS partition $root_prt."
    run_grub2_from_restored_os_mode="yes"
  else
    echo "No, we are not able to chroot the restored OS partition $root_prt."
    run_grub2_from_restored_os_mode="no"
  fi
  unmount_wait_and_try $mnt_pnt
  [ -d "$mnt_pnt" -a -n "$mnt_pnt" ] && rmdir $mnt_pnt
  return 0
} # test_run_grub2_from_restored_os
# Required: grub2 exec program exists, and it works with the lib from Clonezilla live
run_grub2_from_restored_os() {
  local mnt_pnt grub2_run_sh ival rc_
  local grub2_prt="$1"  # e.g. /dev/sda1, the partition where /boot/grub exists
  local root_prt="$2"   # e.g. /dev/sda2, the partition where / exists
  local grub2_hd="$3"   # e.g. /dev/sda, the disk to install grub
  for i in grub2_prt root_prt grub2_hd; do
    eval ivar=\$${i}
    if [ -z "$ivar" ]; then
      echo "No \"$i\" assigned in function run_grub2_from_restored_os!"
      echo "Program terminated!"
      exit 1
    fi
  done
  echo "Re-installing grub2 on disk $grub2_hd with grub2 dir in partition $grub2_prt and root partition $root_prt... "
  mnt_pnt="$(mktemp -d /tmp/root_reinst.XXXXXX)"
  mount $root_prt $mnt_pnt
  rc_=$?
  if [ "$rc_" -gt 0 ]; then
    echo "Failed to mount $root_prt! Maybe it does not exist or the file system is not supported in the kernel. Skip running grub2 reinstallation!" 
    return 1
  fi
  mkdir -p $mnt_pnt/dev
  mount --bind /dev $mnt_pnt/dev
  mount --bind /proc $mnt_pnt/proc
  mount --bind /sys $mnt_pnt/sys

  grub2_run_sh="$(mktemp $mnt_pnt/grub2-sh-XXXXXX)"
  cat <<-SH_END > $grub2_run_sh
#!/bin/bash
export LC_ALL=C
export PATH=/sbin/:/usr/sbin:/bin/:/usr/bin
if ! type update-grub2 &>/dev/null; then
   echo "No grub2 program (e.g. update-grub2) is found in the restored OS!"
   echo "Skipping grub2 reinstallation!"
   exit 1
fi
rm -f /boot/grub/device.map
grub-install --force --recheck --no-floppy ${grub2_hd}
SH_END
  chmod 755 $grub2_run_sh
  # If grub partition is not in the same partition as root partition, we have to mount grub partition in the root partition.
  diff_grub_pt_flag="no"
  if [ "$grub2_prt" != "$root_prt" ]; then
     mkdir -p $mnt_pnt/boot/grub
     chroot $mnt_pnt mount $grub2_prt /boot/
     diff_grub_pt_flag="yes"
  fi
  # Run grub-install
  chroot $mnt_pnt /"$(basename $grub2_run_sh)"
  rc_=$?
  [ -e "$grub2_run_sh" ] && rm -f $grub2_run_sh

  #
  if [ "$diff_grub_pt_flag" = "yes" ]; then
    chroot $mnt_pnt umount /boot/
    rmdir $mnt_pnt/boot/grub 2>/dev/null
  fi
  # Remove the device.map in case it provents the restored OS fails to run update-grub2 due to different boot device name (e.g. sda <-> hda issue)
  [ -f "$mnt_pnt/boot/grub/device.map" ] && rm -f $mnt_pnt/boot/grub/device.map

  unmount_wait_and_try $mnt_pnt/sys
  unmount_wait_and_try $mnt_pnt/proc
  unmount_wait_and_try $mnt_pnt/dev
  unmount_wait_and_try $mnt_pnt
  [ -d "$mnt_pnt/dev" ] && rmdir $mnt_pnt/dev
  [ -d "$mnt_pnt" -a -n "$mnt_pnt" ] && rmdir $mnt_pnt
  return $rc_
} # end of run_grub2_from_restored_os
# re-install grub
install_grub_hd() {
    local grub_partition # e.g. /dev/sda1
    local selected_hd selected_parts  # selected_hd e.g. sda, selected_parts e.g. sda1, sda2
    local grub_ver_in_restored_os=""
    # local functions in install_grub_hd
    do_run_grub2_from_restored_os() {
      [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
      echo "Trying to use the grub2 in the restored OS!"
      [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
      unmount_wait_and_try $grub_partition 2>/dev/null
      unmount_wait_and_try $hd_img 2>/dev/null
      [ -d "$hd_img/boot" -a -n "$hd_img" ] && rmdir $hd_img/boot
      [ -d "$hd_img" -a -n "$hd_img" ] && rmdir $hd_img
      echo "Running: run_grub2_from_restored_os \"$grub_partition\" \"$found_root_partition\" \"/dev/$grub_hd\""
      run_grub2_from_restored_os "$grub_partition" "$found_root_partition" "/dev/$grub_hd"
      rc=$?
    }
    #
    do_run_grub2_from_running_os() {
      prepare_grub2_files_if_required
      [ -f "$hd_img/boot/grub/device.map" ] && rm -f $hd_img/boot/grub/device.map
      echo "Trying to use the grub2 from the running OS..."
      check_grub_install_version 2
      rc_grub_check=$?
      if [ "$rc_grub_check" -eq 0 ]; then
        echo "Running: $grub_install_exec --force --recheck $grub_no_floopy_opt --root-directory=$hd_img /dev/$grub_hd"
        $grub_install_exec --force --recheck $grub_no_floopy_opt --root-directory=$hd_img /dev/$grub_hd
        rc=$?
      else
        [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
        echo "The required version of grub-install for grub 2 was not found!"
        [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
        echo "Command grub-install is skipped!"
        rc=1
      fi
      unmount_wait_and_try $grub_partition 2>/dev/null
      unmount_wait_and_try $hd_img 2>/dev/null
      [ -d "$hd_img/boot" -a -n "$hd_img" ] && rmdir $hd_img/boot
      [ -d "$hd_img" -a -n "$hd_img" ] && rmdir $hd_img
    }
    # -s or -p option is used to pass the selected device, which means only these device will be searched for grub partition. If no assigning, all the available partitions on the system will be searched.
    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
           ;;
        -p|--selected-parts)
           shift
           if [ -z "$(echo $1 |grep ^-.)" ]; then
             # skip the -xx option, in case 
             selected_parts="$1"
             shift
           fi
           [ -z "$selected_parts" ] && echo "-p is used, but no selected_parts assigned." && exit 1
           ;;
        -*)     echo "${0}: ${1}: invalid option" >&2
                USAGE >& 2
                exit 2 ;;
        *)      break ;;
      esac
    done
    grub_partition="$1"

    # From OpenSuSE 10.3 or later, the original grub-install is renamed as grub-install.unsupported, and OpenSuSE uses a script to run grub-install by yast2. Bad! Very bad idea! They should use another name for this grub-install.
    if [ -e /etc/SuSE-release -a -e /usr/sbin/grub-install.unsupported ]; then
      grub_install_exec="grub-install.unsupported"
    else
      grub_install_exec="grub-install"
    fi

    # if grub_partition is not set, set default
    if [ "$grub_partition" = "auto" ]; then
       grub_partition=""   # reset it as none
       check_grub_partition "$selected_hd"
       # We get $found_grub_partition from check_grub_partition, it might be 1 or 2 or more partitions, e.g. /dev/sda1 /dev/hda1
       # 3 cases:
       # (1) selected_parts is assigned
       # From the selected_parts, we can decide which one got from check_grub_partition is really for the target disk
       # (2) selected_hd is assigned
       # found_grub_partition e.g.: /dev/sda1 /dev/hda1
       # selected_hd e.g.: sda sdb
       # (3) no selected_parts or selected_hd is assigned
       if [ -n "$selected_parts" ]; then
         # found_grub_partition e.g.: /dev/sda1 /dev/hda1
         # selected_parts e.g.: sda1 sda2 sda5
         for i in $found_grub_partition; do
           for j in $selected_parts; do
             if [  "$(basename $i)" = "$j" ]; then
              grub_partition="$i"  # e.g. /dev/sda1
              break
             fi
           done
           [ -n "$grub_partition" ] && break
         done
       elif [ -n "$selected_hd" ]; then
         # found_grub_partition e.g.: /dev/sda1 /dev/hda1
         # selected_hd e.g.: sda sdb
         for i in $found_grub_partition; do
           for j in $selected_hd; do
             if [ "$(echo $(basename $i) | sed -r -e "s/[[:digit:]]+$//g")" = "$j" ]; then
              grub_partition="$i"  # e.g. /dev/sda1
              break
             fi
           done
           [ -n "$grub_partition" ] && break
         done
       else
         # No assignment, use the one found. However, maybe there are more than 1. We only take the first one.
         if [ "$(LC_ALL=C echo $found_grub_partition | wc -w)" -gt 1 ]; then
           grub_partition="$(echo $found_grub_partition | awk -F" " '{print $1}')"
           echo "More than one grub config partitions exist: $found_grub_partition. Choose the 1st one: $grub_partition."
         else
           grub_partition="$found_grub_partition"
         fi
       fi
       if [ -n "$grub_partition" ]; then
         echo "Found grub partition: $grub_partition"
       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 running $grub_install_exec." 
	 return 1
       fi
       # Check if grub_partition is on the list of target partitions
       if [ -n "$selected_parts" ]; then
         # Only if we assign the selected partitions, we have to check...
         if [ -n "$(echo $selected_parts | grep -Ewo "$(basename $grub_partition)")" ]; then
           echo "Found grub partition \"$grub_partition\", which is on the restored partition list ($selected_parts). Will run $grub_install_exec later."
         else
           # E.g. echo "sda1 sda3" | grep -Ewo "$(basename /dev/sda1)"
           echo "The found grub partition \"$grub_partition\" is _NOT_ on the restored partition list ($selected_parts). Skip running $grub_install_exec."
           return 3
         fi
       fi
    fi
    # grub_partition is like "/dev/hda1" or "/dev/sda1"
    # So grub_hd is like "hda" or "sda"
    # Remove the leading space, just in case.
    grub_partition="$(LC_ALL=C echo "$grub_partition" | sed -r -e "s/^[[:space:]]*//g")"
    grub_hd="${grub_partition:5:3}"
    # active this device
    fdisk -l /dev/$grub_hd &>/dev/null
    # Check if grub exists on the MBR. If not, skip this function.
    if [ -z "$(LANG=C dd if=/dev/$grub_hd bs=512 count=1 2>/dev/null | strings | grep "GRUB")" ]; then
       echo "The boot loader on /dev/$grub_hd is not grub. Skip running grub-install!"
       return 5
    else 
       echo "Found boot loader grub in the MBR of disk /dev/$grub_hd."
    fi
    # 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_exec --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 no "/boot/", it must be the grub partition itself (i.e. a separate partition will be mounted as /boot, and it conains /grub/ in the partition.)
    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
       unmount_wait_and_try $hd_img
       [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
       echo "There is a separate boot partition in target device. Trying to mount root partition for $grub_install_exec 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  # return variable $found_root_partition
       # just in case no /boot in root partition
       mkdir -p $hd_img/boot
       mount $grub_partition $hd_img/boot/
       # Check if it's grub2
       if [ -n "$(unalias ls 2>/dev/null; ls $hd_img/boot/grub/*.mod 2>/dev/null)" ]; then
         grub_ver_in_restored_os="2"
         echo "Found grub 2 installed in the restored OS."
         if [ -z "$run_grub2_from_restored_os_mode" ]; then
           # We will get variable run_grub2_from_restored_os_mode with this
           test_run_grub2_from_restored_os "$found_root_partition"
         fi
         if [ "$run_grub2_from_restored_os_mode" = "yes" ]; then
           do_run_grub2_from_restored_os
           if [ "$rc" -ne 0 ]; then
             [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
             echo "Unable to use the grub 2 in the restored OS, trying another method..."
             [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
             # Plan B:
             mount_root_partition_for_separate_boot_part $hd_img  # return variable $found_root_partition
             # just in case no /boot in root partition
             mkdir -p $hd_img/boot
             mount $grub_partition $hd_img/boot/
             do_run_grub2_from_running_os
           fi
         else
           do_run_grub2_from_running_os
         fi
       else
         # grub version 1
         echo "Found grub 1 installed in the restored OS."
         grub_ver_in_restored_os="1"
         prepare_grub1_files_if_required
         # 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
         check_grub_install_version 1
         rc_grub_check=$?
         if [ "$rc_grub_check" -eq 0 ]; then
           echo "Running: $grub_install_exec $grub_no_floopy_opt --root-directory=$hd_img /dev/$grub_hd"
           $grub_install_exec $grub_no_floopy_opt --root-directory=$hd_img /dev/$grub_hd
           rc=$?
         else
           [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
           echo "The required version of grub-install for grub 1 was not found!"
           [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
           echo "Command grub-install is skipped!"
           rc=1
         fi
       fi
    else
       # In this case, /boot must co-exist with root system in same partition, 
       # Then found_root_partition is the grub partition
       found_root_partition="$grub_partition"
       # Check if it's grub2
       if [ -n "$(unalias ls 2>/dev/null; ls $hd_img/boot/grub/*.mod 2>/dev/null)" ]; then
         grub_ver_in_restored_os="2"
         echo "Found grub 2 installed in the restored OS."
         if [ -z "$run_grub2_from_restored_os_mode" ]; then
           # We will get variable run_grub2_from_restored_os_mode with this
           test_run_grub2_from_restored_os "$found_root_partition"
         fi
         if [ "$run_grub2_from_restored_os_mode" = "yes" ]; then
           do_run_grub2_from_restored_os
           if [ "$rc" -ne 0 ]; then
             [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
             echo "Unable to use the grub 2 in the restored OS, trying another method..."
             [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
             # Plan B:
             mount_root_partition_for_separate_boot_part $hd_img  # return variable $found_root_partition
             # just in case no /boot in root partition
             mkdir -p $hd_img/boot
             mount $grub_partition $hd_img/boot/
             do_run_grub2_from_running_os
           fi
         else
           do_run_grub2_from_running_os
         fi
       else
         # grub version 1
         echo "Found grub 1 installed in the restored OS."
         grub_ver_in_restored_os="1"
         prepare_grub1_files_if_required
         # 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
         check_grub_install_version 1
         rc_grub_check=$?
         if [ "$rc_grub_check" -eq 0 ]; then
           echo "Running: $grub_install_exec $grub_no_floopy_opt --root-directory=$hd_img /dev/$grub_hd"
           $grub_install_exec $grub_no_floopy_opt --root-directory=$hd_img /dev/$grub_hd
           rc=$?
         else
           [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
           echo "The required version of grub-install for grub 1 was not found!"
           [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
           echo "Command grub-install is skipped!"
           rc=1
         fi
       fi
    fi

    if [ "$grub_ver_in_restored_os" = "1" ]; then 
      # Check if grub config exists or not
      [ ! -e "$hd_img/boot/grub/menu.lst" -a ! -e "$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 restored or cloned!!!"
      
      if [ -f "$hd_img/boot/grub/menu.lst" -a ! -e "$hd_img/boot/grub/grub.conf" ]; then
        # For some version of grub, only honors grub.conf, we have to deal with.
        # Test if soft link works, if yes, link it. If not, copy it.
        # if it's VFAT, grub-install will fail when try to link menu.lst as grub.conf, we just copy it.
        (cd $hd_img/boot/grub/ 
         ln -fs menu.lst grub.conf 2>/dev/null
        )
        if [ ! -e "$hd_img/boot/grub/grub.conf" ]; then
          cp -f $hd_img/boot/grub/menu.lst $hd_img/boot/grub/grub.conf
        fi
      fi
      if [ ! -f "$hd_img/boot/grub/menu.lst" -a -e "$hd_img/boot/grub/grub.conf" ]; then
        # For the grub from debian, only honors menu.lst, we have to deal with.
        # Test if soft link works, if yes, link it. If not, copy it.
        # if it's VFAT, grub-install will fail when try to link grub.conf as menu.lst, we just copy it.
        (cd $hd_img/boot/grub/ 
         ln -fs grub.conf menu.lst 2>/dev/null
        )
        if [ ! -e "$hd_img/boot/grub/menu.lst" ]; then
          cp -f $hd_img/boot/grub/grub.conf $hd_img/boot/grub/menu.lst
        fi
      fi

      # Remove the device.map in case it provents the restored OS fails to run update-grub due to different boot device name (e.g. sda <-> hda issue)
      [ -f "$hd_img/boot/grub/device.map" ] && rm -f $hd_img/boot/grub/device.map

      unmount_wait_and_try $grub_partition 2>/dev/null
      unmount_wait_and_try $hd_img 2>/dev/null
      [ -d "$hd_img/boot" -a -n "$hd_img" ] && rmdir $hd_img/boot
      [ -d "$hd_img" -a -n "$hd_img" ] && rmdir $hd_img
    fi

    if [ "$rc" -ne 0 ]; then
        [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
	echo "Failed to install grub!!!"
        [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
	[ "$debug_mode" = "on" ] && sulogin
    else
	echo "done!"
    fi
    return $rc
} # end of install_grub_hd

# output the CHS values of a HD
output_HD_CHS() {
   local DEV=$1
   local target_d=$2
   local opt_gm
   [ -z "$DEV" -o -z "$target_d" ] && return 1
   # Check if -G (from partition table) exists for sfdisk. If not, use -g (from kernel). It's better to use -G.
   if [ -n "$(LC_ALL=C sfdisk --help 2>&1 | grep -w "\-G")" ]; then
     opt_gm="-G"
   else
     opt_gm="-g"
   fi
   cylinders="$(LC_ALL=C sfdisk $opt_gm /dev/$DEV 2>/dev/null | grep -oEi "[[:digit:]]+ cylinders" | awk -F" " '{print $1}')"
   heads="$(LC_ALL=C sfdisk $opt_gm /dev/$DEV 2>/dev/null | grep -oEi "[[:digit:]]+ heads" | awk -F" " '{print $1}')"
   sectors="$(LC_ALL=C sfdisk $opt_gm /dev/$DEV 2>/dev/null |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
} # output the CHS values of a HD

# 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_opt_from_image() {
  local target_d="$1"
  local DEV="$2"  # e.g. hda
  # sfdisk_opt is global variable
  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
} # end of load_HD_geometry_opt_from_image
#
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.
  ocs-lvm2-stop
  sleep 1
} # 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
  [ -e "$dsk_chk_tmp" ] && rm -f $dsk_chk_tmp
} # 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
  [ -e "$dsk_chk_tmp" ] && rm -f $dsk_chk_tmp
} # 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 a global variable
  local tgt_hd_file="$1"
  local tgt_dir="$2"
  local tgt_hd_name="$(basename $tgt_hd_file)"
  local pt_type=""
  local disk_size_sec to_seek do_gpt_dd gdisk_tmp sgdisk_rc

  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..."
  check_if_source_dev_busy_before_create_partition $tgt_hd_file

  # From the output file of parted, we can decide if the partition is gpt or mbr
  if [ -n "$(grep -iE "^Partition Table:" $tgt_dir/${tgt_hd_name}-pt.parted 2>/dev/null | grep -iE "gpt")" ]; then
    pt_type="gpt"
  else
    pt_type="mbr"
  fi

  #
  case "$pt_type" in
  mbr)
    # From use_HD_CHS_from_EDD_pref we will decide if use_HD_CHS_from_EDD is yes or no. The rule is if we find the target disk does _NOT_ contain grub boot loader, set use_HD_CHS_from_EDD as yes, others, set it as no. This is because we will re-run grub-install by deafult.
    if [ "$use_HD_CHS_from_EDD_pref" = "yes" ]; then
      if [ -z "$(LC_ALL=C strings $tgt_dir/${tgt_hd_name}-mbr | grep -i "grub")" ]
      then
        # It's not grub boot loader, set use_HD_CHS_from_EDD as yes
	echo "Non-grub boot loader found on $tgt_dir/${tgt_hd_name}-mbr..."
	echo "The CHS value of hard drive from EDD will be used for sfdisk."
	use_HD_CHS_from_EDD="yes"
      fi
    fi
    if [ "$use_HD_CHS_from_EDD" = "yes" ]; then
      rawhead=""
      rawsector=""
      rawcylinder=""
      get_RawCHS_of_HD_from_EDD $tgt_hd_file
      if [ -n "$rawhead" -a -n "$rawsector" -a -n "$rawcylinder" ]; then
        sfdisk_opt="$sfdisk_opt -C $rawcylinder -H $rawhead -S $rawsector"
      else
        echo "No CHS value was found from EDD info for disk $tgt_hd_file."
      fi
    elif [ "$load_HD_CHS_from_img" = "yes" ]; then
      # Append the CHS to variable "$sfdisk_opt" if CHS can be find in the image dir.
      load_HD_geometry_opt_from_image $tgt_dir $tgt_hd_name
    fi
    # If the partition table is tag as "gpt", change it as msdos
    if [ -n "$(LC_ALL=C parted -s $tgt_hd_file print | grep -iE "^Partition Table:" | grep -iE "gpt")" ]; then
      echo "The partition table in $tgt_hd_file is for GPT, now make it for MBR."
      LC_ALL=C parted -s $tgt_hd_file mklabel msdos
    fi
    # 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
    LC_ALL=C date | tee $RESTORE_SFDISK_LOG
    echo "Writing 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
    # Ref: http://en.wikipedia.org/wiki/Master_boot_record
    # Master Boot Record (MBR) is the 512-byte boot sector:
    # 446 bytes (executable code area) + 64 bytes (table of primary partitions) + 2 bytes (MBR signature; # 0xAA55) = 512 bytes.
    # However, some people also call executable code area (first 446 bytes in MBR) as MBR.
    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])
        case "$create_part_type" in
	  "proportion")
            [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
	    echo "Create the proportional partition table in $tgt_hd_file based on $tgt_dir/${tgt_hd_name}-pt.sf and the size of $tgt_hd_file..."
            [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
	    # We use environmental variable EXTRA_SFDISK_OPT so it can be used in ocs-expand-mbr-pt
	    EXTRA_SFDISK_OPT=""
	    [ -n "$sfdisk_opt" ] && EXTRA_SFDISK_OPT="$sfdisk_opt"
	    EXTRA_SFDISK_OPT="$EXTRA_SFDISK_OPT" ocs-expand-mbr-pt --batch $tgt_dir/${tgt_hd_name}-pt.sf $tgt_hd_file
	    ;;
	  "manual")
	    echo "$msg_enter_another_shell_for_fdisk"
            echo -n "$msg_press_enter_to_continue..."
            read
            /bin/bash
	    ;;
          *)
            # Create partition table by sfdisk and [hsv]d[a-z]-pt.sf
            echo "Running sfdisk $sfdisk_opt $tgt_hd_file < $tgt_dir/${tgt_hd_name}-pt.sf" | tee -a $RESTORE_SFDISK_LOG
            sfdisk $sfdisk_opt $tgt_hd_file < $tgt_dir/${tgt_hd_name}-pt.sf | tee -a $RESTORE_SFDISK_LOG
            RETVAL="$?"
            echo "This is done by sfdisk $sfdisk_opt $tgt_hd_file < $tgt_dir/${tgt_hd_name}-pt.sf" | tee -a $RESTORE_SFDISK_LOG
            # This is useless if $sfdisk_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
        ;;
    esac
    # Due to some reason, although LVM was deactived before we ran sfdisk, "sfdisk --force..." might cause LVM to active again. Therefore we have to deactive and run "sfdisk -R..." to make the kernel know the new partition table again.
    echo $msg_delimiter_star_line
    if [ -n "$(LC_ALL=C lvscan | grep -Ewi "ACTIVE")" ]; then
      echo "LV is still active, try to deactive it..."
      ocs-lvm2-stop
    fi
    echo "Informing kernel that the OS that partition table has changed..."
    inform_kernel_partition_table_changed mbr $tgt_hd_file
    ;;
  gpt)
    echo $msg_delimiter_star_line
    if [ -e "$tgt_dir/${tgt_hd_name}-gpt.gdisk" ]; then
      gdisk_tmp="$(mktemp /tmp/gdisk_tmp.XXXXXX)"
      echo "Running: sgdisk -l $tgt_dir/${tgt_hd_name}-gpt.gdisk $tgt_hd_file"
      LC_ALL=C sgdisk -l $tgt_dir/${tgt_hd_name}-gpt.gdisk $tgt_hd_file | tee $gdisk_tmp
      sgdisk_rc=$?
      if [ "$sgdisk_rc" -eq 0 ]; then
        # There is a bug about sgdisk 0.6.14, i.e.
        # sgdisk -l /home/partimag/2011-02-27-02-img-hfsplus-gdisk/sda-gpt.gdisk /dev/sdb
        # Warning! Current disk size doesn't match that of the backup!
        # Adjusting sizes to match, but subsequent problems are possible!
        # The justLooking flag is set. This probably means you can't write to the disk.
        # Aborting write of new partition table.
        # However, its return code is still 0. Therefore we can not use return code to judge that.
        if [ -n "$(LC_ALL=C grep -iE "Aborting write of new partition table" $gdisk_tmp)" ]; then
          [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
          echo "Failed to use sgdisk to restore the GPT partition table. Will use dd to restore the GPT partition table."
          [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
          do_gpt_dd="yes"
        else
          do_gpt_dd="no"
        fi
      else
        do_gpt_dd="yes"
      fi
      [ -e "$gdisk_tmp" ] && rm -f $gdisk_tmp
    else
      # For old style of image, there is no ${tgt_hd_name}-gpt.gdisk. We have to use dd to create the GPT partition table.
      do_gpt_dd="yes"
    fi
    if [ "$do_gpt_dd" = "yes" ]; then
      echo "Restoring the primary GPT to $tgt_hd_file..."
      # ///NOTE/// We can not use ocs-create-gpt (parted) to clone GPT, since the original GPT contains unique GUID! Ref:  http://developer.apple.com/technotes/tn2006/tn2166.html#SECCOPYINGCONCERNS for more details.
      # ocs-create-gpt $tgt_dir/${tgt_hd_name}-pt.parted
      LC_ALL=C dd if=$tgt_dir/${tgt_hd_name}-gpt-1st of=$tgt_hd_file
      retv=$?
      if [ "$retv" -gt 0 ]; then
        [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
        echo -n "Failed to restore GPT partition to $tgt_hd_file! $msg_press_enter_to_continue... "
        [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
        read
      fi
      # We need to get the total size of disk so that we can skip and dump the last block:
      # The output of 'parted -s /dev/sda unit s print' is like:
      # --------------------
      # Disk /dev/hda: 16777215s
      # Sector size (logical/physical): 512B/512B
      # Partition Table: gpt
      # 
      # Number  Start     End        Size       File system  Name     Flags  
      #  1      34s       409640s    409607s    fat32        primary  msftres
      #  2      409641s   4316406s   3906766s   ext2         primary         
      #  3      4316407s  15625000s  11308594s  reiserfs     primary         
      # --------------------
      echo "Restoring the secondary GPT to $tgt_hd_file..."
      disk_size_sec="$(LC_ALL=C grep -E "^Disk /dev/" $tgt_dir/${tgt_hd_name}-pt.parted | awk -F":" '{print $2}' | sed -e "s/s$//g")"
      to_seek="$((${disk_size_sec}-33+1))"
      LC_ALL=C dd if=$tgt_dir/${tgt_hd_name}-gpt-2nd of=$tgt_hd_file seek=${to_seek} bs=512 count=33
    fi

    echo "Informing kernel that the OS that partition table has changed..."
    inform_kernel_partition_table_changed gpt $tgt_hd_file
    sleep 1
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "$msg_the_partition_in_the_system_now:"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo $msg_delimiter_star_line
    LC_ALL=C parted -s $tgt_hd_file print
    echo $msg_delimiter_star_line
    ;;
  esac
} # end of create_partition
#
countdown_or_confirm_before_save() {
  local save_img_name="$1"
  local continue_choice
  # 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:"
    echo $msg_delimiter_star_line
    echo -e "$dev_model_shown"
    echo $msg_delimiter_star_line
    echo "-> \"$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:"
    echo $msg_delimiter_star_line
    echo -e "$dev_model_shown"
    echo $msg_delimiter_star_line
    echo "-> \"$save_img_name\"."
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    continue_choice=""
    while [ -z "$continue_choice" ]; do
      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])
              continue_choice="yes"
              echo "$msg_ok_let_do_it!"
              ;;
           n|N|[nN][oO])
              continue_choice="no"
              echo "$msg_program_stop!"
              exit 1
              ;;
           *)
              [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
              echo "$msg_you_have_to_enter_yes_or_no!"
              [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
              echo $msg_delimiter_star_line
              ;;
      esac
    done
  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 new_dev_to_be_restore="$2"
  local i partition_table
  local continue_choice
  # 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\" -> \"$new_dev_to_be_restore\""
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    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
    echo "$msg_uppercase_Warning!!! $msg_uppercase_Warning!!! $msg_uppercase_Warning!!!"
    if [ -n "$dev_model_shown" ]; then
      echo "$msg_uppercase_Warning! $msg_all_data_in_dev_will_be_overwritten:"
      echo $msg_delimiter_star_line
      echo -e "$dev_model_shown"
      echo $msg_delimiter_star_line
    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\" -> \"$new_dev_to_be_restore\""
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    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
    echo "$msg_uppercase_Warning!!! $msg_uppercase_Warning!!! $msg_uppercase_Warning!!!"
    if [ -n "$dev_model_shown" ]; then
      echo "$msg_uppercase_Warning! $msg_all_data_in_dev_will_be_overwritten:"
      echo $msg_delimiter_star_line
      echo -e "$dev_model_shown"
      echo $msg_delimiter_star_line
    fi
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    continue_choice=""
    while [ -z "$continue_choice" ]; do
      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])
              continue_choice="yes"
              echo "$msg_ok_let_do_it!"
              ;;
           n|N|[nN][oO])
              continue_choice="no"
              rm_tmp_cnvt_img
              echo "$msg_program_stop!"
              exit 1
              ;;
           *)
              [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
              echo "$msg_you_have_to_enter_yes_or_no!"
              [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
              echo $msg_delimiter_star_line
              ;;
      esac
    done
  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
} # end of task_preprocessing
#
task_processing_after_parameters_checked() {
  #
  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 [ "$debug_mode" = "on" ]; then
    echo "Enter shell...Use \"exit\" to back to the original program."
    sulogin
  fi
} # end of task_processing_after_parameters_checked
#
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() {
  local ipart retv
  #
  echo "*****************************************************"
  case "$change_win_hostname" in
     By_IP)
          for ipart in $target_parts; do
            echo "Changing the hostname of M$ windows based on IP address..."
            ocs-chnthn -b -v IP -p $win_hostname_prefix -d /dev/$ipart
            retv=$?
            [ "$retv" -eq 0 ] && break
          done
          ;;
     By_MAC)
          for ipart in $target_parts; do
            echo "Changing the hostname of M$ windows based on MAC address..."
            ocs-chnthn -b -v MAC -p $win_hostname_prefix -d /dev/$ipart
            retv=$?
            [ "$retv" -eq 0 ] && break
          done
          ;;
  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="$(get-client-ip-list | wc -l | sed -e "s/ //g")"
  else
    n_clients_tmp_default="$mcast_cln_no"
  fi
  if [ -z "$dcs_mcast_type" ]; then
    $DIA --backtitle "$msg_nchc_free_software_labs" --title "$msg_nchc_clonezilla | $msg_mode: $ocs_mode_prompt" \
    --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
    dcs_mcast_type="$(cat $TMP)"
  fi
  case "$dcs_mcast_type" in
    "time-to-wait") 
       $DIA --backtitle "$msg_nchc_free_software_labs" --title "$msg_nchc_clonezilla | $msg_mode: $ocs_mode_prompt" \
       --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 | $msg_mode: $ocs_mode_prompt" \
       --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 | $msg_mode: $ocs_mode_prompt" \
       --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 | $msg_mode: $ocs_mode_prompt" \
       --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
       ;;
    *)
       [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
       echo "You must specify a correct multicast mode! \"dcs_mcast_type\" is an unknown or unsupported mode!"
       [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
       echo "$msg_program_stop!"
       exit 1
  esac 
  [ -f "$TMP" ] && rm -f $TMP
} # end of ask_time_or_clients_to_wait
#
check_existing_target_image() {
  local tgt_image="$1"
  if [ "${tgt_image/%\//}" = "${ocsroot/%\//}" -o -z "$tgt_image" ]; then
    echo "Something went wrong! Existing target image is nothing! Contact DRBL developers to fix this problem!"
    echo "$msg_program_stop!!!"
    exit 1
  else
    [ -f "$tgt_image" ] && rm -f $tgt_image
    [ -d "$tgt_image" -a -n "$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 does 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" -eq 1 ]; do
      tgt_name_default="$(date +%F-%H-img)"
      $DIA --backtitle "$msg_nchc_free_software_labs" --title "$msg_nchc_clonezilla | $msg_mode: $ocs_mode_prompt" \
         --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 | $msg_mode: $ocs_mode_prompt" \
        --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 | $msg_mode: $ocs_mode_prompt" \
        --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|\.ptcl-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 | $msg_mode: $ocs_mode_prompt" \
        --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 | $msg_mode: $ocs_mode_prompt" \
        --yesno "$msg_you_must_input_filename! $msg_do_u_want_to_do_it_again" 0 0 
        ans_="$?"
        case "$ans_" in
          0) # yes is chosen
             ASK_IMGNAME=1;;
          1) # no is chosen
             echo "$msg_program_stop!"
             [ -f "$TMP" ] && rm -f $TMP
             exit 1;;
        esac
      elif [ -d "$ocsroot/$tgt_img_name" ]; then
        $DIA --backtitle "$msg_nchc_free_software_labs" --title "$msg_nchc_clonezilla | $msg_mode: $ocs_mode_prompt" \
        --yesno "$msg_the_image \"$tgt_img_name\" $msg_was_saved_before!\n\n$msg_do_you_want_to_replace ?" 0 0 2> $TMP
        # Yes (to overwrite) is chosen, $?=0, if no is chosen, $?=1
        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_disk_serial_no() {
  # This is specially for sdx. For hdx, we can get from "cat /sys/block/$p/device/serial 2>/dev/null"
  local dsk_="$1"  # e.g. sda
  serialno=""
  [ -z "$dsk_" ] && echo "No input value for dsk_!" && exit 1
  # lrwxrwxrwx 1 root root   9 2009-01-23 20:18 ata-ST3160815AS-6RX1880B -> ../../sda
  serialno="$(LC_ALL=C find /dev/disk/by-id/ -lname "*${dsk_}" -print 2>/dev/null | sort | uniq | head -n 1 | xargs basename 2>/dev/null)"
  # results e.g.: 
  # ata-ST3160815AS-6RX1880B
  # scsi-SATA_ST3160815AS_6RX1880B
  # ata-Hitachi_HTS543232L9A300_080617FB0400LEG4X7EA
  # scsi-1ATA_Hitachi_HTS543232L9A300_080617FB0400LEG4X7EA
  # If we want to polish, maybe:
  #serialno="$(echo $serialno | awk -F"-" '{print $NF}' | awk -F"_" '{print $NF}')"
  # However, the rule is not always true that there is only one "_"
} # end of get_disk_serial_no
#
get_disk_or_part_hardware_info() {
  # ///NOTE/// TO use this function, the device must exist in this system. Otherwise the hardware info (for example, if /dev/hda1 is not created yet) won't be found.
  # DEV_MODEL is a global variable to output
  local p="$1"  # p is like hda, sda, or hda1, sda1
  local dsize x dsk_ rc serialno
  dsize="" # device size
  case "$p" in
     hd[a-z])
       if [ "$(cat /proc/ide/$p/media)" = "disk" ] ; then
         # parted output Ex. Disk /dev/sda: 160GB // This is only for newer parted. For old parted (1.6.3 in redhat 9, nothing will be shown)
         dsize="$(LC_ALL=C parted -s /dev/$p print 2>/dev/null | grep -E "^[[:space:]]*Disk[[:space:]]+/dev/$p:" | awk -F":" '{print $2}' | sed -e "s/ //g")"
         if [ -z "$dsize" ]; then
          # Try the other way since parted will fail if the disk is not partitioned.
          dsize="$(LC_ALL=C echo "scale=1; $(cat /sys/block/$p/size 2>/dev/null)*512/1000.0^3" | bc -l)GB"
         fi
         if [ -n "$dsize" ]; then
            dsize="${dsize}_"
         else
            dsize="Unknown_size_"
         fi
	 # put serial #
	 serialno="$(cat /sys/block/$p/device/serial 2>/dev/null | sed -e "s/ //g")"
         if [ -n "$serialno" ]; then
           serialno="_${serialno}"
         else
           serialno="_No_disk_serial_no"
         fi
         # The result is like 8590MB_VMware_Virtual_IDE
         DEV_MODEL="${dsize}$(tr ' ' _ </proc/ide/$p/model)${serialno}"
       fi
       [ -z "$DEV_MODEL" ] && DEV_MODEL="No_description"
       ;;
     sd[a-z])
       # TODO: This method will fail in cciss device. Since it's like:
       # ls -alF /sys/block/cciss\!c0d0/
       # total 0
       # drwxr-xr-x  7 root root    0 2008-03-06 12:25 ./
       # drwxr-xr-x 28 root root    0 2008-03-06 11:41 ../
       # -r--r--r--  1 root root 4096 2008-03-06 11:41 capability
       # drwxr-xr-x  3 root root    0 2008-03-06 12:25 cciss!c0d0p1/
       # drwxr-xr-x  3 root root    0 2008-03-06 12:25 cciss!c0d0p2/
       # -r--r--r--  1 root root 4096 2008-03-06 11:41 dev
       # lrwxrwxrwx  1 root root    0 2008-03-06 11:41 device -> ../../devices/pci0000:00/0000:00:06.0/0000:17:00.0/
       # drwxr-xr-x  2 root root    0 2008-03-06 11:41 holders/
       # drwxr-xr-x  3 root root    0 2008-03-06 11:41 queue/
       # -r--r--r--  1 root root 4096 2008-03-06 11:41 range
       # -r--r--r--  1 root root 4096 2008-03-06 11:41 removable
       # -r--r--r--  1 root root 4096 2008-03-06 11:41 size
       # drwxr-xr-x  2 root root    0 2008-03-06 11:41 slaves/
       # -r--r--r--  1 root root 4096 2008-03-06 11:41 stat
       # lrwxrwxrwx  1 root root    0 2008-03-06 11:41 subsystem -> ../../block/
       # --w-------  1 root root 4096 2008-03-06 11:41 uevent
       #
       x="$(cat /sys/block/$p/device/model 2>/dev/null | sed -r -e "s/[[:space:]]+/ /g" | 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%\"*}
       [ -z "$x" ] && x="Unknown_model"
       # parted output Ex. Disk /dev/sda: 160GB // This is only for newer parted. For old parted (1.6.3 in redhat 9, nothing will be shown)
       # If it's cciss device, since we already made a link in /dev/, so here it won't be a problem.
       dsize="$(LC_ALL=C parted -s /dev/$p print 2>/dev/null | grep -E "^[[:space:]]*Disk[[:space:]]+/dev/" | awk -F":" '{print $2}' | sed -e "s/ //g")"
       if [ -z "$dsize" ]; then
        # Try the other way since parted will fail if the disk is not partitioned.
        dsize="$(LC_ALL=C echo "scale=1; $(cat /sys/block/$p/size 2>/dev/null)*512/1000.0^3" | bc -l)GB"
       fi
       if [ -n "$dsize" ]; then
          dsize="${dsize}_"
       else
          dsize="Unknown_size_"
       fi
       # Serial #
       #serialno="$(LC_ALL=C cat $lshw_disk_info | grep -A3 -Ewi "logical name: /dev/$p" | grep -i "serial: " | awk -F":" '{print $2}' | sed -e "s/ //g")"
       get_disk_serial_no $p
       if [ -n "$serialno" ]; then
	  serialno="_${serialno}"
       else
	  serialno="_No_disk_serial_no"
       fi
       # The result is like 8590MB_VMware_Virtual_IDE
       DEV_MODEL="${dsize}${x}${serialno}"
       # just in case if nothing found
       [ -z "$DEV_MODEL" ] && DEV_MODEL="No_description"
       ;;
     [hsv]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" 2>/dev/null)"
       part_type="$(LC_ALL=C ocs-get-part-info "/dev/$p" type filesystem 2>/dev/null)"
       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
       dsize="$(LC_ALL=C ocs-get-part-info /dev/$p size)"
       [ -n "$dsize" ] && dsize="${dsize}_"
       DEV_MODEL="${dsize}$(LC_ALL=C ocs-get-part-info /dev/$p filesystem)"
       # show more info (From which disk). The result is like:
       # 20GB_ext3(In_VMware_Virtual_IDE_Hard_Drive)
       # The disk is either hdx or sdx here.
       # hda1 -> hda
       dsk_=${p:0:3}
       if [ "$(cat /proc/ide/${dsk_}/media 2>/dev/null)" = "disk" ] ; then
         DEV_MODEL_EXT="$(tr ' ' _ </proc/ide/${dsk_}/model)"
       else
         x="$(cat /sys/block/${dsk_}/device/model 2>/dev/null | sed -r -e "s/[[:space:]]+/ /g" | 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_EXT=${x}
       fi
       [ -n "$DEV_MODEL_EXT" ] && DEV_MODEL="${DEV_MODEL}(In_${DEV_MODEL_EXT})"
       # Put serial #
       case "$dsk_" in
        hd[a-z]*)
         serialno="$(cat /sys/block/$dsk_/device/serial 2>/dev/null | sed -e "s/ //g")"
	 ;;
        sd[a-z]*)
         #serialno="$(LC_ALL=C cat $lshw_disk_info | grep -A3 -Ewi "logical name: /dev/$dsk_" | grep -i "serial: " | awk -F":" '{print $2}' | sed -e "s/ //g")"
         get_disk_serial_no $dsk_
	 ;;
       esac
       if [ -n "$serialno" ]; then
         serialno="_${serialno}"
       else
         serialno="_No_disk_serial_no"
       fi
       DEV_MODEL="${DEV_MODEL}${serialno}"
       ;;
  esac
} # end of get_disk_or_part_hardware_info
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 height
    local part_list hdtmp msg_not_mounted_dev_found TMP FILE dsk_ DEV_MODEL_EXT dsize ans_
    local ASK_INPUT=1   # init value 1 means we have to ask

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

    gen_proc_partitions_map_file

    # 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.
    # ///NOTE/// Do NOT sort them! Otherwise the partitions list will be like:
    # sda1 sda11 sda12 sda2 sda3...
    # XXX part_list="$(LC_ALL=C awk '/[hsv]d[a-z][0-9]+($| )/ { print $4; }' $partition_table | sort)"
    part_list="$(LC_ALL=C awk '/[hsv]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 input harddisk, not only one selection
	[ -z "$msg_choose_dev_to_what" ] && msg_choose_dev_to_what="$msg_choose_disks_to_save $msg_linux_disk_naming $msg_press_space_to_mark_selection"
	msg_not_mounted_dev_found="$msg_no_umounted_disks_found"
        dev_list="$(LC_ALL=C awk '/[hsv]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_press_space_to_mark_selection"
	msg_not_mounted_dev_found="$msg_no_umounted_parts_found"
        # ///NOTE/// Do NOT sort them! Otherwise the partitions list will be like:
        # sda1 sda11 sda12 sda2 sda3...
        # XXX dev_list=$(LC_ALL=C awk '/[hsv]d[a-z][0-9]+($| )/ { print $4; }' $partition_table | sort)
        dev_list="$(LC_ALL=C awk '/[hsv]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")"
          FILE="$(LC_ALL=C ocs-get-part-info "/dev/$p" type filesystem 2>/dev/null)"
          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=""
       #FILE="$(LC_ALL=C file -Ls "/dev/$p")"
       FILE="$(LC_ALL=C ocs-get-part-info "/dev/$p" type filesystem 2>/dev/null)"
       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
       get_disk_or_part_hardware_info "$p"
       [ -z "$DEV_MODEL" ] && DEV_MODEL="No_description"
       # If only 1 device, make it on as default, else let user choose (off).
       #echo "selection=$selection"
       case "$selection" in
         "radiolist"|"checklist")
           if [ "$NUMDEV" = 1 ]; then
             dev_chosen_def="on"
           else
             dev_chosen_def="off"
           fi
           ;;
         "menu")
           dev_chosen_def=""
           ;;
       esac
       HARDDEVS="$HARDDEVS $p $DEV_MODEL $dev_chosen_def"
    done
    # If large number of devices, we must allow it to be scrolled down.
    if [ "$NUMDEV" -lt "$MAX_DIALOG_HEIGHT" ]; then
       height="$NUMDEV"
    else
       height="$MAX_DIALOG_HEIGHT"
    fi
    #
    while [ "$ASK_INPUT" -eq 1 ]; do
      $DIA --backtitle "$msg_nchc_free_software_labs" --title "$msg_nchc_clonezilla | $msg_mode: $ocs_mode_prompt" \
        --$selection "$msg_choose_dev_to_what:" 0 0 $height $HARDDEVS \
      2> $TMP
      target_dev="$(cat $TMP)"
      if [ -z "$target_dev" ]; then
        $DIA --backtitle "$msg_nchc_free_software_labs" --title "$msg_nchc_clonezilla | $msg_mode: $ocs_mode_prompt" \
        --yesno "$msg_you_must_choose_a_dev $msg_do_u_want_to_do_it_again" 0 0 
         ans_="$?"
         case "$ans_" in
           0) # yes is chosen
              ASK_INPUT=1;;
           1) # no is chosen
              echo "$msg_program_stop!"
              [ -f "$TMP" ] && rm -f $TMP
              [ -f "$mounted_table" ] && rm -f $mounted_table
              [ -f "$partition_table" ] && rm -f $partition_table
              exit 1;;
         esac
      else
         ASK_INPUT=0
      fi
    done

    [ -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 | $msg_mode: $ocs_mode_prompt" \
       --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
} # end of get_existing_hda1_image
#
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="" ans_
   local ASK_IMGNAME=1
   local TMP="$(mktemp /tmp/ocs.XXXXXX)"

   echo -n "Searching for images..."
   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
     echo -n "."
     # only directory ..., not file
     [ ! -d "$imagedir/$file" ] && continue
     [ ! -f "$imagedir/$file/disk" ] && continue
     fileinfo="$(LC_ALL=C ls -l --time-style=+%Y-%m%d-%H%M $imagedir/$file/disk 2>/dev/null| awk '{ print $6; }')"
     fileinfo=$fileinfo"_"$(tr ' ' _ < $imagedir/$file/disk 2>/dev/null)
     filelist="$filelist $file $fileinfo"
     numfiles="$(LC_ALL=C expr $numfiles + 1)"
   done
   echo

   if [ "$numfiles" -gt 0 ]; then
     if [ "$numfiles" -lt "$MAX_DIALOG_HEIGHT" ]; then
       height="$numfiles"
     else
       height="$MAX_DIALOG_HEIGHT"
     fi
     while [ "$ASK_IMGNAME" -eq 1 ]; do
       $DIA \
         --backtitle "$msg_nchc_free_software_labs" \
         --title "$msg_nchc_clonezilla | $msg_mode: $ocs_mode_prompt" \
         --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 | $msg_mode: $ocs_mode_prompt" \
        --yesno "$msg_you_must_input_filename! $msg_do_u_want_to_do_it_again" 0 0
         ans_="$?"
         case "$ans_" in
           0) # yes is chosen
              ASK_IMGNAME=1;;
           1) # no is chosen
              echo "$msg_program_stop!"
              [ -f "$TMP" ] && rm -f $TMP
              exit 1;;
         esac
       else
         # Got the image name. Continue.
         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! Or make sure you did not try to restore an image saved from partition(s) to disk(s)! $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_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 action_mode="$2"
   local tgt_file="" ans_ action_des
   local ASK_IMGNAME=1
   local TMP="$(mktemp /tmp/ocs.XXXXXX)"
   case "$action_mode" in
     "check") action_des="$msg_choose_the_image_to_be_checked" ;;
           *) action_des="$msg_choose_the_image_to_restore" ;;
   esac

   echo -n "Searching for images..."
   trap "[ -f "$TMP" ] && rm -f $TMP" HUP INT QUIT TERM EXIT
   numfiles="$(LC_ALL=C ls $imagedir 2> /dev/null | wc -l)"
   numfiles="$(LC_ALL=C expr $numfiles + 0)"
   filelist=""
   numfiles=0
   for file in `ls $imagedir 2> /dev/null`; do
     echo -n "."
     # only directory ..., not file
     [ ! -d "$imagedir/$file" ] && continue
     [ ! -f "$imagedir/$file/parts" -a ! -f "$imagedir/$file/disk" ] && continue
     fileinfo="$(LC_ALL=C ls -ld --time-style=+%Y-%m%d-%H%M $imagedir/$file | awk '{ print $6;}')"
     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="$(LC_ALL=C expr $numfiles + 1)"
   done
   echo

   if [ $numfiles -gt 0 ]; then
     if [ $numfiles -lt $MAX_DIALOG_HEIGHT ]; then
       height="$numfiles"
     else
       height="$MAX_DIALOG_HEIGHT"
     fi
     while [ "$ASK_IMGNAME" -eq 1 ]; do
       $DIA \
       --backtitle "$msg_nchc_free_software_labs" \
       --title "$msg_nchc_clonezilla | $msg_mode: $ocs_mode_prompt" \
       --menu "$action_des:" 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 | $msg_mode: $ocs_mode_prompt" \
        --yesno "$msg_you_must_input_filename! $msg_do_u_want_to_do_it_again" 0 0
         ans_="$?"
         case "$ans_" in
           0) # yes is chosen
              ASK_IMGNAME=1;;
           1) # no is chosen
              echo "$msg_program_stop!"
              [ -f "$TMP" ] && rm -f $TMP
              exit 1;;
         esac
       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
     [ -f "$TMP" ] && rm -f $TMP
     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 NUMDISKS ans_
   local ASK_INPUT=1   # init value 1 means we have to ask
   local pt_tmp="" 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_INPUT" -eq 1 ]; do
       $DIA --backtitle "$msg_nchc_free_software_labs" --title  \
       "$msg_nchc_clonezilla | $msg_mode: $ocs_mode_prompt" --checklist "$msg_choose_the_disks_to_restore ($msg_press_space_to_mark_selection):" \
       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 | $msg_mode: $ocs_mode_prompt" \
        --yesno "$msg_you_must_input_partition! $msg_do_u_want_to_do_it_again" 0 0 
         ans_="$?"
         case "$ans_" in
           0) # yes is chosen
              ASK_INPUT=1;;
           1) # no is chosen
              echo "$msg_program_stop!"
              [ -f "$TMP" ] && rm -f $TMP
              exit 1;;
         esac
       else
         ASK_INPUT=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 request_mode="$4"
   local tgt_parts ans_
   local ASK_INPUT=1
   local pt_tmp="" TMP prompt_msg_part1

   # 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
     if [ "$request_mode" = "check" ]; then
        prompt_msg_part1="$msg_choose_the_parts_to_check"
     else
        prompt_msg_part1="$msg_choose_the_parts_to_restore"
     fi
     while [ "$ASK_INPUT" -eq 1 ]; do
       $DIA --backtitle "$msg_nchc_free_software_labs" --title  \
       "$msg_nchc_clonezilla | $msg_mode: $ocs_mode_prompt" --checklist "$prompt_msg_part1 $msg_linux_parts_MS_mapping ($msg_press_space_to_mark_selection)" \
       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 | $msg_mode: $ocs_mode_prompt" \
        --yesno "$msg_you_must_input_partition! $msg_do_u_want_to_do_it_again" 0 0 
         ans_="$?"
         case "$ans_" in
           0) # yes is chosen
              ASK_INPUT=1;;
           1) # no is chosen
              echo "$msg_program_stop!"
              [ -f "$TMP" ] && rm -f $TMP
              exit 1;;
         esac
       else
         ASK_INPUT=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.name 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 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 [ "$ocs_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\"..."
    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..."
    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
  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 $multi_broad_cast_prompt 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 $multi_broad_cast_prompt seed in this clonezilla server."
} # end of find_multicast_ethernet_port
#
clean_filesystem_header_in_partition() {
  # function to clean the filesystem header 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 header 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 rcode
  # 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)"
  if [ -z "$speed" ]; then
    rcode=1
    # show it with unit
    speed="N/A $space_used_unit/min"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
    echo "$msg_something_weng_wrong"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo -n "$msg_press_enter_to_continue..."
    read
  else
    # show it with unit
    speed="$speed $space_used_unit/min"
    rcode=0
  fi
  return $rcode
} # end of get ntfs restore statistics
get_partclone_image_info() {
  local report_info=$1
  local start_t=$2
  local end_t=$3
  local space_used_number space_used_unit rcode
  # 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 partclone is like:
  # Device size:  4001 MB
  # Space in use: 955 MB
  # Block size: 4096 Byte
  # Used block count: 232996
  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)"
  if [ -z "$speed" ]; then
    rcode=1
    # show it with unit
    speed="N/A $space_used_unit/min"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
    echo "$msg_something_weng_wrong"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo -n "$msg_press_enter_to_continue..."
    read
  else
    # show it with unit
    speed="$speed $space_used_unit/min"
    rcode=0
  fi
  return $rcode
} # end of get_partclone_image_info
#
unicast_restore_by_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;"
} # end of unicast_restore_by_partimage
#
unicast_restore_by_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="$(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_prefix 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 $ntfsclone_progress_option $ntfsclone_restore_extra_opt_def --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;"
} # end of unicast_restore_by_ntfsclone
#
unicast_restore_by_partclone() {
  local file_ fs_ partclone_img_info_tmp file_basename
  # First, we find the filesystem 
  file_="$(unalias ls &>/dev/null; ls $target_d/$img_file.*-img* 2>/dev/null | sort | head -n 1)"
  file_basename="$(basename ${file_})"
  if [ -n "${file_}" ]; then
    if [ -n "$(echo $file_basename | grep -Eo -- "-ptcl-img")" ]; then
      # new format, image file is like: sda1.ext4-ptcl-img.gz, sda1.ext4-ptcl-img.gz.aa
      fs_="$(echo $file_basename | sed -e "s/^$img_file\.//g" -e "s/-ptcl-img.*//g")"
    else
      # old format, image file is like: sda2.hfsp-img.aa  sda2.hfsp-img.ab  sda2.hfsp-img.ac
      fs_="$(echo $file_basename | sed -e "s/^$img_file\.//g" -e "s/-img.*//g")"
    fi
  fi
  if [ -z "${fs_}" ]; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
    echo "The file system can not be decided in function unicast_restore_by_partclone!!!"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo "$msg_program_stop!"
    exit 1
  fi
 
  if [ -n "$(echo "$file_basename" | grep -Eo -- "-ptcl-img")" ]; then
    # New format
    if [ -n "$(echo "$file_basename" | grep -Eo -- "-ptcl-img.*.aa")" ]; then
      # New format with image split, e.g. sda1.ext4-ptcl-img.gz.aa
      get_image_cat_zip_cmd ${file_basename}
      # e.g. sda1.ext4-ptcl-img.gz.aa -> sda1.ext4-ptcl-img.gz.*
      img_file_prefix="$(echo ${file_basename} | sed -e "s/\.aa$//").*"
    else
      # New format with image not split, e.g. sda1.ext4-ptcl-img.gz
      get_image_cat_zip_cmd ${file_basename}
      # The file is NOT split, so the file name is just like "sda1.ext4-ptcl-img.gz" only, no "."
      img_file_prefix="${file_basename}"
    fi
  else
    # Old format
    if [ -f "$target_d/$img_file.${fs_}-img.aa" ]; then
      # Old format with image split
      get_image_cat_zip_cmd $target_d/$img_file.${fs_}-img.aa
      # The files are split, like hda1.00, hda1.01, so we have to add "."
      img_file_prefix="$img_file.${fs_}-img.*"
    else
      # Old format with image not split
      get_image_cat_zip_cmd $target_d/$img_file.${fs_}-img
      # The file is NOT split, so the file name is just like "hda1" only, no "."
      img_file_prefix="$img_file.${fs_}-img"
    fi
  fi
  # assign partclone_img_info tmp file.
  # TODO: partclone_progress
  case "$partclone_progress" in
    "image_dir")
       IP="$(get-ip-link-2-drbl-srv)"
       partclone_img_info_tmp="$target_d/restoring-${IP}-${img_file}-`date +%Y%m%d%H%M`" ;;
    *)
       partclone_img_info_tmp="/var/log/partclone.log" ;;
  esac
  [ -f "$partclone_img_info_tmp" ] && rm -f $partclone_img_info_tmp
  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_prefix 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)"
  # partclone_hfsp does not put any header, so just cat them
  # //NOTE// Here we force to use LC_ALL=C for partclone since we need to use get_partclone_image_info to parse the log file to get the rate. Only the keyword in English is supported in get_partclone_image_info.
  ( for img in $target_d/$img_file_prefix; do
      cat $img
    done
  ) | \
  $unzip_stdin_cmd | \
  LC_ALL=C partclone.${fs_} $PARTCLONE_RESTORE_OPT -L $partclone_img_info_tmp -s - -r -o $part
  rc="$?"
  end_time="$(date +%s%N)"
  sync_and_active_exec_files
  get_partclone_image_info $partclone_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)"
  # prepare statistic report
  conv_return_code_to_human_read $rc
  report_msg="$report_msg $part, $clone_status, $space_used, $time_elapsed_in_min mins;"
} # end of unicast_restore_by_partclone
unicast_restore_by_dd() {
  local partclone_img_info_tmp
  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

  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..."
  if [ "$S2S_IMAGE_PROG_IN_OCS" = "dd" ]; then
    echo "$msg_status_report_is_very_primitive..."
  fi
  [ "$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_prefix corrupted ?" 
  [ "$(root_over_nfs)" = "yes" ] && echo "* Network connection and NFS service."
  [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL

  case "$S2S_IMAGE_PROG_IN_OCS" in
  dd)
    dd_img_info_tmp="$(mktemp /tmp/dd_info.XXXXXX)"
    # 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
    ;;
  partclone)
    # assign partclone_img_info tmp file.
    # TODO: partclone_progress
    case "$partclone_progress" in
      "image_dir")
         IP="$(get-ip-link-2-drbl-srv)"
         partclone_img_info_tmp="$target_d/restoring-${IP}-${img_file}-`date +%Y%m%d%H%M`" ;;
      *)
         partclone_img_info_tmp="/var/log/partclone.log" ;;
    esac
    [ -f "$partclone_img_info_tmp" ] && rm -f $partclone_img_info_tmp
    echo $msg_delimiter_star_line

    start_time="$(date +%s%N)"
    # partclone_hfsp does not put any header, so just cat them
    # //NOTE// Here we force to use LC_ALL=C for partclone since we need to use get_partclone_image_info to parse the log file to get the rate. Only the keyword in English is supported in get_partclone_image_info.
    ( for img in $target_d/$img_file_prefix; do
        cat $img
      done
    ) | \
    $unzip_stdin_cmd | \
    LC_ALL=C partclone.dd $PARTCLONE_RESTORE_OPT -L $partclone_img_info_tmp -s - -r -o $part
    rc="$?"
    end_time="$(date +%s%N)"
    sync_and_active_exec_files
    get_partclone_image_info $partclone_img_info_tmp $start_time $end_time
    ;;
  esac
  # For unicast, no preparation time, it's accurate enough.
  echo ">>> Time elapsed: $time_elapsed secs (~ $time_elapsed_in_min mins)"
  [ -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;"
} # end of unicast_restore_by_dd
#
multicast_restore_by_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;"
} # end of multicast_restore_by_partimage
#
multicast_restore_by_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="$(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 the image from server..."
  [ "$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 $ntfsclone_progress_option $ntfsclone_restore_extra_opt_def --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;"
} # end of multicast_restore_by_ntfsclone
#
multicast_restore_by_partclone() {
  local file_ fs_ partclone_img_info_tmp IP file_basename
  # First, we find the filesystem 
  # Image files are like: sda2.hfsp-img.aa  sda2.hfsp-img.ab  sda2.hfsp-img.ac
  file_="$(unalias ls &>/dev/null; ls $target_d/$img_file.*-img* 2>/dev/null | sort | head -n 1)"
  file_basename="$(basename ${file_})"
  if [ -n "${file_}" ]; then
    if [ -n "$(echo $file_basename | grep -Eo -- "-ptcl-img")" ]; then
      # new format, image file is like: sda1.ext4-ptcl-img.gz, sda1.ext4-ptcl-img.gz.aa
      fs_="$(echo $file_basename | sed -e "s/^$img_file\.//g" -e "s/-ptcl-img.*//g")"
    else
      # old format, image file is like: sda2.hfsp-img.aa  sda2.hfsp-img.ab  sda2.hfsp-img.ac
      fs_="$(echo $file_basename | sed -e "s/^$img_file\.//g" -e "s/-img.*//g")"
    fi
  fi
  if [ -z "${fs_}" ]; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
    echo "The file system can not be decided in function multicast_restore_by_partclone!!!"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo "$msg_program_stop!"
    exit 1
  fi

  # get $unzip_stdin_cmd from image file
  # No matter it split or not, ${file_basename} or ${file_} can be .aa file or the single image itself. We only need one file to test.
  # Since server will feed the image, therefore here we do not have to prepare a variable "$img_file_prefix" like that in function unicast_restore_by_partclone
  if [ -n "$(echo "$file_basename" | grep -Eo -- "-ptcl-img")" ]; then
    # New format, decide by file name, so we do not have to include dir name. It's better to do so since maybe user will assing a image name (dir name) with the "magic" keyword in a coincidence
    get_image_cat_zip_cmd ${file_basename}
  else
    # Old format, since it's tested by command "file -Ls", we need the absolute path so file command can access it.
    get_image_cat_zip_cmd ${file_}
  fi

  # assign partclone tmp file.
  case "$partclone_progress" in
  "image_dir")
     IP="$(get-ip-link-2-drbl-srv)"
     partclone_img_info_tmp="$target_d/restoring-${IP}-${img_file}-`date +%Y%m%d%H%M`" ;;
  *)
     partclone_img_info_tmp="/var/log/partclone.log" ;;
  esac
  [ -f "$partclone_img_info_tmp" ] && rm -f $partclone_img_info_tmp
  echo $msg_delimiter_star_line
  echo "Waiting for the image from server..."
  [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
  echo "If this action fails or hangs, check:"
  echo "* Is the saved image $target_d/$file_basename* 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)"
  # //NOTE// Here we force to use LC_ALL=C for partclone since we need to use get_partclone_image_info to parse the log file to get the rate. Only the keyword in English is supported in get_partclone_image_info.
  $udpcast_rec_cmd 2>$udpcast_stderr | $unzip_stdin_cmd | \
  LC_ALL=C partclone.${fs_} $PARTCLONE_RESTORE_OPT -L $partclone_img_info_tmp -s - -r -o $part
  rc="$?"
  end_time="$(date +%s%N)"
  sync_and_active_exec_files
  get_partclone_image_info $partclone_img_info_tmp $start_time $end_time
  echo ">>> Time elapsed: $time_elapsed secs (~ $time_elapsed_in_min mins)"
  # For multicast, with preparation time, it's no so accurate.
  echo ">>> NOTE: The elapsed time may include some preparation time, so it is not very accurate."
  # prepare statistic report
  conv_return_code_to_human_read $rc
  report_msg="$report_msg $part, $clone_status, $space_used, $time_elapsed_in_min mins;"
} # end of multicast_restore_by_partclone
#
multicast_restore_by_dd() {
  local partclone_img_info_tmp
  # 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

  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..."
  if [ "$S2S_IMAGE_PROG_IN_OCS" = "dd" ]; then
    echo "$msg_status_report_is_very_primitive..."
  fi
  [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
  echo $msg_delimiter_star_line
  echo "Waiting for the image from server..."
  [ "$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

  case "$S2S_IMAGE_PROG_IN_OCS" in
  dd)
    # Assign dd tmp file
    dd_img_info_tmp="$(mktemp /tmp/dd_info.XXXXXX)"
    # 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
    ;;
  partclone)
    # assign partclone_img_info tmp file.
    # TODO: partclone_progress
    case "$partclone_progress" in
      "image_dir")
         IP="$(get-ip-link-2-drbl-srv)"
         partclone_img_info_tmp="$target_d/restoring-${IP}-${img_file}-`date +%Y%m%d%H%M`" ;;
      *)
         partclone_img_info_tmp="/var/log/partclone.log" ;;
    esac
    [ -f "$partclone_img_info_tmp" ] && rm -f $partclone_img_info_tmp
    echo $msg_delimiter_star_line

    start_time="$(date +%s%N)"
    # //NOTE// Here we force to use LC_ALL=C for partclone since we need to use get_partclone_image_info to parse the log file to get the rate. Only the keyword in English is supported in get_partclone_image_info.
    $udpcast_rec_cmd 2>$udpcast_stderr | $unzip_stdin_cmd | \
    LC_ALL=C partclone.dd $PARTCLONE_RESTORE_OPT -L $partclone_img_info_tmp -s - -r -o $part
    rc="$?"
    end_time="$(date +%s%N)"
    sync_and_active_exec_files
    get_partclone_image_info $partclone_img_info_tmp $start_time $end_time
    ;;
  esac
  echo ">>> Time elapsed: $time_elapsed secs (~ $time_elapsed_in_min mins)"
  # 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;"
} # end of multicast_restore_by_dd
# 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 hdtmp pt_type
  # 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
  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
    clean_filesystem_header_in_partition $part
    unicast_restore_by_partimage
  elif [ -f "$target_d/$img_file.ntfs-img" -o -f "$target_d/$img_file.ntfs-img.aa" ]; then
    # The saved image is from ntfsclone
    clean_filesystem_header_in_partition $part
    unicast_restore_by_ntfsclone
  elif is_partclone_image $target_d $img_file; then
    # The saved image is from partclone
    clean_filesystem_header_in_partition $part
    unicast_restore_by_partclone
  elif [ -f "$target_d/$img_file.dd-img" -o -f "$target_d/$img_file.dd-img.aa" ]; then
    # The saved image is from dd
    clean_filesystem_header_in_partition $part
    unicast_restore_by_dd
  fi
  echo "Finished unicast restoring image ${target_d##*/} to $part."
  sleep 1
  # Informing kernel that the OS that partition table has changed since for BSD system, the "partition" (slices) info exists in the partition headers
  hdtmp="$(get_disk_from_part $part)"
  pt_type="$(get_partition_table_type_from_disk $hdtmp)"
  inform_kernel_partition_table_changed $pt_type $hdtmp
  echo $msg_delimiter_star_line
  return $rc
} # end of do_unicast_stdin_restore
#
do_multicast_udpcast_restore() {
  # part is like: /dev/hda1
  # port is a 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 hdtmp pt_type
  # 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 $udp_receiver_extra_opt_default --nokbd --mcast-all-addr $MULTICAST_ALL_ADDR --portbase $port $TIME_TO_LIVE_OPT" 
  echo $msg_delimiter_star_line
  echo "Starting to restore 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
    clean_filesystem_header_in_partition $part
    multicast_restore_by_partimage
  elif [ -f "$target_d/$img_file.ntfs-img" -o -f "$target_d/$img_file.ntfs-img.aa" ]; then
    # The saved image is from ntfsclone
    clean_filesystem_header_in_partition $part
    multicast_restore_by_ntfsclone
  elif is_partclone_image $target_d $img_file; then
    # The saved image is from partclone
    clean_filesystem_header_in_partition $part
    multicast_restore_by_partclone
  elif [ -f "$target_d/$img_file.dd-img" -o -f "$target_d/$img_file.dd-img.aa" ]; then
    # The saved image is from dd
    clean_filesystem_header_in_partition $part
    multicast_restore_by_dd
  fi
  echo "Finished restoring image ${target_d##*/} to $part."
  echo $msg_delimiter_star_line
  # Informing kernel that the OS that partition table has changed since for BSD system, the "partition" (slices) info exists in the partition headers
  hdtmp="$(get_disk_from_part $part)"
  pt_type="$(get_partition_table_type_from_disk $hdtmp)"
  inform_kernel_partition_table_changed $pt_type $hdtmp
  
  # 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
#
multicast_feed_img_for_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 image of $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
} # end of multicast_feed_img_for_partimage
#
multicast_feed_img_for_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
} # end of multicast_feed_img_for_ntfsclone
#
multicast_feed_img_for_partclone() {
  local file_ fs_ file_basename
  # Variable "$img_file" is like "sda1"
  # First, we find the filesystem 
  # Image files are like: sda1.hfsp-img.aa  sda1.hfsp-img.ab  sda1.hfsp-img.ac
  file_="$(unalias ls &>/dev/null; ls $imagedir/$target_d/$img_file.*-img* 2>/dev/null | sort | head -n 1)"
  file_basename="$(basename ${file_})"
  if [ -n "${file_}" ]; then
    if [ -n "$(echo $file_basename | grep -Eo -- "-ptcl-img")" ]; then
      # new format, image file is like: sda1.ext4-ptcl-img.gz, sda1.ext4-ptcl-img.gz.aa
      fs_="$(echo $file_basename | sed -e "s/^$img_file\.//g" -e "s/-ptcl-img.*//g")"
    else
      # old format, image file is like: sda2.hfsp-img.aa  sda2.hfsp-img.ab  sda2.hfsp-img.ac
      fs_="$(echo $file_basename | sed -e "s/^$img_file\.//g" -e "s/-img.*//g")"
    fi
  fi
  if [ -z "${fs_}" ]; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
    echo "The file system can not be decided in function multicast_feed_img_for_partclone!!!"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo "$msg_program_stop!"
    exit 1
  fi

  # here we do not have to run get_image_cat_zip_cmd since partclone image does not contain its own special header, so we do not have to uncompress and strip that.
  if [ -n "$(echo "$file_basename" | grep -Eo -- "-ptcl-img")" ]; then
    # New format
    if [ -n "$(echo "$file_basename" | grep -Eo -- "-ptcl-img.*.aa")" ]; then
      # New format with image split, e.g. sda1.ext4-ptcl-img.gz.aa
      # e.g. sda1.ext4-ptcl-img.gz.aa -> sda1.ext4-ptcl-img.gz.*
      img_file_prefix="$(echo ${file_basename} | sed -e "s/\.aa$//").*"
    else
      # New format with image not split, e.g. sda1.ext4-ptcl-img.gz
      # The file is NOT split, so the file name is just like "sda1.ext4-ptcl-img.gz" only, no "."
      img_file_prefix="${file_basename}"
    fi
  else
    # Old format
    if [ -f "$imagedir/$target_d/$img_file.${fs_}-img.aa" ]; then
      # The files are split, like hda1.00, hda1.01, so we have to add "."
      img_file_prefix="$img_file.${fs_}-img.*"
    else
      # The file is NOT split, so the file name is just like "hda1" only, no "."
      img_file_prefix="$img_file.${fs_}-img"
    fi
  fi
  # we use the original format (do not uncompress) to save the bandwidth
  # partclone 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
} # end of multicast_feed_img_for_partclone
#
multicast_feed_img_for_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
} # multicast_feed_img_for_dd
#
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"  # e.g. hda1
  local part="$3"      # e.g. hda1
  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
    multicast_feed_img_for_partimage
  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
    multicast_feed_img_for_ntfsclone
  elif is_partclone_image $imagedir/$target_d $img_file; then
    # The saved image is from partclone
    multicast_feed_img_for_partclone
  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
    multicast_feed_img_for_dd
  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 -af $ocs_log_dir/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="" time_tag
  # 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 we partition is listed in lvm_vg_dev.list, process LVM later. //NOTE// LVM might use Id=83 instead of 8e, so we can not parse it based on Id.
    if [ -n "$(grep -Ew "$partition" $imagedir/$tgt_dir/lvm_vg_dev.list 2>/dev/null)" ]; then
      # We have to do restore LVM (PV/VG/LV) together, not follow every partition. 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.
  # 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. Run resize
  # 5. Notify ocsmgrd its job is done (time necessary: $NOTIFY_OCS_SERVER_TIME_LIMIT secs)
  # For 0-4, we estimate it as 120 secs totally.
  time_to_pause_before_mail="$((120+$SLEEP_TIME_AFTER_PART_CLONED+$NOTIFY_OCS_SERVER_TIME_LIMIT))"
  (
   if [ -n "$mail_client" ]; then
     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
  ) &
  # Keep the log. We have to wait like what mail_clonezilla_log_to_root does
  (
    sleep $time_to_pause_before_mail
    time_tag="$(LC_ALL=C head -n 1 $ocs_log_dir/clonezilla-jobs.log | cut -d "," -f 1 | sed -r -e "s/[[:space:]]//g")"
    cp -a $ocs_log_dir/clonezilla-jobs.log $ocs_log_dir/jobs-$time_tag.log
  ) &
  # 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 $multi_broad_cast_prompt 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-clean-dhcpd-leases &>/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 ocs-get-part-info:
  # if [ -n "$(file -Ls $part | grep -i "LVM" 2>/dev/null)" ]; then
  if [ -n "$(ocs-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_ rc_save_logv
  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
  ocs-lvm2-start
  echo "Parsing LVM layout..."
  LC_ALL=C 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

  if [ ! -e "$PV_PARSE_CONF" ]; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "///WARNING/// The LVM setting is not found!"
    echo "Unable to save LVM image!!!"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
  fi
  
  # 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.
  LC_ALL=C 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
  vgcfg_tmp="$(mktemp /tmp/vgcfg_tmp.XXXXXX)"
  echo "Saving the VG config... "
  while read vg dev uuid; do
    # Here we avoid to run vgcfgbackup with output on the $target_dir, since due to some reason, if $target_dir is on NFS, when vgcfgbackup output the file on NFS, the lockd might fail to lock it (vgcfgbackup will use the output file path to creating temp file and try to lock it)...
    rm -f $vgcfg_tmp
    LC_ALL=C vgcfgbackup -f $vgcfg_tmp $vg 2>/dev/null
    cp -a $vgcfg_tmp $target_dir/lvm_$vg.conf
  done < $PV_PARSE_CONF
  [ -e "$vgcfg_tmp" ] && rm -f $vgcfg_tmp
  echo "Checking if the VG config was saved correctly... "
  while read vg dev uuid; do
    if [ ! -e "$target_dir/lvm_$vg.conf" ]; then
      [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
      echo "$target_dir/lvm_$vg.conf NOT created! Not volume group of LVM was saved!"
      [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
      echo "$msg_program_stop!"
      exit 1
    fi
  done < $PV_PARSE_CONF
  echo "done!"
  [ ! -f "$PV_PARSE_CONF" ] && exit 1
  [ ! -f "$LOGV_PARSE_CONF" ] && exit 1
  # To avoid the stdin/stdout in ntfsclone/partimage/partclone (image_save $lv $target_dir $fn) conflict with "while read", here we use different file descriptor  (3)
  exec 3< $LOGV_PARSE_CONF
  while read -u 3 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
   rc_save_logv="$(($rc_save_logv + $?))"
   echo $msg_delimiter_star_line
  done
  exec 3<&-
  return $rc_save_logv
} # 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 lvm_tmp
  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_fullpath/lvm_vg_dev.list"
  LOGV_PARSE_CONF="$target_dir_fullpath/lvm_logv.list"
  [ ! -f "$PV_PARSE_CONF" ] && exit 1
  [ ! -f "$LOGV_PARSE_CONF" ] && exit 1
  #
  ocs-lvm2-stop
  # 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

  #
  lvm_tmp="$(mktemp -d /tmp/lvm_tmp.XXXXXX)"
  # create PV first
  echo "Creating the PV... "
  while read vg dev uuid; do
    cp -f $target_dir_fullpath/lvm_$vg.conf $lvm_tmp/  # Since mmap function maybe not available on remote disk (Ex. image is on samba disk). We have to copy the config file to local disk. Thanks to Gerald HERMANT <ghermant _at_ astrel fr> for reporting this bugs.
    #pvcreate -ff --yes --uuid $uuid $dev
    pvcreate -ff --yes --uuid $uuid --zero y --restorefile $lvm_tmp/lvm_$vg.conf $dev
    rm -f $lvm_tmp/lvm_$vg.conf
  done < $PV_PARSE_CONF
  echo "done!"
  
  # Restore the vg conf
  echo "Restoring the VG config... "
  while read vg dev uuid; do
    cp -f $target_dir_fullpath/lvm_$vg.conf $lvm_tmp/  # Since mmap function maybe not available on remote disk (Ex. image is on samba disk). We have to copy the config file to local disk. Thanks to Gerald HERMANT <ghermant _at_ astrel fr> for reporting this bugs.
    # vgcfgrestore -f $target_dir_fullpath/lvm_$vg.conf $vg 2>/dev/null
    vgcfgrestore -f $lvm_tmp/lvm_$vg.conf $vg 2>/dev/null
    rm -f $lvm_tmp/lvm_$vg.conf
  done < $PV_PARSE_CONF
  [ -d "$lvm_tmp" -a -n "$lvm_tmp" ] && rmdir $lvm_tmp 
  echo "done!"
  #
  ocs-lvm2-start
  
  # To avoid the stdin/stdout in ntfsclone/partimage/partclone ( do_unicast_stdin_restore $target_dir_fullpath $fn $lv; do_multicast_udpcast_restore $target_dir_fullpath $fn $lv) conflict with "while read", here we use different file descriptor  (3)
  exec 3< $LOGV_PARSE_CONF
  while read -u 3 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 info, create it by:"
        # read LABEL, UUID info for $partition if swappt-${fn}.info exists
        uuid_opt=""
        label_opt=""
        if [ -e "$target_dir_fullpath/swappt-${fn}.info" ]; then
          UUID=""
          LABEL=""
          . "$target_dir_fullpath/swappt-${fn}.info"
          [ -n "$UUID" ] && uuid_opt="-U $UUID"
          [ -n "$LABEL" ] && label_opt="-L $LABEL"
        fi
        get_mkswap_uuid_cmd
        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_fullpath $fn $lv
       ;;
     multicast)
       do_multicast_udpcast_restore $target_dir_fullpath $fn $lv
       ;;
   esac
  done
  exec 3<&-
} # 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/broadcast 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
  
  # To avoid the stdin/stdout in ntfsclone/partimage/partclone (udp_send_part_img $target_dir $fn $lv) conflict with "while read", here we use different file descriptor  (3)
  exec 3< $LOGV_PARSE_CONF
  while read -u 3 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")"
   # Skip feeding swap
   case "$fs" in 
     *[Ss][Ww][Aa][Pp]*) continue;; 
   esac
   if [ -z "$(unalias ls 2>/dev/null; ls $imagedir/$target_dir/$fn* 2>/dev/null)" ]; then
     [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
     echo "$imagedir/$target_dir/$fn* was not found! Something went wrong! The restored OS might be uncompleted!"
     [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
     echo -n "$msg_press_enter_to_continue..."
     read
     continue
   fi
   udp_send_part_img $target_dir $fn $lv
  done
  exec 3<&-
} # end of feed_LVM_multicast
#
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 a global variable.
  if [ "$diskless_client_os" = "clonezilla-live" ]; then
    remove_opt_in_pxelinux_cfg_block Clonezilla-live ocs_live_run
  else
    remove_opt_in_pxelinux_cfg_block clonezilla ocs_opt
  fi
  # 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
  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-gethostip $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"
      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-gen-ssi-files -t $ssi_ip
    fi
    if [ "$clean_dhcpd_lease_in_clone_box_mode" = "yes" ]; then
      echo "Clean the dhcpd leases..."
      drbl-clean-dhcpd-leases &>/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.lock 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.name or http://drbl.nchc.org.tw for more details."
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
  fi

  # Warning if range option is used in 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 a 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 [ "$ocs_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_rc_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="$multi_broad_cast_prompt restore $tgt_img_menu to disk $tgt_dev_menu"
       ;;
    "multicast_restoreparts")
       ocs_menu_label="$multi_broad_cast_prompt 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.
  if [ "$diskless_client_os" = "clonezilla-live" ]; then
    force_pxe_clients_boot_label Clonezilla-live "Clonezilla: $ocs_menu_label"
  else
    force_pxe_clients_boot_label clonezilla "Clonezilla: $ocs_menu_label"
  fi
  # 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
  ocsmgrd &
  ocs_log_file="$(LC_ALL=C grep -F "joblogfile =>" `which 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

  #
  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.
      # Thanks to Orgad Shaneh to provide this 1st-disk patch.
      # Ref: https://sourceforge.net/tracker/?func=detail&atid=671650&aid=2817447&group_id=115473
      if [ "$target_hd" = "1st-disk" ]; then
        target_hd="$(< $imagedir/$target_dir/disk)"
      fi
      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
      if [ -z "$target_parts" ]; then
        [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
        echo "No target partitions were found from image $target_dir!"
	echo "Failed to start $multi_broad_cast_prompt clone service for image $target_dir!"
        [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
        echo "$msg_program_stop!"
        exit 1
      fi
      feed_multicast_restoreparts "$target_dir" "$target_parts" &
      ;;

    "multicast_restoreparts")
      # Let feed_multicast_restoreparts do the real job
      #feed_multicast_restoreparts "$option" &
      if [ -z "$target_parts" ]; then
        [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
        echo "No target partitions were found from image $target_dir!"
	echo "Failed to start $multi_broad_cast_prompt clone service for image $target_dir!"
        [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
        echo "$msg_program_stop!"
        exit 1
      fi
      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}')"
      tune-clientdir-opt -l en --no-stop-ocs -t $ssi_ip -z yes $regen_opt
    else
      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-clean-dhcpd-leases &>/dev/null
    fi
    echo $msg_delimiter_star_line
  fi

  # We do not have to run drbl-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_rc_srv() {
  # Usage:
  # prep_client_inittab_rc_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 rc_task=$2
  local rc_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
    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 [ "$rc_task" = "select_in_client" ]; then
     # reset these variables, we just want to run clonezilla 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"
     rc_opt=""
  fi

  case "$ocs_client_trig_type" in
    ocs-run.param)
      # NOTE! This mode will _NOT_ work in Clonezilla box, and _NOT_ in 
      # clonezilla_live mode, 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.
      if [ "$diskless_client_os" = "clonezilla-live" ]; then
        remove_opt_in_pxelinux_cfg_block Clonezilla-live ocs_live_run
      else
        remove_opt_in_pxelinux_cfg_block clonezilla ocs_opt
      fi
      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 -l en_US.UTF-8 $OCS_OPT $rc_task $rc_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
      if [ "$diskless_client_os" = "clonezilla-live" ]; then
        case "$rc_task" in
          *select_in_client*)
           # remove select_in_client in the option so the clonezilla can run.
            rc_task="$(echo $rc_task | sed -e "s/select_in_client//g")"
            add_opt_in_pxelinux_cfg_block Clonezilla-live ocs_live_run "clonezilla -l en_US.UTF-8 $OCS_OPT $rc_task $rc_opt"
            ;;
          *)
            add_opt_in_pxelinux_cfg_block Clonezilla-live ocs_live_run "ocs-sr -l en_US.UTF-8 $OCS_OPT $rc_task $rc_opt"
            ;;
        esac
      else
        # For nfsroot's client, we process select_in_client in /etc/rc1.d/s19ocs-run
        add_opt_in_pxelinux_cfg_block clonezilla ocs_opt "-l en_US.UTF-8 $OCS_OPT $rc_task $rc_opt"
      fi
      ;;
    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 -l en_US.UTF-8 $OCS_OPT $rc_task $rc_opt > $drblroot/$node_ip/etc/ocs/ocs-run.param
      fi
      if [ "$diskless_client_os" = "clonezilla-live" ]; then
        case "$rc_task" in
          *select_in_client*)
           # remove select_in_client in the option so the clonezilla can run.
            rc_task="$(echo $rc_task | sed -e "s/select_in_client//g")"
            add_opt_in_pxelinux_cfg_block Clonezilla-live ocs_live_run "clonezilla -l en_US.UTF-8 $OCS_OPT $rc_task $rc_opt"
            ;;
          *)
            add_opt_in_pxelinux_cfg_block Clonezilla-live ocs_live_run "ocs-sr -l en_US.UTF-8 $OCS_OPT $rc_task $rc_opt"
            ;;
        esac
      else
        # For nfsroot's client, we process select_in_client in /etc/rc1.d/s19ocs-run
        add_opt_in_pxelinux_cfg_block clonezilla ocs_opt "-l en_US.UTF-8 $OCS_OPT $rc_task $rc_opt"
      fi
      ;;
    *)
      [ "$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_rc_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="$(get-nic-devs)"
  for device in $netdevices; do
    ip="$(drbl-get-ipadd $device)"
    [ -z "$ip" ] && continue
    # ocs_server might be assigned by boot parameter
    [ -z "$ocs_server" ] && ocs_server="$(drbl-get-nfsserver $ip)"
    # If ocsserver is not found, then this NIC is not for DRBL, try another one.
    [ -z "$ocs_server" ] && continue
    mac="$(drbl-get-macadd $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... "
  ocs-socket -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
#
get_target_dir_name_when_saving() {
  # target_dir is a global variable
  local ANS_TMP
  local ASK_IMG_NAME=1
  ANS_TMP=`mktemp /tmp/ocs_ans.XXXXXX`
  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 | $msg_mode: $ocs_mode_prompt" \
      --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.
} # end of get_target_dir_name_when_saving
#
get_target_hd_name_from_local_machine() {
  # target_hd is a global variable
  local input_dev_prompt="$1"
  local input_selection_opt="$2"
  local ANS_TMP
  local ASK_DEV_NAME=1
  ANS_TMP=`mktemp /tmp/ocs_ans.XXXXXX`
  trap "[ -f "$ANS_TMP" ] && rm -f $ANS_TMP" HUP INT QUIT TERM EXIT
  [ -z "$input_selection_opt" ] && input_selection_opt="default"
  while [ "$ASK_DEV_NAME" -ne 0 ]; do
    get_input_dev_name $ANS_TMP harddisk $input_selection_opt default "$input_dev_prompt"
    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
} # end of get_target_hd_name_from_local_machine
#
get_target_parts_name_from_local_machine() {
  # target_parts is a global variable
  local input_dev_prompt="$1"
  local input_selection_opt="$2"
  local ANS_TMP
  local ASK_DEV_NAME=1
  ANS_TMP=`mktemp /tmp/ocs_ans.XXXXXX`
  trap "[ -f "$ANS_TMP" ] && rm -f $ANS_TMP" HUP INT QUIT TERM EXIT
  [ -z "$input_selection_opt" ] && input_selection_opt="default"
  while [ "$ASK_DEV_NAME" -ne 0 ]; do
    get_input_dev_name $ANS_TMP partition $input_selection_opt default "$input_dev_prompt"
    # 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 \")"
    [ -n "$target_parts" ] && ASK_DEV_NAME=0
  done
  [ -f "$ANS_TMP" ] && rm -f $ANS_TMP
  check_input_partition $target_parts
} # end of get_target_parts_name_from_local_machine
#
get_target_dir_name_when_restoring_disk() {
  # target_dir and ocsroot are gloable variables
  local ANS_TMP
  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/
} # end of get_target_dir_name_when_restoring_disk
#
get_target_dir_name_when_restoring_parts() {
  # target_dir and ocsroot are gloable variables
  local ANS_TMP
  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 restore
  # the return name will be only one image name.
  # Note! We won't add leading $ocsroot in $target_dir, because when task_restoreparts do the real job later, it will add that.
  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/
} # end of get_target_dir_name_when_restoring_parts
#
get_target_dir_name_when_checking_img_restorable() {
  # target_dir and ocsroot are gloable variables
  local ANS_TMP
  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 check
  # the return name will be only one image name.
  # Note! We won't add leading $ocsroot in $target_dir, because when task_restoreparts do the real job later, it will add that.
  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
} # end of get_target_dir_name_when_checking_img_restorable
#
rm_tmp_cnvt_img() {
  # img_cnvt_flag is a global variable
  [ -z "$img_cnvt_flag" ] && return 10
  if [ "$img_cnvt_flag" -ge "1" ]; then
    if [ -d "$ocsroot/${target_dir}" -a \
         -n "$(echo $target_dir | grep -i "tmp-cnvted")" ]; then
      rm -rf "$ocsroot/${target_dir}" 
    fi
  fi
} # end of rm_tmp_cnvt_img
#
create_temp_image_for_different_target_dev_name_if_necessary() {
  # If the target_hd does not equal to the one in target_dir, create a temp image by create-ocs-tmp-img
  # TODO 200806:
  # (1) How about if target_hd is multi harddisks ? -> Won't supported. Too complicated.
  # (2) Add an option in create-ocs-tmp-img to assign the ocsroot
  # (3) Avoid multiple unicast clones to convert the same image.
  #img_cnvt_flag="0"

  # Check if target disk name exists in the image.
  tgtdisk_exist_flag=0
  for i in $target_hd; do
    if [ -z "$(echo $dsksname_from_img | grep -iEw $i)" ]; then
      echo "Target disk $i does not exist in the image saved from disk(s) \"$dsksname_from_img\"."
      tgtdisk_exist_flag=1
      break
    fi
  done
  if [ "$tgtdisk_exist_flag" -eq 1 ]; then
    # Some cases not supported:
    # (1) Multicast clone.
    # (2) Unicast clone with batch mode (Since maybe there will be more than 1 restoration, and they will create the same temp image, which will be in a mess.
    # (3) source hardisk number is >= 2
    if [ -n "$port" ]; then
      [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
      echo "$msg_create_cvrt_img_for_different_not_supported_in_multicast_mode $msg_only_same_disk_name_work_try_cnvt_ocs_dev_instead"
      [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
      echo "$msg_program_stop!"
      exit 1
    fi
    if [ "$src_dsk_no" -ge 2 ]; then
      [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
      echo "$msg_create_cvrt_img_for_more_than_2_disks_not_supported $msg_only_same_disk_name_work_try_cnvt_ocs_dev_instead"
      [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
      echo "$msg_program_stop!"
      exit 1
    fi
    is_spawned_by_drbl_ocs $ocs_ppid
    rc=$?
    if [ "$rc" -eq 0 ]; then
      echo "$msg_create_cvrt_img_for_clonezilla_server_edition_client_not_supported $msg_only_same_disk_name_work_try_cnvt_ocs_dev_instead"
      echo "$msg_program_stop!"
      exit 1
    fi

    # Creating a temp converted image:
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "Creating a tmp Clonezilla image \"${target_dir}-tmp-cnvted\" based on the image \"$target_dir\" so that we can restore the image $target_dir (was saved from $dsksname_from_img) to $target_hd..."
    echo "$msg_restored_OS_change_fstab_grub_conf"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    if [ "$ocs_batch_mode" != "on" ]; then
      echo -n "$msg_press_enter_to_continue..."
      read
    fi
    if [ -d "$ocsroot/${target_dir}-tmp-cnvted" -a \
         -n "${target_dir}-tmp-cnvted" ]; then
      rm -rf "$ocsroot/${target_dir}-tmp-cnvted" 
    fi
    create-ocs-tmp-img $target_dir ${target_dir}-tmp-cnvted $dsksname_from_img $target_hd
    # Change the ocsroot to /tmp now since create-ocs-tmp-img will create the image in /tmp
    ocsroot="/tmp"
    target_dir="${target_dir}-tmp-cnvted"
    img_cnvt_flag="$(($img_cnvt_flag + 1))"
  fi
} # end of create_temp_image_for_different_target_dev_name_if_necessary

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

  screen_not_blank
  active_proc_partitions
  #######################################################
  # 2003/05/12: ask user about target_dir and target_hd #
  #######################################################
  if [ "$target_dir" = "ask_user" ]; then
    get_target_dir_name_when_saving
  fi
   
  check_existing_target_image "$ocsroot/$target_dir"

  if [ "$target_hd" = "ask_user" ]; then
    get_target_hd_name_from_local_machine "$msg_local_source_disk \n$msg_linux_disk_naming $msg_press_space_to_mark_selection"
  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=""
    echo "Searching for data partition(s)..."
    get_known_partition_proc_format $idisk data
    target_parts="$target_parts $BACKUP_DEVS"

    BACKUP_DEVS=""
    echo "Searching for swap partition(s)..."
    get_known_partition_proc_format $idisk swap
    target_swap_parts="$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. However, we do not generate md5sums, sha1sums in task_saveparts, since there are some files will be created later. We will generate them in the end of this function.
  gen_md5sum_status="$gen_md5sum"; gen_sha1sum_status="$gen_sha1sum" # save values
  gen_md5sum="no"; gen_sha1sum="no"
  task_saveparts $target_dir "$target_parts"
  rc_task_savedisk="$(($rc_task_savedisk + $?))"

  gen_md5sum="$gen_md5sum_status"; gen_sha1sum="$gen_sha1sum_status" # restore values

  # save swap partition UUID/LABEL if it exists.
  echo $msg_delimiter_star_line
  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=$?
  if [ "$rc" -gt 0 ]; then
    show_warning_about_write_failed $ocsroot/$target_dir
  else
    gen_md5_sha1_sums $ocsroot/$target_dir
  fi
  rc_task_savedisk="$(($rc_task_savedisk + $?))"

  if [ "$rc_task_savedisk" -eq 0 ]; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "$msg_this_image_saved_successfully: $target_dir"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
  else
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
    echo "$msg_this_image_not_saved_correctly"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
  fi
  return $rc_task_savedisk
} # end of task_savedisk

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

  # 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
    get_target_dir_name_when_saving
  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
    get_target_parts_name_from_local_machine
  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.
  dev_to_be_saved_shown_in_warning=""
  if [ -n "$(echo "$ocs_sr_type" | grep -E "disk")" ]; then
    # To save a disk, we list all the existing partition(s), too.
    dev_to_be_saved_shown_in_warning="$target_hd $target_parts"
  elif [ -n "$(echo "$ocs_sr_type" | grep -E "parts")" ]; then
    # To save partition(s) only
    dev_to_be_saved_shown_in_warning="$target_parts"
  fi
  # get_dev_model_shown will give $dev_model_shown
  get_dev_model_shown "$dev_to_be_saved_shown_in_warning"
  countdown_or_confirm_before_save "$target_dir" 

  # 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..."
    # No matter it's MBR or GPT, we use sfdisk to dump partition table since if dual boot (Mac OS, Linux) on Mac book, this partition table is required for Linux normally.
    LC_ALL=C sfdisk -d /dev/$ihd > $target_dir/${ihd}-pt.sf 2>/dev/null
    RETVAL="$?"
    echo "RETVAL=$RETVAL"
    clean_cylinder_boundary_warning $target_dir/${ihd}-pt.sf
    output_HD_CHS $ihd $target_dir
    # Output the info from parted, it contains more info.
    LC_ALL=C parted -s /dev/$ihd unit s print > $target_dir/${ihd}-pt.parted
    # From the output file of parted, we can decide if the partition is gpt or mbr
    if [ -n "$(grep -iE "^Partition Table:" $target_dir/${ihd}-pt.parted 2>/dev/null | grep -iE "gpt")" ]; then
      # GPT, output the GPT data. Ref: http://en.wikipedia.org/wiki/GUID_Partition_Table
      # Legacy MBR (LBA 0), Partition table header (LBA 1), Partition entries (LBA 2-33)
      echo $msg_delimiter_star_line
      echo "Saving the primary GPT of $ihd as $target_dir/${ihd}-gpt-1st by dd..."
      LC_ALL=C dd if=/dev/$ihd of=$target_dir/${ihd}-gpt-1st bs=512 count=34
      echo $msg_delimiter_star_line
      # There is a redundant GPT (secondary GPT) in the last block: LBA -34, -33... -1. Ref: http://developer.apple.com/technotes/tn2006/tn2166.html#SECLASTBLOCK
      # We need to get the total size of disk so that we can skip and dump the last block:
      # The output of 'parted -s /dev/sda unit s print' is like:
      # --------------------
      # Disk /dev/hda: 16777215s
      # Sector size (logical/physical): 512B/512B
      # Partition Table: gpt
      # 
      # Number  Start     End        Size       File system  Name     Flags  
      #  1      34s       409640s    409607s    fat32        primary  msftres
      #  2      409641s   4316406s   3906766s   ext2         primary         
      #  3      4316407s  15625000s  11308594s  reiserfs     primary         
      # --------------------
      echo "Saving the secondary GPT of $ihd as $target_dir/${ihd}-gpt-2nd by dd..."
      disk_size_sec="$(LC_ALL=C grep -E "^Disk /dev/" $target_dir/${ihd}-pt.parted | awk -F":" '{print $2}' | sed -e "s/s$//g")"
      to_skip="$((${disk_size_sec}-33+1))"
      LC_ALL=C dd if=/dev/$ihd of=$target_dir/${ihd}-gpt-2nd skip=${to_skip} bs=512 count=33
      echo $msg_delimiter_star_line
      if type sgdisk &>/dev/null; then
        echo "Saving the GPT of $ihd as $target_dir/${ihd}-gpt.gdisk by gdisk..."
        LC_ALL=C sgdisk -b $target_dir/${ihd}-gpt.gdisk /dev/$ihd
        echo $msg_delimiter_star_line
      fi
    else
      # Not GPT type of disk, we can save the hidden data between MBR and 1st partition. Maybe the data is useless, maybe it's useful. Since some of the recovery programs put data on that.
      echo $msg_delimiter_star_line
      if [ "$clone_hidden_data" = "yes" ]; then
        save_hidden_data_after_MBR $ihd $target_dir/${ihd}-hidden-data-after-mbr
      fi
      echo $msg_delimiter_star_line
    fi
    if [ "$RETVAL" -ne 0 ]; then
      sfdisk_fail_process
      exit 1
    fi
  done
  echo "done!"

  # save the mbr 
  for ihd in $target_hd; do
    # Ref: http://en.wikipedia.org/wiki/Master_boot_record
    # Master Boot Record (MBR) is the 512-byte boot sector:
    # 446 bytes (executable code area) + 64 bytes (table of primary partitions) + 2 bytes (MBR signature; # 0xAA55) = 512 bytes.
    # However, some people also call executable code area (first 446 bytes in MBR) as MBR.
    echo "Saving the MBR data 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
    rc_saveparts="$(($rc_saveparts + $?))"
  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"
    rc_saveparts="$(($rc_saveparts + $?))"
  fi

  # Dump hardware info
  dump_hardware_software_info $target_dir

  echo "$target_parts" > $target_dir/parts
  rc=$?
  if [ "$rc" -gt 0 ]; then
    show_warning_about_write_failed $target_dir
  else
    gen_md5_sha1_sums $target_dir
  fi
  rc_saveparts="$(($rc_saveparts + $rc))"
  return $rc_saveparts
  # TODO: show error message if failing; check image?
} # end of task_saveparts()

# task "restoredisk" 
# parameter:
#   $1 target_image
#   $2 target_hd
task_restoredisk() {
  local target_dir="$1"
  local target_hd="$2"
  # we use the port to identify it's unicast or multicast.
  local port="$3"
  local dsksname_from_img src_dsk_no dia_sel_opt tgt_dsk_no partition_table
  # img_cnvt_flag is a global variable

  if [ "$target_dir" = "ask_user" ]; then
    get_target_dir_name_when_restoring_disk
  fi
  check_input_target_image "$ocsroot/$target_dir"

  #
  dsksname_from_img="$(cat $ocsroot/$target_dir/disk | sed -e "s/ *$//g")"
  src_dsk_no="$(echo $dsksname_from_img | wc -w)"

  case "$target_hd" in
   "ask_user")
    if [ "$src_dsk_no" -eq 1 ]; then
      dia_sel_opt="menu"
    else
      dia_sel_opt="checklist"
    fi
    get_target_hd_name_from_local_machine "$msg_choose_the_disks_to_restore \n$msg_linux_disk_naming $msg_press_space_to_mark_selection" $dia_sel_opt
    ;;
   "1st-disk")
    # By using 1st-disk, we can avoid the disk name type is hdx or sdx.
    # Ref: http://sourceforge.net/tracker2/?func=detail&aid=2115612&group_id=115473&atid=671653
    # Thanks to Orgad Shaneh.
    # //NOTE// Now this method only works for unicast. Not for multicast/broadcast.
    gen_proc_partitions_map_file
    target_hd="$(LC_ALL=C awk '/[hsv]d[a-z]($| )/ { print $4; }' $partition_table | sort | head -n 1)"
    [ -f "$partition_table" ] && rm -f $partition_table
    ;;
  esac
  
  # Check MD5SUMS, SHA1SUMS before the image is converted in case
  check_md5_sha1_sums "$ocsroot/$target_dir"

  #
  create_temp_image_for_different_target_dev_name_if_necessary

  # 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.
  # //NOTE// file parts already contained all the available partitions name.
  for ihd in $target_hd; do
    for partition in `cat $ocsroot/$target_dir/parts`; do
      if [ -n "$(echo "$partition" | grep -iE "$ihd")" ]; then
        # Ex. hda2 is for hda, so when hda is chosen, we will include hda2 as target partition.
        target_parts="$target_parts $partition"
      fi
    done
  done

  # Let task_restoreparts do the real job
  # However, we do not check md5sums, sha1sums in task_restoreparts, since maybe there are some files (like create-ocs-tmp-img will convert hda img to sda...) will be created later. We have to check them before the image was converted.
  check_md5sum="no"; check_sha1sum="no"
  task_restoreparts "$target_dir" "$target_parts" "$port"
} # end of task_restoredisk

# 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
  # For multicast, we check md5/sha1 sums on DRBL server, not clients. Force to set check_md5sum and check_sha1sum as no
  check_md5sum="no"; check_sha1sum="no"
  # 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"
} # end of task_multicast_restoredisk

# 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"
  local port="$3" # we use the port to identify it's unicast or multicast.
  local dev_to_be_overwrite parts_included target_dir_fullpath source_hd
  local target_hd=""
  local thd_tmp=""
  local source_hd=""
  local shd_tmp=""
  local continue_choice
  local ptype

  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

  if [ "$target_dir" = "ask_user" ]; then
    get_target_dir_name_when_restoring_parts
  fi

  #
  source_parts_no="$(cat $ocsroot/$target_dir/parts | sed -e "s/ *$//g" | wc -w | awk '{print $1}')"

  #
  if [ "$target_parts" = "ask_user" ]; then
    if [ "$source_parts_no" -eq 1 ]; then
      dia_sel_opt="menu"
    else
      dia_sel_opt="checklist"
    fi
    get_target_parts_name_from_local_machine "$msg_choose_the_parts_to_restore \n$msg_linux_parts_MS_mapping" $dia_sel_opt
  fi

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

  # Find the source hd
  for jpart in `cat $ocsroot/$target_dir/parts`; do
    shd_tmp=${jpart:0:3}
    if [ -z "$source_hd" ]; then
      source_hd="$shd_tmp"
    elif [ -z "$(echo $source_hd | grep -Ew "$shd_tmp" 2>/dev/null)" ]; then
      source_hd="$source_hd $shd_tmp"
    fi
  done
  dsksname_from_img="$source_hd"
  src_dsk_no="$(echo $dsksname_from_img | wc -w)"

  # Check MD5SUMS, SHA1SUMS
  check_md5_sha1_sums "$ocsroot/$target_dir"

  #
  create_temp_image_for_different_target_dev_name_if_necessary

  # Use $target_dir_fullpath as the absolute path, i.e. put leading $ocsroot, since from now on we need the full path dir to access them.
  target_dir_fullpath="$ocsroot/$target_dir"
  check_input_target_image "$target_dir_fullpath"

  # 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
  dev_to_be_overwrite=""
  if [ -n "$(echo "$ocs_sr_type" | grep -E "parts")" -a "$create_part" = "no" ]; then
    # To restore partition(s) only
    dev_to_be_overwrite="$target_parts"
  else
    # To restore a disk, we will erase the whole data in the disk, so list partition(s), too 
    # Find the existing partition(s) in the system.
    gen_proc_partitions_map_file
    parts_included="$(grep -Eo "${target_hd}[[:digit:]]+" $partition_table)"
    [ -f "$partition_table" ] && rm -f $partition_table
    dev_to_be_overwrite="$target_hd $parts_included"
  fi
  # get_dev_model_shown will give $dev_model_shown
  # Only show this when not in batch mode
  [ "$ocs_batch_mode" != "on" ] && get_dev_model_shown "$dev_to_be_overwrite" 
  countdown_or_confirm_before_restore "$target_dir_fullpath" "$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 "$ocs_batch_mode" != "on" ]; then
    echo "This program is not started by clonezilla server."
    echo "$msg_the_following_step_is_to_restore_img_to_disk_part: \"$target_dir_fullpath\" -> \"$target_hd ($target_parts)\""
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    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
    echo "$msg_uppercase_Warning!!! $msg_uppercase_Warning!!! $msg_uppercase_Warning!!!"
    if [ -n "$dev_model_shown" ]; then
      echo "$msg_uppercase_Warning! $msg_all_data_in_dev_will_be_overwritten:"
      echo $msg_delimiter_star_line
      echo -e "$dev_model_shown"
      echo $msg_delimiter_star_line
    fi
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    continue_choice=""
    while [ -z "$continue_choice" ]; do
      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])
         continue_choice="yes"
         echo "$msg_ok_let_do_it!"
         ;;
       n|N|[nN][oO])
         continue_choice="no"
         rm_tmp_cnvt_img
         echo "$msg_program_stop!"
         exit 1
         ;;
       *)
         [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
         echo "$msg_you_have_to_enter_yes_or_no!"
         [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
         echo $msg_delimiter_star_line
         ;;
      esac
    done
  fi

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

  # Install MBR which displays a failure message on boot. This is for fault tolerance. If all the partitions are cloned successfully, the correcting MBR will be restored later. For more info, please check https://sourceforge.net/tracker/?func=detail&atid=671653&aid=2793248&group_id=115473
  if [ "$restore_mbr" = "yes" ]; then
    for ihd in $target_hd; do
      cat $DRBL_SCRIPT_PATH/pkg/misc/fail-mbr.bin > /dev/$ihd
    done
  fi

  if [ "$clone_hidden_data" = "yes" ]; then
    for ihd in $target_hd; do
      # Restore the hidden data between MBR and 1st partition. Maybe it's useless, maybe it's useful. Since some of the recovery programs put data on that.
      # Exclude GPT type of disk. We only deal with MBR for this hidden data
      [ -n "$(LC_ALL=C parted -s /dev/$ihd print | grep -iE "^Partition Table:" | grep -iE "gpt")" ] && continue
        echo $msg_delimiter_star_line
        if [ -e "$target_dir_fullpath/${ihd}-hidden-data-after-mbr" ]; then
          restore_hidden_data_after_MBR $ihd $target_dir_fullpath/${ihd}-hidden-data-after-mbr
        else
          echo "File $target_dir_fullpath/${ihd}-hidden-data-after-mbr not found!"
          echo "Skip restoring hidden data after MBR for disk $ihd."
        fi
      echo $msg_delimiter_star_line
    done
  fi

  do_LVM_restore="no"
  for partition in $target_parts; do
    # hda1 -> hda
    hd_tmp="${partition:0:3}"
    # If we partition is listed in lvm_vg_dev.list, process LVM later. //NOTE// LVM might use Id=83 instead of 8e, so we can not parse it based on Id.
     if [ -n "$(grep -Ew "$partition" $target_dir_fullpath/lvm_vg_dev.list 2>/dev/null)" ]; then
      # We have to do restore LVM (PV/VG/LV) together, not follow every partition. Do not process LVM partition here, we will process LVM partition and its LV together, later
      do_LVM_restore="yes"
      continue
    fi
    echo "Restoring partition /dev/$partition..."
    # From partimage 0.6.6, batch mode won't stop if checkInodeForDevice is not run. Therefore comment it.
    # checkInodeForDevice /dev/$partition
    # Before we start to clone, check if kernel can find /dev/$partition
    check_specify_part_exists $partition
    case "$net_mode" in 
     "unicast")
        do_unicast_stdin_restore $target_dir_fullpath $partition /dev/$partition
        rc="$?"
        ;;
     "multicast")
        do_multicast_udpcast_restore $target_dir_fullpath $partition /dev/$partition
        rc="$?"
        ;;
    esac
    if [ $rc -gt 0 ]; then
       [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
       echo "Failed to restore partition image file $target_dir_fullpath/${partition}* to /dev/$partition! Maybe this image is corrupt or there is no $target_dir_fullpath/${partition}*! If you are restoring the image of partition to different partition, check the FAQ on Clonezilla website for how to make it. $msg_press_enter_to_continue..."
       [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
       read
    fi
  done

  # swap partition
  # Only when restoredisk (including unicast & multicast restoredisk, i.e. restoredisk or multicast_restoredisk), we will recreate swap partition. If the swap partition is encrypted, and we re-create it, the restored OS will complain that.
  if [ -n "$(echo "$ocs_sr_type" | grep -Ei "restoredisk")" ]; then
    for ihd in $target_hd; do
      # Get the partition type from parted output
      if [ -n "$(LC_ALL=C grep -iE "^Partition Table:" $target_dir_fullpath/${ihd}-pt.parted | grep -iE "gpt")" ]; then
        ptype="gpt"
      else
        ptype="mbr"
      fi
      case "$ptype" in
       mbr)
        echo "Finding swap partition(s) in MBR table $target_dir_fullpath/${ihd}-pt.sf..."
        for partition in `get_swap_partition_sf_format $target_dir_fullpath/${ihd}-pt.sf`; do
          echo "Creating swap partition /dev/$partition..."
          check_specify_part_exists $partition
          echo "Found the swap partition /dev/$partition info in the image dir, create it by:"
          # read LABEL, UUID info for $partition if swappt-$partition.info exists
          uuid_opt=""
          label_opt=""
          if [ -e "$target_dir_fullpath/swappt-$partition.info" ]; then
            UUID=""
            LABEL=""
            . "$target_dir_fullpath/swappt-$partition.info"
            [ -n "$UUID" ] && uuid_opt="-U $UUID"
            [ -n "$LABEL" ] && label_opt="-L $LABEL"
          fi
          get_mkswap_uuid_cmd
          echo "${MKSWAP_UUID} $label_opt $uuid_opt /dev/$partition"
          ${MKSWAP_UUID} $label_opt $uuid_opt /dev/$partition
          echo $msg_delimiter_star_line
        done
        ;;
       gpt)
        echo "Finding swap partition(s) in GPT table $target_dir_fullpath/${ihd}-pt.parted..."
        for partition in `get_swap_partition_parted_format $target_dir_fullpath/${ihd}-pt.parted`; do
          echo "Creating swap partition /dev/$partition..."
          check_specify_part_exists $partition
          echo "Found the swap partition /dev/$partition info in the image dir, create it by:"
          # read LABEL, UUID info for $partition if swappt-$partition.info exists
          uuid_opt=""
          label_opt=""
          if [ -e "$target_dir_fullpath/swappt-$partition.info" ]; then
            UUID=""
            LABEL=""
            . "$target_dir_fullpath/swappt-$partition.info"
            [ -n "$UUID" ] && uuid_opt="-U $UUID"
            [ -n "$LABEL" ] && label_opt="-L $LABEL"
          fi
          get_mkswap_uuid_cmd
          echo "${MKSWAP_UUID} $label_opt $uuid_opt /dev/$partition"
          ${MKSWAP_UUID} $label_opt $uuid_opt /dev/$partition
          echo $msg_delimiter_star_line
        done
        ;;
      esac
    done
  fi

  # 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

  # Reinstall executable code area (first 446 bytes in MBR)
  for ihd in $target_hd; do
    if [ "$restore_mbr" = "yes" ]; then
      # Ref: http://en.wikipedia.org/wiki/Master_boot_record
      # Master Boot Record (MBR) is the 512-byte boot sector:
      # 446 bytes (executable code area) + 64 bytes (table of primary partitions) + 2 bytes (MBR signature; # 0xAA55) = 512 bytes.
      # However, some people also call executable code area (first 446 bytes in MBR) as MBR.
      if [ "$restore_prebuild_mbr" = "yes" ]; then
        # For M$ Windows system, sometimes it will fail if we restore the mbr from the one we saved (Ex. hda-mbr). Another option is to use mbr.bin from syslinux
	echo -n "Restoring the mbr.bin from syslinux to /dev/$ihd... "
        cat $pxelinux_binsrc_dir/mbr.bin > /dev/$ihd
        echo "done!"
      else
        echo -n "Restoring the first 446 bytes of MBR data, i.e. executable code area, for $ihd... "
        dd if=$target_dir_fullpath/${ihd}-mbr of=/dev/$ihd bs=446 count=1 &>/dev/null
        echo "done!"
      fi
      echo $msg_delimiter_star_line
    fi
  done

  # 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"
      ocs-resize-part --batch $hd_tmp $part_no_tmp
      echo $msg_delimiter_star_line
    done
  fi

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

  # Reloc ntfs boot partition
  if [ "$change_ntfs_boot_chs" = "on" ]; then
    run_ntfsreloc_part -p "$target_parts" $ntfs_boot_partition
    echo $msg_delimiter_star_line
  fi

  # 
  # Reinstall whole MBR (512 bytes)
  if [ "$dump_mbr_in_the_end" = "yes" ]; then
    turn_off_swap_and_LVM2
    sleep 1
    for ihd in $target_hd; do
      # Ref: http://en.wikipedia.org/wiki/Master_boot_record
      # Master Boot Record (MBR) is the 512-byte boot sector:
      # 446 bytes (executable code area) + 64 bytes (table of primary partitions) + 2 bytes (MBR signature; # 0xAA55) = 512 bytes.
      # However, some people also call executable code area (first 446 bytes in MBR) as MBR.
      echo -n "Restoring the MBR data (512 bytes), i.e. executable code area + table of primary partitions + MBR signature, for $ihd... "
      dd if=$target_dir_fullpath/${ihd}-mbr of=/dev/$ihd bs=512 count=1 &>/dev/null
      echo "done!"
      echo -n "Making kernel re-read the partition table of /dev/$ihd... "
      sfdisk -R /dev/$ihd
      echo "done!"
      echo "The partition table of /dev/$ihd is:"
      fdisk -l /dev/$ihd
      echo $msg_delimiter_star_line
    done
  fi

  # Remove the temp converted image. //NOTE// Now target_dir is *-tmp-cnvted. img_cnvt_flag is just for insurance.
  rm_tmp_cnvt_img

} # 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"
  # For multicast, we check md5/sha1 sums on DRBL server, not clients. Force to set check_md5sum and check_sha1sum as no
  # To do backward compatability, we still keep task_multicast_restoreparts function, but let task_restoreparts do the real job.
  check_md5sum="no"; check_sha1sum="no"
  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/broadcast 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/broadcast 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

    # Check MD5SUMS, SHA1SUMS
    check_md5_sha1_sums "$imagedir/$target_image"

    #
    [ -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 restore
             # 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/broadcast 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 restore
           # 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 restore
             # 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/broadcast 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 restore
           # 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

    # Check MD5SUMS, SHA1SUMS
    check_md5_sha1_sums "$imagedir/$target_image"

    #
    [ -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_rc_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.
  if [ "$diskless_client_os" = "clonezilla-live" ]; then
    force_pxe_clients_boot_label Clonezilla-live "Clonezilla: choose save or restore later"
  else
    force_pxe_clients_boot_label clonezilla "Clonezilla: choose save or restore later"
  fi
  # 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
  ocsmgrd &
  ocs_log_file="$(LC_ALL=C grep -F "joblogfile =>" `which 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_opt_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 opt_name="$2" # can be ocs_opt (for NFSRoot-based Clonezilla client) or ocs_live_run (for Clonezilla-live-based client)
  local opt_content="$3"
  local tag_found
  [ -z "$label" ] && echo "No label in add_opt_in_pxelinux_cfg_block!" && exit 1
  [ -z "$opt_name" ] && echo "opt_name NOT specified in add_opt_in_pxelinux_cfg_block! Abort!" && exit 1
  [ -z "$opt_content" ] && echo "opt_content NOT specified in add_opt_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}')"
  if [ -z "$begin_line" -o -z "$end_line" ]; then
    echo "No \"$label\" found in $PXE_CONF, skip adding that."
    return 1
  fi
  tag_found="$(LC_ALL=C head -n $end_line $PXE_CONF | tail -n $(($end_line-$begin_line)) | grep -Eiw "^[[:space:]]*append[[:space:]]*.*[[:space:]]+$opt_name=.*([[: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 $opt_name=\"$opt_content\"|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.
    # If tag if found, 2 cases: (1) with "" (e.g. ocs_daemonon="ssh", (2) without "" (e.g. ocs_daemonon=ssh)
    if [ -n "$(echo $tag_found | grep -iE "$opt_name=\"[^\"]*\"")" ]; then
      sub_menu_label_cmd="if ($begin_line..$end_line) {s|(^[[:space:]]*append[[:space:]]*.*)[[:space:]]+$opt_name=\"[^\"]*\"([[:space:]]+.*$)|\$1 $opt_name=\"$opt_content\"\$2|i}"
    else
      sub_menu_label_cmd="if ($begin_line..$end_line) {s|(^[[:space:]]*append[[:space:]]*.*)[[:space:]]+$opt_name=[^[:space:]]+([[:space:]]+.*$)|\$1 $opt_name=\"$opt_content\"\$2|i}"
    fi
    LC_ALL=C perl -pi -e "$sub_menu_label_cmd" $PXE_CONF
  fi
} # end of add_opt_in_pxelinux_cfg_block
#
remove_opt_in_pxelinux_cfg_block() {
  local label="$1"
  local opt_name="$2" # can be ocs_opt (for NFSRoot-based Clonezilla client) or ocs_live_run (for Clonezilla-live-based client)
  local tag_found
  [ -z "$label" ] && echo "No label in remove_opt_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}')"
  if [ -z "$begin_line" -o -z "$end_line" ]; then
    echo "No \"$label\" found in $PXE_CONF, skip removing that."
    return 1
  fi
  # 2 types of boot parameters, 1st one is with "=" (e.g. ocs_opt=...), the 2nd one is without "=" (e.g. quiet). Here first we test if the one with "=" is found or not.
  tag_found="$(LC_ALL=C head -n $end_line $PXE_CONF | tail -n $(($end_line-$begin_line)) | grep -Eiw "^[[:space:]]*append[[:space:]]*.*[[:space:]]+$opt_name=.*([[:space:]]+|$)")"
  if [ -n "$tag_found" ]; then
    # 1st type, i.e. with "=". 2 types of them. 
    # (1) with ", i.e. ocs_live_run="ocs-live-general"
    # (2) without ", i.e. ocs_live_run=ocs-live-general

    # (1) with ", i.e. ocs_live_run="ocs-live-general"
    sub_menu_label_cmd="if ($begin_line..$end_line) {s|(^[[:space:]]*append[[:space:]]+.*)[[:space:]]+$opt_name=\"[^\"]*\"([[:space:]]+.*)|\$1\$2|i}"
    LC_ALL=C perl -pi -e "$sub_menu_label_cmd" $PXE_CONF
    # (2) without ", i.e. ocs_live_run=ocs-live-general
    sub_menu_label_cmd="if ($begin_line..$end_line) {s|(^[[:space:]]*append[[:space:]]+.*)[[:space:]]+$opt_name=[^[:space:]]+([[:space:]]+.*)|\$1\$2|i}"
    LC_ALL=C perl -pi -e "$sub_menu_label_cmd" $PXE_CONF
  else
    # 2nd type, i.e. without "=", or nothing. Even if nothing, perl won't fail. Therefore just run it.
    sub_menu_label_cmd="if ($begin_line..$end_line) {s|(^[[:space:]]*append[[:space:]]+.*)[[:space:]]+$opt_name([[:space:]]+.*)|\$1\$2|i}"
    LC_ALL=C perl -pi -e "$sub_menu_label_cmd" $PXE_CONF
  fi
} # end of remove_opt_in_pxelinux_cfg_block()
#
check_ocs_input_params(){
  local more_compress_progs
  # 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
  
  more_compress_progs="lzma xz pxz lzip plzip"
  for i in $more_compress_progs; do
    eval iz=\$i
    if [ "$IMG_CLONE_CMP" = "$iz -c" ]; then
      if ! type $iz &>/dev/null; then
        [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
        echo "$iz is NOT found in this server. The default compression program (gzip) will be used."
        [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
        IMG_CLONE_CMP="gzip -c $extra_gzip_opt"
      fi
      
      if [ "$USE_PARTCLONE" != "yes" ]; then
        # Since new file name format is only for partclone, if user, say, choose partimage + $iz, the created partition image file is like: hda1.aa, hda1.ab... If we use "file -Ls hda1.aa", it will only show "data", and the restoration will fail since there is no way to know the compression format. Either file name or file magic are not available.
        [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
        echo "$iz must be used with partclone in Clonezilla. The default compression program (gzip) will be used!"
        [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
        IMG_CLONE_CMP="gzip -c $extra_gzip_opt"
      fi
    fi
  done
  
  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 the MBR of the disk containing partition GRUB_PARTITION with root grub directory in the same GRUB_PARTITION when restoration finishs, GRUB_PARTITION can be one of \"/dev/hda1\", \"/dev/hda2\"... or \"auto\" (\"auto\" will let clonezilla detect the grub root partition automatically). If \"auto\" is assigned, it will work if grub partition and root partition are not in the same partition."
    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. 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 " -k1,                     Create partition table in the target disk proportionally."
    echo " -k2,                     Enter command line prompt to create partition table manually before restoring image."
    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 " -e1, --change-geometry NTFS-BOOT-PARTITION Force to change the CHS (cylinders, heads, sectors) value of NTFS boot partitoin after image is restored. NTFS-BOOT-PARTITION can be one of \"/dev/hda1\", \"/dev/hda2\"... or \"auto\" (\"auto\" will let clonezilla detect the NTFS boot partition automatically)"
    echo " -e2, --load-geometry-from-edd  Force to use the CHS (cylinders, heads, sectors) from EDD (Enhanced Disk Device) when creating partition table by 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 " -j1, --dump-mbr-in-the-end  Use dd to dump the MBR (total 512 bytes, i.e. 446 bytes (executable code area) + 64 bytes (table of primary partitions) + 2 bytes (MBR signature; # 0xAA55) = 512 bytes) after disk image was restored. This is an insurance for some hard drive has different numbers of cylinder, head and sector between image was saved and restored."
    echo " -j2, --clone-hidden-data  Use dd to clone the image of the data between MBR (1st sector, i.e. 512 bytes) and 1st partition, which might be useful for some recovery tool."
    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"
    echo " -cm, --check-md5sum   Check the MD5 checksum for the image. To use this option, you must enable -gm|--gen-md5sum option when the image is saved. Note! It might take a lot of time to check if the image size is large."
    echo " -cs, --check-sha1sum  Check the SHA1 checksum for the image. To use this option, you must enable -gs|--gen-sha1sum option when the image is saved. Note! It might take a lot of time to check if the image size is large."
} # end of USAGE_common_restore
#
USAGE_common_save(){
    # Common save usage help messages for both drbl-ocs and ocs-sr
    echo " -j2, --clone-hidden-data  Use dd to clone the image of the data between MBR (1st sector, i.e. 512 bytes) and 1st partition, which might be useful for some recovery tool."
    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 " -z1p, --smp-gzip-compress  Compress using parallel gzip program (pigz) when saving: fast and small image file, good for multi-core or multi-CPU machine"
    echo " -z2, --bz2-compress      Compress using bzip2 when saving: slow but smallest image file"
    echo " -z2p, --smp-bzip2-compress  Compress using parallel bzip2 program ($parallel_bzip2_prog) when saving: faster and smallest image file, good for multi-core or multi-CPU machine"
    echo " -z3, --lzo-compress      Compress using lzop when saving: similar to the size by gzip, but faster than gzip."
    echo " -z4, --lzma-compress     Compress using lzma when saving: slow but smallest image file, faster decompression than bzip2."
    echo " -z5, --xz-compress       Compress using xz when saving: slow but smallest image file, faster decompression than bzip2."
    echo " -z5p, --smp-xz-compress  Compress using parallel xz when saving: slow but smallest image file, faster decompression than bzip2."
    echo " -z6, --lzip-compress     Compress using lzip when saving: slow but smallest image file, faster decompression than bzip2."
    echo " -z6p, --smp-lzip-compress  Compress using parallel lzip when saving: slow but smallest image file, faster decompression than bzip2."
    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 (i.e. Priority: ntfsclone > partimage > dd)"
    echo " -q1, --force-to-use-dd   Force to use dd to save partition(s) (inefficient method, very slow, but works for all the file system)."
    echo " -q2, --use-partclone     Use partclone to save partition(s) (i.e. partclone > partimage > dd)."
    echo " -gm, --gen-md5sum   Generate the MD5 checksum for the image. Later you can use -cm|--check-md5sum option to check the image when restoring the image. Note! It might take a lot of time to generate if the image size is large."
    echo " -gs, --gen-sha1sum  Generate the SHA1 checksum for the image. Later you can use -cs|--check-sha1sum option to check the image when restoring the image. Note! It might take a lot of time to generate if the image size is large."
    echo " -sc, --skip-check-restorable  By default Clonezilla will check the image if restorable after it is created. This option allows you to skip that."
} # 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, --batch      (DANGEROUS!) Run program in batch mode, i.e. without any prompt or wait for pressing enter key.  //NOTE// You have to use '-batch' instead of '-b' when you want to use it in the boot parameters. Otherwise the program init on system will honor '-b', too."
    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 " -um, --user-mode [beginner|expert]      Specify the mode to use. If not specified, default mode is for a beginner."
    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 $msg_delimiter_star_line
    echo "Clean the incomplete image $img_name... "
    # To avoid if user input empty for the image name, the $ocsroot will be all removed (https://sourceforge.net/tracker/?func=detail&atid=671650&aid=2956592&group_id=115473)
    if [ -n "$img_name" -a -d "$img_name" ] && \
       [ "$(LC_ALL=C readlink -f "$img_name")" != "$(LC_ALL=C readlink -f "$ocsroot")" ]; then
      echo "$msg_do_u_want_to_remove"
      echo -n "[y/N] "
      read rm_dir_confirm
      case "$rm_dir_confirm" in
          y|[yY][eE][sS])
                        rm_dir_confirm="yes"
                        ;;
          *) 
                        rm_dir_confirm="no"
                        ;;
      esac 
      [ "$rm_dir_confirm" = "yes" ] && rm -rfv $img_name
    fi
    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
  # 2 cases: 
  # (1) Clonezilla live-based Clonezilla SE (where ocs_server=$IP is assigned)
  # (2) NFS-root based Clonezilla SE
  if [ -n "$(LC_ALL=C grep -iE ocs_server /proc/cmdline)" ]; then
    echo "Found 'ocs_server' in boot parameters. Assume clonezilla job was spawned by DRBL server."
    parse_cmdline_option ocs_server
    rc=0
  else
    if [ -z "$ppid_" ]; then
      echo "No PID is assigned! Unable to decide that!"
      return 1
    fi
    ocs_pproc="$(LC_ALL=C ps -eo pid,ucmd | grep -Ew "^[[:space:]]*$ppid_" | awk -F" " '{print $2}')"
    rc=1
    if [ -n "$(LC_ALL=C echo "$ocs_pproc" | grep -Ew "S[0-9][0-9]ocs-run$")" ]; then
      echo "$ocs_file was spawned by $ocs_pproc"
      rc=0
    fi
  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 [ "$ocs_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="$(ocs-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" -o "$TERM" = "jfbterm" ] && ([ -e /dev/fb/0 ] || [ -e /dev/fb0 ]); then 
     TMP="$(mktemp /tmp/lang.XXXXXX)"
     drbl-langchooser $TMP
     ocs_lang="$(cat $TMP)"
     [ -f "$TMP" ] && rm -f $TMP
   fi
 fi
}
#
show-general-ocs-live-prompt() {
  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 a 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
#
is_partclone_support_fs() {
  # function to check if the input filesystem is supported by partclone or not
  # partclone_support_fs is a global variable in drbl-ocs.conf
  local input_fs="$1"
  local rc
  # 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 "$partclone_support_fs" | grep -Fiw "$input_fs")" ]; then
    # check if the corresponding programs exists 
    if type partclone.${input_fs} &>/dev/null; then
      rc=0
    else
      rc=1
    fi
  else
    rc=1
  fi
  return $rc
} # end of is_partclone_support_fs
# get dd save/restore statistics
get_dd_image_info() {
  local report_info=$1
  local start_t=$2
  local end_t=$3
  local rcode
  # 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 
  if [ -z "$speed" ]; then
    rcode=1
    # show it with unit
    speed="N/A $space_used_unit/min"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
    echo "$msg_something_weng_wrong"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo -n "$msg_press_enter_to_continue..."
    read
  else
    # show it with unit
    speed="$speed $space_used_unit/min"
    rcode=0
  fi
  return $rcode
} # end of get_dd_image_info
#
check_if_tty_the_specified_one_and_continue() {
  # This function is run in clonezilla live, by default only run on tty1 (no more ttyS0). If you want to use ttyS0, add live-getty and console=ttyS0,38400n81 in the boot parameter.
  local TMP ocslivemode
  # CURRENT_TTY is environment variable from S30ocs-live-menu
  # ocs_live_run_tty is variable loaded from /etc/ocs/ocs-live.conf
  # By default we will run $ocs_live_run in /dev/tty1 if ocs_live_run_tty is not specified.
  if [ -n "$ocs_live_run_tty" ]; then
    # tty is specified. Check if it the current tty
    echo "ocs_live_run_tty is specified as $ocs_live_run_tty. Checking if the current tty is the specified one..."
    if [ "$CURRENT_TTY" != "$ocs_live_run_tty" ]; then
      echo "Current tty ($CURRENT_TTY) does not match ocs_live_run_tty ($ocs_live_run_tty). Program terminated!"
      exit 3
    fi
  else
    # No tty is specified to run $ocs_live_run_tty. Default to run only on /dev/tty1.
    # If it's not in /dev/tty1, just exit.
    if [ "$CURRENT_TTY" != "/dev/tty1" ]; then
      echo "$0 only works in environment variable 'CURRENT_TTY' = '/dev/tty1'"
      exit 3
    fi
  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_tty_the_specified_one_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.
    gen_proc_partitions_map_file
    disk_list="$(LC_ALL=C awk '/[hsv]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.
    gen_proc_partitions_map_file
    # ///NOTE/// Do NOT sort them! Otherwise the partitions list will be like:
    # sda1 sda11 sda12 sda2 sda3...
    # XXX part_list="$(LC_ALL=C awk '/[hsv]d[a-z][0-9]+($| )/ { print $4; }' $partition_table | sort)"
    part_list="$(LC_ALL=C awk '/[hsv]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
#
get_live_media_mnt_point() {
  local extra_live_media_path squash_f="filesystem.squashfs"
  # LIVE_MEDIA is a global variable.
  # Decide the path for live media.
  # live_media_path_chklist and live_sys_files_dir_list are from drbl-ocs.conf
  LIVE_MEDIA=""
  # squash_f default is filesystem.squashfs, but on PXE server, it might be changed. E.g. fetch=tftp://192.168.1.254/Clonezilla-live-filesystem.squashfs. We will overwrite the default value if fetch is found.
  parse_cmdline_option fetch
  [ -n "$fetch" ] && squash_f="$(basename $fetch)"
  # Besids $live_sys_files_dir_list, user might assign his/her own live_sys_files_dir_list in the media (e.g. /live-hd). We can try to find it from the boot parameter "live-media-path" (//NOTE// Function parse_cmdline_option in drbl-functions is not working for this varialbe live-media-path, since it can not be a shell script variable.
  extra_live_media_path="$(LC_ALL=C grep -oE "live-media-path=([[:alnum:]]|_|-|\.|\/)*([[:space:]]|$)+" /proc/cmdline | sed -e "s/live-media-path=//g")"
  for i in $live_media_path_chklist; do
    for j in $live_sys_files_dir_list $extra_live_media_path; do
      if [ -f "$i/$j/$squash_f" ]; then
        LIVE_MEDIA="$i"
        break
      fi
    done
    [ -n "$LIVE_MEDIA" ] && break
  done
  if [ -z "$LIVE_MEDIA" ]; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "///WARNING/// $squash_f not found! No idea where is LIVE MEDIA!!! Assume this is running in DRBL client."
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
  fi
  # echo "Live media is in $LIVE_MEDIA"
} # end of get_live_media_mnt_point
#
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

  # Decide where is the $LIVE_MEDIA
  get_live_media_mnt_point

  # 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. This link file will be removed by prep-ocsroot if it need the mount point $ocsroot (dir).
    # Remove the stale mountpoint if it's empty. This will avoid link twice: 
    # If we run this later:
    # ln -fs /live_media/home/partimag /home/partimag
    # if the latter dir /home/partimag exists then link will create something like:
    # ls -l /home/partimag/
    # lrwxrwxrwx 1 root root 26 2007-12-13 23:29 partimag -> /live_media//home/partimag
    # If we embedded the image in Clonezilla live, /$LIVE_MEDIA/$ocsroot will exist, so if we run "mkdir -p /$LIVE_MEDIA/$ocsroot", it will succeed, and this is what we want, too. Since later we will link /$LIVE_MEDIA/$ocsroot to $ocsroot.
    [ -d "$ocsroot" -a -z "$(unalias ls &>/dev/null; ls $ocsroot/ 2>/dev/null)" ] && rmdir $ocsroot 
    [ -L "$ocsroot" ] && rm -f $ocsroot 
    mkdir -p "$(dirname $ocsroot)"
    ln -fs /$LIVE_MEDIA/$ocsroot $ocsroot
  else
    # mkdir a mount point to be used later if it does not exist. Normally it will be created in /etc/ocs/ocs-live.d/S03prep-drbl-clonezilla, so here it's just a insurance.
    [ ! -d "$ocsroot" ] && 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_tty_the_specified_one_and_continue
} # end of ocs-live-env-prepare
#
network_config_if_necessary() {
  local configured_ip run_net_cfg run_again_ans ttys
  configured_ip="$(get-all-nic-ip --all-ip-address)"
  if [ -z "$configured_ip" ]; then
    echo "Running ocs-live-netcfg to configure network..."
    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
#
unmount_mounted_fs_before_ocs_live_reboot() {
  umount_dir="$ocsroot /tmp/local-dev"
  for i in $umount_dir; do
    if mountpoint $i &>/dev/null; then
      echo -n "Trying to unmount $i... "
      umount $i
      echo "done!"
    fi
  done
} # end of unmount_mounted_fs_before_ocs_live_reboot
#
run_post_cmd_when_clonezilla_live_end() {
  local tty_bash_id umount_dir
  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) unmount_mounted_fs_before_ocs_live_reboot
       echo -n 'Will poweroff... '
       countdown 5
       poweroff $HALT_REBOOT_OPT ;;
    1) unmount_mounted_fs_before_ocs_live_reboot
       echo -n 'Will reboot... '
       countdown 5
       reboot $HALT_REBOOT_OPT ;;
    3) # umount the clonezilla home dir
       unmount_mounted_fs_before_ocs_live_reboot
       echo -n 'Run Clonezilla live again... '
       # 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.
       if [ -n "$ocs_live_run_tty" ]; then
         # ocs_live_run_tty is like: /dev/tty2, /dev/pts/2 (very unlikely)
         ttys="$(LC_ALL=C echo $ocs_live_run_tty | sed -e "s|/dev/||g")"
       else
         ttys="tty1"
       fi
       for i in $ttys; do
         tty_bash_id="$(LC_ALL=C ps -t $i | grep bash | awk -F" " '{print $1}')"
         kill -9 $tty_bash_id
       done
       ;;
    *) echo ;;
  esac
} # end of run_post_cmd_when_clonezilla_live_end
#
get_live_boot_param() {
  # For live helper with live-initramfs, maybe it is: boot=live username=casper union=aufs
  # ---------------------------
  # LABEL live
  # 	MENU LABEL Start Debian Live
  # 	kernel /live/vmlinuz1
  # 	append initrd=/live/initrd1.img boot=live username=casper union=aufs 
  # ---------------------------
  
  # For live helper with casper, maybe it is: boot=casper username=casper union=unionfs
  # ---------------------------
  # LABEL live
  #       MENU LABEL Start Debian Live
  # 	kernel /casper/vmlinuz1
  # 	append initrd=/casper/initrd1.img boot=casper username=casper hostname=debian 
  # ---------------------------
  # "boot_param" is global variable
  local iso_tmp="$1"
  local cand_files="live.cfg menu.cfg isolinux.cfg"
  local bparam_chk_list boot_opt union_opt username_opt hostname_opt live_config_opt tmp_var
  [ -z "$iso_tmp" ] && echo "No path for iso mount point! Exit!" && exit 1
  echo "Trying to find the boot params from template live cd..."
  bparam_chk_list="boot_opt union_opt username_opt hostname_opt live_config_opt"
  for i in $cand_files; do
   if [ -f "$iso_tmp/$i" ]; then
      # (1) boot=casper|live
      boot_opt="$(LC_ALL=C grep -oE "boot=([[:alnum:]]|_|-|\.|\/|:)+" $iso_tmp/$i | sort | uniq | head -n 1)"
      # (2) union=aufs|unionfs	
      union_opt="$(LC_ALL=C grep -oE "union=([[:alnum:]]|_|-|\.|\/|:)+" $iso_tmp/$i | sort | uniq | head -n 1)"
      # (3) username
      username_opt="$(LC_ALL=C grep -oE "username=([[:alnum:]]|_|-|\.|\/|:)+" $iso_tmp/$i | sort | uniq | head -n 1)"
      hostname_opt="$(LC_ALL=C grep -oE "hostname=([[:alnum:]]|_|-|\.|\/|:)+" $iso_tmp/$i | sort | uniq | head -n 1)"
      # (4) live-config or config (live-build < 2.0.0 puts live-config, live-build >= 2.0.0-1 put "config")
      live_config_opt="$(LC_ALL=C grep -owE "(live-config|config)" $iso_tmp/$i | sort | uniq | head -n 1)"
      # Once we got the param, we can break later
      [ -n "$union_opt" -o -n "$boot_opt" -o -n "$username_opt" -o -n "$hostname_opt" -o -n "$live_config_opt" ] && next_step=break
      # We'd better not to assign boot_opt and union_opt here if they are not found. Comment them! However, for username, it's important! Without correct username, live-initramfs or casper won't be able to login command line automatically.
      # [ -z "$union_opt" ] && union_opt="union=unionfs"
      # [ -z "$boot_opt" ] && boot_opt="boot=casper"
      if [ -z "$username_opt" ]; then
        # A workaround is to search the isolinux config if it contains something lie:
	# append initrd=/live/initrd.img boot=live hostname=ubuntu union=aufs
	# If we can find hostname=ubuntu, then use username=ubuntu. Otherwise leave it alone! We assume this is only a bug in the live-helper from Ubuntu 7.10. For newer live-helper from Debian SID, it will automatically add username in the created iso.
	if grep -q "hostname=ubuntu" $iso_tmp/$i; then
          username_opt="username=ubuntu"
	else
          username_opt=""
	fi
      fi
      boot_param=""
      for i in $bparam_chk_list; do
        eval tmp_var="\$${i}"
        [ -n "$tmp_var" ] && boot_param="$boot_param $tmp_var"
      done
      $next_step
   fi
  done
  # Remove the leading space characters
  boot_param="$(LC_ALL=C echo $boot_param | sed -r -e "s/^[[:space:]]*//g")"
  # BOOT_PARAM_DEFAULT is from drbl-ocs.conf.
  [ -z "$boot_param" ] && boot_param="$BOOT_PARAM_DEFAULT"
  return 0
} # end of get_live_boot_param
#
# Function to check if bterm is necessary to display language. By default we prefer to use normal term, since in bterm no color output...
locale_required_bterm_or_not() {
  # locale_need_bterm is a global variable in drbl-ocs.conf
  local lang="$1"
  local rc=1
  if [ -n "$(echo "$locale_need_bterm" | grep -iEwo "$lang")" ]; then
   rc=0
  else
   rc=1
  fi
  return $rc
} # end of locale_required_bterm_or_not
#
get_fb_term(){
  # fb_term is global variable.
  # Due to a bug in jfbterm in Ubuntu, we force to use bterm temporarily.
  # For debian etch, both bterm or jfbterm can work.
  [ -e /etc/lsb-release ] && . /etc/lsb-release
  if [ "$DISTRIB_ID" = "Ubuntu" ]; then
    if type bterm &>/dev/null; then
      fb_term="bterm"
    else
     echo "No bterm was found! We need to use bterm in Ubuntu!"
     echo "Program terminated!"
     exit 1
    fi
  fi

  [ -n "$fb_term" ] && return 0
  # If no fb_term was found or assigned, we try to find it now.
  if type jfbterm &>/dev/null; then
    fb_term="jfbterm"
  elif type bterm &>/dev/null; then
    fb_term="bterm"
  else
   echo "No jfbterm or bterm was found!"
   echo "Program terminated!"
   exit 1
  fi
} # end of get_fb_term
check_if_ocsroot_a_mountpoint() {
  local mntpnt_continue
  fail_mountpoint() {
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
    echo "Clonezilla image home directory $ocsroot is not a mounting point! Failed to mount other device as $ocsroot!"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    confirm_continue_or_default_quit
  }
  success_mountpoint() {
    echo "$msg_df_report"
    echo $msg_delimiter_star_line
    df -ah
    echo $msg_delimiter_star_line
    echo -n "$msg_press_enter_to_continue..."
    read
  }
  # check if $ocsroot is a mounting point
  if ! mountpoint $ocsroot &>/dev/null; then
    # A workaround to avoid false alarm for cifs.
    # Ref: https://bugs.launchpad.net/ubuntu/+source/linux/+bug/486667
    if [ -n "$(LC_ALL=C mount | grep -Ew "$ocsroot" | grep -i cifs)" ]; then
      # For cifs, we check in another method
      if [ -n "$(LC_ALL=C mount |grep -Ew "$ocsroot")" ]; then
        success_mountpoint
      else
        fail_mountpoint
      fi
    else
      fail_mountpoint
    fi
  else
    success_mountpoint
  fi
} # end of check_if_ocsroot_a_mountpoint
#
check_ntfs_boot_partition() {
  local select_parts="$1"  # select_disk is like hda or sda
  local partition_list=""
  local fs flags
  found_ntfs_boot_partition=""
  # found_ntfs_boot_partition is a global variable
  gen_proc_partitions_map_file
  if [ -n "$select_parts" ]; then
    partition_list="$select_parts"
  else
    # No partitions are assigned, search available partitions.
    partition_list="$(LC_ALL=C awk '/[hsv]d[a-z][0-9]+/ { print $4; }' $partition_table | sort)"
  fi
  for ipartition in $partition_list; do
    ntfs_boot_partition="/dev/$ipartition"
    # If the partition is not ntfs, skip it.
    fs="$(ocs-get-part-info $ntfs_boot_partition filesystem)"
    [ -z "$(echo "$fs" | grep -i "ntfs")" ] && continue
    flags="$(ocs-get-part-info $ntfs_boot_partition flags)"
    if [ -n "$(echo "$flags" | grep -E "boot")" ]; then
      found_ntfs_boot_partition="$ntfs_boot_partition"
      break;
    fi
  done
  [ -f "$partition_table" ] && rm -f $partition_table
} # end of check_ntfs_boot_partition
# Get IDE HD Raw CHS
get_RawCHS_of_HD_from_hdparm() {
  local selected_hd RawCHS CurCHS
  # rawcyl rawhead rawsector are global variables
  # The result of "hdparm -i /dev/hda" is like:
  # /dev/hda:
  # Model=VMware Virtual IDE Hard Drive, FwRev=00000001, SerialNo=00000000000000000001
  # Config={ HardSect NotMFM HdSw>15uSec SpinMotCtl Fixed DTR>5Mbs FmtGapReq }
  # RawCHS=16383/15/63, TrkSize=0, SectSize=0, ECCbytes=0
  # BuffType=unknown, BuffSize=32kB, MaxMultSect=64, MultSect=off
  # CurCHS=17475/15/63, CurSects=16513875, LBA=yes, LBAsects=16777216
  # IORDY=on/off, tPIO={min:160,w/IORDY:120}, tDMA={min:120,rec:120}
  # PIO modes:  pio0 pio1 pio2 pio3 pio4
  # DMA modes:  mdma0 mdma1 mdma2
  # UDMA modes: udma0 udma1 *udma2
  # AdvancedPM=yes: disabled (255)
  # Drive conforms to: ATA/ATAPI-4 T13 1153D revision 17:  ATA/ATAPI-1 ATA/ATAPI-2 ATA/ATAPI-3 ATA/ATAPI-4
  # 
  selected_hd="$1"  # e.g. /dev/hda
  [ -z "$selected_hd" ] && echo "You have to assign selected_hd in function get_RawCHS_of_HD_from_hdparm" && exit 1
  RawCHS="$(hdparm -i $selected_hd 2>/dev/null | grep -iEo "RawCHS=.*," | awk -F "," '{print $1}' | sed -s "s/RawCHS=//g")"
  CurCHS="$(hdparm -i $selected_hd 2>/dev/null | grep -iEo "CurCHS=.*," | awk -F "," '{print $1}' | sed -s "s/CurCHS=//g")"
  if [ -n "$RawCHS" ]; then
    # RawCHS=16383/15/63
    rawcyl="$(echo $RawCHS | awk -F"/" '{print $1}')"
    rawhead="$(echo $RawCHS | awk -F"/" '{print $2}')"
    rawsector="$(echo $RawCHS | awk -F"/" '{print $3}')"
    echo "RawCHS of $selected_hd from hdparm info: $RawCHS"
    # If RawCHS exists, CurCHS must exist, too.
    # echo "CurCHS of $selected_hd: $CurCHS"
  else
    echo "RawCHS and CurCHS of $selected_hd is not found! Maybe this is ScSI device ?"
  fi
} # end of get_RawCHS_of_HD_from_hdparm
#
edd_id_map_by_mbr() {
  local disk_="$1"  # e.g. /dev/hda
  local mbr_sig edd_mapdev
  # Function to get the disk signature by the data in MBR:
  # Address (Hex: 01B8, or Dec: 440) 
  # Ref: https://sourceforge.net/forum/message.php?msg_id=6108241
  #      http://en.wikipedia.org/wiki/Master_boot_record
  # Because /lib/udev/edd_id maybe not work in some cases.
  # E.g.
  # If /sys/firmware/edd/int13_dev8*/mbr_signature contains the following (it does exists):
  # 0xf7aef7ae
  # 0x00000000
  # 0x00000000
  # 0x00000000
  # 0x00000000
  # 0x00000000
  # 0x00000000
  # 0x00000000
  # 0xf7aef7ae
  # 0xf7aef7ae
  # 0xf7aef7ae
  # 0xf7aef7ae
  # then if "/lib/udev/edd_id /dev/hda" is run, the result is: "does not have a unique signature"
  [ ! -e "$disk_" ] && return 1
  mbr_sig="$(LC_ALL=C hexdump -n 4 -s 440 -e '"0x%.8x\n"' $disk_)"
  if [ -n "$mbr_sig" ]; then
    for i in /sys/firmware/edd/int13_*; do
      if grep -qEw "$mbr_sig" $i/mbr_signature 2>/dev/null; then
        edd_mapdev="$(basename $i)"
	break
      fi
    done
  fi
  [ -n "$edd_mapdev" ] && echo $edd_mapdev
} # End of edd_id_map_by_mbr
#
edd_id_map_by_capacity() {
  local disk_="$(basename $1)"  # e.g. hda
  local capacity edd_mapdev
  [ ! -e "$1" ] && return 1
  capacity="$(< /sys/block/$disk_/size)"
  if [ -n "$capacity" ]; then
    for i in /sys/firmware/edd/int13_*; do
      if grep -q "$capacity" $i/sectors 2>/dev/null; then
        edd_mapdev="$(basename $i)"
        break
      fi
    done
  fi
  [ -n "$edd_mapdev" ] && echo $edd_mapdev
} # End of edd_id_map_by_capacity
#
get_RawCHS_of_HD_from_EDD() {
  # About EDD (Enhanced Disk Device): http://lwn.net/Articles/9042/
  # BIOS Enhanced Disk Device Services (EDD) 3.0 provides the ability for disk adapter BIOSs to tell the OS what it believes is the boot disk.
  local selected_hd map_dev sectors_from_edd
  selected_hd="$1"  # e.g. /dev/hda
  [ -z "$selected_hd" ] && echo "You have to assign selected_hd in function get_RawCHS_of_HD_from_EDD" && exit 1
  [ ! -d /sys/firmware/edd ] && modprobe edd 2>/dev/null
  if [ ! -d /sys/firmware/edd ]; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "Kernel EDD (sysfs interface to BIOS EDD (Enhanced Disk Device) information) not supported!"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo "You can try to enable it if it's builtin by putting edd=on as boot parameter."
    return 1
  fi
  map_dev="$(/lib/udev/edd_id $selected_hd 2>/dev/null)"  # Result e.g.: int13_dev80
  # Try another method to get if edd_id failed
  [ -z "$map_dev" ] && map_dev="$(edd_id_map_by_mbr $selected_hd)"
  # Try to match by capacity
  [ -z "$map_dev" ] && map_dev="$(edd_id_map_by_capacity $selected_hd)"
  # If still not found, return 
  [ -z "$map_dev" ] && return 1

  rawhead="$(cat /sys/firmware/edd/$map_dev/legacy_max_head 2>/dev/null)"
  rawhead="$((rawhead+ 1))"
  rawsector="$(cat /sys/firmware/edd/$map_dev/legacy_sectors_per_track 2>/dev/null)"
  # Orgad Shaneh mentioned that the cylinders value from legacy_max_cylinder is WRONG. It is limited to 1022 or something like that. The actual cylinders value must be calculated as a division result.
  # Ref: https://sourceforge.net/forum/message.php?msg_id=6688755
  sectors_from_edd="$(cat /sys/firmware/edd/$map_dev/sectors 2>/dev/null)"
  if [ -n "$sectors_from_edd" -a -n "$rawhead" -a -n "$rawsector" ]; then
    # cylinder = sectors / heads / sectors
    rawcylinder="$(($sectors_from_edd/$rawhead/$rawsector))"
  fi
  if [ -z "$rawhead" -o -z "$rawsector" ]; then
    echo "No head or sector number of $selected_hd was found from EDD info."
    # Reset them
    rawhead=""
    rawsector=""
    rawcylinder=""
    return 1
  else
    echo "Head and sector no. of $selected_hd from EDD: $rawhead, $rawsector."
  fi
  return 0
} # end of get_RawCHS_of_HD_from_EDD
#
get_RawCHS_of_HD_from_sfdisk() {
  local selected_hd opt_sf
  # rawcyl rawhead rawsector are global variables
  # 
  selected_hd="$1"  # e.g. /dev/hda
  opt_sf="$2"    # e.g. -g or -G
  [ -z "$selected_hd" ] && echo "You have to assign selected_hd in function get_RawCHS_of_HD_from_hdparm" && exit 1
  rawhead=""
  rawsector=""
  rawhead="$(LC_ALL=C sfdisk $opt_sf $selected_hd | grep -oEi "[[:digit:]]+ heads" | awk -F" " '{print $1}')"
  rawsector="$(LC_ALL=C sfdisk $opt_sf $selected_hd | grep -oEi "[[:digit:]]+ sectors" | awk -F" " '{print $1}')"
  if [ -z "$rawhead" -o -z "$rawsector" ]; then
    echo "No head or sector number of $selected_hd was found from sfdisk $opt_sf output."
    # Reset them
    rawhead=""
    rawsector=""
    return 1
  else
    echo "Head and sector no. of $selected_hd from sfdisk $opt_sf: $rawhead, $rawsector."
  fi
  return 0
} # end of get_RawCHS_of_HD_from_sfdisk
# Relocate ntfs CHS
run_ntfsreloc_part() {
    local ntfs_boot_partition
    local selected_hd selected_parts ntfslock_chsopt ntfs_boot_hd ntfs_start_sector
    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
           ;;
        -p|--selected-parts)
           shift
           if [ -z "$(echo $1 |grep ^-.)" ]; then
             # skip the -xx option, in case 
             selected_parts="$1"
             shift
           fi
           [ -z "$selected_parts" ] && echo "-p is used, but no selected_parts assigned." && exit 1
           ;;
        -*)     echo "${0}: ${1}: invalid option" >&2
                USAGE >& 2
                exit 2 ;;
        *)      break ;;
      esac
    done
    ntfs_boot_partition="$1"

    # if ntfs_boot_partition is not set, set default
    if [ "$ntfs_boot_partition" = "auto" ]; then
       check_ntfs_boot_partition "$selected_parts"
       ntfs_boot_partition="$found_ntfs_boot_partition"  # e.g. /dev/hda1
       if [ -n "$ntfs_boot_partition" ]; then
         echo "Found NTFS boot partition among the restored partition(s): $ntfs_boot_partition"
       else
         echo "The NTFS boot partition was not found or not among the restored partition(s). Skip running partclone.ntfsfixboot." 
	 return 1
       fi
    fi

    ntfs_boot_hd="${ntfs_boot_partition:0:8}"  # e.g. /dev/hda1 -> /dev/hda
    # use_RawCHS_from_EDD_for_ntfsreloc or use_RawCHS_from_hdparm_for_ntfsreloc is from drbl-ocs.conf. The priority is use_RawCHS_from_EDD_for_ntfsreloc first, then use_RawCHS_from_hdparm_for_ntfsreloc
    rawhead=""
    rawsector=""
    rawcylinder=""
    if [ "$use_RawCHS_from_EDD_for_ntfsreloc" = "yes" ]; then
      get_RawCHS_of_HD_from_EDD "$ntfs_boot_hd"
    fi
    # If no rawhead or rawsector, try to find it from sfdisk -g or -G
    if [ -z "$rawhead" -o -z "$rawsector" ]; then
      if [ "$use_RawCHS_from_sfdisk_for_ntfsreloc" = "yes" ]; then
        # Ref: https://sourceforge.net/forum/message.php?msg_id=6181406
        if [ "$load_HD_CHS_from_img" = "yes" ]; then
	  # Since user forces to load CHS, we read CHS from image file 
          . $ocsroot/$target_dir/"$(basename ${ntfs_boot_hd})"-chs.sf
          rawhead="$heads"
          rawsector="$sectors"
          echo "Head and sector number of ${ntfs_boot_hd} from image info $ocsroot/$target_dir/"$(basename ${ntfs_boot_hd})"-chs.sf: $rawhead, $rawsector."
	else
	  # Read from the partition table
          get_RawCHS_of_HD_from_sfdisk "$ntfs_boot_hd" "-G"
	fi
      fi
    fi

    if [ -n "$rawhead" -a -n "$rawsector" ]; then
      ntfslock_chsopt="-h $rawhead -t $rawsector"
    else
      ntfslock_chsopt=""
    fi
    # Find the start sector of the NTFS partitoin
    ntfs_start_sector="$(LC_ALL=C ocs-get-part-info -u s $ntfs_boot_partition start | sed -e "s/s$//g")"
    if [ -n "$ntfs_start_sector" ]; then
      echo "The start sector of NTFS partition $ntfs_boot_partition: $ntfs_start_sector"
      ntfslock_chsopt="$ntfslock_chsopt -s $ntfs_start_sector"
    fi

    echo "Adjust filesystem geometry for the NTFS partition: $ntfs_boot_partition"
    echo "Running: partclone.ntfsfixboot -w $ntfslock_chsopt $ntfs_boot_partition"
    partclone.ntfsfixboot -w $ntfslock_chsopt $ntfs_boot_partition
    rc=$?

    if [ "$rc" -eq 2 ]; then
        [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
	echo "Failed to run partclone.ntfsfixboot!!!"
        [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    fi
} # end of run_ntfsreloc_part
#
get_mkswap_uuid_cmd() {
  # MKSWAP_UUID is global variable
  if [ -n "$(mkswap -help 2>&1 | grep -oE -- "-U[[:space:]]+UUID")" ]; then
   # Newer mkswap (e.g. from util-linux-ng 2.13.1.1) supports "-U UUID"
   MKSWAP_UUID="mkswap"
  else
   # If not, we have to use the patched one
   MKSWAP_UUID="mkswap-uuid"
  fi
} # get_mkswap_uuid_cmd
#
fsck_partition() {
  local partfs_ dev_  # dev_ is like /dev/sda1
  partfs_="$1"
  dev_="$2"
  [ -z "$partfs_" ] && echo "Not partfs_ assigned in function fsck_partition!" && exit 1
  [ -z "$dev_" ] && echo "Not dev_ assigned in function fsck_partition!" && exit 1
  [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
  echo "Running file system check and repair for $dev_..."
  [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
  case "$partfs_" in
    ntfs) #ntfsfix $dev_;;
          # If we use ntfsfix to fsck NTFS, ntfsclone and partclone need to use some special parameter to force clone. e.g. for ntfsclone, we have to use options "--force --rescue" (No such options for patclone.ntfs on Aug/05/2009). Therefore here we do not want to use ntfsfix to do that. It might cause some problems.
          [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
          echo "There is no good tool to check and repair NTFS, skip fsck this partition $dev_"
          [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
          ;;
    *)    fsck -f $dev_;;
  esac
} # end of fsck_partition
#
decide_live_kernel_related_pkgs_from_debian() {
  # This function is for create-debian-live, create-gparted-live, and create-drbl-live, not for create-ubuntu-live.
  # debian_dist and kernel_related_pkgs are global variables
  # This function will output "kernel_related_pkgs" and "export MKSQUASHFS_OPTIONS"
  if [ "$debian_dist" = "etch" ]; then
    if [ -n "$live_kernel_ver" ]; then
      kernel_related_pkgs="linux-image-${live_kernel_ver} aufs-modules-${live_kernel_ver} squashfs-modules-${live_kernel_ver}"
    else
      kernel_related_pkgs="linux-image-2.6 aufs-modules-2.6 squashfs-modules-2.6"
    fi
    # exclude some stuffs from the squashfs root, since in Live system, we will only use vmlinuz and initrd in /{casper,live}, not in the /boot/ in the squashfs.
    # For etch, mksquashfs only allows 1024 to 64k. //NOTE// Do not use "64K", use "65536" since older mksquash does not accept that (k).
    export MKSQUASHFS_OPTIONS="-b 65536 -e boot"
  elif [ "$debian_dist" = "lenny" ]; then
    # For 2.6.29, squashfs is already in mainline kernel, so squashfs-modules-2.6 is no more required.
    # Ref: http://lists.debian.org/debian-live/2009/04/msg00030.html
    if [ -n "$live_kernel_ver" ]; then
      if dpkg --compare-versions "$live_kernel_ver" ">>" "2.6.31" &>/dev/null; then
        kernel_related_pkgs="linux-image-${live_kernel_ver}"
      elif dpkg --compare-versions "$live_kernel_ver" ">>" "2.6.29" &>/dev/null; then
        kernel_related_pkgs="linux-image-${live_kernel_ver} aufs-modules-${live_kernel_ver}"
      else
        kernel_related_pkgs="linux-image-${live_kernel_ver} aufs-modules-${live_kernel_ver} squashfs-modules-${live_kernel_ver}"
      fi
    else
      kernel_related_pkgs="linux-image-2.6 aufs-modules-2.6 squashfs-modules-2.6"
    fi
    # exclude some stuffs from the squashfs root, since in Live system, we will only use vmlinuz and initrd in /{casper,live}, not in the /boot/ in the squashfs.
    export MKSQUASHFS_OPTIONS="-b 1024k -e boot"
  elif [ "$debian_dist" = "squeeze" ]; then
    # squeeze uses kernel 2.6.30 from Aug/20/2009, so no more squashfs-modules. From kernel 2.6.31, aufs is builtin, so no more aufs-modules.
    if [ -n "${live_kernel_ver}" ]; then
      kernel_related_pkgs="linux-image-${live_kernel_ver}"
    else
      kernel_related_pkgs="linux-image-2.6"
    fi
    # exclude some stuffs from the squashfs root, since in Live system, we will only use vmlinuz and initrd in /{casper,live}, not in the /boot/ in the squashfs.
    export MKSQUASHFS_OPTIONS="-b 1024k -e boot"
  elif [ "$debian_dist" = "sid" ]; then
    # For sid with kernel 2.6.31, aufs is builtin in linux-image. Therefore no more aufs-modules
    if [ -n "${live_kernel_ver}" ]; then
      kernel_related_pkgs="linux-image-${live_kernel_ver}"
    else
      kernel_related_pkgs="linux-image-2.6"
    fi
    # exclude some stuffs from the squashfs root, since in Live system, we will only use vmlinuz and initrd in /{casper,live}, not in the /boot/ in the squashfs.
    export MKSQUASHFS_OPTIONS="-b 1024k -e boot"
  else
    echo "This distribution is not supported!"
    echo "Program terminated!"
    exit 1
  fi
} # end of decide_live_kernel_related_pkgs_from_debian
#
get_live_autologin_account(){
  # This function is only for used in Clonezilla live.
  # Find the account with NOPASSWD in sudoers:
  # e.g.  user  ALL=(ALL) NOPASSWD: ALL
  live_autologin_account="$(LC_ALL=C grep -iE "^[^#].*ALL=\(ALL\)[[:space:]]*NOPASSWD:[[:space:]]*ALL" /etc/sudoers | awk -F" " '{print $1}')"
} # end of get_live_autologin_account
#
get_live_auto_login_id_home() {
  # This function is only for used in Clonezilla live.
  live_auto_login_id_home="$(LC_ALL=C bash -c "echo ~$live_autologin_account")"
} # end of get_live_auto_login_id_home
#
check_image_if_restorable() {
  local ans_continue
  # This function is used to test the intrgrity of clonzilla image
  if [ -z "$chk_img_restoreable" ]; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "$msg_do_u_want_to_check_img_is_restorable?"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo -n "[Y/n] "
    read ans_continue
  fi
  case "$ans_continue" in
    n|N|[nN][oO]) chk_img_restoreable="no" ;;
    *)            chk_img_restoreable="yes" ;;
  esac

  if [ "$chk_img_restoreable" = "yes" ]; then
    sleep 1
    echo "Start checking the saved image $target_dir if restorable..."
    ocs-chkimg $target_dir
  else
    echo "Skip checking image $target_dir if restorable..."
  fi
} # end of check_image_if_restorable
#
get_part_id_from_sf_format() {
  local sf_file_in_image="$1"  # e.g. /home/partimag/img-name/sda-pt.sf
  local part_name="$2"  # e.g. sda2
  # The function to get partition ID from sfdisk output format
  # A typical partition table is like:
  # partition table of /dev/sda
  #unit: sectors
  #/dev/sda1 : start=       63, size=  2249037, Id=83, bootable
  #/dev/sda2 : start=  2249100, size=   224910, Id=fc
  #/dev/sda3 : start=  2474010, size=207238500, Id= 5
  #/dev/sda4 : start=        0, size=        0, Id= 0
  #/dev/sda5 : start=  2474073, size=207238437, Id=fb
  LC_ALL=C grep -Ew "^/dev/$part_name" $sf_file_in_image 2>/dev/null | grep -Eo "Id=.*" | awk -F "," '{print $1}' | sed -e "s/Id=//g" -e "s/^[[:space:]]*//g"
} # end of get_part_id_from_sf_format
