#!/bin/bash
#
# go-xcat - Install xCAT automatically.
#
# Version 1.0.29
#
# Copyright (C) 2016, 2017, 2018 International Business Machines
# Eclipse Public License, Version 1.0 (EPL-1.0)
#     <http://www.eclipse.org/legal/epl-v10.html>
#
# 2016-06-16 GONG Jie <gongjie@linux.vnet.ibm.com>
#     - Draft
# 2016-06-20 GONG Jie <gongjie@linux.vnet.ibm.com>
#     - Released to the field
# 2016-09-20 GONG Jie <gongjie@linux.vnet.ibm.com>
#     - Bug fix
# 2018-03-28 GONG Jie <gongjie@linux.vnet.ibm.com>
#     - Use curl when it is available. Otherwise fall back to use wget
#     - Improved tarball file extension handling
#     - Disable xCAT-core.repo and xCAT-dep.repo if they exist
#

function usage()
{
	local script="${0##*/}"
	local version="$(version)"

	while read -r ; do echo "${REPLY}" ; done <<-EOF
	${script} version ${version}

	Usage: ${script} [OPTION]... [ACTION]
	Install xCAT automatically

	Options:
	Mandatory arguments to long options are mandatory for short options too.
	  -h, --help                    display this help and exit
	  --xcat-core=[URL]             use a different URL or path for the xcat-core
	                                repository
	  --xcat-dep=[URL]              use a different URL or path for the xcat-dep
	                                repository
	  -x, --xcat-version=[VERSION]  specify the version of xCAT; imply the subdirectory
	                                of corresponding xCAT version under
	                                  http://xcat.org/files/xcat/repos/yum/  or
	                                  http://xcat.org/files/xcat/repos/apt/
	                                cannot use with --xcat-core
	  -y, --yes                     answer yes for all questions

	Actions:
	  install                       installs all the latest versions of xcat-core
	                                and xcat-dep packages from the repository
	  update                        updates installed xcat-core packages to the
	                                latest version from the repository

	Examples:
	  ${script}
	  ${script} install
	  ${script} update
	  ${script} --yes install
	  ${script} -x 2.12 -y install
	  ${script} --xcat-version=devel install
	  ${script} --xcat-core=/path/to/xcat-core.tar.bz2 \\
	          --xcat-dep=/path/to/xcat-dep.tar.bz2 install
	  ${script} --xcat-core=http://xcat.org/path/to/xcat-core.tar.bz2 \\
	          --xcat-dep=http://xcat.org/path/to/xcat-dep.tar.bz2 install

	xCAT (Extreme Cloud/Cluster Administration Toolkit): <http://xcat.org/>
	Full documentation at: <http://xcat-docs.readthedocs.io/en/stable/>
	EOF
}

#
# verbose_usage		This function be will be called when user run
#			`go-xcat --long-help'.
#			Including a bunch of secert usage.
#
function verbose_usage()
(
	local script="${0##*/}"

	exec 42< <(usage)

	function println()
	{
		local -i i
		for (( i = 0 ; i < "$1" ; ++i ))
		do
			read -r -u 42
			[[ "$?" -ne "0" ]] && break
			echo "${REPLY}"
		done
	}

	println 7
	println 1 >/dev/null	# Drop a line
	while read -r ; do echo "${REPLY}" ; done <<-EOF
	  -h, --help                    display a simply version of help and exit
	  --long-help                   display this help and exit
	EOF
	println 12
	while read -r ; do echo "${REPLY}" ; done <<-EOF
	  check                         check the version of the installed packages
	                                of xCAT and packages in the repository
	EOF
	println 2
	while read -r ; do echo "${REPLY}" ; done <<-EOF
	  smoke-test                    preform basic tests of the xCAT installation
	EOF
	println 10
	while read -r ; do echo "${REPLY}" ; done <<-EOF
	  ${script} --xcat-core=/path/to/xcat-core.repo install
	  ${script} --xcat-core=/path/to/xcat-core install
	  ${script} --xcat-core=/path/to/xcat-core.tar install
	  ${script} --xcat-core=/path/to/xcat-core.tar.Z install
	  ${script} --xcat-core=/path/to/xcat-core.tar.gz install
	  ${script} --xcat-core=/path/to/xcat-core.tar.bz2 install
	  ${script} --xcat-core=/path/to/xcat-core.tar.xz install
	  ${script} --xcat-core=http://xcat.org/path/to/xcat-core.repo install
	  ${script} --xcat-core=http://xcat.org/path/to/xcat-core install
	  ${script} --xcat-core=http://xcat.org/path/to/xcat-core.tar.bz2 install
	  ${script} --xcat-core=/path/to/xcat-core.repo \\
	          --xcat-dep=/path/to/xcat-dep.repo install
	  ${script} --xcat-core=/path/to/xcat-core \\
	          --xcat-dep=/path/to/xcat-dep install
	EOF
	println 999999	# Print out all the rest of lines

	exec 42<&-
)

#
# version		Print out the version number.
#
function version()
{
	# Read the first ten lines of this script
	for i in {0..9}
	do
		read -r
		[[ ${REPLY} =~ \#\ +[Vv]ersion ]] && echo "${REPLY##* }" && return 0
	done <"$0"
	return 1
}

GO_XCAT_DEFAULT_BASE_URL="http://xcat.org/files/xcat/repos"
GO_XCAT_DEFAULT_INSTALL_PATH="/install/xcat"

# The package list of xcat-core
GO_XCAT_CORE_PACKAGE_LIST=()
GO_XCAT_DEP_PACKAGE_LIST=()

# The package list of all the packages should be installed
GO_XCAT_INSTALL_LIST=(perl-xCAT xCAT xCAT-buildkit xCAT-client
	xCAT-genesis-scripts-ppc64 xCAT-genesis-scripts-x86_64 xCAT-server
	conserver-xcat elilo-xcat grub2-xcat ipmitool-xcat syslinux-xcat
	xCAT-genesis-base-ppc64 xCAT-genesis-base-x86_64 xnba-undi yaboot-xcat)
# For Debian/Ubuntu, it will need a sight different package list
type dpkg >/dev/null 2>&1 &&
GO_XCAT_INSTALL_LIST=(perl-xcat xcat xcat-buildkit xcat-client
	xcat-genesis-scripts-amd64 xcat-genesis-scripts-ppc64 xcat-server
	conserver-xcat elilo-xcat grub2-xcat ipmitool-xcat syslinux-xcat
	xcat-genesis-base-amd64 xcat-genesis-base-ppc64 xnba-undi)

PATH="/usr/sbin:/usr/bin:/sbin:/bin"
export PATH

#
# warn_if_bad		Put out warning message(s) if $1 has bad RC.
#
#	$1	0 (pass) or non-zero (fail).
#	$2+	Remaining arguments printed only if the $1 is non-zero.
#
#	Incoming $1 is returned unless it is 0
#
function warn_if_bad()
{
	local -i rc="$1"
	local script="${0##*/}"

	# Ignore if no problems
	[ "${rc}" -eq "0" ] && return 0

	# Broken
	shift
	echo "${script}: $@" >&2
	return "${rc}"
}

#
# exit_if_bad		Put out error message(s) if $1 has bad RC.
#
#	$1	0 (pass) or non-zero (fail).
#	$2+	Remaining arguments printed only if the $1 is non-zero.
#
#               Exits with 1 unless $1 is 0
#
function exit_if_bad()
{
	warn_if_bad "$@" || exit 1
	return 0
}

#
# check_root_or_exit
#
#	Breaks the script if not running as root.
#
#	If this returns 1, the invoker MUST abort the script.
#
#	Returns 0 if running as root
#	Returns 1 if not (and breaks the script)
#
function check_root_or_exit()
{
	[ "${UID}" -eq "0" ]
	exit_if_bad "$?" "Must be run by UID=0. Actual UID=${UID}."
	return 0
}

#
# check_executes	Check for executable(s)
#
#	Returns 0 if true.
#	Returns 1 if not.
#
function check_executes()
{
	local cmd
	local all_ok="yes"
	for cmd in "$@"
	do
		if ! type "${cmd}" &>/dev/null
		then
			echo "Command \"${cmd}\" not found." >&2
			all_ok="no"
		fi
	done
	[ "${all_ok}" = "yes" ]
}

#
# check_exec_or_exit	Check for required executables.
#
#	Exits (not returns) if commands listed on command line do not exist.
#
#	Returns 0 if true.
#	Exits with 1 if not.
#
function check_exec_or_exit()
{
	check_executes "$@"
	exit_if_bad "$?" "Above listed required command(s) not found."
	return 0
}

TMP_DIR=""

#
# internal_setup	Script setup
#
#	Returns 0 on success.
#	Exits (not returns) with 1 on failure.
#
function internal_setup()
{
	shopt -s extglob

	# Trap exit for internal_cleanup function.
	trap "internal_cleanup" EXIT

	check_exec_or_exit mktemp rm

	umask 0077

	TMP_DIR="$(mktemp -d "/tmp/${0##*/}.XXXXXXXX" 2>/dev/null)"
	[ -d "${TMP_DIR}" ]
	exit_if_bad "$?" "Make temporary directory failed."

	custom_setup
}

#
# internal_cleanup	Script cleanup (reached via trap 0)
#
#	Destory any temporarily facility created by internal_setup.
#
function internal_cleanup()
{
	custom_cleanup

	[ -d "${TMP_DIR}" ] && rm -rf "${TMP_DIR}"
}

#
# custom_setup
#
function custom_setup()
{
	check_exec_or_exit awk cat comm grep printf sleep tee
	check_root_or_exit
}

#
# custom_cleanup
#
function custom_cleanup()
{
	:
}

#
# cleanup_n_exec	Do the cleanup, then execute the command
#
#	$1+	The command to execute
#
function cleanup_n_exec()
{
	internal_cleanup

	exec "$@"
}

internal_setup

# Check operating system
function check_os()
{
	case "${OSTYPE}" in
	"aix"*)     # AIX
		echo "aix"
		;;
	"darwin"*)  # OS X
		echo "darwin"
		;;
	"linux"*)   # Linux
		echo "linux"
		;;
	*)
		# Unknown
		echo "${OSTYPE}"
		;;
	esac
}

