#!/bin/sh
#Copyright (c) 2014-2024 Pierre Pronchery <khorben@EdgeBSD.org>
#
#This code is derived from software contributed to the EdgeBSD Project
#by Pierre Pronchery <khorben@EdgeBSD.org>
#
#Redistribution and use in source and binary forms, with or without
#modification, are permitted provided that the following conditions
#are met:
#1. Redistributions of source code must retain the above copyright
#   notice, this list of conditions and the following disclaimer.
#2. Redistributions in binary form must reproduce the above copyright
#   notice, this list of conditions and the following disclaimer in the
#   documentation and/or other materials provided with the distribution.
#
#THIS SOFTWARE IS PROVIDED BY THE EDGEBSD PROJECT AND CONTRIBUTORS
#``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
#TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
#PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
#BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
#CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
#SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
#INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
#CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
#ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
#POSSIBILITY OF SUCH DAMAGE.



#environment
umask 022
#variables
PROGNAME="edgebsd-update"
#executables
CAT="/bin/cat"
CMP="/usr/bin/cmp"
CP="/bin/cp -af"
DEBUG="_debug"
DUMPFS="/usr/sbin/dumpfs"
FTP="/usr/bin/ftp"
GREP="grep"
GZIP="/usr/bin/gzip -9"
INSTALL="/usr/bin/install"
INSTALLBOOT="/usr/sbin/installboot"
MKDIR="/bin/mkdir -p"
MKTEMP="/usr/bin/mktemp"
MOUNT="/sbin/mount"
MV="/bin/mv -f"
NETPGP="/usr/bin/netpgp"
NETPGPKEYS="/usr/bin/netpgpkeys"
PAXCTL="/usr/sbin/paxctl"
PKGIN="/usr/pkg/bin/pkgin"
POSTINSTALL="/usr/sbin/postinstall"
PRINTF="printf"
PROGRESS="/usr/bin/progress"
REBOOT="/sbin/reboot"
RM="/bin/rm -f"
RMDIR="/bin/rmdir"
ROOTDEV=
SYSCTL="/sbin/sysctl"
TAR="/rescue/tar"
TR="tr"
UMOUNT="/sbin/umount"
UNAME="/usr/bin/uname"
ZCAT="/usr/bin/zcat"

#settings
BOOTLOADER=0
BOOTXX="/usr/mdec/"
DESTDIR=
DEVNULL="/dev/null"
EDGEBSD_ARCH=$($UNAME -m)
EDGEBSD_ARCH_FULL="$EDGEBSD_ARCH"
case "$EDGEBSD_ARCH_FULL" in
	evbarm)
		EDGEBSD_ARCH_FULL="$EDGEBSD_ARCH_FULL-$($UNAME -p)"
		;;
esac
EDGEBSD_KERNEL=
EDGEBSD_MIRROR="https://ftp.EdgeBSD.org"
EDGEBSD_PATH=
EDGEBSD_VENDOR="EdgeBSD"
ETCMTREE="/etc/mtree"
INIT=0
KERNEL=0
[ -n "$TMPDIR" ] || TMPDIR="/tmp"
MKTEMP_TEMPLATE="$TMPDIR/$PROGNAME-XXXXXX"
MODULES=0
NOOP=0
PACKAGES=0
RELEASE=
SETEXT=
SETS=
SYSCONFDIR="@SYSCONFDIR@"
UPDATE_ETC=0
USRMDECBOOT="/usr/mdec/boot"
USRMDECBOOTXX="/usr/mdec/bootxx"
USRMDECPREKERN="/usr/mdec/prekern"
VERBOSE=1
VERIFY=1
VERIFY_USERID="root@EdgeBSD.org"

#load local settings
[ -f "$SYSCONFDIR/$EDGEBSD_VENDOR/$PROGNAME.conf" ] &&
	. "$SYSCONFDIR/$EDGEBSD_VENDOR/$PROGNAME.conf"
[ -f "$HOME/.config/$EDGEBSD_VENDOR/$PROGNAME.conf" ] &&
	. "$HOME/.config/$EDGEBSD_VENDOR/$PROGNAME.conf"


