#!/bin/bash
##############################################################################
#
# $Id: qbuild 357 2011-03-14 09:37:25Z micke $
#
# This script is used to build QPKGs.
#
# Copyright (c) 2010,2011  Michael Nordstrom
#
# Information about required tail data is retrieved from QNAP's build
# script, qpkg_build_QNAP.sh, Copyright (C) 2008 QNAP Systems, Inc.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
##############################################################################

##### System definitions #####
PREFIX="App Center"

##### Command definitions #####
CMD_AWK="${CMD_AWK:-$(command -v awk)}"
CMD_CMP="${CMD_CMP:-$(command -v cmp)}"
CS_PYTHON="${CS_PYTHON:-$(command -v python2)}"
if [ ! -e "$CS_PYTHON" ]; then
CS_PYTHON="/usr/local/bin/python2"
fi

if [ -f "/etc/config/uLinux.conf" ]; then
    IS_NAS="TRUE"
else
    IS_NAS="FALSE"
fi

# Cleanup any temporary directories and files at termination.
trap 'cleanup_all' INT TERM

if [ $IS_NAS = "TRUE" ]; then
    CONF=/etc/config/qpkg.conf
    QPKG_NAME=qdk
    QDK_PATH=$(/sbin/getcfg $QPKG_NAME Install_Path -d "" -f $CONF)
else
    QDK_PATH="${QDK_PATH:-$(dirname "$(dirname "$(readlink -f "$0")")")}"
fi
# Check for deprecated definitions and replace them with new
# definitions (when applicable).
check_deprecated_definitions(){
	if [ -n "$QDK_SRC_DIR" ]; then
		[ -z "$QDK_ROOT_DIR" ] && QDK_ROOT_DIR="$QDK_SRC_DIR"
		QDK_SRC_DIR=
		warn_msg "QDK_SRC_DIR: deprecated definition"
	fi
	if [ -n "$QDK_SRC_X09" ]; then
		[ -z "$QDK_DATA_DIR_X09" ] && QDK_DATA_DIR_X09="$QDK_SRC_X09"
		QDK_SRC_X09=
		warn_msg "QDK_SRC_X09: deprecated definition"
	fi
	if [ -n "$QDK_SRC_X19" ]; then
		[ -z "$QDK_DATA_DIR_X19" ] && QDK_DATA_DIR_X19="$QDK_SRC_X19"
		QDK_SRC_X19=
		warn_msg "QDK_SRC_X19: deprecated definition"
	fi
	if [ -n "$QDK_SRC_X31" ]; then
		[ -z "$QDK_DATA_DIR_X31" ] && QDK_DATA_DIR_X31="$QDK_SRC_X31"
		QDK_SRC_X31=
		warn_msg "QDK_SRC_X31: deprecated definition"
	fi
	if [ -n "$QDK_SRC_X41" ]; then
		[ -z "$QDK_DATA_DIR_X41" ] && QDK_DATA_DIR_X41="$QDK_SRC_X41"
		QDK_SRC_X41=
		warn_msg "QDK_SRC_X41: deprecated definition"
	fi
	if [ -n "$QDK_SRC_ARM_64" ]; then
		[ -z "$QDK_DATA_DIR_ARM_64" ] && QDK_DATA_DIR_ARM_64="$QDK_SRC_ARM_64"
		QDK_SRC_X41=
		warn_msg "QDK_SRC_X41: deprecated definition"
	fi
	if [ -n "$QDK_SRC_X86" ]; then
		[ -z "$QDK_DATA_DIR_X86" ] && QDK_DATA_DIR_X86="$QDK_SRC_X86"
		QDK_SRC_X86=
		warn_msg "QDK_SRC_X86: deprecated definition"
	fi
	if [ -n "$QDK_SRC_X86_CE53XX" ]; then
		[ -z "$QDK_DATA_DIR_X86_CE53XX" ] && QDK_DATA_DIR_X86_CE53XX="$QDK_SRC_X86_CE53XX"
		QDK_SRC_X86_CE53XX=
		warn_msg "QDK_SRC_X86_CE53XX: deprecated definition"
	fi
	if [ -n "$QDK_SRC_X86_BR" ]; then
		[ -z "$QDK_DATA_DIR_X86_BR" ] && QDK_DATA_DIR_X86_BR="$QDK_SRC_X86_BR"
		QDK_SRC_X86_BR=
		warn_msg "QDK_SRC_X86_BR: deprecated definition"
	fi
	if [ -n "$QDK_SRC_X86_SB" ]; then
		[ -z "$QDK_DATA_DIR_X86_SB" ] && QDK_DATA_DIR_X86_SB="$QDK_SRC_X86_SB"
		QDK_SRC_X86_SB=
		warn_msg "QDK_SRC_X86_SB: deprecated definition"
	fi
	if [ -n "$QDK_SRC_X86_64" ]; then
		[ -z "$QDK_DATA_DIR_X86_64" ] && QDK_DATA_DIR_X86_64="$QDK_SRC_X86_64"
		QDK_SRC_X86_64=
		warn_msg "QDK_SRC_X86_64: deprecated definition"
	fi
	if [ -n "$QDK_SRC_X86_64_OMX" ]; then
		[ -z "$QDK_DATA_DIR_X86_64_OMX" ] && QDK_DATA_DIR_X86_64_OMX="$QDK_SRC_X86_64_OMX"
		QDK_SRC_X86_64_OMX=
		warn_msg "QDK_SRC_X86_64_OMX: deprecated definition"
	fi
	if [ -n "$QDK_SRC_SHARED" ]; then
		[ -z "$QDK_DATA_DIR_SHARED" ] && QDK_DATA_DIR_SHARED="$QDK_SRC_SHARED"
		QDK_SRC_SHARED=
		warn_msg "QDK_SRC_SHARED: deprecated definition"
	fi
	if [ -n "$QDK_SRC_ICONS" ]; then
		[ -z "$QDK_DATA_DIR_ICONS" ] && QDK_DATA_DIR_ICONS="$QDK_SRC_ICONS"
		QDK_SRC_ICONS=
		warn_msg "QDK_SRC_ICONS: deprecated definition"
	fi
	if [ -n "$QDK_SRC_CONFIG" ]; then
		[ -z "$QDK_DATA_DIR_CONFIG" ] && QDK_DATA_DIR_CONFIG="$QDK_SRC_CONFIG"
		QDK_SRC_CONFIG=
		warn_msg "QDK_SRC_CONFIG: deprecated definition"
	fi
	if [ -n "$QDK_SRC_FILE" ]; then
		[ -z "$QDK_DATA_FILE" ] && QDK_DATA_FILE="$QDK_SRC_FILE"
		QDK_SRC_FILE=
		warn_msg "QDK_SRC_FILE: deprecated definition"
	fi
	if [ -f "$QDK_QPKG_CONFIG" ] && /bin/grep -q '^QPKG_CONFIG_DIR' $QDK_QPKG_CONFIG; then
		[ -n "$HAS_WARNED_CONFIG_DIR" ] || warn_msg "QPKG_CONFIG_DIR: deprecated definition"
		HAS_WARNED_CONFIG_DIR=TRUE
	fi
	if [ -f "$QDK_QPKG_CONFIG" ] && /bin/grep -q '^QDK_EXTRA_SRC_FILE' $QDK_QPKG_CONFIG; then
		[ -n "$HAS_WARNED_EXTRA_SRC_FILE" ] || warn_msg "QDK_EXTRA_SRC_FILE: deprecated definition"
		HAS_WARNED_EXTRA_SRC_FILE=TRUE
	fi
}

# Retrieve data in network byte order.
network_order_32bit(){
	[ -n "$1" ] && [ -n "$2" ] || err_msg "internal error: network_order_32bit called with missing arguments"
	local off="$1"
	local qpkg="$2"
	[ -f $qpkg ] || err_msg "$qpkg: no such file"
	local word=$(/usr/bin/hexdump -s $off -n 1 -e '1/1 "%u"' $qpkg)
	word=$((word * 256))
	off=$((off + 1))
	word=$(((word + $(/usr/bin/hexdump -s $off -n 1 -e '1/1 "%u"' $qpkg)) * 256))
	off=$((off + 1))
	word=$(((word + $(/usr/bin/hexdump -s $off -n 1 -e '1/1 "%u"' $qpkg)) * 256))
	off=$((off + 1))
	word=$((word + $(/usr/bin/hexdump -s $off -n 1 -e '1/1 "%u"' $qpkg)))
	echo "$word"
}

# Retrieve size of content (that is the size of the header, control data, data
# archive, and any optional extra data archives.)
get_content_size(){
	[ -n "$1" ] || err_msg "internal error: get_content_size called with no argument"
	local qpkg="$1"
	[ -f $qpkg ] || err_msg "$qpkg: no such file"
	local offset_command="$(/bin/sed -n "1,/^exit 1/{
s/^script_len.*/&;/p
s/^offset.*/&;/p
/^exit 1/q
}" $qpkg) echo \$offset"
	echo "$(eval $offset_command)"
}

# Code signing server info
readonly DEFAULT_QNAP_CODE_SIGNING_SERVER_IP="codesigning.qnap.com.tw"
readonly DEFAULT_QNAP_CODE_SIGNING_SERVER_PORT="5001"

# Supported data types in the QDK area.
readonly QDK_AREA_SIGNATURE=1
readonly QDK_AREA_CODE_SIGNING=254
readonly QDK_AREA_EOF=255

# Length of QDK tag that is added to the front of the QDK area.
readonly QDK_AREA_TAG_LEN=3

# Length of data type value.
readonly QDK_AREA_DATA_TYPE_LEN=1

# Length of data size value.
readonly QDK_AREA_DATA_SIZE_LEN=4

# Length of tail data.
readonly TAIL_DATA_LEN=100

# Retrieve location of QDK area.
get_qdk_area_pos(){
	[ -n "$1" ] || err_msg "internal error: get_qdk_area_pos called with no argument"
	local qpkg="$1"
	[ -f $qpkg ] || err_msg "$qpkg: no such file"
	local qdk_area="$(get_content_size $qpkg)"
	local qdk_string="$(/bin/dd if=$qpkg bs=$qdk_area skip=1 2>/dev/null | /bin/dd bs=$QDK_AREA_TAG_LEN count=1 2>/dev/null)"
	if [ "$qdk_string" = "QDK" ]; then
		echo "$qdk_area"
		return 0
	fi
	return 1
}

# Retrieve location of specified data type in QDK area (if found)
get_qdk_area_data_pos(){
	[ -n "$1" ] && [ -n "$2" ] || err_msg "internal error: get_qdk_area_data_pos called with missing arguments"
	local data_type="$1"
	local qpkg="$2"
	[ -f $qpkg ] || err_msg "$qpkg: no such file"
	local qdk_area=
	qdk_area="$(get_qdk_area_pos $qpkg)" || return 1
	qdk_area="$(($qdk_area + $QDK_AREA_TAG_LEN))"

	# Traverse QDK area looking for specified data type. Stop if reaching the end
	# of the QDK area either by finding the QDK_AREA_EOF tag or by reaching the
	# tail data.
	local qdk_area_end="$(($(/bin/ls -l $qpkg | $CMD_AWK '{ print $5 }') - $TAIL_DATA_LEN))"
	local qdk_size=
	local qdk_pos=$qdk_area
	local qdk_type="$(/usr/bin/hexdump -s $qdk_area -n $QDK_AREA_DATA_TYPE_LEN -e '1/1 "%u"' $qpkg)"
	while [ $qdk_type -ne $data_type ] && [ $qdk_type -ne $QDK_AREA_EOF ] && (( $qdk_area <= $qdk_area_end ))
	do
		qdk_area="$(($qdk_area + $QDK_AREA_DATA_TYPE_LEN))"
		qdk_size="$(network_order_32bit $qdk_area $qpkg)"
		qdk_area="$(($qdk_area + $qdk_size + $QDK_AREA_DATA_SIZE_LEN))"
		qdk_pos=$qdk_area
		qdk_type="$(/usr/bin/hexdump -s $qdk_area -n $QDK_AREA_DATA_TYPE_LEN -e '1/1 "%u"' $qpkg)"
	done
	if [ $qdk_type -eq $data_type ]; then
		echo "$qdk_pos"
		return 0
	fi
	return 1
}

# Retrieve location of signature (if found)
get_signature_pos(){
	[ -n "$1" ] || err_msg "internal error: get_signature_pos called with no argument"
	local qpkg="$1"
	[ -f $qpkg ] || err_msg "$qpkg: no such file"
	local qdk_pos=
	qdk_pos="$(get_qdk_area_data_pos $QDK_AREA_SIGNATURE $qpkg)" || return 1

	# The signature is located after the data type and size data.
	echo "$(($qdk_pos + $QDK_AREA_DATA_TYPE_LEN + $QDK_AREA_DATA_SIZE_LEN))"
}

# Retrieve size of signature (if found)
get_signature_len(){
	[ -n "$1" ] || err_msg "internal error: get_signature_len called with no argument"
	local qpkg="$1"
	[ -f $qpkg ] || err_msg "$qpkg: no such file"
	local qdk_pos=
	qdk_pos="$(get_qdk_area_data_pos $QDK_AREA_SIGNATURE $qpkg)" || return 1
	qdk_pos="$(($qdk_pos + $QDK_AREA_DATA_TYPE_LEN))"

	echo "$(network_order_32bit $qdk_pos $qpkg)"
}

# Retrieve location of code signing digital signature (if found)
get_code_signing_pos(){
	[ -n "$1" ] || err_msg "internal error: get_code_signing_pos called with no argument"
	local qpkg="$1"
	[ -f $qpkg ] || err_msg "$qpkg: no such file"
	local qdk_pos=
	qdk_pos="$(get_qdk_area_data_pos $QDK_AREA_CODE_SIGNING $qpkg)" || return 1

	# The code signing digital signature is located after the data type and size data.
	echo "$(($qdk_pos + $QDK_AREA_DATA_TYPE_LEN + $QDK_AREA_DATA_SIZE_LEN))"
}