# Check instruction set architecture
function check_arch()
{
	case "${HOSTTYPE}" in
	"powerpc64")
		echo "ppc64"
		;;
	"powerpc64le")
		echo "ppc64le"
		;;
	"mipsel")
		echo "mips"
		;;
	"i"?"86")
		echo "i386"
		;;
	*)
		echo "${HOSTTYPE}"
		;;
	esac
}

function check_linux_distro()
{
	local distro="$(source /etc/os-release >/dev/null 2>&1 &&
		echo "${ID}")"
	[[ -z "${distro}" && -f /etc/redhat-release ]] && distro="rhel"
	[[ -z "${distro}" && -f /etc/SuSE-release ]] && distro="sles"
	echo "${distro}"
}

function check_linux_version()
{
	local ver="$(source /etc/os-release >/dev/null 2>&1 &&
		echo "${VERSION_ID}")"
	[[ -z "${ver}" && -f /etc/redhat-release ]] &&
		# Need gawk to do this trick
		ver="$(awk '{ match($0, /([.0-9]+)/, a); print substr($0, a[1, "start"], a[1, "length"]); }' \
			/etc/redhat-release)"
	[[ -z "${ver}" && -f /etc/SuSE-release ]] &&
		ver="$(awk '/VERSION/ { print $NF }' /etc/SuSE-release)"
	echo "${ver}"
}

function function_dispatch()
{
	local base="$1"
	local cmd=""
	local ret=""
	shift
	for cmd in $(compgen -A function "${base}_")
	do
		"${cmd}" "$@"
		ret="$?"
		[[ "${ret}" -ne "255" ]] && break
	done
	[[ "${ret}" -ne "255" ]]
	exit_if_bad "$?" "${base}: unsupported function"
	return "${ret}"
}

#       $@      package names
function check_package_version_rpm()
{
	type rpm >/dev/null 2>&1 || return 255
	local ver=""
	while read -r ver
	do
		if [[ -z "${ver}" || "${ver}" =~ not\ installed ]]
		then
			echo "(not installed)"
		else
			echo "${ver}"
		fi
	done < <(rpm -q --qf '%{version}-%{release}\n' "$@" 2>/dev/null)
	return 0
}

#       $@      package names
function check_package_version_deb()
{
	type dpkg-query >/dev/null 2>&1 || return 255
	local name=""
	local ver=""
	while read -r name ver
	do
		name+=("${name}")
		ver+=("${ver}")
	done < <(dpkg-query --show '--showformat=${Package} ${Version}\n' \
		"$@" 2>/dev/null)
	local -i i
	while [[ -n "$1" ]]
	do
		for i in "${!name[@]}"
		do
			if [[ "$1" = "${name[i]}" ]]
			then
				if [[ -n "${ver[i]}" ]]
				then
					echo "${ver[i]}"
				else
					echo "(not installed)"
				fi
				unset "name[${i}]" "ver[${i}]"
				shift && continue 2
			fi
		done
		echo "(not installed)"
		shift
	done
	return 0
}

function check_package_version()
{
	function_dispatch "${FUNCNAME}" "$@"
}

#       $@      package names
function check_repo_version_dnf()
{
	type dnf >/dev/null 2>&1 || return 255
	local -a name=()
	local -a ver=()
	while read -r name ver
	do
		name+=("${name}")
		ver+=("${ver}")
	done < <(dnf repoquery -q --qf '%{name} %{version}-%{release}' "$@" 2>/dev/null)
	local -i i
	while [[ -n "$1" ]]
	do
		for i in "${!name[@]}"
		do
			if [[ "$1" = "${name[i]}" ]]
			then
				echo "${ver[i]}"
				unset "name[${i}]" "ver[${i}]"
				shift && continue 2
			fi
		done
		echo "(not found)"
		shift
	done
	return 0
}

#       $@      package names
function check_repo_version_yum()
{
	type yum >/dev/null 2>&1 || return 255
	check_executes repoquery
	exit_if_bad "$?" "Install the \`yum-utils' package and rerun."
	local -a name=()
	local -a ver=()
	while read -r name ver
	do
		name+=("${name}")
		ver+=("${ver}")
	done < <(repoquery --qf '%{name} %{version}-%{release}' "$@" 2>/dev/null)
	local -i i
	while [[ -n "$1" ]]
	do
		for i in "${!name[@]}"
		do
			if [[ "$1" = "${name[i]}" ]]
			then
				echo "${ver[i]}"
				unset "name[${i}]" "ver[${i}]"
				shift && continue 2
			fi
		done
		echo "(not found)"
		shift
	done
	return 0
}

#       $@      package names
function check_repo_version_zypper()
{
	type zypper >/dev/null 2>&1 || return 255
	local -a name=()
	local -a ver=()
	while read -r name ver
	do
		name+=("${name}")
		ver+=("${ver}")
	done < <(zypper --no-gpg-checks -n search --match-exact -C -t package \
		-s "$@" 2>/dev/null |
		awk -F ' +\\| +' '/ package / { if ("(" != substr($6, 0, 1)) print $2, $4 }')
	local -i i
	while [[ -n "$1" ]]
	do
		for i in "${!name[@]}"
		do
			if [[ "$1" = "${name[i]}" ]]
			then
				echo "${ver[i]}"
				unset "name[${i}]" "ver[${i}]"
				shift && continue 2
			fi
		done
		echo "(not found)"
		shift
	done
	return 0
}

