#!/bin/sh
#
# pfSense-upgrade
#
# part of pfSense (https://www.pfsense.org)
# Copyright (c) 2015-2016 Rubicon Communications, LLC (Netgate)
# All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

usage() {
	me=$(basename $0)
	cat << EOD >&2
Usage: ${me} [-46bdfhnRUy] [-l logfile] [-p socket] [-c|-u|[-i|-d] pkg_name]"
	-4          - Force IPv4"
	-6          - Force IPv6"
	-b          - Platform is booting"
	-d          - Turn on debug"
	-f          - Force package installation"
	-h          - Show this usage help"
	-l logfile  - Logfile path (defaults to /cf/conf/upgrade_log.txt)"
	-n          - Dry run"
	-p socket   - Write pkg progress to socket"
	-R          - Do not reboot (this can be dangerous)"
	-U          - Do not update repository information"
	-y          - Assume yes as the answer to any possible interaction"
"
The following parameters are mutually exclusive:"
	-c          - Check if upgrade is necessary"
	-i pkg_name - Install package PKG_NAME"
	-r pkg_name - Remove package PKG_NAME"
	-u          - Update repository information"
EOD
}

_echo() {
	local _n=""
	if [ "${1}" = "-n" ]; then
		shift
		_n="-n"
	fi

	if [ -z "${logfile}" ]; then
		logfile=/dev/null
	fi

	echo ${_n} "${@}" | tee -a ${logfile}
}

_exec() {
	local _cmd="${1}"
	local _msg="${2}"
	local _mute="${3}"
	local _ignore_result="${4}"
	local _do_not_exit="${5}"

	local _stdout="${stdout}"

	if [ -z "${_cmd}" ]; then
		return 1
	fi

	if [ "${_mute}" != "mute" ]; then
		_stdout=''
	fi

	[ -n "${_msg}" ] \
	    && _echo -n ">>> ${_msg}... "
	if [ -z "${_stdout}" ]; then
		_echo ""
		# Ref. https://stackoverflow.com/a/30658405
		exec 4>&1
		# Silence annoying pkg message of Major OS upgrade
		# XXX locked packages message should be printed on stderr by pkg
		local _result=$( \
		    { { ${_cmd} 2>&1 3>&-; printf $? 1>&3; } 4>&- \
		    | tee -a ${logfile} \
		    | grep -v \
		    'pkg-static: Warning: Major OS version upgrade detected.' \
		    | grep -v 'is locked and may not be modified' \
		    1>&4; } 3>&1 \
		)
		exec 4>&-
	else
		# Ref. https://stackoverflow.com/a/30658405
		exec 4>&1
		# Silence annoying pkg message of Major OS upgrade
		# XXX locked packages message should be printed on stderr by pkg
		local _result=$( \
		    { { ${_cmd} >${_stdout} 2>&1 3>&-; printf $? 1>&3; } 4>&- \
		    | tee -a ${logfile} \
		    | grep -v \
		    'pkg-static: Warning: Major OS version upgrade detected.' \
		    | grep -v 'is locked and may not be modified' \
		    1>&4; } 3>&1 \
		)
		exec 4>&-
	fi

	if [ ${_result} -eq 0 -o -n "${_ignore_result}" ]; then
		[ -n "${_stdout}" -a -n "${_msg}" ] \
		    && _echo "done."
		return 0
	else
		[ -n "${_stdout}" -a -n "${_msg}" ] \
		    && _echo "failed."
		[ -n "${_do_not_exit}" ] \
		    && return 1 \
		    || _exit 1
	fi
}

_pkg() {
	pkg-static $* 2>/dev/null
	return $?
}

_exit() {
	trap "-" 1 2 15 EXIT

	if [ -n "${delete_pid}" -a -f "${pid_file}" ]; then
		rm -f ${pid_file}
	fi

	if [ -z "${booting}" -o "${boot_stage}" != "2" ]; then
		php /etc/rc.conf_mount_ro
	fi

	if [ -n "${nc_pid}" ] && ps -p ${nc_pid} >/dev/null 2>&1; then
		kill ${nc_pid}
	fi

	if [ -n "${delete_annotation}" ]; then
		_pkg annotate -q -D ${kernel_pkg} next_stage
	fi

	if [ -n "${unlock_pkg}" ]; then
		pkg_unlock pkg
	fi

	if [ -n "${unlock_additional_pkgs}" ]; then
		pkg_unlock "${pkg_prefix}*"
	fi

	local _rc=${1:-"0"}

	# If EVENT_PIPE is defined, GUI is calling
	if [ -n "${progress_socket}" ]; then
		local _need_reboot_str=""
		[ -n "${need_reboot}" ] \
		    && _need_reboot_str=" __REBOOT_AFTER=${reboot_after}"
		_echo "__RC=${_rc}${_need_reboot_str}"
	fi

	exit ${_rc}
}

