#!/bin/bash
#===============================================================================
# Copyright (c) 2012-2015 Wind River Systems, Inc.
# The right to copy, distribute, modify, or otherwise
# make use of this software may be licensed only pursuant
# to the terms of an applicable Wind River license agreement.
#
# Author: Junxian.Xiao@windriver.com
# Contributor: Martin.Edwards@windriver.com
#
#===============================================================================

# Make sure all command outputs will be English, even host use other language
LANG=en_US.UTF-8

#===============================================================================
# Constants
#===============================================================================
DI_VERSION="4.1.1"
DI_DATE="2016-01-22"
DI_MYNAME=$(basename $0)

# Default values
DI_DEFAULT_LOG_LEVEL=6
DI_DEFAULT_VFAT_SIZE="512M"
DI_DEFAULT_SFR_VFAT_SIZE="512M"                 #vfat partition for size for SFR
DI_DEFAULT_USBIMG_SIZE="1536M"
DI_DEFAULT_LVMIMG_SIZE="4G"
DI_DEFAULT_FORMAT="vfat+ext3"
DI_DEFAULT_VFAT_LABEL="WR_BOOT"
DI_DEFAULT_ROOTFS_LABEL="WR_ROOTFS"
DI_DEFAULT_LVM=1
DI_DEFAULT_ROOTFS_REAL_SIZE=$((512*1024*1024))  #rootfs tarball uncompressed size
DI_DEFAULT_ROOTFS_FREE_SIZE=$((512*1024*1024))  #rootfs partition free size required
DI_DEFAULT_ROOTFS_MINI_SIZE=$((1024*1024*1024)) #rootfs partition mini size
DI_DEFAULT_LVM_SIZE="45%VG"
DI_DEFAULT_LVM_VG="rootfs"
DI_DEFAULT_LVM_LV="runtime"
DI_DEFAULT_VG_PREFIX="VGWR"
DI_DEFAULT_LV_PREFIX="LVWR"
DI_DEFAULT_NO_INTERACTION=0

# Global settings
DI_TMPDIR="/tmp/deploy-image"
DI_RUNNING_FILE="is_running"
DI_VFAT_AUTOMOUNT="/boot/efi"
DI_SFR_BACKUP_DIR="SFR"
DI_SFR_ROOTFS_NAME="SFR-rootFs.tar.bz2"
DI_SFR_BACKUP_ROOTFS=$DI_VFAT_AUTOMOUNT/$DI_SFR_BACKUP_DIR/$DI_SFR_ROOTFS_NAME
DI_LOCKDOWN_CMD="/sbin/lockdown"
DI_CAPSULE_UPDATE_CMD="/sbin/capsule_update"

DI_LOG_LEVEL_ERROR=3
DI_LOG_LEVEL_WARNING=4
DI_LOG_LEVEL_STEP=5
DI_LOG_LEVEL_INFO=6
DI_LOG_LEVEL_DEBUG=7

DI_LOG_LEVEL_LOGFILE=8
DI_LOG_LEVEL_TIMESTAMP=9

COMMANDS_NORMAL1="echo readlink grep rm tar dd df cat sed cp file mkdir mktemp"
COMMANDS_NORMAL2="sync mount umount whoami parted partprobe sort uniq blockdev"
COMMANDS_BINUTILS="objdump"
COMMANDS_USBIMAGE="kpartx losetup"
COMMANDS_SYNCFS="rsync"
COMMANDS_FORMAT1="mkfs.vfat mkfs.ext3"
COMMANDS_FORMAT2="mkfs.vfat mkfs.ext4"
COMMANDS_LVM1="vgcreate vgremove vgdisplay vgscan vgimport vgexport vgchange"
COMMANDS_LVM2="pvcreate pvremove pvs lvcreate lvremove lvchange dmsetup"

DI_REBOOT=0
DI_ISROOT=0
DI_STEPS=0
DI_NEED_UPDATE_GRUB_CONF=0
DI_LOG_LEVEL=$DI_DEFAULT_LOG_LEVEL
DI_LOG_FILE=/tmp/${DI_MYNAME}.log

# Error codes
# +---------------+------------------+---------------------+
# | common errors | parameter errors | other system errors |
# +---------------+------------------+---------------------+
# | 2 - 31        | 32 - 127         | 128 - 255           |
# +---------------+------------------+---------------------+
SUCCESS=0                       # Success
ERR_FAIL=1                      # Other than the following special failures
ERR_IS_RUNNING=2                # Another deploytool process is running.
ERR_NOT_ROOT=3                  # Need to run as root but it is not.
ERR_NO_ENOUGH_SIZE=4            # No enough space left to save image file.
ERR_DEVICE_TOO_SMALL=5          # Device or image file size is too small.
ERR_COMMAND_CHECK=6             # Required command is not found.
ERR_PART_TYPES=7                # Not supported partition layout or format.
ERR_PART_READY=8                # Partition node is not ready in the system.
ERR_INVALID_FILE=9              # File does not exist or is invalid.
ERR_BLOCK_DEVICE=10             # Device is not a block device.
ERR_NO_GRUBFILE=11              # Cannot find the grub.conf file.
ERR_UPDATE_GRUB=12              # Fail to modify the grub.conf file.
ERR_NO_CAPFILE=13               # Fail to find the cap file for capsule update.
ERR_EXTRACT_TARBALL=14          # Fail to extract the rootfs tarball.
ERR_COPY_FILE=15                # Fail to copy file.
ERR_CREATE_PART=16              # Fail to create partition in the device.
ERR_FORMAT_FS=17                # Fail to format the file system.

ERR_PARAMETER=32                # Invalid or conflicting command line parameter.
ERR_SYSCALL=128                 # Error when call system functions.

#===============================================================================
# Basic Functions
#===============================================================================
SHOW() {
    local level=$1 c p e="\n" ll="" ts=""


    if [ $DI_LOG_LEVEL -ge $DI_LOG_LEVEL_LOGFILE ] ; then
        [ $DI_LOG_LEVEL -ge $DI_LOG_LEVEL_TIMESTAMP ] && ts="[$(date +%s.%N)] "
        echo -ne "${PREFIX:-$p}${ts}$@${END:-$e}" >>$DI_LOG_FILE
    fi

    eval ll=\$DI_LOG_LEVEL_$level
    [ -z "$ll" ] && return 0 || shift
    [ $ll -gt $DI_LOG_LEVEL ] && return 0
    case $level in
        STEP)    DI_STEPS=$(($DI_STEPS + 1))
                 c=34; p="[ STEP$(printf %02d $DI_STEPS)] " ;;
        ERROR)   c=31; p="[  ERROR] " ;;
        WARNING) c=32; p="[WARNING] " ;;
        INFO)    c=00; p="[   INFO] " ;;
        DEBUG)   c=00; p="[  DEBUG] " ;;
    esac
    echo -ne "\033[1;${c}m${PREFIX:-$p}${ts}$@${END:-$e}\033[0m"
}

EXIT() {
    # EXIT ERROR error-code-name message_string
    # EXIT other-level message-string
    local level=$1 errname="" errno=""

    shift
    if [ "$level" == "ERROR" ] ; then
        errname=$1 ; shift
        eval errno=\$$errname
    fi
    [ -z "$errno" -o "$errno" == "$" ] && errno=$ERR_FAIL
    SHOW $level "$@"
    SHOW INFO "Exit ..."
    di_remove_temporary_things $errno
    exit $errno
}

#===============================================================================
# System Functions
#===============================================================================
sys_change_kernel_log_level() {
    local ll=$1

    [ -z "$OLD_LOG_LEVEL" ] && OLD_LOG_LEVEL="$(cat /proc/sys/kernel/printk)"
    [ $ll -lt $DI_LOG_LEVEL_DEBUG ] && ll=1 || ll=$DI_LOG_LEVEL_DEBUG
    SHOW DEBUG "Change system kernel log level to \"$ll 4 1 $ll\""
    echo "$ll 4 1 $ll" >/proc/sys/kernel/printk 2>/dev/null
}

sys_restore_kernel_log_level() {
    if [ -n "$OLD_LOG_LEVEL" ] ; then
        SHOW DEBUG "Restore system kernel log level to \"$OLD_LOG_LEVEL\""
        echo "$OLD_LOG_LEVEL" >/proc/sys/kernel/printk 2>/dev/null
    fi
}

sys_get_user_answer() {
    local ans=""

    [ "$DI_NO_INTERACTION" == "1" ] && return 0
    echo -ne "\033[1;35m[ ANSWER] $1 [Y/n]? " && read ans
    if [ $DI_LOG_LEVEL -gt $DI_LOG_LEVEL_DEBUG ] ; then
        echo -e "\033[0m[ ANSWER] $1 [Y/n]? $ans" >> $DI_LOG_FILE
    fi
    [ -z "$ans" -o "$ans" = "y" -o "$ans" = "Y" ] && return 0 || return 1
}

sys_parse_option_size() {
    # A value with a suffix of K, M, or G is multiplied accordingly
    # and has the suffix stripped.
    local compact_val full_val

    [ -z "$1" ] && return 0
    compact_val=${1%%[Bi]*}
    full_val=${compact_val%%[kKmMgG]*}

    case ${compact_val: -1} in
        k|K) let "full_val *= 1024" ;;
        m|M) let "full_val *= 1024 * 1024" ;;
        g|G) let "full_val *= 1024 * 1024 * 1024" ;;
    esac
    echo $full_val
}

sys_check_commands() {
    local cmd

    SHOW DEBUG "Check command(s): $@"
    for cmd in $@ ; do
        which $cmd >/dev/null 2>&1 || [ -x "$cmd" -o -x "./$cmd" ] || {
            EXIT ERROR ERR_COMMAND_CHECK "Cannot find command \"$cmd\" in the host system!"
        }
    done
}

sys_is_root_user() {
    [ "$(whoami)" = "root" ] && return 0
    EXIT ERROR ERR_NOT_ROOT "Please use 'sudo' or root account to run this tool!"
}

sys_is_wr_target() {
    grep -q "Wind River Linux" /proc/version 2>/dev/null
}

sys_validated_file() {
    local filename="$1" filetype="$2" typestr=""
    local realfile=$(readlink -f "$filename")

    # Maybe just have not set filename to the option
    [ -z "$filename" ] && return 1

    if [ -e "$realfile" ] ; then
        [ -z "$filetype" ] && return 0
        case $filetype in
            bzip2) typestr="bzip2 compressed data" ;;
            *) return 0;
        esac
        file $realfile 2>&- | grep -q "$typestr" && return 0
        SHOW ERROR "File exist but type is not $filetype:"
        EXIT ERROR ERR_INVALID_FILE "    $realfile"
    else
        SHOW ERROR "No such file or invalid soft link file:"
        EXIT ERROR ERR_INVALID_FILE "    $filename"
    fi
}

sys_is_block_device() {
    local dev_node=$1

    [ -b "$dev_node" ] || return 1
    echo "$dev_node" | grep -q "[hs]d[a-z]$" && return 0
    echo "$dev_node" | grep -q "loop[0-9]$" && return 0
    echo "$dev_node" | grep -q "mmcblk[0-9]$" && return 0
    return 1
}

sys_is_device_partition() {
    local dev_node=$1

    [ -b "$dev_node" ] || return 1
    echo "$dev_node" | grep -q "[hs]d[a-z][0-9]$" && return 0
    echo "$dev_node" | grep -q "loop[0-9]p[0-9]$" && return 0
    echo "$dev_node" | grep -q "mmcblk[0-9]p[0-9]$" && return 0
    return 1
}

sys_make_tmpdir() {
    local temp_dir

    [ -d "$DI_TMPDIR" ] || mkdir -p $DI_TMPDIR
    [ -d "$DI_TMPDIR" ] || EXIT ERROR ERR_SYSCALL "Fail to make temporary top directory"

    temp_dir="$(mktemp -d -p ${DI_TMPDIR})"
    SHOW INFO "Make temporary directory: $temp_dir"
    [ -d "$temp_dir" ] || EXIT ERROR ERR_SYSCALL "Fail to make temporary directory"
    eval $1="$temp_dir"
}

sys_make_tmpfile() {
    local temp_file

    [ -d "$DI_TMPDIR" ] || mkdir -p $DI_TMPDIR
    [ -d "$DI_TMPDIR" ] || EXIT ERROR ERR_SYSCALL "Fail to make temporary top directory"

    temp_file="$(mktemp -p ${DI_TMPDIR} $2)"
    SHOW INFO "Make temporary file: $temp_file"
    [ -f "$temp_file" ] || EXIT ERROR ERR_SYSCALL "Fail to make temporary file"
    eval $1="$temp_file"
}

sys_make_dir() {
    local dir=$1

    [ -z "$dir" ] && return 0
    SHOW DEBUG "mkdir -p $dir"
    mkdir -p $dir 2>/dev/null
    [ -d "$dir" ] || EXIT ERROR ERR_SYSCALL "Fail to create directory: $dir"
}

sys_make_link() {
    local target=$1 link=$2

    SHOW DEBUG "ln -s $target $link"
    rm -rf $link 2>/dev/null
    ln -s $target $link || EXIT ERROR ERR_SYSCALL "Fail to create soft link $link"
}

sys_get_file_size() {
    # Return file size in byte
    local file=$1

    [ -e "$file" ] && file=$(readlink -sf $file) || return 1
    ls -la "$file" | awk '{print $5}'
}

sys_file_append_zero() {
    local file=$1 size=$2 tmp_file

    [ -e "$file" -a -n "$size" ] || return 1
    SHOW DEBUG "Append ${size}M bytes zero to $file"
    sys_make_dir $DI_TMPDIR
    tmp_file="$DI_TMPDIR/tmp_zero"
    dd if=/dev/zero of=$tmp_file bs=1M count=$size >/dev/null 2>&1
    cat $tmp_file >> $file
    rm -rf $tmp_file
}

sys_get_tarball_size() {
    # Return tarball size after uncompressed in byte
    local tarball=$1 size=$2 dir

    [ -z "$tarball" ] && return 1
    SHOW INFO "Calculate the size of tarball:"
    SHOW INFO "    $tarball"
    sys_make_tmpdir dir >/dev/null 2>&1
    if tar -C $dir -mxf $tarball 2>/dev/null && \
       which du >/dev/null 2>&1 && \
       eval $size=$(du -bs $dir | cut -f1) ; then
        rm -rf "$dir"
        return 0
    else
        rm -rf "$dir"
        return 1
    fi
}