#       $@      package names
function check_repo_version_apt()
{
	type apt-cache >/dev/null 2>&1 || return 255
	local name=""
	local ver=""
	while read -r name ver
	do
		if [[ "${name}" =~ ^[a-z] ]]
		then
			while [[ -n "$1" ]]
			do
				[[ "${name}" = "${1}:" ]] && break
				echo "(not found)"
				shift
			done
		fi
		if [[ "${name}" = "Candidate:" ]]
		then
			echo "$ver"
			shift
		fi
	done < <(apt-cache policy "$@" 2>/dev/null)
	while [[ -n "$1" ]]
	do
		echo "(not found)"
		shift
	done
	return 0
}

function check_repo_version()
{
	function_dispatch "${FUNCNAME}" "$@"
}

#       $1      repo id
function get_package_list_dnf()
{
	type dnf >/dev/null 2>&1 || return 255
	local repo_id="$1"
	[[ -z "${repo_id}" ]] && return 1
	dnf repoquery -q "--repoid=${repo_id}" --qf "%{name}" 2>/dev/null
}

#       $1      repo id
function get_package_list_yum()
{
	type yum >/dev/null 2>&1 || return 255
	check_executes repoquery
	exit_if_bad "$?" "Install the \`yum-utils' package and rerun."
	local repo_id="$1"
	[[ -z "${repo_id}" ]] && return 1
	repoquery -qa "--repoid=${repo_id}" --qf "%{name}" 2>/dev/null
}

#       $1      repo id
function get_package_list_zypper()
{
	type zypper >/dev/null 2>&1 || return 255
	local repo_id="$1"
	[[ -z "${repo_id}" ]] && return 1
	zypper --no-gpg-checks -n search -r "${repo_id}" 2>/dev/null |
		awk -F ' +\\| +' '/ package$/ { print $2 }'
}

#       $1      repo id
function get_package_list_apt()
{
	[[ -d /var/lib/apt/lists ]] || return 255
	local repo_id="$1"
	[[ -z "${repo_id}" ]] && return 1
	local -i rc=0
	awk '/^Package: / { print $2 }' \
		"/var/lib/apt/lists/"*"_${repo_id}_dists"*"_main_binary-"*"_Packages" \
		2>/dev/null
	# This is a dirty hack, and use recursion.
	# For the `devel' branch of the online repo for apt, it has the
	# subdirectory name of `core-snap' instead of `xcat-core'.
	rc="$?"
	if [[ "${rc}" -ne "0" && "${repo_id}" = "xcat-core" ]]
	then
		"${FUNCNAME}" "core-snap"
		rc="$?"
	fi
	return "${rc}"
}

#       $1      repo id
function get_package_list()
{
	function_dispatch "${FUNCNAME}" "$@"
}

function download_file_curl()
{
	local script="${0##*/}"
	local version="$(version)"
	local user_agent="${script}/${version} (${GO_XCAT_OS}; ${GO_XCAT_ARCH}; ${GO_XCAT_LINUX_DISTRO} ${GO_XCAT_LINUX_VERSION})"
	type curl >/dev/null 2>&1 || return 255
	local url="$1"
	local local_file="$2"
	local log_file="${TMP_DIR}/curl.log.${RANDOM}"
	local -i rc=0
	curl -A "${user_agent}" "${url}" -f -o "${local_file}" -S -s >"${log_file}" 2>&1
	rc="$?"
	if [[ "${rc}" -ne "0" ]]
	then
		while read -r ; do echo "${REPLY}" ; done <"${log_file}"
		echo -n "${script}: \`curl' exited with an error: (exit code ${rc}, "
		case "${rc}" in
		1) echo -n "unsupported protocol" ;;
		2) echo -n "failed to initialize" ;;
		3) echo -n "URL malformed" ;;
		4) echo -n "you probably need another build of libcurl!" ;;
		5) echo -n "couldn't resolve proxy" ;;
		6) echo -n "couldn't resolve host" ;;
		7) echo -n "failed to connect to host" ;;
		8) echo -n "weird server reply" ;;
		9) echo -n "FTP access denied" ;;
		10) echo -n "FTP accept failed" ;;
		11) echo -n "FTP weird PASS reply" ;;
		12) echo -n "During an active FTP session while waiting for the server to connect back to curl, the timeout expired." ;;
		13) echo -n "FTP weird PASV reply" ;;
		14) echo -n "FTP weird 227 format" ;;
		15) echo -n "FTP can't get host" ;;
		16) echo -n "HTTP/2 error" ;;
		17) echo -n "FTP couldn't set binary" ;;
		18) echo -n "Partial file" ;;
		19) echo -n "FTP couldn't download/access the given file" ;;
		21) echo -n "FTP quote error" ;;
		22) echo -n "HTTP page not retrieved" ;;
		23) echo -n "write error" ;;
		25) echo -n "FTP couldn't STOR file" ;;
		26) echo -n "read error" ;;
		27) echo -n "out of memory" ;;
		28) echo -n "operation timeout" ;;
		30) echo -n "FTP PORT failed" ;;
		31) echo -n "FTP  couldn't  use REST" ;;
		33) echo -n "HTTP range error" ;;
		34) echo -n "HTTP post error" ;;
		35) echo -n "SSL connect error" ;;
		36) echo -n "bad download resume" ;;
		37) echo -n "FILE couldn't read file" ;;
		38) echo -n "LDAP cannot bind" ;;
		39) echo -n "LDAP search failed." ;;
		41) echo -n "function not found" ;;
		42) echo -n "aborted by callback" ;;
		43) echo -n "internal error" ;;
		45) echo -n "interface error" ;;
		47) echo -n "too many redirects" ;;
		48) echo -n "unknown option specified to libcurl" ;;
		49) echo -n "malformed telnet option" ;;
		51) echo -n "the peer's SSL certificate or SSH MD5 fingerprint was not OK" ;;
		52) echo -n "the server didn't reply anything, which here is considered an error" ;;
		53) echo -n "SSL crypto engine not found" ;;
		54) echo -n "cannot set SSL crypto engine as default" ;;
		55) echo -n "failed sending network data" ;;
		56) echo -n "failure in receiving network data" ;;
		58) echo -n "problem with the local certificate" ;;
		59) echo -n "couldn't use specified SSL cipher" ;;
		60) echo -n "peer certificate cannot be authenticated with known CA certificates" ;;
		61) echo -n "unrecognized transfer encoding." ;;
		62) echo -n "invalid LDAP URL" ;;
		63) echo -n "maximum file size exceeded" ;;
		64) echo -n "requested FTP SSL level failed" ;;
		65) echo -n "sending the data requires a rewind that failed" ;;
		66) echo -n "failed to initialise SSL Engine" ;;
		67) echo -n "the user name, password, or similar was not accepted and curl failed to log in" ;;
		68) echo -n "file not found on TFTP server" ;;
		69) echo -n "permission problem on TFTP server" ;;
		70) echo -n "out of disk space on TFTP server" ;;
		71) echo -n "illegal TFTP operation" ;;
		72) echo -n "unknown TFTP transfer ID" ;;
		73) echo -n "file already exists (TFTP)" ;;
		74) echo -n "no such user (TFTP)" ;;
		75) echo -n "character conversion failed" ;;
		76) echo -n "character conversion functions required" ;;
		77) echo -n "problem with reading the SSL CA cert" ;;
		78) echo -n "the resource referenced in the URL does not exist" ;;
		79) echo -n "an unspecified error occurred during the SSH session" ;;
		80) echo -n "failed to shut down the SSL connection" ;;
		82) echo -n "could not load CRL file, missing or wrong format" ;;
		83) echo -n "issuer check failed" ;;
		84) echo -n "the FTP PRET command failed" ;;
		85) echo -n "RTSP: mismatch of CSeq numbers" ;;
		86) echo -n "RTSP: mismatch of Session Identifiers" ;;
		87) echo -n "unable to parse FTP file list" ;;
		88) echo -n "FTP chunk callback reported error" ;;
		89) echo -n "no connection available, the session will be queued" ;;
		90) echo -n "SSL public key does not matched pinned public key" ;;
		*) echo -n "unknown error" ;;
		esac
		echo ")"
		echo "  ... while downloading \`${url}'"
	fi >&2
	[[ "${rc}" -eq "0" ]]
}