pkg_with_pb() {
	local _event_pipe=""
	local _cmd="${1}"
	local _msg="${2}"

	if [ -n "${progress_socket}" ]; then
		if [ -e "${progress_socket}" ]; then
			rm -f ${progress_socket}
		fi

		_event_pipe="-o EVENT_PIPE=${progress_socket}"

		nc -lU ${progress_socket} >> ${progress_file} &
		nc_pid=$!

		while [ ! -e "${progress_socket}" ]; do
			sleep 0.1
		done
	fi

	_exec "pkg-static ${_event_pipe} ${_cmd}" "${_msg}"
	nc_pid=""
}

fetch_upgrade_packages() {
	pkg_with_pb "upgrade -F" "Downloading upgrade packages"

	# Workaround for pkg bug https://github.com/freebsd/pkg/issues/1613
	if pkg-static upgrade -Unq 2>&1 | \
	    sed '/ will be affected/,/ be REINSTALLED:/ {; d; };' | \
	    egrep -q '^[[:blank:]]*pkg-[0-9]'; then
		pkg_with_pb "upgrade -fF pkg" "Downloading pkg"
	fi
}

pkg_lock() {
	local _pkgs="$@"

	if [ -z "${_pkgs}" ]; then
		return
	fi

	for _package in ${_pkgs}; do
		for _pkg_name in $(_pkg query -g %n "${_package}"); do
			_locked=$(_pkg query %k ${_pkg_name})
			if [ "${_locked}" != "0" ]; then
				continue
			fi
			_exec "pkg-static lock ${_pkg_name}" \
			    "Locking package ${_pkg_name}" mute
		done
	done
}

pkg_unlock() {
	local _pkgs="$@"

	if [ -z "${_pkgs}" ]; then
		return
	fi

	for _package in ${_pkgs}; do
		for _pkg_name in $(_pkg query -g %n "${_package}"); do
			_locked=$(_pkg query %k ${_pkg_name})
			if [ "${_locked}" != "1" ]; then
				continue
			fi
			_exec "pkg-static unlock ${_pkg_name}" \
			    "Unlocking package ${_pkg_name}" mute
		done
	done
}

set_vital_flag() {
	for _package in "$@"; do
		local _vflag=$(pkg-static query %V ${_package} 2>/dev/null)

		[ "${_vflag}" != "0" ] \
		    && continue

		_exec "pkg-static set -y -v 1 ${_package}" \
		    "Setting vital flag on ${_package}" mute
	done
}

abi_setup() {
	local _freebsd_version=$(uname -r)
	local _pkg_repo_conf="/usr/local/etc/pkg/repos/${product}.conf"

	local _arch=$(uname -p)

	CUR_ABI="FreeBSD:${_freebsd_version%%.*}:${_arch}"
	CUR_ALTABI="freebsd:${_freebsd_version%%.*}"

	if [ "${_arch}" = "armv6" ]; then
		CUR_ALTABI="${CUR_ALTABI}:${_arch}:32:el:eabi:hardfp"
	elif [ "${_arch}" = "i386" ]; then
		CUR_ALTABI="${CUR_ALTABI}:x86:32"
	else
		CUR_ALTABI="${CUR_ALTABI}:x86:64"
	fi

	if [ ! -e ${_pkg_repo_conf} ]; then
		validate_repo_conf
	fi

	local _repo_abi_file=$(readlink ${_pkg_repo_conf})

	if [ -f ${_repo_abi_file%%.conf}.abi ]; then
		ABI=$(cat ${_repo_abi_file%%.conf}.abi)
	else
		ABI=${CUR_ABI}
	fi

	if [ -f ${_repo_abi_file%%.conf}.altabi ]; then
		ALTABI=$(cat ${_repo_abi_file%%.conf}.altabi)
	else
		ALTABI=${CUR_ALTABI}
	fi

	# Make sure pkg.conf is set properly so GUI can work
	echo "ABI=${ABI}" > /usr/local/etc/pkg.conf
	echo "ALTABI=${ALTABI}" >> /usr/local/etc/pkg.conf

	local _pkg_abi=$(_pkg query %q pkg)
	if [ "${CUR_ABI}" != "${_pkg_abi}" -a "${_pkg_abi}" != "${ABI}" ]; then
		reinstall_pkg=1
	fi

	if [ "${CUR_ABI}" = "${ABI}" -o "${CUR_ABI}" = "${ALTABI}" ] ; then
		NEW_MAJOR=""
	else
		NEW_MAJOR=1
		ABI="${ALTABI}"
	fi

	export CUR_ABI CUR_ALTABI ABI ALTABI NEW_MAJOR
}