sys_extract_tarball() {
    local mnt_dir="$1" tarball="$2"

    SHOW INFO "Remove all files in \"$mnt_dir\""
    [ -d "$mnt_dir" -a "$mnt_dir" != "/" ] && rm -rf ${mnt_dir}/*

    SHOW INFO  "Extracting $(basename $tarball) ... "
    SHOW DEBUG "    From: \"$tarball\""
    SHOW DEBUG "    To  : \"$mnt_dir\""
    tar -C $mnt_dir -mxf $tarball --checkpoint=.1000 && echo "" && return 0
    EXIT ERROR ERR_EXTRACT_TARBALL "Fail to extract rootfs tarball!"
}

sys_copy_file() {
    local src=$1 dst=$2 opt=""

    [ -e "$src" ] || return 1
    [ -d "$src" ] && opt="-r"
    SHOW DEBUG "Copy $src"
    SHOW DEBUG "  to $dst"
    cp $opt "$src" "$dst" || EXIT ERROR ERR_COPY_FILE "Fail to copy $src to $dst"
}

sys_check_device_info() {
    local dev_node=$1
    local opts="--query=property --export --export-prefix=DEVICE_"

    which udevadm >/dev/null 2>&1 || return 0
    eval $(udevadm info $opts -n $dev_node 2>&-)
    if [ -n "$DEVICE_ID_BUS" -o -n "$DEVICE_DEVTYPE" ] ; then
        SHOW INFO "Device information of $dev_node:"
        SHOW INFO " - Bus   : ${DEVICE_ID_BUS:-unknown}"
        SHOW INFO " - Type  : ${DEVICE_ID_TYPE:-$DEVICE_DEVTYPE}"
        SHOW INFO " - Model : ${DEVICE_ID_MODEL:-$DEVICE_ID_NAME}"
        SHOW INFO " - Rev   : $DEVICE_ID_REVISION"
        SHOW INFO " - SN    : ${DEVICE_ID_SERIAL_SHORT:-$DEVICE_ID_SERIAL}"
        if ! sys_get_user_answer "Continue to deploy into $dev_node" ; then
            EXIT INFO "Deploy canceled by user!"
        fi
    fi
}

sys_get_device_size() {
    # return size in byte
    #(echo "unit B"; echo "print"; echo "quit") | parted $dev_node | \
    #    grep "Disk ${dev_node}:" | awk '{print $3}'
    blockdev --getsize64 $1
}

sys_current_system_size() {
    # return used and left size in bytes in mounted file system,
    # dst maybe directory or file in this mount point.
    local dst=$1 used=$2 left=$3 info used_byte left_byte

    [ -z "$dst" ] && return 1

    info=$(df -B1 $dst 2>/dev/null | tail -1)
    echo $info | grep -q "%" || return 1

    # In some host, the Filesystem name is too long and will use two line
    # /dev/mapper/VolGroup00-LogVol00
    #          3937873698816 862449053696 2875385286656 24% /buildarea2
    # So need to parse the result in reverse order.
    used_byte=$(echo $info | awk '{print $(NF-3)}')
    left_byte=$(echo $info | awk '{print $(NF-2)}')
    [ -n "$used" ] && eval $used=$used_byte
    [ -n "$left" ] && eval $left=$left_byte
}

sys_get_current_vfat_node() {
    local curr_vfat=$1
    local root_uuid=$( grep -o '\<root=UUID=[^ ]*'  /proc/cmdline | \
                       cut -d "=" -f 3 | sed 's/"//g')
    local root_label=$(grep -o '\<root=LABEL=[^ ]*' /proc/cmdline | \
                       cut -d "=" -f 3 | sed 's/"//g')
    local root_lvm=$(  grep -o '\<root=LVM=[^ ]*'   /proc/cmdline | \
                       cut -d "=" -f 3 | sed 's/"//g')
    local root_sfr=$(  grep -o '\<root=SFR=[^ ]*'   /proc/cmdline | \
                       cut -d "=" -f 3 | sed 's/"//g')
    local root_dev=$(  grep -o '\<root=[^ ]*'       /proc/cmdline | \
                       cut -d "=" -f 2 | sed 's/"//g')
    local root_node vg

    [ -z "$root_lvm" ] && root_lvm=$root_sfr
    SHOW DEBUG "Current boot: $(grep -o '\<root=[^ ]*' /proc/cmdline)"

    if [ -n "$root_uuid" ] ; then
        root_node=$(blkid -U $root_uuid)
    elif [ -n "$root_label" ] ; then
        root_node=$(blkid -L $root_label)
    elif [ -n "$root_lvm" ] ; then
        vg=$(basename $root_lvm | awk -F'-' '{print $1}')
        root_node=$(pvs 2>/dev/null | grep "$vg" | tail -n1 | awk '{print $1}')
    elif [ -n "$root_dev" ] ; then
        root_node=$root_dev
    fi

    [ -z "$root_node" ] && return 1
    eval $curr_vfat="$(echo $root_node | sed 's/.$//g')1"
}

sys_touch_image_file() {
    local name=$1
    local size=$2

    [ -z "$name" ] && return 1

    if [ -f "$name" ] ; then
        SHOW INFO "Image file $name is opened." && return 0
    else
        local size=$(($size/(1024*1024)))
        SHOW INFO "Creating image file $name (${size}M)..."
        if dd if=/dev/zero of=$name bs=1M count=$size ; then
            SHOW INFO "Image file $name is created."
        else
            EXIT ERROR ERR_SYSCALL "Failed to create an image file!"
        fi
    fi
}

sys_attach_iamge_device() {
    local img_file=$(readlink -sf "$1") ret_node=$2 ldevice=""

    [ -z "$img_file" -o -z "$ret_node" ] && return 1
    ldevice=$(losetup -j "$img_file" 2>/dev/null | head -n1 | awk -F':' '{print $1}')
    if [ -z "$ldevice" ] ; then
        ldevice=$(losetup -f)
        if [ -z "$ldevice" ] ; then
            EXIT ERROR ERR_SYSCALL "Cannot find a free loop device!"
        elif losetup $ldevice $img_file ; then
            SHOW INFO "Setup new virtual device $ldevice for $1"
        else
            EXIT ERROR ERR_SYSCALL "Fail to setup virtual device $ldevice for $img_file"
        fi
    else
        SHOW INFO "Loop device $ldevice is associated"
    fi
    eval $ret_node=$ldevice
}

sys_detach_image_device() {
    local dev image=$(readlink -sf "$1")
    local ldevices=$(losetup -j "$image" 2>/dev/null | awk -F':' '{print $1}')

    for dev in $ldevices ; do
        SHOW DEBUG "Detach loop device $dev"
        losetup -d "$dev"
    done
    if losetup -j "$image" 2>/dev/null | grep -q "/dev/loop" ; then
        SHOW DEBUG "Fail to detach loop device to $image"
        return 1
    fi

    return 0
}

sys_create_image_device_maps() {
    local dev_node=$1

    [ -z "$dev_node" ] && return 1
    SHOW INFO "Create device maps from partition tables in $dev_node"
    if ! kpartx -av $dev_node >/dev/null 2>&1 ; then
        EXIT ERROR ERR_SYSCALL "Fail to create device maps for $dev_node"
    fi
    sleep 3
    return 0
}

sys_delete_image_device_maps() {
    local dev_node=$1

    [ -z "$dev_node" ] && return 1
    if kpartx -l "$dev_node" 2>&- | grep -q "loop[0-9]p[0-9]" ; then
        SHOW DEBUG "Delete device maps for device $dev_node"
        kpartx -d "$dev_node" 2>/dev/null
    fi
    rm -rf ${dev_node}p*
}

sys_clear_partitions() {
    local dev_node=$1
    local partition partitions id start_size end_size

    [ -z "$dev_node" ] && return 1
    sys_umount_partition $dev_node
    SHOW INFO "Clear partition table in $dev_node"

    SHOW DEBUG "Try to clear GPT partitions and table"
    parted -s $dev_node -- unit B print 2>/dev/null | grep "^[ 0-9]" | \
    while read partition; do
        SHOW DEBUG "Partition: $partition"
        id=$(echo $partition | awk '{print $1}')
        start_size=$(echo $partition | awk '{print $2}')
        end_size=$(echo $partition | awk '{print $3}')

        # clear the partition
        SHOW INFO "Remove partition $id: $start_size/$end_size"
        parted -s $dev_node rm $i >/dev/null 2>&1

        # Clear GPT and the beginning and the end 4M in each partition
        # TBD: Not sure whether parted rm command will do this or not.
        start_size=$(sys_parse_option_size $start_size)
        end_size=$(sys_parse_option_size $end_size)
        start_size=$(($start_size/1024))
        end_size=$((($end_size/1024) - 4096))
        SHOW DEBUG "Clear GPT at ${start_size}K/${end_size}K"

        dd if=/dev/zero of=$dev_node bs=1K count=4096 seek=$start_size 1>&- 2>&-
        dd if=/dev/zero of=$dev_node bs=1K count=4096 seek=$end_size 1>&- 2>&-
    done

    # The first 4M for clearing MBR
    SHOW DEBUG "Try to clear MBR partition table"
    dd if=/dev/zero of=$dev_node bs=1M count=4 1>&- 2>&-

    SHOW DEBUG "Try to reread partition table"
    blockdev --rereadpt $dev_node >/dev/null 2>&1
}

sys_create_partitions() {
    # Create two partitions in the device for IDP layout format.
    # Assume that device is unmounted and cleared.
    local dev_node=$1 p1_size_bytes=$2 p2_size_bytes=$3
    local p1_end p2_start p2_end

    SHOW INFO "Create vfat efi and rootfs primary partitions in $dev_node"

    # Maybe return fail because cannot reread partition table.
    parted -s $dev_node mklabel gpt >/dev/null 2>&1 || true

    p1_end=$(($p1_size_bytes / (1024*1024) + 1))
    SHOW DEBUG "parted make partition: efi 1M ${p1_end}M"
    parted -s $dev_node mkpart efi 1M ${p1_end}M >/dev/null 2>&1
    if parted -s $dev_node print | grep -q "1.*efi"; then
        parted -s $dev_node set 1 boot on >/dev/null 2>&1
    else
        EXIT ERROR ERR_CREATE_PART "Fail to create GPT vfat partition"
    fi

    p2_start=$p1_end
    if [ -n "$p2_size_bytes" ] ; then
        p2_end="$(($p2_size_bytes / (1024*1024) - $p2_start))M"
    else
        p2_end="-1"
    fi

    # -1 is recognized as option but not the end of free space
    # in one line command, but -1 works right in interactive mode
    SHOW DEBUG "parted make partition: primary ${p2_start}M ${p2_end}"
    (echo "mkpart primary ${p2_start}M $p2_end" ; echo "quit" ) | \
        parted $dev_node >/dev/null 2>&1 ;
    if parted -s $dev_node print | grep -q "2.*primary"; then
        [ $DI_LVM = 1 ] && parted -s $dev_node set 2 lvm on >/dev/null 2>&1
    else
        EXIT ERROR ERR_CREATE_PART "Fail to create GPT rootfs partition"
    fi

    partprobe >/dev/null 2>&1
    return 0
}

sys_check_partitions() {
    local dev_node=$1 lvm=$2 right_lvm=0 partitions pattern

    [ -z "$dev_node" ] && return 1

    partitions=$(parted -s $dev_node print | grep "^[ 0-9]" | wc -l)
    SHOW DEBUG "Find $partitions partitions in $dev_node"
    if [ "$partitions" != "2" ] ; then
        SHOW ERROR "Less or more than two partitions in the device."
        EXIT ERROR ERR_PART_TYPES "Please add -t or -y option to format disk!"
    fi

    SHOW DEBUG "Check pattern: $pattern"
    pattern="1.*efi.*boot.*2.*primary.*lvm"
    if parted -s $dev_node print | xargs | grep -q $pattern ; then
        right_lvm=1
    fi
    if [ "$lvm" == "1" ] ; then
        if [ $right_lvm != 1 ] ; then
            SHOW ERROR "The rootfs partitions is not of LVM format!"
            SHOW ERROR "If you really want to use LVM partitions,"
            SHOW ERROR "please add -t or -y option to format disk."
            EXIT ERROR ERR_PART_TYPES "Or add -l 0 to confirm that LVM is not used."
        fi
    else
        if [ $right_lvm = 1 ] ; then
            SHOW ERROR "The two partitions is of LVM format!"
            SHOW ERROR "If you don't want to use LVM partitions,"
            SHOW ERROR "please add -t or -y option to format disk."
            EXIT ERROR ERR_PART_TYPES "Or add -l 1 to confirm that LVM is used."
        fi
        pattern="1.*efi.*boot.*2.*primary"
        if ! parted -s $dev_node print | xargs | grep -q $pattern ; then
            SHOW ERROR "The two partitions, but not supported format!"
            SHOW ERROR $ERR_PART_TYPES "Please add -t or -y option to format disk."
        fi
    fi
    return 0
}

sys_get_vfat_options() {
    # Get fatType and SecPerCluster according to fat file system specification.
    # Intel-quark BIOS(edk2) will check fat type and cluster size strictly.
    # If the type and size mismatch, the vfat partition will not be recognized.
    # In other BIOS, no need these extra options to mkfs.vfat.
    #DSKSZTOSECPERCLUS DskTableFAT16 [] = {
    #    {        8400,   0}, /* disks up to  4.1 MB, the 0 value for SecPerClusVal trips an error */
    #    {      32680,   2},  /* disks up to   16 MB,  1k cluster */
    #    {    262144,   4},   /* disks up to 128 MB,  2k cluster */
    #    {   524288,    8},   /* disks up to 256 MB,  4k cluster */
    #    { 1048576,  16},     /* disks up to 512 MB,  8k cluster */
    #    /* The entries after this point are not used unless FAT16 is forced */
    #    { 2097152,  32},     /* disks up to     1 GB, 16k cluster */
    #    { 4194304,  64},     /* disks up to     2 GB, 32k cluster */
    #    { 0xFFFFFFFF, 0} /* any disk greater than 2GB, 0 value for SecPerClusVal trips an error */
    #};
    #DSKSZTOSECPERCLUS DskTableFAT32 [] = {
    #    {       66600,   0}, /* disks up to 32.5 MB, the 0 value for SecPerClusVal trips an error */
    #    {     532480,   1},  /* disks up to 260 MB,  .5k cluster */
    #    { 16777216,   8},    /* disks up to     8 GB,    4k cluster */
    #    { 33554432, 16},     /* disks up to   16 GB,    8k cluster */
    #    { 67108864, 32},     /* disks up to   32 GB,  16k cluster */
    #    { 0xFFFFFFFF, 64}    /* disks greater than 32GB, 32k cluster */
    #};
    local pnode=$1 size sectors

    [ -n "$pnode" ] && size=$(sys_get_device_size $pnode 2>/dev/null)
    [ -n "$size" ] && sectors=$(($size / 512))
    [ -z "$sectors" ] && return 0

    if [ $sectors -le 32680 ] ; then
        echo "-F 16 -s 2"
    elif [ $sectors -le 262144 ] ; then
        echo "-F 16 -s 4"
    elif [ $sectors -le 524288 ] ; then
        echo "-F 16 -s 8"
    elif [ $sectors -le 1048576 ] ; then
        echo "-F 16 -s 16"
    elif [ $sectors -le 2097152 ] ; then
        echo "-F 16 -s 32"
    elif [ $sectors -le 16777216 ] ; then
        echo "-F 32 -s 8"
    elif [ $sectors -le 33554432 ] ; then
        echo "-F 32 -s 16"
    elif [ $sectors -le 67108864 ] ; then
        echo "-F 32 -s 32"
    else
        echo "-F 32 -s 64"
    fi
}