function download_file_wget()
{
	local script="${0##*/}"
	local version="$(version)"
	local user_agent="${script}/${version} (${GO_XCAT_OS}; ${GO_XCAT_ARCH}; ${GO_XCAT_LINUX_DISTRO} ${GO_XCAT_LINUX_VERSION})"
	type wget >/dev/null 2>&1 || return 255
	local url="$1"
	local local_file="$2"
	local log_file="${TMP_DIR}/wget.log.${RANDOM}"
	local -i rc=0
	wget -U "${user_agent}" -nv "${url}" -O "${local_file}" -o "${log_file}"
	rc="$?"
	if [[ "${rc}" -ne "0" ]]
	then
		while read -r ; do echo "${REPLY}" ; done <"${log_file}"
		echo -n "${script}: \`wget' exited with an error: (exit code ${rc}, "
		case "${rc}" in
		1) echo -n "generic error" ;;
		2) echo -n "parse error" ;;
		3) echo -n "file I/O error" ;;
		4) echo -n "network failure" ;;
		5) echo -n "SSL verification failure" ;;
		6) echo -n "username/password authentication failure" ;;
		7) echo -n "protocol errors" ;;
		8) echo -n "server issued an error response" ;;
		*) echo -n "unknown error" ;;
		esac
		echo ")"
		echo "  ... while downloading \`${url}'"
	fi >&2
	[[ "${rc}" -eq "0" ]]
}

function download_file()
{
	function_dispatch "${FUNCNAME}" "$@"
}

#       $1      repo file
#       $2      repo id
function add_repo_by_file_yum()
{
	[[ -d /etc/yum.repos.d ]] || return 255
	local repo_file="$1"
	local repo_id="$2"
	[[ -f "${repo_file}" ]]
	exit_if_bad "$?" "${repo_file}: no such file"
	[[ -r "${repo_file}" ]]
	exit_if_bad "$?" "${repo_file}: permission denied"
	[[ -n "${repo_id}" ]]
	exit_if_bad "$?" "empty repo id"
	[[ "${repo_id}" =~ ^[a-zA-Z][0-9a-zA-Z-]*$ ]]
	exit_if_bad "$?" "${repo_id} illegal character in repo id"
	local tmp="${TMP_DIR}/tmp_repo_file_${repo_id}.repo"
	{
		echo "[${repo_id}]"
		grep -v '^\[' "${repo_file}"
	} >"${tmp}"
	remove_repo_yum "${repo_id}" &&
		cp "${tmp}" "/etc/yum.repos.d/${repo_id}.repo"
}

#       $1      repo file
#       $2      repo id
function add_repo_by_file_zypper()
{
	type zypper >/dev/null 2>&1 || return 255
	local repo_file="$1"
	local repo_id="$2"
	[[ -f "${repo_file}" ]]
	exit_if_bad "$?" "${repo_file}: no such file"
	[[ -r "${repo_file}" ]]
	exit_if_bad "$?" "${repo_file}: permission denied"
	[[ -n "${repo_id}" ]]
	exit_if_bad "$?" "empty repo id"
	[[ "${repo_id}" =~ ^[a-zA-Z][0-9a-zA-Z-]*$ ]]
	exit_if_bad "$?" "${repo_id} illegal character in repo id"
	local tmp="${TMP_DIR}/tmp_repo_file_${repo_id}.repo"
	{
		echo "[${repo_id}]"
		grep -v '^\[' "${repo_file}"
	} >"${tmp}"
	remove_repo_zypper "${repo_id}" &&
		zypper addrepo "${tmp}" >/dev/null 2>&1
}

#       $1      repo file
#       $2      repo id
function add_repo_by_file_apt()
{
	[[ -d /etc/apt/sources.list.d/ ]] || return 255
	local repo_file="$1"
	local repo_id="$2"
	[[ -f "${repo_file}" ]]
	exit_if_bad "$?" "${repo_file}: no such file"
	[[ -r "${repo_file}" ]]
	exit_if_bad "$?" "${repo_file}: permission denied"
	[[ -n "${repo_id}" ]]
	exit_if_bad "$?" "empty repo id"
	[[ "${repo_id}" =~ ^[a-zA-Z][0-9a-zA-Z-]*$ ]]
	exit_if_bad "$?" "${repo_id} illegal character in repo id"
	cp "${repo_file}" "/etc/apt/sources.list.d/${repo_id}.list"
}

function add_repo_by_file()
{
	function_dispatch "${FUNCNAME}" "$@"
}

#       $1      archive
#       $2      repo id
#       $3      install path
function extract_archive()
{
	local archive="$1"
	local repo_id="$2"
	local install_path="$3"
	local umask="$(umask)"
	local -i ret=0

	[[ -f "${archive}" ]]
	warn_if_bad "$?" "${archive}: archive file not found!" || return 1

	umask 0022
	mkdir -p "${install_path}" 2>/dev/null
	ret="$?"
	umask "${umask}"
	warn_if_bad "${ret}" "Failed to create directory \`${install_path}'" ||
		return 1

	case "${archive##*/}" in
	*".tar.Z")
		check_executes uncompress tar grep || return 1
		uncompress -c "${archive}" | tar -t -f - | grep -v "^${repo_id}/"
		[[ "${PIPESTATUS[0]}" -eq 0 && "${PIPESTATUS[1]}" -eq 0 &&
			"${PIPESTATUS[2]}" -eq 1 ]]
		warn_if_bad "$?" "${archive}: bad compressed tarball" || return 1
		rm -rf "${install_path}/${repo_id}"
		uncompress -c "${archive}" | ( cd "${install_path}" && tar -x -f - )
		;;
	*".tz"|*".tgz"|*".tar.gz")
		check_executes gzip tar grep || return 1
		gzip -d -c "${archive}" | tar -t -f - | grep -v "^${repo_id}/"
		[[ "${PIPESTATUS[0]}" -eq 0 && "${PIPESTATUS[1]}" -eq 0 &&
			"${PIPESTATUS[2]}" -eq 1 ]]
		exit_if_bad "$?" "${archive}: bad gzipped tarball"
		rm -rf "${install_path}/${repo_id}"
		gzip -d -c "${archive}" | ( cd "${install_path}" && tar -x -f - )
		;;
	*".tbz"|*".tbz2"|*".tar.bz"|*".tar.bz2")
		check_executes bzip2 tar grep || return 1
		bzip2 -d -c "${archive}" | tar -t -f - | grep -v "^${repo_id}/"
		[[ "${PIPESTATUS[0]}" -eq 0 && "${PIPESTATUS[1]}" -eq 0 &&
			"${PIPESTATUS[2]}" -eq 1 ]]
		warn_if_bad "$?" "${archive}: bad bzipped tarball" || return 1
		rm -rf "${install_path}/${repo_id}"
		bzip2 -d -c "${archive}" | ( cd "${install_path}" && tar -x -f - )
		;;
	*".txz"|*".tar.xz")
		check_executes xz tar grep || return 1
		xz -d -c "${archive}" | tar -t -f - | grep -v "^${repo_id}/"
		[[ "${PIPESTATUS[0]}" -eq 0 && "${PIPESTATUS[1]}" -eq 0 &&
			"${PIPESTATUS[2]}" -eq 1 ]]
		warn_if_bad "$?" "${archive}: bad xzed tarball" || return 1
		rm -rf "${install_path}/${repo_id}"
		xz -d -c "${archive}" | ( cd "${install_path}" && tar -x -f - )
		;;
	*".tar")
		check_executes tar grep || return 1
		tar -t -f "${archive}" | grep -v "^${repo_id}/"
		[[ "${PIPESTATUS[0]}" -eq 0 && "${PIPESTATUS[1]}" -eq 1 ]]
		warn_if_bad "$?" "${archive}: bad tarball" || return 1
		rm -rf "${install_path}/${repo_id}"
		( cd "${install_path}" && tar -x -f - ) <"${archive}"
		;;
	*)
		warn_if_bad "1" "${archive}: unknown archive file"
		return 1
		;;
	esac
	[[ -d "${install_path}/${repo_id}" ]]
	warn_if_bad "$?" "${install_path}/${repo_id}: no such directory"
}