pkg_update() {
	local _force=""
	local _mute=""

	[ "${1}" = "force" ] \
	    && _force=" -f"

	if [ -z "${_force}" -a -n "${dont_update}" ]; then
		return 0
	fi

	[ "${2}" = "mute" ] \
	    && _mute="mute"

	abi_setup

	_exec "pkg-static update${_force}" "Updating repositories metadata" \
	    ${_mute}
}

pkg_upgrade_repo() {
	if [ -n "${reinstall_pkg}" ] \
	    || [ -z "${NEW_MAJOR}" -a "$(compare_pkg_version pkg)" = "<" ]; then
		pkg_unlock pkg
		_exec "pkg-static upgrade${dont_update} pkg" "Upgrading pkg" \
		    mute
		reinstall_pkg=""
		pkg_update force
	fi

	local _repo_pkg="${product}-repo"

	# Deprecated pa
	if is_pkg_installed ${product}-repo-devel; then
		_exec "pkg-static set -A 1 ${product}-repo-devel" \
		    "Scheduling package ${product}-repo-devel for removal"
		_exec "pkg-static install${dont_update} ${_repo_pkg}" \
		    "Installing ${_repo_pkg}" mute
		_exec "pkg-static delete ${product}-repo-devel" \
		    "Removing ${product}-repo-devel" mute ignore_result
		validate_repo_conf
		pkg_update force
	fi

	case "$(compare_pkg_version ${_repo_pkg})" in
		"<")
			local _force=""
			;;
		">")
			local _force=" -f"
			;;
		"=")
			return
			;;
		*)
			_echo "ERROR: Unable to compare version of ${_repo_pkg}"
			_exit 1
	esac

	cp /usr/local/etc/pkg/repos/${product}.conf /tmp/${product}.conf.copy
	_exec "pkg-static upgrade${dont_update}${_force} ${_repo_pkg}" \
	    "Upgrading ${_repo_pkg}" mute
	abi_setup
	# If conf differs, for an update
	if ! cmp -s /usr/local/etc/pkg/repos/${product}.conf \
	    /tmp/${product}.conf.copy; then
		pkg_update force

		# New repo may contain newer pkg
		if [ -z "${NEW_MAJOR}" -a "$(compare_pkg_version pkg)" = "<" ]
		then
			_exec "pkg-static upgrade pkg" "Upgrading pkg" mute
			pkg_update force
		fi
	fi
	rm -f /tmp/${product}.conf.copy
}

upgrade_available() {
	# XXX locked packages message should be printed on stderr by pkg?
	local _lines=$(pkg-static upgrade${dont_update} -nq "$@" 2>/dev/null \
	    | sed -e '/^$/d; /is locked and may not be modified/d' \
	    | wc -l)

	test ${_lines} -gt 0
	return $?
}