# Retrieve size of code signing digital signature (if found)
get_code_signing_len(){
	[ -n "$1" ] || err_msg "internal error: get_code_signing_len called with no argument"
	local qpkg="$1"
	[ -f $qpkg ] || err_msg "$qpkg: no such file"
	local qdk_pos=
	qdk_pos="$(get_qdk_area_data_pos $QDK_AREA_CODE_SIGNING $qpkg)" || return 1
	qdk_pos="$(($qdk_pos + $QDK_AREA_DATA_TYPE_LEN))"

	echo "$(network_order_32bit $qdk_pos $qpkg)"
}

# Add given architecture to comma-separated list in QDK_BUILD_ARCH.
add_arch(){
	debug_msg "Adding $1 to list of architectures"
	[ -n "$1" ] && QDK_BUILD_ARCH="${QDK_BUILD_ARCH}${QDK_BUILD_ARCH:+,}$1"
}

# Error messages are always printed (to stderr) before the application
# removes any temporary files and exit (in cleanup_all).
err_msg(){
	echo "$1" 1>&2
	cleanup_all
}

# Warning messages are printed (to stderr) but won't exit the application
# unless running in strict mode.
warn_msg(){
	if [ "$QDK_STRICT" = "TRUE" ]; then
		err_msg "$1"
	else
		msg "$1" 1>&2
	fi
}

# Verbosity levels
readonly QUIET=0
readonly NORMAL=1
readonly VERBOSE=2
readonly DEBUG=3

# Set to no messages.
is_quiet(){
	[ $QDK_VERBOSE -eq $QUIET ] || return 1
}

# Set to verbose or debug messages.
is_verbose(){
	[ $VERBOSE -le $QDK_VERBOSE ] || return 1
}

# Set to debug messages.
is_debug(){
	[ $DEBUG -le $QDK_VERBOSE ] || return 1
}

# Log messages are printed (to stdout) if verbose mode is greater or equal
# to specified level (default: NORMAL).
msg(){
	[ $QDK_VERBOSE -ge ${2:-$NORMAL} ] && echo "$1" || return 0
}
verbose_msg(){
	msg "$1" $VERBOSE
}
debug_msg(){
	msg "$1" $DEBUG
}

# Return 0 if given directory is empty, otherwise return 1
is_empty_dir(){
	if [ -d "$1" ]; then
		[ -z "$(/bin/ls -A "$1" 2>/dev/null |grep -v "^.svn" 2>/dev/null)" ]
	fi
}

# Set the specified field to given value and write it to the QPKG
# configuration file.
edit_qpkg_config(){
	local field="$1"
	local value="$2"
	local qpkg_cfg="${3:-$QDK_QPKG_CONFIG}"
	if [ -n "$field" ] && [ -n "$value" ] && [ -f "$qpkg_cfg" ]; then
		debug_msg "Setting $field to $value in $qpkg_cfg"
		/bin/sed -i "s/${field}=\".*\"/${field}=\"${value}\"/" "$qpkg_cfg"
		eval $field=\"$value\"
	else
		return 1
	fi
}

# Remove all temporary directories and files created by application.
cleanup_all(){
	/bin/rm -fr build.$$ tmp.$$
	/bin/rm -f $QDK_QPKG_FILE
	exit 1
}

# Sanity check of QDK environment.
sanity_check_qdk_environment(){
	[ -n "$QDK_PATH" ] || err_msg "no QDK_PATH specified in ${QDK_CONF_FILE}"

	QDK_SCRIPTS_DIR="${QDK_SCRIPTS_DIR:-${QDK_PATH}/scripts}"
	QDK_TEMPLATE_DIR="${QDK_TEMPLATE_DIR:-${QDK_PATH}/template}"

	QDK_INSTALL_SCRIPT="${QDK_INSTALL_SCRIPT:-${QDK_SCRIPTS_DIR}/qinstall.sh}"

	[ -x "$QDK_INSTALL_SCRIPT" ] || err_msg "$QDK_INSTALL_SCRIPT: no such file"
	[ -d "$QDK_TEMPLATE_DIR" ] || err_msg "$QDK_TEMPLATE_DIR: no such directory"
	[ -f "${QDK_TEMPLATE_DIR}/package_routines" ] || err_msg "${QDK_TEMPLATE_DIR}/package_routines: no such file"
	[ -f "${QDK_TEMPLATE_DIR}/qpkg.cfg" ] || err_msg "${QDK_TEMPLATE_DIR}/qpkg.cfg: no such file"
	QDK_MD5SUM_APP="${QDK_MD5SUM_APP:-$(command -v md5sum)}"
	[ -x "$QDK_MD5SUM_APP" ] || err_msg "md5sum application: not found"
}

# Copy files to data package directory (with optional exclude)
sync_files(){
	local opts="$1"
	local dir="$2"
	local copy_cmd="/usr/bin/rsync --exclude=.svn $opts ${dir}/. build.$$/ 2>/dev/null"
	debug_msg "$copy_cmd"
	eval $copy_cmd
}

# Copy configuration files to data package directory
copy_config(){
	local file="$1"
	local copy_cmd="/bin/cp -pf ${QDK_DATA_DIR_CONFIG:-config}/$file build.$$/"
	debug_msg "$copy_cmd"
	eval $copy_cmd
}

# Copy icons to data package directory
copy_icons(){
	local src_name="$1"
	local dest_name="$2"
	local copy_cmd="/bin/cp -pf ${QDK_DATA_DIR_ICONS:-icons}/$src_name build.$$/$dest_name"
	debug_msg "$copy_cmd"
	eval $copy_cmd
}

# Add architecture files to data package directory
add_architecture_files(){
	local rsync_opts="$1"
	local arch_dir="$2"

	if [ -d "$arch_dir" ]; then
		verbose_msg "Adding architecture specific files..."

		is_empty_dir "$arch_dir" && warn_msg "$arch_dir: empty directory"
		sync_files "$rsync_opts" "$arch_dir"
	fi
}

# Add shared files to data package directory
add_shared_files(){
	verbose_msg "Adding shared files..."

	local rsync_opts="$1"
	is_empty_dir "${QDK_DATA_DIR_SHARED:-shared}" || sync_files "$rsync_opts" "${QDK_DATA_DIR_SHARED:-shared}"
}

# Add any package icons (only includes icons using correct names)
add_icons(){
	verbose_msg "Adding icons..."

	local dist_icons="${QDK_DATA_DIR_ICONS:-icons}"
	if [ -d "$dist_icons" ]; then
		[ -f "${dist_icons}/${QPKG_NAME}.gif" ] && copy_icons "${QPKG_NAME}.gif" .qpkg_icon.gif
		[ -f "${dist_icons}/${QPKG_NAME}_80.gif" ] && copy_icons "${QPKG_NAME}_80.gif" .qpkg_icon_80.gif
		[ -f "${dist_icons}/${QPKG_NAME}_gray.gif" ] && copy_icons "${QPKG_NAME}_gray.gif" .qpkg_icon_gray.gif
		[ -f "${dist_icons}/${QPKG_NAME}.png" ] && copy_icons "${QPKG_NAME}.png" .qpkg_icon.gif
		[ -f "${dist_icons}/${QPKG_NAME}_80.png" ] && copy_icons "${QPKG_NAME}_80.png" .qpkg_icon_80.gif
		[ -f "${dist_icons}/${QPKG_NAME}_gray.png" ] && copy_icons "${QPKG_NAME}_gray.png" .qpkg_icon_gray.gif
	fi
}

# Add config files to data package directory
add_config_data(){
	verbose_msg "Adding configuration files..."

	local md5sum=
	local qpkg_config=$(/bin/sed -n '/^QPKG_CONFIG/s/QPKG_CONFIG="\(.*\)"/\1/p' $QDK_QPKG_CONFIG)
	for file in $qpkg_config
	do
		debug_msg "    $file"
		# Configuration files relative to QPKG directory.
		if [ -n "${file##/*}" ]; then
			[ -f "${QDK_DATA_DIR_CONFIG:-config}/$file" ] && copy_config "$file"
			if [ -f "build.$$/$file" ]; then
				md5sum=$($QDK_MD5SUM_APP "build.$$/$file" 2>/dev/null | /bin/cut -d' ' -f1)
				echo "$file = $md5sum" >> tmp.$$/md5sum
			elif [ "$QDK_FORCE_CONFIG" = "TRUE" ]; then
				echo "$file = 0" >> tmp.$$/md5sum
			else
				err_msg "$file: no such file; check your QPKG_CONFIG settings"
			fi
		else # Absolute path configuration files.
			if [ -f "${QDK_DATA_DIR_CONFIG:-config}/${file#*/}" ]; then
				/bin/tar rf tmp.$$/conf.tar -C "${QDK_DATA_DIR_CONFIG:-config}" "./${file#*/}"
				md5sum=$($QDK_MD5SUM_APP "${QDK_DATA_DIR_CONFIG:-config}/${file#*/}" 2>/dev/null | /bin/cut -d' ' -f1)
				echo "$file = $md5sum" >> tmp.$$/md5sum
			elif [ "$QDK_FORCE_CONFIG" = "TRUE" ]; then
				echo "$file = 0" >> tmp.$$/md5sum
			else
				err_msg "$file: no such file; check your QPKG_CONFIG settings"
			fi
		fi
	done
	[ -f tmp.$$/conf.tar ] && /bin/gzip -9 tmp.$$/conf.tar
}

# Add built version to data package directory
add_built_version(){
	verbose_msg "Adding built version..."

	local qpkg_built_version=$(/bin/sed -n '/^QPKG_BUILT_VERSION/s/QPKG_BUILT_VERSION="\(.*\)"/\1/p' $QDK_QPKG_CONFIG)
	if [ "$qpkg_built_version" = "TRUE" ]; then
		echo -n "time = " > tmp.$$/built_version
		date "+%Y-%m-%d %H:%M:%S" >> tmp.$$/built_version
		echo -n "svn = " >> tmp.$$/built_version
		svn info . | grep Revision | $CMD_AWK '{print $2}' >> tmp.$$/built_version
	fi
}

# Add built information to control package directory
add_built_information(){
	verbose_msg "Adding built information..."

	echo -n "time = " > build.$$/built_info
	date "+%Y%m%d" >> build.$$/built_info
}

#
perform_data_package_adaptor(){
	verbose_msg "Adapt the data package files (such as file owner)"
	if [ -f "../${QDK_DATA_PACKAGE_ADAPTOR}" ]; then
		echo "Run data package adaptor: ${QDK_DATA_PACKAGE_ADAPTOR}"
		. ../${QDK_DATA_PACKAGE_ADAPTOR} || err_msg "data package adaptor returned error: $?"
	elif [ -n "${QDK_DATA_PACKAGE_ADAPTOR}" ]; then
		warn_msg "${QDK_DATA_PACKAGE_ADAPTOR}: no such file"
	fi
}

# Code signing related routines
do_code_signing(){
	local build_dir="${1:-build.$$}"
	msg "Connecting to code signing server to create anti-tampering data..."
	[ -z "$QNAP_CODE_SIGNING_CSV" ] && QNAP_CODE_SIGNING_CSV="build_sign.csv"
	if [ ! -f "$QNAP_CODE_SIGNING_CSV" ]; then
		msg "Can't find $QNAP_CODE_SIGNING_CSV, ignore anti-tamper"
		return 0
	fi
	[ -z "$QNAP_CODE_SIGNING_SERVER_IP" ] && QNAP_CODE_SIGNING_SERVER_IP=$DEFAULT_QNAP_CODE_SIGNING_SERVER_IP
	[ -z "$QNAP_CODE_SIGNING_SERVER_PORT" ] && QNAP_CODE_SIGNING_SERVER_PORT=$DEFAULT_QNAP_CODE_SIGNING_SERVER_PORT
	if [ "$QNAP_CODE_SIGNING_SERVER_IP" = "172.17.21.68" ]; then
		msg "$QPKG_NAME: Do not used 172.17.21.68:5000 anymore. Please use codesigning.qnap.com.tw:5001. You also need to check the DNS setting on your build environment so that codesigning.qnap.com.tw can be resolved properly"
		QNAP_CODE_SIGNING_SERVER_IP="codesigning.qnap.com.tw"
		QNAP_CODE_SIGNING_SERVER_PORT="5001"
	fi
	$CS_PYTHON "${QDK_SCRIPTS_DIR}/codesigning_qpkg.py" cwd="`pwd`" buildpath=$build_dir csv="${QNAP_CODE_SIGNING_CSV}" server=${QNAP_CODE_SIGNING_SERVER_IP}:${QNAP_CODE_SIGNING_SERVER_PORT} 2>&1 | tee -a code_signing.log
	if [ ${PIPESTATUS[0]} != 0 ]; then
		err_msg "$QPKG_NAME: Failed to add anti-tamper support"
	fi
}