#       $1      URL
#       $2      repo id
function add_repo_by_url_yum_or_zypper()
{
	local -a yes=()
	[[ "$1" = "-y" ]] && yes=("-y") && shift
	local url="$1"
	local repo_id="$2"
	local tmp=""
	local install_path="${GO_XCAT_DEFAULT_INSTALL_PATH}"
	case "${url%%://*}" in
	"ftp"|"http"|"https")
		case "${url##*/}" in
		*".repo"|*".tar"|*".tar.Z"|*".tar.bz"|*".tar.bz2"|*".tar.gz"|*".tar.xz"|*".tbz"|*".tbz2"|*".tgz"|*".tz"|*".txz")
			# an online repo or tarball
			tmp="${TMP_DIR}/tmp_${url##*/}"
			download_file "${url}" "${tmp}"
			warn_if_bad "$?" \
				"download ${repo_id} resource failed" ||
				return 1
			url="${tmp}"
			;;
		*) # assume it is the base url of the repo
			tmp="${TMP_DIR}/tmp_repo.repo"
			while read -r ; do echo "${REPLY}" ; done >"${tmp}" <<-EOF
			[${repo_id}]
			name=${repo_id}
			baseurl=${url}
			enabled=1
			gpgcheck=0
			EOF
			add_repo_by_file "${tmp}" "${repo_id}"
			return "$?"
			;;
		esac
		;;
	"file")
		url="${url#file://}"
		;;
	esac
	if [[ -f "${url}" ]]
	then
		case "${url##*.}" in
		"repo") # local repo file
			add_repo_by_file "${url}" "${repo_id}"
			return "$?"
			;;
		esac
		extract_archive "${url}" "${repo_id}" "${install_path}"
		warn_if_bad "$?" "extract ${repo_id} archive file failed" ||
			return 1
		url="${install_path}/${repo_id}"
	fi
	if [[ -d "${url}" ]]
	then
		# make sure it is an absolute pathname.
		[[ "${url:0:1}" = "/" ]] || url="${PWD}/${url}"
		# directory
		tmp="${TMP_DIR}/tmp_repo.repo"
		while read -r ; do echo "${REPLY}" ; done >"${tmp}" <<-EOF
		[${repo_id}]
		name=${repo_id}
		baseurl=file://${url}
		enabled=1
		gpgcheck=0
		EOF
		add_repo_by_file "${tmp}" "${repo_id}"
		return "$?"
	fi
	warn_if_bad "1" "invalid ${repo_id} URL"
}

#       $1      URL
#       $2      repo id
function add_repo_by_url_apt()
{
	[[ -d /etc/apt/sources.list.d/ ]] || return 255
	local -a yes=()
	[[ "$1" = "-y" ]] && yes=("-y") && shift
	local url="$1"
	local repo_id="$2"
	local tmp=""
	local install_path="${GO_XCAT_DEFAULT_INSTALL_PATH}"
	local codename="$(source /etc/lsb-release >/dev/null 2>&1 &&
		echo "${DISTRIB_CODENAME}")"
	[[ -n "${codename}" ]]
	warn_if_bad "$?" "unknown debian/ubuntu codename" || return 1
	case "${url%%://*}" in
	"ftp"|"http"|"https"|"ssh")
		case "${url##*/}" in
		*".tar"|*".tar.Z"|*".tar.bz"|*".tar.bz2"|*".tar.gz"|*".tar.xz"|*".tbz"|*".tbz2"|*".tgz"|*".tz"|*".txz")
			# an online tarball
			tmp="${TMP_DIR}/tmp_${url##*/}"
			download_file "${url}" "${tmp}"
			warn_if_bad "$?" \
				"download ${repo_id} resource failed" ||
				return 1
			url="${tmp}"
			;;
		*) # assume it is the base url of the repo
			tmp="${TMP_DIR}/tmp_repo.list"
			echo "deb [arch=$(dpkg --print-architecture)] ${url} ${codename} main" >"${tmp}"
			add_repo_by_file_apt "${tmp}" "${repo_id}"
			return "$?"
			;;
		esac
		;;
	"file")
		url="${url#file://}"
		;;
	esac
	if [[ -f "${url}" ]]
	then
		extract_archive "${url}" "${repo_id}" "${install_path}"
		warn_if_bad "$?" "extract ${repo_id} archive file failed" ||
			return 1
		url="${install_path}/${repo_id}"
	fi
	if [[ -d "${url}" ]]
	then
		# make sure it is an absolute pathname.
		[[ "${url:0:1}" = "/" ]] || url="${PWD}/${url}"
		# directory
		tmp="${TMP_DIR}/tmp_repo.list"
		echo "deb [arch=$(dpkg --print-architecture)] file://${url} ${codename} main" >"${tmp}"
		add_repo_by_file_apt "${tmp}" "${repo_id}"
		return "$?"
	fi
	warn_if_bad "1" "invalid ${repo_id} URL"
}

function add_repo_by_url()
{
	function_dispatch "${FUNCNAME}" "$@"
}

#       $1      repo id
function remove_repo_yum()
{
	type yum >/dev/null 2>&1 || return 255
	local repo_id="$1"
	# This deleting method is not good enough. Since there could be more
	# than one repository definitions in a single repo file.
	# This is a quick and dirty method.
	rm -f $(grep -l "^\[${repo_id}\]$" "/etc/yum.repos.d/"*".repo" 2>/dev/null)
	case "${repo_id}" in
	"xcat-core")
		mv /etc/yum.repos.d/xCAT-core.repo{,.nouse} 2>/dev/null
		;;
	"xcat-dep")
		mv /etc/yum.repos.d/xCAT-dep.repo{,.nouse} 2>/dev/null
		;;
	esac
	yum clean metadata
	:
}

#       $1      repo id
function remove_repo_zypper()
{
	type zypper >/dev/null 2>&1 || return 255
	local repo_id="$1"
	zypper removerepo "${repo_id}"
	case "${repo_id}" in
	"xcat-core")
		mv /etc/zypp/repos.d/xCAT-core.repo{,.nouse} 2>/dev/null
		;;
	"xcat-dep")
		mv /etc/zypp/repos.d/xCAT-dep.repo{,.nouse} 2>/dev/null
		;;
	esac
	:
}

#       $1      repo id
function remove_repo_apt()
{
	[[ -d "/etc/apt/sources.list.d" ]] || return 255
	local repo_id="$1"
	rm -f "/etc/apt/sources.list.d/${repo_id}.list"
}

function remove_repo()
{
	function_dispatch "${FUNCNAME}" "$@"
}

#       $1      URL
#       $2      version
#               can be "2.10", "2.11", "2.12", "latest" or "devel"
function add_xcat_core_repo_yum_or_zypper()
{
	type yum >/dev/null 2>&1 || type zypper >/dev/null 2>&1 || return 255
	local -a yes=()
	[[ "$1" = "-y" ]] && yes=("-y") && shift
	local url="$1"
	local ver="$2"
	local tmp=""
	[[ -z "${ver}" ]] && ver="latest"
	if [[ -z "${url}" ]]
	then
		case "${ver}" in
		"devel")
			url="${GO_XCAT_DEFAULT_BASE_URL}/yum/devel/core-snap"
			;;
		*)
			url="${GO_XCAT_DEFAULT_BASE_URL}/yum/${ver}/xcat-core"
			;;
		esac
	fi
	add_repo_by_url_yum_or_zypper "${url}" "xcat-core"
}