pkg_upgrade() {
	# figure out which kernel variant is running
	export kernel_pkg=$(_pkg query %n $(_pkg info \
	    ${product}-kernel-\* | grep -v -- -debug-))

	# Detect if has u-boot package installed
	unset uboot_bin
	unset uboot_pkg
	unset uboot_mntp
	if is_pkg_installed ${product}-u-boot-ufw\*; then
		export uboot_pkg=$(_pkg query %n $(_pkg info \
		    ${product}-u-boot-ufw\*))
		export uboot_mntp=/boot/msdos
		export uboot_bin=u-boot.img
	elif is_pkg_installed ${product}-u-boot-sg3100\*; then
		export uboot_pkg=$(_pkg query %n $(_pkg info \
		    ${product}-u-boot-sg3100\*))
		export uboot_mntp=/boot/u-boot
		export uboot_bin=u-boot.bin
	fi

	if [ -n "${uboot_pkg}" -a \
	    "$(compare_pkg_version ${uboot_pkg})" = "<" ]; then
		if [ ! -f ${uboot_mntp}/${uboot_bin} ]; then
			# Try to mount /boot/msdos
			mount ${uboot_mntp} >/dev/null 2>&1
		fi

		if [ ! -f ${uboot_mntp}/${uboot_bin} ]; then
			_echo "ERROR: u-boot partition (${uboot_mntp}) is not" \
			    "properly mounted"
			_exit 1
		fi
	fi

	if [ -z "${kernel_pkg}" ]; then
		_echo "ERROR: It was not possible to identify which" \
		    "${product} kernel is installed"
		_exit 1
	fi

	export next_stage=$(_pkg annotate -q -S ${kernel_pkg} next_stage)
	export is_pkg_locked=$(_pkg annotate -q -S ${kernel_pkg} new_major)

	if [ -n "${next_stage}" -a -n "${booting}" -a -n "${boot_stage}" ]; then
		if [ ${boot_stage} != ${next_stage} ]; then
			_exit 0
		fi
	fi

	# If it's booting and first stage didn't run, just exit
	if [ -n "${booting}" -a -z "${next_stage}" ]; then
		_exit 0
	fi

	need_reboot=1
	# First upgrade stage
	if [ -z "${next_stage}" ]; then
		if [ -f "${logfile}" ]; then
			rm -f ${logfile}
		fi

		pkg_update

		# Lock pkg to avoid having pkg binary for version N+1 installed
		# on N
		if [ -n "${NEW_MAJOR}" ]; then
			pkg_lock pkg
			unlock_pkg=1
		fi

		pkg_upgrade_repo

		# If a new version of pfSense-upgrade is available, upgrade it
		# and return a special code 99 used by wrapper to run newer
		# version again using the same parameters
		if [ "$(compare_pkg_version ${product}-upgrade)" != "=" ]; then
			_exec "pkg-static upgrade${dont_update} -f \
			    ${product}-upgrade" "Upgrading ${product}-upgrade" \
			    mute
			_exit 99
		fi

		# Old versions used to lock kernel and uboot, make sure we
		# get it reverted
		if pkg-static lock -l 2>&1 | grep -q "kernel"; then
			pkg_unlock ${kernel_pkg}
		fi
		if [ -n "${uboot_pkg}" ] && \
		    pkg-static lock -l 2>&1 | grep -q "u-boot"; then
			pkg_unlock ${uboot_pkg}
		fi

		# Always make sure important packages are set as vital
		set_vital_flag pkg ${kernel_pkg} ${product} ${product}-rc \
		    ${product}-base ${uboot_pkg}

		check_upgrade mute skip_update
		case "$?" in
			0)
				_echo "Your packages are up to date"
				_exit 0
				;;
			3)
				_echo "Your system is on a newer version"
				_exit 0
				;;
		esac

		if [ -n "${dry_run}" ]; then
			_exec "pkg-static upgrade${dont_update} -nq" "" nomute \
			    ignore_result do_not_exit
			_exit 0
		fi

		local _meta_pkg=$(get_meta_pkg_name)
		if [ "${platform}" = "nanobsd" ]; then
			_echo "**** WARNING ****"
			_echo ""
			_echo "NanoBSD platform is no longer supported."
			_echo ""
			_echo "You can find instructions on how to convert it"
			    "to Full Installation at http://bit.ly/2wDinm8"
			_exit 1
		fi

		# ZFS panics with reroot, do not use it in this case
		# https://redmine.pfsense.org/issues/6045
		if ! kldstat -qm zfs; then
			# If kernel is not going to be upgraded, reroot
			if ! upgrade_available ${kernel_pkg}; then
				reboot_params="-r"
			fi
		fi

		if [ -z "${yes}" ]; then
			# Show user which packages are going to be upgraded
			_exec "pkg-static upgrade${dont_update} -nq" "" nomute \
			    ignore_result do_not_exit

			_echo ""
			_echo "**** WARNING ****"
			_echo "Reboot will be required!!"
			_echo -n "Proceed with upgrade? (y/N) "
			read answer
			if [ "${answer}" != "y" ]; then
				_echo "Aborting..."
				_exit 0
			fi
		fi

		# Help users to fix fstab before risk moving to FreeBSD 11
		if grep -q -E '/dev/ad[[:digit:]]' /etc/fstab; then
			_exec "/usr/local/sbin/ufslabels.sh commit" \
			    "Fixing bad /etc/fstab" mute ignore_result
		fi

		# Unlock pkg to make sure it's downloaded
		if [ -n "${NEW_MAJOR}" ]; then
			pkg_unlock pkg
			unlock_pkg=""
		fi

		# Download all upgrade packages first
		fetch_upgrade_packages

		# Then lock it again
		if [ -n "${NEW_MAJOR}" ]; then
			pkg_lock pkg
			unlock_pkg=1
		fi

		if upgrade_available ${kernel_pkg}; then
			# pfSense-SG-3100 is obsolete and must be removed
			# before upgrade kernel / rc and preserve
			# loader.rc.local just to be extra safe
			if is_pkg_installed ${product}-SG-3100; then
				rm -f /tmp/loader.rc.local >/dev/null 2>&1
				[ -f /boot/loader.rc.local ] \
				    && cp /boot/loader.rc.local /tmp 2>/dev/null
				_exec "pkg-static delete ${product}-SG-3100" \
				    "Removing ${product}-SG-3100" mute \
				    ignore_result
				[ -f /tmp/loader.rc.local ] \
				    && cp /tmp/loader.rc.local /boot 2>/dev/null
			fi
			_exec "pkg-static upgrade -U ${kernel_pkg}" \
			    "Upgrading ${product} kernel"
		fi

		if [ -n "${uboot_pkg}" ] && upgrade_available ${uboot_pkg}; then
			_exec "pkg-static upgrade -U ${uboot_pkg}" \
			    "Upgrading ${product} u-boot"
		fi

		if [ -n "${NEW_MAJOR}" ]; then
			_pkg annotate -q -M ${kernel_pkg} new_major 1
		fi

		_pkg annotate -q -M ${kernel_pkg} next_stage 2
		next_stage=2

		do_reboot
		_exit 0
	fi

	if [ "${next_stage}" = "2" ]; then
		pkg_lock "${pkg_prefix}*"
		unlock_additional_pkgs=1

		# XXX: Workaround to upgrade strongswan
		# If those symlinks are present, pkg exit because it expects
		# them to be a directory
		if upgrade_available strongswan; then
			[ -L /usr/local/etc/ipsec.d ] \
			    && rm -f /usr/local/etc/ipsec.d
			[ -L /usr/local/etc/ipsec.conf ] \
			    && rm -f /usr/local/etc/ipsec.conf
			[ -L /usr/local/etc/strongswan.d ] \
			    && rm -f /usr/local/etc/strongswan.d
			[ -L /usr/local/etc/strongswan.conf ] \
			    && rm -f /usr/local/etc/strongswan.conf
		fi

		if upgrade_available; then
			delete_annotation=1
			if [ -n "${is_pkg_locked}" ]; then
				# Upgrade pkg
				pkg_unlock pkg
				_pkg annotate -q -D ${kernel_pkg} new_major
				_exec "pkg-static install -f pkg" \
				    "Reinstalling pkg due to ABI change"

				# Upgrade base packages using pkg-static
				_exec "pkg-static upgrade -r ${product}-core" \
				    "Upgrading necessary core packages"

				# Make sure PHP setup is fine
				/etc/rc.php_ini_setup >/dev/null 2>&1
			fi

			_exec "pkg-static upgrade${dont_update}" \
			    "Upgrading necessary packages"
			delete_annotation=""

			# Always make sure important packages are set as vital
			set_vital_flag pkg ${kernel_pkg} ${product} \
			    ${product}-rc ${product}-base ${uboot_pkg}
		fi

		_pkg annotate -q -M ${kernel_pkg} next_stage 3
		next_stage=3

		pkg_unlock "${pkg_prefix}*"
		unlock_additional_pkgs=""

		# 2nd reboot during new major version upgrade
		if [ -n "${is_pkg_locked}" ]; then
			# Reboot immediately
			do_reboot now
			_exit 0
		fi

		if [ -n "${booting}" ]; then
			_exit 0
		fi
	fi

	if [ "${next_stage}" = "3" ]; then
		if upgrade_available; then
			delete_annotation=1
			_exec "pkg-static upgrade${dont_update}" \
			    "Upgrading necessary packages"
			delete_annotation=""
		fi

		if is_pkg_installed ${pkg_prefix}\*; then
			for _package in $(_pkg query %n $(_pkg info -E -g \
			    ${pkg_prefix}\*)); do
				_pkg rquery -U %v ${_package} >/dev/null \
				    && continue

				# This package was removed from remote repo
				# Make sure it's uninstalled
				_exec "pkg-static set -A 1 ${_package}" \
				    "Scheduling package ${_package} for removal"
			done
		fi

		_pkg annotate -q -D ${kernel_pkg} next_stage

		# cleanup caches
		_exec "pkg-static autoremove" "Removing unnecessary packages" \
		    mute ignore_result
		_exec "pkg-static clean" "Cleanup pkg cache" mute ignore_result
	fi

	gitsync=$(read_xml_tag.sh boolean system/gitsync/synconupgrade)
	if [ "${gitsync}" = "true" ]; then
		repository_url=$(read_xml_tag.sh string \
		    system/gitsync/repositoryurl)
		branch=$(read_xml_tag.sh string system/gitsync/branch)

		minimal=$(read_xml_tag.sh boolean system/gitsync/minimal)
		diff=$(read_xml_tag.sh boolean system/gitsync/diff)
		show_files=$(read_xml_tag.sh boolean system/gitsync/show_files)
		show_command=$(read_xml_tag.sh boolean \
		    system/gitsync/show_command)
		dryrun=$(read_xml_tag.sh boolean system/gitsync/dryrun)

		mute='mute'
		options=''
		if [ "${minimal}" = "true" ]; then
			options=${options}' --minimal'
		fi
		if [ "${diff}" = "true" ]; then
			options=${options}' --diff'
		fi
		if [ "${show_files}" = "true" ]; then
			options=${options}' --show-files'
			mute='off'
		fi
		if [ "${show_command}" = "true" ]; then
			options=${options}' --show-command'
			mute='off'
		fi
		if [ "${dryrun}" = "true" ]; then
			options=${options}' --dry-run'
		fi

		# Repository URL is not mandatory
		if [ -n "${branch}" ]; then
			_exec "pfSsh.php playback gitsync ${repositoryurl} \
			    ${branch} ${options} --upgrading" \
			    "Running gitsync" ${mute} ignore_result
		fi
	fi

	# Save a copy of latest finished upgrade process
	cp -f ${logfile} ${upgrade_logfile}
}