# Create data package for distribution.
create_data_package(){
	local arch_msg="${1:+ for $1}"
	local arch_dir="$2"
	local BUILD_DIR=build.$$
	local TMP_DIR=tmp.$$

	msg "Creating archive with data files${arch_msg}..."

	/bin/mkdir -m 755 -p build.$$ || err_msg "failed to create directory for data files"

	local rsync_opts="-av"
	is_debug || rsync_opts="$rsync_opts -q"
	[ -z "$QDK_RSYNC_EXCLUDE" ] || rsync_opts="$rsync_opts $QDK_RSYNC_EXCLUDE"
	[ -z "$QDK_RSYNC_EXCLUDE_FROM" ] || rsync_opts="$rsync_opts $QDK_RSYNC_EXCLUDE_FROM"
	add_shared_files "$rsync_opts"
	add_architecture_files "$rsync_opts" "$arch_dir"
	add_icons
	add_config_data
	add_built_version

	# A handler to adapt the data package file (such as file owner)
	cd build.$$
	perform_data_package_adaptor
	cd - > /dev/null 2>&1

	find . -type d -name '.qcodesigning' | xargs rm -rf
	if [ "x${QNAP_CODE_SIGNING}" = "x1" ]; then
		do_code_signing
	fi

	verbose_msg "Creating compressed tar archive..."
	case "$QDK_COMPRESS_METHOD" in
		gzip)
			QDK_COMPRESS_FILE=data.tar.gz
			debug_msg "[$QDK_COMPRESS_FILE]"
			debug_msg "$(/bin/tar -cvzf tmp.$$/data.tar.gz -C build.$$ .)"
			;;
		bzip2)
			QDK_COMPRESS_FILE=data.tar.bz2
			debug_msg "[$QDK_COMPRESS_FILE]"
			debug_msg "$(/bin/tar -cvjf tmp.$$/data.tar.bz2 -C build.$$ .)"
			;;
		7zip)
			QDK_COMPRESS_FILE=data.tar.7z
			local tar_verbose=
			is_debug && tar_verbose="-v"
			debug_msg "[$QDK_COMPRESS_FILE]"
			debug_msg "$(/bin/tar $tar_verbose -cf - -C build.$$ . | /usr/local/sbin/7z a -si tmp.$$/data.tar.7z)"
			;;
		xz)
			QDK_COMPRESS_FILE=data.tar.xz
			debug_msg "[$QDK_COMPRESS_FILE]"
                        if [ $IS_NAS = "TRUE" ]; then
                            debug_msg "$(tar cf - -C build.$$ . | xz > tmp.$$/${QDK_COMPRESS_FILE})"
                        else
			    local SIZE=$(du -sb "${BUILD_DIR}" | grep -o '^[0-9]\+')
			    debug_msg "$(tar cf - -C ${BUILD_DIR} . | pv -s $SIZE -N tar | xz > ${TMP_DIR}/${QDK_COMPRESS_FILE})"
                        fi
			;;
		*)
			err_msg "$QDK_COMPRESS_METHOD: unknown compression format"
	esac
	/bin/rm -fr build.$$
}

create_control_package(){
	msg "Creating archive with control files..."

	/bin/mkdir -m 755 -p build.$$ || err_msg "failed to create directory for control files"

	add_built_information

	/bin/cp -p "$QDK_INSTALL_SCRIPT" build.$$/
	/bin/cp -p "$QDK_PACKAGE_ROUTINES" build.$$/package_routines
	/bin/cp -p "$QDK_QPKG_CONFIG" build.$$/qpkg.cfg
	# cp .nc folder
	if [ -d "${QDK_DATA_DIR_SHARED:-shared}/.nc" ]; then
		/bin/cp -fa "${QDK_DATA_DIR_SHARED:-shared}/.nc" build.$$/.nc
		add_icons
	fi
	# xz utility
	if [ "$QDK_COMPRESS_METHOD" == "xz" ]; then
            if [ $IS_NAS = "TRUE" ]; then
		/bin/cp $QDK_PATH/xz_1404_${QDK_XZ_ARCH}.tgz build.$$/xz.tgz
            else
		/bin/cp $(dirname $(dirname $(realpath $0)))/xz_1404_${QDK_XZ_ARCH}.tgz build.$$/xz.tgz
            fi
	fi

	[ -f tmp.$$/md5sum ] && /bin/cp -p tmp.$$/md5sum build.$$/
	[ -f tmp.$$/built_version ] && /bin/cp -p tmp.$$/built_version build.$$/
	[ -f tmp.$$/conf.tar.gz ] && /bin/cp -p tmp.$$/conf.tar.gz build.$$/

	debug_msg "$(/bin/tar -cvzf tmp.$$/${QDK_CONTROL_FILE}.gz -C build.$$ .)"
	/bin/tar -cf tmp.$$/$QDK_CONTROL_FILE -C tmp.$$ ${QDK_CONTROL_FILE}.gz
	/bin/rm -fr build.$$
	/bin/rm -f tmp.$$/${QDK_CONTROL_FILE}.gz
	/bin/rm -f tmp.$$/conf.tar.gz
	/bin/rm -f tmp.$$/md5sum
}

# Include any optional Optware packages.
create_packages_file(){
	local opt_files="$(/bin/sed -n \
		-e '/^QDK_EXTRA_SRC_FILE/s/QDK_EXTRA_SRC_FILE="\(.*\.[io]pk\)"/\1/p' \
		-e '/^QDK_EXTRA_FILE/s/QDK_EXTRA_FILE="\(.*\.[io]pk\)"/\1/p' $QDK_QPKG_CONFIG)"
	for file in $opt_files
	do
		verbose_msg "Add $file to Packages"
		[ -f $file ] || err_msg "$file: no such file"
		/bin/tar -xOf $file ./control.tar.gz | /bin/tar xzO ./control | /bin/sed -e '/Priority.*/d' -e '/.*: $/d' >> tmp.$$/Packages || warn_msg "$file: missing control file"
		echo "Filename: ${file##*/} " >> tmp.$$/Packages
		echo "MD5Sum: $($QDK_MD5SUM_APP $file | $CMD_AWK '{ print $1 }')" >> tmp.$$/Packages
		echo "Size: $(/bin/ls -l $file | $CMD_AWK '{ print $5 }')" >> tmp.$$/Packages
		printf "\n\n" >> tmp.$$/Packages
	done
	if [ -f tmp.$$/Packages ]; then
		is_debug && /bin/cat tmp.$$/Packages
		/bin/gzip tmp.$$/Packages
	fi
}