#       $1      URL
#       $2      version
#               can be "2.10", "2.11", "2.12", "latest" or "devel"
function add_xcat_core_repo_apt()
{
	[[ -d "/etc/apt/sources.list.d" ]] || return 255
	local -a yes=()
	[[ "$1" = "-y" ]] && yes=("-y") && shift
	local url="$1"
	local ver="$2"
	local tmp=""
	[[ -z "${ver}" ]] && ver="latest"
	if [[ -z "${url}" ]]
	then
		# get the apt.key
		local url="${GO_XCAT_DEFAULT_BASE_URL}/apt/apt.key"
		local tmp="${TMP_DIR}/tmp_xcat.key"
		download_file "${url}" "${tmp}"
		warn_if_bad "$?" "download xcat apt key failed" || return 1
		apt-key add "${tmp}" >/dev/null 2>&1
		warn_if_bad "$?" "import xcat apt key failed" || return 1
		case "${ver}" in
		"devel")
			url="${GO_XCAT_DEFAULT_BASE_URL}/apt/devel/core-snap"
			;;
		*)
			url="${GO_XCAT_DEFAULT_BASE_URL}/apt/${ver}/xcat-core"
			;;
		esac
	fi
	add_repo_by_url_apt "${url}" "xcat-core"
}

function add_xcat_core_repo()
{
	function_dispatch "${FUNCNAME}" "$@"
}

function add_xcat_dep_repo_yum_or_zypper()
{
	type yum >/dev/null 2>&1 || type zypper >/dev/null 2>&1 || return 255
	local -a yes=()
	[[ "$1" = "-y" ]] && yes=("-y") && shift
	local url="$1"
	local ver="$2"
	[[ -z "${ver}" ]] && ver="latest"
	local tmp=""
	local install_path="${GO_XCAT_DEFAULT_INSTALL_PATH}"
	local distro="${GO_XCAT_LINUX_DISTRO}${GO_XCAT_LINUX_VERSION%%.*}"
	case "${distro}" in
	"centos"*)             distro="rh${distro#centos}" ;;
	"fedora10"|"fedora11") distro="fedora9" ;;
	"fedora1"[678])        distro="rh6" ;;
	"fedora19"|"fedora2"?) distro="rh7" ;;
	"rhel"*)               distro="rh${distro#rhel}" ;;
	"sles"*)               ;;
	*) warn_if_bad 1 "${distro}: unsupported Linux distro" || return 1
	esac
	[[ -z "${url}" ]] &&
		url="${GO_XCAT_DEFAULT_BASE_URL}/yum/${ver}/xcat-dep"
	case "${url##*.}" in
	"repo") # local repo file
		add_repo_by_url_yum_or_zypper "${url}" "xcat-dep"
		return "$?"
		;;
	esac
	case "${url%%://*}" in
	"ftp"|"http"|"https")
		case "${url##*/}" in
		*".tar"|*".tar.Z"|*".tar.bz"|*".tar.bz2"|*".tar.gz"|*".tar.xz"|*".tbz"|*".tbz2"|*".tgz"|*".tz"|*".txz")
			# an online archive file
			tmp="${TMP_DIR}/tmp_${url##*/}"
			download_file "${url}" "${tmp}"
			warn_if_bad "$?" "download xcat-dep archive file failed" \
				|| return 1
			url="${tmp}"
			;;
		*)
			url="${url}/${distro}/${GO_XCAT_ARCH}"
			add_repo_by_url_yum_or_zypper "${url}" "xcat-dep"
			return "$?"
			;;
		esac
		;;
	"file")
		url="${url#file://}"
		;;
	esac
	if [[ -f "${url}" ]]
	then
		extract_archive "${url}" "xcat-dep" "${install_path}"
		warn_if_bad "$?" "extract xcat-dep archive file failed" ||
			return 1
		url="${install_path}/xcat-dep"
	fi
	if [[ -d "${url}" ]]
	then
		# make sure it is an absolute pathname.
		[[ "${url:0:1}" = "/" ]] || url="${PWD}/${url}"
		url="${url}/${distro}/${GO_XCAT_ARCH}"
		add_repo_by_url_yum_or_zypper "${url}" "xcat-dep"
		return "$?"
	fi
	warn_if_bad "1" "invalid xcat-dep URL"
}

function add_xcat_dep_repo_apt()
{
	[[ -d "/etc/apt/sources.list.d" ]] || return 255
	local -a yes=()
	[[ "$1" = "-y" ]] && yes=("-y") && shift
	local url="$1"
	local ver="$2"
	[[ -z "${ver}" ]] && ver="latest"
	[[ -z "${url}" ]] &&
		url="${GO_XCAT_DEFAULT_BASE_URL}/apt/${ver}/xcat-dep"
	add_repo_by_url_apt "${url}" "xcat-dep"
}

function add_xcat_dep_repo()
{
	function_dispatch "${FUNCNAME}" "$@"
}

function update_repo_dnf()
{
	type dnf >/dev/null 2>&1 || return 255
	dnf --nogpgcheck updateinfo
}

function update_repo_yum()
{
	type yum >/dev/null 2>&1 || return 255
	# Check if `yum' support `updateinfo' command.
	yum --help 2>/dev/null | grep -q "^updateinfo" >/dev/null 2>&1
	warn_if_bad "$?" "Lacking support of \`updateinfo' command for \`yum'."
	warn_if_bad "$?" "Please rerun after install package \`yum-plugin-security'."
	exit_if_bad "$?" "And please update package \`yum' to at least version 3.2.29-17."
	yum --nogpgcheck updateinfo
}

function update_repo_zypper()
{
	type zypper >/dev/null 2>&1 || return 255
	zypper --gpg-auto-import-keys -n refresh
}

function update_repo_apt()
{
	type apt-get >/dev/null 2>&1 || return 255
	apt-get --allow-unauthenticated update
}

function update_repo()
{
	function_dispatch "${FUNCNAME}" "$@"
}

function install_packages_dnf()
{
	type dnf >/dev/null 2>&1 || return 255
	local -a yes=()
	[[ "$1" = "-y" ]] && yes=("-y") && shift
	dnf --nogpgcheck "${yes[@]}" install "$@"
}

function install_packages_yum()
{
	type yum >/dev/null 2>&1 || return 255
	local -a yes=()
	[[ "$1" = "-y" ]] && yes=("-y") && shift
	yum --nogpgcheck "${yes[@]}" install "$@"
}

# Dirty workaround on SLES 11 SP4
function github_issue_5503_workaround()
{
	[[ "${GO_XCAT_LINUX_DISTRO}" = "sles" ]] || return 0
	[[ "${GO_XCAT_LINUX_VERSION}" =~ ^11(\.[0-4]){0,1}$ ]] || return 0
	rpm -e --allmatches gpg-pubkey-ca548a47-5b2c830b >/dev/null 2>&1
	return 0
}

function install_packages_zypper()
{
	type zypper >/dev/null 2>&1 || return 255
	github_issue_5503_workaround
	local -a yes=()
	[[ "$1" = "-y" ]] && yes=("-n") && shift
	zypper --no-gpg-checks "${yes[@]}" install --force-resolution "$@"
}

function install_packages_apt()
{
	type apt-get >/dev/null 2>&1 || return 255
	local -a yes=()
	[[ "$1" = "-y" ]] && yes=("-y") && shift
	apt-get --allow-unauthenticated install "${yes[@]}" "$@"
}

function install_packages()
{
	function_dispatch "${FUNCNAME}" "$@"
}

#       $1      -y
function install_xcat()
{
	install_packages "$@" "${GO_XCAT_INSTALL_LIST[@]}"
}

