#!/bin/bash

set -e

this_dir="$( cd "$( dirname "${BASH_SOURCE[0]}"  )" >/dev/null 2>&1 && pwd )"
occlum_dir="$( cd "$( dirname "$this_dir/../../../"  )" >/dev/null 2>&1 && pwd )"
build_makefile=$occlum_dir/build/bin/occlum_build.mk

if [[ "$occlum_dir" == "/opt/occlum" ]]; then
    version_header=$occlum_dir/include/occlum_version.h
    occlum_sgx_env=$occlum_dir/sgxsdk-tools/environment
else
    version_header=$occlum_dir/src/pal/include/occlum_version.h
    occlum_sgx_env=$occlum_dir/etc/environment
fi

# For deployment environment, version header file may not exist
if [ -f "$version_header" ]; then
    major_ver=`grep '\#define OCCLUM_MAJOR_VERSION' $version_header |  awk '{print $3}'`
    minor_ver=`grep '\#define OCCLUM_MINOR_VERSION' $version_header |  awk '{print $3}'`
    patch_ver=`grep '\#define OCCLUM_PATCH_VERSION' $version_header |  awk '{print $3}'`
    occlum_version="$major_ver.$minor_ver.$patch_ver"
fi

instance_dir=`pwd`

status_file=$instance_dir/.__occlum_status

# For deployment environment, env for sgx-sdk may not exist
if [ -f "$occlum_sgx_env" ]; then
    source $occlum_sgx_env
    SGX_GDB="$SGX_SDK/bin/sgx-gdb"
    if [[ -n $SGX_MODE && "$SGX_MODE" == "HYPER" ]]; then
        ENCLAVE_SIGN_TOOL="$SGX_SDK/bin/x64/sgx_sign_hyper"
    else
        ENCLAVE_SIGN_TOOL="$SGX_SDK/bin/x64/sgx_sign"
    fi
    ENCLAVE_SIGN_KEY="$occlum_dir/etc/template/Enclave.pem"
fi

get_enclave_debuggable_flag() {
    jq '.metadata.debuggable' $instance_dir/Occlum.json
}

get_enclave_enable_kss_flag() {
    jq '.metadata.enable_kss' $instance_dir/Occlum.json
}

exit_error() {
    echo "Error: $@" >&2
    exit 1
}

report_arg_error() {
    echo $1 >&2
    echo ""
    cat <<EOF
Usage:
    occlum new <path> [--init-ra <grpc_ratls/aecs>]
        Create a new directory at <path> and initialize as the Occlum instance.
        If flag --init-ra specifies, generate initfs with RA KMS client function.

    occlum init [--init-ra <grpc_ratls/aecs>]
        Initialize a directory as the Occlum instance.
        If flag --init-ra specifies, generate initfs with RA KMS client function.

    occlum build [--sign-key <key_path>] [--sign-tool <tool_path>] [--image-key <key_path>] [--buildin-image-key] [-f/--force] [--enable-edmm <Y/N>]
        Build and sign an Occlum SGX enclave (.so) and generate its associated secure
        FS image according to the user-provided image directory and Occlum.json config file.
        The whole building process is incremental: the building artifacts are built only
        when needed.
        To force rebuilding all artifacts, give the [-f/--force] flag.

    occlum run <program_name> <program_args>
        Run the user program inside an SGX enclave.

    occlum package [<package_name>.tar.gz]
        Generate a minimal, self-contained package (.tar.gz) for the Occlum instance.
        The resulting package can then be copied to a deployment environment and unpacked
        as a runnable Occlum instance.
        All runtime dependencies required by the Occlum instance---except Intel SGX driver,
        enable_rdfsbase kernel module, and Intel SGX PSW---are included in the package.
        If package_name is not specified, the directory name of Occlum instance will be used.
        In default only HW release mode package is supported. Debug or simulation mode package
        could be supported by adding "--debug" flag.

    occlum gdb <program_name> <program_args>
        Debug the program running inside an SGX enclave with GDB.

    occlum mount [--sign-key <key_path>] [--sign-tool <tool_path>] [--image-key <key_path>] <path>
        Mount the secure FS image of the Occlum instance as a Linux FS at an existing <path>.
        This makes it easy to access and manipulate Occlum's secure FS for debug purpose.

    occlum gen-image-key <key_path>
        Generate a file consists of a randomly generated 128-bit key for encryption of the FS image.

    occlum print mrsigner|mrenclave
        Print Occlum instance's mrsigner, mrenclave.
EOF
}