# Pad given field with spaces up to optional given maximum length (default is 10)
pad_field(){
	[ -n "$1" ] || err_msg "internal error: pad_field called with no argument"

	local field="$1"
	local field_val=
	eval "field_val=\$$field"
	local pad_len=$((${2:-10} - ${#field_val}))

	[ $pad_len -ge 0 ] || err_msg "the length of $field_val must be less than or equal to ${2:-10}"
	while [ $pad_len -gt 0 ]
	do
		field_val="$field_val "
		pad_len=$(($pad_len - 1))
	done
	eval $field=\""$field_val"\"
}

# Add QPKG header (initial extract script).
add_qpkg_header(){
	local arch="$1"

	verbose_msg "Adding header script..."

	# Return value 0 to keep the QPKG package after installation
	# Return value 10 to remove QPKG package after installation
	local retval=10
	local extract_dir="\$QPKG_INSTALL_PATH/.tmp"
	local wrong_arch="[$PREFIX] Failed to install $QPKG_DISPLAY_NAME $QPKG_VER. Installation package is incompatible. Use the correct package."
	local log_tool="/sbin/log_tool -t2 -uSystem -p127.0.0.1 -mlocalhost -a"

	local cpu_arch=
	case "$arch" in
		arm-x09)
			cpu_arch="armv5tejl"
			reject_platform=
			;;
		arm-x19)
			cpu_arch="armv5tel"
			reject_platform=
			;;
		arm-x31)
			cpu_arch="armv7l"
			reject_platform="ARM_AL"
			;;
		arm-x41)
			cpu_arch="armv7l"
			reject_platform="ARM_MS"
			;;
		arm_64)
			cpu_arch="aarch64"
			reject_platform=
			;;
		x86)
			cpu_arch="i.86\|x86_64"
			reject_platform="X86_EVANSPORT"
			;;
		x86_ce53xx)
			cpu_arch="i686"
			reject_platform="X86_LAKEPORT\|X86_ATOM"
			;;
		x86_64)
			cpu_arch="x86_64"
			reject_platform=
			;;
		*)
			cpu_arch=
			reject_platform=
			;;
	esac

	echo "#!/bin/sh" > $QDK_QPKG_FILE
	/bin/cat >>$QDK_QPKG_FILE <<-EOF
	find_base(){
	HDD_MOUNT=\`/sbin/getcfg Public path -f /etc/config/smb.conf\`
	local log_tool="/sbin/log_tool -t2 -uSystem -p127.0.0.1 -mlocalhost -a"

	if [ -e "\$HDD_MOUNT" ]; then
		if [ -z "\$QINSTALL_PATH" ]; then
			BASE_GROUP="/share/HDA_DATA /share/HDB_DATA /share/HDC_DATA /share/HDD_DATA /share/HDE_DATA /share/HDF_DATA /share/HDG_DATA /share/HDH_DATA /share/HDI_DATA /share/HDJ_DATA /share/HDK_DATA /share/HDL_DATA /share/MD0_DATA /share/MD1_DATA /share/MD2_DATA /share/MD3_DATA"
			publicdir=\`/sbin/getcfg Public path -f /etc/config/smb.conf\`
			if [ ! -z \$publicdir ] && [ -d \$publicdir ];then
				publicdirp1=\`/bin/echo \$publicdir | /bin/cut -d "/" -f 2\`
				publicdirp2=\`/bin/echo \$publicdir | /bin/cut -d "/" -f 3\`
				publicdirp3=\`/bin/echo \$publicdir | /bin/cut -d "/" -f 4\`
				if [ ! -z \$publicdirp1 ] && [ ! -z \$publicdirp2 ] && [ ! -z \$publicdirp3 ]; then
					[ -d "/\${publicdirp1}/\${publicdirp2}/Public" ] && QPKG_BASE="/\${publicdirp1}/\${publicdirp2}"
				fi
			fi

			# Determine BASE installation location by checking where the Public folder is.
			if [ -z \$QPKG_BASE ]; then
				for datadirtest in \$BASE_GROUP; do
					[ -d \$datadirtest/Public ] && QPKG_BASE="/\${publicdirp1}/\${publicdirp2}"
				done
			fi
			if [ -z \$QPKG_BASE ] ; then
				echo "The Public share not found."
				return 1
			fi
			QPKG_INSTALL_PATH="\${QPKG_BASE}/.qpkg"
			QPKG_DIR="\${QPKG_INSTALL_PATH}/\${QPKG_NAME}"
		else
			if [ -e "\$QINSTALL_PATH" ]; then
				QPKG_INSTALL_PATH="\${QINSTALL_PATH}"
				QPKG_DIR="\${QINSTALL_PATH}/\${QPKG_NAME}"
			else
				if [ -x "/usr/local/sbin/notify" ]; then
					/usr/local/sbin/notify send -A A039 -C C001 -M 50 -l error -t 3 "[{0}] {1} install failed due to QTS application install volume not found." "$PREFIX" "$QPKG_DISPLAY_NAME"
					echo -1 > /tmp/update_process && exit 1
				else
					$log_tool "[$PREFIX] Failed to install $QPKG_DISPLAY_NAME. The selected installation volume is missing."
				fi
				echo -1 > /tmp/update_process && exit 1
			fi
		fi
		return 0
	else
		if [ ${QDK_ALLOW_NO_VOLUME} = 1 ] && [ -d "/mnt/HDA_ROOT/update_pkg" ]; then
			QPKG_INSTALL_PATH="/mnt/HDA_ROOT/update_pkg"
			QPKG_DIR="\${QPKG_INSTALL_PATH}/${QPKG_NAME}"
			_EXTRACT_DIR="/tmp/$QPKG_NAME"
		else
			if [ -x "/usr/local/sbin/notify" ]; then
				/usr/local/sbin/notify send -A A039 -C C001 -M 50 -l error -t 3 "[{0}] {1} install failed due to QTS application install volume not found." "$PREFIX" "$QPKG_DISPLAY_NAME"
				echo -1 > /tmp/update_process && exit 1
			else
				$log_tool "[$PREFIX] Failed to install $QPKG_DISPLAY_NAME. The selected installation volume is missing."
			fi
			echo -1 > /tmp/update_process && exit 1
		fi
	fi
	}
	EOF

	if [ -n "$cpu_arch" ]; then
		/bin/cat >>$QDK_QPKG_FILE <<-EOF
		wrong_arch(){
			if [ -x "/usr/local/sbin/notify" ]; then
				/usr/local/sbin/notify send -A A039 -C C001 -M 51 -l error -t 3 "[{0}] {1} {2} install failed due to the platform is incompatible. Please use correct package for installation." "$PREFIX" "$QPKG_DISPLAY_NAME" "$QPKG_VER"
				echo -1 > /tmp/update_process && exit 1
			else
				local wrong_arch_msg="$wrong_arch"
				echo "Installation Abort." && echo "\$wrong_arch_msg"
				$log_tool "[$PREFIX] Failed to install $QPKG_DISPLAY_NAME $QPKG_VER. Installation package is incompatible. Use the correct package."
				echo -1 > /tmp/update_process && exit 1
			fi
		}
		arch_ok(){
			local cpu_arch=\$(/bin/uname -m)
			local reject_platform=\$(/sbin/getcfg "" Platform -f /etc/platform.conf)
			if [ \$(/usr/bin/expr match "\$reject_platform" "$reject_platform") == 0 ]; then
				[ \$(/usr/bin/expr match "\$cpu_arch" "$cpu_arch") -ne 0 ] || return 1
			else
				return 1
			fi
		}
		EOF
	fi

	/bin/cat >>$QDK_QPKG_FILE <<-EOF
	/bin/echo "Install QNAP package on TS-NAS..."
	/bin/grep "/mnt/HDA_ROOT" /proc/mounts >/dev/null 2>&1 || exit 1
	EOF
	if [ -n "$cpu_arch" ]; then
		echo "arch_ok || wrong_arch" >>$QDK_QPKG_FILE
	fi

	local ctrl_len=$(/bin/ls -l tmp.$$/$QDK_CONTROL_FILE | $CMD_AWK '{ print $5 }')
	local data_len=$(/bin/ls -l tmp.$$/$QDK_COMPRESS_FILE | $CMD_AWK '{ print $5 }')
	[ -f tmp.$$/extra.tar ] && local extra_len=$(/bin/ls -l tmp.$$/extra.tar | $CMD_AWK '{ print $5 }')

	/bin/cat >>$QDK_QPKG_FILE <<-EOF
	find_base
	_EXTRACT_DIR="$extract_dir"
	/bin/mkdir -p \$_EXTRACT_DIR || exit 1
	script_len=SCRIPT_LEN
	/bin/dd if="\${0}" bs=\$script_len skip=1 | /bin/tar -xO | /bin/tar -xzv -C \$_EXTRACT_DIR || exit 1
	offset=\$(/usr/bin/expr \$script_len + $ctrl_len)
	/bin/dd if="\${0}" bs=\$offset skip=1 | /bin/cat | /bin/dd bs=1024 count=$((($data_len + 1023) / 1024)) of=\$_EXTRACT_DIR/$QDK_COMPRESS_FILE || exit 1
	[ -f /usr/local/bin/python ] && /usr/local/bin/python -c "with open('\$_EXTRACT_DIR/$QDK_COMPRESS_FILE', 'rw+') as f: f.seek($data_len); f.truncate()"
	offset=\$(/usr/bin/expr \$offset + $data_len)
	EOF

	# Extract any optional extra data packages.
	if /bin/grep -q '^QDK_EXTRA_FILE\|^QDK_EXTRA_SRC_FILE' $QDK_QPKG_CONFIG; then
		echo "/bin/dd if="\${0}" bs=\$offset skip=1 | /bin/tar -xv -C \$_EXTRACT_DIR || exit 1" >>$QDK_QPKG_FILE
		echo "offset=\$(/usr/bin/expr \$offset + $extra_len)" >> $QDK_QPKG_FILE
	fi

	/bin/cat >>$QDK_QPKG_FILE <<-EOF
	( cd \$_EXTRACT_DIR && /bin/sh ${QDK_INSTALL_SCRIPT##*/} || echo "Installation Abort." )
	/bin/rm -fr \$_EXTRACT_DIR && exit $retval
	exit 1
	EOF

	# Calculate header size (add 1 if number of digits in length will
	# increase the total size).
	# -10 is for the length of the placeholder, SCRIPT_LEN
	local script_len=$(($(/bin/ls -l $QDK_QPKG_FILE | $CMD_AWK '{ print $5 }') - 10))
	local num_of_digits_in_script_len=${#script_len}
	script_len=$(($script_len + ${#script_len}))
	if [ $num_of_digits_in_script_len -lt ${#script_len} ]; then
		script_len=$(($script_len + 1))
	fi
	/bin/sed -i "s/SCRIPT_LEN/$script_len/" $QDK_QPKG_FILE
	is_debug && /bin/cat $QDK_QPKG_FILE
}

# Add actual content to QPKG.
add_qpkg_content(){
	verbose_msg "Adding content..."

	verbose_msg "    control data..."
	/bin/cat tmp.$$/$QDK_CONTROL_FILE >> $QDK_QPKG_FILE

	verbose_msg "    data archive..."
	/bin/cat tmp.$$/$QDK_COMPRESS_FILE >> $QDK_QPKG_FILE

	if [ -f tmp.$$/extra.tar ]; then
		verbose_msg "    extra data archives..."
		/bin/cat tmp.$$/extra.tar >> $QDK_QPKG_FILE
	fi

	/bin/rm -f tmp.$$/extra.tar tmp.$$/Packages.gz tmp.$$/$QDK_COMPRESS_FILE tmp.$$/$QDK_CONTROL_FILE
}

# Add begin tag to QDK area.
add_qdk_area_begin(){
	local qpkg="${1:-$QDK_QPKG_FILE}"
	debug_msg "QDK area begins"
	printf "QDK" >> $qpkg
}

# Add data type tag to QDK area.
add_qdk_area_data_type(){
	[ -n "$1" ] || err_msg "internal error: add_qdk_area_data_type called with no argument"
	local qpkg="${2:-$QDK_QPKG_FILE}"
	debug_msg "Data type: $1"
	printf "\\$(printf "%o" $1)" >> $qpkg
}

# Add data size to QDK area.
add_qdk_area_data_size(){
	[ -n "$1" ] || err_msg "internal error: add_qdk_area_data_size called with no argument"
	local size="$1"
	local qpkg="${2:-$QDK_QPKG_FILE}"
	printf -v size "%08x" "$size"
	debug_msg "Data size: $size"
	for byte in $(echo $size | /bin/sed 's/../& /g')
	do
		printf "\\$(printf "%o" 0x$byte)" >> $qpkg
	done
}

# Add signature data to QDK area.
add_qdk_area_signature(){
	[ -n "$1" ] || err_msg "internal error: add_qdk_area_signature called with no argument"
	local data="$1"
	local qpkg="${2:-$QDK_QPKG_FILE}"
	local size="$(/bin/ls -l $data | $CMD_AWK '{ print $5 }')"
	add_qdk_area_data_type $QDK_AREA_SIGNATURE $qpkg
	add_qdk_area_data_size $size $qpkg
	debug_msg "Data: $data"
	/bin/cat $data >> $qpkg
}

# Add code signing digital signature to QDK area.
add_qdk_area_code_signing(){
	[ -n "$1" ] || err_msg "internal error: add_qdk_area_code_signing called with no argument"
	local data="$1"
	local qpkg="${2:-$QDK_QPKG_FILE}"
	local size="$(/bin/ls -l $data | $CMD_AWK '{ print $5 }')"
	add_qdk_area_data_type $QDK_AREA_CODE_SIGNING $qpkg
	add_qdk_area_data_size $size $qpkg
	debug_msg "Data: $data"
	/bin/cat $data >> $qpkg
}

# Add end tag to QDK area.
add_qdk_area_end(){
	add_qdk_area_data_type $QDK_AREA_EOF $1
	debug_msg "QDK area ends"
}

# Add signature to QPKG.
add_qpkg_signature(){
	if [ "x${QNAP_CODE_SIGNING}" = "x1" ]; then
		verbose_msg "Connecting to code signing server to create digital signature..."
		[ -z "$QNAP_CODE_SIGNING_SERVER_IP" ] && QNAP_CODE_SIGNING_SERVER_IP=$DEFAULT_QNAP_CODE_SIGNING_SERVER_IP
		[ -z "$QNAP_CODE_SIGNING_SERVER_PORT" ] && QNAP_CODE_SIGNING_SERVER_PORT=$DEFAULT_QNAP_CODE_SIGNING_SERVER_PORT
		if [ "$QNAP_CODE_SIGNING_SERVER_IP" = "172.17.21.68" ]; then
			msg "$QPKG_NAME: Do not used 172.17.21.68:5000 anymore. Please use codesigning.qnap.com.tw:5001. You also need to check the DNS setting on your build environment so that codesigning.qnap.com.tw can be resolved properly"
			QNAP_CODE_SIGNING_SERVER_IP="codesigning.qnap.com.tw"
			QNAP_CODE_SIGNING_SERVER_PORT="5001"
		fi
		openssl dgst -sha1 -binary "${QDK_QPKG_FILE}" > "${QDK_QPKG_FILE}.sha"
		$CS_PYTHON "${QDK_SCRIPTS_DIR}/codesigning_qpkg_cms.py" \
			cwd="`pwd`" server=${QNAP_CODE_SIGNING_SERVER_IP}:${QNAP_CODE_SIGNING_SERVER_PORT} \
			in="${QDK_QPKG_FILE}.sha" out="${QDK_QPKG_FILE}.msg" 2>&1 | tee -a code_signing.log
		/bin/rm ${QDK_QPKG_FILE}.sha
		if [ -f "${QDK_QPKG_FILE}.msg" ]; then
			add_qdk_area_begin
			add_qdk_area_code_signing ${QDK_QPKG_FILE}.msg
			add_qdk_area_end
			SIG=`openssl cms -cmsout -in ${QDK_QPKG_FILE}.msg 2>/dev/null | tr -d '\n' | tail -c 32`
			echo "${SIG}" > ${QDK_BUILD_DIR}/${QDK_QPKG_FILE}.codesigning
			/bin/rm ${QDK_QPKG_FILE}.msg
		else
			err_msg "$QDK_QPKG_FILE: Code signing digital signature was not added"
		fi
	else
		if [ -f "private_key" ] && [ -f "certificate" ]; then
			DO_CODE_SIGNING_OFFLINE="TRUE"
		elif [ -f "private_key" ]; then
			warn_msg "Will not create code signing digital signature because certificate is missing"
		elif [ -f "certificate" ]; then
			warn_msg "Will not create code signing digital signature because private key is missing"
		fi

		if [ "$DO_CODE_SIGNING_OFFLINE" = "TRUE" ] && [ "$QDK_SIGN" = "TRUE" ]; then
			err_msg "Can not sign QPKG using QNAP certificate and gpg key at same time"
		elif [ "$DO_CODE_SIGNING_OFFLINE" = "TRUE" ]; then
			verbose_msg "Creating code signing digital signature..."
			openssl dgst -sha1 -binary "${QDK_QPKG_FILE}" > "${QDK_QPKG_FILE}.sha"
			if [ -f "ca_certs" ]; then
				openssl cms -sign -in "${QDK_QPKG_FILE}.sha" -binary -nodetach -out "${QDK_QPKG_FILE}.msg" \
				-signer certificate -inkey private_key \
				-certfile ca_certs 2>/dev/null
			else
				openssl cms -sign -in "${QDK_QPKG_FILE}.sha" -binary -nodetach -out "${QDK_QPKG_FILE}.msg" \
					-signer certificate -inkey private_key 2>/dev/null
			fi
			local err_code=$?
			if [ $err_code = "2" ]; then
				warn_msg "Failed to open certificate or private key"
			elif [ $err_code = "3" ]; then
				warn_msg "Failed to parse certificate or private key"
			elif [ $err_code != "0" ]; then
				warn_msg "Failed to create digital signature"
			fi

			/bin/rm ${QDK_QPKG_FILE}.sha
			if [ -f "${QDK_QPKG_FILE}.msg" ]; then
				add_qdk_area_begin
				add_qdk_area_code_signing ${QDK_QPKG_FILE}.msg
				add_qdk_area_end
				SIG=`openssl cms -cmsout -in ${QDK_QPKG_FILE}.msg 2>/dev/null | tr -d '\n' | tail -c 32`
				echo "${SIG}" > ${QDK_BUILD_DIR}/${QDK_QPKG_FILE}.codesigning
				/bin/rm ${QDK_QPKG_FILE}.msg
			else
				err_msg "$QDK_QPKG_FILE: Code signing digital signature was not added"
			fi
		elif [ "$QDK_SIGN" = "TRUE" ]; then
			verbose_msg "Adding signature..."

			case "$QDK_SIGNATURE" in
				gpg)
					local gpg_options="--detach-sign -o-"
					[ -n "$QDK_GPG_NAME" ] && gpg_options="$gpg_options -u $QDK_GPG_NAME"
					is_quiet && gpg_options="$gpg_options --no-tty"
					debug_msg "$QDK_GPG_APP $gpg_options $QDK_QPKG_FILE >> ${QDK_QPKG_FILE}.sig"
					if $QDK_GPG_APP $gpg_options $QDK_QPKG_FILE >> ${QDK_QPKG_FILE}.sig; then
						add_qdk_area_begin
						add_qdk_area_signature ${QDK_QPKG_FILE}.sig
						add_qdk_area_end
						/bin/rm ${QDK_QPKG_FILE}.sig
					else
						warn_msg "$QDK_QPKG_FILE: no signature added"
					fi
					;;
				*)
					err_msg "$QDK_SIGNATURE: not supported signature type"
					;;
			esac
		fi
	fi
}

# Add QPKG tail (100 octets).
# [MODEL(10)|RESERVED(40])|FW_VERSION(10)|NAME(20)|VERSION(10)|FLAG(10)]
add_qpkg_tail(){
	local space_50="                                                  "
	local flag="QNAPQPKG  "

	verbose_msg "Adding tail data..."

	local max_qpkg_name_len=20
	local QPKG_NAME_SHORTER=$QPKG_NAME
	if [ $max_qpkg_name_len -lt ${#QPKG_NAME} ]; then
		warn_msg "QPKG_NAME: the length of $QPKG_NAME must be less than or equal to $max_qpkg_name_len; name truncated."
		QPKG_NAME_SHORTER=$(/usr/bin/expr substr "$QPKG_NAME" 1 $max_qpkg_name_len)
	fi
	[ -z "$QDK_BUILD_MODEL" ] || _QPKG_MODEL="$QDK_BUILD_MODEL"
	pad_field _QPKG_MODEL
	_QPKG_NAME="$QPKG_NAME_SHORTER"
	pad_field _QPKG_NAME 20
	_QPKG_VERSION="$QPKG_VER"
	pad_field _QPKG_VERSION
	printf "${_QPKG_MODEL}${space_50}${_QPKG_NAME}${_QPKG_VERSION}${flag}" >> $QDK_QPKG_FILE
	debug_msg "${_QPKG_MODEL}${space_50}${_QPKG_NAME}${_QPKG_VERSION}${flag}"
}

# Add encryption.
add_qpkg_encryption(){
	if [ -x "/sbin/qpkg" ]; then
		verbose_msg "Adding QPKG checksum: $(/sbin/qpkg --encrypt $QDK_QPKG_FILE)"
	else
		verbose_msg "Adding QPKG checksum: $QDK_QPKG_FILE"
		qpkg_encrypt $QDK_QPKG_FILE
	fi
}

create_qpkg(){
	# Model is _ARCH or empty for generic packages
	local arch="$1"
	local model="${arch:+_$arch}"
	local release="${QPKG_RELEASE:+-$QPKG_RELEASE}"

	msg "Creating QPKG package..."

	/bin/mkdir -m 755 -p "${QDK_BUILD_DIR:=build}" || err_msg "$QDK_BUILD_DIR: failed to create directory"

	QDK_QPKG_FILE=${QDK_QPKG_FILE:-${QPKG_NAME}_${QPKG_VER}${release}${model}.qpkg}
	verbose_msg "QPKG package name set to $QDK_QPKG_FILE"

	verbose_msg "Packaging optional extra data files..."
	# Include any optional extra data files.
	local extra_files="$(/bin/sed -n \
		-e '/^QDK_EXTRA_SRC_FILE/s/QDK_EXTRA_SRC_FILE="\(.*\)"/\1/p' \
		-e '/^QDK_EXTRA_FILE/s/QDK_EXTRA_FILE="\(.*\)"/\1/p' $QDK_QPKG_CONFIG)"
	[ -f tmp.$$/Packages.gz ] && extra_files="$extra_files tmp.$$/Packages.gz"
	for file in $extra_files
	do
		debug_msg "    $file"
		[ -f $file ] || err_msg "$file: no such file"
		[ -z "${file##/*}" ] || file="${QDK_ROOT_DIR:-.}/$file"
		/bin/tar rf tmp.$$/extra.tar -C "${file%/*}" "./${file##*/}"
	done

	add_qpkg_header "$arch"
	add_qpkg_content
	add_qpkg_signature
	add_qpkg_tail
	add_qpkg_encryption

	local ret=0
	if [ "x${QNAP_CODE_SIGNING}" = "x1" ] || [ "$DO_CODE_SIGNING_OFFLINE" = "TRUE" ] ; then
		verify_code_signing ${QDK_QPKG_FILE}
		ret=$?
	fi

	debug_msg "Copying $QDK_QPKG_FILE to $QDK_BUILD_DIR"
	/bin/cp -fp $QDK_QPKG_FILE $QDK_BUILD_DIR
	/bin/chmod 644 ${QDK_BUILD_DIR}/${QDK_QPKG_FILE}

	md5sum ${QDK_BUILD_DIR}/${QDK_QPKG_FILE} > ${QDK_BUILD_DIR}/${QDK_QPKG_FILE}.md5
	/bin/rm -f $QDK_QPKG_FILE
	return $ret
}

# Perform steps to build QPKG
build(){
	local arch="$1"
	local arch_dir="$2"
	if [ -z "$QPKG_DISPLAY_NAME" ]; then
		QPKG_DISPLAY_NAME=$QPKG_NAME
	fi
	/bin/mkdir -m 755 tmp.$$ || err_msg "failed to create directory for tar archives"

	if [ -f "$QDK_DATA_FILE" ]; then
		verbose_msg "Using pre-packaged data archive: $QDK_DATA_FILE"
		case "$QDK_DATA_FILE" in
			*.tgz|*.tar.gz)
				debug_msg "Checking gzip compressed archive...."
				/bin/gunzip -c "$QDK_DATA_FILE" 2>/dev/null | /bin/tar t &>/dev/null || err_msg "$QDK_DATA_FILE: this does not look like a gzip compressed tar archive"
				QDK_COMPRESS_FILE="data.tar.gz"
				;;
			*.tbz2|*.tar.bz2)
				debug_msg "Checking bzip2 compressed archive...."
				/usr/bin/bunzip2 -c "$QDK_DATA_FILE" 2>/dev/null | /bin/tar t &>/dev/null || err_msg "$QDK_DATA_FILE: this does not look like a bzip2 compressed tar archive"
				QDK_COMPRESS_FILE="data.tar.bz2"
				;;
			*.tar.7z)
				debug_msg "Checking 7-zip compressed archive...."
				/usr/local/sbin/7z x -t7z -so "$QDK_DATA_FILE" 2>/dev/null | /bin/tar t &>/dev/null || err_msg "$QDK_DATA_FILE: this does not look like a 7-zip compressed tar archive"
				QDK_COMPRESS_FILE="data.tar.7z"
				;;
			*)
				err_msg "$QDK_DATA_FILE: this does not look like a compressed tar archive"
				;;
		esac
		debug_msg "$QDK_DATA_FILE renamed to $QDK_COMPRESS_FILE"
		/bin/cp "$QDK_DATA_FILE" tmp.$$/$QDK_COMPRESS_FILE
	else
		create_data_package "$arch" "$arch_dir"
	fi
	create_control_package
	create_packages_file
	local ret=0
	create_qpkg "$arch" "$arch_dir"
	ret=$?
	/bin/rm -fr tmp.$$
	return $ret
}

# Perform pre-build actions
pre_build(){
	# Clear QPKG package name before each build to be able to set a new default value
	# in create_qpkg unless the pre-build script has set a value.
	QDK_QPKG_FILE=

	if [ -f "$QDK_PRE_BUILD" ]; then
		verbose_msg "Running pre-build script: $QDK_PRE_BUILD"
		. "$QDK_PRE_BUILD" "$1" "$2" || err_msg "pre-build script returned error: $?"
	elif [ -n "$QDK_PRE_BUILD" ]; then
		warn_msg "$QDK_PRE_BUILD: no such file"
	fi

	[ -z "$QDK_BUILD_VERSION" ] || edit_qpkg_config QPKG_VER "$QDK_BUILD_VERSION"
	[ -z "$QDK_BUILD_RELEASE" ] || edit_qpkg_config QPKG_RELEASE "$QDK_BUILD_RELEASE"

	check_deprecated_definitions
}

# Perform post-build actions
post_build(){
	if [ -f "$QDK_POST_BUILD" ]; then
		verbose_msg "Running post-build script: $QDK_POST_BUILD"
		. "$QDK_POST_BUILD" "$1" "$2" || err_msg "post-build script returned error: $?"
	elif [ -n "$QDK_POST_BUILD" ]; then
		warn_msg "$QDK_POST_BUILD: no such file"
	fi
}

# Call specified setup script
call_setup(){
	if [ -f "$QDK_SETUP" ]; then
		verbose_msg "Running setup script: $QDK_SETUP"
		. "$QDK_SETUP" "" || err_msg "setup script returned error: $?"
	elif [ -n "$QDK_SETUP" ]; then
		warn_msg "$QDK_SETUP: no such file"
	fi
}

# Call specified teardown script.
call_teardown(){
	if [ -f "$QDK_TEARDOWN" ]; then
		verbose_msg "Running teardown script: $QDK_TEARDOWN"
		. "$QDK_TEARDOWN" "" || err_msg "teardown script returned error: $?"
	elif [ -n "$QDK_TEARDOWN" ]; then
		warn_msg "$QDK_TEARDOWN: no such file"
	fi
}

# Check for missing configuration files
check_missing_config_files(){
	if [ ! -f "$QDK_QPKG_CONFIG" ]; then
		echo "$QDK_QPKG_CONFIG: No such file" 1>&2
		exit 1
	fi
	if [ ! -f "$QDK_PACKAGE_ROUTINES" ]; then
		echo "$QDK_PACKAGE_ROUTINES: No such file" 1>&2
		exit 1
	fi
}

# Check definitions in QPKG configuration file.
check_mandatory_definitions(){
	# Source the QPKG configuration file to make all its fields available.
	. "$QDK_QPKG_CONFIG" || err_msg "$QDK_QPKG_CONFIG: corrupt configuration file"

	[ -n "$QPKG_AUTHOR" ] || err_msg "$QDK_QPKG_CONFIG: QPKG_AUTHOR must be assigned a value"
	[ -n "$QPKG_NAME" ] || err_msg "$QDK_QPKG_CONFIG: QPKG_NAME must be assigned a value"
	[ -n "$QPKG_VER" ] || err_msg "$QDK_QPKG_CONFIG: QPKG_VER must be assigned a value"
	[ -n "$QPKG_SERVICE_PROGRAM" ] || warn_msg "$QDK_QPKG_CONFIG: QPKG_SERVICE_PROGRAM is undefined"

}

# Handle max lenght and any space in name; anything after a space is removed.
truncate_qpkg_name(){
	if [ "$QPKG_NAME" != "${QPKG_NAME%% *}" ]; then
		warn_msg "QPKG_NAME: space is not allowed; name truncated."
		edit_qpkg_config QPKG_NAME "${QPKG_NAME%% *}"
	fi

	local max_qpkg_name_len=40
	if [ $max_qpkg_name_len -lt ${#QPKG_NAME} ]; then
		warn_msg "QPKG_NAME: the length of $QPKG_NAME must be less than or equal to $max_qpkg_name_len; name truncated."
		QPKG_NAME=$(/usr/bin/expr substr "$QPKG_NAME" 1 $max_qpkg_name_len)
		edit_qpkg_config QPKG_NAME "$QPKG_NAME"
	fi
}

# Handle max lenght and any space in version; anything after a space is removed.
truncate_qpkg_version(){
	if [ "$QPKG_VER" != "${QPKG_VER%% *}" ]; then
		warn_msg "QPKG_VER: space is not allowed; value truncated."
		edit_qpkg_config QPKG_VER "${QPKG_VER%% *}"
	fi

	local max_qpkg_ver_len=10
	if [ $max_qpkg_ver_len -lt ${#QPKG_VER} ]; then
		warn_msg "QPKG_VER: the length of $QPKG_VER must be less than or equal to $max_qpkg_ver_len; value truncated."
		QPKG_VER=$(/usr/bin/expr substr "$QPKG_VER" 1 $max_qpkg_ver_len)
		edit_qpkg_config QPKG_VER "$QPKG_VER"
	fi
}

# Assign default values if no values were included in QPKG configuration file
# nor provided in setup actions
assign_defaults(){
	if [ -z "$QDK_BUILD_ARCH" ]; then
		is_empty_dir "${QDK_DATA_DIR_X09:-arm-x09}" || add_arch "arm-x09"
		is_empty_dir "${QDK_DATA_DIR_X19:-arm-x19}" || add_arch "arm-x19"
		is_empty_dir "${QDK_DATA_DIR_X31:-arm-x31}" || add_arch "arm-x31"
		is_empty_dir "${QDK_DATA_DIR_X41:-arm-x41}" || add_arch "arm-x41"
		is_empty_dir "${QDK_DATA_DIR_ARM_64:-arm_64}" || add_arch "arm_64"
		is_empty_dir "${QDK_DATA_DIR_X86:-x86}" || add_arch "x86"
		is_empty_dir "${QDK_DATA_DIR_X86_64:-x86_64}" || add_arch "x86_64"
		is_empty_dir "${QDK_DATA_DIR_X86_CE53XX:-x86_ce53xx}" || add_arch "x86_ce53xx"
	fi
	if [ -z "$QDK_BUILD_ARCH" ]; then
		if [ -f "$QDK_DATA_FILE" ] || ! is_empty_dir "${QDK_DATA_DIR_SHARED:-shared}"; then
			QDK_BUILD_ARCH="generic"
		fi
	fi
}

# Any actions to initialize build environment. After this function has been
# called all definitions in the qpkg.cfg file are available to other functions.
initialize_build_environment(){
	call_setup
	check_missing_config_files
	check_mandatory_definitions
	truncate_qpkg_name
	truncate_qpkg_version
	check_deprecated_definitions
	assign_defaults
}

# Traverse list of architectures and perform build.
traverse_and_build(){
	local arch=
	local arch_dir=
	local oldifs="$IFS"; IFS=,
	local ret=0
	set ${QDK_BUILD_ARCH:-unknown}
	IFS="$oldifs"
	for arch
	do
		case "$arch" in
			arm-x09)
				arch_dir="${QDK_DATA_DIR_X09:-arm-x09}"
				;;
			arm-x19)
				arch_dir="${QDK_DATA_DIR_X19:-arm-x19}"
				;;
			arm-x31)
				arch_dir="${QDK_DATA_DIR_X31:-arm-x31}"
				;;
			arm-x41)
				arch_dir="${QDK_DATA_DIR_X41:-arm-x41}"
				;;
			arm_64)
				arch_dir="${QDK_DATA_DIR_ARM_64:-arm_64}"
				;;
			x86)
				arch_dir="${QDK_DATA_DIR_X86:-x86}"
				;;
			x86_ce53xx)
				arch_dir="${QDK_DATA_DIR_X86_CE53XX:-x86_ce53xx}"
				;;
			x86_64)
				arch_dir="${QDK_DATA_DIR_X86_64:-x86_64}"
				;;
			generic)
				arch=""
				arch_dir=""
				;;
			?*)
				warn_msg "$arch: unsupported architecture"
				arch="unknown"
				;;
		esac
		if [ "$arch" != "unknown" ]; then
			pre_build "$arch" "$arch_dir"
			build "$arch" "$arch_dir"
			ret=$?
			post_build "$arch" "$arch_dir"
		fi
	done
	return $ret
}

# Any actions to clean up after build
cleanup_after_build(){
	call_teardown
}

# Main build function.
build_qpkg(){
	# All build operation should be run in the QDK_ROOT_DIR directory (current
	# directory by default)
	cd "${QDK_ROOT_DIR:-.}" 2>/dev/null || err_msg "$QDK_ROOT_DIR: no such directory"

	initialize_build_environment

	local ret=0
	if [ -n "$QDK_BUILD_ARCH" ]; then
		traverse_and_build
		ret=$?
	else
		warn_msg "Nothing to build"
	fi

	cleanup_after_build
	return $ret
}

# Create directory with template build environment
create_env(){
	[ -n "$1" ] || err_msg "internal error: create_env called with no argument"
	local qpkg_name="$1"

	[ -d "$qpkg_name" ] && err_msg "$qpkg_name already exists"

	/bin/mkdir -m 755 -p "$qpkg_name" || err_msg "$qpkg_name: failed to create directory"

	/bin/cp -pfr "${QDK_TEMPLATE_DIR}"/* "$qpkg_name"

	local qpkg_cfg="${qpkg_name}/qpkg.cfg"
	edit_qpkg_config QPKG_AUTHOR "$(/usr/bin/whoami)" "$qpkg_cfg"
	edit_qpkg_config QPKG_NAME "$qpkg_name" "$qpkg_cfg"

	local init_script="${qpkg_name}/shared/${qpkg_name}.sh"
	/bin/mv ${qpkg_name}/shared/init.sh $init_script
	/bin/sed -i "s/QPKG_NAME=NAME/QPKG_NAME=\"$qpkg_name\"/" $init_script
	edit_qpkg_config QPKG_SERVICE_PROGRAM "${qpkg_name}.sh" "$qpkg_cfg"

	[ -z "$QDK_BUILD_VERSION" ] || edit_qpkg_config QPKG_VER "$QDK_BUILD_VERSION" "$qpkg_cfg"
	/bin/echo ",/${qpkg_name}.sh," > ${qpkg_name}/build_sign.csv
	verbose_msg "Template QPKG build environment created in $qpkg_name directory"
}

# Extract files from specified QPKG.
extract_qpkg(){
	[ -n "$1" ] || err_msg "internal error: extract_qpkg called with no argument"
	local qpkg="$1"
	local dir="${2:-.}"
	[ -f $qpkg ] || err_msg "$qpkg: no such file"
	[ -d $dir ] || /bin/mkdir -p $dir

	local dd_quiet=
	local tar_quiet=
	is_quiet && tar_quiet=">/dev/null"
	is_verbose || dd_quiet="2>/dev/null"
	local extract_command="$(/bin/sed -n "1,/^exit 1/{
s!if=\"\${0}\" \(.* -C\) .*!$dd_quiet if=$qpkg \1 $dir $tar_quiet;!p
s!if=\"\${0}\" \(.* of.*\) ||.*!$dd_quiet if=$qpkg \1 $dd_quiet;!p
s!if=\${0} \(.* -C\) .*!$dd_quiet if=$qpkg \1 $dir $tar_quiet;!p
s!if=\${0} \(.* of.*\) ||.*!$dd_quiet if=$qpkg \1 $dd_quiet;!p
s/^script_len.*/&;/p
s/^offset.*/&;/p
/^exit 1/q
}" $qpkg)"
	[ -n "$extract_command" ] || err_msg "$qpkg: this does not look like a QPKG"
	extract_command="_EXTRACT_DIR=$dir; $extract_command"
	debug_msg "$extract_command"
	eval $extract_command
}

# Query information from specified QPKG.
query_qpkg(){
	[ -n "$1" ] || err_msg "internal error: query_qpkg called with no argument"
	local option="$1"
	local qpkg="$2"
	[ -f $qpkg ] || err_msg "$qpkg: no such file"

	local dd_quiet=
	is_debug || dd_quiet="2>/dev/null"
	local output=
	if [ "$option" = "funcs" ]; then
		dd_command=$(/bin/sed -n "1,/^exit 1/{
s!if=\${0} \(.*\)\(xz\?\)v -C .*!$dd_quiet if=$qpkg \1\2 -O --no-anchor package_routines!p
s/^script_len.*/&;/p
/^exit 1/q
}" $qpkg)
		[ -n "$dd_command" ] || err_msg "$qpkg: this does not look like a QPKG"
		debug_msg "$dd_command"
		output="$(eval $dd_command)"
	else
		dd_command=$(/bin/sed -n "1,/^exit 1/{
s!if=\${0} \(.*\)\(xz\?\)v -C .*!$dd_quiet if=$qpkg \1\2 -O --no-anchor qpkg.cfg!p
s/^script_len.*/&;/p
/^exit 1/q
}" $qpkg)
		[ -n "$dd_command" ] || err_msg "$qpkg: this does not look like a QPKG"
		debug_msg "$dd_command"
		output="$(eval $dd_command | /bin/sed -n -e '/^QPKG_/p' -e '/^QDK_/p')"
	fi

	case $option in
		dump)
			echo "$output"
			;;
		info)
			eval "$output"
			echo "Name     : $QPKG_NAME"
			echo "Version  : $QPKG_VER"
			echo "Packager : $QPKG_AUTHOR"
			echo "License  : ${QPKG_LICENSE:-Unknown}"
			echo "Summary  : $QPKG_SUMMARY"
			;;
		config)
			local qpkg_config=$(echo "$output" | /bin/sed -n '/^QPKG_CONFIG/s/QPKG_CONFIG="\(.*\)"/\1/p')
			[ -n "$qpkg_config" ] && echo "$qpkg_config"
			qpkg_config=$(echo "$output" | /bin/sed -n '/^QPKG_CONFIG_DIR/s/QPKG_CONFIG_DIR="\(.*\)"/\1/p')
			[ -n "$qpkg_config" ] && echo "$qpkg_config (using obsolete QPKG_CONFIG_DIR)"
			;;
		funcs)
			echo "$output"
			;;
		require)
			eval "$output"
			[ -z "$QPKG_REQUIRE" ] || echo "$QPKG_REQUIRE" | /bin/sed 's/ *, */\n/g'
			;;
		conflict)
			eval "$output"
			[ -z "$QPKG_CONFLICT" ] || echo "$QPKG_CONFLICT" | /bin/sed 's/ *, */\n/g'
			;;
		*)
			;;
	esac
}

# Verify signature
verify_qpkg(){
	[ -n "$1" ] || err_msg "internal error: verify_qpkg called with no argument"
	local qpkg="$1"
	[ -f $qpkg ] || err_msg "$qpkg: no such file"

	local data_size=
	local sign_pos=
	local sign_len=
	data_size=$(get_qdk_area_pos $qpkg) || exit 0
	sign_pos=$(get_signature_pos $qpkg) || exit 0
	sign_len=$(get_signature_len $qpkg) || exit 0

	local gpg_options="--verify --no-default-keyring --keyring $QDK_GPG_PUBKEYRING"
	is_verbose || gpg_options="$gpg_options 2>/dev/null"
	local gpg_cmd="$QDK_GPG_APP $gpg_options \
		<(/bin/dd if=$qpkg bs=1 skip=$sign_pos count=$sign_len 2>/dev/null) \
		<(/bin/dd if=$qpkg bs=$data_size count=1 2>/dev/null)"
	debug_msg "$gpg_cmd"
	if eval $gpg_cmd; then
		msg "Verification OK"
	else
		err_msg "Verification Failure"
	fi
}

# Re-sign QPKG.
add_sign_qpkg(){
	[ -n "$1" ] || err_msg "internal error: add_sign_qpkg called with no argument"
	local qpkg="$1"
	[ -f $qpkg ] || err_msg "$qpkg: no such file"

	case "$QDK_SIGNATURE" in
		gpg)
			verbose_msg "Find and remove any existing signature..."

			local data_size=
			if data_size=$(get_content_size $qpkg); then
				local sign_pos=
				if sign_pos=$(get_signature_pos $qpkg); then
					local sign_len=$(get_signature_len $qpkg)
					debug_msg "Signature located at position $sign_pos with size $sign_len"
					verbose_msg "Existing signature removed"
				fi
			fi
			[ -n "$data_size" ] || err_msg "$qpkg: not QDK 2.0 or later"

			dd_cmd="/bin/dd if=$qpkg bs=$data_size count=1 2>/dev/null > ${qpkg}.$$"
			debug_msg "$dd_cmd"
			eval "$dd_cmd"

			verbose_msg "Generate and add new signature..."
			local gpg_options="--detach-sign -o-"
			[ -n "$QDK_GPG_NAME" ] && gpg_options="$gpg_options -u $QDK_GPG_NAME"
			is_quiet && gpg_options="$gpg_options --no-tty"
			gpg_cmd="$QDK_GPG_APP $gpg_options ${qpkg}.$$ >> ${qpkg}.sig"
			debug_msg "$gpg_cmd"
			eval $gpg_cmd || warn_msg "${qpkg##*/}: no signature added"

			add_qdk_area_begin ${qpkg}.$$
			add_qdk_area_signature ${qpkg}.sig ${qpkg}.$$
			add_qdk_area_end ${qpkg}.$$

			verbose_msg "Add tail data..."
			printf "$(/usr/bin/tail -c$TAIL_DATA_LEN $qpkg)" >> ${qpkg}.$$
			verbose_msg "Adding QPKG checksum: $(/sbin/qpkg --encrypt ${qpkg}.$$)"
			/bin/rm ${qpkg}.sig
			/bin/mv ${qpkg}.$$ $qpkg
			;;
		*)
			err_msg "$QDK_SIGNATURE: not supported signature type"
			;;
	esac
}

# Import public key
import_key(){
	[ -n "$1" ] || err_msg "internal error: import_key called with no argument"
	local key="$1"
	[ -f $key ] || err_msg "$key: no such file"
	gpg_cmd="$QDK_GPG_APP --no-default-keyring --keyring $QDK_GPG_PUBKEYRING --import $key"
	is_quiet && gpg_cmd="$gpg_cmd 2>/dev/null"
	debug_msg "$gpg_cmd"
	eval $gpg_cmd
}

# Remove public key
remove_key(){
	[ -n "$1" ] || err_msg "internal error: remove_key called with no argument"
	local key="$1"
	gpg_cmd="$QDK_GPG_APP --no-default-keyring --keyring $QDK_GPG_PUBKEYRING --delete-keys $key 2>/dev/null"
	debug_msg "$gpg_cmd"
	eval $gpg_cmd
}

# List keys in public keyring
list_keys(){
	gpg_cmd="$QDK_GPG_APP --no-default-keyring --keyring $QDK_GPG_PUBKEYRING --list-keys"
	debug_msg "$gpg_cmd"
	eval $gpg_cmd
}


# Add definitions from specified section
add_qdk_definitions(){
	[ -n "$1" ] || err_msg "internal error: add_qdk_definitions called with no argument"
	local section="$1"

	if [ -f "$QDK_USER_CONFIG_FILE" ]; then
		local section_defs=$(/bin/sed -n "/\[$section\]/,/[[]/p" "$QDK_USER_CONFIG_FILE" | /bin/sed -e '/^\[/d' -e '/^$/d')
		[ -n "$section_defs" ] && eval "$section_defs"
	fi
}

# Add code signing digital signature into QPKG
add_code_signing(){
	[ -n "$1" ] || err_msg "internal error: add_code_signing called with no argument"
	local qpkg="$1"
	local code_signing_cfg="${2:-"`pwd`/qpkg.cfg"}"
	[ -f $qpkg ] || err_msg "$qpkg: no such file"
	[ -f $code_signing_cfg ] || err_msg "$code_signing_cfg: no such file"

	local data_size=
	if data_size=$(get_content_size $qpkg); then
		local sign_pos=
		if sign_pos=$(get_signature_pos $qpkg); then
			local sign_len=$(get_signature_len $qpkg)
			debug_msg "Signature located at position $sign_pos with size $sign_len"
		fi

		local code_signing_pos=
		if code_signing_pos=$(get_code_signing_pos $qpkg); then
			local code_signing_len=$(get_code_signing_len $qpkg)
			debug_msg "Digital signature located at position $code_signing_pos with size $code_signing_len"
		fi
	fi
	[ -n "$data_size" ] || err_msg "$qpkg: not QDK 2.0 or later"

	# Source the configuration file to make all its fields available.
	. "$code_signing_cfg" || err_msg "$code_signing_cfg: corrupt configuration file"

	if [ "x${QNAP_CODE_SIGNING}" = "x1" ]; then
		# Connecting to server
		[ -z "$QNAP_CODE_SIGNING_SERVER_IP" ] && QNAP_CODE_SIGNING_SERVER_IP=$DEFAULT_QNAP_CODE_SIGNING_SERVER_IP
		[ -z "$QNAP_CODE_SIGNING_SERVER_PORT" ] && QNAP_CODE_SIGNING_SERVER_PORT=$DEFAULT_QNAP_CODE_SIGNING_SERVER_PORT
		[ -n "$QPKG_NAME" ] || err_msg "$code_signing_cfg: QPKG_NAME not provided"
		[ -n "$QPKG_VER" ] || err_msg "$code_signing_cfg: QPKG_VER not provided"
		if [ "$QNAP_CODE_SIGNING_SERVER_IP" = "172.17.21.68" ]; then
			msg "$QPKG_NAME: Do not used 172.17.21.68:5000 anymore. Please use codesigning.qnap.com.tw:5001. You also need to check the DNS setting on your build environment so that codesigning.qnap.com.tw can be resolved properly"
			QNAP_CODE_SIGNING_SERVER_IP="codesigning.qnap.com.tw"
			QNAP_CODE_SIGNING_SERVER_PORT="5001"
		fi
	else
		# 3rd party, not connected to server, sign using local certificate and private key
		[ -f "$QNAP_CERT" ] || err_msg "$code_signing_cfg: QNAP_CERT $QNAP_CERT cannot be found"
		[ -f "$PRIVATE_KEY" ] || err_msg "$code_signing_cfg: PRIVATE_KEY $PRIVATE_KEY cannot be found"
	fi

	local qpkg_data_file="${qpkg}.data"
	local qpkg_data1_file="${qpkg}.data1"
	local count=$(($data_size / 4096))
	local rest=$(($data_size - ($count * 4096)))
	/bin/dd if=$qpkg bs=4096 count=$count 2>/dev/null > ${qpkg}.$$
	if [ $rest -gt 0 ]; then
		/bin/dd if=$qpkg bs=4096 skip=$count of=${qpkg}.rest 2>/dev/null
		/bin/dd if=${qpkg}.rest bs=$rest count=1 2>/dev/null >> ${qpkg}.$$
		/bin/rm ${qpkg}.rest
	fi
	#dd_cmd="/bin/dd if=$qpkg bs=$data_size count=1 2>/dev/null > ${qpkg}.$$"
	#debug_msg "$dd_cmd"
	#eval "$dd_cmd"
	openssl dgst -sha1 -binary "${qpkg}.$$" > "${qpkg}.sha"

	if [ "x${QNAP_CODE_SIGNING}" = "x1" ]; then
		# Send qpkg digest to server
		verbose_msg "Connecting to code signing server to create digital signature..."
		$CS_PYTHON "${QDK_SCRIPTS_DIR}/codesigning_qpkg_cms.py" server=${QNAP_CODE_SIGNING_SERVER_IP}:${QNAP_CODE_SIGNING_SERVER_PORT} qpkgname=${QPKG_NAME} version=${QPKG_VER} in="${qpkg}.sha" out="${qpkg}.msg" 2>&1 | tee -a code_signing.log
	else
		# 3rd party, not connected to server, sign using local certificate and private key
		verbose_msg "Creating code signing digital signature..."
		if [ -f "${CA_CERTS}" ]; then
			openssl cms -sign -in "${qpkg}.sha" -binary -nodetach -out "${qpkg}.msg" \
				-signer ${QNAP_CERT} -inkey ${PRIVATE_KEY} \
				-certfile ${CA_CERTS} 2>/dev/null
		else
			openssl cms -sign -in "${qpkg}.sha" -binary -nodetach -out "${qpkg}.msg" \
				-signer ${QNAP_CERT} -inkey ${PRIVATE_KEY} 2>/dev/null
		fi
		local err_code=$?
		if [ $err_code = "2" ]; then
			warn_msg "Failed to open certificate or private key"
		elif [ $err_code = "3" ]; then
			warn_msg "Failed to parse certificate or private key"
		elif [ $err_code != "0" ]; then
			warn_msg "Failed to create digital signature"
		fi
	fi
	/bin/rm ${qpkg}.sha

	# Add code signing digital signature into qpkg
	if [ -f "${qpkg}.msg" ]; then
		add_qdk_area_begin ${qpkg}.$$
		add_qdk_area_code_signing ${qpkg}.msg ${qpkg}.$$
		add_qdk_area_end ${qpkg}.$$
		SIG=`openssl cms -cmsout -in ${qpkg}.msg 2>/dev/null | tr -d '\n' | tail -c 32`
		echo "${SIG}" > ${qpkg}.codesigning
		/bin/rm ${qpkg}.msg
	else
		/bin/rm ${qpkg}.$$
		err_msg "$qpkg: no code signing digital signature added"
	fi

	verbose_msg "Add tail data..."
	printf "$(/usr/bin/tail -c$TAIL_DATA_LEN $qpkg)" >> ${qpkg}.$$
	verbose_msg "Adding QPKG checksum: $(/sbin/qpkg --encrypt ${qpkg}.$$)"
	/bin/mv ${qpkg}.$$ $qpkg

	md5sum $qpkg > $qpkg.md5
}

# Verify code signing digital signature in a QPKG
verify_code_signing_online(){
	[ -z "$QNAP_CODE_SIGNING_SERVER_IP" ] && QNAP_CODE_SIGNING_SERVER_IP=$DEFAULT_QNAP_CODE_SIGNING_SERVER_IP
	[ -z "$QNAP_CODE_SIGNING_SERVER_PORT" ] && QNAP_CODE_SIGNING_SERVER_PORT=$DEFAULT_QNAP_CODE_SIGNING_SERVER_PORT
	if [ "$QNAP_CODE_SIGNING_SERVER_IP" = "172.17.21.68" ]; then
		msg "$QPKG_NAME: Do not used 172.17.21.68:5000 anymore. Please use codesigning.qnap.com.tw:5001. You also need to check the DNS setting on your build environment so that codesigning.qnap.com.tw can be resolved properly"
		QNAP_CODE_SIGNING_SERVER_IP="codesigning.qnap.com.tw"
		QNAP_CODE_SIGNING_SERVER_PORT="5001"
	fi

	local curl_cmd=
	local output=
	local server_err=
	local server_msg=

	# Get ca cert
	local ca_cert_file="${qpkg}.ca"
	curl_cmd="curl -k -X GET -d 'token=${QNAP_CODESIGNING_TOKEN}' \
		https://${QNAP_CODE_SIGNING_SERVER_IP}:${QNAP_CODE_SIGNING_SERVER_PORT}/keys/qnaproot \
		2>/dev/null"
	output="$(eval $curl_cmd)" || err_msg "failed to connect to code signing server"
	server_err=$(echo $output | $CS_PYTHON -c 'import json,sys;obj=json.load(sys.stdin);print obj["error"]')
	if [ $server_err -ne 0 ]; then
		server_msg=$(echo $output | $CS_PYTHON -c 'import json,sys;obj=json.load(sys.stdin);print obj["msg"]')
		err_msg "Error from code signing server: $server_msg"
	fi
	echo $output | $CS_PYTHON -c 'import json,sys;obj=json.load(sys.stdin);print obj["certificate"]["pem"]' > $ca_cert_file

	# Get ca cert (3rd party)
	local ca_cert3_file="${qpkg}.ca3"
	curl_cmd="curl -k -X GET -d 'token=${QNAP_CODESIGNING_TOKEN}&third_party=true' \
		https://${QNAP_CODE_SIGNING_SERVER_IP}:${QNAP_CODE_SIGNING_SERVER_PORT}/keys/qnaproot \
		2>/dev/null"
	output="$(eval $curl_cmd)" || err_msg "failed to connect to code signing server"
	server_err=$(echo $output | $CS_PYTHON -c 'import json,sys;obj=json.load(sys.stdin);print obj["error"]')
	if [ $server_err -ne 0 ]; then
		server_msg=$(echo $output | $CS_PYTHON -c 'import json,sys;obj=json.load(sys.stdin);print obj["msg"]')
		err_msg "Error from code signing server: $server_msg"
	fi
	echo $output | $CS_PYTHON -c 'import json,sys;obj=json.load(sys.stdin);print obj["certificate"]["pem"]' > $ca_cert3_file

	# Get another ca cert (3rd party)
	local ca_cert3_2_file="${qpkg}.ca3_2"
	curl_cmd="curl -k -X GET -d 'token=${QNAP_CODESIGNING_TOKEN}&third_party=true' \
                https://${QNAP_CODE_SIGNING_SERVER_IP}:${QNAP_CODE_SIGNING_SERVER_PORT}/keys/qnaproot/v2 \
                2>/dev/null"
	output="$(eval $curl_cmd)" || err_msg "failed to connect to code signing server"
	server_err=$(echo $output | $CS_PYTHON -c 'import json,sys;obj=json.load(sys.stdin);print obj["error"]')
	if [ $server_err -ne 0 ]; then
                server_msg=$(echo $output | $CS_PYTHON -c 'import json,sys;obj=json.load(sys.stdin);print obj["msg"]')
                err_msg "Error from code signing server: $server_msg"
        fi
	echo $output | $CS_PYTHON -c 'import json,sys;obj=json.load(sys.stdin);print obj["certificate"]["pem"]' > $ca_cert3_2_file

	local verify_dgst_file="${qpkg}.verify"
	# Verify using non-3rd party CA
	local ret=
	openssl cms -verify -in $1 -CAfile $ca_cert_file 2>/dev/null > $verify_dgst_file
	$CMD_CMP $2 $verify_dgst_file 2>/dev/null
	if [ $? -eq 0 ]; then
		msg "Code signing digital signature verification successful"
		ret=0
	else
		# Verify using 3rd party CA
		openssl cms -verify -in $signature_file -CAfile $ca_cert3_file 2>/dev/null > $verify_dgst_file
		$CMD_CMP $2 $verify_dgst_file 2>/dev/null
		if [ $? -eq 0 ]; then
			msg "Code signing digital signature verification successful (3rd party qpkg)"
			ret=0
		else
			openssl cms -verify -in $signature_file -CAfile $ca_cert3_2_file 2>/dev/null > $verify_dgst_file
			$CMD_CMP $2 $verify_dgst_file 2>/dev/null
			if [ $? -eq 0 ]; then
				msg "Code signing digital signature verification successful (3rd party qpkg)"
				ret=0
			else
				msg "Code signing digital signature verification failed"
				ret=1
			fi
		fi
	fi
	/bin/rm $verify_dgst_file
	/bin/rm $ca_cert_file
	/bin/rm $ca_cert3_file
	/bin/rm $ca_cert3_2_file
	return $ret
}

# Verify code signing digital signature in a QPKG offline
verify_code_signing_offline(){
	local ca_cert="${QDK_SCRIPTS_DIR}/ca_cert3"
	[ -f $ca_cert ] || err_msg "Can not find QNAP CA certificate ${ca_cert}"
	local ca_cert_2="${QNAP_CA_CERT:-${QDK_SCRIPTS_DIR}/ca_cert3_2}"
	[ -f $ca_cert_2 ] || err_msg "Can not find QNAP CA certificate ${ca_cert_2}"

	# Verify using 3rd party CA
	local verify_dgst_file="${qpkg}.verify"
	local ret=
	openssl cms -verify -in $1 -CAfile $ca_cert 2>/dev/null > $verify_dgst_file
	$CMD_CMP $2 $verify_dgst_file 2>/dev/null
	if [ $? -eq 0 ]; then
		msg "Code signing digital signature verification successful (QNAP-issued certificate)"
		ret=0
	else
		openssl cms -verify -in $1 -CAfile $ca_cert_2 2>/dev/null > $verify_dgst_file
		$CMD_CMP $2 $verify_dgst_file 2>/dev/null
		if [ $? -eq 0 ]; then
			msg "Code signing digital signature verification successful (QNAP-issued certificate)"
			ret=0
		else
			openssl cms -verify -purpose any -CApath /etc/ssl/certs/ -in $1 > $verify_dgst_file
			$CMD_CMP $2 $verify_dgst_file 2>/dev/null
			if [ $? -eq 0 ]; then
				msg "Code signing digital signature verification successful (non QNAP-issued certificate)"
				ret=0
			else
				msg "Code signing digital signature verification failed"
				ret=1
			fi
		fi
	fi
	/bin/rm $verify_dgst_file
	return $ret
}

verify_code_signing(){
	[ -n "$1" ] || err_msg "internal error: verify_code_signing called with no argument"
	local qpkg="$1"
	[ -f $qpkg ] || err_msg "$qpkg: no such file"
	local code_signing_cfg="${2:-"`pwd`/qpkg.cfg"}"
	[ -f $code_signing_cfg ] || err_msg "$code_signing_cfg: no such file"

	. "$code_signing_cfg" || err_msg "$code_signing_cfg: corrupt configuration file"

	local data_size=$(get_qdk_area_pos $qpkg) || err_msg "Code signing verification failed: cannot find digital signature"
	local code_signing_pos=$(get_code_signing_pos $qpkg) || \
		err_msg "Code signing verification failed: cannot find digital signature"
	local code_signing_len=$(get_code_signing_len $qpkg) || \
		err_msg "Code signing verification failed: cannot find digital signature"

	local qpkg_data_file="${qpkg}.data"
	local qpkg_data1_file="${qpkg}.data1"
	local dgst_file="${qpkg}.dgst"
	local signature_file="${qpkg}.msg"
	local count=$(($data_size / 4096))
	local rest=$(($data_size - ($count * 4096)))
	/bin/dd if=$qpkg bs=4096 count=$count 2>/dev/null > $qpkg_data_file
	if [ $rest -gt 0 ]; then
		/bin/dd if=$qpkg bs=4096 skip=$count of=$qpkg_data1_file 2>/dev/null
		/bin/dd if=$qpkg_data1_file bs=$rest count=1 2>/dev/null >> $qpkg_data_file
		/bin/rm $qpkg_data1_file
	fi
	#/bin/dd if=$qpkg bs=$data_size count=1 2>/dev/null > $qpkg_data_file
	openssl dgst -sha1 -binary $qpkg_data_file 2>/dev/null > $dgst_file
	/bin/dd if=$qpkg bs=1 skip=$code_signing_pos count=$code_signing_len 2>/dev/null > $signature_file

	local ret=
	if [ "x${QNAP_CODE_SIGNING}" = "x1"  ]; then
		verify_code_signing_online ${signature_file} ${dgst_file}
		ret=$?
	else
		verify_code_signing_offline ${signature_file} ${dgst_file}
		ret=$?
	fi
	/bin/rm $qpkg_data_file
	/bin/rm $dgst_file
	/bin/rm $signature_file
	return $ret
}

usage(){
	/bin/cat <<EOF
usage: $(/usr/bin/basename $0) [--extract QPKG [DIR]] [--create-env NAME] [-s|--section SECTION]
	[--root ROOT_DIR] [--build-arch ARCH] [--build-version VERSION]
	[--build-number NUMBER] [--build-model MODEL] [--build-dir BUILD_DIR] [--force-config]
	[--setup SCRIPT] [--teardown SCRIPT] [--pre-build SCRIPT]
	[--post-build SCRIPT] [--exclude PATTERN] [--exclude-from FILE]
	[--gzip|--bzip2|--7zip|--xz {amd64|armhf}] [--sign] [--gpg-name ID] [--verify QPKG]
	[--add-sign QPKG] [--import-key KEY] [--remove-key ID] [--list-keys]
	[--query OPTION QPKG] [-v|--verbose] [-q|--quiet] [--strict]
	[--add-code-signing QPKG] [--verify-code-signing QPKG]
	[--code-signing-cfg CODE_SIGNING_CFG]
	[--allow-no-volume]
	[-?|-h|--help] [--usage] [-V|--version]
EOF
	exit 0
}

help(){
	/bin/cat <<EOF
usage: $(/usr/bin/basename $0) [--extract QPKG [DIR]] [--create-env NAME] [-s|--section SECTION]
	[--root ROOT_DIR] [--build-arch ARCH] [--build-version VERSION]
	[--build-number NUMBER] [--build-model MODEL] [--build-dir BUILD_DIR]
	[--force-config] [--setup SCRIPT] [--teardown SCRIPT]
	[--pre-build SCRIPT] [--post-build SCRIPT] [--exclude PATTERN]
	[--exclude-from FILE] [--gzip|--bzip2|--7zip] [--sign] [--gpg-name ID]
	[--verify QPKG] [--add-sign QPKG] [--import-key KEY] [--remove-key ID]
	[--list-keys] [--query OPTION QPKG] [-v|--verbose] [-q|--quiet] [--strict]
	[--add-code-signing QPKG] [--verify-code-signing QPKG]
	[--code-signing-cfg CODE_SIGNING_CFG]
	[--allow-no-volume]
	[-?|-h|--help] [--usage] [-V|--version]

-s
--section SECTION
	Add SECTION to the list of searched sections in the configuration file.
	A section is a named set of definitions. By default, the DEFAULT section
	will be searched and then any sections specified on the command line.
--root ROOT_DIR
	Use files and meta-data in ROOT_DIR when the QPKG is built (default is
	the current directory, '.').
--build-version VERSION
	Use given version when QPKG is built (also updates the QPKG_VER
	definition in qpkg.cfg).
--build-number NUMBER
	Use given build number when QPKG is built.
--build-model MODEL
	Include check for given model in the QPKG package.
--build-arch ARCH
	Build QPKG for specified ARCH (supported values: arm-x09, arm-x19, arm-x31, arm-x41, arm_64, x86, x86_ce53xx,
	and x86_64). Only one architecture per option, but you can repeat the
	option on the command line to add multiple architectures.
--build-dir BUILD_DIR
	Place built QPKG in BUILD_DIR. If a full path is not specified then it
	is relative to the ROOT_DIR (default is ROOT_DIR/build).
--setup SCRIPT
	Run specified script to setup build environment. Called once before
	build process is initiated.
--teardown SCRIPT
	Run specified script to cleanup after all builds are finished. Called
	once after all builds are completed.
--pre-build SCRIPT
	Run specified script before the build process is started. Called before
	each and every build. First argument contains the architecture (one of
	arm-x09, arm-x19, arm-x31, arm-x41, arm_64, x86, x86_ce53xx, and x86_64) and the second argument contains the
	location of the architecture specific code. For the generic build the
	arguments are empty.
--post-build SCRIPT
	Run specified script after the build process is finished. Called after
	each and every build. First argument contains the architecture (one of
	arm-x09, arm-x19, arm-x31, arm-x41, arm_64, x86, x86_ce53xx, and x86_64) and the second argument contains the
	location of the architecture specific code. For the generic build the
	arguments are empty.
--create-env NAME
	Create a template build environment in the current directory
	for a QPKG named NAME.
--extract QPKG [DIR]
	Extract archive of files and meta-data from QPKG to DIR (default is
	current directory).
--exclude PATTERN
	Do not include files matching PATTERN in data package. This option is
	passed on to rsync and follows the same rules as rsync's --exclude
	option. Only one exclude pattern per option, but you can repeat the
	option on the command line to add multiple patterns.
--exclude-from FILE
	Related to --exclude, but specifies a FILE that contains exclude
	patterns (one per line). This option is passed on to rsync and follows
	the same rules as rsync's --exclude-from option.
--strict
	Treat warnings as errors.
--force-config
	Ignore missing configuration files specified in QPKG_CONFIG.
--gzip
	Compress QPKG content using gzip (this is the default compression when
	no compression option is specified.)
--bzip2
	Compress QPKG content using bzip2.
--7zip
	Compress QPKG content using 7-zip.
--query OPTION QPKG
	Retrieve information from QPKG. Available options:
	  dump		dump settings from qpkg.cfg
	  info		summary of settings in qpkg.cfg
	  config	list configuration files
	  require	list required packages
	  conflict	list conflicting packages
	  funcs		output package specific functions
--sign
	Generate and insert digital signature to QPKG. By default the first key
	in the secret keyring is used.
--gpg-name ID
	Use specified user ID to sign QPKG.
--verify QPKG
	Verify digital signature assigned to QPKG.
--add-sign QPKG
	Generate and insert digital signature to QPKG, replacing any existing
	signature.
--import-key KEY
	Import ASCII armored key to public keyring.
--list-keys
	Show keys in public keyring.
--remove-key ID
	Remove key with specified ID from public keyring.
--add-code-signing QPKG
	Add code signing digital signature into QPKG. By default configuration file 'qpkg.cfg' is used
--verify-code-signing QPKG
	Verify code signing digital signature in the QPKG. By default configuration file 'qpkg.cfg' is used
--code-signing-cfg CODE_SIGNING_CFG
	The configuration file for --add-code-signing and --verify-code-signing
--allow-no-volume
        Allow qpkg install without volume. The default path is /mnt/HDA_ROOT/update_pkg when the volume can not be found.
-?
-h
--help
	Show this help message.
--usage
	Display brief usage information.
-q
--quiet
	Silent mode. Do not write anything to standard output. Normally only
	error messages will be displayed.
-v
--verbose
	Verbose mode. Multiple options increase the verbosity. The maximum is 3.
-V
--version
	Print a single line containing the version number of QDK.
EOF
	exit 0
}

version(){
	echo "$(/usr/bin/basename $0) $QDK_VERSION"
	exit 0
}

main(){
	QDK_QPKG_CONFIG="${QDK_QPKG_CONFIG:-qpkg.cfg}"
	QDK_PACKAGE_ROUTINES="${QDK_PACKAGE_ROUTINES:-package_routines}"

	QDK_COMPRESS_METHOD="${QDK_COMPRESS_METHOD:-gzip}"
	QDK_CONTROL_FILE="${QDK_CONTROL_FILE:-control.tar}"
	QDK_COMPRESS_FILE="${QDK_COMPRESS_FILE:-data.tar.gz}"
	QDK_GPG_PUBKEYRING="${QDK_GPG_PUBKEYRING:-/etc/config/qpkg.gpg}"

	QDK_ALLOW_NO_VOLUME=0

	[ -n "$QDK_GPG_KEYPATH" ] && export GNUPGHOME="$QDK_GPG_KEYPATH"

	QDK_VERBOSE=${QDK_VERBOSE:-$NORMAL}

	sanity_check_qdk_environment

	check_deprecated_definitions

	local qbuild_usage=
	local qbuild_help=
	local qbuild_quiet=
	local qbuild_version=
	local qbuild_qpkg_name=
	local qbuild_extract_file=
	local qbuild_import_key=
	local qbuild_remove_key=
	local qbuild_verify_file=
	local qbuild_add_sign_file=
	local qbuild_sign=
	local qbuild_list_keys=
	local qbuild_add_code_signing_file=
	local qbuild_verify_code_signing_file=
	local qbuild_code_signing_cfg_file=
	while  [ $# -gt 0 ]
	do
		case "$1" in
			--usage)	qbuild_usage=TRUE ;;
			--help|-h|-\?)	qbuild_help=TRUE ;;
			--version|-V)	qbuild_version=TRUE ;;
			--quiet|-q)	qbuild_quiet=TRUE ;;
			--verbose|-v)	QDK_VERBOSE=$(($QDK_VERBOSE + 1));;
			--strict)	QDK_STRICT=TRUE ;;
			--force-config)	QDK_FORCE_CONFIG=TRUE ;;
			--7zip)		QDK_COMPRESS_METHOD="7zip"
				;;
			--gzip)		QDK_COMPRESS_METHOD="gzip"
				;;
			--bzip2)	QDK_COMPRESS_METHOD="bzip2"
				;;
			--xz)	QDK_COMPRESS_METHOD="xz"
				[ -n "$QDK_COMPRESS_METHOD" ] || err_msg "--xz: no arch"
				QDK_XZ_ARCH=$2
				shift
				;;
			--create-env)	qbuild_qpkg_name="$2"
				[ -n "$qbuild_qpkg_name" ] || err_msg "--create-env: no name"
				shift
				;;
			--setup)	QDK_SETUP="$2"
				[ -n "$QDK_SETUP" ] || err_msg "--setup: no script"
				shift
				;;
			--teardown)	QDK_TEARDOWN="$2"
				[ -n "$QDK_TEARDOWN" ] || err_msg "--teardown: no script"
				shift
				;;
			--pre-build)	QDK_PRE_BUILD="$2"
				[ -n "$QDK_PRE_BUILD" ] || err_msg "--pre-build: no script"
				shift
				;;
			--post-build)	QDK_POST_BUILD="$2"
				[ -n "$QDK_POST_BUILD" ] || err_msg "--post-build: no script"
				shift
				;;
			--build-dir) 	QDK_BUILD_DIR="$2"
				[ -n "$QDK_BUILD_DIR" ] || err_msg "--build-dir: no directory"
				shift
				;;
			--build-version) QDK_BUILD_VERSION="$2"
				[ -n "$QDK_BUILD_VERSION" ] || err_msg "--build-version: no version"
				shift
				;;
			--build-number) QDK_BUILD_RELEASE="$2"
				[ -n "$QDK_BUILD_RELEASE" ] || err_msg "--build-number: no number"
				shift
				;;
			--build-arch)	local arch="$2"
				[ -n "$arch" ] || err_msg "--build-arch: no architecture"
				add_arch "$arch"
				shift
				if [ -f qinstall-$arch.sh ]; then
					cp -af qinstall-$arch.sh qinstall.sh
				fi
				;;
			--build-model)	QDK_BUILD_MODEL="$2"
				[ -n "$QDK_BUILD_MODEL" ] || err_msg "--build-model: no model"
				shift
				;;
			--root)		QDK_ROOT_DIR="$2"
				[ -n "$QDK_ROOT_DIR" ] || err_msg "--root: no directory"
				shift
				;;
			--extract)	qbuild_extract_file="$2"
				qbuild_extract_dir="$3"
				[ -n "$qbuild_extract_file" ] || err_msg "--extract: no file"
				shift
				;;
			--exclude)	local rsync_pattern="$2"
				[ -n "$rsync_pattern" ] || err_msg "--exclude: no pattern"
				QDK_RSYNC_EXCLUDE="$QDK_RSYNC_EXCLUDE --exclude=$rsync_pattern"
				shift
				;;
			--exclude-from)	local rsync_file="$2"
				[ -n "$rsync_file" ] || err_msg "--exclude-from: no file"
				QDK_RSYNC_EXCLUDE_FROM="--exclude-from=$rsync_file"
				shift
				;;
			--section|-s)	local section="$2"
				[ -n "$section" ] || err_msg "--section: no section"
				add_qdk_definitions "$section"
				shift
				;;
			--sign)		qbuild_sign=TRUE ;;
			--gpg-name)	QDK_GPG_NAME="$2"
				[ -n "$QDK_GPG_NAME" ] || err_msg "--gpg-name: no name"
				QDK_SIGNATURE="gpg"
				QDK_SIGN="TRUE"
				shift
				;;
			--verify)	qbuild_verify_file="$2"
				[ -n "$qbuild_verify_file" ] || err_msg "--verify: no file"
				shift
				;;
			--add-sign)	qbuild_add_sign_file="$2"
				[ -n "$qbuild_add_sign_file" ] || err_msg "$1: no file"
				shift
				;;
			--import-key)	qbuild_import_key="$2"
				[ -n "$qbuild_import_key" ] || err_msg "--import-key: no file"
				shift
				;;
			--remove-key)	qbuild_remove_key="$2"
				[ -n "$qbuild_remove_key" ] || err_msg "--remove-key: no name"
				shift
				;;
			--list-keys)	qbuild_list_keys=TRUE ;;
			--query)	qbuild_query_option="$2"
				qbuild_query_qpkg="$3"
				[ -n "$qbuild_query_qpkg" ] || err_msg "--query: missing argument"
				shift
				;;
			--add-code-signing)
				qbuild_add_code_signing_file="$2"
				[ -n "$qbuild_add_code_signing_file" ] || err_msg "--add-code-signing: no qpkg file"
				shift
				;;
			--verify-code-signing)
				qbuild_verify_code_signing_file="$2"
				[ -n "$qbuild_verify_code_signing_file" ] || err_msg "--verify-code-signing: no qpkg file"
				shift
				;;
			--code-signing-cfg)
				qbuild_code_signing_cfg_file="$2"
				[ -n "$qbuild_code_signing_cfg_file" ] || err_msg "--code-signing-cfg: no cfg file"
				shift
				;;
			--allow-no-volume) QDK_ALLOW_NO_VOLUME=1
                ;;
			-*)		qbuild_usage=TRUE ;;
		esac
		shift
	done

	[ -z "$qbuild_quiet" ] || QDK_VERBOSE=$QUIET
	# Check for extra verbose debug messages.
	[ $QDK_VERBOSE -le $DEBUG ] || set -x

	[ -n "$qbuild_version" ] && version
	[ -n "$qbuild_usage" ] && usage
	[ -n "$qbuild_help" ] && help

	QDK_STRICT=${QDK_STRICT:-FALSE}
	QDK_FORCE_CONFIG=${QDK_FORCE_CONFIG:-FALSE}

	if [ -n "$qbuild_sign" ]; then
		[ -n "$QDK_SIGNATURE" ] || err_msg "--sign: QDK_SIGNATURE must be defined"
		case "$QDK_SIGNATURE" in
			gpg)
				QDK_SIGN="TRUE"
				;;
			*)
				err_msg "$QDK_SIGNATURE: not supported signature type"
				;;
		esac
	fi
	if [ "$QDK_SIGN" = "TRUE" ] || [ -n "$qbuild_verify_file" ] ||
	   [ -n "$qbuild_add_sign_file" ] || [ -n "$qbuild_import_key" ] ||
	   [ -n "$qbuild_remove_key" ] || [ -n "$qbuild_list_keys" ]; then
		QDK_GPG_APP="${QDK_GPG_APP:-$(command -v gpg2)}"
		[ -x "$QDK_GPG_APP" ] || err_msg "${QDK_GPG_APP:-gpg2}: command not found"
	fi

	if [ -n "$qbuild_qpkg_name" ]; then
		create_env "$qbuild_qpkg_name"
	elif [ -n "$qbuild_extract_file" ]; then
		extract_qpkg "$qbuild_extract_file" "$qbuild_extract_dir"
	elif [ -n "$qbuild_verify_file" ]; then
		verify_qpkg "$qbuild_verify_file"
	elif [ -n "$qbuild_add_sign_file" ]; then
		add_sign_qpkg "$qbuild_add_sign_file"
	elif [ -n "$qbuild_import_key" ]; then
		import_key "$qbuild_import_key"
	elif [ -n "$qbuild_remove_key" ]; then
		remove_key "$qbuild_remove_key"
	elif [ -n "$qbuild_list_keys" ]; then
		list_keys
	elif [ -n "$qbuild_query_qpkg" ]; then
		query_qpkg "$qbuild_query_option" "$qbuild_query_qpkg"
	elif [ -n "$qbuild_add_code_signing_file" ] && [ -n "$qbuild_code_signing_cfg_file" ]; then
		add_code_signing "$qbuild_add_code_signing_file" "$qbuild_code_signing_cfg_file"
	elif [ -n "$qbuild_add_code_signing_file" ]; then
		add_code_signing "$qbuild_add_code_signing_file"
	elif [ -n "$qbuild_verify_code_signing_file" ] && [ -n "$qbuild_code_signing_cfg_file" ]; then
		verify_code_signing ${qbuild_verify_code_signing_file} ${qbuild_code_signing_cfg_file}
		exit $?
	elif [ -n "$qbuild_verify_code_signing_file" ]; then
		verify_code_signing ${qbuild_verify_code_signing_file}
		exit $?
	else
		build_qpkg
		exit $?
	fi
}

# System-wide definitions

if [ -f "/etc/config/qdk.conf" ]; then
    QDK_CONF_FILE="/etc/config/qdk.conf"
else
    if [ -f "${QDK_PATH}/qdk.conf" ]; then
        QDK_CONF_FILE="${QDK_PATH}/qdk.conf"
    elif [ -f "./qdk.conf" ]; then
        QDK_CONF_FILE="./qdk.conf"
    else
        err_msg "qdk.conf: not found"
    fi
fi

. ${QDK_CONF_FILE}

# User definitions
QDK_USER_CONFIG_FILE="${QDK_USER_CONFIG_FILE:-$HOME/.qdkrc}"
add_qdk_definitions DEFAULT

main "$@"