#       $1      -y
function update_xcat()
{
	local -i i=0
	local ver=""
	local -a xcat_core_package_list
	xcat_core_package_list=($(get_package_list xcat-core))
	exit_if_bad "$?" "Failed to get package list from repository \`xcat-core'."
	local -a install_list=($(
		comm <(echo "${GO_XCAT_INSTALL_LIST[@]}") \
			<(echo "${xcat_core_package_list[@]}")
	))
	for i in "${!install_list[@]}"
	do
		read -r ver
		[[ "${ver}" = "(not installed)" ]] &&
			unset "install_list[${i}]"
	done < <(check_package_version "${install_list[@]}")
	[[ "${#install_list[@]}" -gt "0" ]]
	warn_if_bad "$?" "xCAT is not installed."
	exit_if_bad "$?" "In order to install xCAT, please rerun with \`${0##*/} install'."
	install_packages "$@" "${install_list[@]}"
}

function list_xcat_packages()
{
	GO_XCAT_CORE_PACKAGE_LIST=($(get_package_list xcat-core))
	exit_if_bad "$?" "Failed to get package list from repository \`xcat-core'."
	GO_XCAT_DEP_PACKAGE_LIST=($(get_package_list xcat-dep))
	exit_if_bad "$?" "Failed to get package list from repository \`xcat-dep'."

	local -i cols="$(type tput >/dev/null 2>&1 && tput cols)"
	[[ "${cols}" -lt 80 ]] && cols=80
	[[ "${cols}" -gt 90 ]] && cols=90
	[[ -t 1 ]] || cols=90
	local -i first_col=27
	local -i second_col=$(( ( cols - 30 ) / 2 ))
	local -i third_col=${second_col}
	local pkg=""

	echo
	echo "xCAT Core Packages"
	echo "=================="
	echo

	printf "%-${first_col}s %-${second_col}s %-${third_col}s\n" \
		"Package Name" "Installed" "In Repository"
	printf "%-${first_col}s %-${second_col}s %-${third_col}s\n" \
		"------------" "---------" "-------------"
	for pkg in "${GO_XCAT_CORE_PACKAGE_LIST[@]}"
	do
		read -r i_ver && read -u 42 -r r_ver
		printf "%-${first_col}s %-${second_col}s %-${third_col}s\n" \
			"${pkg:0:${first_col}}" \
			"${i_ver:0:${second_col}}" \
			"${r_ver:0:${third_col}}"
	done < <(check_package_version "${GO_XCAT_CORE_PACKAGE_LIST[@]}") \
		42< <(check_repo_version "${GO_XCAT_CORE_PACKAGE_LIST[@]}")

	echo
	echo "xCAT Dependency Packages"
	echo "========================"
	echo

	printf "%-${first_col}s %-${second_col}s %-${third_col}s\n" \
		"Package Name" "Installed" "In Repository"
	printf "%-${first_col}s %-${second_col}s %-${third_col}s\n" \
		"------------" "---------" "-------------"

	for pkg in "${GO_XCAT_DEP_PACKAGE_LIST[@]}"
	do
		read -r i_ver
		read -u 42 -r r_ver
		printf "%-${first_col}s %-${second_col}s %-${third_col}s\n" \
			"${pkg:0:${first_col}}" \
			"${i_ver:0:${second_col}}" \
			"${r_ver:0:${third_col}}"
	done < <(check_package_version "${GO_XCAT_DEP_PACKAGE_LIST[@]}") \
		42< <(check_repo_version "${GO_XCAT_DEP_PACKAGE_LIST[@]}")
}

# Test case 000
# Check if all the xcat-core packages are on the same version
function test_case_000_version()
{
	local ver=""
	local -i ret=0
	while read -r
	do
		[[ "${REPLY}" = "(not installed)" ]] && continue
		[[ -z "${ver}" ]] && ver="${REPLY%%-*}"
		[[ "${ver}" = "${REPLY%%-*}" ]]
		(( ret += $? ))
	done < <(check_package_version "${GO_XCAT_CORE_PACKAGE_LIST[@]}")
	return "${ret}"
}

# Test case 001
# Check if xcatd is running
function test_case_001_xcatd()
{
	local f=""
	local -i ret=0
	for f in /var/run/xcat/{main,install,udp}service.pid
	do
		kill -0 "$(<"${f}")"
		(( ret += $? ))
	done
	for f in /var/run/xcat/cmdlogservice.pid
	do
		[[ -f "${f}" ]] || continue
		kill -0 "$(<"${f}")"
		(( ret += $? ))
	done
	return "${ret}"
}

# Test case 002
# Check if command lsdef can be run
function test_case_002_lsdef()
{
	(source /etc/profile.d/xcat.sh && lsdef)
}

# Perform basic smoke test
function perform_smoke_test()
{
	local test_case=""
	local -i ret=0
	for test_case in $(compgen -A function "test_case_")
	do
		"${test_case}" >"${TMP_DIR}/${test_case}.stdout" \
			2>"${TMP_DIR}/${test_case}.stderr"
		ret="$?"
		if [[ "${ret}" -ne "0" || -s "${TMP_DIR}/${test_case}.stderr" ]]
		then
			# Something went wrong
			echo "-- 8< -- -- -- -- -- -- -- -- -- -- -- -- -- -- --"
			echo "==== ${test_case} failed with exit code ${ret} ===="
			echo "-- 8< ${test_case} stdout -- --"
			while read -r ; do echo "${REPLY}" ; done \
				<"${TMP_DIR}/${test_case}.stdout"
			echo "-- 8< ${test_case} stderr -- --"
			while read -r ; do echo "${REPLY}" ; done \
				<"${TMP_DIR}/${test_case}.stderr"
			echo "-- 8< -- -- -- -- -- -- -- -- -- -- -- -- -- -- --"
			# skip all the remain test cases
			return "${ret}"
		fi >&2
	done
	echo "It seems everything went well. :)"
	return 0
}

GO_XCAT_METERS=""

function show_progress_meters()
{
	[[ -t 2 ]] || return 0
	# Show the progress meters
	(
		declare -i length=0
		while :
		do
			for bar in \
				"...... " \
				".o..o. " \
				"oOooOo " \
				"OoUUoO " \
				"ooUUoo " \
				"oOooOo " \
				"Oo..oO " \
				"o....o "
				#12345678901234567890123456789012345678901
			do
				msg="${bar}"
				for (( i = 0; i < length; ++i ))
				do
					echo -ne "\b"
				done
				length=${#msg}
				echo -n "${msg}"

				sleep 0.1 2>/dev/null || sleep 1
				kill -0 "$$" >/dev/null 2>&1 || break 2
			done
		done
	) >&2 &
	GO_XCAT_METERS="$!"
	disown "${GO_XCAT_METERS}"
}

function stop_progress_meters()
{
	if [[ -t 2 ]]
	then
		kill "${GO_XCAT_METERS}" >/dev/null 2>&1
		echo -ne "\b\b\b\b\b\b\b" >&2
	fi
	echo -n "...... "
}

#
# |\/| _.o._   ._ .__  _ .__.._ _    _  _  _  _  |_  _ .__
# |  |(_||| |  |_)|(_)(_||(_|| | |  (_|(_)(/__>  | |(/_|(/_ o
#              |       _|            _|
#
# Main program goes here.

declare -a GO_XCAT_YES=()
GO_XCAT_ACTION=""
GO_XCAT_CORE_URL=""
GO_XCAT_DEP_URL=""
GO_XCAT_VERSION="latest"

while [ "$#" -gt "0" ]
do
	case "$1" in
	"-h"|"--help")
		usage
		exit 0
		;;
	"--long-help")
		verbose_usage
		exit 0
		;;
	"--xcat-core")
		shift
		GO_XCAT_CORE_URL="$1"
		;;
	"--xcat-core="*)
		GO_XCAT_CORE_URL="${1##--xcat-core=}"
		;;
	"--xcat-dep")
		shift
		GO_XCAT_DEP_URL="$1"
		;;
	"--xcat-dep="*)
		GO_XCAT_DEP_URL="${1##--xcat-dep=}"
		;;
	"--xcat-version")
		shift
		GO_XCAT_VERSION="$1"
		;;
	"--xcat-version="*)
		GO_XCAT_VERSION="${1##--xcat-version=}"
		;;
	"-x")
		shift
		GO_XCAT_VERSION="$1"
		;;
	"-y"|"--yes")
		GO_XCAT_YES=("-y")
		;;
	"-"*)
		warn_if_bad 1 "invalid option -- \`$1'"
		exit_if_bad 1 "Try \`$0 --help' for more information"
		;;
	*)
		[ "$1" == "--" ] && shift
		[ -z "${GO_XCAT_ACTION}" ]
		warn_if_bad "$?" "redundancy action -- \`$1'"
		exit_if_bad "$?" "Try \`$0 --help' for more information"
		GO_XCAT_ACTION="$1"
		;;
	esac
	shift