check_has_init() {
    if [ ! -f "$status_file"  ]; then
        echo "Error: the current working directory is not initialized as an Occlum instance. Need to run \"occlum init\" first."
        exit 1
    fi
}

check_has_built() {
    check_has_init

    if [ ! -d "$instance_dir/run/mount/__ROOT"  ]; then
        echo "Error: the Occlum image and enclave are not built yet. Need to run \"occlum build\" first."
        exit 1
    fi
}

check_has_run() {
    if pgrep --full "$instance_dir/build/bin/occlum-run" > /dev/null ; then
        echo "Error: the process of current Occlum instance is running. Need to wait for the process to finish or kill it first."
        exit 1
    fi
}

check_has_start() {
    if pgrep --full "build/bin/occlum_exec_server -d $instance_dir" > /dev/null ; then
        echo "Error: the server of current Occlum instance has been started."
        exit 1
    fi
}

check_has_not_start() {
    if ! pgrep --full "build/bin/occlum_exec_server -d $instance_dir" > /dev/null ; then
        echo "Error: the server of current Occlum instance has not been started."
        exit 1
    fi
}

check_aesm_service() {
    # Ignore AESM service status for simulation mode
    if [ "$(cat $instance_dir/.sgx_mode 2>/dev/null)" != "HW" ]; then
        return
    fi

    # AESM is not necessary for SGX2
    lscpu | grep sgx_lc > /dev/null 2>&1 || ret=$?
    if [[ $ret -eq 0 ]]; then
        return
    fi

    AESM_SOCKET_FILE=/var/run/aesmd/aesm.socket
    for i in $(seq 1 3); do
        if [ -S "$AESM_SOCKET_FILE" ]; then
            return
        else
            sleep 5
        fi
    done
    echo "Error: AESM service is not started yet. Need to start it first"
    exit 1
}