sys_format_partition() {
    local pnode=$1 fstype=$2 label=$3
    local cmd opts s_num try_count=0

    [ -z "$pnode" ] && return 1

    case $fstype in
        vfat)  cmd="mkfs.vfat" && opts="-n $label $(sys_get_vfat_options $pnode)" ;;
        ext3)  cmd="mkfs.ext3" && opts="-L $label -I 128" ;;
        ext4)  cmd="mkfs.ext4" && opts="-L $label -I 128" ;;
        *)     EXIT ERROR ERR_PARAMETER "Unsupported fs format type: $fstype!" ;;
    esac

    SHOW INFO "Format $pnode to $fstype"
    while [ $try_count -lt 10 ] ; do
        mount | grep -q "$pnode" && umount "$pnode" > /dev/null 2>&1
        SHOW INFO "$cmd $opts $pnode"
        eval $cmd $opts $pnode >/dev/null 2>&1 && break
        SHOW DEBUG "Fail to run $cmd in $pnode, tries:$try_count"
        sleep 1
        let try_count++
    done
    if [ $try_count -ge 10 ] ; then
        EXIT ERROR ERR_FORMAT_FS "Fail to format $pnode to $fstype"
    fi
    return 0
}

sys_wait_partition_node_ready() {
    local dev_node=$1 try_count=0

    [ -z "$dev_node" ] && return 1
    while [ $try_count -lt 30 ] ; do
        [ -e $dev_node ] && return 0
        SHOW DEBUG "Wait $dev_node ready ($try_count) ..."
        sleep 1
        let try_count++
    done
    EXIT ERROR ERR_PART_READY "Fail to find partition node $dev_node"
}

sys_mount_partition() {
    local dev_node=$1 mnt_dir=$2

    SHOW DEBUG "Mount \"${dev_node}\" \"${mnt_dir}\""
    mount $dev_node $mnt_dir && return 0
    EXIT ERROR ERR_SYSCALL "Fail to mount $dev_node to $mnt_dir"
}

sys_umount_partition() {
    # /dev/sdb  to unmount all partition of /dev/sdb
    # /dev/sdb1 to unmount a single partition /dev/sdb1
    # /mnt/myusb to unmount a single mount point
    local dev_node=${1%/}

    [ -z "$dev_node" ] && return 1
    SHOW INFO "Unmount \"$dev_node\""
    umount $dev_node 2>/dev/null

    # Make sure all the mount points are unmounted
    for i in $(mount | grep "$dev_node" | awk '{print $1}') ; do
        SHOW DEBUG "    Unmount $i"
        umount $i 2>/dev/null || SHOW INFO "Fail to unmount $i"
    done
    return 0
}

sys_get_mount_point() {
    local pnode=$1 ptype=$2 pmount=$3 mnt_dir=""

    SHOW DEBUG "Try to get $ptype mount point on $pnode"
    [ -z "$pnode" ] && return 1
    [ -b "$pnode" ] || EXIT ERROR ERR_BLOCK_DEVICE "Invalid $ptype partition: $pnode"

    # Wait the udev event, some host will mount parition automatically.
    # Add this to avoid the timing conflict issue here
    udevadm settle

    mnt_dir=$(mount | grep "${pnode}.*rw" | head -n 1 | awk '{print $3}')
    if [ -z "$mnt_dir" ] ; then
        mount | grep -q "$pnode" && umount ${pnode}
        SHOW INFO "Need to mount $ptype partition: $pnode"
        sys_make_tmpdir mnt_dir
        sys_mount_partition $pnode $mnt_dir
        if mount | grep -q "${pnode}.*rw" ; then
            SHOW DEBUG "$(mount | grep $pnode)"
        else
            EXIT ERROR ERR_PART_TYPES "Cannot find $ptype partition mount point!"
        fi
    else
        SHOW INFO "$pnode already mounted!"
        SHOW DEBUG "$(mount | grep $pnode)"
    fi

    SHOW INFO "Get $ptype partition($pnode) mount point:"
    SHOW INFO "    $mnt_dir"
    SHOW INFO "    $(df -BM $mnt_dir | tail -n1 | sed 's/[^ ]*$//g')"
    eval $pmount=$mnt_dir
}

sys_create_lvm_physical_volume() {
    local pnode=$1 try_cnt=3

    [ -z "$pnode" ] && return 1
    SHOW INFO "Create LVM physical volume on $pnode"
    while [ $try_cnt -gt 0 ] ; do
        SHOW DEBUG "Try to create lvm physical volume: $try_cnt"
        sys_umount_partition $pnode
        pvcreate -ff -y "$pnode" >/dev/null 2>&1
        pvs --noheadings "$pnode" 2>/dev/null | grep -q "/dev/" && return 0
        try_cnt=$(($try_cnt - 1))
        sleep 1
    done
    EXIT ERROR ERR_SYSCALL "Fail to create LVM physical volume on $pnode"
}

sys_create_lvm_volume_group() {
    local pnode=$1 vgname=$2

    [ -z "$pnode" -o -z "$vgname" ] && return 1
    SHOW INFO "Create LVM volume group $vgname on $pnode"
    vgcreate "$vgname" "$pnode" >/dev/null 2>&1
    vgchange -ay --ignorelockingfailure $vgname >/dev/null 2>&1
    vgdisplay -c 2>/dev/null | grep -q "$vgname" && return 0
    EXIT ERROR ERR_SYSCALL "Fail to create LVM volume group $vgname on $pnode"
}

sys_create_lvm_logical_volume() {
    local vgname=$1 lvname=$2 lvsize=${3:-100%FREE} opts=""

    [ -z "$vgname" -o -z "$lvname" ] && return 1
    echo "$lvsize" | grep -q "%" && opts="-l $lvsize" || opts="-L $lvsize"

    SHOW INFO "Create LVM logical volume $lvname in group $vgname"
    SHOW INFO "LVM logical volume size is $lvsize"

    lvcreate --noudevsync -n $lvname $opts $vgname >/dev/null 2>&1
    lvdisplay -c 2>/dev/null | grep -q "/dev/$vgname/$lvname" && return 0

    SHOW DEBUG "Create LVM logical volume $lvname in group $vgname again!"
    lvcreate -n $lvname $opts $vgname >/dev/null 2>&1

    SHOW DEBUG "Logical volumes after created:"
    lvdisplay -c 2>/dev/null | grep -q "/dev/$vgname/$lvname" && return 0
    EXIT ERROR ERR_SYSCALL "Fail to reate LVM logical volume $lvname in group $vgname"
}

sys_remove_lvm_logical_volumes() {
    local vgname=$1 line lvname

    SHOW DEBUG "Try to remove LVM logical volumes in $vgname group"
    [ -z "$vgname" ] && return 1
    lvdisplay -c 2>/dev/null | grep "/dev/$vgname/.*" | while read line ; do
        lvname=$(echo $line | awk -F':' '{print $1}')
        SHOW INFO "Removeimg logical volume $lvname"
        lvchange -an $lvname >/dev/null 2>&1
        lvremove -f $lvname >/dev/null 2>&1
    done
    lvdisplay -c 2>/dev/null | grep -q "/dev/$vgname/.*" || return 0
    SHOW INFO "Fail to remove LVM logical volumes in $vgname group"
}

sys_remove_lvm_physical_volumes() {
    local vgname=$1 pv

    SHOW DEBUG "Try to remove LVM physical volumes in $vgname group"
    [ -z "$vgname" ] && return 1
    SHOW INFO "Removing LVM physical volumes in $vgname group"
    vgchange -an $vgname >/dev/null 2>&1
    vgreduce -a $vgname >/dev/null 2>&1
    for pv in $(vgs --noheadings -o pv_name "$vgname" 2>/dev/null) ; do
        SHOW INFO "Remove physical volume $pv"
        pvremove -ff -y $pv >/dev/null 2>&1
    done
}

sys_remove_lvm_volume_group() {
    local vgname=$1

    SHOW DEBUG "Try to remove LVM volume group $vgname"
    [ -z "$vgname" ] && return 1
    vgdisplay -c 2>/dev/null | grep -q "$vgname" || return 0
    if vgdisplay -c 2>&1 | grep -q "$vgname is exported" ; then
        sys_import_lvm_volume_group $vgname
    fi

    sys_remove_lvm_logical_volumes $vgname
    sys_remove_lvm_physical_volumes $vgname
    SHOW INFO "Removing LVM volume group $vgname"
    vgremove -f $vgname >/dev/null 2>&1
    vgdisplay -c 2>/dev/null | grep -q "$vgname" || return 0
    SHOW INFO "Fail to remove LVM volume group $vgname"
}

sys_remove_lvm_logical_devices() {
    local vgname=$1 device devices
    local all="${DI_DEFAULT_VG_PREFIX}.*-${DI_DEFAULT_LV_PREFIX}.*"
    local default="${DI_DEFAULT_LVM_VG}-${DI_DEFAULT_LVM_LV}"

    if [ -n "$vgname" ] ; then
        devices=$(dmsetup -c info | grep "${vgname}-" | awk '{print $1}')
    else
        devices=$(dmsetup -c info | grep "$all" | awk '{print $1}')
    fi

    SHOW INFO "Clear LVM logical devices"
    for device in $devices ; do
        grep -q "/dev/mapper/$device" /proc/cmdline && continue
        SHOW INFO "Removing LVM logical device $device"
        dmsetup remove -f $device >/dev/null 2>&1
        dmsetup -c info 2>/dev/null | grep -q "$device" || return 0
        SHOW INFO "Fail to remove LVM logical device $device"
    done
    dmsetup remove -f $default >/dev/null 2>&1
}

sys_export_lvm_volume_group() {
    local vgname=$1

    SHOW DEBUG "Try to export LVM volume group: $vgname"
    [ -z "$vgname" ] && return 1
    vgdisplay -c 2>&1 | grep -q "$vgname is exported" && return 0
    SHOW INFO "Export LVM volume group: $vgname"
    vgchange -an $vgname >/dev/null 2>&1
    vgexport $vgname >/dev/null 2>&1
    SHOW DEBUG "Volume groups after exported:"
}

sys_import_lvm_volume_group() {
    local vgname=$1

    SHOW DEBUG "Try to import LVM volume group $vgname"
    [ -z "$vgname" ] && return 1
    vgdisplay -c 2>/dev/null | grep -q "$vgname" || return 0
    SHOW INFO "Importing LVM volume group $vgname"
    vgscan --mknodes --ignorelockingfailure >/dev/null 2>&1
    vgimport $vgname >/dev/null 2>&1
    vgchange -ay --ignorelockingfailure $vgname >/dev/null 2>&1
}

sys_get_lvm_names() {
    local pnode=$1 vgname=$2 lvname=$3 vg lv

    vg=$(pvs --noheadings -o vg_name $pnode 2>/dev/null | sed 's/\ //g')
    [ -z "$vg" ] && EXIT ERROR ERR_SYSCALL "Cannot find volume group for $pnode"
    SHOW DEBUG "Find volume group $vg on $pnode"

    sys_import_lvm_volume_group $vg

    lv=$(lvdisplay -c 2>&- | grep "/dev/$vg.*:$vg:" | awk -F':' '{print $1}')
    [ -z "$lv" ] && EXIT ERROR ERR_SYSCALL "Cannot find logical volume in $vg"
    lv=$(basename $lv)

    SHOW DEBUG "Find logical volume $lv in $vg"
    eval $vgname=$vg
    eval $lvname=$lv
}

sys_generate_lvm_names() {
    local vgname=$1 lvname=$2 uuid=""

    uuid="$(cat /proc/sys/kernel/random/uuid 2>/dev/null)"
    if [ -n "$uuid" ] ; then
        local tmpvg tmplv
        tmpvg="$(echo $uuid | awk -F'-' '{print $2}' | tr [a-z] [A-Z])"
        tmplv="$(echo $uuid | awk -F'-' '{print $3}' | tr [a-z] [A-Z])"
        eval $vgname=${DI_DEFAULT_VG_PREFIX}${tmpvg}
        eval $lvname=${DI_DEFAULT_LV_PREFIX}${tmplv}
    else
        SHOW WARNING "Fail to generate LVM VG/LV names, use default values"
    fi
}

sys_clear_lvm_stuffs() {
    # This function is called when begin the lvm deployment
    local dev_node=$1 vg vgs

    [ -z "$dev_node" ] && return 1

    SHOW INFO "Clear LVM volume group related to $dev_node"
    vgs=$(pvs --noheadings 2>/dev/null | grep "${dev_node}" | \
        awk '{print $2}' | sort | uniq)
    for vg in $vgs ; do
        SHOW INFO "Find volume group $vg in $dev_node"
        sys_umount_partition /dev/mapper/${vg}*
        sys_remove_lvm_volume_group $vg
    done
    sys_remove_lvm_logical_devices
}

sys_exit_lvm_stuffs() {
    # This function is called when the deployment is finished
    # Need export LVM for later import when boot target.
    # Should not remove LVM metadata in physical volume.
    local vgname=$1

    [ -z "$vgname" ] && return 1
    SHOW INFO "Clear LVM stuffs when exit"
    sys_export_lvm_volume_group $vgname
    sys_remove_lvm_logical_devices $vgname
}

sys_rsync_dir() {
    local src=$1 dst=$2 exclude_file=$3 verbose=$4
    local opts="-a --one-file-system --delete"

    [ -n "$exclude_file" ] && opts="$opts --exclude-from=$exclude_file"
    [ "$verbose" == "1" ]  && opts="$opts -v --progress"
    SHOW DEBUG "rsync $opts $src $dst"
    rsync $opts $src $dst && return 0
    EXIT SHOW "Fail to sync current rootfs"
}

sys_create_rootfs_tarball() {
    local rootfs_dir=$1 dst_name=$2 verbose=$3

    if [ "$verbose" == 1 ] ; then
        SHOW DEBUG " tar -jcvf $dst_name -C $rootfs_dir ."
        tar -jpcvf $dst_name -C $rootfs_dir . && return 0
    else
        tar -jpcf $dst_name -C $rootfs_dir . >/dev/null 2>&1 && return 0
    fi
    EXIT ERROR ERR_SYSCALL "Fail to create rootfs tarball"
}

#===============================================================================
# Deploy Image Functions
#===============================================================================
di_get_single_command() {
    local cmd=$1 value=$2

    if [ -n "$value" -a -z "$DI_SINGLE_CMD" ] ; then
        DI_SINGLE_CMD=$cmd
        eval $cmd=$value
    elif [ -n "$value" -a -n "$DI_SINGLE_CMD" ] ; then
        SHOW ERROR "You have already specified ${DI_SINGLE_CMD}."
        EXIT ERROR ERR_PARAMETER "Don't support two commands at the same time."
    fi
}