done

case "${GO_XCAT_ACTION}" in
"check"|"install"|"update")
	;;
"smoke-test")
	perform_smoke_test
	exit "$?"
	;;
"")
	usage
	exit 1
	;;
*)
	warn_if_bad 1 "invalid action -- \`${GO_XCAT_ACTION}'"
	exit_if_bad 1 "Try \`$0 --help' for more information"
	;;
esac

GO_XCAT_OS="$(check_os)"
GO_XCAT_ARCH="$(check_arch)"

while read -r ; do echo "${REPLY}" ; echo "${REPLY}" >&2
	done 2>"${TMP_DIR}/go-xcat.log.000" <<EOF
Operating system:   ${GO_XCAT_OS}
Architecture:       ${GO_XCAT_ARCH}
EOF

case "${GO_XCAT_OS}" in
"linux")
	;;
*)
	exit_if_bad 1 "${GO_XCAT_OS}: unsupported operating system"
	;;
esac

case "${GO_XCAT_ARCH}" in
"ppc64"|"ppc64le"|"x86_64")
	;;
*)
	exit_if_bad 1 "${GO_XCAT_ARCH}: unsupported instruction set architecture"
	;;
esac

GO_XCAT_LINUX_DISTRO="$(check_linux_distro)"
GO_XCAT_LINUX_VERSION="$(check_linux_version)"

while read -r ; do echo "${REPLY}" ; echo "${REPLY}" >&2
	done 2>"${TMP_DIR}/go-xcat.log.001" <<EOF
Linux Distribution: ${GO_XCAT_LINUX_DISTRO}
Version:            ${GO_XCAT_LINUX_VERSION}
EOF

case "${GO_XCAT_LINUX_DISTRO}" in
"centos"|"fedora"|"rhel"|"sles"|"ubuntu")
	;;
*)
	warn_if_bad 1 "${GO_XCAT_LINUX_DISTRO}: unsupported Linux distro"
	;;
esac

echo
echo -n "Reading repositories "
show_progress_meters
ERR_MSG="$({
	if add_xcat_core_repo -y "${GO_XCAT_CORE_URL}" "${GO_XCAT_VERSION}"
	then
		if add_xcat_dep_repo -y "${GO_XCAT_DEP_URL}" "${GO_XCAT_VERSION}"
		then
			update_repo && exit 0
			remove_repo "xcat-dep"
		fi
		remove_repo "xcat-core"
	fi
	exit 1
} 2>&1)"
RET="$?"
stop_progress_meters
if [[ "${RET}" -ne 0 ]]
then
	echo "failed"
	echo "${ERR_MSG}" >&2
	exit "${RET}"
fi
echo "done"

case "${GO_XCAT_ACTION}" in
"check")
	list_xcat_packages
	;;
"install"|"update")
	GO_XCAT_INSTALLER="${GO_XCAT_ACTION}_xcat"
	if [[ "${#GO_XCAT_YES[@]}" -eq "0" ]]
	then
		echo
		echo "xCAT is going to be ${GO_XCAT_ACTION/%e/}ed."
		while true; do

			read -r -p "Continue? [y/n] "
			case "${REPLY}" in
			"Y"*|"y"*)
				break	
				;;
			"N"*|"n"*)
				echo "Good-bye!"
				exit 0
				;;
			*)
				echo "Invalid response!"
				;;
			esac
		done
	fi
	# Use `-y' here. Since the STDOUT is redirect to tee.
	# `yum' does not display the prompt message properly when
	# working with tee.
	"${GO_XCAT_INSTALLER}" -y \
		> >(tee "${TMP_DIR}/${GO_XCAT_INSTALLER}.stdout") \
		2> >(tee "${TMP_DIR}/${GO_XCAT_INSTALLER}.stderr" >&2)
	RET="$?"
	{
		# Creating logs
		echo "-- 8< -- -- -- -- -- -- -- -- -- -- -- -- -- -- --"
		echo "==== ${GO_XCAT_INSTALLER} exited with exit code ${RET} ===="
		echo "-- 8< ${GO_XCAT_INSTALLER} stdout -- --"
		while read -r ; do echo "${REPLY}" ; done \
			<"${TMP_DIR}/${GO_XCAT_INSTALLER}.stdout"
		echo "-- 8< ${GO_XCAT_INSTALLER} stderr -- --"
		while read -r ; do echo "${REPLY}" ; done \
			<"${TMP_DIR}/${GO_XCAT_INSTALLER}.stderr"
		echo "-- 8< -- -- -- -- -- -- -- -- -- -- -- -- -- -- --"
	} >"${TMP_DIR}/go-xcat.log.005"
	if [[ "${RET}" -eq "0" && ! -s "${TMP_DIR}/${GO_XCAT_INSTALLER}.stderr" ]]
	then
		# xCAT has been installed and so far so good
		perform_smoke_test >"${TMP_DIR}/perform_smoke_test.stdout" \
			2>"${TMP_DIR}/perform_smoke_test.stderr"
		RET="$?"
		if [[ "${RET}" -ne "0" || -s "${TMP_DIR}/perform_smoke_test.stderr" ]]
		then
			# Smoke test failed
			echo "-- 8< -- -- -- -- vv -- -- -- vv -- -- -- -- 8< --"
			echo "==== perform_smoke_test failed with exit code ${RET} ===="
			echo "-- 8< perform_smoke_test stdout -- --"
			while read -r ; do echo "${REPLY}" ; done \
				<"${TMP_DIR}/perform_smoke_test.stdout"
			echo "-- 8< perform_smoke_test stderr -- --"
			while read -r ; do echo "${REPLY}" ; done \
				<"${TMP_DIR}/perform_smoke_test.stderr"
			echo "-- 8< -- -- -- -- ^^ -- -- -- ^^ -- -- -- -- 8< --"
		fi
	fi >"${TMP_DIR}/go-xcat.log.008"

	if [[ "${RET}" -ne "0" ]]
	then
		GO_XCAT_LOG="/tmp/go-xcat.log"
		cat "${TMP_DIR}/go-xcat.log."* >"${GO_XCAT_LOG}" 2>/dev/null
		while read -r ; do echo "${REPLY}" ; done >&2 <<-EOF


		Boo-boo
		=======

		Something went wrong. :(

		Please check log file \`${GO_XCAT_LOG}' for more details.
		EOF

		exit "${RET}"
	fi

	case "${GO_XCAT_ACTION}" in
	"install")
		# Only print out this message on install
		while read -r ; do echo "${REPLY}" ; done <<-EOF

		xCAT has been installed!
		========================

		If this is the very first time xCAT has been installed, run the following
		commands to set environment variables into your PATH:

		For sh:
		    source /etc/profile.d/xcat.sh

		For csh:
		    source /etc/profile.d/xcat.csh
		EOF
		;;
	"update")
		while read -r ; do echo "${REPLY}" ; done <<-EOF

		xCAT has been successfully updated!
		EOF
		;;
	esac
	;;
*)
	exit 1
	;;
esac # case "${GO_XCAT_ACTION}" in

exit 0

# vim: filetype=sh
# vim: noautoindent
# vim: tabstop=4 shiftwidth=4 softtabstop=4

# End of file