gen_initfs_aecs()
{
    echo "Generate initfs with AECS client"
    mkdir -p initfs
    mkdir -p initfs/bin
    mkdir -p initfs/lib
    mkdir -p initfs/dev
    mkdir -p initfs/proc
    mkdir -p initfs/etc/kubetee
    mkdir -p initfs/lib64
    mkdir -p initfs/opt/occlum/
    # add default timezone file
    cp /etc/localtime initfs/etc/

    local os_lib_path=${1:-"/usr/lib/x86_64-linux-gnu"}

    # add glibc base libraries
    # just copy from the image
    cp -rf image/opt/occlum/glibc initfs/opt/occlum/
    cp -f image/lib64/ld-linux-x86-64.so.2 initfs/lib64/

    local occlum_glibc_lib=/opt/occlum/glibc/lib
    # add aecs required libs
    cp -t initfs/$occlum_glibc_lib \
        "$occlum_dir"/toolchains/aecs_client/* \
        $os_lib_path/libssl.so.1.1 \
        $os_lib_path/libz.so.1 \
        $os_lib_path/libcrypto.so.1.1 \
        $os_lib_path/libdl.so.2 \
        $occlum_glibc_lib/libnss_files.so.2 \
        $occlum_glibc_lib/libnss_dns.so.2 \
        $occlum_glibc_lib/libresolv.so.2

    # add libprotobuf.so.32 if existed
    if [ -f $os_lib_path/libprotobuf.so.32 ]; then
        cp -t initfs/$occlum_glibc_lib $os_lib_path/libprotobuf.so.32
    fi

    # add template init_ra_conf
    cp "$occlum_dir"/etc/template/init_aecs.json "$instance_dir"/init_ra_conf.json

    cp "$occlum_dir"/build/bin/init_aecs initfs/bin/init
    cp "$occlum_dir"/etc/template/Occlum.json "$instance_dir"/
}

gen_initfs_grpc_ratls()
{
    echo "Generate initfs with GRPC RATLS KMS client"
    mkdir -p initfs
    mkdir -p initfs/bin
    mkdir -p initfs/lib
    mkdir -p initfs/dev
    mkdir -p initfs/proc
    mkdir -p initfs/etc
    # add default timezone file
    cp /etc/localtime initfs/etc/
    # add ssl ca-certificates
    mkdir -p initfs/etc/ssl/certs
    cp /etc/ssl/certs/ca-certificates.crt initfs/etc/ssl/certs

    # add musl
    local occlum_musl_lib=/usr/local/occlum/x86_64-linux-musl/lib
    cp -t initfs/lib \
        /lib/ld-musl-x86_64.so.1 \
        "$occlum_musl_lib/libc.so" \
        "$occlum_musl_lib/libstdc++.so.6" \
        "$occlum_musl_lib/libgcc_s.so.1" \
        "$occlum_musl_lib/libgomp.so.1"

    # add grpc_ratls required libs
    cp -t initfs/lib \
        "$occlum_dir"/toolchains/grpc_ratls/musl/libgrpc_ratls_client.so \
        "$occlum_dir"/toolchains/grpc_ratls/musl/libhw_grpc_proto.so \
        "$occlum_dir"/toolchains/dcap_lib/musl/libocclum_dcap.so.0.1.0 \
        "$occlum_dir"/toolchains/gcc/x86_64-linux-musl/lib/libcjson.so.1

    # add template init_ra_conf
    cp "$occlum_dir"/etc/template/init_grpc_ratls.json "$instance_dir"/init_ra_conf.json

    cp "$occlum_dir"/build/bin/init_grpc_ratls initfs/bin/init
    cp "$occlum_dir"/etc/template/Occlum.json "$instance_dir"/
}

gen_initfs()
{
    mkdir -p initfs
    mkdir -p initfs/bin
    mkdir -p initfs/lib
    mkdir -p initfs/dev
    mkdir -p initfs/proc
    mkdir -p initfs/etc
    # add default /etc/hosts
    echo "127.0.0.1   localhost" > initfs/etc/hosts
    # add default timezone file
    cp /etc/localtime initfs/etc/

    # add musl
    local occlum_musl_lib=/usr/local/occlum/x86_64-linux-musl/lib
    cp -t initfs/lib \
        /lib/ld-musl-x86_64.so.1 \
        "$occlum_musl_lib/libc.so" \
        "$occlum_musl_lib/libstdc++.so.6" \
        "$occlum_musl_lib/libgcc_s.so.1" \
        "$occlum_musl_lib/libgomp.so.1"

    cp "$occlum_dir"/build/bin/init initfs/bin/
    cp "$occlum_dir"/etc/template/Occlum.json "$instance_dir"/
}

cmd_new() {
    if [ -z $1 ]; then
        echo "Error: target directory is not set"
        exit 1
    fi

    dir_path="$1"
    if [[ "$dir_path" != "/"* ]]; then
        dir_path="$instance_dir/$1"
    fi

    if [[ -e "$dir_path" ]]; then
        echo "Error: destination \"$dir_path\" already exists"
        exit 1
    fi

    mkdir -p $dir_path
    instance_dir=$dir_path
    status_file=$instance_dir/.__occlum_status
    cd $dir_path && cmd_init ${@:2:2}
}

cmd_init() {
    if [ -f "$status_file"  ]; then
        echo "Error: the current working directory has been initialized as an Occlum instance"
        exit 1
    fi

    local init_ra=""
    while [ -n "$1" ]; do
        case "$1" in
        --init-ra)     [ -n "$2" ] && init_ra=$2 ; shift 2 || exit_error "Empty init-ra option provided"     ;;
        *)  ;;
        esac
    done

    echo "initialized" > $status_file

    cd "$instance_dir"
    mkdir -p image
    mkdir -p image/bin
    mkdir -p image/lib
    mkdir -p image/lib64
    mkdir -p image/root
    mkdir -p image/host
    mkdir -p image/tmp
    mkdir -p image/dev
    mkdir -p image/proc
    mkdir -p image/etc
    local occlum_glibc_lib=/opt/occlum/glibc/lib
    local occlum_glibc_etc=/opt/occlum/glibc/etc
    if [ -d "$occlum_glibc_lib" ]; then
        mkdir -p "image/$occlum_glibc_lib"
        mkdir -p "image/$occlum_glibc_etc"
    fi
    # add default /etc/hosts
    echo "127.0.0.1   localhost" > image/etc/hosts
    # add default timezone file
    cp /etc/localtime image/etc/

    # add musl
    local occlum_musl_lib=/usr/local/occlum/x86_64-linux-musl/lib
    cp -t image/lib \
        /lib/ld-musl-x86_64.so.1 \
        "$occlum_musl_lib/libc.so" \
        "$occlum_musl_lib/libstdc++.so.6" \
        "$occlum_musl_lib/libgcc_s.so.1" \
        "$occlum_musl_lib/libgomp.so.1"

    # add glibc
    local os_lib_path="/usr/lib/x86_64-linux-gnu" # for ubuntu
    if [ -d "$occlum_glibc_lib" ]; then
        cp -t image/lib64 \
            "$occlum_glibc_lib/ld-linux-x86-64.so.2"
        ln -sf /lib64/ld-linux-x86-64.so.2 "image/$occlum_glibc_lib/ld-linux-x86-64.so.2"
        cp -t "image/$occlum_glibc_lib" \
            "$occlum_glibc_lib/libc.so.6" \
            "$occlum_glibc_lib/libpthread.so.0" \
            "$occlum_glibc_lib/libm.so.6"
        local os_release=`awk -F= '/^NAME/{print $2}' /etc/os-release`
        if [ "$os_release" != "\"Ubuntu\"" ]; then
            os_lib_path="/usr/lib64/"  # for openanolis, alios
        fi

        cp -t "image/$occlum_glibc_lib" \
            "${os_lib_path}/libstdc++.so.6" \
            "${os_lib_path}/libgcc_s.so.1"
        cp -t "image/$occlum_glibc_etc" \
            /etc/localtime
    fi

    if [[ "$init_ra" == "grpc_ratls" ]]; then
        gen_initfs_grpc_ratls
    elif [[ "$init_ra" == "aecs" ]]; then
        gen_initfs_aecs ${os_lib_path}
    else
        gen_initfs
    fi

    chmod 644 "$instance_dir"/Occlum.json

    echo "$instance_dir initialized as an Occlum instance"
}

cmd_build() {
    check_has_init
    pal_lib=libocclum-pal.so
    libos_lib=libocclum-libos.so
    BUILDIN_IMAGE_KEY=false

    while [ -n "$1" ]; do
        case "$1" in
        --sign-key)     [ -n "$2" ] && ENCLAVE_SIGN_KEY=$2 ; shift 2 || exit_error "empty signing key path"     ;;
        --sign-tool)    [ -n "$2" ] && ENCLAVE_SIGN_TOOL=$2 ; shift 2 || exit_error "empty signing tool path"   ;;
        --sgx-mode)     [[ -n "$2" && "$2" != "HW" ]] && export SGX_MODE=$2 ; shift 2 || exit_error "empty sgx mode";;
        --enable-edmm)  [[ -n "$2" && "$2" != "N" ]] && export ENABLE_EDMM=Y ; shift 2 || exit_error "empty edmm mode";;
        --image-key)    [ -n "$2" ] && SECURE_IMAGE_KEY=$2 ; shift 2 || exit_error "empty secure image key path"   ;;
        --buildin-image-key)  BUILDIN_IMAGE_KEY=true ; shift ;;
        --force | -f)   MAKE_OPTION="clean" ; shift ;;
        *) exit_error "Unknown option: $1" ;;
        esac
    done
    [ -e "$ENCLAVE_SIGN_KEY" ] || exit_error "invalid signing key path: $ENCLAVE_SIGN_KEY"
    [ -e "$ENCLAVE_SIGN_TOOL" ] || exit_error "invalid signing tool path: $ENCLAVE_SIGN_TOOL"
    if [ -n "$SECURE_IMAGE_KEY" ]; then
        [ -e "$SECURE_IMAGE_KEY" ] || exit_error "invalid secure image key path: $SECURE_IMAGE_KEY"
    fi
    echo "Enclave sign-tool: $ENCLAVE_SIGN_TOOL"
    echo "Enclave sign-key: $ENCLAVE_SIGN_KEY"
    [ -n "$SECURE_IMAGE_KEY" ] && echo "Image encryption key: $SECURE_IMAGE_KEY"

    if [[ -n $SGX_MODE && "$SGX_MODE" != "HW" ]]; then
        export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$SGX_SDK/sdk_libs
        if [[ "$SGX_MODE" != "HYPER" ]]; then
            pal_lib=libocclum-pal_sim.so
            libos_lib=libocclum-libos_sim.so
        else
            pal_lib=libocclum-pal_hyper.so
            libos_lib=libocclum-libos_hyper.so
        fi
        echo "SGX mode: $SGX_MODE"
        INSTANCE_IS_FOR_EDMM_PLATFORM="NO"
    else
        echo "SGX mode: HW"

         # ENABLE_EDMM is only valid for HW mode and is not enabled by default
        case "$ENABLE_EDMM" in
            "Y" | "YES" | "Yes" | "yes" | "True" | "true" | "1")
                INSTANCE_IS_FOR_EDMM_PLATFORM="YES"
            ;;
            *)
                INSTANCE_IS_FOR_EDMM_PLATFORM="NO"
            ;;
        esac
    fi

    if [[ -f "$instance_dir/init_ra_conf.json" ]]; then
        cp "$instance_dir/init_ra_conf.json" "$instance_dir/initfs/etc/"
    fi

    # If sgx mode is changed, build thoroughly again
    if [[ -n $SGX_MODE && "$SGX_MODE" != "HW" ]]; then
        if [ "$(cat $instance_dir/.sgx_mode 2>/dev/null)" != "$SGX_MODE" ]; then
            MAKE_OPTION="clean"
        fi
    else
        #HW mode
        if [ "$(cat $instance_dir/.sgx_mode 2>/dev/null)" != "HW" ]; then
            MAKE_OPTION="clean"
        fi
    fi

    rm -rf "$instance_dir/run"
    if [[ -n $MAKE_OPTION ]]; then
        occlum_dir=$occlum_dir instance_dir=$instance_dir \
        make -f $build_makefile $MAKE_OPTION
    fi
    occlum_dir=$occlum_dir instance_dir=$instance_dir pal_lib=$pal_lib major_ver=$major_ver \
    occlum_version=$occlum_version libos_lib=$libos_lib INSTANCE_IS_FOR_EDMM_PLATFORM=$INSTANCE_IS_FOR_EDMM_PLATFORM \
    ENCLAVE_SIGN_KEY=$ENCLAVE_SIGN_KEY ENCLAVE_SIGN_TOOL=$ENCLAVE_SIGN_TOOL \
    SECURE_IMAGE_KEY=$SECURE_IMAGE_KEY BUILDIN_IMAGE_KEY=$BUILDIN_IMAGE_KEY \
    make -f $build_makefile --no-builtin-rules

    cd "$instance_dir"
    echo "built" > $status_file

    if [[ -n $SGX_MODE && "$SGX_MODE" != "HW" ]]; then
        echo "$SGX_MODE" > .sgx_mode
    else
        echo "HW" > .sgx_mode
    fi

    mkdir -p "$instance_dir/run/mount/__ROOT"

    mkdir -p "$instance_dir/run/initfs/__ROOT"

    echo "Built the Occlum image and enclave successfully"
}


cmd_run() {
    check_has_built
    check_has_run
    check_aesm_service

    loop=true
    while [ -n "$1" ] && [ "$loop" = "true" ]; do
        case "$1" in
        --config-id)     [ -n "$2" ] && export OCCLUM_CONF_ID_BASE64=$2 ; shift 2 || exit_error "Empty Base64 Encoded Occlum Config ID provided"     ;;
        --config-svn)    [ -n "$2" ] && export OCCLUM_CONF_SVN=$2 ; shift 2 || exit_error "Empty Occlum Config SVN provided"   ;;
        *) loop=false ;;
        esac
    done

    SGX_MODE=$(cat $instance_dir/.sgx_mode)
    if [[ -n $SGX_MODE && "$SGX_MODE" != "HW" ]]; then
        export LD_LIBRARY_PATH="$instance_dir/build/lib:$SGX_SDK/sdk_libs/"
    else
        export LD_LIBRARY_PATH="$instance_dir/build/lib"
    fi

    echo "running" > $status_file

    if [ "`get_enclave_debuggable_flag`" == "false" ]; then
        export OCCLUM_RELEASE_ENCLAVE=1
    fi

    if [ "`get_enclave_enable_kss_flag`" == "true" ]; then
        export OCCLUM_ENABLE_KSS=1
    fi

    RUST_BACKTRACE=1 "$instance_dir/build/bin/occlum-run" "$@"

    echo "built" > $status_file
}

cmd_start() {
    check_has_built
    check_has_start
    check_aesm_service

    loop=true
    while [ -n "$1" ] && [ "$loop" = "true" ]; do
        case "$1" in
        --config-id)     [ -n "$2" ] && export OCCLUM_CONF_ID_BASE64=$2 ; shift 2 || exit_error "Empty Base64 Encoded Occlum Config ID provided"     ;;
        --config-svn)    [ -n "$2" ] && export OCCLUM_CONF_SVN=$2 ; shift 2 || exit_error "Empty Occlum Config SVN provided"   ;;
        *) loop=false ;;
        esac
    done

    SGX_MODE=$(cat $instance_dir/.sgx_mode)
    if [[ -n $SGX_MODE && "$SGX_MODE" != "HW" ]]; then
        export LD_LIBRARY_PATH="$instance_dir/build/lib:$SGX_SDK/sdk_libs/"
    else
        export LD_LIBRARY_PATH="$instance_dir/build/lib"
    fi

    echo "running" > $status_file

    if [ "`get_enclave_debuggable_flag`" == "false" ]; then
        export OCCLUM_RELEASE_ENCLAVE=1
    fi

    if [ "`get_enclave_enable_kss_flag`" == "true" ]; then
        export OCCLUM_ENABLE_KSS=1
    fi
    RUST_BACKTRACE=1 "$instance_dir/build/bin/occlum_exec_client" start

    echo "built" > $status_file
}

cmd_exec() {
    check_has_built
    check_has_not_start

    SGX_MODE=$(cat $instance_dir/.sgx_mode)
    if [[ -n $SGX_MODE && "$SGX_MODE" != "HW" ]]; then
        export LD_LIBRARY_PATH="$instance_dir/build/lib:$SGX_SDK/sdk_libs/"
    else
        export LD_LIBRARY_PATH="$instance_dir/build/lib"
    fi

    echo "running" > "$status_file"

    RUST_BACKTRACE=1 "$instance_dir/build/bin/occlum_exec_client" exec -- "$@"

    echo "built" > "$status_file"
}

cmd_stop() {
    check_has_built
    check_has_not_start

    SGX_MODE=$(cat $instance_dir/.sgx_mode)
    if [[ -n $SGX_MODE && "$SGX_MODE" != "HW" ]]; then
        export LD_LIBRARY_PATH="$instance_dir/build/lib:$SGX_SDK/sdk_libs/"
    else
        export LD_LIBRARY_PATH="$instance_dir/build/lib"
    fi

    echo "running" > "$status_file"

    RUST_BACKTRACE=1 "$instance_dir/build/bin/occlum_exec_client" stop -t 0

    ## Ensure the server is stopped, default timeout is 30s
    for i in $(seq 0 30); do
        if pgrep --full "build/bin/occlum_exec_server -d $instance_dir" > /dev/null ; then
            sleep 1
        else
            echo "server stopped."
            echo "built" > "$status_file"
            exit 0
        fi
    done

    echo "Error: timeout to stop the server, let's kill it."
    pkill -SIGKILL --full "build/bin/occlum_exec_server -d $instance_dir"
    echo "built" > "$status_file"
}

cmd_package() {
    check_has_built

    debug="false"
    instance_base_name=$(basename $instance_dir)
    package_name="$instance_base_name.tar.gz"
    while [ -n "$1" ]; do
        case "$1" in
        --debug)  debug="true" ; shift ;;
        *) package_name=$1 ; shift ;;
        esac
    done

    if [[ "$package_name" != *.tar.gz ]]; then
        package_name="$package_name.tar.gz"
    fi

    if [[ "`get_enclave_debuggable_flag`" == "true" && "$debug" != "true" ]]; then
        echo 'Warning: current Occlum instance is configured as "debuggable".'
        echo '(If it is not expected, you can modify the Occlum.json "metadata" - "debuggable" field to "false" and build again. And then use "occlum package")'
        echo 'Or, use "occlum package --debug" to support debug mode package'
        exit 1
    fi

    SGX_MODE=$(cat $instance_dir/.sgx_mode)
    if [[ -n $SGX_MODE && "$SGX_MODE" == "SIM" && "$debug" != "true" ]]; then
        echo '"occlum package" command should only be used for an Occlum instance of SGX hardware mode or hyper mode, not the simulation mode.'
        echo 'Please run "occlum build --sgx-mode HW/HYPER" and then use "occlum package"'
        echo 'Or, use "occlum package --debug" to support similation mode package'
        exit 1
    fi

    rm -f $package_name

    pkg_files="\
    $instance_base_name/Occlum.json \
    $instance_base_name/build/bin \
    $instance_base_name/build/lib/libocclum-libos.signed.so \
    $instance_base_name/build/lib/libocclum-pal.so* \
    $instance_base_name/build/initfs $instance_base_name/build/mount \
    $instance_base_name/build/.Occlum_sys.json.protected \
    $instance_base_name/initfs $instance_base_name/run \
    $instance_base_name/.__occlum_status $instance_base_name/.sgx_mode \
    "
    if [[ "$SGX_MODE" == "SIM" ]]; then
        extra_files="\
        $instance_base_name/build/lib/libocclum-pal_sim.so* \
        "
    elif [[ "$SGX_MODE" == "HYPER" ]]; then
        extra_files="\
        $instance_base_name/build/lib/libocclum-pal_hyper.so* \
        "
    fi

    cd .. && tar -cvzf $instance_dir/$package_name \
                --transform s/$instance_base_name/$(basename $package_name .tar.gz)/ \
                $pkg_files $extra_files

    echo "The package $package_name is generated successfully"
}

cmd_gdb() {
    check_has_built

    SGX_MODE=$(cat $instance_dir/.sgx_mode)
    if [[ -n $SGX_MODE && "$SGX_MODE" != "HW" ]]; then
        export LD_LIBRARY_PATH="$instance_dir/build/lib:$SGX_SDK/sdk_libs/"
    else
        export LD_LIBRARY_PATH="$instance_dir/build/lib"
    fi

    echo "debugging" > "$status_file"

    OCCLUM_GDB=1 $SGX_GDB --args "$instance_dir/build/bin/occlum-run" "$@"

    echo "built" > "$status_file"
}

cmd_mount() {
    check_has_built

    while [ -n "$1" ]; do
        case "$1" in
        --sign-key)     [ -n "$2" ] && ENCLAVE_SIGN_KEY=$2 ; shift 2 || exit_error "empty signing key path"     ;;
        --sign-tool)    [ -n "$2" ] && ENCLAVE_SIGN_TOOL=$2 ; shift 2 || exit_error "empty signing tool path"   ;;
        --image-key)    [ -n "$2" ] && SECURE_IMAGE_KEY=$2 ; shift 2 || exit_error "empty secure image key path"   ;;
        *) MNT_POINT=$1 ; shift ;;
        esac
    done
    [ -e "$ENCLAVE_SIGN_KEY" ] || exit_error "invalid signing key path: $ENCLAVE_SIGN_KEY"
    [ -e "$ENCLAVE_SIGN_TOOL" ] || exit_error "invalid signing tool path: $ENCLAVE_SIGN_TOOL"
    if [ -n "$SECURE_IMAGE_KEY" ]; then
        [ -e "$SECURE_IMAGE_KEY" ] || exit_error "invalid secure image key path: $SECURE_IMAGE_KEY"
    fi
    [ -d "$MNT_POINT" ] || exit_error "invalid mount point: $MNT_POINT"
    echo "Mount tool sign-tool: $ENCLAVE_SIGN_TOOL"
    echo "Mount tool sign-key: $ENCLAVE_SIGN_KEY"
    [ -n "$SECURE_IMAGE_KEY" ] && echo "Image decryption key: $SECURE_IMAGE_KEY"
    echo "Mount point: $MNT_POINT"

    SGX_MODE=$(cat $instance_dir/.sgx_mode)
    if [[ -n $SGX_MODE && "$SGX_MODE" != "HW" ]]; then
        if [[ "$SGX_MODE" != "HYPER" ]]; then
            sefs_cli="$occlum_dir/build/bin/sefs-cli_sim"
            sefs_cli_lib="$occlum_dir/build/lib/libsefs-cli_sim.so"
        else
            sefs_cli="$occlum_dir/build/bin/sefs-cli_hyper"
            sefs_cli_lib="$occlum_dir/build/lib/libsefs-cli_hyper.so"
        fi
        echo "SGX mode: $SGX_MODE"
    else
        sefs_cli="$occlum_dir/build/bin/sefs-cli"
        sefs_cli_lib="$occlum_dir/build/lib/libsefs-cli.so"
        echo "SGX mode: HW"
    fi

    signed_sefs_cli_lib="$instance_dir/build/lib/libsefs-cli.signed.so"
    echo "Signing the mount tool..."
    "$ENCLAVE_SIGN_TOOL" sign \
        -key "$ENCLAVE_SIGN_KEY" \
        -config "$occlum_dir/build/sefs-cli.Enclave.xml" \
        -enclave "$sefs_cli_lib" \
        -out "$signed_sefs_cli_lib"

    image_fs="$instance_dir/build/mount/__ROOT"
    if [ -e "$instance_dir/run/mount/__ROOT/metadata" ]; then
        container_fs="$instance_dir/run/mount/__ROOT"
    else
        container_fs=""
    fi

    [ -n "$SECURE_IMAGE_KEY" ] && SECURE_IMAGE_KEY_OPTION="--key $SECURE_IMAGE_KEY"
    echo "Start to mount the FS..."
    LD_LIBRARY_PATH="$SGX_SDK/sdk_libs" "$sefs_cli" \
        --enclave "$signed_sefs_cli_lib" \
        mount \
        $SECURE_IMAGE_KEY_OPTION \
        "$image_fs" \
        "$container_fs" \
        "$MNT_POINT"

    # After mounting the FS, remove the signed mount tool
    rm -f "$signed_sefs_cli_lib"
}

cmd_status() {
    cat "$status_file"
}

cmd_gen_image_key() {
    if [ -z $@ ]; then
        echo "Error: target file is not set"
        exit 1
    fi

    key_path="$@"
    if [[ "$key_path" != "/"* ]]; then
        key_path="$instance_dir/$@"
    fi
    cat /dev/urandom | tr -dc 'a-f0-9' | fold -w 32 | head -n 1 | sed -r 's/.{2}/&-/g; s/.$//' > $key_path
}

cmd_print_info() {
    if [ -z $1 ]; then
        echo "Error: print info name not provided"
        exit 1
    fi

    libos_so="build/lib/libocclum-libos.signed.so"
    info="$1"

    [ -e "$libos_so" ] || \
        exit_error "No $libos_so existed"

    if [[ "$info" == "mrenclave" ]]; then
        sgx_sign dump -enclave $libos_so -dumpfile dumpfile >/dev/null 2>&1
        sed -n -e '/enclave_hash.m/,/metadata->enclave_css.body.isv_prod_id/p' dumpfile \
            |head -3|tail -2|xargs|sed 's/0x//g'|sed 's/ //g'
        rm dumpfile
    elif [[ "$info" == "mrsigner" ]]; then
        sgx_sign dump -enclave $libos_so -dumpfile dumpfile >/dev/null 2>&1
        tail -2 dumpfile |xargs|sed 's/0x//g'|sed 's/ //g'
        rm dumpfile
    else
        exit_error "No valid info name provided"
    fi
}

if [[ ( "$#" < 1 ) ]] ; then
    report_arg_error "Error: no sub-command is given"
    exit 1
fi

cmd=$1
case "$cmd" in
    new)
        cmd_new "${@:2}"
        ;;
    init)
        cmd_init "${@:2}"
        ;;
    build)
        cmd_build "${@:2}"
        ;;
    run)
        cmd_run "${@:2}"
        ;;
    start)
        cmd_start "${@:2}"
        ;;
    exec)
        cmd_exec "${@:2}"
        ;;
    stop)
        cmd_stop
        ;;
    package)
        cmd_package "${@:2}"
        ;;
    gdb)
        cmd_gdb "${@:2}"
        ;;
    mount)
        cmd_mount "${@:2}"
        ;;
    status)
        cmd_status
        ;;
    gen-image-key)
        cmd_gen_image_key "${@:2:1}"
        ;;
    print)
        cmd_print_info "${@:2:1}"
        ;;
    *)
        report_arg_error "Error: unknown sub-command $cmd"
        exit 1
esac