di_prepare_image_device() {
    [ -z "$DI_IMAGE_NAME" ] && return 0

    SHOW STEP "Preparing the USB image device ..."
    sys_touch_image_file $DI_IMAGE_NAME $DI_IMAGE_SIZE_BYTES
    sys_attach_iamge_device $DI_IMAGE_NAME DI_DEVICE
}

di_create_partitions() {
    [ -z "$DI_FORMAT" ] && return 0

    SHOW STEP "Removing LVM stuffs in the device ..."
    sys_clear_lvm_stuffs "$DI_DEVICE"

    SHOW STEP "Clearing partition table in the device ..."
    sys_clear_partitions $DI_DEVICE

    SHOW STEP "Creating new partitions in the device ..."
    sys_create_partitions $DI_DEVICE $DI_VFAT_SIZE_BYTES $DI_ROOTFS_SIZE_BYTES
}

di_query_partitions() {
    local vfat_node rootfs_node line try_cnt

    SHOW STEP "Querying vfat and rootfs partition nodes ..."

    if [ -n "$DI_DEVICE" ] ; then
        # Fix backup GPT table is not at the end of the disk issue
        parted $DI_DEVICE print -- Fix Fix >/dev/null 2>&1

        SHOW INFO "Current partitions in the device: "
        parted -s $DI_DEVICE print | grep -e 'efi' -e 'primary' | while read line ; do
            SHOW INFO "$line"
        done

        sys_check_partitions $DI_DEVICE $DI_LVM

        try_cnt=10   # wait for the device node to be ready
        while [ $try_cnt -gt 0 ] ; do
            if [ -n "$DI_IMAGE_NAME" ] ; then
                sys_create_image_device_maps $DI_DEVICE
                vfat_node="/dev/mapper/$(basename $DI_DEVICE)p1"
                rootfs_node="/dev/mapper/$(basename $DI_DEVICE)p2"
            elif echo "$DI_DEVICE" | grep -q "/dev/mmcblk" ; then
                vfat_node="${DI_DEVICE}p1"
                rootfs_node="${DI_DEVICE}p2"
            else
                vfat_node=$(ls ${DI_DEVICE}*1 2>/dev/null)
                rootfs_node=$(ls ${DI_DEVICE}*2 2>/dev/null)
            fi
            [ -e "$vfat_node" -a -e "$rootfs_node" ] && break

            SHOW DEBUG "Wait for vfat and rootfs node to be ready: $try_cnt"
            if which udevadm >/dev/null 2>&1 ; then
                # Trigger udev events once, because some time the /dev/xxx nodes
                # are not created immediately after the partitions are created.
                if [ $try_cnt -eq 10 ] ; then
                    SHOW INFO "Trigger and wait device node uevent"
                    udevadm trigger
                    udevadm settle
                fi
            fi
            try_cnt=$(($try_cnt - 1))
            sleep 1
        done

        [ -z "$DI_VFAT_NODE" ] && DI_VFAT_NODE=$vfat_node
        [ -z "$DI_ROOTFS_NODE" ] && DI_ROOTFS_NODE=$rootfs_node
    fi

    # There should always be two partitions in the device for IDP
    if [ -z "$DI_VFAT_NODE" -o -z "$DI_ROOTFS_NODE" ] ; then
        EXIT ERROR ERR_PART_READY "Invalid vfat or rootfs partition node"
    fi
    SHOW INFO "VFAT partition node: $DI_VFAT_NODE"
    SHOW INFO "Rootfs partition node: $DI_ROOTFS_NODE"

    if [ $DI_LVM = 1 -a -n "$DI_ROOTFS_NODE" ] ; then
        if [ -n "$DI_FORMAT" ]; then
            sys_create_lvm_physical_volume $DI_ROOTFS_NODE
            sys_create_lvm_volume_group $DI_ROOTFS_NODE $DI_LVM_VG_NAME
            sys_create_lvm_logical_volume $DI_LVM_VG_NAME $DI_LVM_LV_NAME $DI_LVM_SIZE
        fi

        # Do not set rootfs node to LVM node if it is already specified by -R option.
        if ! echo $DI_ROOTFS_NODE | grep -q "/dev/mapper/.*-.*" ; then
            sys_get_lvm_names $DI_ROOTFS_NODE DI_LVM_VG_NAME DI_LVM_LV_NAME
            DI_ROOTFS_NODE="/dev/mapper/${DI_LVM_VG_NAME}-${DI_LVM_LV_NAME}"
            SHOW INFO "LVM Rootfs partition node: $DI_ROOTFS_NODE"
        fi
    fi

    sys_wait_partition_node_ready $DI_VFAT_NODE
    sys_wait_partition_node_ready $DI_ROOTFS_NODE
}