get_meta_pkg_name() {
	# figure out main meta package name
	if is_pkg_installed ${product}-vmware; then
		echo "${product}-vmware"
	elif is_pkg_installed ${product}; then
		echo "${product}"
	else
		_echo "ERROR: It was not possible to identify which" \
		    "${product} meta package is installed"
		_exit 1
	fi
}

check_upgrade() {
	local _mute="$1"
	local _skip_update="$2"
	local _meta_pkg=$(get_meta_pkg_name)
	local _core_pkgs=$(pkg-static query -e \
	    "%n ~ ${product}-kernel-* || %n ~ ${product}-base*" %n 2>/dev/null)

	[ -z "${_skip_update}" ] \
	    && pkg_update "" mute

	pkg_upgrade_repo

	local _version_compare=""
	for _package in ${_meta_pkg} ${_core_pkgs}; do
		_version_compare=$(compare_pkg_version ${_package})
		case "${_version_compare}" in
			=)
				continue
				;;
			'>')
				[ -z "${_mute}" ] \
				    && _echo "Your system is on a newer version"
				return 0
				;;
		esac

		local _new_version=$(_pkg rquery -U %v ${_package})
		[ -z "${_mute}" ] \
		    && _echo \
		    "${_new_version} version of ${product} is available"
		return 2
	done

	[ -z "${_mute}" ] \
	    && _echo "Your system is up to date"
	return 0
}