#functions
#edgebsd_update
_edgebsd_update()
{
	#detect the release
	_detect_release "$RELEASE"
	case "$EDGEBSD_RELEASE/$EDGEBSD_ARCH" in
		EdgeBSD-9/alpha|EdgeBSD-9/amd64|EdgeBSD-9/sparc64|\
			NetBSD-9.*/alpha|NetBSD-9.*/amd64|NetBSD-9.*/sparc64|\
			EdgeBSD-10/alpha|EdgeBSD-10/amd64|EdgeBSD-10/sparc64|\
			NetBSD-10.*/alpha|NetBSD-10.*/amd64|NetBSD-10.*/sparc64)
			[ -n "$SETEXT" ] || SETEXT=".tar.xz"
			;;
		EdgeBSD-6/*|EdgeBSD-7/*|EdgeBSD-8/*|EdgeBSD-9/*|EdgeBSD-10/*|\
			NetBSD-2.*|NetBSD-3.*|NetBSD-4.*|NetBSD-5.*|\
			NetBSD-6.*/*|NetBSD-7.*/*|NetBSD-8.*/*|NetBSD-9.*/*)
			[ -n "$SETEXT" ] || SETEXT=".tgz"
			;;
		*)
			_error "$RELEASE: Unsupported $EDGEBSD_VENDOR release"
			return $?
			;;
	esac

	if [ "$EDGEBSD_VENDOR" = "EdgeBSD" ]; then
		if [ $VERIFY -ne 0 ]; then
			#import the GPG key
			_info "Importing the GPG key..."
			#XXX does not work
			_import_gpg_key				|| return 2
		fi
	fi

	#detect the sets to update
	[ -n "$SETS" ] || SETS=$(_detect_sets)

	if [ $MODULES -ne 0 ]; then
		has_stand=0
		for i in $SETS; do
			[ "$i" = "stand" ] && has_stand=1
		done
		[ $has_stand -eq 0 ] && SETS="$SETS stand"
	fi

	if [ -z "$EDGEBSD_PATH" ]; then
		EDGEBSD_PATH="$EDGEBSD_MIRROR/pub/$EDGEBSD_VENDOR/$EDGEBSD_RELEASE/$EDGEBSD_ARCH_FULL/binary/sets"
	else
		EDGEBSD_MIRROR="/nonexistent"
	fi

	#detect and download the kernel
	[ -n "$EDGEBSD_KERNEL" ] || EDGEBSD_KERNEL=$(_detect_kernel)
	_download_kernel
	if [ $? -ne 0 ]; then
		_error "$EDGEBSD_KERNEL: Could not download the kernel"
		return $?
	fi
	_download_kernel_signature
	if [ $? -ne 0 ]; then
		_error "$EDGEBSD_KERNEL: Could not download the kernel signature"
		return $?
	fi

	_download_sets
	if [ $? -ne 0 ]; then
		_error "Could not download the sets"
		return $?
	fi

	#update the kernel
	clean_kernel=0
	_update_kernel
	ret=$?
	if [ $ret -eq 3 ]; then
		#rebooting is requested
		if [ $INIT -ne 0 ]; then
			#remove the kernel set
			_clean_kernel
			#reboot after kernel update
			_info "Rebooting after kernel update..."
			$DEBUG $REBOOT
			if [ $? -ne 0 ]; then
				_error "Could not reboot after kernel update"
				return $?
			fi
		fi
	elif [ $ret -eq 0 ]; then
		[ $INIT -ne 0 -o -n "${EDGEBSD_MIRROR%%/*}" ] && clean_kernel=1
	else
		_error "Could not update the kernel"
	fi

	#update the sets
	clean_sets=0
	clean_sets_keep="etc"
	if [ $ret -eq 0 ]; then
		_update_sets
		ret=$?
		if [ $ret -eq 0 ]; then
			[ $INIT -ne 0 -o -n "${EDGEBSD_MIRROR%%/*}" ] && clean_sets=1
		else
			_error "Could not update the sets"
		fi
	fi

	#update the configuration
	if [ $ret -eq 0 ]; then
		_update_configuration
		ret=$?
		if [ $ret -eq 0 ]; then
			clean_sets_keep=
		else
			_error "Could not update the configuration"
		fi
	fi

	#update the packages
	if [ $ret -eq 0 ]; then
		_update_packages
		ret=$?
		[ $ret -eq 0 ] || _error "Could not update the packages"
	fi

	#update the bootloader
	if [ $ret -eq 0 ]; then
		_update_bootloader
		ret=$?
		[ $ret -eq 0 ] || _error "Could not update the bootloader"
	fi

	#cleanup
	[ $clean_kernel -eq 1 ] && _clean_kernel
	[ $clean_sets -eq 1 ] && _clean_sets "$clean_sets_keep"

	if [ $ret -eq 0 ]; then
		_info "Update successful."
	else
		_info "Some errors occurred."
	fi

	#reboot after update
	if [ $ret -eq 0 ]; then
		_update_reboot
		ret=$?
		if [ $ret -ne 0 ]; then
			_error "Could not reboot after update"
			return $?
		fi
	fi

	return $ret
}


#architecture-specific
_arch_amd64_update_bootloader()
{
	_arch_i386_update_bootloader
}

_arch_amd64_update_kernel()
{
	_arch_i386_update_kernel
}

_arch_evbarm_update_bootloader()
{
	#XXX assumes the Raspberry PI and DESTDIR=/
	#TODO replace the firmware content if available
	return 0
}

_arch_evbarm_update_kernel()
{
	ret=0
	kernelfiles="./bcm283?-rpi-* ./netbsd.img"
	kernelimg="kernel7.img"
	mounted=0

	#XXX assumes the Raspberry PI and DESTDIR=/
	if [ ! -f "$DESTDIR/boot/$kernelimg" ]; then
		if ! $DEBUG $MOUNT "$DESTDIR/boot"; then
			_error "$EDGEBSD_KERNEL: Could not mount the boot partition"
			return $?
		fi
		mounted=1
	fi
	file="kern-${EDGEBSD_KERNEL}${SETEXT}"
	[ -z "${EDGEBSD_MIRROR##/*}" ] && file="$EDGEBSD_PATH/$file"
	if [ ! -f "$file" ]; then
		if [ $INIT -ne 0 ]; then
			_info "$EDGEBSD_KERNEL: Skipping kernel update (update file missing)"
			return 0
		fi
		_error "$EDGEBSD_KERNEL: Update file missing"
		return $?
	fi
	if [ $VERIFY -ne 0 ]; then
		_info "Verifying kernel ${EDGEBSD_KERNEL}..."
		_verify "$file"
		if [ $? -ne 0 ]; then
			_error "$EDGEBSD_KERNEL: Could not verify the kernel"
			return $?
		fi
	fi
	#extract the new kernel
	_arch_generic_extract_kernel "${EDGEBSD_KERNEL}" "/boot" $kernelfiles
	ret=$?
	[ $ret -eq 0 ] || return $ret
	#compare the kernel files
	$DEBUG $CMP "$DESTDIR/boot/netbsd.img" "$DESTDIR/boot/$kernelimg" \
		> "$DEVNULL"
	if [ $? -eq 0 ]; then
		_info "$EDGEBSD_KERNEL: Skipping kernel update (already installed)"
		return 0
	fi
	#backup the current kernel
	_arch_generic_backup_kernel "$kernelimg" "/boot"
	ret=$?
	[ $ret -eq 0 ] || return $ret
	#install the new kernel
	_info "Installing kernel ${EDGEBSD_KERNEL}..."
	#rename the new kernel
	_info "Renaming kernel ${EDGEBSD_KERNEL}..."
	$DEBUG $MV -f -- "$DESTDIR/boot/netbsd.img" "$DESTDIR/boot/$kernelimg"
	if [ $? -ne 0 ]; then
		_error "$EDGEBSD_KERNEL: Could not rename the kernel"
		return $?
	fi
	ret=$?
	[ $mounted -eq 0 ] || $DEBUG $UMOUNT "$DESTDIR/boot"
	[ $? -ne 0 ] && _error "$EDGEBSD_KERNEL: Could not unmount the boot partition"
	#request a reboot
	[ $ret -eq 0 ] && return 3
	return $ret
}

_arch_generic_backup_kernel()
{
	kernel="$1"
	dest="$2"

	[ ! -f "$DESTDIR$dest/$kernel" ] && return 0
	_info "Creating a kernel backup..."
	$DEBUG $CP -- "$DESTDIR$dest/$kernel" "$DESTDIR$dest/o$kernel"
	if [ $? -ne 0 ]; then
		_error "$EDGEBSD_KERNEL: Could not backup the kernel"
		return $?
	fi
	return 0
}

_arch_generic_extract_kernel()
{
	kernel="$1"
	dest="$2"
	shift 2

	_info "Extracting kernel ${kernel}..."
	_progress -f "$file" -p "$($PRINTF "%-7s " "$kernel")" \
		$TAR -xzpf - -C "$DESTDIR$dest" "$@"
	if [ $? -ne 0 ]; then
		_error "$EDGEBSD_KERNEL: Could not extract the kernel"
		return $?
	fi
	return 0
}

_arch_generic_update_bootloader()
{
	if [ -f "$DESTDIR$USRMDECBOOT" ]; then
		if [ -f "$DESTDIR/altroot/boot" ]; then
			_update_file 0444 "$DESTDIR$USRMDECBOOT" \
				"$DESTDIR/altroot/boot"		|| return 2
		fi
		if [ -f "$DESTDIR/boot" ]; then
			_update_file 0444 "$DESTDIR$USRMDECBOOT" \
				"$DESTDIR/boot"			|| return 2
		fi
	fi
	if [ -f "$DESTDIR$USRMDECPREKERN" ]; then
		if [ -f "$DESTDIR/altroot/prekern" ]; then
			_update_file 0444 "$DESTDIR$USRMDECPREKERN" \
				"$DESTDIR/altroot/prekern"	|| return 2
		fi
		if [ -f "$DESTDIR/prekern" ]; then
			_update_file 0444 "$DESTDIR$USRMDECPREKERN" \
				"$DESTDIR/prekern"		|| return 2
		fi
	fi
	return 0
}

_arch_generic_update_kernel()
{
	file="kern-${EDGEBSD_KERNEL}${SETEXT}"
	[ -z "${EDGEBSD_MIRROR##/*}" ] && file="$EDGEBSD_PATH/$file"
	if [ ! -f "$file" ]; then
		if [ $INIT -ne 0 ]; then
			_info "$EDGEBSD_KERNEL: Skipping kernel update (update file missing)"
			return 0
		fi
		_error "$EDGEBSD_KERNEL: Update file missing"
		return $?
	fi
	if [ $VERIFY -ne 0 ]; then
		_info "Verifying kernel ${EDGEBSD_KERNEL}..."
		_verify "$file"
		if [ $? -ne 0 ]; then
			_error "$EDGEBSD_KERNEL: Could not verify the kernel"
			return $?
		fi
	fi
	#detect the kernel file booted
	bootedfile=$($SYSCTL -n "machdep.booted_kernel" 2> "$DEVNULL")
	[ $? -eq 0 -a -n "$bootedfile" ] || bootedfile="/netbsd.gz"
	bootedfile=${bootedfile#/}
	#detect if a backup was booted
	if [ "${bootedfile#o}" != "$bootedfile" ]; then
		bootedfile="${bootedfile#o}"
	elif [ -f "$DESTDIR/altroot/$bootedfile" ]; then
		_arch_generic_backup_kernel "$bootedfile" "/altroot"
		ret=$?
		[ $ret -eq 0 ] || return $ret
	elif [ -f "$DESTDIR/$bootedfile" ]; then
		_arch_generic_backup_kernel "$bootedfile"
		ret=$?
		[ $ret -eq 0 ] || return $ret
	fi
	#install the new kernel
	tmpdir=$($MKTEMP -d "$MKTEMP_TEMPLATE")
	[ $? -eq 0 ]						|| return 2
	_info "Installing kernel ${EDGEBSD_KERNEL}..."
	_progress -f "$file" -p "$($PRINTF "%-7s " "$EDGEBSD_KERNEL")" \
		$TAR -xzpf - -C "$tmpdir"
	if [ $? -ne 0 ]; then
		_error "$EDGEBSD_KERNEL: Could not install the kernel"
		$DEBUG $RM -- "$tmpdir/netbsd"
		NOOP=0 $DEBUG $RMDIR -- "$tmpdir"
		return $?
	fi
	if [ "${bootedfile%.gz}" != "$bootedfile" ]; then
		#compress the kernel
		_info "Compressing kernel ${EDGEBSD_KERNEL}..."
		$DEBUG $GZIP -f -- "$tmpdir/netbsd" &&
			$DEBUG $MV -- "$tmpdir/netbsd.gz" "$tmpdir/netbsd"
		if [ $? -ne 0 ]; then
			_error "$EDGEBSD_KERNEL: Could not compress the kernel"
			$DEBUG $RM -- "$tmpdir/netbsd" "$tmpdir/netbsd.gz"
			NOOP=0 $DEBUG $RMDIR -- "$tmpdir"
			return $?
		fi
	fi
	if [ -f "$DESTDIR/altroot/$bootedfile" ]; then
		$DEBUG $MV -- "$tmpdir/netbsd" "$DESTDIR/altroot/$bootedfile"
	else
		$DEBUG $MV -- "$tmpdir/netbsd" "$DESTDIR/$bootedfile"
	fi
	if [ $? -ne 0 ]; then
		_error "$EDGEBSD_KERNEL: Could not update the kernel"
		$DEBUG $RM -- "$tmpdir/netbsd"
		NOOP=0 $DEBUG $RMDIR -- "$tmpdir"
		return $?
	fi
	NOOP=0 $DEBUG $RMDIR -- "$tmpdir"
	#request a reboot
	return 3
}

_arch_i386_update_bootloader()
{
	_arch_generic_update_bootloader				|| return 2
	#detect the boot method
	bootmethod=$($SYSCTL -n "machdep.bootmethod" 2> "$DEVNULL")
	if [ $? -ne 0 -o -z "$bootmethod" ]; then
		_error "Could not detect the boot method (assuming BIOS)"
		bootmethod="BIOS"
	fi
	case "$bootmethod" in
		UEFI)
			#TODO update the EFI boot file
			return 0
			;;
		BIOS)
			_arch_i386_update_bootloader_bios
			return $?
			;;
		*)
			_error "$bootmethod: Unsupported boot method"
			return $?
			;;
	esac
}

_arch_i386_update_bootloader_bios()
{
	#XXX require the root device to be set
	if [ -z "$ROOTDEV" ]; then
		_error "Could not update the bootloader (root device not set)"
		return 0
	fi
	#detect the underlying filesystem
	filesystem=$($DUMPFS "$ROOTDEV" | $GREP '^format	')
	if [ $? -ne 0 -o -z "$filesystem" ]; then
		_error "$DESTDIR/: Could not detect the filesystem"
		return $?
	fi
	filesystem=${filesystem#*	}
	case "$filesystem" in
		FFSv1|FFSv2)
			filesystem=$(echo "$filesystem" | _tolower)
			;;
	esac
	if [ ! -f "${USRMDECBOOTXX}_$filesystem" ]; then
		_error "$filesystem: Unsupported filesystem for boot partition"
		return $?
	fi
	$DEBUG $INSTALLBOOT "$ROOTDEV" "${USRMDECBOOTXX}_$filesystem"
	if [ $? -eq 0 ]; then
		_error "$ROOTDEV: Could not install the bootloader"
		return $?
	fi
	return 0
}

_arch_i386_update_kernel()
{
	_arch_generic_update_kernel
}

_arch_sparc_update_bootloader()
{
	_arch_generic_update_bootloader
}

_arch_sparc_update_kernel()
{
	_arch_generic_update_kernel
}

_arch_sparc64_update_bootloader()
{
	_arch_sparc_update_bootloader
}

_arch_sparc64_update_kernel()
{
	_arch_sparc_update_kernel
}


#clean_kernel
_clean_kernel()
{
	if [ $KERNEL -ne 0 ]; then
		file="kern-${EDGEBSD_KERNEL}${SETEXT}"
		[ -z "${EDGEBSD_MIRROR##/*}" ] && file="$EDGEBSD_PATH/$file"
		#XXX ignore errors
		if [ -f "$file" ]; then
			_info "Removing downloaded kernel ${EDGEBSD_KERNEL}..."
			$DEBUG $RM -- "$file"
		fi
		if [ $VERIFY -ne 0 -a -f "$file.sig" ]; then
			_info "Removing downloaded signature for kernel ${EDGEBSD_KERNEL}..."
			$DEBUG $RM -- "$file.sig"
		fi
	fi
}


#clean_sets
_clean_sets()
{
	keep="$1"

	for set in $SETS; do
		[ -n "$keep" -a "$keep" = "$set" ]		&& continue
		file="$set${SETEXT}"
		[ -z "${EDGEBSD_MIRROR##/*}" ] && file="$EDGEBSD_PATH/$file"
		#XXX ignore errors
		if [ -f "$file" ]; then
			_info "Removing downloaded set ${set}..."
			$DEBUG $RM -- "$file"
		fi
		if [ $VERIFY -ne 0 -a -f "$file.sig" ]; then
			_info "Removing downloaded signature for set ${set}..."
			$DEBUG $RM -- "$file.sig"
		fi
	done
}


#debug
_debug()
{
	[ $VERBOSE -ge 3 ] && echo "$@" 1>&2
	[ $NOOP -ne 0 ] || "$@"
}


#detect_kernel
_detect_kernel()
{
	kernel=$($UNAME -v)
	kernel="${kernel##*(}"
	echo "${kernel%)*}"
}


#detect_release
_detect_release()
{
	if [ -n "$1" ]; then
		EDGEBSD_RELEASE="$EDGEBSD_VENDOR-$1"
	elif [ -z "$EDGEBSD_RELEASE" ]; then
		EDGEBSD_RELEASE=$($UNAME -r)
		case "$EDGEBSD_VENDOR" in
			NetBSD)
				EDGEBSD_RELEASE="${EDGEBSD_RELEASE%_*}"
				;;
			*)
				EDGEBSD_RELEASE="${EDGEBSD_RELEASE%.*}"
				;;
		esac
		EDGEBSD_RELEASE="$EDGEBSD_VENDOR-$EDGEBSD_RELEASE"
	fi
}


#detect_sets
_detect_sets()
{
	for i in "$DESTDIR$ETCMTREE/set."*; do
		[ -f "$i" ]					|| continue
		set="${i#$DESTDIR$ETCMTREE/set.}"
		[ -z "$set" ] || echo "$set"
	done
}


#download
_download()
{
	file="$1"

	NOOP=0 $DEBUG $FTP "$EDGEBSD_PATH/$file"
	res=$?
	[ $res -eq 0 ] || $DEBUG $RM -- "$file"
	return $res
}


#download_kernel
_download_kernel()
{
	#download the kernel
	if [ $KERNEL -ne 0 -a -n "${EDGEBSD_MIRROR##/*}" ]; then
		if [ ! -f "kern-${EDGEBSD_KERNEL}${SETEXT}" ]; then
			_info "Downloading kernel ${EDGEBSD_KERNEL}..."
			_download "kern-${EDGEBSD_KERNEL}${SETEXT}"
			return $?
		fi
	fi
	return 0
}


#download_kernel_signature
_download_kernel_signature()
{
	#download the kernel signature
	if [ $KERNEL -ne 0 -a -n "${EDGEBSD_MIRROR##/*}" ]; then
		if [ $VERIFY -ne 0 -a ! -f "kern-${EDGEBSD_KERNEL}${SETEXT}.sig" ]; then
			_info "Downloading signature for kernel ${EDGEBSD_KERNEL}..."
			_download "kern-${EDGEBSD_KERNEL}${SETEXT}.sig"
			return $?
		fi
	fi
	return 0
}


#download_sets
_download_sets()
{
	res=0

	#download the relevant sets
	for set in $SETS; do
		[ -z "${EDGEBSD_MIRROR##/*}" ] && continue
		[ -f "${set}${SETEXT}" ] && continue
		_info "Downloading set ${set}..."
		_download "${set}${SETEXT}"
		res=$?
		[ $res -eq 0 ] || break
		[ $VERIFY -eq 0 ] && continue
		_info "Downloading signature for set ${set}..."
		_download "${set}${SETEXT}.sig"
		res=$?
		[ $res -eq 0 ] || break
	done
	if [ $res -ne 0 -a -n "${EDGEBSD_MIRROR##/*}" ]; then
		for set in $SETS; do
			$DEBUG $RM -- "${set}${SETEXT}"
			[ $VERIFY -eq 0 ] && continue
			$DEBUG $RM -- "${set}${SETEXT}.sig"
		done
	fi
	return $res
}


#error
_error()
{
	echo "$PROGNAME: $@" 1>&2
	return 2
}


#import_gpg_key
_import_gpg_key()
{
	ret=0

	tmpfile=$($MKTEMP "$MKTEMP_TEMPLATE")
	[ $? -eq 0 ]						|| return 2
	_import_gpg_key_echo > "$tmpfile" &&
	$DEBUG $NETPGPKEYS --import-key "$tmpfile" > "$DEVNULL"	|| ret=2
	$RM -- "$tmpfile"
	return $ret
}


#import_gpg_key_echo
_import_gpg_key_echo()
{
	echo "-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v1

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
mEH+2rR/ZC0mmQ==
=dmei
-----END PGP PUBLIC KEY BLOCK-----"
}


#info
_info()
{
	[ $VERBOSE -lt 2 ] || echo "$PROGNAME: $@"
}


#progress
_progress()
{(
	cat="$CAT"
	options=
	filename=
	prefix=

	while getopts "ef:p:z" name; do
		case "$name" in
			e)
				options="-e $options"
				;;
			f)
				#XXX must be last
				options="$options -f"
				filename="$OPTARG"
				;;
			p)
				prefix="$OPTARG"
				;;
			z)
				options="-z $options"
				cat="$ZCAT"
				;;
			*)
				_progress_usage
				return $?
				;;
		esac
	done
	shift $((OPTIND - 1))
	if [ $# -lt 1 ]; then
		return 1
	fi
	#only use progress(1) if in interactive and verbose mode
	if [ \( -n "$PS1" -o $INIT -ne 0 \) -a $VERBOSE -ge 1 ]; then
		$DEBUG $PROGRESS -p "$prefix" $options "$filename" -- "$@"
	else
		$DEBUG $cat -- "$filename" | "$@"
	fi
)}

_progress_usage()
{
	echo "Usage: progress [-ez][-f filename][-p prefix] cmd [args...]" 1>&2
	return 1
}


#tolower
_tolower()
{
	$TR A-Z a-z
}


#update_bootloader
_update_bootloader()
{
	#update the bootloader
	if [ $BOOTLOADER -ne 0 ]; then
		_info "Updating the bootloader..."
		case "$EDGEBSD_ARCH" in
			amd64|evbarm|i386|sparc|sparc64)
				"_arch_${EDGEBSD_ARCH}_update_bootloader"
				return $?
				;;
			*)
				return 2
				;;
		esac
	fi
	return 0
}


#update_configuration
_update_configuration()
{
	res=0

	#update the configuration
	if [ $UPDATE_ETC -eq 1 ]; then
		set="etc"
		file="${set}${SETEXT}"
		[ -z "${EDGEBSD_MIRROR##/*}" ] && file="$EDGEBSD_PATH/$file"
		if [ $VERIFY -ne 0 ]; then
			_info "Verifying set ${set}..."
			_verify "$file"
			res=$?
			[ $res -eq 0 ] || return $res
		fi
		_info "Updating the configuration..."
		$DEBUG $POSTINSTALL -s "$file" -d "$DESTDIR/" fix > "$DEVNULL"
		res=$?
	fi
	return $res
}


#update_file
_update_file()
{
	mode="$1"
	src="$2"
	dst="$3"

	$DEBUG $CMP "$src" "$dst" > "$DEVNULL"
	[ $? -eq 0 ] || $DEBUG $INSTALL -m "$mode" "$src" "$dst"
}


#update_kernel
_update_kernel()
{
	#update the kernel
	if [ $KERNEL -ne 0 ]; then
		_info "Updating the kernel..."
		case "$EDGEBSD_ARCH" in
			amd64|evbarm|i386|sparc|sparc64)
				"_arch_${EDGEBSD_ARCH}_update_kernel"
				return $?
				;;
		esac
		return 2
	fi
	return 0
}


#update_packages
_update_packages()
{
	#update the packages
	if [ $PACKAGES -ne 0 ]; then
		_info "Updating the packages..."
		if [ -n "$DESTDIR" ]; then
			$DEBUG $PKGIN -y -c "$DESTDIR" full-upgrade
		else
			$DEBUG $PKGIN -y full-upgrade
		fi
		return $?
	fi
	return 0
}


#update_reboot
_update_reboot()
{
	if [ $INIT -ne 0 ]; then
		if [ $UPDATE_ETC -eq 1 -a -z "$clean_sets_keep" ]; then
			_info "Rebooting after set update..."
			$DEBUG $REBOOT
			return $?
		else
			_error "Partial update (not rebooting)"
		fi
	fi
	return 0
}


#update_sets
_update_sets()
{(
	ret=0
	reset_cursor=0

	#verify the sets available if requested
	if [ $VERIFY -ne 0 ]; then
		_info "Verifying the sets..."
		for set in $SETS; do
			file="${set}${SETEXT}"
			[ -z "${EDGEBSD_MIRROR##/*}" ] && file="$EDGEBSD_PATH/$file"
			#missing sets are caught later
			[ -f "$file" ] || continue
			_info "Verifying set ${set}..."
			#XXX TOCTOU
			_verify "$file"
			res=$?
			[ $res -eq 0 ] || ret=$res
		done
		[ $ret -eq 0 ] || return $ret
	fi

	#update the relevant sets
	_info "Updating the sets..."
	$MKDIR -- "$DESTDIR/"
	if [ $? -ne 0 ]; then
		_error "$DESTDIR: Could not create the destination directory"
		return $?
	fi
	for set in $SETS; do
		file="${set}${SETEXT}"
		[ -z "${EDGEBSD_MIRROR##/*}" ] && file="$EDGEBSD_PATH/$file"
		if [ ! -f "$file" ]; then
			if [ $INIT -ne 0 ]; then
				_info "$set: Skipping set update (update file missing)"
				continue
			fi
			_error "$set: Set file missing"
			ret=$?
			break
		fi
		if [ "$set" = "etc" ]; then
			UPDATE_ETC=1
			#skip the "etc" set if already installed
			[ -f "$DESTDIR$ETCMTREE/set.$set" ]	&& continue
		fi
		_progress -f "$file" -p "$($PRINTF "%-7s " "${set}")" \
			$TAR -xzpf - -C "$DESTDIR/"
		res=$?
		reset_cursor=1
		if [ $res -ne 0 ]; then
			ret=$res
			break
		fi
		#also extract the modules to /altroot if relevant
		if [ "$set" = "modules" -a -d "$DESTDIR/altroot/stand" ]; then
			_progress -f "$file" -p "$($PRINTF "%-7s " "${set}")" \
				$TAR -xzpf - -C "$DESTDIR/altroot"
			res=$?
			if [ $res -ne 0 ]; then
				ret=$res
				break
			fi
		fi
	done
	#XXX ensure the cursor position is always set correctly
	[ $reset_cursor -ne 0 ] && echo
	return $ret
)}


#usage
_usage()
{
	echo "Usage: $PROGNAME [-BbIKkmnpqSsv][-D destdir][-M mirror][-P path][-r release]" 1>&2
	echo "  -B	Do not update the bootloader" 1>&2
	echo "  -b	Update the bootloader" 1>&2
	echo "  -D	Specify a different directory where to update" 1>&2
	echo "  -I	Perform a boot-time update" 1>&2
	echo "  -K	Do not update the kernel" 1>&2
	echo "  -k	Update the kernel" 1>&2
	echo "  -M	Specify a different mirror when downloading" 1>&2
	echo "  -m	Update the kernel modules" 1>&2
	echo "  -n	Download the sets but do not actually update" 1>&2
	echo "  -P	Full path to the sets" 1>&2
	echo "  -p	Update the packages" 1>&2
	echo "  -q	Enable quiet mode" 1>&2
	echo "  -r	Set the release" 1>&2
	echo "  -S	Do not verify signatures" 1>&2
	echo "  -s	Verify signatures (default)" 1>&2
	echo "  -v	Be more verbose" 1>&2
	return 1
}


#verify
_verify()
{
	file="$1"

	$NETPGP --verify --userid "$VERIFY_USERID" "${file}.sig"
}


#main
#parse arguments
while getopts "D:O:M:P:BbIKkmnpqr:Ssv" name; do
	case "$name" in
		D)
			DESTDIR="$OPTARG"
			;;
		O)
			export "${OPTARG%%=*}"="${OPTARG#*=}"
			;;
		P)
			EDGEBSD_PATH="$OPTARG"
			;;
		B)
			BOOTLOADER=0
			;;
		b)
			BOOTLOADER=1
			;;
		I)
			BOOTLOADER=1
			KERNEL=1
			INIT=1
			;;
		K)
			KERNEL=0
			;;
		k)
			KERNEL=1
			;;
		M)
			EDGEBSD_MIRROR="$OPTARG"
			;;
		m)
			MODULES=1
			;;
		n)
			NOOP=1
			;;
		p)
			PACKAGES=1
			;;
		q)
			FTP="/usr/bin/ftp -V"
			VERBOSE=0
			;;
		r)
			RELEASE="$OPTARG"
			;;
		S)
			VERIFY=0
			;;
		s)
			VERIFY=1
			;;
		v)
			VERBOSE=$((VERBOSE + 1))
			[ $VERBOSE -ge 1 ] && FTP="/usr/bin/ftp"
			[ $VERBOSE -ge 2 ] && FTP="/usr/bin/ftp -v"
			;;
		*)
			_usage
			exit $?
			;;
	esac
done
shift $((OPTIND - 1))
if [ $# -ne 0 ]; then
	_usage
	exit $?
fi

_edgebsd_update
