#!/bin/bash
# Authors: Steven Shiau <steven _at_ nchc org tw>, Ceasar Sun <ceasar _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
}
# grand_jobs_before_exit and my_ocs_exit are in drbl-functions.
#
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
#
gen_md5_sha1_sums_if_assigned() {
  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_if_assigned
#
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_not_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_not_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 hdtmp
  hdtmp="/dev/$(get_diskname $ipart)"
  echo "$hdtmp"
} # end of get_disk_from_part
#
get_partition_table_type_from_disk() {
  local tdisk=$1  # e.g. /dev/sda
  local ptype
  if `is_gpt_partitition_table_disk $tdisk`; then
    ptype="gpt"
  elif `is_mbr_partitition_table_disk $tdisk`; then
    ptype="mbr"
  else
    # Here we do not have to check if it's PV on a disk, because we do not need to do anything when it's "pv_disk" like that in function "get_partition_table_type_from_img".
    ptype="unknown"
  fi
  echo "$ptype"
} # get_partition_table_type_from_disk
#
get_partition_table_type_from_img() {
  local img_dir_fullpath="$1"  # image dir full path
  local hdsk="$2"  # hard drive name, like: sda, cciss/c0d0
  local ptype ip
  ptype=""
  if [ -z "$img_dir_fullpath" -o -z "$hdsk" ]; then
    exit 1
  fi
  # From the output file of parted, we can decide if the partition is gpt or mbr
  if `is_gpt_partitition_table_file $img_dir_fullpath/$(to_filename ${hdsk})-pt.parted`; then
    ptype="gpt"
  elif `is_mbr_partitition_table_file $img_dir_fullpath/$(to_filename ${hdsk})-pt.parted`; then
    ptype="mbr"
  else
    # There is still one possiblity that we need to deal with LVM, i.e. PV is not on partition, 
    pv_on_dsk="$(get_pv_on_disk_from_PV_conf_list "$img_dir_fullpath/lvm_vg_dev.list")"
    for ip in $pv_on_dsk; do
      if [ -n "$(LC_ALL=C echo "${hdsk}" | grep -Ew "${ip}")" ]; then
        # Found PV on this disk
        ptype="pv_disk"
        break
      fi
    done
    if [ -z "$ptype" ]; then
      ptype="unknown"
    fi
  fi
  echo "$ptype"
} # end of get_partition_table_type
#
is_gpt_partitition_table_file() {
  local p_table_f="$1"
  local rt_code=""
  if [ -z "$p_table_f" ]; then
    echo "No input parameter \$p_table_f in function is_gpt_partitition_table!"
    return 3
  fi
  if [ -n "$(LC_ALL=C grep -iE "^Partition Table:" $p_table_f 2>/dev/null | grep -iE "gpt")" ]; then
    rt_code=0
  else
    rt_code=1
  fi
  return $rt_code
} # end of is_gpt_partitition_table_file
#
is_mbr_partitition_table_file() {
  local p_table_f="$1"
  local rt_code=""
  if [ -z "$p_table_f" ]; then
    echo "No input parameter \$p_table_f in function is_mbr_partitition_table!"
    return 3
  fi
  if [ -n "$(LC_ALL=C grep -iE "^Partition Table:" $p_table_f 2>/dev/null | grep -iE "msdos")" ]; then
    rt_code=0
  else
    rt_code=1
  fi
  return $rt_code
} # end of is_mbr_partitition_table_file
#
is_gpt_partitition_table_disk() {
  local input_disk_="$1"  # e.g. /dev/sda
  local rt_code=""
  if [ -z "$input_disk_" ]; then
    echo "No input parameter \$input_disk_ in function is_gpt_partitition_table!"
    return 3
  fi
  if [ -n "$(LC_ALL=C parted -s $input_disk_ print | grep -iE "^Partition Table:" | grep -iE "gpt")" ]
  then 
    rt_code=0
  else
    rt_code=1
  fi
  return $rt_code
} # end of is_gpt_partitition_table_disk
#
is_mbr_partitition_table_disk() {
  local input_disk_="$1"  # e.g. /dev/sda
  local rt_code=""
  if [ -z "$input_disk_" ]; then
    echo "No input parameter \$input_disk_ in function is_mbr_partitition_table!"
    return 3
  fi
  if [ -n "$(LC_ALL=C parted -s $input_disk_ print | grep -iE "^Partition Table:" | grep -iE "msdos")" ]
  then
    rt_code=0
  else
    rt_code=1
  fi
  return $rt_code
} # end of is_mbr_partitition_table_disk
#
inform_kernel_partition_table_changed() {
  # Function to inform kernel the partition table has changed
  local ptype=$1  # e.g. mbr, gpt, or both. Actually mbr covers the gpt one,
  		  # but with "both" it's clearer.
  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."
    [ "$save_restore_error_log" = "yes" ] && copy_error_log
    exit 1
  fi
  # if tdisk is not a disk (e.g. LVM), skip.
  if ! is_whole_disk $tdisk; then
    return 3
  fi
  echo -n "Informing the OS of partition table changes..."
  case "$ptype" in
  mbr|both)
    # //NOTE// Put "blockdev --rereadpt $tdisk" as the last action, since for BSD partitions, partprobe does not know, and if it is the last one, kernel info (/proc/partitions) won't show any partitions. "sleep 1" is also required, otherwise we might see the error message: "BLKRRPART: Device or resource busy"
    # //NOTE// util-linux >= 2.26 removes support for "sfdisk -R". Therefore we switched to "blockdev --rereadpt". Thanks to Ismael (razzziel _at_ users sf net) for reporting this.
    LC_ALL=C partprobe $tdisk
    echo -n "."
    sleep 1
    LC_ALL=C kpartx $tdisk &>/dev/null
    echo -n "."
    sleep 1
    LC_ALL=C blockdev --rereadpt $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
    ;;
    *)
    echo -n " Not MBR or GPT type of device. Skipping informing kernel."
    ;;
  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 "[ -e "$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
    [ -e "$lshw_disk_info" ] && rm -f $lshw_disk_info
  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
  # 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 hidden_data_after_mbr_limit_in_sec size_exp
  for i in src_dsk img_s; do
    eval real_i=\$$i
    if [ -z "$real_i" ]; then
      echo "You have to assign $i in function save_hidden_data_after_MBR." | tee --append ${OCS_LOGFILE}
      echo "$msg_program_stop." | tee --append ${OCS_LOGFILE}
      # Saving mode, always copy error log to image dir.
      copy_error_log 
      exit 1
    fi
  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." | tee --append ${OCS_LOGFILE}
  hidden_data_size="$(echo "scale=0; $start_s - 1" | bc -l)"

  if [ -z "$hidden_data_size" ]; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
    echo "The hidden data size can NOT be found." | tee --append ${OCS_LOGFILE}
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo "$msg_program_stop." | tee --append ${OCS_LOGFILE}
    # Saving mode, always copy error log to image dir.
    copy_error_log
    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." | tee --append ${OCS_LOGFILE}
    return 3
  fi

  # If the hidden_data_size is larger than hidden_data_after_mbr_limit (MB) (from drbl-ocs.conf), skip this.
  hidden_data_after_mbr_limit_in_sec="$(echo "scale=0; $hidden_data_after_mbr_limit*1024*1024/512" | bc -l)"
  size_exp="$(echo "scale=0; $hidden_data_size >= $hidden_data_after_mbr_limit_in_sec" | bc -l)"
  if [ "$size_exp" -eq 1 ]; then
    echo "The hidden data space size ($hidden_data_size sectors) is larger than $hidden_data_after_mbr_limit_in_sec sectors (\$hidden_data_after_mbr_limit, defined in drbl-ocs.conf). Skip saving that!" | tee ${img_s}.notes.txt | tee --append ${OCS_LOGFILE}
    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:" | tee --append ${OCS_LOGFILE}
  echo dd if=/dev/${src_dsk} of=${img_s} skip=1 bs=512 count=$hidden_data_size | tee --append ${OCS_LOGFILE}
  dd if=/dev/${src_dsk} of=${img_s} skip=1 bs=512 count=$hidden_data_size 2>&1 | tee --append ${OCS_LOGFILE}

} # 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
    if [ -z "$real_i" ]; then
      echo "You have to assign $i in function restore_hidden_data_after_MBR!" | tee --append ${OCS_LOGFILE}
      echo "$msg_program_stop!" | tee --append ${OCS_LOGFILE}
      [ "$save_restore_error_log" = "yes" ] && copy_error_log
      exit 1
    fi
  done
  if [ ! -e "$img_r" ]; then
    echo "No hidden data (between MBR and 1st partition) image was found. Skip restoring it."  | tee --append ${OCS_LOGFILE}
    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." | tee --append ${OCS_LOGFILE}
  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." | tee --append ${OCS_LOGFILE}
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo "Skip restoring the hidden data between MBR and 1st partition." | tee --append ${OCS_LOGFILE}
    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:" | tee --append ${OCS_LOGFILE}
  echo dd if=${img_r} of=/dev/${tgt_dsk} seek=1 bs=512 count=$data_size | tee --append ${OCS_LOGFILE}
  dd if=${img_r} of=/dev/${tgt_dsk} seek=1 bs=512 count=$data_size 2>&1 | tee --append ${OCS_LOGFILE}
} # end of restore_hidden_data_after_MBR
#
gen_proc_partitions_map_file() {
  # partition_table is a global variable
  partition_table="$(mktemp /tmp/parttable-ocs.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
  fi
} # end of gen_proc_partitions_map_file
#
get_part_vol_name() {
  local part_w="$1"  # part_w is like: /dev/sda1
  local part_f="$2"
  local part_v
  # If no device or file system (possible, only partition is created, not formated), we should not give any error message in stdin. Otherwise it will become the part_v to be returned.
  if [ -z "$part_w" ]; then
    return 1 
  fi
  if [ -z "$part_f" ]; then
    return 1 
  fi
  case "$part_f" in
  ntfs|NTFS)
    # Get volume name, and replace space with "_" to avoid dialog failure.
    part_v="$(LC_ALL=C ntfsinfo -m $part_w 2>/dev/null | grep -iE "^[[:space:]]*Volume Name:" | awk -F":" '{print $2}' | sed -e "s/^[[:space:]]*//g" -e "s/[[:space:]]*$//g" -e "s/[[:space:]]/_/g")"
    # Only show the first 12 characters
    part_v="${part_v:0:12}"
    ;;
  fat*|vfat|FAT*|VFAT)
    # Get volume name, and replace space with "_" to avoid dialog failure.
    part_v="$(LC_ALL=C dosfslabel $part_w | sed -e "s/^[[:space:]]*//g" -e "s/[[:space:]]*$//g" -e "s/[[:space:]]/_/g")"
    # Only show the first 12 characters
    part_v="${part_v:0:12}"
    ;;
  esac

  if [ -n "$part_v" ]; then
    echo "$part_v"
  fi 
} # end of get_part_vol_name
#
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="$(LC_ALL=C 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_vendor_product_name() {
  # dev_model_shown is a global variable
  local autoproductname vendor_name product_name

  # Find the machine product name
  # We have to format the output of dmidecode, otherwise the name might not be able to be used as a dir name. Therefore the special characters, e.g. space,,,.,:)({}[] must have to be filtered.
  vendor_name="$(LC_ALL=C dmidecode -s system-manufacturer 2>/dev/null | head -n 1 | sed -r -e "s/[[:space:]]+/_/g" -e "s/(:|,|\.|\(|\)|\{|\}|\[|\])//g")"
  product_name="$(LC_ALL=C dmidecode -s system-product-name 2>/dev/null | head -n 1 |  sed -r -e "s/[[:space:]]+/_/g" -e "s/(:|,|\.|\(|\)|\{|\}|\[|\])//g")"
  if [ -z "$vendor_name" -o "$vendor_name" = "To Be Filled By O.E.M." ]; then
    vendor_name="unknown_vendor"
  fi
  if [ -z "$product_name" -o "$product_name" = "To Be Filled By O.E.M." ]; then
    product_name="unknown_product"
  fi
  autoproductname="${vendor_name}_${product_name}"
  # Remove the \n in the end
  autoproductname="$(echo $autoproductname | sed -e "s|\\\n$||g")"
  echo "$autoproductname"
} # end of get_vendor_product_name
#
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, img_comp_format 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"
 
  img_comp_format="" 
  # (1)
  comp_prog_ref="$(echo "$imgf" | grep -Eo -- ".ptcl-img.(gz|bz2|lzo|lzma|xz|lzip|lrz|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 pixz &>/dev/null; then
                cat_prog="pixz -d"
                zip_stdin_cmd="pixz $extra_pixz_opt"
                unzip_stdin_cmd="pixz -d"
              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
              ;;
      lrz|lrzip)     
              # Although lrzip uses "-p value      Set processor count to override number of threads" to assign the CPU number, however, from the manual of lrzip:
              # -p value
              # Set  the  number  of  processor count to determine the number of
              # threads to run.  Normally lrzip will scale according to the num‐
              # ber  of  CPUs  it detects. Using this will override the value in
              # case you wish to use less CPUs to either decrease  the  load  on
              # your  machine,  or  to improve compression. Setting it to 1 will
              # maximise compression but will not attempt to use more  than  one
              # CPU.
              # Therefore there is no need to assign that.
              cat_prog="lrzip -q -d -o -" 
              zip_stdin_cmd="lrzip -q - $extra_lzip_opt"
              unzip_stdin_cmd="lrzip -q -d -o -"
              ;;
    esac
  fi
  # img_comp_format is global variable for later reference use.
  img_comp_format="$comp_prog"
  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." | tee --append ${OCS_LOGFILE}
      [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
      echo "$msg_program_stop." | tee --append ${OCS_LOGFILE}
      [ "$save_restore_error_log" = "yes" ] && copy_error_log
      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 "LRZIP compressed data")" ]; then
      # Although lrzip uses "-p value      Set processor count to override number of threads" to assign the CPU number, however, from the manual of lrzip:
      # -p value
      # Set  the  number  of  processor count to determine the number of
      # threads to run.  Normally lrzip will scale according to the num‐
      # ber  of  CPUs  it detects. Using this will override the value in
      # case you wish to use less CPUs to either decrease  the  load  on
      # your  machine,  or  to improve compression. Setting it to 1 will
      # maximise compression but will not attempt to use more  than  one
      # CPU.
      # Therefore there is no need to assign that.
      cat_prog="lrzip -q -d -o -" 
      zip_stdin_cmd="lrzip -q - $extra_lzip_opt"
      unzip_stdin_cmd="lrzip -q -d -o -"
    elif [ -n "$(file -Ls $imgf | grep -i "XZ compressed data")" ]; then
      # Ex: sda1.ntfs-ptcl-img.xz.aa: XZ compressed data
      if [ "$cpu_no" -gt 1 ] && \
         [ "$use_parallel_decompression" = "yes" ] && \
         type pixz &>/dev/null; then
        cat_prog="pixz -d"
        zip_stdin_cmd="pixz $extra_pixz_opt"
        unzip_stdin_cmd="pixz -d"
      else
        cat_prog="xzcat"
        zip_stdin_cmd="xz -c $extra_xz_opt"
        unzip_stdin_cmd="unxz -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." | tee --append ${OCS_LOGFILE}
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo "$msg_program_stop." | tee --append ${OCS_LOGFILE}
    [ "$save_restore_error_log" = "yes" ] && copy_error_log
    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*|gunzip*|zcat*)              suffix="gz";;
    bzip2*|pbzip2*|lbzip2*|bzcat*|bunzip2*) suffix="bz2";;
    lzop*)                                  suffix="lzo";;
    lzma*|lzcat*|unlzma*)                   suffix="lzma";;
    xz*|pixz*|xzcat*|pxz*|unxz*)            suffix="xz";;
    lzip*|plzip*)                           suffix="lzip";;
    lrzip*)                                 suffix="lrz";;
  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!" | tee --append ${OCS_LOGFILE}
    [ -n "$prompt_msg" ] && echo $prompt_msg
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo "$msg_program_stop!" | tee --append ${OCS_LOGFILE}
    [ -f "$diskfull_test" ] && rm -f $diskfull_test
    echo -n "$msg_press_enter_to_continue..."
    read
    # Saving mode, always copy error log to image dir.
    copy_error_log
    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
    [ -e "$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 "$swap_dev swap" >> "$(LC_ALL=C dirname $output_swap_info)"/dev-fs.list
  echo $msg_delimiter_star_line
} # end of output_swap_partition_uuid_label
#
get_split_img_1st_chunk() {
  # Function to get the 1st chunk of split image. The image might be split as
  # sda1.ext4-ptcl-img.gz.aa, sda1.ext4-ptcl-img.gz.ab, sda1.ext4-ptcl-img.gz.ac...  or
  # sda1.ext4-ptcl-img.gz.aaa, sda1.ext4-ptcl-img.gz.aab, sda1.ext4-ptcl-img.gz.aac...
  local wkd="$1"
  local f_prefix="$2"
  local ic
  if [ -z "$wkd" -o ! -d "$wkd" -o -z "$f_prefix" ]; then
    return 1
  fi
  for ic in $wkd/${f_prefix}*; do
    if [ -e "$ic" ]; then
      echo "$ic"
      break
    fi
  done
} # end of get_split_img_1st_chunk
#
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_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 -a $split_suf_len -b ${VOL_LIMIT}MB - $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"
  [ -e "$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=$(to_filename $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." | tee --append ${OCS_LOGFILE}
    echo "$msg_program_stop." | tee --append ${OCS_LOGFILE}
    # Saving mode, always copy error log to image dir.
    copy_error_log
    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 | tee --append ${OCS_LOGFILE}
  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 | tee --append ${OCS_LOGFILE}
  echo "Use partclone with $compress_prog_prompt to save the image." | tee --append ${OCS_LOGFILE}
  case "$VOL_LIMIT" in
    [1-9]*)
       echo "Image file will be split with size limit $VOL_LIMIT MB." | tee --append ${OCS_LOGFILE} ;;
    *)
       echo "Image file will not be split." | tee --append ${OCS_LOGFILE} ;;
  esac
  echo $msg_delimiter_star_line | tee --append ${OCS_LOGFILE}
  [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
  echo "If this action fails or hangs, check:" | tee --append ${OCS_LOGFILE}
  echo "* Is the disk full ?"  | tee --append ${OCS_LOGFILE}
  [ "$(root_over_nfs)" = "yes" ] && echo "* Network connection and NFS service." | tee --append ${OCS_LOGFILE}
  [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
  echo $msg_delimiter_star_line | tee --append ${OCS_LOGFILE}

  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.

  split_error="$(mktemp /tmp/split_error.XXXXXX)"
  trap "[ -f "$TMP" ] && rm -f $split_error" HUP INT QUIT TERM EXIT

  cmd_partclone="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)
       cmd_partclone="${cmd_partclone} | split -a $split_suf_len -b ${VOL_LIMIT}MB - $tgt_dir/$(to_filename ${tgt_file}).${fs_pre}-img.${comp_suf}. 2> $split_error"
       ;;
    *)
       cmd_partclone="${cmd_partclone} > $tgt_dir/$(to_filename ${tgt_file}).${fs_pre}-img.${comp_suf} 2> $split_error"
       ;;
  esac
  echo "Run partclone: $cmd_partclone" | tee --append ${OCS_LOGFILE}
  LC_ALL=C eval "(${cmd_partclone} && exit \${PIPESTATUS[0]})"
  rc="$?"
  cat "${partclone_img_info_tmp}" >> ${OCS_LOGFILE}
  end_time="$(date +%s%N)"
  if [ -s "$split_error" ]; then
    cat $split_error | tee --append ${OCS_LOGFILE}
  fi
  rm -f $split_error
  # 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/$(to_filename ${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
  [ $rc -eq 0 ] && rc=$?
  if [ $rc -eq 0 ]; then
    # no preparation time, it's accurate enough.
    echo ">>> Time elapsed: $time_elapsed secs (~ $time_elapsed_in_min mins)" | tee --append ${OCS_LOGFILE}
    # For better security
    chmod 600 $tgt_dir/$(to_filename ${tgt_file}).${fs_pre}-img.${comp_suf}* 2>/dev/null
  else
    if ls $tgt_dir/$(to_filename ${tgt_file}).${fs_pre}-img.${comp_suf}* &>/dev/null; then
      rm -f $tgt_dir/$(to_filename ${tgt_file}).${fs_pre}-img.${comp_suf}*
    fi
  fi
  # 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;"

  if [ $rc -eq 0 ]; then
    echo $msg_delimiter_star_line | tee --append ${OCS_LOGFILE}
    echo "Finished saving $source_dev as $tgt_dir/$(to_filename ${tgt_file}).${fs_pre}-img.${comp_suf}" | tee --append ${OCS_LOGFILE}
  fi
  return $rc
} #  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." | tee --append ${OCS_LOGFILE}
  case "$VOL_LIMIT" in
    [1-9]*)
       echo "Image file will be split with size limit $VOL_LIMIT MB." | tee --append ${OCS_LOGFILE} ;;
    *)
       echo "Image will not be split." | tee --append ${OCS_LOGFILE} ;;
  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)"
  cmd_partimage="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)
       cmd_partimage="${cmd_partimage} | split -a $split_suf_len -b ${VOL_LIMIT}MB - $tgt_dir/${tgt_file}."
       ;;
    *)
       cmd_partimage="${cmd_partimage} > $tgt_dir/${tgt_file}"
       ;;
  esac
  echo "Run partimage: $cmd_partimage" | tee --append ${OCS_LOGFILE}
  LC_ALL=C eval "(${cmd_partimage} && exit \${PIPESTATUS[0]})"
  rc="$?"
  end_time="$(date +%s%N)"
  calculate_elapsed_time $start_time $end_time
  echo ">>> Time elapsed: $time_elapsed secs (~ $time_elapsed_in_min mins)" | tee --append ${OCS_LOGFILE}
  # 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" | tee --append ${OCS_LOGFILE}
  return $rc
} # end of save_part_by_partimage
#
save_part_by_dd() {
  local partclone_img_info_tmp IP s2s_img_prog_for_save dd_save_extra_opt
  # 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
  #
  # If file system is BSD_slice, we make use of this function to save the BSD slice data
  # dd if=/dev/sda1 of=slice1-table.dd bs=512 count=16
  # Ref: http://fuse4bsd.creo.hu/localcgi/man-cgi.cgi?bsdlabel+8
  if [ "$part_fs" = "BSD_slice" ]; then
    # For BSD Slice, we force to use dd since we need to dump 16 sectors like:
    # dd if=/dev/sda1 of=slice1-table.dd bs=512 count=16
    s2s_img_prog_for_save="dd"
    dd_save_extra_opt="bs=512 count=16"
  else
    s2s_img_prog_for_save="$S2S_IMAGE_PROG_IN_OCS"
    dd_save_extra_opt=""
  fi
  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 | tee --append ${OCS_LOGFILE}
  echo "Checking the disk space... " && disk_full_test $image_name_
  [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
  echo "$msg_fs_not_supported_by_partimage_ntfsclone: $part_fs" | tee --append ${OCS_LOGFILE}
  echo "$msg_cons_for_dd_clone" | tee --append ${OCS_LOGFILE}
  echo "$msg_will_be_inefficent_and_slow..."
  case "$s2s_img_prog_for_save" in
  dd)
    echo "$msg_use_this_method_to_save_img: dd + $compress_prog_prompt" | tee --append ${OCS_LOGFILE}
    echo "$msg_status_report_is_very_primitive..." | tee --append ${OCS_LOGFILE}
    ;;
  partclone)
    echo "$msg_use_this_method_to_save_img: partclone.dd + $compress_prog_prompt" | tee --append ${OCS_LOGFILE}
    ;;
  esac
  [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
  case "$VOL_LIMIT" in
    [1-9]*)
       echo "Image file will be split with size limit $VOL_LIMIT MB." | tee --append ${OCS_LOGFILE} ;;
    *)
       echo "Image file will not be split." | tee --append ${OCS_LOGFILE} ;;
  esac
  [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
  echo "If this action fails or hangs after several minutes, check:" | tee --append ${OCS_LOGFILE}
  echo "* Is the disk full ?" | tee --append ${OCS_LOGFILE}
  [ "$(root_over_nfs)" = "yes" ] && echo "* Network connection and NFS service."
  [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
  echo $msg_delimiter_star_line | tee --append ${OCS_LOGFILE}

  case "$s2s_img_prog_for_save" 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" | tee --append ${OCS_LOGFILE}
    echo $msg_delimiter_star_line | tee --append ${OCS_LOGFILE}
    # 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)"
    dd_retcode_file="$(mktemp /tmp/dd_retcode.XXXXXX)"
    echo 1 > $dd_retcode_file
    #
    ( 
      (
        LC_ALL=C dd bs=1M if=$source_dev $dd_save_extra_opt
        echo $? > $dd_retcode_file
      ) \
      | $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 -a $split_suf_len -b ${VOL_LIMIT}MB - $tgt_dir/$(to_filename ${tgt_file}).dd-img.
            ;;
         *)
            cat - > $tgt_dir/$(to_filename ${tgt_file}).dd-img
            ;;
       esac
      )
    ) 2>&1 \
    | tee $dd_img_info_tmp
    tail -n 30 "${dd_img_info_tmp}" >> ${OCS_LOGFILE}
    dd_retcode="$(cat $dd_retcode_file)"
    if [ "$dd_retcode" = "0" ]; then
      echo "dd successfully cloned the device ($source_dev) to the image (-)" | tee --append ${OCS_LOGFILE}
    fi
    rm -f $dd_retcode_file
    rc=$dd_retcode

    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/$(to_filename ${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/$(to_filename ${tgt_file}).dd-img is incomplete. You have to increase the disk space, then start Clonzilla save mode again."
    get_dd_image_info $dd_img_info_tmp $start_time $end_time
    [ $rc -eq 0 ] && rc=$?
    [ -e "$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.

    split_error="$(mktemp /tmp/split_error.XXXXXX)"
    trap "[ -f "$TMP" ] && rm -f $split_error" HUP INT QUIT TERM EXIT

    cmd_partclone="partclone.dd $PARTCLONE_SAVE_OPT -L $partclone_img_info_tmp -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)
         cmd_partclone="${cmd_partclone} | split -a $split_suf_len -b ${VOL_LIMIT}MB - $tgt_dir/$(to_filename ${tgt_file}).dd-img. 2> $split_error"
         ;;
      *)
         cmd_partclone="${cmd_partclone} > $tgt_dir/$(to_filename ${tgt_file}).dd-img 2> $split_error"
         ;;
    esac
    echo "Run partclone: $cmd_partclone" | tee --append ${OCS_LOGFILE}
    LC_ALL=C eval "(${cmd_partclone} && exit \${PIPESTATUS[0]})"
    rc="$?"
    cat "${partclone_img_info_tmp}" >> ${OCS_LOGFILE}
    end_time="$(date +%s%N)"
    if [ -s "$split_error" ]; then
      cat $split_error | tee --append ${OCS_LOGFILE}
    fi
    rm -f $split_error
    # 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/$(to_filename {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
    [ $rc -eq 0 ] && rc=$?
    ;;
  esac

  if [ $rc -eq 0 ]; then
    # no preparation time, it's accurate enough.
    echo ">>> Time elapsed: $time_elapsed secs (~ $time_elapsed_in_min mins)" | tee --append ${OCS_LOGFILE}
    # For better security
    chmod 600 $tgt_dir/$(to_filename ${tgt_file}).dd-img* 2>/dev/null
  else
    if ls $tgt_dir/$(to_filename ${tgt_file}).dd-img* &>/dev/null; then
      rm -f $tgt_dir/$(to_filename ${tgt_file}).dd-img*
    fi
  fi

  # Put this partition info if we can find it
  # (1) Check if it's VMware vmkcore partition
  # (2) Check if it's BSD swap partition
  hdtmp="$(get_diskname $tgt_file)"
  part_id="$(get_part_id_from_sf_format $tgt_dir/$(to_filename ${hdtmp})-pt.sf ${tgt_file})"
  # Find if it's a BSD swap partition
  bsd_swap_partition="$(get_bsd_swap_partition /dev/$hdtmp)"
  if [ -n "$(LC_ALL=C echo "$part_id" | grep -Ewi "fc")" ]; then
    echo 'partition=VMware_vmkcore_partition' > $tgt_dir/$(to_filename ${tgt_file}).dd-img.info
  elif [ -n "$(LC_ALL=C echo "$part_id" | grep -Ewi "(a5|a6|a9)")" ]; then
    # Partition ID a5 (FreeBSD), a6 (OpenBSD), a9 (NetBSD) are slices
    echo 'partition=BSD_slice' > $tgt_dir/$(to_filename ${tgt_file}).dd-img.info
  elif [ -n "$(LC_ALL=C echo "$bsd_swap_partition" | grep -Ewo "/dev/$tgt_file")" ]; then
    echo 'partition=BSD_swap_partition' > $tgt_dir/$(to_filename ${tgt_file}).dd-img.info
  fi

  # 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 | tee --append ${OCS_LOGFILE}
  echo "Finished saving $source_dev as $tgt_dir/$(to_filename ${tgt_file}).dd-img" | tee --append ${OCS_LOGFILE}
  return $rc
} # 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 | tee --append ${OCS_LOGFILE}
  fi

  if [ "$fsck_src_part_auto" = "yes" ]; then
    # It's auto (actually say "yes" to all), so we add "-y" for fsck.
    fsck_extra_opt="-y"
  fi
  if [ "$fsck_src_part_intr" = "yes" -o \
       "$fsck_src_part_auto" = "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 $fsck_extra_opt
    echo $msg_delimiter_star_line
  fi

  echo "Starting saving $source_dev as $tgt_dir/${tgt_file}.XXX..." | tee --append ${OCS_LOGFILE}
  if [ -z "$part_fs" ]; then
    part_fs_shown="Unknown or unsupported"
  else
    part_fs_shown="$part_fs"
  fi
  echo "$source_dev $part_fs" >> $tgt_dir/dev-fs.list
  [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
  echo "$source_dev filesystem: $part_fs_shown." | tee --append ${OCS_LOGFILE}
  [ "$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
  rc=$?
  echo $msg_delimiter_star_line | tee --append ${OCS_LOGFILE}
  if [ "$rc" -ne 0 ]; then
     [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
     echo "Failed to save partition $source_dev." | tee --append ${OCS_LOGFILE}
     [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
     echo "$msg_press_enter_to_continue..."
     read
     # Saving mode, always copy error log to image dir.
     copy_error_log
  fi
  # We put the complete log in clonezilla-img file, not here.
  #copy_log ${tgt_dir} ${source_dev}
  return $rc
} # end of image_save

# check if the user input /dev/hda, /dev/hdb...
check_input_hd() {
    local target_hd="$*"
    if [ -z "$target_hd" ]; then
      echo "No device selected." | tee --append ${OCS_LOGFILE}
      echo "$msg_program_stop."  | tee --append ${OCS_LOGFILE}
      if [ -n "$(echo $ocs_mode_prompt | grep -iw "restore")" ]; then
        [ "$save_restore_error_log" = "yes" ] && copy_error_log
      fi
      exit 1
    fi
    for idisk in $target_hd; do
      if ! is_supported_dev "$idisk"; then
        [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
        echo "\"$idisk\" is an unknown hard drive device. $msg_program_stop." | tee --append ${OCS_LOGFILE}
        [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
        echo -n "Press Enter to exit..." | tee --append ${OCS_LOGFILE}
        read
        if [ -n "$(echo $ocs_mode_prompt | grep -iw "restore")" ]; then
          [ "$save_restore_error_log" = "yes" ] && copy_error_log
        fi
        exit 1
      fi
    done
}
check_input_partition() {
    local tgt_parts="$*"
    [ -z "$tgt_parts" ] && echo "No device selected!" && exit 1
    for ipart in $tgt_parts; do
      if ! is_partition "$ipart"; then
        [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
        echo "\"$ipart\" is an unknown partition device. $msg_program_stop." | tee --append ${OCS_LOGFILE}
        [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
        echo -n "Press Enter to exit..."
        read
        if [ -n "$(echo $ocs_mode_prompt | grep -iw "restore")" ]; then
          [ "$save_restore_error_log" = "yes" ] && copy_error_log
        fi
        exit 1
      fi
    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\"!!!" | tee --append ${OCS_LOGFILE}
     echo "$msg_check_if_this_hd_exists_and_more: $target_hd_2_be_checked" | tee --append ${OCS_LOGFILE}
     [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
     echo "$msg_the_partition_in_the_system_now:" | tee --append ${OCS_LOGFILE}
     [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
     echo $msg_delimiter_star_line | tee --append ${OCS_LOGFILE}
     cat /proc/partitions | tee --append ${OCS_LOGFILE}
     echo $msg_delimiter_star_line | tee --append ${OCS_LOGFILE}
     echo "$msg_program_stop!!!" | tee --append ${OCS_LOGFILE}
     [ "$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." | tee --append ${OCS_LOGFILE}
    echo "Skip checking HD." | tee --append ${OCS_LOGFILE}
    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\"." | tee --append ${OCS_LOGFILE}
     [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
     echo $msg_delimiter_star_line | tee --append ${OCS_LOGFILE}
     echo "The disk and partition in this system:" | tee --append ${OCS_LOGFILE}
     cat $partition_table | tee --append ${OCS_LOGFILE}
     echo $msg_delimiter_star_line | tee --append ${OCS_LOGFILE}
     echo "Check if the partition $check_target_part really exists, otherwise maybe the kernel is too old." | tee --append ${OCS_LOGFILE}
     echo "$msg_program_stop." | tee --append ${OCS_LOGFILE}
     echo -n "$msg_press_enter_to_continue..."
     read
     [ "$save_restore_error_log" = "yes" ] && copy_error_log
     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 dsk_ file_2_parse
    # 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 -qiEw "Id=82" $partition_table 2>/dev/null; then
       fuzzy_parts="$(LC_ALL=C grep -iEw "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
         dsk_=${i#/dev/*}
         # For ocs-onthefly, the file to be checked is tgt-blkdev.list
         if [ -e "$workdir/blkdev.list" -o -e "$workdir/tgt-blkdev.list" ]; then
           # If blkdev.list exits, we use it first. Since it's more precise when dm exists.
           # TODO: Check if cciss partition OK or not.
	   if [ -e "$workdir/tgt-blkdev.list" ]; then
             file_2_parse="$workdir/tgt-blkdev.list"
	   else
             file_2_parse="$workdir/blkdev.list"
	   fi
	   if [ -n "$(LC_ALL=C awk -F" " "/^$(to_sysblock_name ${dsk_})[[:space:]]+/ {print \$5}" \
	            $file_2_parse | grep -i swap)" ]; then
             known_parts="$known_parts $dsk_"
	   fi
         else
           # /dev/sda2 -> sda2 -> 2
	   # dsk_pt is like /home/partimag/sarge-r8/sda-pt.parted
           dsk_pt="$workdir/$(to_filename $(get_diskname $i))-pt.parted"
           # For cciss partition, e.g. /dev/cciss/c0d0p2 will be "p2" which is got from $(get_part_number /dev/cciss/c0d0p2)", here the part_index we want is only number, hence we need to trip the leading characters.
           index="$(LC_ALL=C get_part_number $i | sed -r -e "s|^[^[:digit:]]*||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 [ -z "$part_fs" ]; then  # No swap keyword in parted table for $index
               known_parts="$known_parts $dsk_"
             fi
           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 dev_ file_2_parse
    [ -z "$partition_table" ] && return 1
    if LC_ALL=C grep -qiEw "Id=82" $partition_table 2>/dev/null; then
       fuzzy_parts="$(LC_ALL=C grep -iEw "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
         dsk_=${i#/dev/*}
         # For ocs-onthefly, the file to be checked is tgt-blkdev.list
         if [ -e "$workdir/blkdev.list" -o -e "$workdir/tgt-blkdev.list" ]; then
           # If blkdev.list exits, we use it first. Since it's more precise when dm exists.
           # TODO: Check if cciss partition OK or not.
	   if [ -e "$workdir/tgt-blkdev.list" ]; then
             file_2_parse="$workdir/tgt-blkdev.list"
	   else
             file_2_parse="$workdir/blkdev.list"
	   fi
	   if [ -n "$(LC_ALL=C awk -F" " "/^$(to_sysblock_name ${dsk_})[[:space:]]+/ {print \$5}" \
	            $file_2_parse | grep -i swap)" ]; then
             known_parts="$known_parts $dsk_"
	   fi
         else
           # /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/$(to_filename $(get_diskname $i))-pt.parted"
           else
             # Special case for ocs-onthefly, the file name is like tgt_pt.sf
             dsk_pt="$workdir/tgt-pt.parted"
           fi
	   # For /dev/sda2 -> 2
           # For cciss partition, e.g. /dev/cciss/c0d0p2 will be "p2" which is got from $(get_part_number /dev/cciss/c0d0p2)", here the part_index we want is only number, hence we need to trip the leading characters.
           index="$(LC_ALL=C get_part_number $i | sed -r -e "s|^[^[:digit:]]*||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
         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

    get_not_busy_disks_or_parts partition "$chosen_disk" ""  # we will get dev_list
    echo "Unmounted 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 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
         ;;
       swap)
         # We want swap partition only, so only include swap.
         [ -z "$(echo "$part_type" | grep -i 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 p_type rc
  # found_grub_partition is a global variable

  # Here we do not search LVM, since grub partition won't be on LVM.
  get_partition_list false "$select_disk"
  # The partition_list is got from get_partition_list.

  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
    # 2012/06/07, due to an issue about mount: it will hang when mounting extended partition. We have to skip mounting extended partition.
    p_type="$(LC_ALL=C ocs-get-part-info "$grub_p" type filesystem 2>/dev/null)"
    rc=$?
    [ "$rc" -gt 0 ] && continue 
    case "$p_type" in *[Ss][Ww][Aa][Pp]*|*extended*) continue ;; esac
    #
    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/"
    # For Fedora >= 18, the grub dir is named as "grub2", while for other distributions,
    # "grub" is still used.
    if [ -d "$hd_img/boot/grub" -o \
	 -d "$hd_img/grub/" -o \
	 -d "$hd_img/boot/grub2" -o \
	 -d "$hd_img/grub2/" ]; 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
    # "sleep 0.5" was added to avoid ocs-restore-mdisks run too fast error.
    # Without it, it might show this error when ocs-restore-mdisks is run:
    # rmdir: failed to remove `/tmp/hd_img.weAw4i': Device or resource busy
    [ -d "$hd_img/boot" -a -n "$hd_img" ] && (sleep 0.5; rmdir $hd_img/boot)
    [ -d "$hd_img" -a -n "$hd_img" ] && (sleep 0.5; 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 p_type rc
  [ -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=$(get_part_list $partition_table)
  for ipartition in $partition_list; do
    root_partition="/dev/$ipartition"
    # process the boot loader
    # 2012/06/07, due to an issue about mount: it will hang when mounting extended partition. We have to skip mounting extended partition.
    p_type="$(LC_ALL=C ocs-get-part-info "$root_partition" type filesystem 2>/dev/null)"
    rc=$?
    [ "$rc" -gt 0 ] && continue 
    case "$p_type" in *[Ss][Ww][Aa][Pp]*|*extended*) continue ;; esac
    #
    mount $root_partition $hd_img >/dev/null 2>&1
    mrc=$?
    # root partition must have /boot, however, we have to skip the Clonezilla live boot media because it also includes /boot/.
    if [ -d "$hd_img/boot/" -a ! -e "$hd_img/Clonezilla-Live-Version" ]; 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" | tee --append ${OCS_LOGFILE}
  else
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
    echo "No inputted devices found!" | tee --append ${OCS_LOGFILE}
    echo "$msg_program_stop!!!" | tee --append ${OCS_LOGFILE}
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    # Saving mode, always copy error log to image dir.
    copy_error_log
    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!" | tee --append ${OCS_LOGFILE}
           echo -n "$msg_press_enter_to_continue..."
           read
           # Saving mode, always copy error log to image dir.
           copy_error_log
           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 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/grub1-pkgs-list.txt" ]; then
    grub1_pkg_name="$(LC_ALL=C awk -F":" '{print $1}' $DRBL_SCRIPT_PATH/pkg/grub/grub1-pkgs-list.txt | sed -e "s/^[[:space:]]*//g")"
  fi
  if [ -e "$DRBL_SCRIPT_PATH/pkg/grub/grub2-pkgs-list.txt" ]; then
    grub2_pkg_name="$(LC_ALL=C awk -F":" '{print $1}' $DRBL_SCRIPT_PATH/pkg/grub/grub2-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_2_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
  grub_1_installed="yes"
  for i in $grub1_pkg_name; do
    if ! dpkg -L ${i} &>/dev/null; then
      grub_1_installed="no"
      break
    fi
  done
  # Check if grub 2 installed
  grub_2_installed="yes"
  for i in $grub2_pkg_name; do
    if ! dpkg -L ${i} &>/dev/null; then
      grub_2_installed="no"
    fi
  done
  if [ "$grub_2_installed" = "yes" ]; then
    echo "Grub 2 is installed in Clonezilla live or SE. We need grub 1. Removing grub 2 from Clonezilla live/SE..."
    # Preset postrm_purge_boot_grub otherwise user will be asked, which is annoying.
cat <<-DEB_END >> /var/cache/debconf/config.dat
Name: grub-pc/postrm_purge_boot_grub
Template: grub-pc/postrm_purge_boot_grub
Value: false
Owners: grub-pc
Flags: seen
DEB_END
    # //NOTE// grub-common is required by both grub-legacy and grub2, so we can not purge it.
    # Here we use "apt-get purge" instead of "dpkg --purge" since some packages might depend on ${grub2_pkg_name}. E.g. For Ubuntu 11.10, grub-gfxpayload-lists depends on grub-pc.
    LC_ALL=C apt-get -y purge $(echo ${grub2_pkg_name} | sed -e "s/grub-common//g")
  fi
  if [ "$grub_1_installed" = "no" ]; then
    echo "Installing grub 1 on Clonezilla live/SE now..."
    for i in $DRBL_SCRIPT_PATH/pkg/grub/grub1/*.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_2_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
  grub_1_installed="yes"
  for i in $grub1_pkg_name; do
    if ! dpkg -L ${i} &>/dev/null; then
      grub_1_installed="no"
      break
    fi
  done
  # Check if grub 2 installed
  grub_2_installed="yes"
  for i in $grub2_pkg_name; do
    if ! dpkg -L ${i} &>/dev/null; then
      grub_2_installed="no"
    fi
  done
  if [ "$grub_1_installed" = "yes" ]; then
    echo "Grub 1 is installed in Clonezilla live/SE. We need grub 2. Removing grub 1 from Clonezilla live/SE..."
    # //NOTE// grub-common is required by both grub-legacy and grub2, so we can not purge it.
    # Here we use "apt-get purge" instead of "dpkg --purge" since some packages might depend on ${grub1_pkg_name} which we do not know...
    LC_ALL=C apt-get -y purge $(echo ${grub1_pkg_name} | sed -e "s/grub-common//g")
  fi
  if [ "$grub_2_installed" = "no" ]; then
    echo "Installing grub 2 on Clonezilla live/SE now..."
    for i in $DRBL_SCRIPT_PATH/pkg/grub/grub2/*.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
  # E.g. for "grub-install --help" output
  # grub-install (GRUB) 1.98+20100804-14+squeeze1  (Debian Squeeze)
  # grub-install (GNU GRUB 1.98-1ubuntu12)  (Ubuntu 10.04)
  # grub-install (GRUB) 1.99-14  (Debian Sid on 2012/Jan/25)
  echo "Checking grub-install version..."
  if [ -n "$(LC_ALL=C $grub_install_exec --version 2>&1 | grep -iE "^grub-install.*GRUB" | grep -Eo "[[:space:]]0.")" ]; then
    grub_version="1"
  elif [ -n "$(LC_ALL=C $grub_install_exec --version 2>&1 | grep -iE "^grub-install.*GRUB" | grep -Eo "[[:space:]][12].")" ]; then
    grub_version="2"
  else
    grub_version="x"
  fi
  if [ "$required_ver" != "$grub_version" ]; then
   rc=1
  else
   rc=0
  fi
  return $rc
} # end of check_grub_install_version
#
test_run_grub1_from_restored_os(){
  # The run_grub1_from_restored_os_mode we have to decide to run grub1 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 grub1 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_grub1_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_grub1_from_restored_os." | tee --append ${OCS_LOGFILE}
    echo "Program terminated." | tee --append ${OCS_LOGFILE}
    [ "$save_restore_error_log" = "yes" ] && copy_error_log
    umount $hd_img/boot/ &>/dev/null
    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 grub1 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
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "Yes, we are able to chroot the restored OS partition $root_prt."
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    run_grub1_from_restored_os_mode="yes"
  else
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "No, we are not able to chroot the restored OS partition $root_prt."
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    run_grub1_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_grub1_from_restored_os
#
run_grub1_from_restored_os() {
  local mnt_pnt grub1_run_sh ival rc_
  local grub1_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 grub1_hd_p="$3" # e.g. /dev/sda, the disk to install grub boot loader, or like /dev/sda2, the partition where grub boot loader is installed.
  for i in grub1_prt root_prt grub1_hd_p; do
    eval ivar=\$${i}
    if [ -z "$ivar" ]; then
      echo "No \"$i\" assigned in function run_grub1_from_restored_os!"
      echo "Program terminated!"
      exit 1
    fi
  done
  echo "Re-installing grub1 on disk/partition $grub1_hd_p with grub1 dir in partition $grub1_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

  grub1_run_sh="$(mktemp $mnt_pnt/grub1-sh-XXXXXX)"
  cat <<-SH_END > $grub1_run_sh
#!/bin/bash
export LC_ALL=C
export PATH=/sbin/:/usr/sbin:/bin/:/usr/bin
# We need to find a grub1-related file so we are sure grub1 exists.
if ! type grub-install &>/dev/null; then
   echo "No grub program (grub-install) was found in the restored OS!"
   echo "Skipping grub1 reinstallation!"
   exit 1
fi
rm -f /boot/grub/device.map
# touch a file to avoid grep error in grub-install
touch /boot/grub/device.map
grub-install --recheck --no-floppy ${grub1_hd_p}
SH_END
  chmod 755 $grub1_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 [ "$grub1_prt" != "$root_prt" ]; then
     mkdir -p $mnt_pnt/boot/grub
     chroot $mnt_pnt mount $grub1_prt /boot/
     diff_grub_pt_flag="yes"
  fi
  # Run grub-install
  LC_ALL=C chroot $mnt_pnt /"$(basename $grub1_run_sh)"
  rc_=$?
  [ -e "$grub1_run_sh" ] && rm -f $grub1_run_sh

  # Remove the device.map in case it provents the restored OS fails to run update-grub1 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

  check_grub1_config_file $mnt_pnt
  #
  if [ "$diff_grub_pt_flag" = "yes" ]; then
    chroot $mnt_pnt umount /boot/
    rmdir $mnt_pnt/boot/grub 2>/dev/null
  fi

  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_grub1_from_restored_os
#
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." | tee --append ${OCS_LOGFILE}
    echo "Program terminated." | tee --append ${OCS_LOGFILE}
    [ "$save_restore_error_log" = "yes" ] && copy_error_log
    umount $hd_img/boot/ &>/dev/null
    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
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "Yes, we are able to chroot the restored OS partition $root_prt."
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    run_grub2_from_restored_os_mode="yes"
  else
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "No, we are not able to chroot the restored OS partition $root_prt."
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    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_p="$3" # e.g. /dev/sda, the disk to install grub boot loader, or like /dev/sda2, the partition where grub boot loader is installed.
  for i in grub2_prt root_prt grub2_hd_p; do
    eval ivar=\$${i}
    if [ -z "$ivar" ]; then
      echo "No \"$i\" assigned in function run_grub2_from_restored_os." | tee --append ${OCS_LOGFILE}
      echo "Program terminated." | tee --append ${OCS_LOGFILE}
      [ "$save_restore_error_log" = "yes" ] && copy_error_log
      exit 1
    fi
  done
  echo "Re-installing grub2 on disk/partition $grub2_hd_p 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
# We need to find a grub2-related file so we are sure grub2 exists.
# update-grub2 is from Debian/Ubuntu, grub2-install is from Fedora 16.
if ! type update-grub2 &>/dev/null && ! type grub2-install &>/dev/null; then
   echo "No grub2 program (e.g. update-grub2 or grub2-install) is found in the restored OS!"
   echo "Skipping grub2 reinstallation!"
   exit 1
fi
if type grub2-install &>/dev/null; then
  # For Fedora 16 system
  grub_install_cmd="grub2-install"
else 
  # For Debian-like system
  grub_install_cmd="grub-install"
fi
rm -f /boot/grub/device.map
\$grub_install_cmd --force --recheck --no-floppy ${grub2_hd_p}
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
  LC_ALL=C 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
#
do_run_update_initrd_from_restored_os() {
  local mnt_pnt mkinitrd_run_sh mkinitrd_cmd 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_p="$3" # e.g. /dev/sda, the disk to install grub boot loader, or like /dev/sda2, the partition where grub boot loader is installed.
  local kver_initrd req_mod_for_vm_candi
  for i in grub2_prt root_prt grub2_hd_p; do
    eval ivar=\$${i}
    if [ -z "$ivar" ]; then
      echo "No \"$i\" assigned in function run_grub2_from_restored_os." | tee --append ${OCS_LOGFILE}
      echo "Program terminated." | tee --append ${OCS_LOGFILE}
      [ "$save_restore_error_log" = "yes" ] && copy_error_log
      exit 1
    fi
  done
  echo "Re-creating initrd on disk $grub2_hd_p with boot 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

  # 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

  kver_initrd="$(LC_ALL=C find $mnt_pnt/boot/ -iname "vmlinuz*" -print | sort -r -V | head -n 1 | xargs basename | sed -r -e "s/^vmlinuz-//g")"
  # mkinitrd --with ata_piix --with dm-mirror --with dm-mod --with dm-snapshot --with dm-zero --with ext3 --with jbd --with libata --with mptbase --with mptsas --with mptscsih --with mptscsi --with mptspi --with scsi_mod --with sd_mod /boot/initrd-2.6.9-11.EL.img 2.6.9-11.EL
  req_mod_for_vm_candi="ata_piix dm-mirror dm-mod dm-snapshot dm-zero ext3 ext4 jbd libata mptbase mptsas mptscsih mptscsi mptspi scsi_mod sd_mod"
  mkinitrd_with_opt=""
  for i in $req_mod_for_vm_candi; do
    if [ -n "$(find $mnt_pnt/lib/modules/$kver_initrd/ -name "${i}.ko" -print)" ]
    then
      mkinitrd_with_opt="$mkinitrd_with_opt --with $i"
    fi
  done
  mkinitrd_run_sh="$(mktemp $mnt_pnt/grub2-sh-XXXXXX)"
  mkinitrd_cmd="mkinitrd -f $mkinitrd_with_opt /boot/initrd-${kver_initrd}.img $kver_initrd"
  cat <<-SH_END > $mkinitrd_run_sh
#!/bin/bash
export LC_ALL=C
export PATH=/sbin/:/usr/sbin:/bin/:/usr/bin
$mkinitrd_cmd
SH_END
  chmod 755 $mkinitrd_run_sh
  # Run mkinitrd
  echo "Running the command in the restored OS: $mkinitrd_cmd"
  LC_ALL=C chroot $mnt_pnt /"$(basename $mkinitrd_run_sh)"
  rc_=$?
  [ -e "$mkinitrd_run_sh" ] && rm -f $mkinitrd_run_sh

  #
  if [ "$diff_grub_pt_flag" = "yes" ]; then
    chroot $mnt_pnt umount /boot/
    rmdir $mnt_pnt/boot/grub 2>/dev/null
  fi

  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 do_run_update_initrd_from_restored_os
# 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/$(to_filename ${DEV})-chs.sf" ] && rm -f $target_d/$(to_filename ${DEV})-chs.sf
   for i in cylinders heads sectors; do
     eval num=\$$i
     echo "$i=$num" >> $target_d/$(to_filename ${DEV})-chs.sf
   done
} # end of output_HD_CHS
#
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/$(to_filename ${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/$(to_filename ${DEV})-chs.sf" ]; then
    . $target_d/$(to_filename ${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...
  # //NOTE// Here we only run perl after grep finds the words we want to replace. Otherwise even if it's empty, once perl is run the original file still will be processed, and some version of perl, like 5.10.1-17 in Debian Squeeze, will create a temp file like ".nfs0000000096af2bf700000013" in the image dir, and it won't be removed after it's run.
  local part_file=$1
  if [ -f "$part_file" ]; then
    if [ -n "$(LC_ALL=C grep -E "^Warning: extended partition does not start at a cylinder boundary" $part_file)" ]; then
      LC_ALL=C perl -pi -e "s/^Warning: extended partition does not start at a cylinder boundary.//gi" $part_file
    fi
    if [ -n "$(LC_ALL=C grep -E "^DOS and Linux will interpret the contents differently" $part_file)" ]; then
      LC_ALL=C perl -pi -e "s/^DOS and Linux will interpret the contents differently.//gi" $part_file
    fi
  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 | tee --append ${OCS_LOGFILE}
  # 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 | tee --append ${OCS_LOGFILE}
  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
  local pv_on_dsk pv_on_dsk_flag
  if [ -z "$disk_file" ]; then
    echo "To check partition table, you have to assign disk." | tee --append ${OCS_LOGFILE}
    echo "$msg_program_stop" | tee --append ${OCS_LOGFILE}
    # Saving mode, always copy error log to image dir.
    copy_error_log
    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... " | tee --append ${OCS_LOGFILE}
  parted -s $disk_file print &> $dsk_chk_tmp
  rc=$?
  if [ "$rc" -gt 0 ]; then
    # There is still one possiblity that we need to deal with LVM, i.e. PV is not on partition, 
    pv_on_dsk="$(get_pv_on_disk_from_local_machine)"
    pv_on_dsk_flag="0"
    for ip in $pv_on_dsk; do
      if [ -n "$(LC_ALL=C echo "${disk_file#/dev/*}" | grep -Ew "${ip}")" ]; then
        # Found PV on disk
        pv_on_dsk_flag="1"
        break
      fi
    done
    if [ "$pv_on_dsk_flag" -eq 0 ]; then
      # No PV on disk, it does not make sense for this bare disk.
      disk_name="$(basename $disk_file)"
      [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
      echo "$msg_the_partition_table_in_disk_is_illegal: $disk_file" | tee --append ${OCS_LOGFILE}
      echo "$msg_parted_detect_as_wrong" | tee --append ${OCS_LOGFILE}
      [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
      if [ -e "$dsk_chk_tmp" ]; then
        echo "$msg_error_messages_from_parted_are:" | tee --append ${OCS_LOGFILE}
        echo $msg_delimiter_star_line               | tee --append ${OCS_LOGFILE}
        cat $dsk_chk_tmp                            | tee --append ${OCS_LOGFILE}
        echo $msg_delimiter_star_line               | tee --append ${OCS_LOGFILE}
        rm -f $dsk_chk_tmp
      fi
      [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
      echo "$msg_continue_with_weried_partition_table"      | tee --append ${OCS_LOGFILE}
      [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
      echo -n "$msg_are_u_sure_u_want_to_continue (y/N) " | tee --append ${OCS_LOGFILE}
      read continue_confirm_ans
      case "$continue_confirm_ans" in
           y|Y|[yY][eE][sS])
              echo "Very bad choice."  | tee --append ${OCS_LOGFILE}
              echo "$msg_ok_let_do_it" | tee --append ${OCS_LOGFILE}
              ;;
           *)
              echo "Smart choice."      | tee --append ${OCS_LOGFILE}
              echo "$msg_program_stop." | tee --append ${OCS_LOGFILE}
              # Saving mode, always copy error log to image dir.
              copy_error_log
              exit 1
      esac
    fi
  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. 
  #
  # 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... " | tee --append $OCS_LOGFILE
  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" | tee --append $OCS_LOGFILE
    echo "$msg_does_this_part_table_file_fit_disk: $img_name/$(to_filename ${disk_name})-pt.sf ?" | tee --append $OCS_LOGFILE
    echo "$msg_is_this_disk_too_small: $disk_file ?" | tee --append $OCS_LOGFILE
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    if [ -e "$dsk_chk_tmp" ]; then
      echo "$msg_error_messages_from_parted_are:" | tee --append $OCS_LOGFILE
      echo $msg_delimiter_star_line | tee --append $OCS_LOGFILE
      cat $dsk_chk_tmp | tee --append $OCS_LOGFILE
      echo $msg_delimiter_star_line | tee --append $OCS_LOGFILE
      rm -f $dsk_chk_tmp
    fi
    echo "$msg_program_stop." | tee --append $OCS_LOGFILE
    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." | tee --append $OCS_LOGFILE
    echo "$msg_program_stop." | tee --append $OCS_LOGFILE
    # Saving mode, always copy error log to image dir.
    copy_error_log
    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
#
clean_mbr_partition_table_before_gen_gpt_table() {
  local gpt_disk="$1" # gpt_disk is like: /dev/sdg1
  # If MBR partition table exists, gdisk might fail when creating the new GPT table.
  # Ref: http://sourceforge.net/projects/clonezilla/forums/forum/663168/topic/4657187
  if [ -z "$gpt_disk" ]; then
    echo "No \"$gpt_disk\" in function clean_mbr_partition_table_before_gen_gpt_table!"
    echo "Program terminated!"
    exit 1
  fi
  echo "Running: dd if=/dev/zero of=$gpt_disk bs=512 count=1" | tee --append $OCS_LOGFILE
  LC_ALL=C dd if=/dev/zero of=$gpt_disk bs=512 count=1 2>&1 | tee --append $OCS_LOGFILE
} # end of clean_mbr_partition_table_before_gen_gpt_table
#
create_gpt_table_if_no_table() {
  local gpt_disk="$1" # gpt_disk is like: /dev/sdg1
  # If new disk, then parted should show something like:
  # root@oneiric:~# parted -s /dev/sda print
  # Error: /dev/sda: unrecognised disk label
  # Without an initial GPT table, "sgdisk -l $tgt_dir/${tgt_hd_name}-gpt.gdisk $tgt_hd_file" will fail to reload the saved GPT table. This is before gdisk 0.7.2.
  if [ -z "$gpt_disk" ]; then
    echo "No \"$gpt_disk\" in create_gpt_table_if_no_table!"
    echo "Program terminated!"
    exit 1
  fi
  if [ -n "$(LC_ALL=C parted -s $gpt_disk print | grep -iE "Error:.*: unrecognised disk label")" ]; then
    echo "No initial GPT table on disk $gpt_disk. Create one now by:" | tee --append $OCS_LOGFILE
    echo "parted -s $gpt_disk mklabel gpt" | tee --append $OCS_LOGFILE
    LC_ALL=C parted -s $gpt_disk mklabel gpt | tee --append $OCS_LOGFILE
  fi
} # end of create_gpt_table_if_no_table
#
clean_mbr_gpt_part_table() {
  local dsk_dev="$1"  # Device name like /dev/sdc
  # Clean the MBR and GPT partition table on the destination disk first
  if [ -z "$dsk_dev" ]; then
    echo "No dsk_dev was assigned in function clean_mbr_gpt_part_table!" | tee --append ${OCS_LOGFILE}
    echo "$msg_program_stop." | tee --append ${OCS_LOGFILE}
    # Saving mode, always copy error log to image dir.
    copy_error_log 
    exit 1
  fi
  if [ ! -e "$dsk_dev" ]; then
    echo "Disk \"$tdsk_dev\" not found on the system!" | tee --append ${OCS_LOGFILE}
    echo "$msg_program_stop." | tee --append ${OCS_LOGFILE}
    # Saving mode, always copy error log to image dir.
    copy_error_log 
    exit 1
  fi
  if type sgdisk &>/dev/null; then
    echo "Trying to clean the MBR and GPT partition table on the destination disk first: $dsk_dev" | tee --append ${OCS_LOGFILE}
    LC_ALL=C sgdisk --zap-all $dsk_dev &>/dev/null
    sleep 1
    inform_kernel_partition_table_changed both $dsk_dev | tee --append ${OCS_LOGFILE}
  else
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
    echo "Missing program \"sgdisk\"! Without it, no way to clean the MBR and GPT partition table on disk \"$dsk_dev\"" | tee --append ${OCS_LOGFILE}
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo "$msg_program_stop." | tee --append ${OCS_LOGFILE}
    # Saving mode, always copy error log to image dir.
    copy_error_log 
    exit 1
  fi
} # end of clean_mbr_gpt_part_table
# 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="${tgt_hd_file#/dev/*}"
  local pt_type=""
  local src_disk_size_sec src_disk_size_GB tgt_disk_size_sec tgt_disk_size_GB to_seek do_gpt_dd gdisk_tmp sgdisk_rc
  local ocs_expand_mbr_pt_opt=""

  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..." | tee --append $OCS_LOGFILE
  check_if_disk_busy_before_create_partition $tgt_hd_file

  # src_disk_size_sec, src_disk_size_GB, tgt_disk_size_sec, tgt_disk_size_GB will be got from get_dsk_size_from_img_and_tgt_dsk_size_from_machine 
  get_dsk_size_from_img_and_tgt_dsk_size_from_machine "$tgt_dir" "${tgt_hd_name}" "${tgt_hd_name}"

  if [ "$chk_tgt_disk_size_bf_mk_pt" = "yes" ]; then
    if [ "$src_disk_size_sec" -gt "$tgt_disk_size_sec" ]; then
      echo $msg_delimiter_star_line | tee --append ${OCS_LOGFILE}
      [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
      echo "$msg_destination_disk_too_small" | tee --append ${OCS_LOGFILE}
      echo "$msg_destination_disk_size: $tgt_disk_size_sec sectors ($tgt_disk_size_GB GB)" | tee --append ${OCS_LOGFILE}
      echo "$msg_src_disk_size_from_image: $src_disk_size_sec sectors ($src_disk_size_GB GB)" | tee --append ${OCS_LOGFILE}
      [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
      echo "$msg_program_stop!" | tee --append ${OCS_LOGFILE}
      [ "$save_restore_error_log" = "yes" ] && copy_error_log
      exit 1
    fi
  fi

  clean_mbr_gpt_part_table $tgt_hd_file

  # Decide if the partition is gpt or mbr
  pt_type="$(get_partition_table_type_from_img "$tgt_dir" "${tgt_hd_name}")"

  #
  case "$pt_type" in
  unknown)
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
    echo "Unknown partition table format from the file $tgt_dir/$(to_filename ${tgt_hd_name})-pt.parted!" | tee --append ${OCS_LOGFILE}
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo "$msg_program_stop!" | tee --append ${OCS_LOGFILE}
    exit 1
    ;;
  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/$(to_filename ${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/$(to_filename ${tgt_hd_name})-mbr..." | tee --append $OCS_LOGFILE
	echo "The CHS value of hard drive from EDD will be used for sfdisk." | tee --append $OCS_LOGFILE
	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." | tee --append $OCS_LOGFILE
      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 `is_gpt_partitition_table_disk $tgt_hd_file`; then
      echo "The partition table in $tgt_hd_file is for GPT, now make it for MBR." | tee --append $OCS_LOGFILE
      echo "Running: parted -s $tgt_hd_file mklabel msdos" | tee --append $OCS_LOGFILE
      LC_ALL=C parted -s $tgt_hd_file mklabel msdos | tee --append $OCS_LOGFILE
    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 | tee --append $OCS_LOGFILE
    LC_ALL=C date | tee --append $OCS_LOGFILE
    echo "Writing the partition table..." | tee -a $OCS_LOGFILE
    # 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/$(to_filename $tgt_hd_name)-mbr of=$tgt_hd_file skip=446 seek=446 bs=1 count=66 2>&1 | tee --append $OCS_LOGFILE 
        RETVAL="${PIPESTATUS[0]}"
        if [ "$RETVAL" -eq 0 ]; then
          echo -n "Making kernel re-read the partition table of $tgt_hd_file... " | tee --append $OCS_LOGFILE
          # //NOTE// util-linux >= 2.26 removes support for "sfdisk -R". Therefore we switched to "blockdev --rereadpt". Thanks to Ismael (razzziel _at_ users sf net) for reporting this.
          blockdev --rereadpt $tgt_hd_file 2>&1 | tee --append $OCS_LOGFILE
          echo "done." | tee --append $OCS_LOGFILE
          echo "The partition table of $tgt_hd_file is:" | tee --append $OCS_LOGFILE
          fdisk -l $tgt_hd_file | tee --append $OCS_LOGFILE
        else
          [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
          echo "Unable to use dd to dump the partition table in $tgt_hd_file!" | tee --append $OCS_LOGFILE
          [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
          echo "$msg_program_stop!" | tee --append ${OCS_LOGFILE}
          [ "$save_restore_error_log" = "yes" ] && copy_error_log
          exit 1
        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/$(to_filename ${tgt_hd_name})-pt.sf and the size of $tgt_hd_file..." | tee --append $OCS_LOGFILE
            [ "$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"
	    if [ "$chk_tgt_disk_size_bf_mk_pt" = "no" ]; then
	      ocs_expand_mbr_pt_opt="-icds"
              PARTCLONE_RESTORE_OPT="$PARTCLONE_RESTORE_OPT -C"
            fi
	    EXTRA_SFDISK_OPT="$EXTRA_SFDISK_OPT" ocs-expand-mbr-pt $ocs_expand_mbr_pt_opt --batch $tgt_dir/$(to_filename ${tgt_hd_name})-pt.sf $tgt_hd_file 2>&1 | tee --append $OCS_LOGFILE
	    ;;
	  "manual")
	    echo "$msg_enter_another_shell_for_fdisk" | tee --append $OCS_LOGFILE
            echo -n "$msg_press_enter_to_continue..."
            read
            /bin/bash
	    ;;
          *)
            # Create partition table by sfdisk and [hsv]d[a-z]-pt.sf
            # If target_disk size is larger than 2 TiB (~2.2 TB = 2,199,023,255,040 bytes), exit. It's over the MBR's limitation.
	    init_mbr_part_table_if_not_existing $tgt_hd_file
            check_mbr_disk_size_gt_2TiB $tgt_hd_file warning 
            echo "Running sfdisk $sfdisk_opt $tgt_hd_file < $tgt_dir/$(to_filename ${tgt_hd_name})-pt.sf" | tee -a $OCS_LOGFILE
            LC_ALL=C sfdisk $sfdisk_opt $tgt_hd_file < $tgt_dir/$(to_filename ${tgt_hd_name})-pt.sf 2>&1 | tee -a $OCS_LOGFILE
            RETVAL="${PIPESTATUS[0]}"
            echo "This was done by sfdisk $sfdisk_opt $tgt_hd_file < $tgt_dir/$(to_filename ${tgt_hd_name})-pt.sf" | tee -a $OCS_LOGFILE
            # 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
              [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
              echo "$msg_failed_to_create_partition_table_on_disk: $tgt_hd_file" | tee -a $OCS_LOGFILE
              [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
              echo "$msg_is_this_disk_too_small: $tgt_hd_file?" | tee -a $OCS_LOGFILE
              [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
              echo "$msg_program_stop!" | tee -a ${OCS_LOGFILE}
              [ "$save_restore_error_log" = "yes" ] && copy_error_log
	      my_ocs_exit 1
            fi
            if [ -f $tgt_dir/$(to_filename ${tgt_hd_name})-multipath ]; then
              for i in $(cat $tgt_dir/$(to_filename ${tgt_hd_name})-multipath); do
                echo -n "Making kernel re-read the partition table of /dev/$i... " | tee --append $OCS_LOGFILE
                inform_kernel_partition_table_changed mbr /dev/$i | tee --append ${OCS_LOGFILE}
                echo "done." | tee --append $OCS_LOGFILE
              done
            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 "blockdev --rereadpt..." 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
    inform_kernel_partition_table_changed mbr $tgt_hd_file | tee --append ${OCS_LOGFILE}
    ;;
  gpt)
    echo $msg_delimiter_star_line
    if [ -e "$tgt_dir/$(to_filename ${tgt_hd_name})-gpt.gdisk" ]; then
      clean_mbr_partition_table_before_gen_gpt_table $tgt_hd_file
      create_gpt_table_if_no_table $tgt_hd_file
      gdisk_tmp="$(mktemp /tmp/gdisk_tmp.XXXXXX)"
      echo "Running: sgdisk -l $tgt_dir/$(to_filename ${tgt_hd_name})-gpt.gdisk $tgt_hd_file"
      LC_ALL=C sgdisk -l $tgt_dir/$(to_filename ${tgt_hd_name})-gpt.gdisk $tgt_hd_file | tee $gdisk_tmp
      sgdisk_rc="${PIPESTATUS[0]}"
      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
      # Somehow "sgdisk -l" (<= version 0.8.4) does not create protective MBR, either. Therefore we have to use the following workaround to write it again.
      if [ -n "$(LC_ALL=C parted -s $tgt_hd_file print | grep -i "it does not have a valid fake msdos partition table" )" ]; then
        # FixME. In the future if sgdisk supports an option to do this, we should swith to use sgdisk instead of gdisk+expect.
        sleep 1
        LC_ALL=C echo "spawn gdisk $tgt_hd_file; expect Command; send wq\r; expect \"Do you want to proceed? (Y/N)\"; send Y\r; expect eof" | expect -f -
      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..." | tee -a $OCS_LOGFILE
      # ///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/$(to_filename ${tgt_hd_name})-gpt-1st of=$tgt_hd_file 2>&1 | tee -a $OCS_LOGFILE
      retv="${PIPESTATUS[0]}"
      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..."
      to_seek="$((${src_disk_size_sec}-33+1))"
      LC_ALL=C dd if=$tgt_dir/$(to_filename ${tgt_hd_name})-gpt-2nd of=$tgt_hd_file seek=${to_seek} bs=512 count=33 2>&1 | tee -a $OCS_LOGFILE
    fi

    inform_kernel_partition_table_changed gpt $tgt_hd_file | tee --append ${OCS_LOGFILE}
    sleep 1
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "$msg_the_partition_in_the_system_now:" | tee -a $OCS_LOGFILE
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo $msg_delimiter_star_line | tee -a $OCS_LOGFILE
    LC_ALL=C parted -s $tgt_hd_file print | tee -a $OCS_LOGFILE
    echo $msg_delimiter_star_line | tee -a $OCS_LOGFILE
    ;;
  esac
} # end of create_partition
#
countdown_or_confirm_before_save() {
  local save_img_name="$1"
  local continue_choice tui_prompt_txt
  # 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:" | tee --append $OCS_LOGFILE
    echo $msg_delimiter_star_line | tee --append $OCS_LOGFILE
    echo -e "$dev_model_shown" | tee --append $OCS_LOGFILE
    echo $msg_delimiter_star_line | tee --append $OCS_LOGFILE
    echo "-> \"$save_img_name\"." | tee --append $OCS_LOGFILE
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    countdown $TIME_to_wait
  fi
  if [ "$confirm_before_clone" = "yes" ]; then
    if [ "$ocs_prompt_mode" = "tui" ]; then
      # TUI mode
      tui_prompt_txt="$msg_the_following_step_is_to_save_the_disk_part_as_img:\n$msg_delimiter_star_line\n$dev_model_shown\n$msg_delimiter_star_line\n-> \"$save_img_name\".\n"
      confirm_continue_no_default_answer -d "$tui_prompt_txt"
    else
      # CMD mode, no TUI
      echo $msg_delimiter_star_line
      [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
      echo "$msg_the_following_step_is_to_save_the_disk_part_as_img:" | tee --append $OCS_LOGFILE
      echo $msg_delimiter_star_line | tee --append $OCS_LOGFILE
      echo -e "$dev_model_shown" | tee --append $OCS_LOGFILE
      echo $msg_delimiter_star_line | tee --append $OCS_LOGFILE
      echo "-> \"$save_img_name\"." | tee --append $OCS_LOGFILE
      [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
      confirm_continue_no_default_answer
    fi
  fi
} # end of countdown_or_confirm_before_save
#
warning_about_run_parts_in_debian() {
  # //NOTE// This function is deprecated. Now we use drbl-run-parts, which is a script modified from Fedora's run-parts in package crontabs.
  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 new_dev_to_be_restore
  local i partition_table ctdn rest_conf init_des
  local continue_choice img_time img_time_prompt
  # wait for some commands if specified by user
  # init_des is the 1st line description used in confirming

  # Default settings
  ctdn="yes"
  rest_conf="yes"
  while [ $# -gt 0 ]; do
    case "$1" in
      -s|--skip-countdown) shift; ctdn="no" ;;
      -m|--skip-confirm) shift; rest_conf="no" ;;
      -i|--init-description)
         shift
         if [ -z "$(echo $1 |grep ^-.)" ]; then
           # skip the -xx option, in case 
           init_des="$1"
           shift
         fi
         if [ -z "$init_des" ]; then
           echo "-i is used, but no init_des assigned."
           echo "$msg_program_stop"
           [ "$save_restore_error_log" = "yes" ] && copy_error_log
           exit 1
         fi
         ;;
      -*)     echo "${0}: ${1}: invalid option"
              echo "$msg_program_stop"
              [ "$save_restore_error_log" = "yes" ] && copy_error_log
              exit 2 ;;
      *)      break ;;
    esac
  done
  restore_img_name="$1"  # full path of image dir
  new_dev_to_be_restore="$2"

  if [ -n "$TIME_to_wait" ] && [ "$ctdn" = "yes" ]; then
    n="$TIME_to_wait"
    echo $msg_delimiter_star_line | tee -a $OCS_LOGFILE
    echo "$msg_the_following_step_is_to_restore_img_to_disk_part: \"$restore_img_name\" -> \"$new_dev_to_be_restore\"" | tee -a $OCS_LOGFILE
    [ "$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" | tee --append $OCS_LOGFILE
    fi
    echo "$msg_uppercase_Warning!!! $msg_uppercase_Warning!!! $msg_uppercase_Warning!!!" | tee --append $OCS_LOGFILE
    if [ -n "$dev_model_shown" ]; then
      echo "$msg_uppercase_Warning. $msg_all_data_in_dev_will_be_overwritten:" | tee --append $OCS_LOGFILE
      echo $msg_delimiter_star_line | tee --append $OCS_LOGFILE
      echo -e "$dev_model_shown" | tee --append $OCS_LOGFILE
      echo $msg_delimiter_star_line | tee --append $OCS_LOGFILE
    fi
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo "Last chance to quit in $TIME_to_wait seconds... Press Shutdown button in this machine to avoid continuing." | tee --append $OCS_LOGFILE
    countdown $TIME_to_wait
  fi
  if [ "$confirm_before_clone" = "yes" ] && [ "$rest_conf" = "yes" ]; then
    echo $msg_delimiter_star_line | tee -a $OCS_LOGFILE
    img_time="$(get_img_created_time $restore_img_name)"
    if [ -n "$img_time" ]; then
      img_time_prompt="$msg_img_created_time: $img_time"
    fi
    if [ "$ocs_prompt_mode" = "tui" ]; then
      # TUI mode
      tui_prompt_txt="${init_des}${msg_the_following_step_is_to_restore_img_to_disk_part}: \"$restore_img_name\" -> \"$new_dev_to_be_restore\"\n$img_time_prompt\n"
      if [ "$ocs_sr_type" = "restoreparts" -a "$create_part" = "yes" ]; then
        tui_prompt_txt="${tui_prompt_txt}$msg_uppercase_Warning. $msg_option_k_is_not_chosen_part_table_will_be_recreated\n"
      fi
      tui_prompt_txt="${tui_prompt_txt}$msg_uppercase_Warning!!! $msg_uppercase_Warning!!! $msg_uppercase_Warning!!!\n"
      if [ -n "$dev_model_shown" ]; then
        tui_prompt_txt="${tui_prompt_txt}$msg_all_data_in_dev_will_be_overwritten:\n$msg_delimiter_star_line\n$dev_model_shown\n$msg_delimiter_star_line\n"
      fi
      confirm_continue_no_default_answer -d "$tui_prompt_txt"
    else
      # CMD mode, no TUI
      echo -e "${init_des}${msg_the_following_step_is_to_restore_img_to_disk_part}: \"$restore_img_name\" -> \"$new_dev_to_be_restore\"\n$img_time_prompt" | tee --append $OCS_LOGFILE
      [ "$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" | tee --append $OCS_LOGFILE
      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:" | tee --append $OCS_LOGFILE
        echo $msg_delimiter_star_line | tee --append $OCS_LOGFILE
        echo -e "$dev_model_shown" | tee --append $OCS_LOGFILE
        echo $msg_delimiter_star_line | tee --append $OCS_LOGFILE
      fi
      [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
      confirm_continue_no_default_answer
    fi
  fi
} # end of countdown_or_confirm_before_restore
#
task_preprocessing() {
  # (1) Check if the kernel cmdline wants this client to abort or not...
  # (2) Load the specified modules
  # (3) Mount the NFS server if $mount_point exists
  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)" | tee -a $OCS_LOGFILE
      echo "We can NOT go on. Press \"c\" to enter command prompt or any other key to quit the program..."  | tee -a $OCS_LOGFILE
      read fail_answer
      case "$fail_answer" in
        [cC]) /sbin/sulogin ;;
         *) task_postprocessing ;;
      esac
    fi
  fi
} # end of task_preprocessing
#
prepare_ecryptfs_mount_point_if_necessary() {
  local rc_
  # Reset the variables ocsroot_orig and target_dir_orig
  ocsroot_orig=""
  target_dir_orig=""
  # Function to prepare ecryptefs mount point
  case "$ocs_sr_type" in
   *save*) if [ "$encrypt_ocs_img" = "yes" ]; then 
	     mkdir -p $ocsroot/$target_dir
	     task_ecryptfs_mount_point "$ocs_sr_type"
	     rc_="$?"
           fi
	   ;;
   *)
	   # For the rest, we all need the existing image
	   encrypt_ocs_img="no"
	   # Check if the image is encrypted or not.
	   if is_ecryptfs_img $ocsroot/$target_dir; then
             encrypt_ocs_img="yes"
	     task_ecryptfs_mount_point "$ocs_sr_type"
	     rc_="$?"
           fi
	   ;;
  esac
  return $rc_
} # end of prepare_ecryptfs_mount_point_if_necessary
#
task_processing_after_parameters_checked() {
  #
  if [ "$run_prerun_dir" = "yes" ]; then
    echo $msg_delimiter_star_line | tee -a $OCS_LOGFILE
    echo "Start to run the program(s) in $OCS_PRERUN_DIR..." | tee -a $OCS_LOGFILE
    drbl-run-parts $OCS_PRERUN_DIR
    echo $msg_delimiter_star_line | tee -a $OCS_LOGFILE
  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)
      if [ "$ocs_postmode_prompt" = "cmd" ] || [ "$nogui" = "on" ]; then
        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
      else
        # Default to use TUI mode
        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_nchc_clonezilla | $msg_mode: $ocs_mode_prompt" \
        --default-item 2 --menu "$msg_clone_finished_choose_to:" 0 0 0 \
        "0" "$msg_poweroff" \
        "1" "$msg_reboot" \
        "2" "$msg_enter_cml" \
        2> $TMP
	final_action="$(cat $TMP)"
        [ -f "$TMP" ] && rm -f $TMP
      fi
      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
} # end of run_post_cmd_when_clone_end

# task for post processing
task_postprocessing() {
  # (1) Change the hostname of MS Windows in the restored machine
  # (2) Restore the inittab so that if ocsmgrd fails to get the message, the client will not clone again.
  # (3) Restore the udevd rules (if exists) which were disabled by ocs-lvm2-stop.
  # (4) Unmount NFS mounting poing if $mount_point exists
  # (5) Enter debug mode if assigned
  # (6) Notify the server if necessary
  # (7) Output a file name to tag the image and put the log for saving the image
  # (8) Unmount ecryptfs if necessary
  # (9) Remove some temp files
  # (10) Syncing - flush filesystem buffers
  # (11) Reboot/poweroff/choose...

  local mode="$1"
  local img_dir="$ocsroot/$2"
  local ipart retv
  local disk_list parts_list img_time disk_size_ disks_size_all
  #
  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..." | tee --append ${OCS_LOGFILE}
            ocs-chnthn -b -v IP -p $win_hostname_prefix -d /dev/$ipart | tee --append ${OCS_LOGFILE}
            retv="${PIPESTATUS[0]}"
            [ "$retv" -eq 0 ] && break
          done
          ;;
     By_MAC)
          for ipart in $target_parts; do
            echo "Changing the hostname of M$ windows based on MAC address..." | tee --append ${OCS_LOGFILE}
            ocs-chnthn -b -v MAC -p $win_hostname_prefix -d /dev/$ipart
            retv="${PIPESTATUS[0]}"
            [ "$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 | tee --append ${OCS_LOGFILE}
     echo "Start to run the program(s) in $OCS_POSTRUN_DIR..." | tee --append ${OCS_LOGFILE}
     drbl-run-parts $OCS_POSTRUN_DIR | tee --append ${OCS_LOGFILE}
     echo $msg_delimiter_star_line | tee --append ${OCS_LOGFILE}
  fi

  # Restore the udevd rules (if exists) which were disabled by ocs-lvm2-stop.
  restore_lvm2_udevd_rules | tee --append ${OCS_LOGFILE}

  # 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_job_is_done | tee --append ${OCS_LOGFILE}

  # TODO: many temp partition_table files have to be removed. Find a better
  # way to remove them.
  # Remove some temp files
  if  [ -n "$partition_table" -a \
	-e "$partition_table" ]; then
    rm -f $partition_table
  fi

  #
  case "$mode" in
    "saveparts"|"savedisk")
      # Output a file name to tag the image and put the log for saving the image. It's easier for us to identify in the future.
      echo "This image was saved by Clonezilla at $(LC_ALL=C date +%F' '%T' '%Z)." > $img_dir/clonezilla-img
      echo "The log during saving:" >> $img_dir/clonezilla-img
      echo "----------------------------------------------------------" >> $img_dir/clonezilla-img
      cat ${OCS_LOGFILE} >> $img_dir/clonezilla-img
      echo "### End of log ###" >> $img_dir/clonezilla-img
      echo "### Image created time: $(date +%Y-%m%d-%H%M)" >> $img_dir/clonezilla-img
      # Unmount the encrypted image
      if [ "$encrypt_ocs_img" = "yes" ]; then
	# Before unmounting ecryptfs img, we have to keep something for later to be saved in the file ecryptfs.info.
        get_ecryptfs_info  # Inputs are global variables $ocsroot and $target_dir
	                   # Outputs are disk_list, parts_list, img_time, disks_size_all
        umount_ecryptfs_mount_point_if_necessary
	# Put a tag file in the original image dir
	put_ecryptefs_tag_file_in_img # Inputs are ocsroot_orig, target_dir_orig, 
	                              # disk_list, parts_list, img_time, disks_size_all
      fi
      ;;
    *)
      # For restoring or others, we still need to unmount the ecryptfs system.
      umount_ecryptfs_mount_point_if_necessary
      ;;
  esac

  #
  echo "Now syncing - flush filesystem buffers..." | tee --append ${OCS_LOGFILE}
  sync;sync;sync | tee --append ${OCS_LOGFILE}

  # reboot/poweroff/choose...
  run_post_cmd_when_clone_end "$postrun" | tee --append ${OCS_LOGFILE}

  # 
} # 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
#
rm_target_image_if_exist() {
  local tgt_image="$1"
  local tgt_image_fs
  if [ "${tgt_image/%\//}" = "${ocsroot/%\//}" -o -z "$tgt_image" ]; then
    echo "Existing destination image is nothing!" | tee --append $OCS_LOGFILE
    echo "$msg_program_stop." | tee --append $OCS_LOGFILE
    exit 1
  else
    # If it's ecryptfs mounting point, we should not remove it.
    # Otherwise it will show busy error message.
    tgt_image_fs="$(LC_ALL=C df -T "$tgt_image" 2>/dev/null | tail -n 1 | awk -F" " '{print $2}')"
    if [ "$encrypt_ocs_img" = "yes" ] && [ "$tgt_image_fs" = "ecryptfs" ]; then
      if [ -n "$ocsroot_orig" -a \
	   -n "$target_dir_orig" -a \
	   -d "$ocsroot_orig/$target_dir_orig" ]; then
        # We have to clean all the files from the ecryptfs source dir, otherwise
	# if there is same file existing, the ecryptfs won't be able to create it.
        rm -f $ocsroot_orig/$target_dir_orig/*
      fi
    else
      [ -f "$tgt_image" ] && rm -f $tgt_image
      if [ -d "$tgt_image" -a -n "$tgt_image" ]; then
	rm -f $tgt_image/*
	rmdir $tgt_image
      fi
    fi
  fi
} # end of rm_target_image_if_exist
#
check_input_target_image() {
  local tgt_dir="$1"
  [ -z "$tgt_dir" ] && echo "Image name not found!" && exit 1
  if [ ! -d "$tgt_dir" ]; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
    echo "The directory $tgt_dir for the inputed name does NOT exist!" | tee --append ${OCS_LOGFILE}
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo "$msg_program_stop!!!" | tee --append ${OCS_LOGFILE}
    [ "$save_restore_error_log" = "yes" ] && copy_error_log
    exit 1
  fi
} # end of check_input_target_image
#
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 img_name_assign="$2"  # target name assigned. This is optional.
    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
      if [ -z "$img_name_assign" ]; then
        tgt_name_default="$(date +%F-%H-img)"
      else
        tgt_name_default="$img_name_assign"
      fi
      $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!" | tee --append ${OCS_LOGFILE}
             [ -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=""  # serialno is global variable
  [ -z "$dsk_" ] && echo "No input value for dsk_!" && exit 1
  if type udevadm &>/dev/null; then
    serialno="$(LC_ALL=C udevadm info -q env -n /dev/${dsk_} | grep ID_SERIAL= | sed -e 's/ID_SERIAL=//g')"
  fi
  if [ -z "$serialno" ]; then
    # 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)"
  fi
  # 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 part_fst
  dsize="" # device size

  if ! is_supported_dev "$p"; then
    DEV_MODEL="NOT_SUPPORTED_DEV"
    return 1
  fi
  if is_partition "$p"; then
    # Partition 
    # 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" ] && DEV_MODEL="${dsize}"
    # File system
    part_fst="$(LC_ALL=C ocs-get-part-info /dev/$p filesystem)"
    [ -n "$part_fst" ] && DEV_MODEL="${DEV_MODEL}_${part_fst}"
    # Volume name
    part_v="$(LC_ALL=C get_part_vol_name /dev/$p $part_fst)"
    [ -n "$part_v" ] && DEV_MODEL="${DEV_MODEL}_${part_v}"
    # 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_="$(get_diskname $p)"
    if [ "$(cat /proc/ide/${dsk_}/media 2>/dev/null)" = "disk" ] ; then
      DEV_MODEL_EXT="$(tr ' ' _ </proc/ide/${dsk_}/model)"
    else
      x="$(cat /sys/block/$(to_sysblock_name ${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 #
    if is_ide_disk "$dsk_"; then
      serialno="$(cat /sys/block/$(to_sysblock_name $dsk_)/device/serial 2>/dev/null | sed -e "s/ //g")"
    else
      #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_
    fi
    if [ -n "$serialno" ]; then
      serialno="_${serialno}"
    else
      serialno="_No_disk_serial_no"
    fi
    if [ -L /dev/$p ]; then
      DEV_MODEL="${DEV_MODEL}_$(readlink -f /dev/$p)"
    else
      DEV_MODEL="${DEV_MODEL}${serialno}"
    fi
  else
    # Disk
    if is_ide_disk "$p"; then
      if [ "$(cat /proc/ide/$(get_diskname $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/$(to_sysblock_name $p)/size 2>/dev/null)*512/1000.0^3" | bc -l 2>/dev/null)GB"
        fi
        if [ -n "$dsize" ]; then
           dsize="${dsize}_"
        else
           dsize="Unknown_size_"
        fi
        # put serial #
  	serialno="$(cat /sys/block/$(to_sysblock_name $(get_diskname $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/$(get_diskname $p)/model)${serialno}"
      fi
    else
      x="$(cat /sys/block/$(to_sysblock_name $(get_diskname $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/$(to_sysblock_name $(get_diskname $p))/size 2>/dev/null)*512/1000.0^3" | bc -l 2>/dev/null)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
      if [ -L /dev/$p ]; then
        DEV_MODEL="${dsize}${x}_$(readlink -f /dev/$p)"
      else
        DEV_MODEL="${dsize}${x}${serialno}"
      fi
    fi
  fi
  # just in case if nothing found
  [ -z "$DEV_MODEL" ] && DEV_MODEL="No_description"
  return 0
} # end of get_disk_or_part_hardware_info
#
get_not_busy_disks_or_parts() {
    local dev_type="$1"
    local chosen_disk="$2"
    local dev_to_be_exclude="$3"
    local partition_table=""
    local mounted_table=""
    local part_list=""
    local dsk_list=""
    # dev_list is global in this function (but not true for others) to be returned
    # initialize dev_list
    dev_list=""
    
    gen_proc_partitions_map_file
    # Leave only selected disk
    if [ -n "$chosen_disk" ]; then
      # //NOTE// If we do not separate cciss device and others, we might choose sda1, sda2, sdap1, sdap2 for $chosen_disk is "sda". Apparently sdap1 is not the partition of sda.
      # i.e. we can not use the following command for all cases:
      # LC_ALL=C perl -i -ne "print if m!^.*[[:space:]]+${chosen_disk}p?[0-9]*[[:space:]].*\$!" $partition_table
      # Ref: https://sourceforge.net/p/clonezilla/bugs/179/
      case "${chosen_disk}" in
      cciss*|mmcblk*|md*|rd*|ida*)
            LC_ALL=C perl -i -ne "print if m|^.*[[:space:]]+${chosen_disk}p?[0-9]*[[:space:]].*\$|" $partition_table
            ;;
      *)
            LC_ALL=C perl -i -ne "print if m!^.*[[:space:]]+${chosen_disk}[0-9]*[[:space:]].*\$!" $partition_table
            ;;
      esac
    fi

    # 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)"
    # To overcome the sorting problem, we use "-V" option. //NOTE// This won't work for older sort... e.g. the one in RHEL4?
    part_list="$(get_part_list $partition_table)"
    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="$(get_diskname $ipart)"
	# strip disk
        LC_ALL=C perl -i -ne "print unless m!^.*${hdtmp}[[:space:]]+!" $partition_table
	# strip parititon
        LC_ALL=C perl -i -ne "print unless m!^.*${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 m!^.*${ipart}[[:space:]]+!" $partition_table
    done

    case "$dev_type" in
      harddisk)
        dev_list="$(get_disk_list $partition_table)" ;;
      partition)
        dev_list="$(get_part_list $partition_table)" ;;
    esac
    dev_list="$(echo $dev_list)"   # convert to 1 line
    [ -f "$mounted_table" ] && rm -f $mounted_table
    [ -f "$partition_table" ] && rm -f $partition_table
} # end of get_not_busy_disks_or_parts
#
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 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"

    get_not_busy_disks_or_parts $dev_type "" "$dev_to_be_exclude"  # we will get dev_list

    # With dev_list, continue
    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"
        NUMDEV="$(LC_ALL=C 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"
        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
      case "$dev_type" in
        harddisk)
          [ "$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
          ;;
        partition)
          # This is for restoring an image of parts to blank disk (no partitions on the destination disk)
          if [ "$ocs_sr_type" = "restoreparts" ]; then
            # only when running restoreparts
            gen_proc_partitions_map_file
            if [ -n "$(get_part_list $partition_table)" ]; then
              # If exists in /proc/partitions, let's create the partition table
              [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
              echo "$msg_no_partition_table_need_to_create"
	      echo "$msg_enter_another_shell_for_fdisk"
              [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
              echo -n "$msg_press_enter_to_continue..."
              read
              /bin/bash
              get_not_busy_disks_or_parts $dev_type "" "$dev_to_be_exclude"  # we will get dev_list
            fi
          else
            [ "$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
          ;;
      esac
    fi

    :> $TMP # Clean the TMP file
    # prepare the dialog menu info
    for p in $(reduce_multipath_dev $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
    :> $TMP # Clean the TMP file
    # 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!" | tee --append ${OCS_LOGFILE}
              [ -f "$TMP" ] && rm -f $TMP
              exit 1;;
         esac
      else
         ASK_INPUT=0
      fi
    done

    [ -f "$TMP" ] && rm -f $TMP 

    # 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 list_mode="$2"
   local tgt_file="" ans_ list_des
   local ASK_IMGNAME=1
   local TMP="$(mktemp /tmp/ocs.XXXXXX)"
   local disk_size_ ihd disks_size_all

   case "$list_mode" in
     "rest-unenc") list_des="$msg_choose_the_image_to_restore $msg_only_nonenc_is_shown" ;;
     *)            list_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=`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
   # The result shown for each image is like: 2014-1216-0930_sda_8590MB
   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
     # When mode is nonenc or enc, we should skip some dirs.
     case "$list_mode" in
       "rest-unenc") [ -e "$imagedir/$file/ecryptfs.info" ] && continue ;;
     esac
     if is_ecryptfs_img $imagedir/$file; then
       # Encrypted image
       . $imagedir/$file/ecryptfs.info
       fileinfo="${time_of_img}-enc"
       fileinfo=$fileinfo"_""$(echo "$disk_of_img" | tr ' ' _)"
       disks_size_all="$disks_size_all_of_img"
     else
       # Not encrypted
       fileinfo="$(get_img_created_time $imagedir/$file)"
       fileinfo=$fileinfo"_"$(tr ' ' _ < $imagedir/$file/disk 2>/dev/null)
       disks_size_all=""
       if [ -e "$imagedir/$file/disk" ]; then
         for ihd in `get_disk_list_from_img $imagedir/$file` ; do 
           disk_size_=""
           if [ -e "$imagedir/$file/$(to_filename ${ihd})-pt.parted.compact" ]; then
             disk_size_="$(LC_ALL=C grep -E "^Disk /dev/" $imagedir/$file/$(to_filename ${ihd})-pt.parted.compact | awk -F":" '{print $2}' | sed -r -e "s/[[:space:]]//g")"
           fi
           [ -n "$disk_size_" ] && disks_size_all="$(LC_ALL=C echo "${disks_size_all}_${disk_size_}" | tr ' ' _)"
         done
       fi
     fi
     #
     [ -n "$disks_size_all" ] && fileinfo=${fileinfo}""${disks_size_all}
     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 "$list_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!" | tee --append ${OCS_LOGFILE}
              [ -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!!!" | tee --append ${OCS_LOGFILE}
     [ "$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 list_mode="$2"
   local tgt_file="" ans_ list_des
   local ASK_IMGNAME=1
   local TMP="$(mktemp /tmp/ocs.XXXXXX)"
   case "$list_mode" in
     "check")   list_des="$msg_choose_the_image_to_be_checked" ;;
     "convert") list_des="$msg_choose_the_image_to_be_converted_compression" ;;
     "p2v")     list_des="$msg_choose_the_image_to_be_converted_virtual" ;;
     "nonenc")  list_des="$msg_choose_the_unenc_image_to_enc" ;;
     "enc")     list_des="$msg_choose_the_enc_image_to_decrypt" ;;
     "rest-unenc") list_des="$msg_choose_the_image_to_restore $msg_only_nonenc_is_shown" ;;
     *)         list_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
     # When mode is nonenc or enc, we should skip some dirs.
     case "$list_mode" in
       "nonenc"|"rest-unenc") [ -e "$imagedir/$file/ecryptfs.info" ] && continue ;;
       "enc")    [ ! -e "$imagedir/$file/ecryptfs.info" ] && continue ;;
     esac
     if is_ecryptfs_img $imagedir/$file; then
       # Encrypted image
       . $imagedir/$file/ecryptfs.info
       fileinfo="${time_of_img}-enc"
       if [ -f "$imagedir/$file/parts" ]; then 
	 fileinfo=$fileinfo"_""$(echo $parts_of_img | tr ' ' _)"
       elif [ -f "$imagedir/$file/disk" ]; then 
	 fileinfo=$fileinfo"_""$(echo $disk_of_img | tr ' ' _)"
       fi
     else
       fileinfo="$(get_img_created_time $imagedir/$file)"
       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
     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 "$list_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!" | tee --append ${OCS_LOGFILE}
              [ -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!!!" | tee --append ${OCS_LOGFILE}
     [ "$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 `get_disk_list_from_img $tgt_file` ; 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!" | tee --append ${OCS_LOGFILE}
              [ -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!!!" | tee --append ${OCS_LOGFILE}
     [ "$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
   local p_list

   # 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"
   #
   p_list="$(get_parts_list_from_img "$tgt_file")"
   NUMPARTS="$(LC_ALL=C echo "$p_list" | wc -w 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
     # List the partitions in dialog/whiptail like:
     # [ ] sda1  disk(sda)_partition(1)
     # [ ] sda5  disk(sda)_partition(5)    
     # 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.
     PROMPT="$(for p in $p_list; do 
       if [ "$skip_mounted_part" = "yes" ]; then
         [ -n "$(mount | grep -Ew "^/dev/$p")" ] && continue
       fi
       echo "$p" "disk("$(get_diskname $p)")_partition("$(get_part_number $p)")" 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!" | tee --append ${OCS_LOGFILE}
       [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
       echo "$msg_program_stop!" | tee --append ${OCS_LOGFILE}
       exit 1
     fi
     if [ "$request_mode" = "check" ]; then
        prompt_msg_part1="$msg_choose_the_parts_to_check"
     elif [ "$request_mode" = "select_from_img" ]; then
        prompt_msg_part1="$msg_choose_the_parts_from_img_to_restore"
     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!" | tee --append ${OCS_LOGFILE}
              [ -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!!!" | tee --append ${OCS_LOGFILE}
     [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
     [ -f "$TMP" ] && rm -f $TMP
     exit 1
   fi
   [ -f "$TMP" ] && rm -f $TMP
   # return the value
   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.org for more details!"
      [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
      echo "$msg_program_stop!!!" | tee --append ${OCS_LOGFILE}
      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 "Failed to use ntfsclone program to save or restore an image!"
    [ "$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 2>/dev/null)"
  if [ -z "$speed" ]; then
    rcode=1
    # show it with unit
    speed="N/A $space_used_unit/min"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
    echo "Failed to use partclone program to save or restore an image!" | tee --append ${OCS_LOGFILE}
    [ "$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 [ -n "$(ls $target_d/$img_file.aa* 2>/dev/null)" ]; then
    # get_image_cat_zip_cmd $target_d/$img_file.aa
    get_image_cat_zip_cmd `get_split_img_1st_chunk $target_d $img_file`
    # 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 | tee --append ${OCS_LOGFILE}
  echo "Starting unicast restoring image ${target_d##*/} to $part..." | tee --append ${OCS_LOGFILE}
  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
cat <<_EOT_ >> ${OCS_LOGFILE}
      ( 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 | tee --append ${OCS_LOGFILE}
_EOT_
      ( 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 | tee --append ${OCS_LOGFILE}
      ;;
    split)
      # ntfsclone+split will NOT put header in 2nd and later volumes, so just cat them
cat <<_EOT_ >> ${OCS_LOGFILE}
      ( 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 | tee --append ${OCS_LOGFILE}
_EOT_
      ( 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 | tee --append ${OCS_LOGFILE}
      ;;
  esac
  # 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. This is very minor since now we use partclone as default engine
  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 [ -n "$(ls $target_d/$img_file.ntfs-img.aa* 2>/dev/null)" ]; then
    # get_image_cat_zip_cmd $target_d/$img_file.ntfs-img.aa
    get_image_cat_zip_cmd `get_split_img_1st_chunk $target_d $img_file.ntfs-img`
    # 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_restore_extra_opt_def --restore-image --overwrite $part - | tee $ntfs_img_info_tmp
  rc="${PIPESTATUS[0]}"
  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
  local unzip_stdin_cmd_error
  local -a retcodes='()'
  # 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." | tee --append ${OCS_LOGFILE}
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo "$msg_program_stop." | tee --append ${OCS_LOGFILE}
    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.*
      # e.g. sda1.ext4-ptcl-img.gz.aaa -> sda1.ext4-ptcl-img.gz.*
      img_file_prefix="$(echo ${file_basename} | sed -r -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
    # The split suffix length for old format is only 2, so we do not have to consider >=3 (*.aaa or more).
    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
  unzip_stdin_cmd_error="$(mktemp /tmp/unzip_stdin_cmd_error.XXXXXX)"
  [ -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..." | tee --append ${OCS_LOGFILE}
  [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
  echo "If this action fails or hangs, check:" | tee --append ${OCS_LOGFILE}
  echo "* Is the saved image $target_d/$img_file_prefix corrupted ?"  | tee --append ${OCS_LOGFILE}
  [ "$(root_over_nfs)" = "yes" ] && echo "* Network connection and NFS service."
  [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
  echo $msg_delimiter_star_line | tee --append ${OCS_LOGFILE}

  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.
cat <<_EOT_ >> ${OCS_LOGFILE}
  ( for img in $target_d/$img_file_prefix; do
      cat $img
    done
  ) \
  | $unzip_stdin_cmd 2> $unzip_stdin_cmd_error \
  | LC_ALL=C partclone.${fs_} $PARTCLONE_RESTORE_OPT -L $partclone_img_info_tmp -s - -r -o $part
_EOT_
  ( for img in $target_d/$img_file_prefix; do
      cat $img
    done
  ) \
  | $unzip_stdin_cmd 2> $unzip_stdin_cmd_error \
  | LC_ALL=C partclone.${fs_} $PARTCLONE_RESTORE_OPT -L $partclone_img_info_tmp -s - -r -o $part
  retcodes=(${PIPESTATUS[@]})

  if [ ${retcodes[1]} -eq 0 -a ${retcodes[2]} -eq 0 ]; then
    rc=0
  else
    rc=1
  fi

  cat "${partclone_img_info_tmp}" >> ${OCS_LOGFILE}
  if [ -s "$unzip_stdin_cmd_error" ]; then
    cat "$unzip_stdin_cmd_error" | tee --append ${OCS_LOGFILE}
  fi
  rm -f $unzip_stdin_cmd_error

  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)" | tee --append  ${OCS_LOGFILE}
  # prepare statistic report
  conv_return_code_to_human_read $rc
  report_msg="$report_msg $part, $clone_status, $space_used, $time_elapsed_in_min mins;"
  return $rc
} # end of unicast_restore_by_partclone
#
unicast_restore_by_dd() {
  local partclone_img_info_tmp
  local unzip_retcode_file unzip_retcode
  local dd_retcode_file dd_retcode
  local unzip_stdin_cmd_error
  local -a retcodes='()'
  if [ -n "$(ls $target_d/$img_file.dd-img.aa* 2>/dev/null)" ]; then
    # get_image_cat_zip_cmd $target_d/$img_file.dd-img.aa
    get_image_cat_zip_cmd `get_split_img_1st_chunk $target_d $img_file.dd-img`
    # 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
    unzip_retcode_file="$(mktemp /tmp/unzip_retcode.XXXXXX)"
    dd_retcode_file="$(mktemp /tmp/dd_retcode.XXXXXX)"
    echo 1 > $unzip_retcode_file
    echo 1 > $dd_retcode_file
    (
      ( for img in $target_d/$img_file_prefix; do
          cat $img
        done
      ) \
      | ( $unzip_stdin_cmd ; echo $? > $unzip_retcode_file ) \
      | ( LC_ALL=C dd bs=1M of=$part ; echo $? > $dd_retcode_file ) \
    ) 2>&1 \
    | tee $dd_img_info_tmp
    tail -n 30 "${dd_img_info_tmp}" >> ${OCS_LOGFILE}

    unzip_retcode="$(cat $unzip_retcode_file)"
    dd_retcode="$(cat $dd_retcode_file)"
    rm -f $unzip_retcode_file $dd_retcode_file

    if [ "$unzip_retcode" = "0" -a "$dd_retcode" = "0" ]; then
      echo "dd successfully restored the image (-) to the device ($part)" | tee --append ${OCS_LOGFILE}
      rc=0
    else
      rc=1
    fi

    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
    unzip_stdin_cmd_error="$(mktemp /tmp/unzip_stdin_cmd_error.XXXXXX)"
    [ -f "$partclone_img_info_tmp" ] && rm -f $partclone_img_info_tmp
    echo $msg_delimiter_star_line | tee --append ${OCS_LOGFILE}

    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.
cat <<_EOT_ >> ${OCS_LOGFILE}
    ( for img in $target_d/$img_file_prefix; do
        # Exclude the image info file like "sda1.dd-img.info"
        [ -n "$(echo "$img" | grep -E "dd-img.info$")" ] && continue
        cat $img
      done
    ) \
    | $unzip_stdin_cmd 2> $unzip_stdin_cmd_error \
    | LC_ALL=C partclone.dd $PARTCLONE_RESTORE_OPT -L $partclone_img_info_tmp -s - -o $part
_EOT_
    ( for img in $target_d/$img_file_prefix; do
        # Exclude the image info file like "sda1.dd-img.info"
        [ -n "$(echo $img | grep -E "dd-img.info$")" ] && continue
        cat $img
      done
    ) \
    | $unzip_stdin_cmd 2> $unzip_stdin_cmd_error \
    | LC_ALL=C partclone.dd $PARTCLONE_RESTORE_OPT -L $partclone_img_info_tmp -s - -o $part
    retcodes=(${PIPESTATUS[@]})
    
    if [ ${retcodes[1]} -eq 0 -a ${retcodes[2]} -eq 0 ]; then
      rc=0
    else
      rc=1
    fi
    
    cat "${partclone_img_info_tmp}" >> ${OCS_LOGFILE}
    if [ -s "$unzip_stdin_cmd_error" ]; then
      cat "$unzip_stdin_cmd_error" | tee --append ${OCS_LOGFILE}
    fi
    rm -f $unzip_stdin_cmd_error

    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)" | tee --append  ${OCS_LOGFILE}
  [ -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;"
  return $rc
} # 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 [ -n "$(ls $target_d/$img_file.aa* 2>/dev/null)" ]; then
    # get_image_cat_zip_cmd $target_d/$img_file.aa
    get_image_cat_zip_cmd `get_split_img_1st_chunk $target_d $img_file`
  else
    get_image_cat_zip_cmd $target_d/$img_file
  fi
  echo $msg_delimiter_star_line | tee --append ${OCS_LOGFILE}

  start_time="$(date +%s%N)"
cat <<_EOT_ >>  ${OCS_LOGFILE}
  $udpcast_rec_cmd 2>$udpcast_stderr | $unzip_stdin_cmd | partimage \
  $DEFAULT_PARTIMAGE_RESTORE_OPT $PARTIMAGE_RESTORE_OPT restore $part stdin | tee --append ${OCS_LOGFILE}
_EOT_
  $udpcast_rec_cmd 2>$udpcast_stderr | $unzip_stdin_cmd | partimage \
  $DEFAULT_PARTIMAGE_RESTORE_OPT $PARTIMAGE_RESTORE_OPT restore $part stdin | tee --append ${OCS_LOGFILE}
  # 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. This is very minor since now we use partclone as default engine
  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 [ -n "$(ls $target_d/$img_file.ntfs-img.aa* 2>/dev/null)" ]; then
    # get_image_cat_zip_cmd $target_d/$img_file.ntfs-img.aa
    get_image_cat_zip_cmd `get_split_img_1st_chunk $target_d $img_file.ntfs-img`
  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_restore_extra_opt_def --restore-image --overwrite $part - | tee $ntfs_img_info_tmp
  rc="${PIPESTATUS[0]}"
  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." | tee --append  ${OCS_LOGFILE}
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo "$msg_program_stop." | tee --append  ${OCS_LOGFILE}
    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..." | tee --append  ${OCS_LOGFILE}
  [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
  echo "If this action fails or hangs, check:" | tee --append  ${OCS_LOGFILE}
  echo "* Is the saved image $target_d/$file_basename* corrupted ?"  | tee --append  ${OCS_LOGFILE}
  [ "$(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 ?" | tee --append  ${OCS_LOGFILE}
  [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
  echo $msg_delimiter_star_line | tee --append  ${OCS_LOGFILE}

  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.
cat <<_EOT_ >> ${OCS_LOGFILE}
  $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
_EOT_
  $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)" | tee --append  ${OCS_LOGFILE}
  # 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." | tee --append  ${OCS_LOGFILE}
  # 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 [ -n "$(ls $target_d/$img_file.dd-img.aa* 2>/dev/null)" ]; then
    # get_image_cat_zip_cmd $target_d/$img_file.dd-img.aa
    get_image_cat_zip_cmd `get_split_img_1st_chunk $target_d $img_file.dd-img`
  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="${PIPESTATUS[0]}"
    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 - -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="$(to_filename $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 | tee --append ${OCS_LOGFILE}
  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 \
       -n "$(ls $target_d/$img_file.aa* 2>/dev/null)" -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 \
	 -n "$(ls $target_d/$img_file.ntfs-img.aa* 2>/dev/null)" ]; 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 \
	 -n "$(ls $target_d/$img_file.dd-img.aa* 2>/dev/null)" ]; 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." | tee --append ${OCS_LOGFILE}
  sleep 1
  # Informing kernel that the OS that partition table has changed since for BSD system, the partitions info exists in the slice
  hdtmp="$(get_disk_from_part $part)"
  pt_type="$(get_partition_table_type_from_disk $hdtmp)"
  inform_kernel_partition_table_changed $pt_type $hdtmp | tee --append ${OCS_LOGFILE}
  echo $msg_delimiter_star_line | tee --append ${OCS_LOGFILE}
  #copy_log $target_d $img_file
  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 | tee --append ${OCS_LOGFILE}
  echo "Starting to restore image ${target_d##*/} to $part..."
  if [ -f "$target_d/$img_file.000" -o \
       -n "$(ls $target_d/$img_file.aa* 2>/dev/null)" -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 \
	 -n "$(ls $target_d/$img_file.ntfs-img.aa* 2>/dev/null)" ]; 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 \
	 -n "$(ls $target_d/$img_file.dd-img.aa* 2>/dev/null)" ]; 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." | tee --append ${OCS_LOGFILE}
  echo $msg_delimiter_star_line
  # Informing kernel that the OS that partition table has changed since for BSD system, the partitions info exists in the slice
  hdtmp="$(get_disk_from_part $part)"
  pt_type="$(get_partition_table_type_from_disk $hdtmp)"
  inform_kernel_partition_table_changed $pt_type $hdtmp | tee --append ${OCS_LOGFILE}
  
  # 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... " | tee --append ${OCS_LOGFILE}
  # 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 [ -n "$(ls $imagedir/$target_d/$img_file.aa* 2>/dev/null)" ]; then
    # get_image_cat_zip_cmd $imagedir/$target_d/$img_file.aa
    get_image_cat_zip_cmd `get_split_img_1st_chunk $imagedir/$target_d $img_file`
    # 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 [ -n "$(ls $imagedir/$target_d/$img_file.ntfs-img.aa* 2>/dev/null)" ]; then
    # The files are split, like sda1.ntfs-img.aa, sda1.ntfs-img.ab, 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!!!" | tee --append ${OCS_LOGFILE}
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo "$msg_program_stop!" | tee --append ${OCS_LOGFILE}
    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.*
      # e.g. sda1.ext4-ptcl-img.gz.aaa -> sda1.ext4-ptcl-img.gz.*
      img_file_prefix="$(echo ${file_basename} | sed -r -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
    # The split suffix length for old format is only 2, so we do not have to consider >=3 (*.aaa or more).
    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 [ -n "$(ls $imagedir/$target_d/$img_file.dd-img.aa* 2>/dev/null)" ]; then
    # The files are split, like sda1.dd-img.aa, sda1.dd-img.ab, 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
      # Exclude the image info file like "sda1.dd-img.info"
      [ -n "$(echo "$img" | grep -E "dd-img.info$")" ] && continue
      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="$(to_filename $2)"  # e.g. sda1.ext4-ptcl-img.gz.aa or cciss-c0d0p1.ext4-ptcl-img.gz.aa
  local part="$3"      # e.g. sda1
  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 \
       -n "$(ls $imagedir/$target_d/$img_file.aa* 2>/dev/null)" -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 \
	 -n "$(ls $imagedir/$target_d/$img_file.ntfs-img.aa* 2>/dev/null)" ]; 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 \
	 -n "$(ls $imagedir/$target_d/$img_file.dd-img.aa* 2>/dev/null)" ]; 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 part_is_lvm
  # 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="$(get_diskname $partition)"
    part_is_lvm="no"
    # 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
      part_is_lvm="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 [ "$part_is_lvm" = "yes" ]; then
    do_LVM_restore_feed="yes"
    # 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 TF
  local image_name_ rc_save_logv FND_PV
  local target="$1"
  # target_dir_fullpath is local (in function task_saveparts) global variable, since save_logv is used in function task_saveparts 
  PV_PARSE_CONF="$target_dir_fullpath/lvm_vg_dev.list"
  LOGV_PARSE_CONF="$target_dir_fullpath/lvm_logv.list"
  # if the report_msg is empty, put the initial one: image_name_
  image_name_="$(basename $target_dir_fullpath)"
  [ -z "$report_msg" ] && report_msg="Saved $image_name_,"
  rm -f $PV_PARSE_CONF $LOGV_PARSE_CONF
  ocs-lvm2-start
  echo "Parsing LVM layout for ${target} ..." | tee --append $OCS_LOGFILE
  # Ref: https://sourceforge.net/p/clonezilla/discussion/Open_discussion/thread/075d3f5a/
  # Thanks to Uditha De Silva for providing a better mechanism to parse the PV we are interested here.
  # Was using: LC_ALL=C pvscan 2>/dev/null 3<&- 4<&- | grep -Ew lvm2 | while read LINE; do
  TF="devices { filter = [ 'a:(${target/ /|})\$:', 'r:.*:' ] }"
  LC_ALL=C pvscan --config "$TF" 2>/dev/null 3<&- 4<&- | grep -Ew lvm2 | while read LINE; do
    DEV="$(LC_ALL=C echo $LINE | tr -s ' ' | cut -d' ' -f2)"
    if [ "$(LC_ALL=C echo $LINE | tr -s ' ' | cut -d' ' -f3)" = "VG" ]; then
      VG="$(LC_ALL=C echo $LINE | tr -s ' ' | cut -d' ' -f4)"
    else
      # The volume group is not found in the DEV.
      # We set "/NOT_FOUND" to VG because to parse logical volumes.
      VG="/NOT_FOUND"
    fi
    # 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
    # New way to deal with CCISS RAID from Miracle Linux
    tgt_parts=$(expand_multipath_dev "$target")
    for pt in $tgt_parts; do
      if [ -n "$(echo "$pt" | grep -E "\<${DEV##*/}\>")" ]; then
        UUID="$(LC_ALL=C pvdisplay $DEV 2>/dev/null | grep "PV UUID" | awk -F" " '{print $3}')"
        echo "$VG $DEV $UUID" | tee -a $PV_PARSE_CONF | tee --append $OCS_LOGFILE
      fi
    done
  done

  if [ ! -e "$PV_PARSE_CONF" ]; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "///WARNING/// The LVM physical volume setting was not found." | tee --append $OCS_LOGFILE
    echo "Unable to save LVM image." | tee --append $OCS_LOGFILE
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo $msg_delimiter_star_line | tee --append $OCS_LOGFILE
    #copy_log $target_dir_fullpath "lvm"
    return 1
  fi
  if [ -n "$(grep -E "/NOT_FOUND" $PV_PARSE_CONF 2>/dev/null)" ]; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "///WARNING/// The volume group setting is not found." | tee --append $OCS_LOGFILE
    echo "Unable to save LVM image." | tee --append $OCS_LOGFILE
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo $msg_delimiter_star_line | tee --append $OCS_LOGFILE
    #copy_log $target_dir_fullpath "lvm"
    return 1
  fi
  
  # We just want the LV in chosen target
  echo "Parsing logical volumes..." | tee --append $OCS_LOGFILE
  # 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 2>/dev/null 3<&- 4<&- | grep "/.*/" | while read LINE; do
    LOGV="$(LC_ALL=C echo $LINE | tr -s ' ' | cut -d' ' -f2 | tr -d "'")"
    # LOGV is like: /dev/vg3/lvol0
    while read vg dev uuid; do
     if [ "$vg" = "/NOT_FOUND" ]; then
      [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
      echo "///WARNING/// No volume group in $dev." | tee --append $OCS_LOGFILE
      [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
      # copy_log $target_dir_fullpath "lvm"
      continue
     fi
     # only keep LOGV is in the chosen vg
     if [ -n "$(LC_ALL=C echo $LOGV | grep -E "/dev/$vg/" 2>/dev/null)" ]; then
      file_system="$(LC_ALL=C 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... " | tee --append $OCS_LOGFILE
  while read vg dev uuid; do
    [ "$vg" = "/NOT_FOUND" ] && continue
    # Here we avoid to run vgcfgbackup with output on the $target_dir_fullpath, since due to some reason, if $target_dir_fullpath 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>&1 3<&- 4<&- | tee --append $OCS_LOGFILE
    rc=${PIPESTATUS[0]}
    if [ $rc -ne 0 ]; then
      echo "$msg_program_stop." | tee --append ${OCS_LOGFILE}
      # copy_error_log
      exit 1
    else
      cp -a $vgcfg_tmp $target_dir_fullpath/lvm_$vg.conf
    fi
  done < $PV_PARSE_CONF
  [ -e "$vgcfg_tmp" ] && rm -f $vgcfg_tmp
  echo "done." | tee --append $OCS_LOGFILE

  echo "Checking if the VG config was saved correctly... " | tee --append ${OCS_LOGFILE}
  while read vg dev uuid; do
    if [ ! -e "$target_dir_fullpath/lvm_$vg.conf" ]; then
      [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
      echo "$target_dir_fullpath/lvm_$vg.conf NOT created! No volume group of LVM was saved!" | tee --append ${OCS_LOGFILE}
      [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
      echo "$msg_program_stop!" | tee --append ${OCS_LOGFILE}
      exit 1
    fi
  done < $PV_PARSE_CONF
  echo "done." | tee --append $OCS_LOGFILE

  if [ ! -e "$LOGV_PARSE_CONF" ]; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "///WARNING/// The logical volume setting is not found." | tee --append $OCS_LOGFILE
    echo "Unable to save LVM image." | tee --append $OCS_LOGFILE
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo "$msg_program_stop!" | tee --append ${OCS_LOGFILE}
    #copy_log $target_dir_fullpath "lvm"
    return 1
  fi

  # To avoid the stdin/stdout in ntfsclone/partimage/partclone (image_save $lv $target_dir_fullpath $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. $lv info: $fs" | tee --append $OCS_LOGFILE
   # skip swap or extended partition
   case "$fs" in 
     *[Ss][Ww][Aa][Pp]*)
       output_swap_partition_uuid_label $lv $target_dir_fullpath/swappt-$(to_filename ${fn}).info
       continue ;;
     *extended*) 
       continue ;;
   esac
   image_save $lv $target_dir_fullpath $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 ipt
  # target_dir_fullpath is local (in function task_saveparts) global variable, since restore_logv is used in function task_restoreparts 
  if [ -z "$mode" ]; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
    echo "To restore LVM, you must specify it's unicast or multicast!!!" | tee --append ${OCS_LOGFILE}
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo "$msg_program_stop!!!" | tee --append ${OCS_LOGFILE}
    exit 1
  fi
  PV_PARSE_CONF="$target_dir_fullpath/lvm_vg_dev.list"
  LOGV_PARSE_CONF="$target_dir_fullpath/lvm_logv.list"

  echo "Preparing the LVM (PV/VG/LV)... " | tee --append $OCS_LOGFILE
  #
  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
       if check_LVM_partition /dev/$ipt; then
         clean_filesystem_header_in_partition /dev/$ipt
       fi
     fi
   done
  done < $LOGV_PARSE_CONF

  if [ ! -e "$PV_PARSE_CONF" ]; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "///WARNING/// $(basename $PV_PARSE_CONF) not found." | tee --append $OCS_LOGFILE
    echo "Unable to create PV." | tee --append $OCS_LOGFILE
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo $msg_delimiter_star_line | tee --append $OCS_LOGFILE
    # copy_log $target_dir_fullpath "lvm"
    return 1
  fi
  #
  lvm_tmp="$(mktemp -d /tmp/lvm_tmp.XXXXXX)"
  # create PV first
  echo "Creating the PV... " | tee --append $OCS_LOGFILE
  while read vg dev uuid; do
    if [ "$vg" = "/NOT_FOUND" -o ! -e $target_dir_fullpath/lvm_$vg.conf ]; then
      if is_whole_disk $dev; then
        # Make sure there is no any partition on the disk. Otherwise even -ff is used, pvcreate will still quit it.
        clean_mbr_gpt_part_table $dev
      fi
      pvcreate -ff --yes --uuid $uuid --zero y $dev 3<&- 4<&-
    else
      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.
      if is_whole_disk $dev; then
        # Make sure there is no any partition on the disk. Otherwise even -ff is used, pvcreate will still quit it.
        clean_mbr_gpt_part_table $dev
      fi
      pvcreate -ff --yes --uuid $uuid --zero y --restorefile $lvm_tmp/lvm_$vg.conf $dev 3<&- 4<&-
      rm -f $lvm_tmp/lvm_$vg.conf
    fi
  done < $PV_PARSE_CONF
  echo "done." | tee --append $OCS_LOGFILE
  
  if [ -z "$(grep -v "/NOT_FOUND" $PV_PARSE_CONF 2>/dev/null)" ]; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "///WARNING/// The volume group setting is not found." | tee --append $OCS_LOGFILE
    echo "Unable to create VG." | tee --append $OCS_LOGFILE
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo $msg_delimiter_star_line | tee --append $OCS_LOGFILE
    # copy_log $target_dir_fullpath "lvm"
    return 1
  fi

  # Restore the vg conf
  echo "Restoring the VG config... " | tee --append $OCS_LOGFILE
  while read vg dev uuid; do
    if [ "$vg" = "/NOT_FOUND" ]; then
      [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
      echo "///WARNING/// $dev didn't have the volume group." | tee --append $OCS_LOGFILE
      [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
      # copy_log $target_dir_fullpath "lvm"
      continue
    fi
    if [ ! -e $target_dir_fullpath/lvm_$vg.conf ]; then
      [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
      echo "///WARNING/// "lvm_$vg.conf" not found." | tee --append $OCS_LOGFILE
      [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
      echo $msg_delimiter_star_line | tee --append $OCS_LOGFILE
      # copy_log $target_dir_fullpath "lvm"
      continue
    fi
    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>&1 3<&- 4<&- | tee --append $OCS_LOGFILE
    rc=${PIPESTATUS[0]}
    rm -f $lvm_tmp/lvm_$vg.conf
    [ $rc -ne 0 ] && break
  done < $PV_PARSE_CONF
  [ -d "$lvm_tmp" -a -n "$lvm_tmp" ] && rmdir $lvm_tmp 
  echo "done." | tee --append $OCS_LOGFILE
  if [ $rc -ne 0 ]; then
    echo "$msg_program_stop." | tee --append ${OCS_LOGFILE}
    [ "$save_restore_error_log" = "yes" ] && copy_error_log
    exit 1
  fi
  #
  ocs-lvm2-start
  
  if [ ! -e "$LOGV_PARSE_CONF" ]; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "///WARNING/// $(basename $LOGV_PARSE_CONF) not found." | tee --append $OCS_LOGFILE
    echo "Unable to create LV." | tee --append $OCS_LOGFILE
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo $msg_delimiter_star_line | tee --append $OCS_LOGFILE
    #copy_log $target_dir_fullpath "lvm"
    return 1
  fi

  # Log the target_logv so that later we can used. target_logv is global variable
  # 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
     for i in $(sed -e 's!^.*/dev/\([^[:space:]]\{3,\}\)[[:space:]]*.*$!\1!g' $PV_PARSE_CONF); do
       if [ "$ipt" = "$(get_master_dev_of_multipath $i)" ]; then
         is_in_chosen_partition="yes"
         continue
       fi
     done
     #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
   # Convert to file name prefix, e.g. /dev/lucid-server/root -> lucid-server-root
   fn="$(echo $lv | sed -e "s|^/dev/||" -e "s|/|-|g")"
   # Log the logical volume which is restored. The leading "/dev/" is removed, i.e. the results might be: lucid-server/root lucid-server/usr
   target_logv="$target_logv ${lv#/dev/*}"
   # 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-$(to_filename ${fn}).info exists
        uuid_opt=""
        label_opt=""
	if [ -e "$target_dir_fullpath/swappt-$(to_filename ${fn}).info" ]; then
          UUID=""
          LABEL=""
	  . "$target_dir_fullpath/swappt-$(to_filename ${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..." | tee --append $OCS_LOGFILE
   case "$mode" in
     unicast)
       do_unicast_stdin_restore $target_dir_fullpath $fn $lv
       rc=$?
       ;;
     multicast)
       do_multicast_udpcast_restore $target_dir_fullpath $fn $lv
       rc=$?
       ;;
   esac
   if [ $rc -gt 0 ]; then
     [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
     echo "Failed to restore partition image file $target_dir_fullpath/${fn}* to $lv. Maybe this image is corrupt." | tee --append $OCS_LOGFILE
     [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
     echo "$msg_program_stop"      | tee --append ${OCS_LOGFILE}
     echo $msg_delimiter_star_line | tee --append ${OCS_LOGFILE}
     [ "$save_restore_error_log" = "yes" ] && copy_error_log
     exit 1
   fi
  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!!!" | tee --append ${OCS_LOGFILE}
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo "$msg_program_stop!!!" | tee --append ${OCS_LOGFILE}
    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/$(to_filename $fn)* 2>/dev/null)" ]; then
     [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
     echo "$imagedir/$target_dir/$(to_filename $fn)* was not found! 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!" | tee --append ${OCS_LOGFILE}
    echo "$disk_file is empty ?" | tee --append ${OCS_LOGFILE}
    echo "Maybe disk is full when you saved image?" | tee --append ${OCS_LOGFILE}
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo "$msg_program_stop!" | tee --append ${OCS_LOGFILE}
    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!" | tee --append ${OCS_LOGFILE}
    echo "$parts_file is empty ?" | tee --append ${OCS_LOGFILE}
    echo "Maybe diskfull when you saved image ?" | tee --append ${OCS_LOGFILE}
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo "$msg_program_stop!" | tee --append ${OCS_LOGFILE}
    exit 1
  fi
}
#
stop_ocs_service() {
  local to_gen_ssi_files="$1"
  local IP ihost
  local pxecfg grubcfg pxecfg_MAC grubcfg_MAC
  # 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
    remove_opt_in_grub_efi_cfg_block "clonezilla-se-client" ocs_opt
    # Update the description to avoid confusion.
    force_grub_efi_clients_boot_label clonezilla-se-client "Clonezilla"
    hide_reveal_grub_efi_ent "clonezilla-se-client" hide $GRUB_CONF
  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

    # The stale PXE config files and GRUB EFI NB config files are removed when drbl-ocs is started in
    # the beginning. We should not process here again.
    ## Remove the pxe config file in $pxecfg_pd/pxelinux.cfg/ (IP-based), like C0A84601
    ## Remove the GRUB uEFI NB config file in $pxecfg_pd/grub-efi.cfg/ (IP-based), like grub.cfg-C0A84601
    #pxecfg="$(drbl-gethostip $IP)"
    #grubcfg="grub.cfg-""$pxecfg"
    #[ -f "$PXELINUX_DIR/$pxecfg" ] && rm -f $PXELINUX_DIR/$pxecfg
    #[ -f "$GRUB_EFINB_DIR/$grubcfg" ] && rm -f $GRUB_EFINB_DIR/$grubcfg
    #
    ## 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 ":" "-")"
    #grubcfg_MAC="grub.cfg-""$pxecfg_MAC"
    #[ -f "$PXELINUX_DIR/$pxecfg_MAC" ] && rm -f $PXELINUX_DIR/$pxecfg_MAC
    #[ -f "$GRUB_EFINB_DIR/$grubcfg_MAC" ] && rm -f $GRUB_EFINB_DIR/$grubcfg_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_service

#
# 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
  local mgrd_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.org 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_client_menu_label_prefix: $ocs_menu_label"
  else
    force_pxe_clients_boot_label clonezilla "$clonezilla_client_menu_label_prefix: $ocs_menu_label"
    force_grub_efi_clients_boot_label clonezilla-se-client "$clonezilla_client_menu_label_prefix: $ocs_menu_label"
  fi
  # set runlevel 1 to kernel parameter in pxelinux config
  add_runlevel_1_in_pxelinux_cfg_block clonezilla
  add_runlevel_1_in_grub_efi_cfg_block clonezilla-se-client

  # 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"
	 # TODO, a better way to replace boot local under this case that
	 # there is no easy way to boot local in uEFI mode.
         ;;
     "drbl")
         force_pxe_clients_boot_label drbl
         force_grub_efi_clients_boot_label drbl-client
         ;;
   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
  if [ "$always_restore" = "yes" ]; then
    # If it's always_restore, we do not generate client's PXE config when receiving job is done message from client.
    mgrd_opt="--nopxecfg"
  fi
  ocsmgrd $mgrd_opt &
  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 $OCS_LOGFILE"
  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" 
    if [ -z "$udpcast_hold_opt1" -a -z "$udpcast_hold_opt2" -a -z "$udpcast_hold_opt3" ]; then
      echo "No time_to_wait, clients_to_wait or clients+time_to_wait option was found." | tee --append ${OCS_LOGFILE}
      echo "$msg_program_stop!" | tee --append ${OCS_LOGFILE}
      exit 1
    fi
  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_parts_list_from_img $imagedir/$target_dir`; 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

      # Strip the single white space which should be nothing. Thanks to Borksoft.
      target_parts="$(echo $target_parts | sed -e "s/^  *$//")"
      # 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!" | tee --append ${OCS_LOGFILE}
	echo "Failed to start $multi_broad_cast_prompt clone service for image $target_dir!" | tee --append ${OCS_LOGFILE}
        [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
        echo "$msg_program_stop!" | tee --append ${OCS_LOGFILE}
        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!" | tee --append ${OCS_LOGFILE}
	echo "Failed to start $multi_broad_cast_prompt clone service for image $target_dir!" | tee --append ${OCS_LOGFILE}
        [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
        echo "$msg_program_stop!" | tee --append ${OCS_LOGFILE}
        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 "/usr/sbin/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 ]
     # The OCS_OPT is passed to program "/usr/sbin/clonezilla", not ocs-sr.
     # We added "-k|--skip-prep-ocsroot" to skip prep-ocsroot action
     # First we extract the "-p action" part
     # E.g. echo "--batch -y1 -p reboot -k1" | grep -oEw -- "-p[[:space:]]+[[:alnum:]]+([[:space:]]|$)"
     OCS_OPT="$(LC_ALL=C echo "$OCS_OPT" | grep -oEw -- "-p[[:space:]]+[[:alnum:]]+([[:space:]]|$)")"
     # Then append "-k" for "/usr/sbin/clonezilla".
     OCS_OPT="$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
        remove_opt_in_grub_efi_cfg_block "clonezilla-se-client" 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"
        add_opt_in_grub_efi_cfg_block "clonezilla-se-client" 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"
        add_opt_in_grub_efi_cfg_block "clonezilla-se-client" 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 /etc/drbl/drbl-ocs.conf!" | tee --append ${OCS_LOGFILE}
      [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
      echo "$msg_program_stop!" | tee --append ${OCS_LOGFILE}
      exit 1
  esac
} # end of prep_client_inittab_rc_srv

# task_notify_job_is_done
# ask for localboot next time
task_notify_job_is_done() {
  # Since now ocsmgrd has an option "--nopxecfg" (otherwise in Clonezilla live mode on Clonezilla SE, $always_restore won't be passed to client). No more skipping from clients.
  #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 $efi_os_label $efi_system_part_no $efi_sys_part_boot_file $report_msg\" to $ocs_server:$OCSMGRD_PORT... "
  ocs-socket -m "$ip $mac $efi_os_label $efi_system_part_no $efi_sys_part_boot_file $report_msg" -h $ocs_server -p $OCSMGRD_PORT
  echo "done!"
  echo $msg_delimiter_star_line
  echo "Finished!"
} # end of task_notify_job_is_done
#
show_warning_about_write_failed() {
  local img_dir=$1
  [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
  echo "The disk or partition information can NOT be written in target directory $img_dir!" | tee --append $OCS_LOGFILE
  echo "Maybe the disk or partition is full in Clonezilla server ?" | tee --append $OCS_LOGFILE
  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 img_name_prompt="$1" # The image name is optional, only for prompt in the dialog.
  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 $img_name_prompt
    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 
    elif [ "$target_dir" = "autoname" ]; then
       $DIA --backtitle "$msg_nchc_free_software_labs" --title "$msg_nchc_clonezilla | $msg_mode: $ocs_mode_prompt" \
      --msgbox "$msg_autoname_is_reserved_for_save_mode\n$msg_please_do_it_again!!!" 0 0 
    elif [ "$target_dir" = "autohostname" ]; then
       $DIA --backtitle "$msg_nchc_free_software_labs" --title "$msg_nchc_clonezilla | $msg_mode: $ocs_mode_prompt" \
      --msgbox "$msg_autohostname_is_reserved_for_save_mode\n$msg_please_do_it_again!!!" 0 0 
    elif [ "$target_dir" = "autoproductname" ]; then
       $DIA --backtitle "$msg_nchc_free_software_labs" --title "$msg_nchc_clonezilla | $msg_mode: $ocs_mode_prompt" \
      --msgbox "$msg_autoproductname_is_a_reserved\n$msg_please_do_it_again!!!" 0 0 
    else
       ASK_IMG_NAME=0
    fi
  done
  [ -e "$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
  [ -e "$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
  [ -e "$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)"
  [ -e "$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"
  [ -e "$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"
  [ -e "$ANS_TMP" ] && rm -f $ANS_TMP
} # end of get_target_dir_name_when_checking_img_restorable
#
get_target_dir_name_when_converting_img() {
  # target_dir and ocsroot are gloable variables
  local ANS_TMP
  local only_type  # Available types: p2v, enc (encrypted image), nonenc (non-encrypted image)
  #
  while [ $# -gt 0 ]; do
    case "$1" in
      -o|--only)
         shift
         if [ -z "$(echo $1 |grep ^-.)" ]; then
           # skip the -xx option, in case 
           only_type="$1"
           shift
         fi
         [ -z "$only_type" ] && echo "-x is used, but no only_type assigned." && exit 1
         ;;
      -*)     echo "${0}: ${1}: invalid option" | tee --append ${OCS_LOGFILE} >&2
              echo "Unknown option in function get_target_dir_name_when_converting_img" | tee --append ${OCS_LOGFILE} >& 2
              echo "$msg_program_stop" | tee --append ${OCS_LOGFILE}
              [ "$save_restore_error_log" = "yes" ] && copy_error_log
              exit 2 ;;
      *)      break ;;
    esac
  done
  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
  case "$only_type" in
    p2v)    get_existing_parts_image $ANS_TMP p2v ;;
    enc)    get_existing_parts_image $ANS_TMP enc ;;
    nonenc) get_existing_parts_image $ANS_TMP nonenc ;;
    *)      get_existing_parts_image $ANS_TMP convert ;;
  esac
  # 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"
  [ -e "$ANS_TMP" ] && rm -f $ANS_TMP
} # end of get_target_dir_name_when_converting_img
#
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"
  local tmp_img_from_to_opt

  # 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
      [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
      echo "Target disk $i does not exist in the image saved from disk(s) \"$dsksname_from_img\"."
      [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
      tgtdisk_exist_flag=1
      break
    fi
  done
  # source_part is a global variable.
  # If source_part exists, it means the image of a partition will be restored to different part.
  tmp_img_from_to_opt=""
  if [ -n "$source_part" ]; then
    tgtdisk_exist_flag=1
    tmp_img_from_to_opt="-f $source_part -d $target_parts"
  fi
  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_FAILURE
      echo "$msg_create_cvrt_img_for_different_not_supported_in_multicast_mode $msg_only_same_disk_name_work_try_cnvt_ocs_dev_instead" | tee --append ${OCS_LOGFILE}
      [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
      echo "$msg_program_stop!" | tee --append ${OCS_LOGFILE}
      exit 1
    fi
    if [ "$src_dsk_no" -ge 2 ]; then
      [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
      echo "$msg_create_cvrt_img_for_more_than_2_disks_not_supported $msg_only_same_disk_name_work_try_cnvt_ocs_dev_instead" | tee --append ${OCS_LOGFILE}
      [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
      echo "$msg_program_stop!" | tee --append ${OCS_LOGFILE}
      exit 1
    fi
    is_spawned_by_drbl_ocs $ocs_ppid
    rc=$?
    if [ "$rc" -eq 0 ]; then
      [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
      echo "$msg_create_cvrt_img_for_clonezilla_server_edition_client_not_supported $msg_only_same_disk_name_work_try_cnvt_ocs_dev_instead" | tee --append ${OCS_LOGFILE}
      [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
      echo "$msg_program_stop!" | tee --append ${OCS_LOGFILE}
      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 -or $ocsroot $tmp_img_from_to_opt $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
#
check_mbr_gpt_partition_table() {
  # With fdisk, sfdisk and sgdisk, we can decide the partition type is MBR or GPT, or corrupt
  local tdisk=$1  # e.g. sda

  # Different cases
  # (1) Only MBR
  #     "WARNING: GPT (GUID Partition Table) detected" will be shown by fdisk or sgdisk
  # (2) Only GPT
  # (3) Valid MBR, corrupt GPT
  # (4) Corrupt MBR, valid GPT
  # (5) Valid MBR, valid GPT, but mismatch each other
  # Ref: https://sourceforge.net/projects/clonezilla/forums/forum/663168/topic/4880971
  # For (1) and (2), OK, for (3)-(5), we have to give warning and prompt
  # By using "sgdisk -v $device", it should give the warning like this:
  # sgdisk -v /dev/sda
  # Invalid partition data!
  # Verification may miss some problems or report too many!
  # 
  # Warning! Mismatched GPT and MBR partition! MBR partition 1, of type 0x83,
  # has no corresponding GPT partition! You may continue, but this condition
  # might cause data loss in the future!
  # 
  # Identified 1 problems!
  # 
  if ! type sgdisk &>/dev/null; then
    echo "Program sgdisk not found! Skip checking MBR and GPT partition table!" | tee --append ${OCS_LOGFILE}
    return 3
  fi
  if [ -n "$(LC_ALL=C sgdisk -v /dev/$tdisk 2>&1 | grep -i "Mismatched GPT and MBR partition")" ]; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
    echo "$msg_mismatched_GPT_MBR_partition: /dev/$tdisk" | tee --append ${OCS_LOGFILE}
    echo "$msg_confuse_clonezilla_and_saved_image_useless" | tee --append ${OCS_LOGFILE}
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "$msg_description_to_fix_mismatched_GPT_and_MBR_partition:" | tee --append ${OCS_LOGFILE}
    echo "sudo sgdisk -z /dev/sdx" | tee --append ${OCS_LOGFILE}
    echo "$msg_replace_sdx_with_harddrive_name" | tee --append ${OCS_LOGFILE}
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo "$msg_please_fix_this_issue_then_restart_clonezilla_again" | tee --append ${OCS_LOGFILE}
    echo "$msg_program_stop!" | tee --append ${OCS_LOGFILE}
    # Saving mode, always copy error log to image dir.
    copy_error_log
    exit 1
  fi
} # end of check_mbr_gpt_partition_table
#
check_if_windows_boot_reserve_part() {
  local tpart="$1"  # e.g. /dev/sda1
  local tfs rv
  if [ -z "$tpart" ]; then
    echo "No \"tpart\" in function check_if_windows_boot_reserve_part!"
    return 1
  fi
  rv=1
  tfs="$(LC_ALL=C ocs-get-part-info $tpart fs)"
  if [ "$tfs" = "ntfs" ]; then
    ntfs_mnt="$(mktemp -d /tmp/ntfs_mnt.XXXXXX)"
    # ntfs-3g -o ro $tpart $ntfs_mnt
    mount -o ro $tpart $ntfs_mnt
    if [ -d "$ntfs_mnt/Boot" -a \
	 -e "$ntfs_mnt/bootmgr" -a \
	 -e "$ntfs_mnt/BOOTSECT.BAK" ]; then
	 rv=0
    fi
    umount $ntfs_mnt
  fi
  [ -d "$ntfs_mnt" -a -n "$(echo $ntfs_mnt | grep "ntfs_mnt")" ] && rmdir $ntfs_mnt
  return $rv
} # end of check_if_windows_boot_reserve_part

# 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
  local pv_on_dsk

  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
   
  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
    
    # Check if mbr and gpt partition table coexist
    check_mbr_gpt_partition_table $idisk

    BACKUP_DEVS=""
    echo "Searching for data partition(s)..." | tee --append $OCS_LOGFILE
    get_known_partition_proc_format $idisk data
    target_parts="$target_parts $BACKUP_DEVS"

    BACKUP_DEVS=""
    echo "Searching for swap partition(s)..." | tee --append $OCS_LOGFILE
    get_known_partition_proc_format $idisk swap
    target_swap_parts="$target_swap_parts $BACKUP_DEVS"
  done

  # There is still one possiblity that we need to deal with LVM, i.e. PV is not on partition, 
  # instead it's on a disk (e.g. /dev/sdb).
  pv_on_dsk="$(get_pv_on_disk_from_local_machine)"  # pv_on_dsk: sda, sdb...
  for ip in $pv_on_dsk; do
    if [ -n "$(LC_ALL=C echo "$target_hd" | grep -Ew "${ip}")" ]; then
      # Only when it's listed in the target_hd, we add it.
      if [ -z "$(echo $target_parts | grep -Ew ${ip})" ]; then
        # Avoid duplicated one since task_savedisk might already list it already.
        target_parts="$target_parts ${ip}"
      fi
    fi
  done

  # Strip the single white space which should be nothing. Thanks to Borksoft.
  target_parts="$(echo $target_parts | sed -e "s/^  *$//")"
  target_swap_parts="$(echo $target_swap_parts | sed -e "s/^  *$//")"
  [ -z "$target_parts" ] || echo "The data partition to be saved: $target_parts" | tee --append $OCS_LOGFILE
  [ -z "$target_swap_parts" ] || echo "The swap partition to be saved: $target_swap_parts" | tee --append $OCS_LOGFILE
  # 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 | tee --append $OCS_LOGFILE
  for isp in $target_swap_parts; do
    echo "Saving swap partition $isp info in $ocsroot/$target_dir/swappt-$(to_filename ${isp}).info if it exists..." | tee --append $OCS_LOGFILE
    output_swap_partition_uuid_label /dev/$isp $ocsroot/$target_dir/swappt-$(to_filename ${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_if_assigned $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
  echo "End of savedisk job for image $target_dir." | tee --append $OCS_LOGFILE

  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
  local target_dir_fullpath
  local minix_subpt_start minix_subpt_end minix_subpt_size cmd_save_minix_sub_parts_table
  local pv_on_dsk pv_on_dsk_flag

  # target_hd will be extracted 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 absolute path, i.e. put leading $ocsroot.
  target_dir_fullpath="$ocsroot/$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="$(get_diskname $ipart)"
    if [ -z "$target_hd" ]; then
	    target_hd="$hd_tmp"
    elif [ -z "$(echo $target_hd | grep -Ew "$hd_tmp" 2>/dev/null)" ]; then
	    target_hd="$target_hd $hd_tmp"
    fi
  done

  # Check if mbr and gpt partition table coexist
  for ihd in $target_hd; do
    check_specify_hd_exists $ihd
    check_mbr_gpt_partition_table $ihd
  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_fullpath" 

  # Check if the image repository is writable.
  if [ ! -w "$ocsroot" ]; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
    echo "$msg_img_repository_not_writable: $ocsroot"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo "$msg_program_stop!"
    exit 1
  fi

  # If the existing target image exists, remove it.
  rm_target_image_if_exist $target_dir_fullpath

  # Turn on HD DMA
  for ihd in $target_hd; do
    [ "$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_fullpath

  # Save multipath infomation file
  if ls ${MULTIPATH_INFODIR}/*-multipath >/dev/null 2>&1; then
    cp -f ${MULTIPATH_INFODIR}/*-multipath $target_dir_fullpath
  fi

  # Save device mapper info file. We want to output a table listing:
  # dmraid (disk): dm (partition)
  # E.g. the following layoutput will give a table:
  # dm-0: dm-1 dm-3
  #
  # lsblk output example:
  #  NAME                              MAJ:MIN RM   SIZE RO TYPE   MOUNTPOINT
  #  sdb                                 8:16   0 931.5G  0 disk   
  #  └─isw_jdbgiifhb_Volume0 (dm-0)    254:0    0   1.8T  0 dmraid 
  #    ├─isw_jdbgiifhb_Volume01 (dm-1) 254:1    0   244M  0 dmraid 
  #    └─isw_jdbgiifhb_Volume05 (dm-3) 254:3    0   1.8T  0 dmraid 
  #      ├─sars--007-root (dm-2)       254:2    0   1.8T  0 lvm    
  #      └─sars--007-swap_1 (dm-4)     254:4    0   3.9G  0 lvm    
  #  sda                                 8:0    0 931.5G  0 disk   
  #  ├─sda1                              8:1    0   244M  0 part   
  #  ├─sda2                              8:2    0     1K  0 part   
  #  └─isw_jdbgiifhb_Volume0 (dm-0)    254:0    0   1.8T  0 dmraid 
  #    ├─isw_jdbgiifhb_Volume01 (dm-1) 254:1    0   244M  0 dmraid 
  #    └─isw_jdbgiifhb_Volume05 (dm-3) 254:3    0   1.8T  0 dmraid 
  #      ├─sars--007-root (dm-2)       254:2    0   1.8T  0 lvm    
  #      └─sars--007-swap_1 (dm-4)     254:4    0   3.9G  0 lvm    
  #  sdc                                 8:32   1   3.7G  0 disk   
  #  └─sdc1                              8:33   1   3.7G  0 part   
  #  sr0                                11:0    1  1024M  0 rom    
  #  loop0                               7:0    0  88.3M  1 loop   
  if type lsblk &>/dev/null; then
    if grep -qE "dm-[[:digit:]]+" /proc/partitions; then
      echo "Dumping the device mapper table in $target_dir_fullpath/dmraid.table..." | tee --append $OCS_LOGFILE
      echo "# dmraid: dm" > $target_dir_fullpath/dmraid.table
      # First find all the devices with type "dmraid"
      dmraid_dev="$(LC_ALL=C lsblk -n -o type,kname | awk -F" " '/^dmraid/ {print $2}' | sort | uniq)"
      for i in $dmraid_dev; do
        # Check if the "dmraid" contains other "dmraid", "dm" or "part". If yes, then it's really a dmraid (disk), and its slaves are dm (partition)
        dm_p="$(LC_ALL=C lsblk -n -o type,kname /dev/${i} | awk -F" " '/^(dmraid|dm|part)/ {print $2}' | sort | uniq | grep -v -Ew "${i}")"
        dm_p="$(echo $dm_p)"    # make them in a line
        if [ -n "$dm_p" ]; then
          # dmraid $i contains at least one or more dm. $i is a "disk", and $dm_p must be partitions.
          echo "$i: $dm_p" >> $target_dir_fullpath/dmraid.table
        fi
      done
    fi

    echo "Saving block devices info in $target_dir_fullpath/blkdev.list..." | tee --append $OCS_LOGFILE
    output_blkdev_info $target_dir_fullpath/blkdev.list

    echo "Saving block devices attributes in $target_dir_fullpath/blkid.list..." | tee --append $OCS_LOGFILE
    output_blkid_info $target_dir_fullpath/blkid.list
  fi
  # Add the column explanation in dev-fs.list. Later we will append line by line in task_saveparts.
  echo "# <Device name>   <File system>" > $target_dir_fullpath/dev-fs.list
  echo "# File system is got from ocs-get-part-info. It might be different from that of blkid or parted." >> $target_dir_fullpath/dev-fs.list

  # 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..." | tee --append $OCS_LOGFILE
    # 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_fullpath/$(to_filename ${ihd})-pt.sf 2>/dev/null
    RETVAL="$?"
    echo "RETVAL=$RETVAL" | tee --append $OCS_LOGFILE
    clean_cylinder_boundary_warning $target_dir_fullpath/$(to_filename ${ihd})-pt.sf
    output_HD_CHS $ihd $target_dir_fullpath
    # Output the info from parted, it contains more info.
    LC_ALL=C parted -s /dev/$ihd unit s print > $target_dir_fullpath/$(to_filename ${ihd})-pt.parted
    # Output another one which is easier for human to read
    LC_ALL=C parted -s /dev/$ihd unit compact print > $target_dir_fullpath/$(to_filename ${ihd})-pt.parted.compact
    # From the output file of parted, we can decide if the partition is gpt or mbr
    # //NOTE// Here we can not use function "get_partition_table_type_from_img"
    # because the image is not created completely yet.
    if `is_gpt_partitition_table_file $target_dir_fullpath/$(to_filename ${ihd})-pt.parted`; 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 | tee --append ${OCS_LOGFILE}
      echo "Saving the primary GPT of $ihd as $target_dir_fullpath/$(to_filename ${ihd})-gpt-1st by dd..." | tee --append ${OCS_LOGFILE}
      LC_ALL=C dd if=/dev/$ihd of=$target_dir_fullpath/$(to_filename ${ihd})-gpt-1st bs=512 count=34 2>&1 | tee --append ${OCS_LOGFILE}
      echo $msg_delimiter_star_line | tee --append ${OCS_LOGFILE}
      # 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_fullpath/$(to_filename ${ihd})-gpt-2nd by dd..." | tee --append ${OCS_LOGFILE}
      disk_size_sec="$(LC_ALL=C grep -E "^Disk /dev/" $target_dir_fullpath/$(to_filename ${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_fullpath/$(to_filename ${ihd})-gpt-2nd skip=${to_skip} bs=512 count=33 2>&1 | tee --append ${OCS_LOGFILE}
      echo $msg_delimiter_star_line | tee --append ${OCS_LOGFILE}
      if type sgdisk &>/dev/null; then
	echo "Saving the GPT of $ihd as $target_dir_fullpath/$(to_filename ${ihd})-gpt.gdisk by gdisk..." | tee --append ${OCS_LOGFILE}
	LC_ALL=C sgdisk -b $target_dir_fullpath/$(to_filename ${ihd})-gpt.gdisk /dev/$ihd | tee --append ${OCS_LOGFILE}

        # Dump the GPT table in plain text
	LC_ALL=C sgdisk -p /dev/$ihd > $target_dir_fullpath/$(to_filename ${ihd})-gpt.sgdisk | tee --append ${OCS_LOGFILE}
        echo $msg_delimiter_star_line | tee --append ${OCS_LOGFILE}
      fi
    elif `is_mbr_partitition_table_file $target_dir_fullpath/$(to_filename ${ihd})-pt.parted`; then
      # MBR 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 | tee --append ${OCS_LOGFILE}
      if [ "$clone_hidden_data" = "yes" ]; then
	save_hidden_data_after_MBR $ihd $target_dir_fullpath/$(to_filename ${ihd})-hidden-data-after-mbr
      fi
      # Find if the 1st partition (e.g. /dev/sda1) is minix device (Id=81).
      # We do not care about multipath device (e.g. cciss, i.e. /dev/cciss/c0d0p1), since we believe no one will install Minix on multipath device.
      if [ -n "$(grep -Ew "^/dev/$(to_filename ${ihd})1" $target_dir_fullpath/$(to_filename ${ihd})-pt.sf 2>/dev/null | grep -Ewi "Id=81")" ]; then
        # It's minix slice, we only have to dump the sub-partition table, i.e. 32 sectors.
	# The slices, sub-partitions list is like:
	# partx -b -P -o nr,type,scheme,start,end /dev/sda
	# NR="1" TYPE="0x81" SCHEME="dos" START="63" END="16771859"
	# NR="5" TYPE="0x81" SCHEME="minix" START="95" END="131166"
	# NR="6" TYPE="0x81" SCHEME="minix" START="131167" END="3233886"
	# NR="7" TYPE="0x81" SCHEME="minix" START="3233887" END="16771859"
	minix_subpt_start="$(LC_ALL=C partx -g -b -o start /dev/$(to_filename ${ihd})1 2>/dev/null)"
	minix_subpt_end="$(LC_ALL=C partx -g -b -o start /dev/$(to_filename ${ihd})5 2>/dev/null)"
	if [ -n "$minix_subpt_start" -a -n "$minix_subpt_end" ]; then
	  minix_subpt_size="$((minix_subpt_end - minix_subpt_start))"
        fi
	if [ "$minix_subpt_size" -gt 0 ]; then
	  cmd_save_minix_sub_parts_table="dd if=/dev/$(to_filename ${ihd})1 of=$target_dir_fullpath/$(to_filename ${ihd})1-sub-pt.dd bs=512 count=$minix_subpt_size"
	  echo "Saving the Minix sub-partition table on /dev/$(to_filename ${ihd})1 by:" | tee --append ${OCS_LOGFILE}
	  echo $cmd_save_minix_sub_parts_table | tee --append ${OCS_LOGFILE}
          LC_ALL=C eval "(${cmd_save_minix_sub_parts_table} && exit \${PIPESTATUS[0]})"
	  rc=$?
	  if [ "$rc" -eq 0 ]; then
	    # Removing "$(to_filename ${ihd})1" from target_parts, we do not have to save it since this will be duplicated with other sub partitions
	    target_parts="$(echo $target_parts | sed -r -e "s|$(to_filename ${ihd})1[[:space:]]+||g")"
	  fi
	fi
      fi
      echo $msg_delimiter_star_line | tee --append ${OCS_LOGFILE}
    else
      # There is still one possiblity that we need to deal with LVM, i.e. PV is not on partition, 
      pv_on_dsk="$(get_pv_on_disk_from_local_machine)"
      pv_on_dsk_flag="0"
      for ip in $pv_on_dsk; do
        if [ -n "$(LC_ALL=C echo "$ihd" | grep -Ew "${ip}")" ]; then
          # Found PV on disk
          pv_on_dsk_flag="1"
	  break
        fi
      done
      if [ "$pv_on_dsk_flag" -eq 0 ]; then
        # No PV on disk, it does not make sense to save this bare disk.
        [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
        echo "Unknown partition table format on disk /dev/$ihd." | tee --append ${OCS_LOGFILE}
        [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
        echo "$msg_program_stop!" | tee --append ${OCS_LOGFILE}
        exit 1
      fi
    fi
    if [ "$RETVAL" -ne 0 ]; then
      [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
      echo "$msg_failed_to_save_partition_table_for_disk: $ihd" | tee --append ${OCS_LOGFILE}
      [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
      echo "$msg_program_stop!" | tee --append ${OCS_LOGFILE}
      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..." | tee --append $OCS_LOGFILE
    dd if=/dev/$ihd of=$target_dir_fullpath/$(to_filename ${ihd})-mbr count=1 bs=512 2>&1 | tee --append ${OCS_LOGFILE}
  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_fullpath $partition
    rc_saveparts="$(($rc_saveparts + $?))"
    # Tag the partition info. This is special for NTFS boot reserved partiition
    rc_boot_reserve=""
    check_if_windows_boot_reserve_part /dev/$partition
    rc_boot_reserve="$?"
    if [ "$rc_boot_reserve" -eq 0 ]; then
      echo "PARTITION_TYPE=Win_boot_reserved" > $target_dir_fullpath/$partition.info
    fi
  done
  # There is still one possiblity that we need to deal with LVM, i.e. PV is not on partition, 
  # instead it's on a disk (e.g. /dev/sdb).
  # Although there is a checking in task_savedisk function, but this is for "saveparts" option.
  pv_on_dsk="$(get_pv_on_disk_from_local_machine)"  # pv_on_dsk: sda, sdb...
  for ip in $pv_on_dsk; do
    if [ -n "$(LC_ALL=C echo "$target_hd" | grep -Ew "${ip}")" ]; then
      # Only when it's listed in the target_hd, we add it.
      if [ -z "$(echo $target_parts | grep -Ew ${ip})" ]; then
        # Avoid duplicated one since task_savedisk might already list it already.
        target_parts="$target_parts ${ip}"
      fi
    fi
  done

  # Strip the single white space which should be nothing. Thanks to Borksoft.
  target_parts="$(echo $target_parts | sed -e "s/^  *$//")"
  # 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

  # Save EFI NVRAM info. What we need is actually the label
  # Only if dir /sys/firmware/efi/efivars exists, the data exist in EFI NVRAM 
  if [ -d "/sys/firmware/efi/efivars" ]; then
    LC_ALL=C efibootmgr -v 2>/dev/null > $target_dir_fullpath/efi-nvram.dat
  fi

  # Dump hardware info
  dump_hardware_software_info $target_dir_fullpath

  echo "$target_parts" > $target_dir_fullpath/parts
  rc=$?
  if [ "$rc" -gt 0 ]; then
    show_warning_about_write_failed $target_dir_fullpath
  else
    gen_md5_sha1_sums_if_assigned $target_dir_fullpath
  fi

  # TODO: show error message if failing; check image?
  echo "End of saveparts job for image $target_dir_fullpath." | tee --append $OCS_LOGFILE

  rc_saveparts="$(($rc_saveparts + $rc))"
  return $rc_saveparts
} # 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
  local pv_on_dsk
  # img_cnvt_flag is a global variable
  get_sort_V_opt

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

  #
  dsksname_from_img="$(get_disk_list_from_img $ocsroot/$target_dir | 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="$(get_disk_list $partition_table | awk -F" " '{print $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 `get_parts_list_from_img $ocsroot/$target_dir`; 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

  # There is still one possiblity that we need to deal with LVM, i.e. PV is not on partition, 
  # instead it's on a disk (e.g. /dev/sdb).
  pv_on_dsk="$(get_pv_on_disk_from_PV_conf_list "$ocsroot/$target_dir/lvm_vg_dev.list")"
  for ip in $pv_on_dsk; do
    if [ -n "$(LC_ALL=C echo "$target_hd" | grep -Ew "${ip}")" ]; then
      # Found PV on disk, force do_LVM_restore as "yes"
      do_LVM_restore="yes"
      # Only when it's listed in the target_hd, we add it.
      if [ -z "$(echo $target_parts | grep -Ew ${ip})" ]; then
        # Avoid duplicated one since task_savedisk might already list it already.
        target_parts="$target_parts ${ip}"
      fi
    fi
  done

  # Strip the single white space which should be nothing. Thanks to Borksoft.
  target_parts="$(echo $target_parts | sed -e "s/^  *$//")"
  # 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"
  echo "Running: task_restoreparts \"$target_dir\" \"$target_parts\" \"$port\"" >> $OCS_LOGFILE
  task_restoreparts "$target_dir" "$target_parts" "$port"

  echo "End of restoredisk job for image $target_dir." | tee --append $OCS_LOGFILE
} # 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 target_logv
  local target_hd=""
  local thd_tmp=""
  local source_hd=""
  local shd_tmp=""
  local continue_choice
  local ptype
  local whole_disk=""
  local cmd_restore_minix_sub_parts_table
  local resize_ntfsfix_opt part_is_lvm
  local pv_on_dsk
  local p_fs ipartition
  local efi_info_tmp

  if [ -z "$target_dir" ]; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
    echo "target_dir is NOT assigned in function task_restoreparts." | tee --append $OCS_LOGFILE
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo "$msg_program_stop." | tee --append $OCS_LOGFILE
    exit 1
  fi
  if [ -z "$target_parts" ]; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
    echo "target_parts is NOT assigned in function task_restoreparts." | tee --append $OCS_LOGFILE
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo "$msg_program_stop." | tee --append $OCS_LOGFILE
    exit 1
  fi

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

  #
  source_parts_no="$(get_parts_list_from_img $ocsroot/$target_dir | 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
    if is_whole_disk $ipart ; then
      whole_disk="$whole_disk $ipart"
      continue
    fi
    thd_tmp="$(get_diskname $ipart)"
    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 `get_parts_list_from_img $ocsroot/$target_dir`; do
    shd_tmp="$(get_diskname $jpart)"
    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
    if [ -n "$target_hd" ]; then
      parts_included="$(grep -Eo "${target_hd}[[:digit:]]+" $partition_table)"
    fi
    [ -f "$partition_table" ] && rm -f $partition_table
    dev_to_be_overwrite="$target_hd $whole_disk $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." | tee --append $OCS_LOGFILE
    #
    countdown_or_confirm_before_restore -s -i "$msg_let_me_ask_you_again.\n" "$target_dir_fullpath" "$target_hd $target_parts"
  fi

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

  # We will only restore hidden data when the mode is restoredisk (including unicast & multicast restoredisk, i.e. restoredisk or multicast_restoredisk).
  if [ "$clone_hidden_data" = "yes" -a -n "$(echo "$ocs_sr_type" | grep -Ei "restoredisk")" ]; 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.
      # Only for MBR, excluding GPT type of disk. We only deal with MBR for this hidden data
      if `is_mbr_partitition_table_disk /dev/$ihd`; then
        echo $msg_delimiter_star_line | tee --append $OCS_LOGFILE
	if [ -e "$target_dir_fullpath/$(to_filename ${ihd})-hidden-data-after-mbr" ]; then
          restore_hidden_data_after_MBR $ihd $target_dir_fullpath/$(to_filename ${ihd})-hidden-data-after-mbr
        else
	  echo "File $target_dir_fullpath/$(to_filename ${ihd})-hidden-data-after-mbr not found!" | tee --append $OCS_LOGFILE
          echo "Skip restoring hidden data after MBR for disk $ihd." | tee --append $OCS_LOGFILE
        fi
        echo $msg_delimiter_star_line | tee --append $OCS_LOGFILE
	# For minix sub-partition table
        if [ -n "$(grep -Ew "^/dev/$(to_filename ${ihd})1" $target_dir_fullpath/$(to_filename ${ihd})-pt.sf 2>/dev/null | grep -Ewi "Id=81")" ]; then
	  if [ -e "$target_dir_fullpath/$(to_filename ${ihd})1-sub-pt.dd" ]; then
	    cmd_restore_minix_sub_parts_table="dd if=$target_dir_fullpath/$(to_filename ${ihd})1-sub-pt.dd of=/dev/$(to_filename ${ihd})1"
	    echo "Restoring the Minix sub-partition table to /dev/$(to_filename ${ihd})1 by:" | tee --append ${OCS_LOGFILE}
	    echo $cmd_restore_minix_sub_parts_table | tee --append ${OCS_LOGFILE}
            LC_ALL=C eval "(${cmd_restore_minix_sub_parts_table} && exit \${PIPESTATUS[0]})"
	    inform_kernel_partition_table_changed mbr /dev/${ihd} | tee --append ${OCS_LOGFILE}
	  fi
	fi
        echo $msg_delimiter_star_line | tee --append $OCS_LOGFILE
      fi
    done
  fi

  PV_PARSE_CONF="$target_dir_fullpath/lvm_vg_dev.list"
  do_LVM_restore="no"
  for partition in $target_parts; do
    # hda1 -> hda
    hd_tmp="$(get_diskname $partition)"
    part_is_lvm="no"
    # 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 [ -e $PV_PARSE_CONF ]; then
      for i in $(sed -e 's!^.*/dev/\([^[:space:]]\{3,\}\)[[:space:]]*.*$!\1!g' $PV_PARSE_CONF); do
        if [ "$partition" = "$(get_master_dev_of_multipath $i)" ]; then
          part_is_lvm="yes"
          break
        fi
      done
    fi
    # If part_is_lvm is yes, we should skip the rest...
    if [ "$part_is_lvm" = "yes" ]; then
      do_LVM_restore="yes"
      # This continue is to skip the rest of this partition do loop
      continue
    fi
    echo "Restoring partition /dev/$partition..." | tee --append $OCS_LOGFILE
    # 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." | tee --append $OCS_LOGFILE
       echo "$msg_press_enter_to_continue..." | tee --append ${OCS_LOGFILE}
       [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
       read
       echo $msg_delimiter_star_line | tee --append ${OCS_LOGFILE}
    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
      # Decide if the partition is gpt or mbr
      ptype="$(get_partition_table_type_from_img "$target_dir_fullpath" "${ihd}")"
      case "$ptype" in
       mbr)
	  echo "Finding swap partition(s) in MBR table $target_dir_fullpath/$(to_filename ${ihd})-pt.sf..." | tee --append $OCS_LOGFILE
	  for partition in `get_swap_partition_sf_format $target_dir_fullpath/$(to_filename ${ihd})-pt.sf`; do
          echo "Creating swap partition /dev/$partition..." | tee --append $OCS_LOGFILE
          check_specify_part_exists $partition
          echo "Found the swap partition /dev/$partition info in the image dir, create it by:" | tee --append $OCS_LOGFILE
	  # read LABEL, UUID info for $partition if swappt-$(to_filename $partition).info exists
          uuid_opt=""
          label_opt=""
	  if [ -e "$target_dir_fullpath/swappt-$(to_filename $partition).info" ]; then
            UUID=""
            LABEL=""
	    . "$target_dir_fullpath/swappt-$(to_filename $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" | tee --append $OCS_LOGFILE
          ${MKSWAP_UUID} $label_opt $uuid_opt /dev/$partition | tee --append $OCS_LOGFILE
          echo $msg_delimiter_star_line | tee --append $OCS_LOGFILE
        done
        ;;
       gpt)
	echo "Finding swap partition(s) in GPT table $target_dir_fullpath/$(to_filename ${ihd})-pt.parted..." | tee --append $OCS_LOGFILE
	for partition in `get_swap_partition_parted_format $target_dir_fullpath/$(to_filename ${ihd})-pt.parted`; do
          echo "Creating swap partition /dev/$partition..." | tee --append $OCS_LOGFILE
          check_specify_part_exists $partition
          echo "Found the swap partition /dev/$partition info in the image dir, create it by:" | tee --append $OCS_LOGFILE
	  # read LABEL, UUID info for $partition if swappt-$(to_filename $partition).info exists
          uuid_opt=""
          label_opt=""
	  if [ -e "$target_dir_fullpath/swappt-$(to_filename $partition).info" ]; then
            UUID=""
            LABEL=""
	    . "$target_dir_fullpath/swappt-$(to_filename $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" | tee --append $OCS_LOGFILE
          ${MKSWAP_UUID} $label_opt $uuid_opt /dev/$partition | tee --append $OCS_LOGFILE
          echo $msg_delimiter_star_line | tee --append $OCS_LOGFILE
        done
        ;;
       unknown)
        [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
	echo "Unknown partition table format from the file $target_dir_fullpath/$(to_filename ${ihd})-pt.parted!" | tee --append ${OCS_LOGFILE}
        [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
        echo "$msg_program_stop!" | tee --append ${OCS_LOGFILE}
        exit 1
        ;;
      esac
    done
  fi

  # There is still one possiblity that we need to deal with LVM, i.e. PV is not on partition, 
  # instead it's on a disk (e.g. /dev/sdb).
  pv_on_dsk="$(get_pv_on_disk_from_PV_conf_list "$PV_PARSE_CONF")"
  for ip in $pv_on_dsk; do
    if [ -n "$(LC_ALL=C echo "$target_hd" | grep -Ew "${ip}")" ]; then
      # Found PV on disk, force do_LVM_restore as "yes"
      do_LVM_restore="yes"
      # Only when it's listed in the target_hd, we add it.
      if [ -z "$(echo $target_parts | grep -Ew ${ip})" ]; then
        # Avoid duplicated one since task_savedisk might already list it already.
        target_parts="$target_parts ${ip}"
      fi
    fi
  done

  # Strip the single white space which should be nothing. Thanks to Borksoft.
  target_parts="$(echo $target_parts | sed -e "s/^  *$//")"
  # We have to do restore LVM (PV/VG/LV) together, not follow every partition
  target_logv=""
  if [ "$do_LVM_restore" = "yes" ]; then
    # LVM exists, restore PV/VG/LV.
    echo "LVM exists, restoring PV/VG/LV." | tee --append ${OCS_LOGFILE}
    # Append the whole disk to the partitions list. Althouth it's a disk, but it contains LV.
    # However, we do not put the disk name to $target_parts otherwise it might cause confusion.
    restore_logv "$target_parts" $net_mode $port
    echo $msg_delimiter_star_line | tee --append ${OCS_LOGFILE}
  fi

  # Reinstall executable code area (first 446 bytes in MBR)
  if [ "$restore_mbr" = "yes" ]; then
    if [ "$restore_prebuild_mbr" = "yes" ]; then
      prebuild_opt="-p"
    fi
    for ihd in $target_hd; do
      echo "Running: ocs-restore-mbr --ocsroot $ocsroot $prebuild_opt $target_dir $ihd" | tee --append ${OCS_LOGFILE}
      ocs-restore-mbr --ocsroot $ocsroot $prebuild_opt $target_dir $ihd | tee --append ${OCS_LOGFILE}
    done
  fi

  # resize partition if necessary
  if [ "$resize_partition" = "on" ]; then
    resize_ntfsfix_opt=""
    if [ "$chk_tgt_disk_size_bf_mk_pt" = "no" ]; then
      # Force to run ntfsfix before running ntfsresize. This is specially for when forcing to restore an image from larger partition to smaller one. Then the NTFS partition has to be fixed by ntfsfix first before resizing.
      resize_ntfsfix_opt="--ntfsfix"
    fi
    for partition in $target_parts; do
      is_whole_disk $partition && continue
      echo "Now tuning the file system size on partition /dev/$partition to fit the partition size..." | tee --append $OCS_LOGFILE
      echo "Running: ocs-resize-part $resize_ntfsfix_opt --batch /dev/$partition" | tee --append $OCS_LOGFILE
      ocs-resize-part $resize_ntfsfix_opt --batch /dev/$partition | tee --append $OCS_LOGFILE
      echo $msg_delimiter_star_line | tee --append $OCS_LOGFILE
    done
  fi

  # Clear the NTFS volume dirty flag if the volume can be fixed and mounted.
  if [ "$rm_ntfs_vol_dirty_flag" = "yes" ]; then
    for ipartition in $target_parts; do
      is_whole_disk $ipartition && continue
      p_fs="$(LC_ALL=C ocs-get-part-info /dev/$ipartition filesystem)"
      if [ "$p_fs" = "ntfs" ]; then
        echo "Now clear the NTFS volume dirty flag if the volume can be fixed and mounted: /dev/$ipartition..." | tee --append $OCS_LOGFILE
        echo "Running: ntfsfix -d /dev/$ipartition" | tee --append $OCS_LOGFILE
        LC_ALL=C ntfsfix -d /dev/$ipartition | tee --append $OCS_LOGFILE
        echo $msg_delimiter_star_line | tee --append $OCS_LOGFILE
      fi
    done
  fi

  # Remove the udev MAC address records on the restored GNU/Linux
  if [ "$do_rm_hardware_record" = "yes" ]; then
    echo "Running: ocs-tux-postprocess $target_parts $target_logv" | tee --append $OCS_LOGFILE
    ocs-tux-postprocess $target_parts $target_logv | tee --append $OCS_LOGFILE
    echo $msg_delimiter_star_line | tee --append $OCS_LOGFILE
  fi

  # re-install syslinux
  if [ "$do_update_syslinux" = "yes" ]; then
    echo "Running: ocs-update-syslinux -b $target_parts" | tee --append $OCS_LOGFILE
    ocs-update-syslinux -b $target_parts | tee --append $OCS_LOGFILE
    echo $msg_delimiter_star_line | tee --append $OCS_LOGFILE
  fi

  # re-install grub here
  if [ "$install_grub" = "on" ]; then
    echo "Running: ocs-install-grub -p \"$target_parts\" $grub_partition" | tee --append $OCS_LOGFILE
    ocs-install-grub -p "$target_parts" $grub_partition | tee --append $OCS_LOGFILE
    echo $msg_delimiter_star_line | tee --append $OCS_LOGFILE
  fi

  # Reloc ntfs boot partition
  if [ "$change_ntfs_boot_chs" = "on" ]; then
    echo "Running: run_ntfsreloc_part -p \"$target_parts\" $ntfs_boot_partition" | tee --append $OCS_LOGFILE
    run_ntfsreloc_part -p "$target_parts" $ntfs_boot_partition | tee --append $OCS_LOGFILE
    echo $msg_delimiter_star_line | tee --append $OCS_LOGFILE
  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... " | tee --append $OCS_LOGFILE
      dd if=$target_dir_fullpath/$(to_filename ${ihd})-mbr of=/dev/$ihd bs=512 count=1 &>/dev/null
      echo "done." | tee --append $OCS_LOGFILE
      echo -n "Making kernel re-read the partition table of /dev/$ihd... " | tee --append $OCS_LOGFILE
      inform_kernel_partition_table_changed mbr /dev/$ihd | tee --append ${OCS_LOGFILE}
      echo "done." | tee --append $OCS_LOGFILE
      echo "The partition table of /dev/$ihd is:" | tee --append $OCS_LOGFILE
      fdisk -l /dev/$ihd
      echo $msg_delimiter_star_line | tee --append $OCS_LOGFILE
    done
  fi

  # Updating EFI NVRAM for the boot device
  if [ "$update_efi_nvram" = "yes" ]; then
    for ihd in $target_hd; do
      if `is_gpt_partitition_table_disk /dev/$ihd`; then
        efi_info_tmp="$(mktemp /tmp/efi_info.XXXXXX)"
        echo "Running: update-efi-nvram-boot-entry -r $target_dir_fullpath/efi-nvram.dat -f $efi_info_tmp /dev/$ihd" | tee --append $OCS_LOGFILE
        update-efi-nvram-boot-entry -r $target_dir_fullpath/efi-nvram.dat -f $efi_info_tmp /dev/$ihd | tee --append $OCS_LOGFILE
        # Get the new efi_os_label, efi_system_part_no and efi_sys_part_boot_file
        . $efi_info_tmp
        rm -f $efi_info_tmp
        if [ "$efi_netboot_1st_in_nvram" = "yes" ]; then
          # "efi_netboot_1st_in_nvram" is from drbl-ocs.conf.
          is_spawned_by_drbl_ocs $ocs_ppid
          rc=$?
          if [ "$rc" -eq 0 ]; then
	    # After EFI NVRAM is updated, the boot loader of network boot might be shifted. We have to move it back if the BootCurrent is network boot.
	    set-netboot-1st-efi-nvram -b
          fi
        fi
      fi
    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

  #
  echo "End of restoreparts job for image $target_dir." | tee --append $OCS_LOGFILE

} # 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="$(LC_ALL=C ps -C "$prog" -o pid=)"
    if [ -n "$pids" ]; then
      for p in $pids; do
        if [ "$p" != "$ocs_myself_id" ] && [ "$p" != "$ocs_myself_ppid" ]; then
          kill -9 $p &> /dev/null
        fi
      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 rest-unenc
             # the return name will be only one image name.
             target_image="$(cat $ANS_TMP)"
             [ -e "$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 rest-unenc
           # the return name will be only one image name.
           target_image="$(cat $ANS_TMP)"
           [ -e "$ANS_TMP" ] && rm -f $ANS_TMP
           ;;
         *)
           echo "Usage: $ocs_file startdisk {save|restore|multicast_restore} target_image target_hd"
           exit 0
           ;;
      esac
    else
      # Check if the image is encrypted. If so, exit. We do not support it for Clonezilla SE.
      case "$task" in
        *restore*)
          if is_ecryptfs_img "$ocsroot/$target_image"; then
            [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
            echo "$msg_restoring_encrypted_img_not_supported_in_clonezilla_se." | tee --append ${OCS_LOGFILE}
            [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
            echo "$msg_program_stop." | tee --append ${OCS_LOGFILE}
            [ "$save_restore_error_log" = "yes" ] && copy_error_log
            exit 1
          fi
          ;;
      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 \")"
             [ -e "$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 \")"
           [ -e "$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 rest-unenc
             # the return name will be only one image name.
             target_image="$(cat $ANS_TMP)"
             [ -e "$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 rest-unenc
           # the return name will be only one image name.
           target_image="$(cat $ANS_TMP)"
           [ -e "$ANS_TMP" ] && rm -f $ANS_TMP
           ;;
         *)
           echo "Usage: $ocs_file startparts {save|restore|multicast_restore} target_image target_parts"
           exit 0
           ;;
      esac
    else
      # Check if the image is encrypted. If so, exit. We do not support it for Clonezilla SE.
      case "$task" in
        *restore*)
          if is_ecryptfs_img "$ocsroot/$target_image"; then
            [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
            echo "$msg_restoring_encrypted_img_not_supported_in_clonezilla_se." | tee --append ${OCS_LOGFILE}
            [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
            echo "$msg_program_stop." | tee --append ${OCS_LOGFILE}
            [ "$save_restore_error_log" = "yes" ] && copy_error_log
            exit 1
          fi
          ;;
      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 \")"
             [ -e "$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 \")"
           [ -e "$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() {
  local mgrd_opt
  # 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_client_menu_label_prefix: choose save or restore later"
  else
    force_pxe_clients_boot_label clonezilla "$clonezilla_client_menu_label_prefix: choose save or restore later"
    force_grub_efi_clients_boot_label "clonezilla-se-client" "$clonezilla_client_menu_label_prefix: choose save or restore later"
  fi
  # set runlevel 1 to kernel parameter in pxelinux config
  add_runlevel_1_in_pxelinux_cfg_block clonezilla
  add_runlevel_1_in_grub_efi_cfg_block clonezilla-se-client
  # Set the single user mode password if not setting for client...This will be safer..."
  set_clients_rc1_passwd $IP_LIST

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

  # 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
         force_grub_efi_clients_boot_label drbl-client
         ;;
   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
  if [ "$always_restore" = "yes" ]; then
    # If it's always_restore, we do not generate client's PXE config when receiving job is done message from client.
    mgrd_opt="--nopxecfg"
  fi
  ocsmgrd $mgrd_opt &
  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_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"
       [ -e "$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()
#
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!" | tee --append ${OCS_LOGFILE}
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    exit 1
  fi
  # convert 05/006... to 5, 6, also make it as integer
  VOL_LIMIT="$(LC_ALL=C 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 pixz lzip plzip lrzip"
  for i in $more_compress_progs; do
    eval iz=\$i
    # lrzip uses "-q -", the rest use "-c"
    if [ "$IMG_CLONE_CMP" = "$iz -c" -o "$IMG_CLONE_CMP" = "$iz -q -" ]; 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 ! is_supported_dev "$grub_partition" && [ "$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
#
check_and_fix_vol_limit_if_required() {
  local vol_cal
  # VOL_LIMIT is global variable
  # If the VOL_LIMIT is too large (>=20000000000000), split won't work. Therefore we reduce it as the upper limit "2000000000000"
  # Ref: https://sourceforge.net/tracker/?func=detail&atid=671650&aid=3567350&group_id=115473
  vol_cal="$(echo "scale=0; $VOL_LIMIT >= 20000000000000" | bc -l)"
  if [ "$vol_cal" -eq 1 ]; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "Warning! VOL_LIMIT=\"$VOL_LIMIT\" is larger than 20000000000000, reduce it to '2000000000000'."
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    VOL_LIMIT="2000000000000"
  fi
} # end of check_and_fix_vol_limit_if_required
#
USAGE_common_restore(){
    # Common restore usage help messages for both drbl-ocs and ocs-sr
    echo " -f, --from-part-in-img PARTITION      Restore the partition from image. This is especially for \"restoreparts\" to restore the image of partition (only works for one) to different partition, e.g. sda1 of image to sdb6."
    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 resize the file system size to fit the partition size. It is normally used when when a small partition image is restored to a larger partition."
    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 to create the partition table."
    echo " -icrc, --icrc            Skip Partclone CRC checking."
    echo " -irhr, --irhr            Skip removing the Linux udev hardware records on the restored GNU/Linux."
    echo " -irvd, --irvd            Skip removing the NTFS volume dirty flag after the file system is restored."
    echo " -ius, --ius              Skip updating syslinux-related files on the restored GNU/Linux."
    echo " -icds, --ignore-chk-dsk-size-pt  Skip checking destination disk size before creating the partition table on it. By default it will be checked and if the size is smaller than the source disk, quit."
    echo "-iefi, --ignore-update-efi-nvram  Skip updating boot entries in EFI NVRAM after restoring."
    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."
    echo " -srel, --save-restore-error-log  Save the error log file in the image dir. By default the log file won't be saved when error occurs."

} # end of USAGE_common_restore
#
USAGE_reserved_word_for_restore() {
    echo " Some words are reserved for IMAGE_NAME, \"ask_user\" is used to let user to input a name when saving an image. \"autoproductname\" is used to automatically get the image name based on hardware product model from dmidecode."
    echo " A word is reserved for DEVICE, \"ask_user\" could be used to let user to select the source device when saving an image."
} # end of USAGE_reserved_word_for_restore
#
USAGE_common_save(){
    # Common save usage help messages for both drbl-ocs and ocs-sr
    echo " -enc, --enc-ocs-img  To encrypt the image with passphrase."
    echo " -fsck-src-part, --fsck-src-part  Run fsck interactively on the source file system before saving it."
    echo " -fsck-src-part-y, --fsck-src-part-y  Run fsck automatically on the source file system before saving it. This option will always attempt to fix any detected filesystem corruption automatically. //NOTE// Use this option in caution."
    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 " -i, --image-size SIZE  Set the size in MB to split the partition image file into multiple volumes files. For the FAT32 image repository, the SIZE should not be larger than 4096."
    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 " -ntfs-ok, --ntfs-ok      Assume the NTFS integrity is OK, do NOT check again (for ntfsclone only)"
    echo " -rm-win-swap-hib, --rm-win-swap-hib  Try to remove the MS windows swap file in the source partition."
    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 " -rescue, --rescue  Turn on rescue mode, i.e. try to skip bad sectors."
    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."
    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 " -z7, --lrzip-compress     Compress using lrzip when saving."
} # end of USAGE_common_save
#
USAGE_reserved_word_for_save() {
    echo " Some words are reserved for IMAGE_NAME, \"ask_user\" is used to let user to input a name when saving an image. \"autoname\" is used to automatically generate the image name based on network card MAC address and time. \"autohostname\" is used to automatically generate the image name based on hostname. \"autoproductname\" is used to automatically generate the image name based on hardware product model gotten from dmidecode."
    echo " A word is reserved for DEVICE, \"ask_user\" could be used to let user to select the source device when saving an image."
} # end of USAGE_reserved_word_for_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, --nogui          Do not show GUI (TUI) of Partclone or 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." | tee --append ${OCS_LOGFILE}
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo $msg_delimiter_star_line | tee --append ${OCS_LOGFILE}
    echo "Clean the incomplete image $img_name... " | tee --append ${OCS_LOGFILE}
    # 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" | tee --append ${OCS_LOGFILE}
      echo -n "[y/N] "                | tee --append ${OCS_LOGFILE}
      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."              | tee --append ${OCS_LOGFILE}
    echo "$msg_program_stop." | tee --append ${OCS_LOGFILE}
    # Saving mode, always copy error log to image dir.
    copy_error_log
    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." | tee --append ${OCS_LOGFILE}
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo "$msg_program_stop." | tee --append ${OCS_LOGFILE}
    [ "$save_restore_error_log" = "yes" ] && copy_error_log
    exit 1
  fi
} # end of check_if_target_dev_busy_before_restoring
#
check_if_disk_busy_before_create_partition() {
  # Check disk is busy or not. E.g. 
  # /dev/sda is busy due to /dev/sda1 is mounted.
  # /dev/cciss/c0d0 is busy due to /dev/cciss/c0d0p1 is mounted.
  # /dev/mmcblk0 is busy due to /dev/mmcblk0p1 is mounted.
  # /dev/md126 is busy due to /dev/md126p1 is mounted.
  local src_dev=$1
  [ -z "$src_dev" ] && return 1
  mounted_parts="$(LC_ALL=C mount | grep -Ew -- "^${src_dev}([0-9]*|(p[0-9]+))")"
  if [ -n "$mounted_parts" ]; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
    echo "$src_dev is busy. Some partition is mounted." | tee --append ${OCS_LOGFILE}
    echo $msg_delimiter_star_line | tee --append ${OCS_LOGFILE}
    echo "$mounted_parts" | tee --append ${OCS_LOGFILE}
    echo $msg_delimiter_star_line | tee --append ${OCS_LOGFILE}
    echo "You have to unmount them first. Or you can choose '-k' to skip partition recreation when you start clonezilla!" | tee --append ${OCS_LOGFILE}
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo "$msg_program_stop." | tee --append ${OCS_LOGFILE}
    [ "$save_restore_error_log" = "yes" ] && copy_error_log
    exit 1
  fi
} # end of check_if_disk_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}')"
    # Initialized rc value as 1
    rc=1
    # Systemd env in Fedora use "/etc/init.d/ocs-run start" to start up ocs-run, so no S[0-9][0-9], while ocs-live is for select-in-client mode, since it spawns command "clonezilla" then use "ocs-live" to run the real job.
    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
    elif [ -n "$(LC_ALL=C echo "$ocs_pproc" | grep -Ew "ocs-live$")" ]; then
      # If we found it's ocs-live, we have to check if "ocs_opt" and "select_in_client" in /proc/cmdline
      if [ -n "$(LC_ALL=C grep -iEw "ocs_opt" /proc/cmdline | grep -Ew "select_in_client")" ]; then
        echo "$ocs_file was spawned by $ocs_pproc"
        rc=0
      fi
    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="$(get_disk_list_from_img $img_path 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?" | tee --append ${OCS_LOGFILE}
        [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
        echo "$msg_program_stop" | tee --append ${OCS_LOGFILE}
        [ "$save_restore_error_log" = "yes" ] && copy_error_log
        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="$(get_parts_list_from_img $img_path | awk -F" " '{print $1}')"
      hd_dev="$(get_diskname $tgt_hd_tmp)"
    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, use 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.
  [ -z "$input_fs" ] && return 1
  if [ -n "$(LC_ALL=C 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.
  [ -z "$input_fs" ] && return 1
  if [ -n "$(LC_ALL=C 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 "Failed to use dd program to save or restore an image!"
    [ "$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.
    gen_proc_partitions_map_file
    disk_list="$(get_disk_list $partition_table)"
    [ -f "$partition_table" ] && rm -f $partition_table
    echo "$disk_list"
} # end of get_harddisk_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"
  
  # Clean /etc/motd to avoid confusion.
  echo -n "" > /etc/motd

  # Decide where is the $LIVE_MEDIA
  get_live_media_mnt_point

  # Prepare default ocsroot.
  if [ -n "$(find /$LIVE_MEDIA/$ocsroot/ -maxdepth 1 -type d -print 2>/dev/null)" ]; then
    # If images exist in /$LIVE_MEDIA/$ocsroot, 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
  local ether_dev host_ip netmask default_gateway nameserver
  ocs_log_rotate $OCS_NETCFG_LOG
  configured_ip="$(get-all-nic-ip --all-ip-address)"
  if [ -z "$configured_ip" ]; then
    echo $msg_delimiter_star_line             >> $OCS_NETCFG_LOG
    echo "Running ocs-live-netcfg to configure network..." >> $OCS_NETCFG_LOG
    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" | tee --append ${OCS_NETCFG_LOG}
  fi
  #
  ether_dev="$(get-all-nic-ip --all-net-dev)"
  host_ip="$(get-all-nic-ip --all-ip-address)"
  if [ -n "$ether_dev" ]; then
    netmask="$(LC_ALL=C ifconfig $(get-all-nic-ip -d) | grep "Mask:" | sed -e 's/.*Mask:\(.*\)/\1/g')"
    default_gateway="$(LC_ALL=C route -n | grep -Ew "^0.0.0.0" | awk '{ print $2 }')"
  fi
  if [ -f "/etc/resolv.conf" ]; then
    nameserver="$(LC_ALL=C awk '/^nameserver/{printf "%s ",$2}' /etc/resolv.conf | sed -e 's/ $//g')"
  fi

  echo $msg_delimiter_star_line             >> $OCS_NETCFG_LOG
  echo "Network Device  : $ether_dev"       >> $OCS_NETCFG_LOG
  echo "Host IP Address : $host_ip"         >> $OCS_NETCFG_LOG
  echo "Netmask         : $netmask"         >> $OCS_NETCFG_LOG
  echo "Default Gateway : $default_gateway" >> $OCS_NETCFG_LOG
  echo "Name Server     : $nameserver"      >> $OCS_NETCFG_LOG
  echo $msg_delimiter_star_line             >> $OCS_NETCFG_LOG
} # 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
#
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", "quiet_opt" and "splash_opt" are global variables
  local iso_tmp="$1"
  local cand_files="syslinux.cfg isolinux.cfg live.cfg menu.cfg"
  local bparam_chk_list boot_opt union_opt username_opt hostname_opt live_config_opt live_components_opt tmp_var next_step
  [ -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 live_components_opt"
  next_step=""
  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|overlayfs|overlay
      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)"
      # (5) live-config >=4 uses live-config.components or components. Here we should drop "config" in the future.
      live_components_opt="$(LC_ALL=C grep -owE "(live-config.components|components)" $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
      # Check if splash is used in boot parameters. We won't put $splash_opt in the following $boot_param, we just want to parse it for later use.
      # Same for quiet_opt
      if grep -qEw "splash" $iso_tmp/$i; then
        splash_opt="splash"
      fi
      if grep -qEw "quiet" $iso_tmp/$i; then
        quiet_opt="quiet"
      fi
      boot_param=""
      for j in $bparam_chk_list; do
        eval tmp_var="\$${j}"
        [ -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 this bug in jfbterm in Ubuntu:
  # https://bugs.launchpad.net/ubuntu/+source/jfbterm/+bug/253163
  # https://bugzilla.redhat.com/show_bug.cgi?id=698532
  # we force to use bterm temporarily.
  # For debian etch, both bterm or jfbterm can work.
  # Updated: 2011/04/21: Thomas Tsai fixed this issue in jfbterm_0.4.7-8.1drbl. Therefore we commented this:
  # [ -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
ocs_fail_mountpoint() {
  [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
  echo "Clonezilla image home directory $ocsroot is not a mounting point! Failed to mount other device as $ocsroot!" | tee --append ${OCS_MOUNT_LOG}
  [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
  rc=1
  confirm_continue_or_not_default_quit
} # end of ocs_fail_mountpoint
#
ocs_success_mountpoint() {
  echo "$msg_df_report" | tee --append ${OCS_MOUNT_LOG}
  echo $msg_delimiter_star_line | tee --append ${OCS_MOUNT_LOG}
  df -ah 2>&1 | tee --append ${OCS_MOUNT_LOG}
  echo $msg_delimiter_star_line  | tee --append ${OCS_MOUNT_LOG}
  echo -n "$msg_press_enter_to_continue..."
  read
  rc=0
} # end of ocs_success_mountpoint
#
check_if_ocsroot_a_mountpoint() {
  local mntpnt_continue
  local rc
  ocs_log_rotate ${OCS_MOUNT_LOG}
  # 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
        ocs_success_mountpoint
      else
        ocs_fail_mountpoint
      fi
    else
      ocs_fail_mountpoint
    fi
  else
    ocs_success_mountpoint
  fi
  return $r
} # 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
  get_sort_V_opt
  if [ -n "$select_parts" ]; then
    partition_list="$select_parts"
  else
    # No partitions are assigned, search available partitions.
    partition_list="$(get_part_list $partition_table)"
  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/$(to_sysblock_name $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
           if [ -z "$selected_parts" ]; then
             echo "-p is used, but no selected_parts assigned." | tee --append ${OCS_LOGFILE}
             echo "$msg_program_stop" | tee --append ${OCS_LOGFILE}
             [ "$save_restore_error_log" = "yes" ] && copy_error_log
             exit 1
           fi
           ;;
        -*)     echo "${0}: ${1}: invalid option" | tee --append ${OCS_LOGFILE} >&2
                USAGE | tee --append ${OCS_LOGFILE} >& 2
                echo "$msg_program_stop" | tee --append ${OCS_LOGFILE}
                [ "$save_restore_error_log" = "yes" ] && copy_error_log
                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="/dev/$(get_diskname $ntfs_boot_partition)"  # 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/$(to_filename ${ntfs_boot_hd#/dev/*})-chs.sf
          rawhead="$heads"
          rawsector="$sectors"
          echo "Head and sector number of ${ntfs_boot_hd} from image info $ocsroot/$target_dir/$(to_filename ${ntfs_boot_hd#/dev/*})-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() {
  # Function to decide using mkswap or mkswap-uuid
  # MKSWAP_UUID is global variable
  # For mkswap from util-linux < 2.20, e.g. 2.17
  # mkswap --help
  # Usage: mkswap [-c] [-pPAGESZ] [-L label] [-U UUID] /dev/name [blocks]
  # For mkswap from util-linux >= 2.20
  # mkswap --help
  # Usage:
  # mkswap [options] device [size]
  # Options:
  # -c, --check               check bad blocks before creating the swap area
  # -f, --force               allow swap size area be larger than device
  # -p, --pagesize SIZE       specify page size in bytes
  # -L, --label LABEL         specify label
  # -v, --swapversion NUM     specify swap-space version number
  # -U, --uuid UUID           specify the uuid to use
  # -V, --version             output version information and exit
  # -h, --help                display this help and exit
  if [ -n "$(LC_ALL=C mkswap --help 2>&1 | grep -oEw -- "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
} # end of get_mkswap_uuid_cmd
#
fsck_partition() {
  local partfs_ dev_ fsck_exopt_ # dev_ is like /dev/sda1
  partfs_="$1"
  dev_="$2"
  fsck_exopt_="$3"  # This option could be nothing. Not always required.
  [ -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 on GNU/Linux, hence skip fsck this partition $dev_"
          [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
          ;;
    *)    
          echo "Running: fsck -f $fsck_exopt_ $dev_"
          fsck -f $fsck_exopt_ $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" = "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" -o "$debian_dist" = "wheezy" ]; 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"
    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.
    # "-comp xz -Xbcj x86" can be used when the CONFIG_SQUASHFS_XZ is on in kernel.
    # For "-Xbcj x86", refer to: http://kerneltrap.org/mailarchive/linux-fsdevel/2010/12/9/6887823
    # //NOTE// "-e boot" must be the last option if you want to append more.
    export MKSQUASHFS_OPTIONS="-b 1024k -comp xz -Xbcj x86 -e boot"  
  else
    echo "This distribution \"$debian_dist\" 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
  # As of Debian version 1.7.2p1-1, the default /etc/sudoers file created on
  # installation of the package now includes the directive:
  #       #includedir /etc/sudoers.d
  live_autologin_account=""
  for i in /etc/sudoers /etc/sudoers.d/*; do
    [ ! -e $i ] && continue
    live_autologin_account="$(LC_ALL=C grep -iE "^[^#].*ALL=\(ALL\)[[:space:]]*NOPASSWD:[[:space:]]*ALL" $i | awk -F" " '{print $1}')"
    [ -n "$live_autologin_account" ] && break
  done
} # 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 ocs_root_="$1"
  local ans_continue ocs_root_opt
  # 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 [ -n "$ocs_root_" ]; then
    ocs_root_opt="--ocsroot ${ocs_root_}"
  fi

  if [ "$chk_img_restoreable" = "yes" ]; then
    sleep 1
    echo "Start checking the saved image $target_dir if restorable..."
    if [ -z "$(echo "$PARTCLONE_SAVE_OPT" | grep -Ew -- "(-N|--ncurses)")" ]; then
      OCS_CHKIMG_OPT="--nogui"
    else
      OCS_CHKIMG_OPT=""
    fi
    ocs-chkimg $ocs_root_opt $OCS_CHKIMG_OPT $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
  # Another method is to use something like (However, this is from device, not from output file):
  # sfdisk --print-id /dev/sdb 1
  # a5
  # ===
  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
#
get_bsd_swap_partition() {
  # A function to find a BSD swap partition using existing tools on GNU/Linux.
  # Method provided by Thomas Tsai.
  # //NOTE// Slice number may be more than 1.
  # (1) Typical output of "disktype /dev/sda":
  # =============================
  # Partition b: 430.0 MiB (450867200 bytes, 880600 sectors from 1466431)
  #  Type 1 (swap)
  #  Blank disk/medium
  # =============================
  #
  # (2) Typical output of "sfdisk -l -uS /dev/sda":
  # =============================
  #   Device Boot    Start       End   #sectors  Id  System
  #/dev/sda1   *        63  16776584   16776522  a5  FreeBSD
  #/dev/sda2             0         -          0   0  Empty
  #/dev/sda3             0         -          0   0  Empty
  #/dev/sda4             0         -          0   0  Empty
  #/dev/sda5            63   1466430    1466368 
  #/dev/sda6       1466431   2347030     880600 
  #/dev/sda7       2347031   6330390    3983360 
  #/dev/sda8       6330391   7325718     995328 
  #/dev/sda9       7325719  16776584    9450866 
  # =============================
  # We want to find the swap partition (/dev/sda6) from this 2 info. "from 1466431" is the key to find /dev/sda6.
  local c_disk="$1"   # e.g. /dev/sda
  local start_sect swap_p
  [ -z "$c_disk" ] && return 1
  # For multiple slices, the start_sect might be more than 1 result.
  start_sect="$(LC_ALL=C disktype $c_disk 2>/dev/null | grep -B 1 -E "Type 1.*swap.*" | grep -Eo "sectors from.*" | sed -r -e "s/sectors from //g" -e "s/\)$//g")"
  swap_partition=""
  for i in $start_sect; do
    swap_p="$(LC_ALL=C sfdisk -l -uS $c_disk 3>/dev/null | grep -Ew "$i" | awk -F" " '{print $1}')"
    if [ -n "$swap_p" ]; then
      swap_partition="$swap_partition $swap_p"
    fi
  done
  if [ -n "$swap_partition" ]; then
    echo $swap_partition
    return 0
  else
    return 1
  fi
} # end of get_bsd_swap_partition
#
check_if_any_image_exists() {
  # Function to check if Clonezilla image exists
  local img_exist_flag="1"
  [ -z "$ocsroot" ] && return 1
  for i in $ocsroot/*; do
    [ ! -e "$i" ] && continue  # skip *
    if [ -e "$i/clonezilla-img" ]; then
      # New tag file from Oct/21/2011.
      img_exist_flag="0"
      break
    elif [ -e "$i/disk" -o -e "$i/parts" ]; then
      # Old format. No tag file, only disk or parts exists.
      img_exist_flag="0"
      break
    fi
  done
  return $img_exist_flag
} # end of check_if_any_image_exists
#
show_deprecated_ocs_lang_and_keymap() {
  # This function is especially for Clonezilla live boot parameters. To the deprecated ocs_lang and ocs_live_keymap
  if [ -n "$ocs_lang" ]; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "Boot parameter \"ocs_lang\" is deprecated! Please use \"locales\" from live-config." 
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo "For more info, please refer to http://live.debian.net/manual/html/live-manual.en.html#customizing-locale-and-language"
    sleep 1
  fi
  if [ -n "$ocs_live_keymap" ]; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "Boot parameter \"ocs_live_keymap\" is deprecated! Please use \"keyboard-layouts\" from live-config." 
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo "For more info, please refer to http://live.debian.net/manual/html/live-manual.en.html#customizing-locale-and-language"
    sleep 1
  fi
} # end of show_deprecated_ocs_lang_and_keymap
#
init_mbr_part_table_if_not_existing() {
  local dest_dsk="$1"  # e.g. /dev/sdc
  if ! LC_ALL=C parted -s $dest_dsk unit s print &>/dev/null; then
    # Try to create a partition table so that we can read the size via parted -s $dev unit s print
    echo -n "No partition table exists in target disk $dest_dsk, try to initialize one so that we can get the disk size by parted... " | tee --append $OCS_LOGFILE
    #echo 1,,83 | sfdisk -f $dest_dsk &>/dev/null
    echo "Running: parted -s $dest_dsk mklabel msdos" | tee --append $OCS_LOGFILE
    parted -s $dest_dsk mklabel msdos | tee --append $OCS_LOGFILE
    echo "done!" | tee --append $OCS_LOGFILE
  fi
} # init_mbr_part_table_if_not_existing

#
check_mbr_disk_size_gt_2TiB() {
  local dest_dsk="$1"  # e.g. /dev/sdc
  local postaction="$2"  # "exit" will exit, anything eles will continue
  # If dest_dsk size is larger than 2 TiB (~2.2 TB = 2,199,023,255,040 bytes), exit. It's over the MBR's limitation.
  dest_dsk_size_in_B="$(LC_ALL=C parted -s $dest_dsk unit B print | grep -E "^Disk /dev" | awk -F":" '{print $2}' | sed -e "s/B$//g")"
  dest_dsk_size_in_TB="$(LC_ALL=C parted -s $dest_dsk unit TB print | grep -E "^Disk /dev" | awk -F":" '{print $2}')"
  size_cal="$(echo "scale=0; $dest_dsk_size_in_B >= 2199023255040" | bc -l)"
  if [ "$size_cal" -eq 1 ]; then
    case "$postaction" in
      exit) 
            [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
	    echo "Error! Destination disk ($dest_dsk) size is $dest_dsk_size_in_TB, which is larger than the MBR partition table entry maximum 2 TiB (~ 2.2 TB). You have to use GUID partition table format (GPT)." | tee --append ${OCS_LOGFILE}
            [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
	    echo "$msg_program_stop!" | tee --append ${OCS_LOGFILE}
            exit 1
            ;;
      *)
            [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
	    echo "Error! Destination disk ($dest_dsk) size is $dest_dsk_size_in_TB, which is larger than the MBR partition table entry maximum 2 TiB (~ 2.2 TB). You have to use GUID partition table format (GPT)." | tee --append ${OCS_LOGFILE}
            [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
	    echo "$msg_program_stop!" | tee --append ${OCS_LOGFILE}
	    echo -n "$msg_press_enter_to_continue..."
	    read
            ;; 
    esac
  fi
} # end of check_mbr_disk_size_gt_2TiB
# ############################
# Functions from Miracle Linux
# ############################
is_supported_dev() {
  # //NOTE// If new RE* is added, remember to check the function check_if_disk_busy_before_create_partition and get_not_busy_disks_or_parts have to be modified, too.
  # IDE and SCSI disk: /dev/hda, /dev/hdb, /dev/sda, /dev/sdb...
  # KVM virtual disk: /dev/vda, /dev/vdb...
  # Xen virtual disk: /dev/xvda, /dev/xvdb, /dev/xdvc...
  # CCISS RAID disk: /dev/cciss/c0d0, /dev/cciss/c0d1...
  # SD card: /dev/mmcblk0, /dev/mmcblk0p1, /dev/mmcblk0p2, /dev/mmcblk0p3...
  # FakeRAID: with nodmraid boot parameter, it's shown as /dev/md126, /dev/md126p1, /dev/md126p2...
  # USB device: /dev/usb...
  # Mylex ExtremeRAID-2000 SCSI RAID controller: /dev/rd/c0d0, /dev/rd/c0d1...
  # Compaq Smart Array controller: /dev/ida/c0d0, /dev/ida/c0d1...
  local dev_=${1#/dev/*}
  local RE1='x?[hsv]d[a-z]+[0-9]*'
  local RE2='i2o/hd[a-z]+[0-9]*'
  local RE3='cciss/c[0-9]d[0-9](p[0-9]+)?'
  local RE4='mmcblk[0-9]+p*[0-9]*'
  local RE5='md[0-9]+p*[0-9]*'
  local RE6='rd/c[0-9]d[0-9](p[0-9]+)?'
  local RE7='ida/c[0-9]d[0-9](p[0-9]+)?'
  local rc=1

  if [ -n "$dev_" ]; then
    echo $dev_ | grep -Eq "^($RE1|$RE2|$RE3|$RE4|$RE5|$RE6|$RE7)$"
    rc=$?
  fi
  return $rc
} # end of is_supported_dev
#
is_partition() {
  # //NOTE// If new RE* is added, remember to check the function check_if_disk_busy_before_create_partition and get_not_busy_disks_or_parts have to be modified, too.
  # IDE and SCSI disk partition: /dev/hda1, /dev/hdb1, /dev/sda1, /dev/sdb1...
  # KVM virtual disk partition: /dev/vda1, /dev/vdb1...
  # Xen virtual disk partition: /dev/xvda1, /dev/xvdb1, /dev/xdvc1...
  # CCISS RAID disk partition: /dev/cciss/c0d0p1, /dev/cciss/c0d1p1...
  # SD card: /dev/mmcblk0p1, /dev/mmcblk0p2, /dev/mmcblk0p3...
  # FakeRAID: with nodmraid boot parameter, /dev/md126p1, /dev/md126p2...
  # Mylex ExtremeRAID-2000 SCSI RAID controller: /dev/rd/c0d0p1, /dev/rd/c0d1p1...
  # Compaq Smart Array controller: /dev/ida/c0d0p1, /dev/ida/c0d1p2...
  local dev_=${1#/dev/*}
  local RE1='x?[hsv]d[a-z]+[0-9]+'
  local RE2='i2o/hd[a-z]+[0-9]+'
  local RE3='cciss/c[0-9]d[0-9]p[0-9]+'
  local RE4='mmcblk[0-9]+p+[0-9]+'
  local RE5='md[0-9]+p+[0-9]+'
  local RE6='rd/c[0-9]d[0-9]p[0-9]+'
  local RE7='ida/c[0-9]d[0-9]p[0-9]+'
  local rc=1

  if [ -n "$dev_" ]; then
    echo $dev_ | grep -Eq "^($RE1|$RE2|$RE3|$RE4|$RE5|$RE6|$RE7)$"
    rc=$?
  fi

  return $rc
} # end of is_partition
#
is_ide_disk() {
  local disk="$1"
  local RE1='hd[a-z]+[0-9]*'
  local rc=1

  if [ -n "$disk" ]; then
    echo $disk | grep -Eq "^${RE1}$"
    rc=$?
  fi

  return $rc
} # end of is_ide_disk
#
get_part_number() {
  local dev_=${1#/dev/*}
  local num=""

  if [ -n "$dev_" ]; then
    # IDE and SCSI disk partition: /dev/sda15 -> 15
    # KVM virtual disk partition: /dev/vda1 -> 1
    # Xen virtual disk partition: /dev/xvda1 -> 1
    # CCISS RAID disk partition: /dev/cciss/c0d0p1 -> p1 (yes, with p)
    # Mylex ExtremeRAID-2000 SCSI RAID controller: /dev/rd/c0d0p1 -> p1 (yes, with p)
    # Compaq Smart Array controller: /dev/ida/c0d0p1 -> p1 (yes, with p)
    # SD card: /dev/mmcblk0p3 -> p3 (yes, with p)
    # FakeRAID: with nodmraid boot parameter, /dev/md126p1 -> p1 (yes, with p)
    # Just in case. User might use /dev/cciss/c0d0 to get the part number.
    # Here we will continue only if $dev_ is a partition
    # Otherwise
    # echo cciss/c0d0 | sed -r -e 's/^.*[0-9]{1,}(p[0-9]{1,})$/\1/g'
    # will get "cciss/c0d0". However, it should be "".
    # It's easier we use: sed -r -e 's|cciss/c[0-9]+d[0-9]+||g' for cciss/c0d0p3 cases
  case "$dev_" in
    cciss*) num="$(echo "$dev_" | sed -r -e 's|cciss/c[0-9]+d[0-9]+||g')" ;;
    rd*)    num="$(echo "$dev_" | sed -r -e 's|rd/c[0-9]+d[0-9]+||g')" ;;
    ida*)   num="$(echo "$dev_" | sed -r -e 's|ida/c[0-9]+d[0-9]+||g')" ;;
    mmcblk*)num="$(echo "$dev_" | sed -r -e 's|mmcblk[0-9]+||g')" ;;
    md*)    num="$(echo "$dev_" | sed -r -e 's|md[0-9]+||g')" ;;
    *)      num="$(echo "$dev_" | sed -r -e 's/^[^0-9]*-?([0-9]*)$/\1/g')" ;;
  esac
  fi

  echo "$num"
} # end of get_part_number
#
get_diskname() {
  # Disk name type: 
  local dev_=${1#/dev/*}
  # /dev/sda1 -> /dev/sda
  # /dev/cciss/c0d0p1 -> /dev/c0d0
  # /dev/mmcblk0p1 -> /dev/mmcblk0
  # /dev/md126p1 -> /dev/md126
  # However, for device mapper...
  # /dev/dm-1, /dev/dm-2 -> /dev/dm-0 rules?
  # /dev/dm-0 is not always the disk name, e.g. for LVM:
  # sda                               8:0    0     8G  0 disk
  # ├─sda1                            8:1    0   243M  0 part
  # ├─sda2                            8:2    0     1K  0 part
  # └─sda5                            8:5    0   7.8G  0 part
  #   ├─lucid--server-root (dm-0)   254:0    0   7.4G  0 lvm
  #     └─lucid--server-swap_1 (dm-1) 254:1    0   388M  0 lvm

  [ -z "$dev_" ] && return 1
  case "$dev_" in
    cciss*)  echo "$dev_" | sed -r -e 's/^(.*[0-9]{1,})p[0-9]{1,}$/\1/g' ;;
    rd*)     echo "$dev_" | sed -r -e 's/^(.*[0-9]{1,})p[0-9]{1,}$/\1/g' ;;
    ida*)    echo "$dev_" | sed -r -e 's/^(.*[0-9]{1,})p[0-9]{1,}$/\1/g' ;;
    mmcblk*) echo "$dev_" | sed -r -e 's/^([^0-9]*k[0-9]+)p[0-9]*$/\1/g' ;;
    md*)     echo "$dev_" | sed -r -e 's/^([^0-9]*[0-9]+)p[0-9]*$/\1/g' ;;
    *)       echo "$dev_" | sed -r -e 's/^([^0-9]*)[0-9]*$/\1/g' ;;
  esac
} # end of get_diskname
#
is_whole_disk() {
  local dev_=${1#/dev/*} # Input is like /dev/sda, so dev_ becomes sda

  test "$dev_" = "$(get_diskname $dev_)"
  return $?
} # end of is_whole_disk
#
to_dev_name() {
  local src=$1
  echo "$src" | sed -e 's:!:/:g'
} # to_dev_name
#
to_filename() {
  local src=$1
  echo "$src" | sed -e 's!/!-!g'
} # end of to_filename
#
to_sysblock_name() {
  # Function to convert /sys/ file name, e.g. the cciss/c0d0 file name under /sys is /sys/block/cciss!c0d0/size
  # We have convert to /sys/block/cciss!c0d0/size
  local src=$1
  echo "$src" | sed -e 's:/:!:g'
} # end of to_sysblock_name
#
get_part_list() {
  local part_table=$1
  local part_list devs dev
  get_sort_V_opt

  devs="$(LC_ALL=C egrep '[0-9]+[[:blank:]]+[0-9]+[[:blank:]]+[0-9]+[[:blank:]]+.*' $part_table \
         | awk '{ print $4 }' | sort $sort_V_opt)"
  for dev in $devs; do
    dev="$(to_dev_name $dev)"
    if is_supported_dev "$dev" && is_partition "$dev"; then
      part_list="$part_list $dev"
    fi
  done

  echo $part_list | tr ' ' '\n' | ocs-devsort | tr '\n' ' '
} # end of get_part_list
#
get_disk_list() {
  local part_table=$1
  local disk_list devs dev
  get_sort_V_opt

  devs="$(LC_ALL=C egrep '[0-9]+[[:blank:]]+[0-9]+[[:blank:]]+[0-9]+[[:blank:]]+.*' $part_table \
         | awk '{ print $4 }' | sort $sort_V_opt)"
  for dev in $devs; do
    dev="$(to_dev_name $dev)"
    if is_supported_dev "$dev" && ! is_partition "$dev"; then
      disk_list="$disk_list $dev"
    fi
  done

  echo $disk_list | tr ' ' '\n' | ocs-devsort | tr '\n' ' '
} # end of get_disk_list
#
copy_log() {
  local target_dir="$1"
  local source_dev="$2"
  local ip_address log_type log_filename
  # Decide where is the $LIVE_MEDIA
  get_live_media_mnt_point
  [ -e "$LIVE_MEDIA/$ocsroot" ] && return 0
  [ -z "$target_dir" -o ! -e "$target_dir" ] && return 1
  ip_address="$(get-ip-link-2-drbl-srv)"
  test -n "$ip_address" && ip_address="-${ip_address}"
  log_type="${ocs_mode_prompt%disk}"
  log_type="${log_type%parts}"
  case "${log_type}" in
    save)    log_type="saving" ;;
    restore) log_type="restoring" ;;
  esac
  log_filename="$target_dir/${log_type}${ip_address}-$(to_filename ${source_dev#/dev/})-`LC_ALL=C date +%Y%m%d%H%M`"
  cat ${OCS_LOGFILE} > ${log_filename}
  return 0
} # end of copy_log
#
copy_error_log() {
  if [ ! -e "$target_dir_fullpath/Info-lshw.txt" ]; then
    # Dump hardware info
    dump_hardware_software_info $target_dir_fullpath
  fi
  copy_log "$target_dir_fullpath" "error"
} # end of copy_error_log
#
is_ipv4(){
  local addr
  [ $# -lt 1 ] && return 1
  addr=$1
  perl -MData::Validate::IP -e "if (Data::Validate::IP::is_ipv4(\"$addr\")) { exit 0 } else { exit 1 }"
} # end of is_ipv4
#
is_ipv4_netmask(){
  local addr mask ret
  [ $# -lt 2 ] && return 1
  addr=$1
  mask=$2
  ret=$(perl -MNetAddr::IP -e "print NetAddr::IP->new(\"$addr\", \"$mask\")")
  if [ -z "$ret" ]; then
    return 1
  fi
  return 0
} # end of is_ipv4_netmask
#
is_ipv4_broadcast(){
  local addr mask addrmask broadcast
  [ $# -lt 2 ] && return 1
  addr=$1
  mask=$2
  addrmask=$(perl -MNetAddr::IP -e "print NetAddr::IP->new(\"$addr\", \"$mask\")")
  [ -z "$addrmask" ] && return 1
  broadcast=$(perl -MNetAddr::IP -e "print NetAddr::IP->new(\"$addrmask\")->broadcast()")
  [ -z "$broadcast" -o "$addrmask" != "$broadcast" ] && return 1
  return 0
} # end of is_ipv4_broadcast
#
is_ipv4_network(){
  local addr mask addrmask network
  [ $# -lt 2 ] && return 1
  addr=$1
  mask=$2
  addrmask=$(perl -MNetAddr::IP -e "print NetAddr::IP->new(\"$addr\", \"$mask\")")
  [ -z "$addrmask" ] && return 1
  network=$(perl -MNetAddr::IP -e "print NetAddr::IP->new(\"$addrmask\")->network()")
  [ -z "$network" -o "$addrmask" != "$network" ] && return 1
  return 0
} # end of is_ipv4_network
#
is_domain(){
  local domain
  [ $# -lt 1 ] && return 1
  domain=$1
  perl -MData::Validate::Domain -e "if (Data::Validate::Domain::is_domain(\"$domain\")) { exit 0 } else { exit 1 }"
} # end of is_domain
#
select_VG() {
  local target_hd="$*"
  local pvscan_tmp tmp disk vg new_target_hd disk_of_vg

  pvscan_tmp="$(mktemp /tmp/pvscan_tmp.XXXXXX)"
  pvscan 2>/dev/null 3<&- 4<&- | grep -v Total > $pvscan_tmp

  tmp="$(mktemp /tmp/tmp.XXXXXX)"
  for disk in $(expand_multipath_dev $target_hd); do
    echo "$disk" >> $tmp
    if grep "${disk}" $pvscan_tmp 2>/dev/null | grep -q "VG"; then
      vg="$(grep "${disk}" $pvscan_tmp | awk '{ print $4 }')"
      grep "$vg" $pvscan_tmp \
      | awk '{ print $2 }' \
      | while read disk_of_vg; do
        get_master_dev_of_multipath "$(get_diskname $disk_of_vg)"
      done >> $tmp
    fi
  done
  new_target_hd="$(LC_ALL=C sort $tmp | uniq | tr '\n' ' ' | sed -e 's/ $//g')"
  reduce_multipath_dev $new_target_hd

  [ -f "$tmp" ] && rm -f $tmp
  [ -f "$target_hd_tmp" ] && rm -f $target_hd_tmp
  [ -f "$pvscan_tmp" ] && rm -f $pvscan_tmp
} # end of select_VG
#
# Functions for multipath
#
pseudo_get_disk_serial_no() {
  local disk="$1"
  local pseudo_serialno="${MSS_PSEUDO}/${FUNCNAME}/$(to_filename ${disk})-serialno"

  if [ -e "$pseudo_serialno" ]; then
    cat "$pseudo_serialno"
  else
    get_disk_serial_no "$disk"
  fi
} # end of pseudo_get_disk_serial_no
#
get_master_dev_of_multipath() {
  local disk="$(get_diskname $1)"
  local pt=$(get_part_number "$1")
  local i master_dev multipath_infodir

  if [ -e "$ocsroot/$target_dir" ]; then
    multipath_infodir="${ocsroot}/${target_dir}"
  else
    multipath_infodir="${MULTIPATH_INFODIR}"
  fi
  for i in ${multipath_infodir}/*-multipath; do
    if [ ! -e "$i" ]; then
      master_dev=$disk
      break
    fi
    if grep -q "$disk" $i ; then
      master_dev=$(basename $i | sed -e 's/-multipath//g')
    fi
  done
  echo "${master_dev}${pt}"
} # end of get_master_dev_of_multipath
# Function Name: 
#   expand_multipath_dev
# Description:
#   Return the device names associated with the multipath
# Global Variables:
#   MULTIPATH_INFODIR
# Arguments:
#   $@ - Device name (eg. sda)
# Output String:
#   multipath device names
# Return Value:
#   0 - Success
#   1 - Fail
#
expand_multipath_dev() {
  local target="$@"
  local tgt_dsk tgt_ptnum tgt_parts dsk multipath_list i
  local rc=1

  tgt_parts="$target"
  for i in $target; do
    tgt_dsk=$(get_diskname "$i")
    tgt_ptnum=""
    if is_partition $i; then
      tgt_ptnum=$(get_part_number "$i")
    fi
    if [ -n "$ocsroot" ] && [ -e "$ocsroot/$target_dir" ]; then
      multipath_list="${ocsroot}/${target_dir}/$(to_filename ${tgt_dsk})-multipath"
    else
      multipath_list="${MULTIPATH_INFODIR}/$(to_filename ${tgt_dsk})-multipath"
    fi

    if [ -f "$multipath_list" ]; then
      if [ -s "$multipath_list" ]; then
        for dsk in $(cat "$multipath_list"); do
          tgt_parts="$tgt_parts ${dsk}${tgt_ptnum}"
          rc=0
        done
      fi
    fi
  done

  echo $(echo "$tgt_parts" | tr ' ' '\n' | sort)
  return $rc
} # end of expand_multipath_dev
# Function Name: 
#   reduce_multipath_dev
# Description:
#   Eliminate duplicate path
# Global Variables:
#   MULTIPATH_INFODIR
# Arguments:
#   $@ - Device names (e.g. sda sdb)
# Output String:
#   Device names
# Return Value:
#   none
#
reduce_multipath_dev() {
  local dev_list="$@"
  local new_dev_list=""
  local dup_list=""
  local serialno multipath_list pt p file TMP
  TMP="$(mktemp /tmp/ocs_mp.XXXXXX)"

  for p in $dev_list; do
    disk=$(get_diskname "$p")
    pt=""
    if is_partition $p; then
      pt=$(get_part_number "$p")
    fi
    serialno=$(pseudo_get_disk_serial_no "$disk")
    if [ -n "$serialno" ]; then
      serialno_tbl=$(grep "$serialno" $TMP 2>/dev/null)
      if [ $? -eq 0 ]; then
        checked_disk=$(echo $serialno_tbl | awk '{ print $1 }')
        multipath_list="${MULTIPATH_INFODIR}/$(to_filename ${checked_disk})-multipath"
        if [ -n "$pt" ]; then
          if [ "${checked_disk}" = "${disk}" ]; then
            new_dev_list="${new_dev_list} ${disk}${pt}"
          else
            dup_list=$(echo "$(cat ${multipath_list}) ${disk}" | sed -e 's/^ //g')
            echo $dup_list > $multipath_list
          fi
        else
          dup_list=$(echo "$(cat ${multipath_list}) ${disk}" | sed -e 's/^ //g')
          echo $dup_list > $multipath_list
        fi
      else
        if [ -n "$pt" ]; then
          new_dev_list="${new_dev_list} ${disk}${pt}"
        else
          new_dev_list="${new_dev_list} ${disk}"
        fi
        echo "${disk} ${serialno}" >> $TMP
        cp /dev/null "${MULTIPATH_INFODIR}/$(to_filename ${disk})-multipath"
      fi
    else
      new_dev_list="${new_dev_list} ${disk}${pt}"
    fi
  done
  for file in ${MULTIPATH_INFODIR}/*-multipath; do
    [ -e "$file" ] || continue
    if [ $(stat -c "%s" $file) -eq 0 ]; then
      rm -f $file
    fi
  done
  echo "${new_dev_list}" | sed -e 's/^ //g'
  [ -e "$TMP" ] && rm -f $TMP
} # end of reduce_multipath_dev
# ############################
# End of functions from Miracle Linux
# ############################
#
ocs_log_rotate() {
  # Function to rotate the log file, e.g. clonezilla.log -> clonezilla.log.0
  # clonezilla.log.1 -> clonezilla.log.2,
  local file_="$1"
  [ -z "$file_" ] && return 3
  if [ ! -e "${file_}" ]; then
    return 2
  fi
  # Find the next digit
  # Use "/bin/ls" to avoid alias
  last_one="$(LC_ALL=C /bin/ls -1vr ${file_}* | head -n 1 | xargs basename)"
  # We do not compress it, however, just in case.
  last_digit="$(echo $last_one | sed -e "s/$(basename ${file_})\.//g" -e "s/\.gz//g")"
  if [ -z "$last_digit" -o "$(echo $last_digit | grep -E "[^[:digit:]]")" ]; then
    last_digit=0
  fi
  # Rotate the old ones, i.e. clonezilla.log.0 -> clonezilla.log.1, 
  # clonezilla.log.1 -> clonezilla.log.2,...
  for i in `seq $last_digit -1 0`; do
    if [ -e ${file_}.${i} ]; then
      # Suppress the error messages, especially it might occur when
      # ocs-restore-mdisks is run. Parallel programs compete each other.
      mv -f ${file_}.${i} ${file_}.$((i+1)) 2>/dev/null
    fi
  done
  # The last one, i.e. clonezilla.log -> clonezilla.log.0,
  if [ -e ${file_} ]; then
    mv -f ${file_} ${file_}.0
  fi
} # end of ocs_log_rotate
#
get_dsk_size_from_img_and_tgt_dsk_size_from_machine() {
  local path_to="$1"     # e.g. /home/partimag/my-img/
  local src_dsk_n="$2"    # e.g. "sda" in the $path_to, so it becomes /home/partimag/my-img/sda-pt.parted
  local tgt_dsk_f="$3"   # e.g. /dev/sda or /dev/cciss/c0d0
  # src_disk_size_sec, src_disk_size_GB, tgt_disk_size_sec, tgt_disk_size_GB are global variables.

  # Reset the global variables
  src_disk_size_sec=""
  src_disk_size_GB=""
  tgt_disk_size_sec=""
  tgt_disk_size_GB=""
  # Do not use parted here since if no partition table on disk, it shows no disk size info.
  #tgt_disk_size_sec="$(LC_ALL=C parted -s /dev/$target_hd unit s print | grep -E "^Disk /dev/" | awk -F":" '{print $2}' | sed -e "s/s$//g" -e "s/^[[:space:]]*//g")"
  src_disk_size_sec="$(LC_ALL=C grep -E "^Disk /dev/" $path_to/$(to_filename ${src_dsk_n})-pt.parted | awk -F":" '{print $2}' | sed -r -e "s/s$//g" -e "s/^[[:space:]]*//g")"
  src_disk_size_GB="$(LC_ALL=C echo "scale=1; $src_disk_size_sec*512/1000.0^3" | bc -l)"
  tgt_disk_size_sec="$(LC_ALL=C cat /sys/block/$(to_sysblock_name ${tgt_dsk_f})/size)"
  tgt_disk_size_GB="$(LC_ALL=C echo "scale=1; $tgt_disk_size_sec*512/1000.0^3" | bc -l)"
  # Checking if the number got or not.
  if [ -z "$src_disk_size_sec" ]; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
    echo "Disk ($tgt_hd_name) size from image $tgt_dir NOT found." | tee --append ${OCS_LOGFILE}
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo "$msg_program_stop!" | tee --append ${OCS_LOGFILE}
    [ "$save_restore_error_log" = "yes" ] && copy_error_log
    exit 1
  fi
  if [ -z "$tgt_disk_size_sec" ]; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
    echo "Failed to find the disk ($tgt_hd_name) size on this machine." | tee --append ${OCS_LOGFILE}
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo "$msg_program_stop!" | tee --append ${OCS_LOGFILE}
    [ "$save_restore_error_log" = "yes" ] && copy_error_log
    exit 1
  fi
} # end of get_dsk_size_from_img_and_tgt_dsk_size_from_machine
#
get_partition_list(){
  # Function to get the partitions list on the system.
  local LVM_FLAG="$1"  # Searching LVM or not. use true or false
  local select_disk="$2"  # select_disk is like sda, sdb, or c0d0. If not assigned, the available disks on the system will be searched.
  local partition_list_all
  # partition_list is global variable

  # Reset global variable
  partition_list=""
  gen_proc_partitions_map_file
  get_sort_V_opt
  # First we search the whole system
  partition_list_all="$(get_part_list $partition_table)"
  if [ -n "$select_disk" ]; then
    # list only on the select_disk
    for i in $select_disk; do
	# we have target disk, parsing it. //NOTE// multipath (i.e. with "/"), like cciss/c0d0p1 should be considered here. The output should like:
        # cciss/c0d0p1 cciss/c0d0p2 cciss/c0d0p5 cciss/c0d0p6 sda1
        partition_list="$partition_list $(LC_ALL=C echo $partition_list_all | grep -Eo "([^[:space:]]+/$i[^[:space:]]+|$i[^[:space:]]+)" | sort $sort_V_opt )"
        partition_list="$(echo $partition_list)"  # Make it in 1 line
      if [ "$LVM_FLAG" = "true" ]; then
	# (2) For LVM, the output of "pvs -o+lv_name" is like:
	# $ pvs -o+lv_name
	#  PV         VG           Fmt  Attr PSize PFree  LV
	#  /dev/sda5  lucid-server lvm2 a-   7.76g 32.00m root
	#  /dev/sda5  lucid-server lvm2 a-   7.76g 32.00m swap_1
	#  /dev/sda5  lucid-server lvm2 a-   7.76g 32.00m
	#  /dev/sdb5  squeeze      lvm2 a-   7.76g     0  root
	#  /dev/sdb5  squeeze      lvm2 a-   7.76g     0  swap_1
	#  /dev/sdb5  squeeze      lvm2 a-   7.76g     0  home
	# We want the output as like "/lucid-server/root/" (/dev will be added later). Here we do not use something like "/dev/dm-0" since the output of pvs is not that style.
	partition_list="$partition_list $(LC_ALL=C pvs -o+lv_name | grep -E "/dev/${i}[0-9]+" | awk -F" " '{print $2"/"$7}')"
      fi
    done
  else
    # Search whole system
    partition_list="$partition_list_all"
    if [ "$LVM_FLAG" = "true" ]; then
      # Search whole system, not only hda, sda, vda, but also dm-0, dm-1 (LVM).
      # For LVM here we use something like /dev/dm-0 (not /dev/squeeze/root) since it's directly from /proc/partitions. This is easier.
      partition_list="$partition_list $(LC_ALL=C awk '/dm-[0-9]/ { print $4; }' $partition_table | sort $sort_V_opt)"
    fi
  fi
} # end of get_partition_list
#
active_proc_partitions() {
  # Function to active the /proc/partitions. For SCSI devices, even when device
  # modules is loaded, the device is not shown in /proc/partitions if no program
  # like fdisk or sg_map runs... We also do it for /dev/hdx.
  local disk_proc
  gen_proc_partitions_map_file
  disk_proc="$(get_disk_list $partition_table)"
  echo -n "Activating the partition info in /proc... "
  for ihd in $disk_proc; do
     fdisk -l /dev/$ihd &> /dev/null
     # Just in case, use another program. Also for GPT disk.
     parted -s /dev/$ihd print &> /dev/null
  done
  echo "done!"
} # end of active_proc_partitions
#
output_blkdev_info() {
  local output_f="$1"
  local dev_="$2" # if dev_ is mssing, then it means the whole system.
  LC_ALL=C lsblk -o KNAME,NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT,MODEL $dev_ > $output_f
} # end of output_blkdev_info
#
output_blkid_info() {
  local output_f="$1"
  get_sort_V_opt
  LC_ALL=C blkid | sort $sort_V_opt > $output_f
} # end of output_blkid_info
#
get_latest_kernel_ver_in_repository() {
  # Since we might run this on gutsy to create hardy live, so we can not use apt-cache to search that. Another approach is to use apt-file to do that. However, it looks like the file http://opensource.nchc.org.tw/ubuntu/dists/hardy/Contents-i386.gz which apt-file fetched is not updated with repository.
  local ktmp kver dist_path_chklist
  # release_kernel_ver is global variable

  ktmp="$(mktemp -d /tmp/live_kver.XXXXXX || exit 1)"
  if [ -n "$(echo $mirror_url | grep -i "ubuntu")" ]; then
    # For Ubuntu path, we can check update one (e.g. raring-updates) first
    dist_path_chklist="$debian_dist-updates $debian_dist"
  else
    # Debian, no path with "*-updates"
    dist_path_chklist="$debian_dist"
  fi
  for i in $dist_path_chklist; do
    echo "Downloading $mirror_url/dists/$i/main/binary-i386/Packages.gz to finding latest kernel version..."
    LC_ALL=C wget $wget_opt -P $ktmp/ $mirror_url/dists/$i/main/binary-i386/Packages.gz
    # The info in the Packges.gz is like:
    # Package: linux-image-2.6.24-10-386
    # Package: linux-image-2.6.24-10-generic
    # Package: linux-image-2.6.24-10-server
    # Package: linux-image-2.6.24-10-virtual
    # Package: linux-image-2.6.24-11-386
    # Package: linux-image-2.6.24-11-generic
    # Package: linux-image-2.6.24-11-server
    # Package: linux-image-2.6.24-11-virtual
    # Package: linux-image-3.10-2-rt-686-pae-dbg
    # Package: linux-image-3.10-2-rt-686-pae
    # Package: linux-image-3.10-2-amd64
    # Package: linux-image-3.10-2-686-pae-dbg
    # Package: linux-image-3.10-2-686-pae
    # Package: linux-image-3.10-2-486
    kver="$(LC_ALL=C zgrep -E '^Package: linux-image-[[:digit:]]+\.[[:digit:]]+.*-' $ktmp/Packages.gz | sort -r -V | uniq | head -n 1 | sed -r -e 's|^Package: linux-image-||g' -e 's|[^[:digit:]]*-generic$||g' -e 's|[^[:digit:]]*-lowlatency$||g' -e 's|[^[:digit:]]*-amd64||g' -e 's|[^[:digit:]]*-686.*||g' -e 's|[^[:digit:]]*-586.*||g' -e 's|[^[:digit:]]*-486.*||g')"

    if [ -z "$kver" ]; then
      # Clean Packages.gz to avoid wget using another name like Packages.gz.1 then search the next again
      rm -f $ktmp/Packages.gz
    else
      # Found! Use it. 
      break
    fi
  done
  if [ -n "$kver" ]; then
    release_kernel_ver="$kver"
  else
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "Warning!"
    echo "Unable to find the latest kernel version in $mirror_url/dists/$i/main/binary-i386/Packages.gz. Use the pre-defined one."
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo -n "$msg_press_enter_to_continue"
    read 
    eval release_kernel_ver="\$${i}_release_kernel_ver_def"
  fi
  [ -d "$ktmp" -a -n "$(echo $ktmp | grep "live_kver")" ] && rm -rf $ktmp
} # end of get_latest_kernel_ver_in_repository
#
get_efi_hd_boot_entry_info() {
  # This function is used to get some variables for boot entry in EFI NVRAM or the saved file. E.g. it's used to get the following variables in Boot0004 for CentOS 6.
  # Usage: get_efi_hd_boot_entry_info (nvram|full_path_to_efi_nvram_data_file) (boot_order|label|part_no|uuid|boot_file)
  # E.g. to get the "boot_order" in the EFI NVRAM, use:
  # get_efi_hd_boot_entry_info nvram boot_order
  # To get the label from the saved file (it's from the saved image full_path_to_efi_nvram_data_file/efi-nvram.dat):
  # get_efi_hd_boot_entry_info full_path_to_efi_nvram_data_file label

  # A normal output for "efibootmgr -v":
  # root@debian:~# efibootmgr -v
  # BootCurrent: 0001
  # BootOrder: 0004,0000,0001,0002,0003
  # Boot0000* EFI VMware Virtual SCSI Hard Drive (0.0)      ACPI(a0341d0,0)PCI(10,0)SCSI(0,0)
  # Boot0001* EFI VMware Virtual IDE CDROM Drive (IDE 1:0)  ACPI(a0341d0,0)PCI(7,1)ATAPI(1,0,0)
  # Boot0002* EFI Network   ACPI(a0341d0,0)PCI(11,0)PCI(1,0)MAC(000c297f5675,0)
  # Boot0003* EFI Internal Shell (Unsupported option)       MM(b,3f055000,3f3b5fff)
  # Boot0004* CentOS 6	HD(1,800,64000,e8d785ed-a65f-4ea1-adf0-cc61e75677eb)File(\EFI\redhat\grub.efi)
  local type_="$1" # nvram, full_path_to_efi_nvram_data_file
  local term_="$2" # boot_order, label, no, uuid, boot_file
  local cmd_ boot_order label part_no uuid boot_file

  case "$type_" in
    nvram) cmd_="efibootmgr -v 2>/dev/null" ;;
    *)  
           if [ -e "${type_}" ]; then
              cmd_="cat ${type_}"
           else
              return 2
           fi
	   ;;
  esac

  case "$term_" in
    boot_order)
      LC_ALL=C ${cmd_} | grep -iE "^Boot[[:alnum:]]+.*[[:space:]]+HD\(.*\)" | awk -F" " '{print $1}' | sed -r -e "s/^Boot//g" -e "s/\*$//g"
      ;;
    label)
      LC_ALL=C ${cmd_} | grep -iE "^Boot[[:alnum:]]+.*[[:space:]]+HD\(.*\)FILE\(.*\)" | sed -r -e "s|^Boot[[:alnum:]]+[*][[:space:]]+||g" -e "s|[[:space:]]+HD\([[:digit:]]+,[[:alnum:]]+,[[:alnum:]]+,[[:alnum:]-]+\).*||g"
      ;;
    part_no)
      LC_ALL=C ${cmd_} | grep -iE "^Boot[[:alnum:]]+.*[[:space:]]+HD\(.*\)" | grep -o -iE "\([[:digit:]]+,[[:alnum:]]+,[[:alnum:]]+,[[:alnum:]-]+\)" | sed -r -e "s/^\(//g" -e "s/\)$//g" | awk -F"," '{print $1}' | sort | uniq
      ;;
    uuid)
      LC_ALL=C ${cmd_} | grep -iE "^Boot[[:alnum:]]+.*[[:space:]]+HD\(.*\)" | grep -o -iE "\([[:digit:]]+,[[:alnum:]]+,[[:alnum:]]+,[[:alnum:]-]+\)" | sed -r -e "s/^\(//g" -e "s/\)$//g" | awk -F"," '{print $4}' | tr '[:upper:]' '[:lower:]'
      ;;
    boot_file)
      LC_ALL=C ${cmd_} | grep -iE "^Boot[[:alnum:]]+.*[[:space:]]+HD\(.*\)FILE\(.*\)" | grep -o -iE "FILE\(.*\)" | sed -r -e "s/^File\(//g" -e "s/\)$//g"
      ;;
  esac
} # end of get_efi_hd_boot_entry_info
#
get_part_uuid_in_harddrive() {
  # Function to get partition UUID in hard drive. The output strings will be lowercase.
  local part_="$1"
  local uuid_ret=""
  [ -z "${part_}" ] && return 2
  # For old udevadm info output format: UDISKS_PARTITION_UUID=B5BC1DC2-779B-4895-AD7D-6606D7841A5C
  # For new udevadm info output format: ID_PART_ENTRY_UUID=e8d785ed-a65f-4ea1-adf0-cc61e75677eb
  uuid_ret="$(LC_ALL=C udevadm info -q env -n ${part_} 2>/dev/null | grep -E "^ID_PART_ENTRY_UUID=" | sed -r -e "s/^ID_PART_ENTRY_UUID=//g" | tr '[:upper:]' '[:lower:]')"
  if [ -z "$uuid_ret" ]; then
    uuid_ret="$(LC_ALL=C udevadm info -q env -n ${part_} 2>/dev/null | grep -E "^UDISKS_PARTITION_UUID=" | sed -r -e "s/^UDISKS_PARTITION_UUID=//g" | tr '[:upper:]' '[:lower:]')"
  fi
  echo $uuid_ret
} # end of get_uuid_in_harddrive
#
get_best_console_font_size() {
  # console_prefer_cols (input, from drbl-ocs.conf) and console_font_size (output) are  global variables.
  local resoluton_cols font_size_cols
  #
  # The output of "fbset -s" is like:
  # mode "1280x1024"
  #     geometry 1280 1024 1280 1024 32
  #     timings 0 0 0 0 0 0 0
  #     accel true
  #     rgba 8/16,8/8,8/0,0/0
  #     endmode
  #
  # Valid font faces are: VGA (sizes  8x8,  8x14,  8x16,  16x28  and 16x32),  Terminus  (sizes  6x12, 8x14, 8x16, 10x20, 12x24, 14x28 and 16x32), TerminusBold (sizes 8x14, 8x16, 10x20, 12x24,  14x28 and  16x32),  TerminusBoldVGA  (sizes  8x14 and 8x16), and Fixed (sizes  8x13,  8x14,  8x15,  8x16   and   8x18).    If   however CODESET=Ethiopian,  then  the  available font faces are Goha and GohaClassic, each in sizes 8x12, 8x14 and 8x16.
  
  resoluton_cols="$(LC_ALL=C fbset -s | grep -Ew "^[[:space:]]*geometry" | awk -F" " '{print $2}')"
  
  # [ ! -e "/etc/default/console-setup" ] && exit 1
  # . /etc/default/console-setup
  # # FONTSIZE is like: 16, 16x32
  # font_rows="$(LC_ALL=C echo $FONTSIZE | awk -F"x" '{print $1}')"
  # size_rows="$(echo "scale=0; ${resoluton_rows}/${font_rows}" | bc -l)"
  # echo "row size: $size_rows"
  
  if [ -n "$resoluton_cols" ]; then
    font_size_cols="$(echo "scale=0; ${resoluton_cols}/${console_prefer_cols}" | bc -l)"
    echo "Calculated font column size: $font_size_cols"
  fi
  # From Debian Sid (console-setup 1.95), the manual (man 5 console-setup) says:
  # Terminus  (sizes  6x12, 8x14, 8x16, 10x20, 12x24, 14x28 and 16x32), 
  # TerminusBold (sizes 8x14, 8x16, 10x20, 12x24,  14x28 and  16x32),  
  # TerminusBoldVGA  (sizes  8x14 and 8x16), 
  # Fixed (sizes  8x13,  8x14,  8x15,  8x16   and   8x18)
  
  # However, on Ubuntu 13.10, the file /etc/default/console-data says:
  # Valid font faces are: VGA (sizes 8, 14 and 16), Terminus (sizes
  # 12x6, 14, 16, 20x10, 24x12, 28x14 and 32x16), TerminusBold (sizes
  # 14, 16, 20x10, 24x12, 28x14 and 32x16), TerminusBoldVGA (sizes 14
  # and 16) and Fixed (sizes 13, 14, 15, 16 and 18). 
  # The file name is actually like: /usr/share/consolefonts/Lat2-TerminusBold20x10.psf.gz, both on Debian Sid and Ubuntu 13.04.
  # XXX The cols and rows are opposite... weird...
  # However, for console-setup (1.71) unstable; urgency=low, the changelog mentionied:
  # setupcon: accept all methods to state font size in the configuration file (8x14, 14x8, 14). Therefore we use the font size it can be accepted in old version.
  # Since the file names are:
  # dpkg -L console-setup-linux | grep -i uni2-terminusbold
  # /usr/share/consolefonts/Uni2-TerminusBold28x14.psf.gz
  # /usr/share/consolefonts/Uni2-TerminusBold22x11.psf.gz
  # /usr/share/consolefonts/Uni2-TerminusBold32x16.psf.gz
  # /usr/share/consolefonts/Uni2-TerminusBoldVGA16.psf.gz
  # /usr/share/consolefonts/Uni2-TerminusBold24x12.psf.gz
  # /usr/share/consolefonts/Uni2-TerminusBoldVGA14.psf.gz
  # /usr/share/consolefonts/Uni2-TerminusBold16.psf.gz
  # /usr/share/consolefonts/Uni2-TerminusBold20x10.psf.gz
  # /usr/share/consolefonts/Uni2-TerminusBold14.psf.gz
  if [ -n "$font_size_cols" ]; then
    if [ "$font_size_cols" -le 6 ]; then
      console_font_size=12x6
    elif [ "$font_size_cols" -le 8 ]; then
      console_font_size=16x8
    elif [ "$font_size_cols" -le 10 ]; then
      console_font_size=20x10
    elif [ "$font_size_cols" -le 12 ]; then
      console_font_size=24x12
    elif [ "$font_size_cols" -le 14 ]; then
      console_font_size=28x14
    else
      console_font_size=32x16
    fi
  fi
  echo "Best font size for KMS console: $console_font_size"
} # end of get_best_console_font_size
#
gen_iso_sort_file() {
  # Suggestion from Ady <ady-sf _at_ hotmail com>
  # In older versions of Syslinux, the isolinux.bin file (ISOLINUX's 
  # eltorito no-emulation boot loader) was smaller than in the current 
  # 6.xx versions. For example, in version 4.07 the isolinux.bin is 24KB, 
  # while the file is bigger than 40KB in the 6.xx branch.
  # 
  # Evidently, this additional size uses more LBAs, and some buggy BIOS 
  # are having troubles with this situation. In a certain sense (but not 
  # necessarily a strict technical comparison), it is similar to the old 
  # problem about partitions being located over 1024 cylinders and thus 
  # not being bootable (which of course is not an issue nowadays).
  # 
  # Although isolinux.bin 6.xx has been patched so to reduce the chances 
  # of boot failure with such buggy BIOS, putting the entire isolinux.bin 
  # in lower LBAs has been proved to help, even before the introduction 
  # of patches.
  local out_f="$1"
  if [ -z "$out_f" ]; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
    echo "No output was assigned in function gen_iso_sort_file!"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo "$msg_program_stop"
    exit 1
  fi
  # Function to create the sort file for ISO file. For more details, run "man genisoimage", find "-sort"
  cat <<-SORT_END > $out_f
syslinux/isolinux.bin	2
syslinux/boot.cat	1
SORT_END
}
#
download_clonezilla_live() {
  local cz_ver arch_tag local_cz_file
  local c_arch="$1"
  local cz_iso_branch="$2"
  local iso_url_ocs_live="$3" # if iso_url_ocs_live is not assign, iso_url_for_pxe_ocs_live_default from drbl-ocs.conf will be used.
  # ocs_live_iso_file is global variable, and it's the output of this function.
  # ocslive_tmp is global variable. The dir might be removed in function put_clonezilla_live
  ocslive_tmp="$(mktemp -d /tmp/ocslive_tmp.XXXXXX)"
  if [ -z "$c_arch" ]; then
    echo "You have to assign c_arch in function download_clonezilla_live"
    exit 1
  fi

  # If c_arch is assigned, we will switch to that iso
  [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
  echo "The CPU arch for client was assigned as: $c_arch"
  [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
  arch_tag="-$c_arch"

  # iso_url_for_pxe_ocs_live_default is from drbl-ocs.conf. e.g. 
  # (1) http://downloads.sourceforge.net/clonezilla/clonezilla-live-1.2.6-24-i686.iso
  # (2) http://sourceforge.net/projects/clonezilla/files/clonezilla_live_testing/clonezilla-live-1.2.6-24-i686.iso
  if [ -z "$iso_url_ocs_live" ]; then
    # First we try to see if iso_url_for_pxe_ocs_live_default is assigned in drbl-ocs.conf.
    if [ -n "$iso_url_for_pxe_ocs_live_default" ]; then
      iso_url_ocs_live="$iso_url_for_pxe_ocs_live_default"
    fi
    if [ -z "$iso_url_ocs_live" ]; then
      # If not assigned in drbl-ocs.conf, try to find one.
      echo "No Clonezilla live iso was assigned in drbl-ocs.conf or command line option."
      echo "Trying to find the available Clonezilla live iso from sourceforge..."
      cz_ver="$(LC_ALL=C get-latest-ocs-live-ver $cz_iso_branch)"
      iso_url_ocs_live="http://downloads.sourceforge.net/clonezilla/clonezilla-live-${cz_ver}${arch_tag}.iso"
    fi
  fi
  if [ -z "$iso_url_ocs_live" ]; then
    [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
    echo "iso_url_ocs_live _NOT_ found in drbl-ocs.conf, and can not be found from $iso_url_for_pxe_ocs_live_default!"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
    echo "$msg_program_stop"
    [ -d "$ocslive_tmp" -a -n "$(echo "$ocslive_tmp" | grep "ocslive")" ] && rm -rf $ocslive_tmp
    exit 1
  else
    [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
    echo "Available Clonezilla live iso URL: $iso_url_ocs_live"
    [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
  fi

  case "$iso_url_ocs_live" in
  http://*|ftp://*)
    ocs_live_url_path="$(LC_ALL=C dirname $iso_url_ocs_live)"
    ocs_live_iso="$(LC_ALL=C basename $iso_url_ocs_live)"
    if [ "$ocs_live_url_path" = "." ]; then
      [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
      echo "No local iso file $ocs_live_iso was found, and no correct URL was found!"
      [ "$BOOTUP" = "color" ] && $SETCOLOR_WARNING
      echo "Please make sure iso file $ocs_live_iso exists, or specify a correct URL so that $ocs_live_iso can be downloaded!"
      echo "Run \"$0 --help\" to get more details"
      [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
      echo "$msg_program_stop!"
      exit 1
    fi
    echo "Downloading the Clonezilla live iso from $ocs_live_url_path/$ocs_live_iso..."
    wget -P "$ocslive_tmp" $ocs_live_url_path/$ocs_live_iso
    rc="$?"
    if [ "$rc" -ne 0 ]; then
      [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
      echo "Failed to download Clonezilla live iso from $ocs_live_url_path/$ocs_live_iso!"
      [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
      echo "$msg_program_stop"
      [ -d "$ocslive_tmp" -a -n "$(echo "$ocslive_tmp" | grep "ocslive")" ] && rm -rf $ocslive_tmp
      exit 1
    fi
    ocs_live_iso_file="$ocslive_tmp/$ocs_live_iso"
    ;;
  file://*)
    local_cz_file="$(echo $iso_url_ocs_live | sed -e "s|file://||g")"
    if [ -e "$local_cz_file" ]; then
      ocs_live_iso_file="$local_cz_file"
    else
      [ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
      echo "Local Clonezilla live file \"$ocs_live_url_path\" _NOT_ found!"
      [ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
      echo "$msg_program_stop"
      [ -d "$ocslive_tmp" -a -n "$(echo "$ocslive_tmp" | grep "ocslive")" ] && rm -rf $ocslive_tmp
      exit 1
    fi
    ;;
  esac
} # end of download_clonezilla_live
#
get_pv_on_disk_from_local_machine() {
  local pv_list lpv_on_dsk ip
  pv_list="$(LC_ALL=C pvdisplay 2>/dev/null | grep -Ew "PV Name" | sed -r -e "s/PV Name//g" -e "s/^[[:space:]]*//g")"
  lpv_on_dsk=""
  for ip in $pv_list; do
    if is_whole_disk $ip; then
      # List it like: sda, not /dev/sda
      lpv_on_dsk="$lpv_on_dsk ${ip#/dev/*}"
    fi
  done
  # Remove the leading space
  lpv_on_dsk="$(echo  "$lpv_on_dsk" | sed -r -e "s/^[[:space:]]*//g")" 
  if [ -n "$lpv_on_dsk" ]; then
     echo "$lpv_on_dsk"
     return 0
  else
     return 1
  fi
} # end of get_pv_on_disk_from_local_machine

#
get_pv_on_disk_from_PV_conf_list() {
  # There is still one possiblity that we need to deal with LVM, i.e. PV is not on partition, 
  # instead it's on a disk (e.g. /dev/sdb).
  local pv_cfg_list="$1"  # The PV_PARSE_CONF file from the image dir. Absolute path.
  local lpv_on_dsk
  lpv_on_dsk=""
  if [ -e "$pv_cfg_list" ]; then
    for i in $(sed -e 's!^.*/dev/\([^[:space:]]\{3,\}\)[[:space:]]*.*$!\1!g' $pv_cfg_list); do
      if is_whole_disk /dev/$i; then
        # List it like: sda, not /dev/sda
        lpv_on_dsk="$lpv_on_dsk ${i}"
      fi
    done
  fi
  # Remove the leading space
  lpv_on_dsk="$(echo  "$lpv_on_dsk" | sed -r -e "s/^[[:space:]]*//g")" 
  if [ -n "$lpv_on_dsk" ]; then
     echo "$lpv_on_dsk"
     return 0
  else
     return 1
  fi
} # end of get_pv_on_disk_from_PV_conf_list
#
get_ecryptfs_info() {
  # Function to get the encrypted image info, including disks list, partitions list, imaging time, and disks' sizes.
  # It's put in the encrypted image dir while keeping unencrypted so that we can tell the image.
  # Input variables: $ocsroot, $target_dir
  local disk_size_
  # ocsroot, target_dir, disk_list, parts_list, img_time, disks_size_all are global variables
  disk_list=""
  parts_list=""
  img_time=""
  disks_size_all=""
  if [ -e $ocsroot/$target_dir/disk ]; then
    disk_list="$(get_disk_list_from_img $ocsroot/$target_dir)"
  fi
  
  if [ -e $ocsroot/$target_dir/parts ]; then
    parts_list="$(get_parts_list_from_img $ocsroot/$target_dir)"
  fi
  
  img_time="$(get_img_created_time $ocsroot/$target_dir)"
  
  disks_size_all=""
  for ihd in $disk_list; do 
    disk_size_=""
    if [ -e "$ocsroot/$target_dir/$(to_filename ${ihd})-pt.parted.compact" ]; then
      disk_size_="$(LC_ALL=C grep -E "^Disk /dev/" $ocsroot/$target_dir/$(to_filename ${ihd})-pt.parted.compact | awk -F":" '{print $2}' | sed -r -e "s/[[:space:]]//g")"
    fi
    [ -n "$disk_size_" ] && disks_size_all="$(LC_ALL=C echo "${disks_size_all}_${disk_size_}" | tr ' ' _)"
  done
} # end of get_ecryptfs_info
#
put_ecryptefs_tag_file_in_img(){
  # Function to put the unencrypted file "ecryptfs.info" in the encrypted image dir
  # Inputs are ocsroot_orig, target_dir_orig, disk_list, parts_list, img_time, disks_size_all
  # Put a tag file in the original image dir
  if [ -n "$ocsroot_orig" -a -n "$target_dir_orig" ]; then
    echo "# This image was saved with ecryptfs" > $ocsroot_orig/$target_dir_orig/ecryptfs.info
    echo "disk_of_img=\"$disk_list\"" >> $ocsroot_orig/$target_dir_orig/ecryptfs.info
    echo "parts_of_img=\"$parts_list\"" >> $ocsroot_orig/$target_dir_orig/ecryptfs.info
    echo "time_of_img=\"$img_time\"" >> $ocsroot_orig/$target_dir_orig/ecryptfs.info
    echo "disks_size_all_of_img=\"$disks_size_all\"" >> $ocsroot_orig/$target_dir_orig/ecryptfs.info
  fi
} # end of put_ecryptefs_tag_file_in_img
#
get_parts_list_from_img() {
  local img_path="$1"  # Full path for the image dir
  local p_lst
  if [ -z "$img_path" -o \
       ! -d "$img_path" ]; then
    my_ocs_exit 1
  fi
  p_lst=""
  if is_ecryptfs_img $img_path; then
    . $img_path/ecryptfs.info
    p_lst="$parts_of_img"
  else
    p_lst="$(LC_ALL=C cat $img_path/parts)"
  fi
  echo "$p_lst"
  return 0
} # end of get_parts_list_from_img
#
get_disk_list_from_img() {
  local img_path="$1"  # Full path for the image dir
  local d_lst
  if [ -z "$img_path" -o \
       ! -d "$img_path" ]; then
    my_ocs_exit 1
  fi
  d_lst=""
  if is_ecryptfs_img $img_path; then
    . $img_path/ecryptfs.info
    d_lst="$disk_of_img"
  else
    d_lst="$(LC_ALL=C cat $img_path/disk)"
  fi
  echo "$d_lst"
  return 0
} # end of get_disk_list_from_img
#
get_img_created_time() {
  local img_path="$1" # img_path is $ocsroot/$target_dir/
  local img_tm
  if [ -z "$img_path" -o \
       ! -d "$img_path" ]; then
    my_ocs_exit 1
  fi
  img_tm=""
  if is_ecryptfs_img $img_path; then
    . $img_path/ecryptfs.info
    img_tm="$time_of_img"
  else
    # First we find the tag in the file "clonezilla-img". Suppress all the space because it might be used in dialog prompt.
    img_tm="$(LC_ALL=C grep -E "^### Image created time: " $img_path/clonezilla-img 2>/dev/null | awk -F":" '{print $2}' | sed -r -e "s|[[:space:]]*||g")"
    if [ -z "$img_tm" ]; then
      # No tag found. Use the image dir created time.
      img_tm="$(LC_ALL=C ls -ld --time-style=+%Y-%m%d-%H%M $img_path | awk '{ print $6;}')"
    fi
  fi
  echo "$img_tm"
  return 0
} # end of get_img_created_time
#
is_drbl_clonezilla_live_env() {
  # Function to check if it's in DRBL/Clonezilla live env (not in the installed system)
  local rc
  # We check: (1) /etc/ocs/ocs-live.d/ (2) /proc/cmdline contains username=
  if [ -d /etc/ocs/ocs-live.d/ ] && [ -n "$(grep -E "username=" /proc/cmdline)" ]; then
    rc=0
  else
    rc=1
  fi
  return $rc
} # end of is_drbl_clonezilla_live_env
#
is_davfs_ocsroot() {
  # Function to check if the image repository is davfs
  local rc mnt_pnts im check_flag
  # root@vivid:/tmp# df
  # Filesystem                          1K-blocks      Used Available Use% Mounted on
  # overlayfs                             1033564      8056   1025508   1% /
  # udev                                  1023868         4   1023864   1% /dev
  # tmpfs                                  206716       552    206164   1% /run
  # /dev/sr0                               168644    168644         0 100% /lib/live/mount/medium
  # /dev/loop0                             132096    132096         0 100% /lib/live/mount/rootfs/filesystem.squashfs
  # tmpfs                                 1033564         0   1033564   0% /lib/live/mount/overlay
  # none                                        4         0         4   0% /sys/fs/cgroup
  # tmpfs                                 1033564       644   1032920   1% /tmp
  # none                                     5120         0      5120   0% /run/lock
  # none                                  1033564         0   1033564   0% /run/shm
  # none                                   102400         0    102400   0% /run/user
  # http://192.168.120.254:8080/share/ 1333333332 800000000 533333332  61% /home/partimag
  # http://192.168.120.254:8080/share/ 1333333332 800000000 533333332  61% /mnt
  # root@vivid:/tmp# mount -l -t davfs
  # http://192.168.120.254:8080/share/ on /home/partimag type davfs (rw,nosuid,noexec,nodev,_netdev,uid=0,gid=0)
  # http://192.168.120.254:8080/share/ on /mnt type davfs (rw,nosuid,nodev,_netdev,uid=0,gid=0)
  # root@vivid:/tmp# mount -l -t davfs | awk -F" " '{print $3}'
  # /home/partimag
  # /mnt
  # //NOTE// Maybe there are 2 or more davfs.
  # "mount -l -t davfs" only works for Ubuntu. On Debian system, it's marked as fuse:
  # mount | grep davfs
  # http://192.168.120.254:8080/share/ on /home/partimag type fuse (rw,nosuid,nodev,noexec,relatime,user_id=0,group_id=0,allow_other,max_read=10481664,uid=0,gid=0,helper=davfs)
  mnt_pnts="$(LC_ALL=C mount -l -t davfs 2>/dev/null | awk -F" " '{print $3}')"
  if [ -z "$mnt_pnts" ]; then
    # Try again, use different method (for Debian case)	  
    mnt_pnts="$(LC_ALL=C mount -l | grep -E "helper=davfs" | awk -F" " '{print $3}')"
  fi
  rc="1"
  check_flag="false"
  for im in $mnt_pnts; do
    # Use dirname and basename to avoid something like "/home//partimag/" != "/home/partimag".
    if [ "$(dirname $ocsroot)/$(basename $ocsroot)" = "$(dirname $im)/$(basename $im)" ]; then
      check_flag="true"
      break
    fi
  done
  if [ "$check_flag" = "true" ]; then
    rc=0
  else
    rc=1
  fi
  return $rc
} # end of is_davfs_ocsroot
#
is_s3fs_ocsroot() {
  # Function to check if the image repository is davfs
  local rc mnt_pnts im check_flag
  # root@vivid:/tmp# df
  # Filesystem        1K-blocks   Used    Available Use% Mounted on
  # udev                1021160      0      1021160   0% /dev
  # tmpfs                206288   4912       201376   3% /run
  # /dev/sr0             184706 184706            0 100% /lib/live/mount/medium
  # /dev/loop0           147456 147456            0 100% /lib/live/mount/rootfs/filesystem.squashfs
  # tmpfs               1031432      0      1031432   0% /lib/live/mount/overlay
  # overlayfs           1031432 132592       898840  13% /
  # tmpfs               1031432      0      1031432   0% /dev/shm
  # tmpfs                  5120      0         5120   0% /run/lock
  # tmpfs               1031432      0      1031432   0% /sys/fs/cgroup
  # tmpfs               1031432     32      1031400   1% /tmp
  # s3fs           274877906944      0 274877906944   0% /home/partimag
  #
  # root@vivid:/tmp# mount -l | grep -Ew s3fs
  # s3fs on /home/partimag type fuse.s3fs (rw,nosuid,nodev,relatime,user_id=0,group_id=0)
  mnt_pnts="$(LC_ALL=C mount -l | grep -Ew "fuse.s3fs" | awk -F" " '{print $3}')"
  rc="1"
  check_flag="false"
  for im in $mnt_pnts; do
    # Use dirname and basename to avoid something like "/home//partimag/" != "/home/partimag".
    if [ "$(dirname $ocsroot)/$(basename $ocsroot)" = "$(dirname $im)/$(basename $im)" ]; then
      check_flag="true"
      break
    fi
  done
  if [ "$check_flag" = "true" ]; then
    rc=0
  else
    rc=1
  fi
  return $rc
} # end of is_s3fs_ocsroot
#
is_cloudfuse_ocsroot() {
  # Function to check if the image repository is davfs
  local rc mnt_pnts im check_flag
  # root@vivid:~# df
  # Filesystem      1K-blocks   Used  Available Use% Mounted on
  # udev              1009388      0    1009388   0% /dev
  # tmpfs              204076   9016     195060   5% /run
  # /dev/sr0           194382 194382          0 100% /lib/live/mount/medium
  # /dev/loop0         149504 149504          0 100% /lib/live/mount/rootfs/filesystem.squashfs
  # tmpfs             1020364      0    1020364   0% /lib/live/mount/overlay
  # overlayfs         1020364 138044     882320  14% /
  # tmpfs             1020364      0    1020364   0% /dev/shm
  # tmpfs                5120      0       5120   0% /run/lock
  # tmpfs             1020364      0    1020364   0% /sys/fs/cgroup
  # tmpfs             1020364     60    1020304   1% /tmp
  # cloudfuse      8589934588      0 8589934588   0% /home/partimag
  #
  # root@vivid:~# mount -l | grep -Ew cloudfuse
  # cloudfuse on /home/partimag type fuse.cloudfuse (rw,nosuid,nodev,relatime,user_id=0,group_id=0)

  mnt_pnts="$(LC_ALL=C mount -l | grep -Ew "fuse.cloudfuse" | awk -F" " '{print $3}')"
  rc="1"
  check_flag="false"
  for im in $mnt_pnts; do
    # Use dirname and basename to avoid something like "/home//partimag/" != "/home/partimag".
    if [ "$(dirname $ocsroot)/$(basename $ocsroot)" = "$(dirname $im)/$(basename $im)" ]; then
      check_flag="true"
      break
    fi
  done
  if [ "$check_flag" = "true" ]; then
    rc=0
  else
    rc=1
  fi
  return $rc
} # end of is_cloudfuse_ocsroot
#
force_grub_efi_clients_boot_label() {
  local entry_id="$1" # e.g. drbl-client, clonezilla-se-client
  local menu_des="$2" # e.g. "Clonezilla: multicast restore precise-x86 to disk sda"
  [ -z "$entry_id" ] && echo "No entry_id in force_grub_efi_clients_boot_label!" && exit 1
  if [ -n "$menu_des" ]; then
    echo "Setting the GRUB EFI clients to DRBL mode with label \"$menu_des\"..."
    set-default-grub-efi-img -i "$entry_id" -c $GRUB_CONF -l "$menu_des"
  else
    echo "Setting the GRUB EFI clients to DRBL mode, keep orig menu label..."
    set-default-grub-efi-img -i "$entry_id" -c $GRUB_CONF
  fi
  # specify the nodes if assigned by user
  [ "$LIST_HOST" = "on" ] && set_specific_host_grub_efi_conf $IP_LIST
} # end of force_grub_efi_clients_boot_label
#
add_runlevel_1_in_grub_efi_cfg_block() {
  local entry_id="$1"
  local sub_menu_entry_cmd
  [ -z "$entry_id" ] && echo "No entry_id in add_runlevel_1_in_grub_efi_cfg_block!" && exit 1
  lines=$(get_grub_efi_image_block "$entry_id" $GRUB_CONF)
  begin_line=$(echo $lines | awk -F" " '{print $1}')
  end_line=$(echo $lines | awk -F" " '{print $2}')
  tag_found="$(head -n $end_line $GRUB_CONF | tail -n $(($end_line-$begin_line)) | grep -Ei "^[[:space:]]*linux[[:space:]]*.*[[:space:]]+\<1\>([[:space:]]+|$)")"
  if [ -z "$tag_found" ]; then
    sub_menu_entry_cmd="if ($begin_line..$end_line) {s|(^[[:space:]]*linux[[:space:]]+.*)|\$1 1|i}"
    LC_ALL=C perl -pi -e "$sub_menu_entry_cmd" $GRUB_CONF
  fi
} # end of add_runlevel_1_in_grub_efi_cfg_block
#
remove_runlevel_1_in_grub_efi_cfg_block() {
  local entry_id="$1"
  local sub_menu_entry_cmd
  [ -z "$entry_id" ] && echo "No entry_id in remove_runlevel_1_in_grub_efi_cfg_block!" && exit 1
  # remove 1 from the linux .... like this in GRUB config:
  # linux vmlinuz-pxe ramdisk_size=12288  devfs=nomount drblthincli=off selinux=0 1
  lines=$(get_grub_efi_image_block "$entry_id" $GRUB_CONF)
  begin_line="$(echo $lines | awk -F" " '{print $1}')"
  end_line="$(echo $lines | awk -F" " '{print $2}')"
  sub_menu_entry_cmd="if ($begin_line..$end_line) {s|(^[[:space:]]*linux[[:space:]]+.*)[[:space:]]+1([[:space:]]+.*)|\$1\$2|i}"
  LC_ALL=C perl -pi -e "$sub_menu_entry_cmd" $GRUB_CONF
} # end of remove_runlevel_1_in_grub_efi_cfg_block()
#
clean_stale_node_pxe_cfg(){
  # $PXE_CONF is global variable.
  # Clean all the previous saved config file.
  # These files maybe look like: 01-MAC address (with ":" -> "-"), something like C0A8XXXX (only for 192.168.x), default_skeleton
  echo -n "Clean all the previous saved PXELINUX config files if they exist..."
  for ifile in $PXELINUX_DIR/*; do
    # we'd better not to affect the default_skeleton since when in DRBL_SSI and clonezilla box mode, it will always stop clonezilla before it generates the template. If we remove default_skeleton, when clonezilla box mode with LIST_HOST=on, this will have a problem!!! (we'd better to modify drbl-gen-ssi-files to support LIST_HOST=on/off so that this won't be confused)
    if [ "$ifile" != "$PXELINUX_DIR/default" -a \
	 "$ifile" != "$PXELINUX_DIR/default_skeleton" -a \
	 "$ifile" != "$PXELINUX_DIR/bios" -a \
	 "$ifile" != "$PXELINUX_DIR/efi32" -a \
	 "$ifile" != "$PXELINUX_DIR/efi64" ]; then
      [ -f "$ifile" ] && rm -f $ifile
    fi
  done
  echo "done!"
} # end of clean_stale_node_pxe_cfg
#
clean_stale_node_grub_efi_cfg(){
  # $GRUB_CONF is global variable.
  # Clean all the previous saved config file.
  # These files maybe look like: grub.cfg-01-MAC address (with ":" -> "-"), something like grub.cfg-C0A8XXXX (only for 192.168.x), grub.cfg_skeleton
  echo -n "Clean all the previous saved GRUB EFI NB config files if they exist..."
  for ifile in $GRUB_EFINB_DIR/grub.cfg-*; do
    if [ -e "$ifile" ]; then
      rm -f $ifile
    fi
  done
  echo "done!"
} # end of clean_stale_node_grub_efi_cfg