is_pkg_installed() {
	local _pkg_name="${1}"

	pkg-static info -e ${_pkg_name} 2>/dev/null
	return $?
}

compare_pkg_version() {
	local _pkg_name="${1}"

	if [ -z "${_pkg_name}" ]; then
		echo '!'
		return 1
	fi

	if ! is_pkg_installed ${_pkg_name}; then
		echo '!'
		return 1
	fi

	local _lver=$(_pkg query %v ${_pkg_name})

	if [ -z "${_lver}" ]; then
		_echo "ERROR: It was not possible to determine ${_pkg_name}" \
		    "local version"
		_exit 1
	fi

	local _rver=$(_pkg rquery -U %v ${_pkg_name})

	if [ -z "${_rver}" ]; then
		_echo "ERROR: It was not possible to determine ${_pkg_name}" \
		    "remote version"
		_exit 1
	fi

	local _version=$(_pkg version -t ${_lver} ${_rver})

	if [ $? -ne 0 ]; then
		_echo "ERROR: Error comparing ${_pkg_name} local and remote" \
		    "versions"
		_exit 1
	fi

	echo ${_version}
	return 0
}

pkg_install() {
	local _pkg_name="${1}"

	local _force=""
	if [ -n "${2}" ]; then
		_force="-f"
	fi

	if [ -z "${_pkg_name}" ]; then
		_echo "ERROR: Blank package name"
		_exit 1
	fi

	if is_pkg_installed ${_pkg_name}; then
		local _cversion=$(compare_pkg_version ${_pkg_name})

		if [ -z "${_force}" ]; then
			if [ "${_cversion}" = "=" ]; then
				_echo "Package ${_pkg_name} is up to date"
				_exit 0
			elif [ "${_cversion}" = ">" ]; then
				_echo "Installed ${_pkg_name} version is" \
				    "newer than remote"
				_exit 0
			fi
		fi
		local _cmd="upgrade ${_force}"
		local _msg="Upgrading"
	else
		local _cmd="install"
		local _msg="Installing"
	fi

	pkg_with_pb "${_cmd}${dry_run:+ }${dry_run} ${_pkg_name}" \
	    "${_msg} ${_pkg_name}"
	_exec "pkg-static clean" "Cleaning up cache" mute ignore_result
}

# Reinstall every pfSense-pkg-* package
pkg_reinstall_all() {
	for _package in $(_pkg query -e '%a == 0' %n); do
		case ${_package} in "${pkg_prefix}"* )
			_echo "Reinstalling ${_package}"
			pkg_install ${_package} 1
			;;
		esac
	done
}

pkg_delete() {
	local _pkg_name="${1}"

	if [ -z "${_pkg_name}" ]; then
		_echo "ERROR: Blank package name"
		_exit 1
	fi

	if ! is_pkg_installed ${_pkg_name}; then
		_echo "ERROR: Package ${_pkg_name} is not installed"
		_exit 1
	fi

	pkg_with_pb "delete${dry_run:+ }${dry_run} ${_pkg_name}" \
	    "Removing ${_pkg_name}"
	_exec "pkg-static autoremove" "Removing stale packages" mute \
	    ignore_result
}