di_format_partitions() {
    local fs_type

    [ -z "$DI_FORMAT" ] && return 0
    SHOW STEP "Formating vfat and rootfs partitions ..."

    if [ -n "$DI_VFAT_NODE" ] ; then
        fs_type=${DI_FORMAT%%+*}
        sys_umount_partition "$DI_VFAT_NODE"
        sys_format_partition "$DI_VFAT_NODE" "$fs_type" $DI_DEFAULT_VFAT_LABEL
    fi

    if [ -n "$DI_ROOTFS_NODE" ] ; then
        fs_type=${DI_FORMAT##*+}
        sys_umount_partition "$DI_ROOTFS_NODE"
        sys_format_partition "$DI_ROOTFS_NODE" "$fs_type" $DI_DEFAULT_ROOTFS_LABEL
    fi
}

di_mount_partitions() {
    SHOW STEP "Mounting vfat and rootfs partitions ..."
    if [ -n "$DI_VFAT_NODE" ] ; then
        sys_get_mount_point $DI_VFAT_NODE "vfat" DI_VFAT_MOUNT
    fi
    if [ -n "$DI_ROOTFS_NODE" ] ; then
        sys_get_mount_point $DI_ROOTFS_NODE "rootfs" DI_ROOTFS_MOUNT
    fi
}

di_make_rootfs_readonly() {
    # Switch rootfs to read only
    [ $DI_READONLY != 1 ] && return 0

    SHOW STEP "Make rootfs to be read only"
    SHOW INFO "Set read only flag in $DI_ROOTFS_MOUNT"
    touch $DI_ROOTFS_MOUNT/etc/.recovery
}

di_enable_extend() {
    # set a file flag to expend partition when first boot
    [ $DI_EXTEND != 1 ] && return 0

    SHOW STEP "Enable to extend LVM partition when first boot"
    SHOW INFO "Set extend LVM partition flag in $DI_ROOTFS_MOUNT"
    touch $DI_ROOTFS_MOUNT/etc/.extend_partition
}

di_backup_sfr_rootfs_tarball() {
    # Copy rootfs tarball for recovery image and SFR
    local name="$DI_VFAT_MOUNT/$DI_SFR_BACKUP_DIR/$DI_SFR_ROOTFS_NAME"
    local verbose=0

    SHOW STEP "Installing files for factory reset"
    [ "$DI_LOG_LEVEL" -ge "$DI_LOG_LEVEL_DEBUG" ] && verbose=1

    if [ -e "$DI_ROOTFS_TARBALL_BACKUP" ] ; then
        SHOW INFO "Copy rootfs tarball for factory reset"
        sys_make_dir  $(dirname "$name")
        sys_copy_file "$DI_ROOTFS_TARBALL_BACKUP" "$name"
    elif [ -z "$DI_ROOTFS_TARBALL_BACKUP" ] ; then
        if [ "$DI_SFR" == 1 ] ; then
            SHOW INFO "Creating the current rootfs tarball ..."
            sys_make_dir  $(dirname "$name")
            sys_create_rootfs_tarball $DI_ROOTFS_MOUNT $name $verbose
        else
            SHOW INFO "Rootfs tarball for backup is not specified"
        fi
    else
        SHOW INFO "Rootfs tarball doesn't exist for backup"
    fi
}

di_backup_sfr_kernel_image() {
    # Copy SFR Image from vfat partition but not rootfs partition,
    # Because these are the really being used image files.
    local sfr_dir="$DI_VFAT_MOUNT/$DI_SFR_BACKUP_DIR"
    local boot_dir=$DI_VFAT_MOUNT
    local image_file

    [ $DI_SFR != 1 ] && return 0
    [ -d "$DI_ROOTFS_MOUNT" ] || EXIT ERROR ERR_INVALID_FILE "Invalid rootfs directory for SFR"

    sys_make_dir $sfr_dir

    # Copy the bzImage and idp-initramfs.img files to the SFR directory.
    image_file=$(readlink -sf $boot_dir/bzImage)
    [ -e "$image_file" ] || EXIT ERROR ERR_INVALID_FILE "Invalid bzImage for SFR"

    image_file=$boot_dir/$(basename $image_file)
    SHOW INFO "Copy bzImage for factory reset"
    di_copy_protected_file "$image_file" "$sfr_dir/SFR-bzImage"
    SHOW INFO "Copy initramfs image for factory reset"
    di_copy_protected_file "$boot_dir/idp-initramfs.img" "$sfr_dir/SFR-initramfs.img"
    SHOW DEBUG "Finish to install SFR files."
}

di_deploy_rootfs() {
    [ -z "$DI_ROOTFS_TARBALL" ] && return 0

    SHOW STEP "Deploying the rootfs and kernel image ..."

    if [ -f "$DI_ROOTFS_TARBALL" ] ; then
        sys_extract_tarball "$DI_ROOTFS_MOUNT" "$DI_ROOTFS_TARBALL"
    fi

    # This should be do in build system, but not here
    #if [ -e $DI_ROOTFS_MOUNT/lib64/wifi ]; then
    #    cp $DI_ROOTFS_MOUNT/lib64/wifi/* $DI_ROOTFS_MOUNT/lib/wifi/
    #fi

    # create VFAT mount point on ROOTFS partition
    sys_make_dir $DI_ROOTFS_MOUNT/$DI_VFAT_AUTOMOUNT
}

di_sync_current_rootfs() {
    local exclude_dirs="media etc/lvm/archive/ etc/lvm/backup/"
    local exclude_files="$DI_IMAGE_NAME *.swp *~"
    local exclude_file dir file verbose

    [ "$DI_SYNC_CURRENT" == "1" ] || return 0

    if [ -z "$DI_ROOTFS_MOUNT" -o -z "$DI_VFAT_MOUNT" ] ; then
        EXIT ERROR ERR_PART_READY "Cannot access VFAT or rootfs partition"
    fi

    # Sync rootfs partition excluding some special directories.
    SHOW STEP "Sync current rootfs partition ..."
    SHOW INFO "Sync rootfs to: $DI_ROOTFS_MOUNT ($DI_ROOTFS_NODE)"
    if [ $DI_LOG_LEVEL -ge $DI_LOG_LEVEL_DEBUG ] ; then
        if ! sys_get_user_answer "Continue to save current rootfs" ; then
            EXIT INFO "Canceled to save current rootfs"
        fi
    fi

    [ "$DI_LOG_LEVEL" -ge "$DI_LOG_LEVEL_DEBUG" ] && verbose=1 || verbose=0

    sys_make_dir $DI_TMPDIR
    exclude_file="$DI_TMPDIR/rootfs_excludes"
    for dir in $exclude_dirs ; do
        echo $dir >>$exclude_file
    done
    for file in $exclude_files ; do
        file=$(readlink -sf "$file")
        echo "${file#/}" >>$exclude_file
    done

    sys_rsync_dir "/" "$DI_ROOTFS_MOUNT/" "$exclude_file" "$verbose"

    # Generate the exclude directories manually.
    for dir in $exclude_dirs; do
        sys_make_dir $DI_ROOTFS_MOUNT/$dir
    done

    # Clear for first boot
    SHOW INFO "Enable firstboot service in new rootfs partition"
    sys_make_dir $DI_ROOTFS_MOUNT/etc/systemd/system/sysinit.target.wants
    sys_make_link /lib/systemd/system/firstboot.service \
        $DI_ROOTFS_MOUNT/etc/systemd/system/sysinit.target.wants/firstboot.service

    # Clear some security keys
    SHOW INFO "Remove ssh keys and nginx ssl keys"
    rm -rf $DI_ROOTFS_MOUNT/etc/ssh/*key
    rm -rf $DI_ROOTFS_MOUNT/etc/ssh/*key.pub
    rm -rf $DI_ROOTFS_MOUNT/etc/nginx/ssl/*.key
    rm -rf $DI_ROOTFS_MOUNT/etc/nginx/ssl/*.pem

    # Set a special flag for saved rootfs
    touch $DI_ROOTFS_MOUNT/etc/.saved_rootfs

    SHOW INFO "sync / to $DI_ROOTFS_MOUNT done"

    # sync the vfat partition
    # always generate new SFR tarball and image files
    SHOW STEP "Sync current vfat boot partition ..."
    SHOW INFO "Sync vfat boot to: $DI_VFAT_MOUNT ($DI_VFAT_NODE)"

    exclude_file="$DI_TMPDIR/vfat_excludes"
    echo $DI_SFR_BACKUP_DIR > $exclude_file
    sys_rsync_dir "$DI_VFAT_AUTOMOUNT/" "$DI_VFAT_MOUNT/" "$exclude_file" "$verbose"

    # Maybe there are SFR files in last deployment
    rm -rf $DI_VFAT_MOUNT/$DI_SFR_BACKUP_DIR

    SHOW INFO "sync vfat partition to $DI_VFAT_MOUNT done"

    # Update grub.conf in another function, then
    # option -K/-T/-D can run independently.
    DI_NEED_UPDATE_GRUB_CONF=1
}

di_copy_protected_file() {
    local src=$1 auth_file_src=${1}.auth
    local dst=$2 auth_file_dst=${2}.auth

    sys_copy_file $src $dst
    [ -d "$dst" ] && auth_file_dst=$dst
    [ -e "$auth_file_src" ] && sys_copy_file $auth_file_src $auth_file_dst
}

di_install_uefi() {
    local rootfs_dir=$DI_ROOTFS_MOUNT
    local vfat_dir=$DI_VFAT_MOUNT
    local rootfs_grub_dir=${rootfs_dir}/boot/efi/EFI/boot
    local rootfs_capsule_dir=$rootfs_dir/opt/CapsuleUpdate
    local vfat_grub_dir=${vfat_dir}/EFI/boot
    local vfat_capsule_dir=${vfat_dir}/CapsuleUpdate
    local initramfs_file=${rootfs_dir}/boot/idp-initramfs.img
    local image_file=$DI_NEW_KERNEL
    local new_conf_file=$DI_NEW_GRUBCONF

    [ "$DI_UEFI" == "1" ] || return 0
    SHOW STEP "Deploying UEFI executable file and grub conf ..."

    SHOW INFO "Remove all files in \"$vfat_dir\""
    [ -d "$vfat_dir" -a "$vfat_dir" != "/" ] && rm -rf $vfat_dir/*

    SHOW DEBUG "Try to deploy UEFI files"

    # Copy kernel image file
    if [ -z "$image_file" ] ; then
        # If have not specified kernel image by -K option
        # use /boot/bzImage in rootfs partition by default
        image_file=$(readlink -sf $rootfs_dir/boot/bzImage)
        image_file="$rootfs_dir/boot/$(basename $image_file)"
        SHOW DEBUG "Default image: $image_file"
    fi
    SHOW INFO "Copy bzImage to boot partition"
    di_copy_protected_file "$image_file" "$vfat_dir/bzImage"

    if [ -e "$initramfs_file" ] ; then
        SHOW INFO "Copy initramfs image to boot partition"
        di_copy_protected_file "$initramfs_file" "$vfat_dir/"
    fi

    # Copy grub uefi files
    sys_make_dir $vfat_grub_dir
    SHOW INFO "Copy grub executable and conf files"
    sys_copy_file $rootfs_grub_dir ${vfat_dir}/EFI/

    # If have specified grub.conf by -G option
    if [ -n "$new_conf_file" ] ; then
        local grub_conf_file=$(ls $vfat_grub_dir/*.conf 2>/dev/null | awk '{print $1}')
        if [ -n "$grub_conf_file" ] ; then
            SHOW INFO "Replace the $(basename $grub_conf_file)"
            di_copy_protected_file $new_conf_file $grub_conf_file
        else
            SHOW INFO "Cannot find grub.conf file to be replaceed"
        fi
    fi

    # Copy causule update stuffs
    if [ -e "$rootfs_capsule_dir/CapsuleApp.efi" ] ; then
        SHOW INFO "Backup files for capsule update"
        sys_make_dir $vfat_capsule_dir
        sys_copy_file $rootfs_capsule_dir $vfat_capsule_dir
    fi

    # Update grub.conf in another function, then
    # option -K/-T/-D can run independently.
    DI_NEED_UPDATE_GRUB_CONF=1
}

di_restroe_uefi() {
    SHOW STEP "Restoring kernel images and UEFI files ..."
    if [ -z "$DI_VFAT_MOUNT" -o -z "$DI_ROOTFS_MOUNT" ] ; then
        EXIT ERROR ERR_PART_READY "Invalid vfat or rootfs partition mount point"
    fi

    # Restore kernel image file
    SHOW INFO "Restore bzImage to boot partition"
    di_copy_protected_file "$DI_VFAT_MOUNT/bzImage" "$DI_ROOTFS_MOUNT/boot/"

    SHOW INFO "Restore initramfs image to boot partition"
    di_copy_protected_file "$DI_VFAT_MOUNT/idp-initramfs.img" "$DI_ROOTFS_MOUNT/boot/"

    # Restore grub uefi files
    SHOW INFO "Restore grub executable and conf files"
    sys_make_dir  $DI_ROOTFS_MOUNT/boot/efi/EFI/
    sys_copy_file $DI_VFAT_MOUNT/EFI/boot $DI_ROOTFS_MOUNT/boot/efi/EFI/
}

di_update_grub_conf() {
    local grub_conf_file="$1"
    local vfat_dir=$DI_VFAT_MOUNT
    local vfat_grub_dir=$DI_VFAT_MOUNT/EFI/boot
    local rootfs_node=$DI_ROOTFS_NODE
    local boot_root=$DI_BOOT_ROOTFS
    local boot_kernel=$DI_BOOT_KERNEL
    local boot_title=$DI_BOOT_TITLE_SUFFIX
    local read_only=${DI_READONLY:-0}
    local lvm_node=/dev/mapper/$DI_LVM_VG_NAME-$DI_LVM_LV_NAME
    local required=$DI_NEED_UPDATE_GRUB_CONF
    local root_file="" root_node=""

    [ $DI_NEED_UPDATE_GRUB_CONF != 1 ] && return 0
    SHOW STEP "Updating settings in the grub conf ..."

    if [ ! -e "$grub_conf_file" ] ; then
        # use the first *.conf file by default if not specified.
        grub_conf_file=$(ls $vfat_grub_dir/*.conf 2>/dev/null | awk '{print $1}')
        if [ ! -e "$grub_conf_file" ] ; then
            SHOW ERROR "Need to update grub conf, but cannot find such file"
            EXIT ERROR ERR_NO_GRUBFILE "$grub_conf_file"
        fi
        SHOW DEBUG "Found grub conf: $grub_conf_file"
    fi

    for i in $(grep "^[[:space:]]*rootfs" $grub_conf_file | \
        awk '{print $2}' | sort | uniq | xargs) ; do
        root_file="$i $root_file"
    done

    SHOW INFO "Grub file: $grub_conf_file"
    SHOW INFO "Boot rootfs file: $root_file"
    if [ -n "$boot_root" -a -n "$root_file" ] ; then
        # Write boot root device to /rootfs on VFAT partition.
        for i in $root_file ; do
            SHOW INFO "Generate the boot root device file $i"
            if [ "$boot_root" = "UUID" ] ; then
                root_node="UUID=$(blkid -o value -s UUID $rootfs_node)"
            elif [ "$boot_root" = "LVM" ] ; then
                root_node="LVM=$lvm_node"
            else
                root_node=$boot_root
            fi
            if echo "$i" | grep -q "_sfr" ; then
                root_node="$(echo $root_node | sed -e 's/LVM=/SFR=/')"
            fi
            SHOW INFO "    $root_node"
            echo -n "$root_node" > ${vfat_dir}/$i
        done
    fi

    if [ -e "${grub_conf_file}.auth" ] ; then
        if [ -n "$boot_kernel" ] || [ -n "$boot_title" ] ; then
            SHOW WARNING "Ignore invalid -K/-T option for signed rootfs!"
            boot_kernel="" && boot_title=""
        fi
        if [ -n "$boot_root" -a -z "$root_file" ] ; then
            SHOW WARNING "No rootfs subcommand found in grub.conf"
            SHOW WARNING "Ignore invalid -D option for signed rootfs!"
            boot_root=""
        fi
    else
        # Update grub.conf when there is no auth file

        # Use rootfs files if there are rootfs command in grub.conf.
        # Only change grub.conf directly if there is no rootfs command
        if [ -n "$boot_root" -a -z "$root_file" ] ; then
            if [ "$boot_root" = "UUID" ] ; then
                root_node="UUID=$(blkid -o value -s UUID $rootfs_node)"
            elif [ "$boot_root" = "LVM" ] ; then
                root_node="LVM=$lvm_node"
            else
                root_node=$boot_root
            fi
            SHOW INFO "Replace boot root device in grub conf:"
            SHOW INFO "    root=$root_node"
            if grep -q "root=SFR=" $grub_conf_file; then
                if ! sed -i -e "s#root=LVM=[^ ]*#root=${root_node}#" $grub_conf_file ; then
                    EXIT ERROR ERR_UPDATE_GRUB "Modify boot root device in grub.conf failed!"
                fi
                if [ "$boot_root" = "LVM" ] ; then
                    root_node="SFR=$lvm_node"
                    SHOW INFO "    root=$root_node"
                fi
                if ! sed -i -e "s#root=SFR=[^ ]*#root=${root_node}#" $grub_conf_file ; then
                    EXIT ERROR ERR_UPDATE_GRUB "Modify boot root device in grub.conf failed!"
                fi
            else
                if ! sed -i -e "s#root=[^ ]*#root=${root_node}#" $grub_conf_file ; then
                    EXIT ERROR ERR_UPDATE_GRUB "Modify boot root device in grub.conf failed!"
                fi
            fi
        fi

        if [ -n "$boot_kernel" ] ; then
            SHOW INFO "Replace boot kernel in grub conf:"
            SHOW INFO "    kernel $boot_kernel"
            if ! sed -i -e "s#kernel\ /bzImage\ #kernel\ /$boot_kernel\ #" $grub_conf_file ; then
                EXIT ERROR ERR_UPDATE_GRUB "Modify boot kernel in grub.conf failed!"
            fi
        fi

        if [ -n "$boot_title" ] ; then
            SHOW INFO "Replace boot title in grub conf:"
            SHOW INFO "    title suffix - $boot_title"
            if ! sed -i -e "/title\ .*\$/s/\ -\ .*\$/\ -\ $boot_title/" $grub_conf_file ; then
                SHOW WARNING "Modify boot title in grub.conf failed!"
                SHOW WARNING "May no suffix in the boot entry title."
            fi
        fi

        if [ $read_only = 1 ] ; then
            if grep -q -w "rw" $grub_conf_file ; then
                SHOW INFO "Setting read only in grub conf"
                if ! sed -i -e "s/rw/ro/" $grub_conf_file ; then
                    EXIT ERROR ERR_UPDATE_GRUB "Modify read only in grub.conf failed!"
                fi
            fi

            if ! grep -q "ima_appraise=off" $grub_conf_file ; then
                SHOW INFO "Disable ima_appraise in grub conf"
                if ! sed -i -e "s/^kernel.*$/&\ ima_appraise=off/" $grub_conf_file ; then
                    EXIT ERROR ERR_UPDATE_GRUB "Add ima_appraise=off in grub.conf failed!"
                fi
            fi
        fi
    fi
    return 0
}

di_security_lockdown() {
    [ -z "$DI_LOCKDOWN_OPTS" -o -z "$DI_ROOTFS_MOUNT" ] && return 1

    SHOW STEP "Lock down rootfs file system for security ..."
    SHOW INFO "lockdown -r $DI_ROOTFS_MOUNT $DI_LOCKDOWN_OPTS"
    $DI_LOCKDOWN_CMD -r $DI_ROOTFS_MOUNT $DI_LOCKDOWN_OPTS && return 0
    EXIT ERROR ERR_SYSCALL "Fail to lock down $DI_ROOTFS_MOUNT"
}

di_capsule_update() {
    # use the capsule file in the rootfs tarball but not in the current rootfs.
    local opts="-r 0 -t $DI_ROOTFS_MOUNT"

    [ "$DI_CAPSULE_UPDATE" == "1" ] || return 0

    SHOW STEP "Do capsule update ..."
    SHOW INFO "call \"$DI_CAPSULE_UPDATE_CMD $opts\""
    if $DI_CAPSULE_UPDATE_CMD $opts >/dev/null 2>&1 ; then
        DI_REBOOT=1
        return 0
    fi
    EXIT ERROR ERR_SYSCALL "Fail to do capsule update"
}

di_remove_temporary_things() {
    local errno=$1 try_cnt=3

    sys_umount_partition $DI_VFAT_MOUNT
    sys_umount_partition $DI_ROOTFS_MOUNT

    if [ -d "$DI_TMPDIR" ] ; then
        for i in $(ls ${DI_TMPDIR} 2>/dev/null) ; do
            SHOW DEBUG "Temporary: ${DI_TMPDIR}/$i"
            [ "${DI_TMPDIR}/$i" == "$DI_VFAT_MOUNT" ] && continue
            [ "${DI_TMPDIR}/$i" == "$DI_ROOTFS_MOUNT" ] && continue
            if [ -d "${DI_TMPDIR}/$i" ] ; then
                sys_umount_partition ${DI_TMPDIR}/$i 2>/dev/null
            fi
        done
    fi

    if [ "$DI_LVM" = 1 ] ; then
        sys_exit_lvm_stuffs $DI_LVM_VG_NAME
    fi

    if [ -n "$DI_IMAGE_NAME" ] ; then
        [ -n "$DI_DEVICE" ] && SHOW INFO "Clear image loop device"
        while [ $try_cnt -gt 0 ] ; do
            sys_delete_image_device_maps $DI_DEVICE
            sys_detach_image_device $DI_IMAGE_NAME && break
            SHOW DEBUG "Clear image loop device: $try_cnt"
            sleep 1
            try_cnt=$(($try_cnt - 1))
        done

        if [ -z "$errno" -a -n "$DI_FORMAT" ] ; then
            # When dd the image file again and again to the same device
            # need to overwrite the following partition's information.
            sys_file_append_zero $DI_IMAGE_NAME 4
        elif [ "$DI_NOT_REMOVE_IMAGE" != "1" ] ; then
            SHOW INFO "Remove image file when error exit"
            rm -rf $DI_IMAGE_NAME
        fi
    fi

    [ $DI_ISROOT = 1 ] && sys_restore_kernel_log_level

    rm -rf $DI_TMPDIR
    return 0
}

di_simple_exit() {
    rm -rf $DI_TMPDIR/$DI_RUNNING_FILE
    exit $1
}

di_run_all_actions() {
    di_prepare_image_device
    di_create_partitions
    di_query_partitions
    di_format_partitions
    di_mount_partitions

    if [ "$DI_SYNC_CURRENT" == "1" ] ; then
        di_sync_current_rootfs
        di_restroe_uefi
    else
        di_deploy_rootfs
        di_install_uefi
    fi

    di_update_grub_conf
    di_enable_extend
    di_make_rootfs_readonly
    di_security_lockdown

    # All above changes are also saved into SFR backup
    di_backup_sfr_rootfs_tarball
    di_backup_sfr_kernel_image

    di_capsule_update

    if [ "$DI_CMD_MOUNT" == "1" ] ; then
        SHOW WARNING "Partitions are already mounted as shown above"
        SHOW WARNING "Try to check file by 'sudo ls <mount-point>'"
        SHOW WARNING "Do not forget to unmount it after all are done"
        di_simple_exit 0
    fi
}

di_capsule_update_reboot() {
    [ $DI_REBOOT = 1 ] || return 0

    # if sys_get_user_answer "Warm reboot to complete capsule update now"
    SHOW WARNING "Warm reboot to complete the capsule update!"
    SHOW WARNING "Do not plug out the power adapter directly!"
    sleep 3
    reboot
}

di_exit_and_clean_up() {
    # In some host, unmount cannot make sure
    # write all data into hardware device.
    SHOW STEP "Synchronizing all data to ${DI_IMAGE_NAME:-$DI_DEVICE} ..."
    sync

    SHOW STEP "Exiting and clean up ..."
    di_remove_temporary_things

    SHOW STEP "Done!"
    di_capsule_update_reboot

    # make sure it will exit 0 after all is done
    exit 0
}

di_calculate_real_rootfs_size() {
    local mini=$DI_ROOTFS_MINI_SIZE_BYTES tarball_real

    sys_get_tarball_size "$DI_ROOTFS_TARBALL" tarball_real || return 0
    SHOW INFO "Rootfs uncompressed size: $(($tarball_real / (1024*1024)))M"
    mini=$(($mini - $DI_DEFAULT_ROOTFS_REAL_SIZE + $tarball_real))
    DI_ROOTFS_MINI_SIZE_BYTES=$mini
    SHOW DEBUG "Resize minimal rootfs partition: $DI_ROOTFS_MINI_SIZE_BYTES"
}

di_check_rootfs_options() {
    # Whether need to use the current vfat partition
    # 1. Default reset media tarball in $current_vfat/SFR/xxx.tar.bz2
    # 2. Need to save current vfat partition when -C option is specified.
    sys_get_current_vfat_node DI_CURRENT_VFAT_NODE
    if [ "$OPT_SYNC_CURRENT" == "1" -o "$OPT_ROOTFS" == "$DI_SFR_BACKUP_ROOTFS" ] ; then
        if mount | grep -q "/dev/.*1 on $DI_VFAT_AUTOMOUNT" ; then
            SHOW INFO "Current vfat boot partition is already mounted"
        else
            if [ -z "$DI_CURRENT_VFAT_NODE" ] ; then
                EXIT ERROR ERR_SYSCALL "Cannot find current vfat boot partition"
            fi
            SHOW INFO "Mount vfat boot partition $DI_CURRENT_VFAT_NODE at $DI_VFAT_AUTOMOUNT"
            # Workaround for SFR will re-deploy the rootfs but not create this folder
            sys_make_dir $DI_VFAT_AUTOMOUNT
            sys_mount_partition $DI_CURRENT_VFAT_NODE $DI_VFAT_AUTOMOUNT
        fi
    fi

    # Check rootfs tarball to be installed (-f/--rootfs option)
    # There maybe no rootfs tarball specified for a already deployed device
    sys_validated_file "$OPT_ROOTFS" "bzip2" && DI_ROOTFS_TARBALL=$OPT_ROOTFS

    # Check the backup rootfs tarball
    if [ "$OPT_ROOTFS_BACKUP" == "0" ] ; then
        DI_ROOTFS_TARBALL_BACKUP=""
    elif [ "$OPT_ROOTFS_BACKUP" == "1" ] ; then
        if [ -z "$DI_ROOTFS_TARBALL" ] ; then
            EXIT ERROR ERR_PARAMETER "Please specify '-B 1' when install rootfs by -f option"
        else
            # Will backup rootfs tarball when -B 1 even if --lockdown
            DI_ROOTFS_TARBALL_BACKUP=$DI_ROOTFS_TARBALL
        fi
    elif sys_validated_file "$OPT_ROOTFS_BACKUP" "bzip2" ; then
        DI_ROOTFS_TARBALL_BACKUP=$OPT_ROOTFS_BACKUP
    fi

    # Set the default minimal rootfs partition size
    DI_ROOTFS_MINI_SIZE_BYTES=$DI_DEFAULT_ROOTFS_MINI_SIZE

    DI_SFR=${OPT_SFR:-0}
    DI_SYNC_CURRENT=${OPT_SYNC_CURRENT:-0}
    DI_LOCKDOWN_OPTS="${OPT_LOCKDOWN}"   #maybe only a white space

    if [ $DI_SYNC_CURRENT = 1 -a  -n "$DI_ROOTFS_TARBALL" ] ; then
        EXIT ERROR ERR_PARAMETER "Cannot specify -C(--sync-fs)/-f option at the same time"
    fi

    # Backup rootfs tarball for -F option even "-B 0" is specified
    # Make sure to create the new rootfs tarball when -C or --lockdown
    # Make sure to create the new rootfs tarball when -d xxx -F only
    if [ -z "$DI_ROOTFS_TARBALL_BACKUP" -a "$DI_SFR" == "1" ] ; then
        if [ -n "$DI_ROOTFS_TARBALL" -a "$DI_SYNC_CURRENT" != 1 -a -z "$DI_LOCKDOWN_OPTS" ] ; then
            DI_ROOTFS_TARBALL_BACKUP=$DI_ROOTFS_TARBALL
        fi
        if [ -z "$DI_ROOTFS_TARBALL" -a "$DI_SYNC_CURRENT" != 1 -a -n "$OPT_FORMAT" ] ; then
            SHOW ERROR "The rootfs tarball is not specified for SFR backup"
            EXIT ERROR ERR_PARAMETER "Please require SFR backup when deploy rootfs by -f/-C option"
        fi
    fi

    if [ -n "$DI_LOCKDOWN_OPTS" ] ; then
        if [ $DI_SYNC_CURRENT != 1 -a -z "$DI_ROOTFS_TARBALL" -a -n "$OPT_FORMAT" ] ; then
            SHOW ERROR "Nothing to be locked down if only format device"
            EXIT ERROR ERR_PARAMETER "Maybe you lost -f/-C option or do not need -y/-t option"
        fi
        if [ ! -e $DI_LOCKDOWN_CMD ] ; then
            SHOW ERROR "Cannot find $DI_LOCKDOWN_CMD command to lock down the system"
            EXIT ERROR ERR_PARAMETER "--lockdown option is only supported in the target system"
        fi
        if ! $DI_LOCKDOWN_CMD --dry-run $DI_LOCKDOWN_OPTS 2>/dev/null ; then
            SHOW ERROR "Fail to dry run \"$DI_LOCKDOWN_CMD $DI_LOCKDOWN_OPTS\""
            SHOW ERROR "Please specify all lockdown options behind --lockdown"
            EXIT ERROR ERR_PARAMETER "See also all valid options by \"$DI_LOCKDOWN_CMD --help\""
        fi
    fi

    # Switch to read only rootfs
    DI_READONLY=${OPT_READONLY:-0}
}

di_check_device_options() {
    # Verify that the -d option
    if [ -z "$OPT_OUTPUT" -a -z "$OPT_VFAT_NODE" -a -z "$OPT_ROOTFS_NODE" ] ; then
        EXIT ERROR ERR_PARAMETER "Please set an output device or file via the -d option!"
    elif [ -z "$OPT_OUTPUT" ] ; then
        SHOW INFO "Device is not set, but partitions are specified"
    elif sys_is_block_device $OPT_OUTPUT; then
        # -d option specified a block device.
        # The image size option (-s) is only used for files, not devices.
        # Clear it, in case it was set above.
        DI_DEVICE=${OPT_OUTPUT%/}
        if echo "$DI_CURRENT_VFAT_NODE" | grep -q "$DI_DEVICE" ; then
            EXIT ERROR ERR_PARAMETER "Cannot deploy to the device used by current file system"
        fi
        DI_IMAGE_SIZE=""
        DI_DEVICE_SIZE_BYTES=$(sys_get_device_size $DI_DEVICE)
        DI_DEVICE_SIZE="$(($DI_DEVICE_SIZE_BYTES/(1024*1024)))M"
        if echo "$DI_DEVICE_SIZE" | grep -q "-" ; then
            SHOW INFO "Cannot get total size of $DI_DEVICE"
            DI_DEVICE_SIZE_BYTES=""
            DI_DEVICE_SIZE=""
        else
            SHOW INFO "Size of $DI_DEVICE: $DI_DEVICE_SIZE ($DI_DEVICE_SIZE_BYTES Bytes)"
        fi
        sys_check_device_info $DI_DEVICE
    elif sys_is_device_partition $OPT_OUTPUT ; then
        SHOW ERROR "Please specify a device node but not a partition node"
        EXIT ERROR ERR_PARAMETER "via the -d option. e.g. /dev/sdb or /dev/mmcblk0"
    elif [ -d "$OPT_OUTPUT" ] ; then
        EXIT ERROR ERR_PARAMETER "Directory can't be specified as USB iamge file name!"
    elif [ ! -d "$(dirname $OPT_OUTPUT)" ] ; then
        EXIT ERROR ERR_PARAMETER "Directory of image file doesn't exist: $(dirname $OPT_OUTPUT)"
    else
        # -d option specified a file.
        if echo "$OPT_OUTPUT" | grep -q "/dev/" ; then
            SHOW ERROR "Device $OPT_OUTPUT doesn't exist or is not block special file"
            SHOW ERROR "This may happen when hardware disk is inserted but device node"
            EXIT ERROR ERR_PARAMETER "has not been created automatically yet. "
        fi

        sys_check_commands $COMMANDS_USBIMAGE
        DI_IMAGE_NAME=$OPT_OUTPUT

        # Backup original rootfs tarball when "-d image -f xxx" without "--lockdown"
        # There is a security risk when "-d image -f xxx --lockdown", so will not backup
        # In this case, should run more secure command "-d iamge -f xxx -F --lockdown"
        if [ -z "$DI_ROOTFS_TARBALL_BACKUP" -a -n "$DI_ROOTFS_TARBALL" ] ; then
            [ -z "$OPT_LOCKDOWN" ] && DI_ROOTFS_TARBALL_BACKUP=$DI_ROOTFS_TARBALL
        fi

        if [ -f "$DI_IMAGE_NAME" ]; then
            DI_NOT_REMOVE_IMAGE=${OPT_NOT_REMOVE_IMAGE:-0}
            if [ "$OPT_NOT_REMOVE_IMAGE" != 1 ]; then
                if sys_get_user_answer "Image file exists, do you want to re-create it" ; then
                    rm -rf $DI_IMAGE_NAME
                else
                    DI_NOT_REMOVE_IMAGE=1
                    SHOW WARNING "Will use existent image file: $DI_IMAGE_NAME"
                fi
            fi
        fi
        # Maybe usb image file is removed above
        if [ ! -f "$DI_IMAGE_NAME" ]; then
            SHOW INFO "Will create new Image file: $DI_IMAGE_NAME"
            if [ -z "$OPT_FORMAT" ] ; then
                OPT_FORMAT=$DI_DEFAULT_FORMAT
                SHOW INFO "Will use default \"$OPT_FORMAT\" to format USB image."
            fi
            if [ -z "$OPT_UEFI" -a -n "$DI_ROOTFS_TARBALL" ] ; then
                OPT_UEFI=1
                SHOW INFO "Will install uefi for creating image by default."
            fi
        fi
    fi
}

di_check_quark_options() {
    local local_name="$(cat /etc/board_name 2>/dev/null | head -n 1)"

    # Check options for capsule update
    if [ "$OPT_CAPSULE_UPDATE" == "1" ] ; then
        if [ -x "$DI_CAPSULE_UPDATE_CMD" ] ; then
            DI_CAPSULE_UPDATE=$OPT_CAPSULE_UPDATE
            SHOW INFO "Need to do capsule update in quark board: $local_name"
        else
            SHOW ERROR "Cannot find $DI_CAPSULE_UPDATE_CMD command"
            EXIT ERROR ERR_PARAMETER "Option -c is only valid for quark target"
        fi
    fi
}

di_check_format_options() {
    # VFAT partition size (-s option)
    local vfat_size=$DI_DEFAULT_VFAT_SIZE

    if [ -e "$DI_ROOTFS_TARBALL_BACKUP" ] ; then
        local rootfs_backup_size=$(sys_get_file_size $DI_ROOTFS_TARBALL_BACKUP)
        local default_size=$(sys_parse_option_size $DI_DEFAULT_SFR_VFAT_SIZE)
        if [ -n "$rootfs_backup_size" -a "$rootfs_backup_size" -gt "$default_size" ] ; then
            vfat_size=$(($rootfs_backup_size + (1024*1024) -1))
            vfat_size=$(($vfat_size / (1024*1024) ))
            vfat_size="$(($vfat_size + 128))M"
            SHOW DEBUG "Calculate VFAT partition size: $vfat_size"
        else
            vfat_size=$DI_DEFAULT_SFR_VFAT_SIZE
        fi
    fi
    DI_VFAT_SIZE=${OPT_VFAT_SIZE:-$vfat_size}
    DI_VFAT_SIZE_BYTES=$(sys_parse_option_size $DI_VFAT_SIZE)

    # Rootfs partition size (-p option)
    # A blank size means "use all of the partition", a number is the number of
    # bytes to use. Since fdisk will interpret a pure number as a sector count
    # and a number with a K, M, or G suffix as a byte count, the option value
    # is converted to a byte count and then sent to fdisk as a kilobyte count.
    # If parted is used, see start/end definition in 'man parted' in detail.
    if [ -n "$OPT_ROOTFS_SIZE" ] ; then
        DI_ROOTFS_SIZE=$OPT_ROOTFS_SIZE
        DI_ROOTFS_SIZE_BYTES=$(sys_parse_option_size $DI_ROOTFS_SIZE)
    fi

    # Check whether we need to format the device
    case $OPT_FORMAT in
        vfat+ext3) DI_FORMAT=$OPT_FORMAT && sys_check_commands $COMMANDS_FORMAT1 ;;
        vfat+ext4) DI_FORMAT=$OPT_FORMAT && sys_check_commands $COMMANDS_FORMAT2 ;;
        *)         [ -n "$OPT_FORMAT" ]  && EXIT ERROR ERR_PARAMETER "Invalid format type!" ;;
    esac

    if [ -n "$DI_FORMAT" ] ; then
        if [ -n "$OPT_VFAT_NODE" -o -n "$OPT_ROOTFS_NODE" ] ; then
            SHOW WARNING "Ignore -V/-R option as you want to format device."
        fi
    else
        DI_VFAT_NODE=$OPT_VFAT_NODE
        DI_ROOTFS_NODE=$OPT_ROOTFS_NODE

        if [ -n "$OPT_VFAT_SIZE" -o -n "$OPT_ROOTFS_SIZE" ] ; then
            SHOW WARNING "Ignore -s/-p options when you don't format device."
        fi
    fi
}

di_check_lvm_options() {
    DI_LVM=${OPT_LVM:-$DI_DEFAULT_LVM}

    if [ $DI_LVM = 1 ] ; then
        local lvm_size=$DI_DEFAULT_LVM_SIZE
        local tmp_vgname=$DI_DEFAULT_LVM_VG
        local tmp_lvname=$DI_DEFAULT_LVM_LV
        local lvm_rootfs vg

        # Install them in ubuntu use "sudo apt-get install lvm2"
        sys_check_commands $COMMANDS_LVM1
        sys_check_commands $COMMANDS_LVM2

        [ -n "$DI_IMAGE_NAME" ] && lvm_size="100%VG"
        DI_LVM_SIZE=${OPT_LVM_SIZE:-$lvm_size}
        [ -n "$DI_FORMAT" ] && SHOW INFO "Rootfs partition LVM size: $DI_LVM_SIZE"

        sys_generate_lvm_names tmp_vgname tmp_lvname
        DI_LVM_VG_NAME=${OPT_LVM_VGNAME:-$tmp_vgname}
        DI_LVM_LV_NAME=${OPT_LVM_LVNAME:-$tmp_lvname}

        lvm_rootfs=$(grep -o "/dev/mapper/[^ ]*" /proc/cmdline)
        lvm_rootfs=$(basename "$lvm_rootfs")
        vg=$(echo $lvm_rootfs | awk -F'-' '{print $1}')
        SHOW DEBUG "Current rootfs logical volume: $lvm_rootfs"
        if [ "$vg" == "$DI_LVM_VG_NAME" ] ; then
            EXIT ERROR ERR_PARAMETER "Volume group name $DI_LVM_VG_NAME is in use."
        fi
    fi

    DI_EXTEND=${OPT_EXTEND:-0}
    if [ $DI_LVM != 1 -o -z "$DI_IMAGE_NAME" ] ;then
        if [ $DI_EXTEND = 1 ] ; then
            EXIT ERROR ERR_PARAMETER "Only support -E option when deploy to image with LVM partition"
        fi
    fi
}

di_check_boot_options() {
    sys_validated_file "$OPT_NEW_KERNEL" && DI_NEW_KERNEL="$OPT_NEW_KERNEL"
    sys_validated_file "$OPT_NEW_GRUBCONF" && DI_NEW_GRUBCONF="$OPT_NEW_GRUBCONF"

    # Check target rootfs device name (-D option)
    which blkid >/dev/null 2>&1 && DI_HAS_UUID=1 || DI_HAS_UUID=0

    case $OPT_BOOT_ROOTFS in
        UUID)
            if [ $DI_HAS_UUID = 0 ] ; then
                SHOW ERROR "You specified boot root device to UUID via -D option,"
                EXIT ERROR ERR_PARAMETER "But cannot find blkid commands to get device UUID."
            fi
            if [ $DI_LVM = 1 ] ; then
                EXIT ERROR ERR_PARAMETER "Cannot set boot root device to UUID when LVM enabled."
            fi
            DI_BOOT_ROOTFS="UUID"
            ;;
        LVM)
            DI_BOOT_ROOTFS="LVM"
            ;;
        LABEL)
            DI_BOOT_ROOTFS="LABEL=$DI_DEFAULT_ROOTFS_LABEL"
            ;;
        /dev/*|LVM=/dev/mapper/*|UUID=*|LABEL=*)
            DI_BOOT_ROOTFS=$OPT_BOOT_ROOTFS
            ;;
        "")
            if [ $DI_LVM = 1 ] ; then
                DI_BOOT_ROOTFS="LVM"
            elif [ $DI_HAS_UUID = 1 ] ; then
                DI_BOOT_ROOTFS="UUID"
            else
                SHOW WARNING "Command blkid is not found, root device in grub.conf"
                SHOW WARNING "will not use UUID, please confirm the default value"
                SHOW WARNING "in it will work right."
                DI_BOOT_ROOTFS=""
            fi
            ;;
        *)
            EXIT ERROR ERR_PARAMETER "Invalid boot device (-D option): \"$OPT_BOOT_ROOTFS\""
            ;;
    esac

    # Only when user special -D option, but not the case that the tool set it automatically
    if [ -n "$OPT_BOOT_ROOTFS" ] ; then
        DI_NEED_UPDATE_GRUB_CONF=1
    fi

    DI_UEFI=$OPT_UEFI
    if [ -n "$DI_FORMAT" -a "$DI_UEFI" != "1" -a -n "$DI_ROOTFS_TARBALL" ] ; then
        SHOW WARNING "Will install UEFI bootloader by default"
        DI_UEFI=1
    fi

    # Add title suffix in grub.conf (-K option)
    DI_BOOT_TITLE_SUFFIX=$OPT_BOOT_TITLE

    # Replace kernel image in grub.conf (-K option)
    DI_BOOT_KERNEL=$OPT_BOOT_KERNEL

    # Just check rootfs tarball name simply, and provide warning early.
    if echo $DI_ROOTFS_TARBALL | grep -q "srm.tar.bz2" ; then
        if [ -n "$DI_BOOT_KERNEL" ] || [ -n "$DI_BOOT_TITLE_SUFFIX" ] ; then
            SHOW WARNING "Invalid -K/-T option or value for signed rootfs!"
            if sys_get_user_answer "Ignore -K/-T option and continue" ; then
                DI_BOOT_KERNEL=""
                DI_BOOT_TITLE_SUFFIX=""
            else
                EXIT INFO "Canceled because of invalid -K/-T option or value"
            fi
        fi
    fi

    # As far as now, grub.conf for LVM partition don't support other values
    if [ $DI_LVM = 1 -a -n "$DI_BOOT_ROOTFS" -a "$DI_BOOT_ROOTFS" != "LVM" ] ; then
        if ! echo $DI_BOOT_ROOTFS | grep -q "LVM=" ; then
            SHOW ERROR "Invalid boot device specified by -D option"
            EXIT ERROR ERR_PARAMETER "Only support LVM=xxx boot device in LVM grub.conf"
        fi
    fi

    if [ -n "$DI_BOOT_KERNEL" -o -n "$DI_BOOT_TITLE_SUFFIX" ] ; then
        DI_NEED_UPDATE_GRUB_CONF=1
    fi
}

di_check_size_options() {
    local total_size vfat_size rootfs_size left_size
    local image_dir required_size lvm_size lvm_rate
    local current_vfat_used current_vfat_left
    local current_rootfs_used current_rootfs_left

    # Don't check rootfs size when -d option is not specified.
    [ -z "$DI_DEVICE" -a -z "$DI_IMAGE_NAME" ] && return 0

    # Get image file total size
    if [ -n "$DI_IMAGE_NAME" ] ; then
        if [ -f "$DI_IMAGE_NAME" ]; then
            [ -n "$OPT_IMGSIZE" ] && SHOW WARNING "Ignore the -S/--image-size option."
            DI_IMAGE_SIZE=$(sys_get_file_size $DI_IMAGE_NAME)
            SHOW DEBUG "Existent USB image file size: $DI_IMAGE_SIZE"
        else
            if [ -n "$OPT_IMGSIZE" ] ; then
                DI_IMAGE_SIZE=$OPT_IMGSIZE
            else
                di_calculate_real_rootfs_size
                if [ -n "$DI_ROOTFS_MINI_SIZE_BYTES" ] ; then
                    # vfat + rootfs_mini(already DI_DEFAULT_ROOTFS_FREE_SIZE reserved)
                    DI_IMAGE_SIZE="$(($DI_VFAT_SIZE_BYTES + $DI_ROOTFS_MINI_SIZE_BYTES))"
                    DI_IMAGE_SIZE="$((($DI_IMAGE_SIZE + 1024*1024 -1)/(1024*1024)))M"
                    SHOW DEBUG "Set calculated image file size: $DI_IMAGE_SIZE"
                else
                    DI_IMAGE_SIZE=$DI_DEFAULT_USBIMG_SIZE
                    SHOW DEBUG "Set default image file size: $DI_IMAGE_SIZE"
                fi
            fi
        fi
        DI_IMAGE_SIZE_BYTES=$(sys_parse_option_size $DI_IMAGE_SIZE)
    fi

    # Don't check other sizes when don't need to format device
    [ -z "$DI_FORMAT" ] && return 0

    # Get current vfat partition size for saving it.
    if [ "$DI_SYNC_CURRENT" == "1" ] && mount | grep -q "$DI_VFAT_AUTOMOUNT"; then
        sys_current_system_size "$DI_VFAT_AUTOMOUNT" current_vfat_used current_vfat_left
        SHOW INFO "Current VFAT used: $(($current_vfat_used / (1024*1024)))M"
        SHOW INFO "Current VFAT left: $(($current_vfat_left / (1024*1024)))M"
    fi

    # Get current system used and left size for check image file size later
    # And also for the size to save current system
    if [ "$DI_SYNC_CURRENT" == "1" -o -n "$DI_IMAGE_NAME" ] ; then
        image_dir=$(dirname "$DI_IMAGE_NAME")
        sys_current_system_size $image_dir current_rootfs_used current_rootfs_left
        SHOW INFO "Current rootfs used: $(($current_rootfs_used / (1024*1024)))M"
        SHOW INFO "Current rootfs left: $(($current_rootfs_left / (1024*1024)))M"
    fi

    if [ "$DI_SYNC_CURRENT" == "1" ] ; then
        # Must enough size to save current vfat to vfat partition
        if [ -n "$current_vfat_used" ] ; then
            required_size=$(($current_vfat_used + 64*1024*1024))
            if [ $required_size -gt $DI_VFAT_SIZE_BYTES ] ; then
                DI_VFAT_SIZE="$(($required_size / (1024*1024)))M"
                DI_VFAT_SIZE_BYTES=$(sys_parse_option_size $DI_VFAT_SIZE)
            fi
        fi
        # Must enough size to save current rootfs to rootfs partition
        if [ -n "$current_rootfs_used" ] ; then
            if [ -f "$DI_IMAGE_NAME" -a -n "$DI_IMAGE_SIZE_BYTES" ] ; then
                # Does not include the image file size which exists and is in use
                current_rootfs_used=$((current_rootfs_used - $DI_IMAGE_SIZE_BYTES))
            fi

            required_size=$(($current_rootfs_used / 2))
            if [ $required_size -lt $DI_DEFAULT_ROOTFS_FREE_SIZE ] ; then
                DI_ROOTFS_MINI_SIZE_BYTES=$(($current_rootfs_used + $DI_DEFAULT_ROOTFS_FREE_SIZE))
            else
                DI_ROOTFS_MINI_SIZE_BYTES=$(($current_rootfs_used + $required_size))
            fi

            if [ -z "$OPT_IMGSIZE" -a -n "$DI_IMAGE_SIZE_BYTES" -a ! -e "$DI_IMAGE_NAME" ] ; then
                required_size=$(($DI_ROOTFS_MINI_SIZE_BYTES + $DI_VFAT_SIZE_BYTES))
                if [ $required_size -gt $DI_IMAGE_SIZE_BYTES ] ; then
                    DI_IMAGE_SIZE_BYTES=$required_size
                    DI_IMAGE_SIZE="$(($DI_IMAGE_SIZE_BYTES / (1024*1024)))M"
                fi
            fi
        fi
    fi

    # Must enough size to save image file and some runtime caches
    if [ -n "$DI_IMAGE_NAME" -a ! -e "$DI_IMAGE_NAME" ] ; then
        if [ -n "$current_rootfs_left" -a -n "$DI_IMAGE_SIZE_BYTES" ] ; then
            required_size=$(($DI_IMAGE_SIZE_BYTES + 128*1024*1024))
            if [ "$required_size" -gt "$current_rootfs_left" ] ; then
                SHOW ERROR "No enough size left to create new image file"
                EXIT ERROR ERR_NO_ENOUGH_SIZE "Required size: $DI_IMAGE_SIZE + 128M(reserved)"
            fi
        fi
    fi

    # check the rootfs partition size
    total_size=${DI_IMAGE_SIZE_BYTES:-$DI_DEVICE_SIZE_BYTES}
    [ -z "$total_size" ] && return 0

    vfat_size=$DI_VFAT_SIZE_BYTES
    left_size=$(($total_size - $vfat_size))
    rootfs_size=$DI_ROOTFS_SIZE_BYTES

    if [ -z "$rootfs_size" ] ; then
        rootfs_size=$left_size
    elif [ $rootfs_size -gt $left_size ] ; then
        SHOW ERROR "Specified rootfs size $(($rootfs_size / (1024*1024)))M is too larger"
        EXIT ERROR ERR_PARAMETER "Only $(($left_size / (1024*1024)))M left for rootfs in the device"
    fi

    if [ "$DI_LVM" == "1" ] ; then
        lvm_size=$DI_LVM_SIZE
        if echo $lvm_size | grep -q "[0-9]*[0-9][kKmMgG]" ; then
            lvm_size=$(sys_parse_option_size $lvm_size)
            if [ $lvm_size -gt $rootfs_size ] ; then
                SHOW ERROR "LVM size is larger than rootfs partition size!"
                EXIT ERROR ERR_PARAMETER "Rootfs partition size is $(($rootfs_size / (1024*1024)))M"
            fi
        elif echo $lvm_size | grep -q "[0-9]*%FREE" ; then
            lvm_rate=${lvm_size%%\%FREE}
            lvm_size=$((($rootfs_size * $lvm_rate) / 100))
        elif echo $lvm_size | grep -q "[0-9]*%VG" ; then
            lvm_rate=${lvm_size%%\%VG}
            lvm_size=$((($rootfs_size * $lvm_rate) / 100))
        fi
        rootfs_size=$lvm_size
    fi

    # Show the partition size to be created
    SHOW INFO "Total Device/IMG size: $(($total_size / (1024*1024)))M"
    SHOW INFO "  VFAT partition size: $DI_VFAT_SIZE"
    SHOW INFO "Rootfs partition size: $(($rootfs_size / (1024*1024)))M"

    if [ "$rootfs_size" -lt "$DI_ROOTFS_MINI_SIZE_BYTES" ] ; then
        SHOW ERROR "Rootfs partition size $(($rootfs_size / (1024*1024)))M is too small."
        SHOW ERROR "$(($DI_ROOTFS_MINI_SIZE_BYTES / (1024*1024)))M bytes or more are requried."
        if [ -n "$DI_IMAGE_NAME" ] ; then
            SHOW ERROR "Please specify larger size to image file by -S option"
            EXIT ERROR ERR_DEVICE_TOO_SMALL "or change the -p/-L option to use more free space."
        else
            SHOW ERROR "Please try another device or change the -p option"
            EXIT ERROR ERR_DEVICE_TOO_SMALL "or -L option(if LVM enabled) to use more free space."
        fi
    fi
}

di_check_command_options() {
    # Check all command options
    di_get_single_command DI_CMD_MOUNT $OPT_CMD_MOUNT
    di_get_single_command DI_CMD_UNMOUNT $OPT_CMD_UNMOUNT

    return 0
}

di_check_all_options() {
    # change log level and whether interaction at first.
    DI_LOG_LEVEL=${OPT_LOG_LEVEL:-$DI_DEFAULT_LOG_LEVEL}
    DI_NO_INTERACTION=${OPT_DEFAULT:-$DI_DEFAULT_NO_INTERACTION}

    sys_is_root_user && DI_ISROOT=1
    [ $DI_ISROOT = 1 ] && sys_change_kernel_log_level $DI_LOG_LEVEL

    sys_check_commands $COMMANDS_NORMAL1
    sys_check_commands $COMMANDS_NORMAL2

    # In target, file is used, on hosts, objdump is used
    sys_is_wr_target || sys_check_commands $COMMANDS_BINUTILS

    # Don't adjust the order as there are some dependences.
    SHOW STEP "Checking all options (or default values) ..."
    di_check_rootfs_options
    di_check_device_options
    di_check_quark_options
    di_check_format_options
    di_check_lvm_options
    di_check_boot_options
    di_check_size_options
    di_check_command_options

    # Show all global options after validation
    SHOW DEBUG "----------------------------------------------------"
    SHOW DEBUG "Output           (-d): ${DI_DEVICE}${DI_IMAGE_NAME}"
    SHOW DEBUG "Rootfs Tarball   (-f): ${DI_ROOTFS_TARBALL}"
    SHOW DEBUG "Sync current fs  (-C): ${DI_SYNC_CURRENT}"
    SHOW DEBUG "Partitions Format(-t): ${DI_FORMAT}"
    SHOW DEBUG "Image Size       (-S): ${DI_IMAGE_SIZE}"
    SHOW DEBUG "VFAT Size        (-s): ${DI_VFAT_SIZE}"
    SHOW DEBUG "Rootfs Size      (-p): ${DI_ROOTFS_SIZE}"
    SHOW DEBUG "Kernel bzImage   (-I): ${DI_NEW_KERNEL}"
    SHOW DEBUG "Grub conf file   (-G): ${DI_NEW_GRUBCONF}"
    SHOW DEBUG "Boot Title Suffix(-T): ${DI_BOOT_TITLE_SUFFIX}"
    SHOW DEBUG "Boot Kernel Image(-K): ${DI_BOOT_KERNEL}"
    SHOW DEBUG "Boot Root Device (-D): ${DI_BOOT_ROOTFS}"
    SHOW DEBUG "VFAT Partition   (-V): ${DI_VFAT_NODE}"
    SHOW DEBUG "Rootfs Partition (-R): ${DI_ROOTFS_NODE}"
    SHOW DEBUG "Use LVM          (-l): ${DI_LVM}"
    SHOW DEBUG "LVM Size         (-L): ${DI_LVM_SIZE}"
    SHOW DEBUG "UEFI             (-u): ${DI_UEFI}"
    SHOW DEBUG "Factory Reset    (-F): ${DI_SFR}"
    SHOW DEBUG "Capsule Update   (-c): ${DI_CAPSULE_UPDATE}"
    SHOW DEBUG "Read Only        (-r): ${DI_READONLY}"
    SHOW DEBUG "No Interaction   (-Y): ${DI_NO_INTERACTION}"
    SHOW DEBUG "----------------------------------------------------"
    SHOW DEBUG "Special Commands:"
    SHOW DEBUG "            (--mount): ${DI_CMD_MOUNT}"
    SHOW DEBUG "          (--unmount): ${DI_CMD_UNMOUNT}"
    SHOW DEBUG "----------------------------------------------------"
    return 0
}

di_show_welcome() {
    [ $DI_LOG_LEVEL -eq 0 ] && return 0
    cat <<EOF
================================================================================
                         Deploy Tool V${DI_VERSION} @${DI_DATE}
================================================================================
EOF
}

di_show_help() {
    cat <<EOF
Deploy kernel image and rootfs to the USB, SD card or image file.

Usage: $DI_MYNAME options...

Options
 -d/--out device/image-file
    The device or file name that the deployed image is written to.
    It can be a device node (e.g. /dev/sdb or /dev/mmcblk0) or a
    file (e.g. ./usb.img)
    Note that if a device is used, this is the device as seen from the
    deployment machine (i.e. your dev host), and not the device as seen
    by the target.  e.g. the USB stick on your dev host is /dev/sdb, but
    when the target boots from the stick it will appear as /dev/sda.
    Note also that if file is used, and -t/--format option is not specified,
    -y/--format-default option is specified by default.

 -f/--rootfs rootfs-tarball
    Rootfs tarball, e.g. export/intel-quark-glibc-idp-standard-dist.tar.bz2

 -B/--rootfs-backup 1/0/rootfs-tarball
    Whether need to backup rootfs tarball for factory reset later.
    When deploy to image file, it is '1' by default, when deploy to the
    hardware device, it is '0' by default. You can also specify the backup
    rootfs tarball file name by this option. Otherwise it use the same one
    as -f option if -f option is set.
    Specially, '-B 0' will not impact -F option to save the rootfs tarball.

 -C/--sync-fs
    Save the whole current vfat boot partition and rootfs partition to
    another device or image file. Once it is already saved, it will only
    sync the changes next time.

 -t/--format format-type
    Format the device and create partitions to install rootfs.
      vfat+ext3:  vfat for UEFI boot files and ext3 partition for rootfs.
      vfat+ext4:  vfat for UEFI boot files and ext4 partition for rootfs.

 -y/--format-default
    The same as "-t vfat+ext3"

 -S/--image-size size
    Size of image when -d option is set to an image file. If -d specifies a
    device then this option is ignored. Suffixes of K, M, and G are supported.
    Note that the file must be big enough for the partition table and the
    partitions selected by the -t parameter. e.g. -s 4G

 -s/--vfat-size vfat-partition-size
    Specify the size of vfat partion, in bytes. Suffixes of K, M, and G
    are supported. The default size is 256M.
    e.g. -p 512M

 -p/--rootfs-size rootfs-partition-size
    Specify the size of rootfs partition, in bytes. Suffixes of K, M, and G
    are supported.  The default is to use all storage space in the partition.
    e.g. -p 200M, or -p 3G

 -I/--new-kernel bzImage-name
    Replace the kernel bzImage in rootfs with the given one.

 -G/--new_grubconf grub-conf-file
    Replace the grub.conf file for booting with the give one.

 -T/--boot-title title-suffix
    The suffix to specify the default boot entry title, it will be appended
    to the end of default title "Wind River Intelligent Device Platform"
    e.g. "USB" as indentification for booting from USB disk.

 -K/--boot-kernel kernel-name
    Have grub boot the kernel from this partition, path, and file.
    e.g. -K (hd0,1)/boot/bzImage

 -D/--boot-root target-rootfs-device
    Set the device the kernel uses for its rootfs. (default: UUID)
    e.g. : /dev/mmcblk0p2, /dev/sda2, UUID(or UUID=XXX), LABEL(or LABEL=XXX),
    LVM(or LVM=/dev/mapper/xxx-xxx) etc

 -V/--vfat-partition  vfat-partition
    Set the vfat partition used in the tool to install bzImage,grub stuffs.
    e.g. -V /dev/sdb1

 -R/--rootfs-partition  rootfs-partition
    Set the rootfs partition used in the tool to install root filesystem.
    e.g. -R /dev/sdb2

 -l/--lvm 1/0
    Whether to create a LVM partition for the physical rootfs partition.
    1: Create LVM partition. (This is what by default)
    0: Create normal physical partition.

 -L/--lvm-size logical-volume-size
    Set the default size of the logical volume based on the size of its
    volume group N%VG or N%FREE, N=1~100, or as an absolute size N[KMGTPE],
    N<rootfs size as set by the -p option(e.g. 2G). The default is $DI_DEFAULT_LVM_SIZE,
    which implies reserving just over half the space for LVM snapshots.
    For further detail see "man lvcreate".

 -u/--install-uefi
    Deploy uefi files in first vfat partition. Assume that there are grub-efi
    files in /boot/grub-efi/ in the rootfs partition.

 -E/--extend
    Need to extend the partition to use all the space in the target device
    when first boot after dd the image file into a larger size device.

 -F/--factory-reset
    Need to backup rootfs tarball and kernel image for later use do do
    soft factory reset.

 -c/--capsule-update
    Perform a BIOS capsule update *ON TARGET* to update on board Flash.
    As far as know, only support this in QUARK boards.

 -r/--read-only
    Make the rootfs is readonly.

 -Y/--default
    Use all default values without any user interaction。Also try best
    to go on finishing the deployment without asking user any question.

 -e/--ems rootfs-tarball
    A convenience option for EMS deployments.  This option is a short form for
    entering "-f rootfs-tarball -s $DI_DEFAULT_SFR_VFAT_SIZE -S $DI_DEFAULT_LVMIMG_SIZE -y -u -F".
    If any of the -f, -s, -S, -t options are also specified behind it,
    their values will overwrite the values from the -e option. Of course,
    their values will be overwritten by values from -e option if before -e option.
    e.g. -e export/intel-baytrail-64-idp-idp-dist.tar.bz2

 -v/--log-level level
    Set the debug levle. More large the level is, more extra debug messages
    will be produced from the tool. By default, only allow to produce error,
    warning and info messages, do not produce trace and debug message. e.g.
    '-v 3': show all error level messages only.
    '-v 4': show all error and warning message.
    '-v 5': show all above messages and work flow steps messages.
    '-v 6': show all above messages and detail information messages.
    '-v 7': show all above messages and all other debug messages.
    '-v 8': save all messages into $DI_LOG_FILE at the same time.
    '-v 9': show all message, save to file, and with time stamp in messages.
    '-v 0': quiet mode, do not show any message to console or log file.

 --lockdown <lockdown options>
    Specify this option to do security lockdown in the device rootfs.
    See also '$DI_LOCKDOWN_CMD --help' in detail about the lockdown options.
    Note: Must put this option together with all other lockdown options
    at the end of command line, and -r option to lockdown is by default.

 --reset-media
    A convenientce option for reset media. This option is a short form for
    entenring "-f $DI_SFR_BACKUP_ROOTFS -y -u".
    It likes -e option, also can work together with other options and -e option.
    Use case:  $DI_MYNAME --reset-media -d /dev/sda

 -h/--help
    Show this help menu.

 All short options: "d:f:e:B:Ct:yS:s:p:I:G:T:K:D:V:R:l:L:uEFcrYv:h"

EOF
}

di_parse_all_options() {
    local arg

    if [ $# -eq 0 ] || [ $# -eq 1 -a "$1" == "-Y" ] ; then
        SHOW INFO "Please run this tool with some necessary options"
        SHOW INFO "Please try to run '$DI_MYNAME -h' for more help."
        di_simple_exit 0
    fi

    OPTTEST() {
        if [ -z "$1" ] || echo "$1" | grep -q "^-" ; then
            SHOW ERROR "No value for option $arg"
            EXIT ERROR ERR_PARAMETER "Please try -h/--help for help in detail."
        fi
    }
    OPTERROR() {
        if [ -n "$1" ] ; then
            SHOW ERROR "Not supported option $1"
            EXIT ERROR ERR_PARAMETER "Please try -h/--help for help in detail."
        fi
    }
    while [ $# -gt 0 ]; do
        arg=$1
        case $arg in
            -d|--out)            shift && OPTTEST $1 && OPT_OUTPUT="$1" ;;
            -f|--rootfs)         shift && OPTTEST $1 && OPT_ROOTFS="$1" ;;
            -B|--rootfs-backup)  shift && OPTTEST $1 && OPT_ROOTFS_BACKUP="$1" ;;
            -t|--format)         shift && OPTTEST $1 && OPT_FORMAT="$1" ;;
            -S|--image-size)     shift && OPTTEST $1 && OPT_IMGSIZE="$1" ;;
            -s|--vfat-size)      shift && OPTTEST $1 && OPT_VFAT_SIZE="$1" ;;
            -p|--rootfs-size)    shift && OPTTEST $1 && OPT_ROOTFS_SIZE="$1" ;;
            -I|--new-kernel)     shift && OPTTEST $1 && OPT_NEW_KERNEL="$1" ;;
            -G|--new_grubconf)   shift && OPTTEST $1 && OPT_NEW_GRUBCONF="$1" ;;
            -T|--boot_title)     shift && OPTTEST $1 && OPT_BOOT_TITLE="$1" ;;
            -K|--boot-kernel)    shift && OPTTEST $1 && OPT_BOOT_KERNEL="$1" ;;
            -D|--boot-root)      shift && OPTTEST $1 && OPT_BOOT_ROOTFS="$1" ;;
            -V|--vfat-node)      shift && OPTTEST $1 && OPT_VFAT_NODE="$1" ;;
            -R|--rootfs-node)    shift && OPTTEST $1 && OPT_ROOTFS_NODE="$1" ;;
            -l|--lvm)            shift && OPTTEST $1 && OPT_LVM="$1" ;;
            -L|--lvm-size)       shift && OPTTEST $1 && OPT_LVM_SIZE="$1" ;;
            -v|--log-level)      shift && OPTTEST $1 && OPT_LOG_LEVEL="$1" ;;
            -y|--format-default) OPT_FORMAT=$DI_DEFAULT_FORMAT ;;
            -u|--install-uefi)   OPT_UEFI=1 ;;
            -E|--extend)         OPT_EXTEND=1 ;;
            -F|--factory-reset)  OPT_SFR=1 ;;
            -c|--capsule-update) OPT_CAPSULE_UPDATE=1 ;;
            -r|--readonly)       OPT_READONLY=1 ;;
            -Y|--default)        OPT_DEFAULT=1 ;;
            -C|--sync-fs)        OPT_SYNC_CURRENT=1 ;;
            --vg-name)           shift && OPTTEST $1 && OPT_LVM_VGNAME="$1" ;;
            --lv-name)           shift && OPTTEST $1 && OPT_LVM_LVNAME="$1" ;;
            --mount)             OPT_CMD_MOUNT=1 && OPT_NOT_REMOVE_IMAGE=1 ;;
            --unmount)           OPT_CMD_UNMOUNT=1 && OPT_NOT_REMOVE_IMAGE=1;;
            # all options behind --lockdown are pass to lock down tool
            # so must put them together at the end.
            # Note space in "$@ " is to make sure it will not be empty.
            --lockdown)          shift && OPT_LOCKDOWN="$@ " && break ;;
            # Add -e option to be more convenient for EMS deployment
            -e|--ems)
                shift && OPTTEST $1 && OPT_ROOTFS="$1"
                OPT_VFAT_SIZE=$DI_DEFAULT_SFR_VFAT_SIZE
                OPT_IMGSIZE=$DI_DEFAULT_LVMIMG_SIZE
                OPT_FORMAT=$DI_DEFAULT_FORMAT
                OPT_UEFI=1
                OPT_SFR=1
                ;;
            # Add --reset-media option to do reset_media more easier.
            --reset-media)
                OPT_ROOTFS=$DI_SFR_BACKUP_ROOTFS
                OPT_FORMAT=$DI_DEFAULT_FORMAT
                OPT_UEFI=1
                ;;
            -h|--help)           di_show_help  ; di_simple_exit 0 ;;
            *)                   OPTERROR $arg ; di_simple_exit 1 ;;
        esac
        shift
    done
}

di_stop_runing_twice() {
    local last_pid

    if [ -e $DI_TMPDIR/$DI_RUNNING_FILE ] ; then
        last_pid="$(cat $DI_TMPDIR/$DI_RUNNING_FILE)"
        if grep -q "$DI_MYNAME" /proc/${last_pid}/cmdline 2>/dev/null ; then
            SHOW ERROR "Another deploy task is running now"
            SHOW ERROR "Please try again after it is done!"
            exit $ERR_IS_RUNNING
        else
            SHOW WARNING "The last deployment exited in error without clean"
            SHOW WARNING "Trying to do some necessary clean things firstly!"
            di_remove_temporary_things
        fi
    fi

    [ -d "$DI_TMPDIR" ] || mkdir -p $DI_TMPDIR
    echo -ne "$$" > $DI_TMPDIR/$DI_RUNNING_FILE
}

di_pre_parse_options() {
    local level

    # Get debug log level as early as possible
    level=$(echo "$@" | grep -o -- "-v [0-9]*" | head -n1 | awk '{print $2}')
    [ -n "$level" ] && DI_LOG_LEVEL=$level

    # Always show welcome title except that log level is 0
    di_show_welcome

    # Allow "deploytool -h/--help" even another deploytool task is running
    if [ "$1" == "-h" -o "$1" == "--help" ] ; then di_show_help ; exit 0 ; fi
}

#===============================================================================
# Main Start
#===============================================================================
trap "EXIT INFO Canceled" SIGINT

di_pre_parse_options $@
di_stop_runing_twice
di_parse_all_options $@
di_check_all_options
di_run_all_actions
di_exit_and_clean_up