# Delete every pfSense-pkg-* package
pkg_delete_all() {
	for _package in $(_pkg query -e '%a == 0' %n); do
		case ${_package} in "${pkg_prefix}"* )
			_echo "Removing ${_package}"
			pkg_delete ${_package}
			;;
		esac
	done
}

do_reboot() {
	local _now="$1"

	local _msg=""
	if [ "${_now}" = "now" ]; then
		_msg="now."
	else
		_msg="in ${reboot_after} seconds."
	fi

	if [ -z "${dont_reboot}" ]; then
		_echo "Upgrade is complete.  Rebooting ${_msg}"
		echo "Upgrade is complete.  Rebooting ${_msg}" | wall
		/etc/rc.notify_message -e -g -m \
		    "Upgrade is complete.  Rebooting ${_msg}" >/dev/null 2>&1
		if [ "${_now}" = "now" ]; then
			/etc/rc.reboot ${reboot_params}
		else
			(sleep ${reboot_after} \
			    && /etc/rc.reboot ${reboot_params}) &
		fi
	else
		_echo "Upgrade is complete."
		echo "Upgrade is complete." | wall
		/etc/rc.notify_message -e -g -m "Upgrade is complete." \
		    >/dev/null 2>&1
	fi
}

validate_repo_conf() {
	# Make sure to use default repo conf when it doesn't exist
	pkg_repo_conf="/usr/local/etc/pkg/repos/${product}.conf"

	default_path="/usr/local/share/${product}/pkg/repos"
	default_file=$(ls -1 ${default_path}/*.default 2>/dev/null | tail -n 1)

	if [ -n "${default_file}" -a -f "${default_file}" ]; then
		default="${default_file%%.default}"
	else
		default="${default_path}/${product}-repo.conf"
	fi

	pkg_repo_conf_path=$(read_xml_tag.sh string system/pkg_repo_conf_path)

	if [ -z "${pkg_repo_conf_path}" -o ! -f "${pkg_repo_conf_path}" ]; then
		pkg_repo_conf_path=${default}
	fi

	if [ -f "${pkg_repo_conf_path}" ]; then
		if [ -e "${pkg_repo_conf}" -a ! -L "${pkg_repo_conf}" ]; then
			rm -f ${pkg_repo_conf}
			ln -sf ${pkg_repo_conf_path} ${pkg_repo_conf}
		fi

		if [ "$(readlink ${pkg_repo_conf})" != \
		    "${pkg_repo_conf_path}" ]; then
			mkdir -p /usr/local/etc/pkg/repos
			ln -sf ${pkg_repo_conf_path} ${pkg_repo_conf}
		fi
	fi
}

export LANG=C

pid_file="/var/run/$(basename $0).pid"
logfile="/cf/conf/upgrade_log.txt"
upgrade_logfile="/cf/conf/upgrade_log.latest.txt"
stdout='/dev/null'

# Export necessary PATH
export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin

# Setup proxy settings
HTTP_PROXY=$(read_xml_tag.sh string system/proxyurl)
if [ "${HTTP_PROXY}" != "" ]; then
	HTTP_PROXY_PORT=$(read_xml_tag.sh string system/proxyport)
	if [ "${HTTP_PROXY_PORT}" != "" ]; then
		HTTP_PROXY="${HTTP_PROXY}:${HTTP_PROXY_PORT}"
	fi
	export HTTP_PROXY
fi

# Only set proxy variables on FreeBSD 11 until the fix is on 10
# https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=194483
if $(uname -r | grep -q '^11'); then
	HTTP_PROXY_USER=$(read_xml_tag.sh string system/proxyuser)
	HTTP_PROXY_PASS=$(read_xml_tag.sh string system/proxypass)
	if [ "${HTTP_PROXY_USER}" != "" ] &&
	    [ "${HTTP_PROXY_PASS}" != "" ]; then
		HTTP_PROXY_AUTH="${HTTP_PROXY_USER}:${HTTP_PROXY_PASS}"
		HTTP_PROXY_AUTH="basic:*:${HTTP_PROXY_AUTH}"
		export HTTP_PROXY_AUTH
	fi
fi

# pkg should not ask for confirmations
export ASSUME_ALWAYS_YES=true
export FETCH_TIMEOUT=5
export FETCH_RETRY=2

export product=$(php -n /usr/local/sbin/read_global_var product_name pfSense)
export pkg_prefix=$(php -n /usr/local/sbin/read_global_var pkg_prefix \
    pfSense-pkg-)
export platform=$(cat /etc/platform)

USE_MFS_TMPVAR=$(read_xml_tag.sh boolean system/use_mfs_tmpvar)
if [ "${USE_MFS_TMPVAR}" = "true" ]; then
	export PKG_DBDIR=/root/var/db/pkg
	export PKG_CACHEDIR=/root/var/cache/pkg
fi

product_version=$(cat /etc/version)
do_not_send_uniqueid=$(read_xml_tag.sh boolean system/do_not_send_uniqueid)
if [ "${do_not_send_uniqueid}" != "true" ]; then
	uniqueid=$(gnid)
	export HTTP_USER_AGENT="${product}/${product_version}:${uniqueid}"
else
	export HTTP_USER_AGENT="${product}/${product_version}"
fi

validate_repo_conf

# Flags used in _exit
export delete_annotation=""
export unlock_additional_pkgs=""
export unlock_pkg=""
export reinstall_pkg=""
export delete_pid=""

# Save nc_pid to be able to kill it
export nc_pid=""

# Reboot after 10 seconds
export reboot_after=10

# Used to set -r and reroot
export reboot_params=""

unset dry_run
unset dont_reboot
unset dont_update
unset booting
unset boot_stage
unset force
unset yes
unset progress_file
unset progress_socket
unset action
unset action_pkg
unset force_ipv4
unset force_ipv6
while getopts 46b:cdfi:hp:l:nr:RuUy opt; do
	case ${opt} in
		4)
			if [ -n "${force_ipv6}" ]; then
				usage
				exit 1
			fi
			force_ipv4=1
			;;
		6)
			if [ -n "${force_ipv4}" ]; then
				usage
				exit 1
			fi
			force_ipv6=1
			;;
		b)
			booting=1
			boot_stage="${OPTARG}"
			;;
		c)
			action="check"
			;;
		d)
			stdout=''
			;;
		f)
			force=1
			;;
		i)
			if [ -n "${action}" ]; then
				usage
				exit 1
			fi
			action="install"
			action_pkg="${OPTARG}"
			;;
		h)
			usage
			exit 0
			;;
		l)
			logfile="${OPTARG}"
			if [ -z "${logfile}" ]; then
				usage
				exit 1
			fi
			;;
		n)
			dry_run="-n"
			;;
		p)
			progress_socket="${OPTARG}"
			if [ -z "${progress_socket}" ]; then
				usage
				exit 1
			fi
			;;
		r)
			if [ -n "${action}" ]; then
				usage
				exit 1
			fi
			action="delete"
			action_pkg="${OPTARG}"
			;;
		R)
			dont_reboot=1
			;;
		u)
			if [ -n "${action}" ]; then
				usage
				exit 1
			fi
			action="update"
			;;
		U)
			dont_update=" -U"
			;;
		y)
			yes=1
			;;
		*)
			usage
			exit 1
			;;
	esac
done

if [ -n "${force_ipv4}" ]; then
	export IP_VERSION="4"
elif [ -n "${force_ipv6}" ]; then
	export IP_VERSION="6"
fi

# Set default action when no parameter is set
: ${action:="upgrade"}

if pgrep -qF ${pid_file} >/dev/null 2>&1; then
	echo "Another instance is already running... Aborting!"
	exit 1
fi

echo $$ > ${pid_file}

# Since this point it's safe to remove pid_file
delete_pid=1

trap _exit 1 2 15 EXIT

block_external_services=$(read_xml_tag.sh boolean \
    system/block_external_services)

if [ "${block_external_services}" = "true" ]; then
	echo "Aborted due to block_external_services flag"
	exit 0
fi

if [ -z "${booting}" -o "${boot_stage}" != "2" ]; then
	php /etc/rc.conf_mount_rw
fi

if [ -n "${booting}" ]; then
	export REPO_AUTOUPDATE=false
fi

if [ "${action}" != "upgrade" -a -f "${logfile}" ]; then
	rm -f ${logfile}
fi

progress_file=${logfile%.*}.json

if [ -e "${progress_file}" ]; then
	rm -f ${progress_file}
fi

abi_setup

if [ -n "${NEW_MAJOR}" -a "${action}" = "install" ]; then
	_echo "WARNING: Current pkg repository has a new OS major version."
	_echo "         ${product} should be upgraded before doing any other"
	_echo "         operation"
	_exit 1
fi

case "${action}" in
	check)
		check_upgrade
		_exit $?
		;;
	upgrade)
		pkg_upgrade
		;;
	update)
		pkg_update force
		;;
	install)
		if [ ${action_pkg} = "ALL_PACKAGES" ] && [ -n ${force} ]; then
			pkg_reinstall_all
		else
			pkg_install ${action_pkg} ${force}
		fi
		;;
	delete)
		if [ ${action_pkg} = "ALL_PACKAGES" ] && [ -n ${force} ]; then
			pkg_delete_all
		else
			pkg_delete ${action_pkg}
		fi
		;;
	*)
		_echo "ERROR: Invalid action!"
		_exit 1
esac

_exit 0
