summaryrefslogtreecommitdiff
path: root/sysutils
diff options
context:
space:
mode:
Diffstat (limited to 'sysutils')
-rw-r--r--sysutils/sysupgrade/DESCR15
-rw-r--r--sysutils/sysupgrade/Makefile89
-rw-r--r--sysutils/sysupgrade/PLIST9
-rw-r--r--sysutils/sysupgrade/TODO30
-rw-r--r--sysutils/sysupgrade/files/Kyuafile7
-rw-r--r--sysutils/sysupgrade/files/config.subr299
-rw-r--r--sysutils/sysupgrade/files/config_test.sh422
-rw-r--r--sysutils/sysupgrade/files/default.conf35
-rw-r--r--sysutils/sysupgrade/files/sysupgrade.8387
-rw-r--r--sysutils/sysupgrade/files/sysupgrade.conf.5160
-rw-r--r--sysutils/sysupgrade/files/sysupgrade.sh459
-rw-r--r--sysutils/sysupgrade/files/sysupgrade_test.sh1055
-rw-r--r--sysutils/sysupgrade/files/utils.subr125
-rw-r--r--sysutils/sysupgrade/files/utils_test.sh186
14 files changed, 3278 insertions, 0 deletions
diff --git a/sysutils/sysupgrade/DESCR b/sysutils/sysupgrade/DESCR
new file mode 100644
index 00000000000..74eac4397a9
--- /dev/null
+++ b/sysutils/sysupgrade/DESCR
@@ -0,0 +1,15 @@
+sysupgrade is a script to automate NetBSD system upgrades. sysupgrade
+works by first fetching distribution sets from a specified site or local
+directory, then by upgrading the system using such distribution sets and
+later by ensuring that the system configuration is up to date. All the
+process is controlled by a configuration file, and the defaults should
+suit the most common NetBSD upgrades.
+
+sysupgrade can be used to perform upgrades across different system major
+and/or minor versions, and it can also be used to track a stable or
+development branch from the CVS repository.
+
+sysbuild is the perfect companion to sysupgrade in those cases where you
+want to roll your own binaries: both utilities share a very similar
+command-line and configuration interface, and the default configuration
+files provide examples on how to integrate one with the other.
diff --git a/sysutils/sysupgrade/Makefile b/sysutils/sysupgrade/Makefile
new file mode 100644
index 00000000000..9dc54dde232
--- /dev/null
+++ b/sysutils/sysupgrade/Makefile
@@ -0,0 +1,89 @@
+# $NetBSD: Makefile,v 1.1 2012/08/06 17:06:17 jmmv Exp $
+
+DISTNAME= sysupgrade-1.0
+CATEGORIES= sysutils
+MASTER_SITES= # empty
+DISTFILES= # empty
+
+MAINTAINER= jmmv@NetBSD.org
+COMMENT= Automate upgrades of NetBSD
+LICENSE= modified-bsd
+
+PKG_INSTALLATION_TYPES= overwrite pkgviews
+PKG_DESTDIR_SUPPORT= user-destdir
+
+WRKSRC= ${WRKDIR}
+NO_CONFIGURE= YES
+
+UPGRADE_SUBST+= -e 's,@SYSUPGRADE_CACHEDIR@,${CACHEDIR},g'
+UPGRADE_SUBST+= -e 's,@SYSUPGRADE_ETCDIR@,${PKG_SYSCONFDIR},g'
+
+BUILD_DEFS+= VARBASE
+CACHEDIR= ${VARBASE}/cache/sysupgrade
+OWN_DIRS= ${CACHEDIR} root wheel
+
+EGDIR= ${PREFIX}/share/examples/sysupgrade
+CONF_FILES+= ${EGDIR}/default.conf ${PKG_SYSCONFDIR}/sysupgrade.conf
+
+PKG_OPTIONS_VAR= PKG_OPTIONS.sysupgrade
+PKG_SUPPORTED_OPTIONS= tests
+PKG_SUGGESTED_OPTIONS= tests
+
+.include "../../mk/bsd.options.mk"
+
+.if $(PKG_OPTIONS:Mtests)
+TEST_PROGS= config_test sysupgrade_test utils_test
+
+PLIST_SUBST+= TESTS=
+. include "../../devel/atf-libs/buildlink3.mk"
+
+do-build: build-tests
+build-tests:
+ cp ${FILESDIR}/Kyuafile ${WRKSRC}
+.for file in ${TEST_PROGS}
+ ${ECHO} '#! ${BUILDLINK_PREFIX.atf-libs}/bin/atf-sh' \
+ >${WRKSRC}/${file}
+ ${CAT} ${FILESDIR}/*.subr ${FILESDIR}/${file}.sh \
+ | ${SED} ${UPGRADE_SUBST} >>${WRKSRC}/${file}
+ ${CHMOD} +x ${WRKSRC}/${file}
+.endfor
+
+INSTALLATION_DIRS+= tests/sysupgrade
+
+do-install: install-tests
+install-tests:
+ ${INSTALL_DATA} ${WRKSRC}/Kyuafile ${DESTDIR}${PREFIX}/tests/sysupgrade
+.for file in ${TEST_PROGS}
+ ${INSTALL_SCRIPT} ${WRKSRC}/${file} \
+ ${DESTDIR}${PREFIX}/tests/sysupgrade/
+.endfor
+
+do-test:
+ cd ${WRKSRC} && PATH="${WRKSRC}:${PATH}" kyua test
+.else
+PLIST_SUBST+= TESTS=@comment
+.endif
+
+do-build:
+ ${ECHO} '#! ${SH}' >${WRKSRC}/sysupgrade
+ ${ECHO} 'set -e' >>${WRKSRC}/sysupgrade
+ ${CAT} ${FILESDIR}/*.subr ${FILESDIR}/sysupgrade.sh \
+ | ${SED} ${UPGRADE_SUBST} >>${WRKSRC}/sysupgrade
+ ${ECHO} 'sysupgrade_main "$${@}"' >>${WRKSRC}/sysupgrade
+ ${CHMOD} +x ${WRKSRC}/sysupgrade
+.for file in sysupgrade.8 sysupgrade.conf.5 default.conf
+ sed ${UPGRADE_SUBST} <${FILESDIR}/${file} >${WRKSRC}/${file}
+.endfor
+
+INSTALLATION_DIRS+= bin ${PKGMANDIR}/man5 ${PKGMANDIR}/man8 \
+ share/examples/sysupgrade
+
+do-install:
+ ${INSTALL_SCRIPT} ${WRKSRC}/sysupgrade ${DESTDIR}${PREFIX}/bin/
+ ${INSTALL_MAN} ${WRKSRC}/sysupgrade.8 \
+ ${DESTDIR}${PREFIX}/${PKGMANDIR}/man8/
+ ${INSTALL_MAN} ${WRKSRC}/sysupgrade.conf.5 \
+ ${DESTDIR}${PREFIX}/${PKGMANDIR}/man5/
+ ${INSTALL_DATA} ${WRKSRC}/default.conf ${DESTDIR}${EGDIR}
+
+.include "../../mk/bsd.pkg.mk"
diff --git a/sysutils/sysupgrade/PLIST b/sysutils/sysupgrade/PLIST
new file mode 100644
index 00000000000..2f495d47dff
--- /dev/null
+++ b/sysutils/sysupgrade/PLIST
@@ -0,0 +1,9 @@
+@comment $NetBSD: PLIST,v 1.1 2012/08/06 17:06:17 jmmv Exp $
+bin/sysupgrade
+man/man5/sysupgrade.conf.5
+man/man8/sysupgrade.8
+share/examples/sysupgrade/default.conf
+${TESTS}tests/sysupgrade/Kyuafile
+${TESTS}tests/sysupgrade/config_test
+${TESTS}tests/sysupgrade/sysupgrade_test
+${TESTS}tests/sysupgrade/utils_test
diff --git a/sysutils/sysupgrade/TODO b/sysutils/sysupgrade/TODO
new file mode 100644
index 00000000000..d02130ac626
--- /dev/null
+++ b/sysutils/sysupgrade/TODO
@@ -0,0 +1,30 @@
+- Deduce the current NetBSD release from /etc/release and the target
+ release from etc.tgz and inform the user about the changes. This will be
+ necessary if the upgrade process needs to apply specific tweaks depending
+ on the affected NetBSD releases (which is not the case at the moment).
+
+- Ability to automatically deduce the next upgrade target from a collection
+ of directories (e.g. from FTP). We should be able to tell sysupgrade to
+ follow along 6.0.x, or 6.x, or the daily builds and get it to pick the
+ most recent available build. Having to manually scan FTP directories to
+ select the correct build is... inconvenient.
+
+- Ensure that the fetched sets belong to the current architecture. I have
+ bitten once by mistakenly pointing my custom update scripts to the wrong
+ platform directory, rendering the machine unusable as soon as base.tgz
+ was unpacked.
+
+- Download release checksums and validate files against them. The 'fetch'
+ command should unconditionally download the checksums every time it is
+ run and then deduce whether it needs to redownload (possibly-newer) sets
+ or do nothing.
+
+- Add destdir support to etcupdate(8) and allow the 'etcupdate' command to
+ run when destdir is enabled.
+
+- Maybe sysupgrade should be more interactive by default, letting the user
+ know what exactly is going to happen before doing so (e.g. what will be
+ the new version, where things are being downloaded from, etc.), and
+ providing a "quiet mode" flag instead. etcupdate is interactive anyway,
+ so adding more interactive steps (as long as they can be disabled) does
+ not seem a big deal.
diff --git a/sysutils/sysupgrade/files/Kyuafile b/sysutils/sysupgrade/files/Kyuafile
new file mode 100644
index 00000000000..135b420c442
--- /dev/null
+++ b/sysutils/sysupgrade/files/Kyuafile
@@ -0,0 +1,7 @@
+syntax("kyuafile", 1)
+
+test_suite("sysupgrade")
+
+atf_test_program{name="config_test"}
+atf_test_program{name="sysupgrade_test"}
+atf_test_program{name="utils_test"}
diff --git a/sysutils/sysupgrade/files/config.subr b/sysutils/sysupgrade/files/config.subr
new file mode 100644
index 00000000000..d2f6a90bd35
--- /dev/null
+++ b/sysutils/sysupgrade/files/config.subr
@@ -0,0 +1,299 @@
+# Copyright 2012 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * 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.
+# * Neither the name of Google Inc. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT
+# OWNER 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.
+
+# \file config.subr
+# Configuration file processing and queries.
+
+
+# List of valid configuration variables.
+#
+# This is initialized by config_init and should remain unchanged thorough the
+# execution of the program.
+CONFIG_VARS=
+
+
+# List of overrides to apply by config_apply_overrides.
+#
+# The overrides are recorded and applied separately because they need to happen
+# after a configuration file has been loaded. The contents of this list are
+# words of the form: set:<variable> or unset:<variable>. For those variables
+# listed in set:, there is a corresponding config_override_var_<variable>
+# variable containing the new value.
+CONFIG_OVERRIDES=
+
+
+# Initializes the configuration module.
+#
+# \param ... List of configuration variables to recognize in configuration files
+# and user overrides.
+config_init() {
+ CONFIG_VARS="${@}"
+}
+
+
+# Checks if a configuration variable is known.
+#
+# \param var Name of the variable to check.
+#
+# \return True if the variable was registered by config_init, false otherwise.
+config_is_valid() {
+ local var="${1}"; shift
+
+ local known_var
+ for known_var in ${CONFIG_VARS}; do
+ if [ "${known_var}" = "${var}" ]; then
+ return 0
+ fi
+ done
+ return 1
+}
+
+
+# Checks if a configuration variable is defined.
+#
+# \param var The name of the variable to check.
+#
+# \return True if the variable is defined (even if empty), false otherwise.
+config_has() {
+ local var="${1}"; shift
+
+ local is_unset
+ eval is_unset="\${config_var_${var}-yes_this_is_unset}"
+ if [ "${is_unset}" = yes_this_is_unset ]; then
+ return 1
+ else
+ return 0
+ fi
+}
+
+
+# Gets the value of a defined configuration variable.
+#
+# \post The value of the variable is printed to stdout, if any.
+#
+# \post If the variable is not defined, this terminates execution with an error.
+#
+# \param var Name of the configuration variable to query.
+config_get() {
+ local var="${1}"; shift
+
+ if config_has "${var}"; then
+ eval echo "\${config_var_${var}}"
+ else
+ utils_error "Required configuration variable ${var} not set"
+ fi
+}
+
+
+# Gets the value of configuration variable interpreting it as a boolean.
+#
+# \param var The variable to query.
+#
+# \return True if the variable is set to a truth value, false if its value is
+# false or if it is not defined.
+config_get_bool() {
+ local var="${1}"; shift
+
+ if config_has "${var}"; then
+ case "$(config_get "${var}")" in
+ [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee])
+ return 0
+ ;;
+ [Nn][Oo]|[Ff][Aa][Ll][Ss][Ee])
+ return 1
+ ;;
+ *)
+ utils_error "Invalid boolean value in variable ${var}"
+ ;;
+ esac
+ else
+ return 1
+ fi
+}
+
+
+# Gets the value of a configuration variable with a default fallback.
+#
+# \post The value of the variable is printed to stdout, if any.
+#
+# \param var Name of the configuration variable to query.
+# \param default Default value to return if the variable is not defined.
+config_get_default() {
+ local var="${1}"; shift
+ local default="${1}"; shift
+
+ if config_has "${var}"; then
+ config_get "${var}"
+ else
+ echo "${default}"
+ fi
+}
+
+
+# Sets a configuration variable.
+#
+# \post Execution terminates if the variable is not valid.
+#
+# \param var The name of the configuration variable to set.
+# \param value The value to set.
+config_set() {
+ local var="${1}"; shift
+ local value="${1}"; shift
+
+ config_is_valid "${var}" \
+ || utils_usage_error "Unknown configuration variable ${var}"
+ eval "config_var_${var}=\"\${value}\""
+}
+
+
+# Unsets a configuration variable.
+#
+# \param var The name of the configuration variable to unset.
+config_unset() {
+ local var="${1}"; shift
+
+ config_is_valid "${var}" \
+ || utils_usage_error "Unknown configuration variable ${var}"
+ eval unset "config_var_${var}"
+}
+
+
+# Loads a configuration file.
+#
+# \pre config_init should have been called to register the valid configuration
+# variables. Any non-registered configuration variable found in the file will
+# not be available through any of the functions in this module.
+#
+# \post The configuration module is updated with the values defined in the
+# configuration file.
+#
+# \post Any errors in the processing of the configuration file terminate the
+# execution of the script.
+#
+# \param config_file Path to the file to load.
+config_load() {
+ local config_file="${1}"; shift
+
+ [ -e "${config_file}" ] || utils_error "Configuration file ${config_file}" \
+ "does not exist"
+
+ # User-facing variables.
+ local var
+ for var in ${CONFIG_VARS}; do
+ unset "${var}" || true
+ eval local "${var}"
+ done
+
+ local real_config_file
+ case "${config_file}" in
+ */*) real_config_file="${config_file}" ;;
+ *) real_config_file="./${config_file}" ;;
+ esac
+ ( . "${real_config_file}" ) || utils_error "Failed to load configuration" \
+ "file ${config_file}"
+ . "${real_config_file}"
+
+ for var in ${CONFIG_VARS}; do
+ local value
+ eval value="\${${var}-unset}"
+ [ "${value-unset}" != unset ] || continue
+
+ if [ -n "${value}" ]; then
+ config_set "${var}" "${value}"
+ else
+ unset "config_var_${var}" || true
+ fi
+ done
+
+ config_apply_overrides
+}
+
+
+# Applies recorded overrides to the current configuration.
+#
+# This is just a helper function for config_load and should not be used
+# directly.
+#
+# \post The configuration data in memory is modified according to the data
+# recorded in the overrides.
+#
+# \post The contents of CONFIG_OVERRIDES is cleared.
+config_apply_overrides() {
+ for override in ${CONFIG_OVERRIDES}; do
+ case "${override}" in
+ set:*)
+ local var="$(echo ${override} | cut -d : -f 2)"
+ local value
+ eval value="\"\${config_override_var_${var}}\""
+ config_set "${var}" "${value}"
+ ;;
+ unset:*)
+ local var="$(echo ${override} | cut -d : -f 2)"
+ unset "config_var_${var}" || true
+ ;;
+ esac
+ done
+ CONFIG_OVERRIDES=
+}
+
+
+# Records an override to be applied to the configuration.
+#
+# Overrides are configuration variables set through the command line. These can
+# be set before loading the configuration file with config_load.
+#
+# \post Any errors in the processing of the configuration override terminate the
+# execution of the script.
+#
+# \param arg An override of the form variable=value.
+config_override() {
+ local arg="${1}"; shift
+
+ case "${arg}" in
+ *=*)
+ ;;
+ *)
+ utils_usage_error "Invalid configuration override" \
+ "${arg}; must be of the form variable=value"
+ ;;
+ esac
+ local var="$(echo "${arg}" | cut -d = -f 1)"
+ local value="$(echo "${arg}" | cut -d = -f 2-)"
+
+ [ -n "${var}" ] || utils_usage_error "Invalid configuration override" \
+ "${arg}; must be of the form variable=value"
+ config_is_valid "${var}" \
+ || utils_usage_error "Unknown configuration variable ${var}"
+
+ if [ -n "${value}" ]; then
+ eval "config_override_var_${var}=\"${value}\""
+ CONFIG_OVERRIDES="${CONFIG_OVERRIDES} set:${var}"
+ else
+ CONFIG_OVERRIDES="${CONFIG_OVERRIDES} unset:${var}"
+ fi
+}
diff --git a/sysutils/sysupgrade/files/config_test.sh b/sysutils/sysupgrade/files/config_test.sh
new file mode 100644
index 00000000000..0de28814523
--- /dev/null
+++ b/sysutils/sysupgrade/files/config_test.sh
@@ -0,0 +1,422 @@
+# Copyright 2012 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * 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.
+# * Neither the name of Google Inc. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT
+# OWNER 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.
+
+
+atf_test_case is_valid__true
+is_valid__true_body() {
+ config_init VAR1 VAR3
+ for var in VAR1 VAR3; do
+ config_is_valid "${var}" || atf_fail "${var} not found"
+ done
+}
+
+
+atf_test_case is_valid__false
+is_valid__false_body() {
+ config_init VAR1 VAR3
+ for var in VAR11 VAR2 VAR; do
+ if config_is_valid "${var}"; then
+ atf_fail "${var} found but was not registered"
+ fi
+ done
+}
+
+
+atf_test_case has__true__empty
+has__true__empty_body() {
+ config_init TESTVAR
+ config_set TESTVAR ""
+ config_has TESTVAR || atf_fail "Expected variable not found"
+}
+
+
+atf_test_case has__true__not_empty
+has__true__not_empty_body() {
+ config_init TESTVAR
+ config_set TESTVAR "foo"
+ config_has TESTVAR || atf_fail "Expected variable not found"
+}
+
+
+atf_test_case has__false
+has__false_body() {
+ config_init TESTVAR
+ if config_has TESTVAR; then
+ atf_fail "Unexpected variable found"
+ fi
+}
+
+
+atf_test_case get__ok__empty
+get__ok__empty_body() {
+ config_init TESTVAR
+
+ config_set TESTVAR ""
+ [ -z "$(config_get TESTVAR)" ] || atf_fail "Failed to query value"
+}
+
+
+atf_test_case get__ok__not_empty
+get__ok__not_empty_body() {
+ config_init TESTVAR
+
+ config_set TESTVAR some-value
+ [ "$(config_get TESTVAR)" = some-value ] || atf_fail "Failed to query value"
+}
+
+
+atf_test_case get__undefined_variable
+get__undefined_variable_body() {
+ config_init TESTVAR
+
+ if ( config_get TESTVAR ) >out 2>err; then
+ atf_fail "Got unset variable successfully"
+ else
+ grep "Required configuration variable TESTVAR not set" err >/dev/null \
+ || atf_fail "Expected error message not found"
+ fi
+}
+
+
+atf_test_case get_bool__true
+get_bool__true_body() {
+ config_init TESTVAR
+
+ for value in yes Yes true True; do
+ config_set TESTVAR "${value}"
+ config_get_bool TESTVAR || atf_fail "Expected true, but got false"
+ done
+}
+
+
+atf_test_case get_bool__false
+get_bool__false_body() {
+ config_init TESTVAR
+
+ for value in no No false False; do
+ config_set TESTVAR "${value}"
+ if config_get_bool TESTVAR; then
+ atf_fail "Expected false, but got true"
+ fi
+ done
+}
+
+
+atf_test_case get_bool__undefined_variable
+get_bool__undefined_variable_body() {
+ config_init TESTVAR
+
+ if config_get_bool TESTVAR; then
+ atf_fail "Expected false, but got true"
+ fi
+}
+
+
+atf_test_case get_bool__invalid_value
+get_bool__invalid_value_body() {
+ config_init TESTVAR
+
+ config_set TESTVAR not-a-boolean
+ if ( config_get_bool TESTVAR ) >out 2>err; then
+ atf_fail "Got invalid boolean value successfully"
+ else
+ grep "Invalid boolean value in variable TESTVAR" err >/dev/null \
+ || atf_fail "Expected error message not found"
+ fi
+}
+
+
+atf_test_case get_default__defined__empty
+get_default__defined__empty_body() {
+ config_init TESTVAR
+ config_set TESTVAR ""
+ [ "$(config_get_default TESTVAR 'foo')" = "" ] \
+ || atf_fail "Did not fetch defined value"
+}
+
+
+atf_test_case get_default__defined__not_empty
+get_default__defined__not_empty_body() {
+ config_init TESTVAR
+ config_set TESTVAR "bar"
+ [ "$(config_get_default TESTVAR 'foo')" = "bar" ] \
+ || atf_fail "Did not fetch defined value"
+}
+
+
+atf_test_case get_default__default__empty
+get_default__default__empty_body() {
+ config_init TESTVAR
+ [ "$(config_get_default TESTVAR '')" = "" ] \
+ || atf_fail "Did not fetch default value"
+}
+
+
+atf_test_case get_default__default__not_empty
+get_default__default__not_empty_body() {
+ config_init TESTVAR
+ [ "$(config_get_default TESTVAR 'foo')" = "foo" ] \
+ || atf_fail "Did not fetch default value"
+}
+
+
+atf_test_case set__ok
+set__ok_body() {
+ config_init TESTVAR
+
+ config_set TESTVAR some-value
+ [ "${config_var_TESTVAR}" = some-value ] || atf_fail "Failed to set value"
+}
+
+
+atf_test_case set__unknown_variable
+set__unknown_variable_body() {
+ config_init TESTVAR
+
+ if ( config_set TESTVAR2 some-value ) >out 2>err; then
+ atf_fail "Set unknown variable successfully"
+ else
+ grep "Unknown configuration variable TESTVAR2" err >/dev/null \
+ || atf_fail "Expected error message not found"
+ fi
+}
+
+
+atf_test_case unset__ok
+unset__ok_body() {
+ config_init TESTVAR
+
+ config_var_TESTVAR=some-value
+ config_unset TESTVAR
+ [ "${config_var_TESTVAR-unset}" = unset ] \
+ || atf_fail "Failed to unset variable"
+}
+
+
+atf_test_case unset__unknown_variable
+unset__unknown_variable_body() {
+ config_init TESTVAR
+
+ if ( config_unset TESTVAR2 ) >out 2>err; then
+ atf_fail "Unset unknown variable successfully"
+ else
+ grep "Unknown configuration variable TESTVAR2" err >/dev/null \
+ || atf_fail "Expected error message not found"
+ fi
+}
+
+
+atf_test_case load__filter_variables
+load__filter_variables_body() {
+ config_init Z VAR1 EMPTY
+
+ cat >test.conf <<EOF
+A=foo
+Z=bar
+VAR1="some text"
+VAR2="some other text"
+EOF
+
+ config_load $(pwd)/test.conf || atf_fail "Failed to load test configuration"
+
+ [ "${config_var_Z}" = bar ] || \
+ atf_fail "Z not found in configuration"
+ [ "${config_var_VAR1}" = "some text" ] || \
+ atf_fail "VAR1 not found in configuration"
+
+ [ "${config_var_EMPTY-has_not_been_set}" = has_not_been_set ] || \
+ atf_fail "Undefined variable set, but should not have been"
+
+ [ "${config_var_A-unset}" = unset ] || \
+ atf_fail "A set in configuration, but not expected"
+ [ "${config_var_VAR2-unset}" = unset ] || \
+ atf_fail "VAR2 set in configuration, but not expected"
+}
+
+
+atf_test_case load__allow_undefine
+load__allow_undefine_body() {
+ config_init UNDEFINE
+
+ cat >test.conf <<EOF
+UNDEFINE=
+EOF
+
+ config_set UNDEFINE "remove me"
+ config_load $(pwd)/test.conf || atf_fail "Failed to load test configuration"
+ if config_has UNDEFINE; then
+ atf_fail "Undefine attempt from configuration did not work"
+ fi
+}
+
+
+atf_test_case load__current_directory
+load__current_directory_body() {
+ config_init A
+
+ cat >test.conf <<EOF
+A=foo
+EOF
+
+ config_load test.conf || atf_fail "Failed to load test configuration"
+
+ [ "${config_var_A}" = foo ] || \
+ atf_fail "A not found in configuration"
+}
+
+
+atf_test_case load__missing_file
+load__missing_file_body() {
+ if ( config_load missing.conf ) >out 2>err; then
+ atf_fail "Missing configuration file load succeeded"
+ else
+ grep "Configuration file missing.conf does not exist" err >/dev/null \
+ || atf_fail "Expected error message not found"
+ fi
+}
+
+
+atf_test_case load__invalid_file
+load__invalid_file_body() {
+ echo "this file is invalid" >invalid.conf
+
+ if ( config_load invalid.conf ) >out 2>err; then
+ atf_fail "Invalid configuration file load succeeded"
+ else
+ cat err
+ grep "Failed to load configuration file invalid.conf" err >/dev/null \
+ || atf_fail "Expected error message not found"
+ fi
+}
+
+
+atf_test_case override__ok_before_load
+override__ok_before_load_body() {
+ config_init VAR1 VAR2
+
+ cat >test.conf <<EOF
+VAR1="override me"
+VAR2="do not override me"
+EOF
+
+ config_override "VAR1=new value"
+ config_load test.conf || atf_fail "Failed to load test configuration"
+
+ [ "${config_var_VAR1}" = "new value" ] || atf_fail "Override failed"
+ [ "${config_var_VAR2}" = "do not override me" ] \
+ || atf_fail "Overrode more than one variable"
+}
+
+
+atf_test_case override__not_ok_after_load
+override__not_ok_after_load_body() {
+ config_init VAR1 VAR2
+
+ cat >test.conf <<EOF
+VAR1="override me"
+VAR2="do not override me"
+EOF
+
+ config_load test.conf || atf_fail "Failed to load test configuration"
+ config_override "VAR1=new value"
+
+ [ "${config_var_VAR1}" = "override me" ] \
+ || atf_fail "Override succeeded, but it should not have"
+ [ "${config_var_VAR2}" = "do not override me" ] \
+ || atf_fail "Overrode more than one variable"
+}
+
+
+atf_test_case override__invalid_format
+override__invalid_format_body() {
+ for arg in foo =bar ''; do
+ if ( config_override "${arg}" ) >out 2>err; then
+ atf_fail "Invalid configuration override ${arg} succeeded"
+ else
+ cat err
+ grep "Invalid configuration override ${arg}" err >/dev/null \
+ || atf_fail "Expected error message not found"
+ fi
+ done
+}
+
+
+atf_test_case override__unknown_variable
+override__unknown_variable_body() {
+ config_init Z VAR1
+ for arg in A=b VAR2=d; do
+ if ( config_override "${arg}" ) >out 2>err; then
+ atf_fail "Invalid configuration override ${arg} succeeded"
+ else
+ cat err
+ grep "Unknown configuration variable ${var}" err >/dev/null \
+ || atf_fail "Expected error message not found"
+ fi
+ done
+}
+
+
+atf_init_test_cases() {
+ atf_add_test_case is_valid__true
+ atf_add_test_case is_valid__false
+
+ atf_add_test_case has__true__empty
+ atf_add_test_case has__true__not_empty
+ atf_add_test_case has__false
+
+ atf_add_test_case get__ok__empty
+ atf_add_test_case get__ok__not_empty
+ atf_add_test_case get__undefined_variable
+
+ atf_add_test_case get_bool__true
+ atf_add_test_case get_bool__false
+ atf_add_test_case get_bool__undefined_variable
+ atf_add_test_case get_bool__invalid_value
+
+ atf_add_test_case get_default__defined__empty
+ atf_add_test_case get_default__defined__not_empty
+ atf_add_test_case get_default__default__empty
+ atf_add_test_case get_default__default__not_empty
+
+ atf_add_test_case set__ok
+ atf_add_test_case set__unknown_variable
+
+ atf_add_test_case unset__ok
+ atf_add_test_case unset__unknown_variable
+
+ atf_add_test_case load__filter_variables
+ atf_add_test_case load__allow_undefine
+ atf_add_test_case load__current_directory
+ atf_add_test_case load__missing_file
+ atf_add_test_case load__invalid_file
+
+ atf_add_test_case override__ok_before_load
+ atf_add_test_case override__not_ok_after_load
+ atf_add_test_case override__invalid_format
+ atf_add_test_case override__unknown_variable
+}
diff --git a/sysutils/sysupgrade/files/default.conf b/sysutils/sysupgrade/files/default.conf
new file mode 100644
index 00000000000..f1c665416a2
--- /dev/null
+++ b/sysutils/sysupgrade/files/default.conf
@@ -0,0 +1,35 @@
+# $NetBSD: default.conf,v 1.1 2012/08/06 17:06:17 jmmv Exp $
+
+# Configuration of automatic system upgrades by sysupgrade(8).
+#
+# See sysupgrade.conf(5) for details on the syntax of this file and the
+# meaning of the configuration variables. Note that not all the supported
+# configuration variables are listed in this sample configuration file.
+
+# Path to the release files (local path or remote URL).
+#RELEASEDIR="ftp://ftp.NetBSD.org/pub/NetBSD/NetBSD-6.0/$(uname -m)"
+RELEASEDIR="/home/sysbuild/release/$(uname -m)"
+
+# Name of the kernel to be installed.
+KERNEL=AUTO # Guess from /netbsd (requires config(1)).
+
+# Whitespace-separated list of sets to install.
+SETS=AUTO # Guess from /etc/mtree/set.* files.
+
+# Whitespace-separated list of postinstall(8) checks to automatically fix.
+POSTINSTALL_AUTOFIX="obsolete"
+
+# Whether to run etcupdate or not as part of an upgrade.
+#
+# Running etcupdate is the only interactive step in the upgrade process, so
+# setting this variable to 'no' effectively makes upgrades unattended. You
+# can later run etcupdate at a later step by hand.
+#ETCUPDATE=no
+
+# Whether to delete the downloaded files after an upgrade or not.
+#
+# If you set ETCUPDATE=no, you will most likely want to disable
+# auto-cleaning as well. The reason for this is that, if you want to later
+# do "sysupgrade etcupdate" using the same etc.tgz file downloaded during
+# the upgrade, you will need the same etc.tgz file to be present.
+#AUTOCLEAN=no
diff --git a/sysutils/sysupgrade/files/sysupgrade.8 b/sysutils/sysupgrade/files/sysupgrade.8
new file mode 100644
index 00000000000..e90ea72ff4b
--- /dev/null
+++ b/sysutils/sysupgrade/files/sysupgrade.8
@@ -0,0 +1,387 @@
+.\" Copyright 2012 Google Inc.
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions are
+.\" met:
+.\"
+.\" * Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" * 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.
+.\" * Neither the name of Google Inc. nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT
+.\" OWNER 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.
+.Dd August 6, 2012
+.Dt SYSUPGRADE 8
+.Os
+.Sh NAME
+.Nm sysupgrade
+.Nd upgrades a NetBSD system to a newer version
+.Sh SYNOPSIS
+common_flags ::=
+.Op Fl c Ar config_name
+.Op Fl d Ar destdir
+.Op Fl o Ar variable=value
+.Pp
+.Em Major commands :
+.Pp
+.Nm
+.Op common_flags
+.Ar auto
+.Op Ar releasedir
+.Nm
+.Op common_flags
+.Ar config
+.Op Fl a
+.Pp
+.Em Standalone upgrade steps :
+.Pp
+.Nm
+.Op common_flags
+.Ar clean
+.Nm
+.Op common_flags
+.Ar etcupdate
+.Nm
+.Op common_flags
+.Ar fetch
+.Op Ar releasedir
+.Nm
+.Op common_flags
+.Ar kernel
+.Op Ar kernel_name
+.Nm
+.Op common_flags
+.Ar modules
+.Nm
+.Op common_flags
+.Ar sets
+.Op Ar set1 .. setN
+.Nm
+.Op common_flags
+.Ar postinstall
+.Op Ar arg1 .. argN
+.Sh DESCRIPTION
+.Nm
+is a utility that automates the process of upgrading a possibly-running
+.Nx
+system to a newer release.
+.Pp
+.Nm
+works by first fetching the release sets from a remote site or from a local
+directory, then by upgrading the system using such release sets and finally by
+taking care of bringing the system configuration up to date.
+In other words,
+.Nm
+does nothing special on its own: it is just a utility that automates a highly
+manual process and relies on other tools within the system to perform its job.
+.Pp
+.Nm
+has a subcommand-based interface: every command performs a single step of the
+upgrade procedure, and the
+.Sq auto
+command orchestrates a complete upgrade by invoking the rest of the commands in
+a specific order.
+There are a set of options that apply to all commands (those stated before the
+command name), and every particular command may accept its own options and
+arguments as shown in the synopsis.
+.Pp
+The behavior of
+.Nm
+is defined by a configuration file that specifies how to apply an update to the
+system (see
+.Xr sysupgrade.conf 5 ) .
+For example, the configuration states which distribution sets ought to be
+installed, where they need to be downloaded from, and whether the system
+configuration files should be upgraded.
+.Pp
+The following options apply to all commands:
+.Bl -tag -width XoXvariableXvalueXX
+.It Fl c Ar config_file
+Specifies the configuration file to use.
+.Pp
+Default:
+.Pa @SYSUPGRADE_ETCDIR@/sysupgrade.conf
+.It Fl d Ar destdir
+Pato the the
+.Nx
+system to upgrade.
+.Pp
+This optional flag can be used to upgrade a non-live system or for
+testing/development purposes.
+Note that some steps (particularly
+.Sq etcupdate )
+do not support this feature and thus will never be run if set.
+.Pp
+Default: not set (which means
+.Pa /
+is affected).
+.It Fl o Ar variable=value
+Applies an override to the loaded configuration.
+.Pp
+The
+.Ar variable
+part of the argument must be any of the recognized configuration variables
+described in
+.Xr sysupgrade.conf 5 .
+The
+.Ar value ,
+if not empty, specifies the value to set the configuration variable to.
+If
+.Ar value
+is empty, then the configuration variable is unset.
+.El
+.Ss The auto command
+The auto command is the most important command in
+.Nm ,
+and is probably the one you will find yourself using most frequently.
+This is the command that takes care of upgrading a full
+.Nx
+installation, and it does so by invoking the other commands in the tool in the
+specific order in which they are needed.
+.Pp
+The optional argument
+.Ar releasedir
+points to the release directory or URL to use, overriding the value of
+.Va RELEASEDIR
+in the configuration file (if any).
+If you are tracking daily builds from an FTP site, for example, you will
+probably want to avoid setting
+.Va RELEASEDIR
+in the configuration file and instead pass an URL each time you run this
+command.
+.Pp
+The standard upgrade procedure performed by this command is as follows:
+.Bl -enum
+.It
+.Sq fetch :
+Retrieve distribution sets into the local cache directory.
+.It
+.Sq modules :
+Unpack new kernel modules.
+.It
+.Sq kernel :
+Upgrade kernel.
+.It
+.Sq sets :
+Upgrade system sets, except configuration files.
+.It
+.Sq etcupdate :
+Merge new changes to configuration files.
+This is the only interactive step in the process and can be disabled for this
+reason by setting
+.Va ETCUPDATE
+to false.
+.It
+.Sq postinstall :
+Perform sanity checks and optionally apply unconditional fixes to the upgraded
+system.
+.It
+.Sq clean :
+Remove contents of the cache directory.
+This can be disabled by setting
+.Va AUTOCLEAN
+to false in case you want to keep the downloaded distribution files around.
+.El
+.Ss The clean command
+The clean command removes any distribution sets from the local cache directory.
+.Ss The config command
+The config command dumps the loaded configuration to the standard output.
+The format of the output is not a script, so it cannot be fed back into
+.Nm .
+The purpose of this command is to aid in debugging the configuration of the
+tool before performing any builds, particularly when the configuration
+files use shell logic to determine the value of any variables.
+.Pp
+The following options are accepted:
+.Bl -tag -width XaXX
+.It Fl a
+Process automatic variables such as
+.Va KERNEL
+and
+.Va SETS
+and display their deduced values instead of only showing the literal
+.Sq AUTO .
+.El
+.Ss The etcupdate command
+The etcupdate command invokes
+.Xr etcupdate 8
+to perform an interactive upgrade of the system configuration files in
+.Pa /etc .
+This is the only interactive process in a full system upgrade.
+.Pp
+In order for this command to do anything useful, the list of sets to install as
+specified by the
+.Va SETS
+configuration variable
+.Em must include
+.Sq etc ,
+and may optionally include
+.Sq xetc .
+.Pp
+Note that, due to defficiencies in the
+.Xr etcupdate 8
+utility, this command does not work when
+.Va DESTDIR
+is set.
+.Ss The fetch command
+The fetch command gets a copy of the release sets as pointed to by the
+.Va RELEASEDIR
+variable into a local cache directory, or from the directory indicated by the
+optional argument
+.Ar releasedir .
+.Pp
+If
+.Va RELEASEDIR
+points to a local directory, the utility just creates symlinks within the cache
+directory pointing to the original files.
+Otherwise, if
+.Va RELEASEDIR
+points to a remote FTP or HTTP site, the fetch command will retrieve the
+contents of the release directory into the local cache directory.
+.Pp
+Please note that all the commands that access distribution sets do so by looking
+for such files in the cache directory
+.Em even when the release directory is in a local path .
+This means that, for such commands to work, you must run fetch beforehand.
+.Ss The kernel command
+The kernel command upgrades the kernel to a newer version.
+The kernel to install is determined by the optional argument
+.Ar kernel_name
+if present, or otherwise from the
+.Va KERNEL
+variable.
+The kernel is expected to be found in a
+.Sq kern-<NAME>
+distribution set in the release directory.
+.Pp
+The previous kernel is backed up as
+.Pa <destdir>/onetbsd.
+.Ss The modules command
+The modules command upgrades the kernel modules to a newer version.
+This operation only takes place if the sets to be installed, as specified by the
+.Va SETS
+variable, contains the
+.Sq modules
+set.
+.Ss The sets command
+The sets command upgrades all non-kernel, non-modules and non-configuration sets
+of the system to a newer version.
+In other words, this command installs sets like
+.Sq base
+or
+.Sq tests
+but explicitly skips sets of the forms
+.Sq *etc ,
+.Sq kern-*
+and
+.Sq modules .
+.Pp
+The list of sets to be installed is determined by the optional arguments passed
+to the command or, if none, from the value of the
+.Va SETS
+configuration variable.
+.Ss The postinstall command
+The postinstall command invokes the
+.Xr postinstall 8
+utility to perform checks and fixes on the system after all new files have been
+put in place.
+.Pp
+The
+.Va POSTINSTALL_AUTOFIX
+variable can optionally include a list of
+.Xr postinstall 8
+fixes to be applied to a system unconditionally.
+For example, listing
+.Sq obsolete
+in this variable is usually useful as this check fails often during upgrades and
+is safe to auto-fix.
+.Pp
+Any arguments supplied to the command are passed directly to
+.Xr postinstall 8 ,
+which comes handy in those cases where you have to manually fix a broken test.
+.Sh FILES
+.Bl -tag -width XXXX
+.It Pa @SYSUPGRADE_ETCDIR@/sysupgrade.conf
+Default configuration file.
+.It Pa @SYSUPGRADE_CACHEDIR@
+Location where distribution sets are temporarily stored.
+The
+.Sq fetch
+command writes files into this directory and the
+.Sq clean
+command clears its contents.
+.It Pa /home/sysbuild/release/<machine>
+Standard location of the releases built by the
+.Xr sysbuild 1
+utility.
+.El
+.Sh EXAMPLES AND TROUBLESHOOTING
+The most common way of executing
+.Nm
+is by using the
+.Sq auto
+command and relying in the default configuration file:
+.Bd -literal -offset indent
+$ sysupgrade auto
+.Ed
+.Pp
+If you wish to track minor stable releases, you may want to do something like
+this every time a new release is published:
+.Bd -literal -offset indent
+$ sysupgrade auto \\
+ ftp://ftp.NetBSD.org/pub/NetBSD/NetBSD-6.<minor>/$(uname -m)
+.Ed
+.Pp
+If the upgrade process fails due to an aborted FTP or HTTP connection, simply
+rerun
+.Nm
+in
+.Sq auto
+mode and it will resume the download where it left off.
+.Pp
+If the
+.Sq postinstall
+step fails due to broken checks, you can manually resume that stage and complete
+the upgrade by doing:
+.Bd -literal -offset indent
+$ sysupgrade postinstall fix <names of the failed checks>
+$ sysupgrade clean
+.Ed
+.Pp
+If you have decided to run
+.Xr etcupdate 8
+by hand separately from
+.Nm ,
+you could do:
+.Bd -literal -offset indent
+$ sysupgrade -o AUTOCLEAN=no -o ETCUPDATE=no auto
+\&... and later, at your earliest convenience ...
+$ sysupgrade etcupdate
+$ sysupgrade clean
+.Ed
+.Sh SEE ALSO
+.Xr sysbuild 1 ,
+.Xr sysupgrade.conf 5 ,
+.Xr etcupdate 8 ,
+.Xr postinstall 8 .
+.Sh AUTHORS
+The
+.Nm
+utility was developed by
+.An Julio Merino
+.Aq jmmv@NetBSD.org .
diff --git a/sysutils/sysupgrade/files/sysupgrade.conf.5 b/sysutils/sysupgrade/files/sysupgrade.conf.5
new file mode 100644
index 00000000000..1df328fc95d
--- /dev/null
+++ b/sysutils/sysupgrade/files/sysupgrade.conf.5
@@ -0,0 +1,160 @@
+.\" Copyright 2012 Google Inc.
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions are
+.\" met:
+.\"
+.\" * Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" * 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.
+.\" * Neither the name of Google Inc. nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT
+.\" OWNER 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.
+.Dd August 6, 2012
+.Dt SYSUPGRADE.CONF 5
+.Os
+.Sh NAME
+.Nm sysupgrade.conf
+.Nd configuration file to control NetBSD system upgrades
+.Sh DESCRIPTION
+Configuration files for
+.Xr sysupgrade 8
+are simple shell scripts that set, or explicitly clear, a collection of known
+configuration variables.
+.Pp
+The following configuration variables are recognized:
+.Bl -tag
+.It Va AUTOCLEAN
+Whether to automatically delete the downloaded files or not as part of the
+.Sq auto
+command.
+.Pp
+If you set
+.Va ETCUPDATE
+to false, you will probably want to disable autocleaning.
+This will let you run the
+.Sq etcupdate
+command on your own at a later stage reusing the same distribution sets used to
+upgrade the system.
+.Pp
+Default: yes.
+.It Va CACHEDIR
+Path to the local directory into which distribution sets are temporarily fetched
+while applying a system upgrade.
+Note that all
+.Nm
+commands that need access to the distribution sets (pretty much all of them)
+will read from this directory.
+If you plan on issuing different upgrade steps at different times, this
+directory should persist across the different executions.
+.Pp
+Default:
+.Pa @SYSUPGRADE_CACHEDIR@
+.It Va DESTDIR
+Path to the root of the system to be upgraded.
+This is prepended to all other paths affected by
+.Xr sysupgrade 8
+and can be used to perform upgrades of non-live systems or for testing
+purposes.
+.Pp
+Default: not set (which means
+.Pa /
+is affected).
+.It Va ETCUPDATE
+Whether to run
+.Xr etcupdate 8
+as part of an automated upgrade.
+You may want to disable this because this is the only interactive step in the
+upgrade process.
+.Pp
+If set to false, the
+.Sq auto
+subcommand will skip this step.
+In this case, you are also recommended to set
+.Va AUTOCLEAN
+to
+.Sq no
+so that you are not required to redownload the distribution files just to run
+.Xr etcupdate 8
+later.
+.Pp
+Default: yes.
+.It Va KERNEL
+Name of the kernel to install.
+There must be a
+.Sq kern-<KERNEL>
+distribution set in the release directory matching this name.
+.Pp
+If this is set to the magic value
+.Sq AUTO ,
+.Xr sysupgrade 8
+will attempt to automatically determine the name of the current kernel by using
+.Xr config 1
+on
+.Pa <DESTDIR>/netbsd .
+If the guessing fails, an error is raised to prevent installing a mismatching
+kernel.
+.Pp
+Default: AUTO.
+.It Va POSTINSTALL_AUTOFIX
+Whitespace-separated list of
+.Xr postinstall 8
+checks that will be unconditionally fixed as part of an upgrade.
+.Pp
+It is recommended that you include
+.Sq obsolete
+in this list at the very least: this check often fails because it is common for
+newer releases to drop existing files, and fixing it should be harmless.
+Note that old shared libraries are never cleaned by this check, so existing
+third-party binaries should not stop working even after auto-fixing this check.
+.Pp
+Default: empty.
+.It Va RELEASEDIR
+Location of the release directory (which must hold a
+.Pa binary/sets/
+subdirectory).
+This can be either a local path or a URL.
+.Pp
+Default: not set.
+.It Va SETS
+Whitespace-separated list of distribution sets to install.
+If this is set to the magic value
+.Sq AUTO ,
+the list of sets is determined from the files in
+.Pa <DESTDIR>/etc/mtree .
+.Pp
+This list should not include any
+.Sq kern-*
+sets as this is automatically handled by the
+.Va KERNEL
+variable.
+.Pp
+The presence of any configuration sets (such as
+.Sq etc
+or
+.Sq xetc )
+in this list enables the etcupdate and postinstall steps in the automated
+upgrade procedure.
+.Pp
+The presence of a modules set enables the modules step in the automated upgrade
+procedure.
+.Pp
+Default: AUTO.
+.El
+.Sh SEE ALSO
+.Xr sysupgrade 8 .
diff --git a/sysutils/sysupgrade/files/sysupgrade.sh b/sysutils/sysupgrade/files/sysupgrade.sh
new file mode 100644
index 00000000000..78b97605521
--- /dev/null
+++ b/sysutils/sysupgrade/files/sysupgrade.sh
@@ -0,0 +1,459 @@
+# Copyright 2012 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * 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.
+# * Neither the name of Google Inc. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT
+# OWNER 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.
+
+# \file sysupgrade.sh
+# Entry point and main program logic.
+
+
+# List of valid configuration variables.
+#
+# Please remember to update sysbuild(1) if you change this list.
+SYSUPGRADE_CONFIG_VARS="AUTOCLEAN CACHEDIR DESTDIR ETCUPDATE KERNEL
+ POSTINSTALL_AUTOFIX RELEASEDIR SETS"
+
+
+# Directory in which to keep downloaded release files.
+#
+# Can be overriden for test purposes only.
+: ${SYSUPGRADE_CACHEDIR:="@SYSUPGRADE_CACHEDIR@"}
+
+
+# Paths to installed files.
+#
+# Can be overriden for test purposes only.
+: ${SYSUPGRADE_ETCDIR:="@SYSUPGRADE_ETCDIR@"}
+
+
+# Sets defaults for configuration variables that need a value.
+#
+# This function should be called before the configuration file has been loaded.
+# This means that the user can undefine a required configuration variable, but
+# we let him shoot himself in the foot if he so desires.
+sysupgrade_set_defaults() {
+ # Please remember to update sysupgrade(8) if you change any default values.
+ config_set AUTOCLEAN "yes"
+ config_set CACHEDIR "${SYSUPGRADE_CACHEDIR}"
+ config_set ETCUPDATE "yes"
+ config_set KERNEL "AUTO"
+ config_set SETS "AUTO"
+}
+
+
+# Interprets magic configuration values.
+#
+# This function should be called after the configuration file has been loaded.
+# It takes care of performing any required post-processing on the configuration
+# variables, such as expanding the magic AUTO keyword to the actual value.
+sysupgrade_auto_config() {
+ if [ "$(config_get_default KERNEL "")" = "AUTO" ]; then
+ local kernel="$(config_get_default DESTDIR "")/netbsd"
+ if [ -e "${kernel}" ]; then
+ local kernel_name="$(config -x "${kernel}" | head -n 1 \
+ | cut -d \" -f 2)"
+ [ -n "${kernel_name}" ] || utils_error "Failed to determine" \
+ "kernel name; please set KERNEL explicitly"
+ config_set KERNEL "${kernel_name}"
+ else
+ config_unset KERNEL
+ fi
+ fi
+
+ if [ "$(config_get_default SETS "")" = "AUTO" ]; then
+ local mtree="$(config_get_default DESTDIR "")/etc/mtree/"
+ if [ -d "${mtree}" ]; then
+ local all_sets="$(cd "${mtree}" && echo set.* \
+ | sed -e 's,set\.,,g')"
+ config_set SETS "${all_sets}"
+ else
+ config_unset SETS
+ fi
+ fi
+}
+
+
+# Dumps the loaded configuration.
+#
+# \params ... The options and arguments to the command.
+sysupgrade_config() {
+ local eval_auto=no
+ while getopts ':a' arg "${@}"; do
+ case "${arg}" in
+ a) # Evaluate automatic settings.
+ eval_auto=yes
+ ;;
+
+ \?)
+ utils_usage_error "Unknown option -${OPTARG}"
+ ;;
+ esac
+ done
+ shift $((${OPTIND} - 1))
+
+ [ ${#} -eq 0 ] || utils_usage_error "config does not take any arguments"
+
+ [ "${eval_auto}" = no ] || sysupgrade_auto_config
+
+ for var in ${SYSUPGRADE_CONFIG_VARS}; do
+ if config_has "${var}"; then
+ echo "${var} = $(config_get "${var}")"
+ else
+ echo "${var} is undefined"
+ fi
+ done
+}
+
+
+# Gets the path to a set, and ensures it exists.
+#
+# \post The path to the tgz of the set is printed on stdout.
+#
+# \param set_name The name of the set to query.
+get_set() {
+ local set_name="${1}"; shift
+
+ echo "$(config_get CACHEDIR)/${set_name}.tgz"
+}
+
+
+# Ensures that a given set exists.
+#
+# \param set_name The name of the set to query.
+require_set() {
+ local set_name="${1}"; shift
+
+ local set_tgz="$(get_set "${set_name}")"
+ [ -f "${set_tgz}" ] || utils_error "Cannot find ${set_name}; did you run" \
+ "'${Utils_ProgName} fetch' first?"
+}
+
+
+# Extracts a set into the destdir.
+#
+# The set to be extracted must have been previously fetched into the cache
+# directory by sysupgrade_fetch command.
+#
+# \param set_name Name of the set to extract, without the .tgz extension.
+extract_set() {
+ local set_name="${1}"; shift
+
+ require_set "${set_name}"
+
+ local destdir="$(config_get_default DESTDIR "")"
+ local set_tgz="$(get_set "${set_name}")"
+
+ utils_info "Extracting ${set_name} into ${destdir}/"
+ [ -z "${destdir}" ] || utils_run mkdir -p "${destdir}"
+ progress -zf "${set_tgz}" tar -xp -C "${destdir}/" -f -
+}
+
+
+# Fetches the release sets into the cache directory.
+#
+# \param releasedir Optional override of the release directory to use.
+sysupgrade_fetch() {
+ [ ${#} -lt 2 ] \
+ || utils_usage_error "fetch takes zero or one arguments"
+
+ [ -z "${1}" ] || config_set "RELEASEDIR" "${1}"
+
+ local releasedir="$(config_get RELEASEDIR)"
+ local cachedir="$(config_get CACHEDIR)"
+ local fetch_sets="$(config_get SETS)"
+ if config_has KERNEL; then
+ fetch_sets="${fetch_sets} kern-$(config_get KERNEL)"
+ fi
+
+ case "${releasedir}" in
+ ftp://*|http://*)
+ mkdir -p "${cachedir}"
+
+ for set_name in ${fetch_sets}; do
+ local file="${cachedir}/${set_name}.tgz"
+ if [ -f "${file}" ]; then
+ utils_warning "Reusing existing ${file}"
+ else
+ local url="${releasedir}/binary/sets/${set_name}.tgz"
+ utils_info "Downloading ${url} into ${cachedir}"
+ rm -f "${file}"
+ ftp -R -o"${file}.tmp" "${url}" \
+ || utils_error "Failed to fetch ${url}"
+ mv "${file}.tmp" "${file}"
+ fi
+ done
+ ;;
+
+ /*)
+ mkdir -p "${cachedir}"
+
+ for set_name in ${fetch_sets}; do
+ local src="${releasedir}/binary/sets/${set_name}.tgz"
+ utils_info "Linking local ${src} into ${cachedir}"
+ [ -f "${src}" ] || utils_error "Cannot open ${src}"
+ ln -s -f "${src}" "${cachedir}/${set_name}.tgz" \
+ || utils_error "Failed to link ${src} into ${cachedir}"
+ done
+ ;;
+
+ *)
+ utils_error "Don't know how to fetch from ${releasedir}; must" \
+ "be an absolute path or an FTP/HTTP site"
+ ;;
+ esac
+}
+
+
+# Installs a new kernel from a set.
+#
+# \param kernel_name Name of the kernel set to use; optional.
+sysupgrade_kernel() {
+ [ ${#} -lt 2 ] \
+ || utils_usage_error "kernel takes zero or one arguments"
+
+ local kernel_name
+ if [ -n "${1}" ]; then
+ kernel_name="${1}"
+ elif config_has KERNEL; then
+ kernel_name="$(config_get KERNEL)"
+ else
+ utils_info "Skipping kernel installation (KERNEL not set)"
+ return 0
+ fi
+
+ require_set "kern-${kernel_name}"
+
+ local destdir="$(config_get_default DESTDIR "")"
+ utils_info "Upgrading kernel using ${kernel_name} in ${destdir}/"
+
+ if [ -f "${destdir}/netbsd" ]; then
+ utils_info "Backing up 'netbsd' kernel as 'onetbsd'"
+ cp "${destdir}/netbsd" "${destdir}/onetbsd"
+ fi
+ extract_set "kern-${kernel_name}"
+}
+
+
+# Installs new kernel modules.
+sysupgrade_modules() {
+ [ ${#} -eq 0 ] \
+ || utils_usage_error "modules does not take any arguments"
+
+ if ! utils_contains modules $(config_get SETS); then
+ utils_info "Skipping modules installation (modules not in SETS)"
+ return 0
+ fi
+
+ utils_info "Upgrading kernel modules"
+
+ extract_set modules
+}
+
+
+# Installs new sets.
+#
+# \param ... Names of the sets to extract, to override SETS.
+sysupgrade_sets() {
+ utils_info "Upgrading base system"
+
+ local sets=
+ for set_name in "${@:-$(config_get SETS)}"; do
+ case "${set_name}" in
+ *etc) ;; # Handled by etcupdate.
+ kern-*) ;; # Handled by kernel.
+ modules) ;; # Handled by modules.
+ *) sets="${sets} ${set_name}" ;;
+ esac
+ done
+
+ for set_name in ${sets}; do
+ require_set "${set_name}"
+ done
+
+ for set_name in ${sets}; do
+ extract_set "${set_name}"
+ done
+}
+
+
+# Runs etcupdate to install new configuration files.
+sysupgrade_etcupdate() {
+ [ ${#} -eq 0 ] || utils_usage_error "etcupdate does not take any arguments"
+
+ if config_has DESTDIR; then
+ utils_info "Skipping etcupdate (DESTDIR upgrades not supported)"
+ return 0
+ fi
+
+ local sets="$(utils_filter '*etc' $(config_get SETS))"
+ if [ -z "${sets}" ]; then
+ utils_info "Skipping etcupdate (no etc sets in SETS)"
+ return 0
+ fi
+ if ! utils_contains etc ${sets}; then
+ utils_info "Skipping etcupdate (required etc not in SETS)"
+ return 0
+ fi
+
+ local sflags=
+ for set_name in ${sets}; do
+ require_set "${set_name}"
+ sflags="${sflags} -s$(get_set "${set_name}")"
+ done
+
+ utils_info "Upgrading /etc interactively"
+ etcupdate -a -l ${sflags}
+}
+
+
+# Runs postinstall to validate the updated system.
+#
+# \param ... Arguments to pass to postinstall(8).
+sysupgrade_postinstall() {
+ local sets=
+ local sets="$(utils_filter '*etc' $(config_get SETS))"
+ if [ -z "${sets}" ]; then
+ utils_info "Skipping postinstall (no etc sets in SETS)"
+ return 0
+ fi
+ if ! utils_contains etc ${sets}; then
+ utils_info "Skipping postinstall (required etc not in SETS)"
+ return 0
+ fi
+
+ local sflags=
+ for set_name in ${sets}; do
+ require_set "${set_name}"
+ sflags="${sflags} -s$(get_set "${set_name}")"
+ done
+
+ utils_info "Performing postinstall checks"
+ local destdir="$(config_get_default DESTDIR "")"
+ if config_has POSTINSTALL_AUTOFIX; then
+ postinstall "-d${destdir}/" ${sflags} fix \
+ $(config_get POSTINSTALL_AUTOFIX)
+ fi
+ postinstall "-d${destdir}/" ${sflags} "${@:-check}" \
+ || utils_error "Some postinstall(8) checks have failed"
+}
+
+
+# Cleans up the cache directory.
+sysupgrade_clean() {
+ [ ${#} -eq 0 ] || utils_usage_error "clean does not take any arguments"
+
+ utils_info "Cleaning downloaded files"
+ rm -f "$(config_get CACHEDIR)"/*.tgz*
+}
+
+
+# Automated upgrade procedure.
+#
+# This is just a convenience mechanism to execute all the different steps of the
+# upgrade.
+#
+# \param releasedir Optional override of the release directory to use.
+sysupgrade_auto() {
+ [ ${#} -lt 2 ] \
+ || utils_usage_error "auto takes zero or one arguments"
+
+ [ -z "${1}" ] || config_set "RELEASEDIR" "${1}"
+
+ local stages=
+ stages="fetch modules kernel sets"
+ config_get_bool "ETCUPDATE" && stages="${stages} etcupdate"
+ stages="${stages} postinstall"
+ config_get_bool "AUTOCLEAN" && stages="${stages} clean"
+
+ utils_info "Starting auto-update with stages: ${stages}"
+ for stage in ${stages}; do
+ sysupgrade_${stage}
+ done
+
+ config_get_bool "AUTOCLEAN" || utils_info "Distribution sets not deleted;" \
+ "further ${Utils_ProgName} commands will reuse them"
+}
+
+
+# Entry point to the program.
+#
+# \param ... Command-line arguments to be processed.
+#
+# \return An exit code to be returned to the user.
+sysupgrade_main() {
+ local config_file="${SYSUPGRADE_ETCDIR}/sysupgrade.conf"
+
+ config_init ${SYSUPGRADE_CONFIG_VARS}
+
+ while getopts ':c:d:o:' arg "${@}"; do
+ case "${arg}" in
+ c) # Path to the configuration file.
+ config_file="${OPTARG}"
+ ;;
+
+ d) # Path to the destdir.
+ config_set DESTDIR "${OPTARG}"
+ ;;
+
+ o) # Override for a particular configuration variable.
+ config_override "${OPTARG}"
+ ;;
+
+ \?)
+ utils_usage_error "Unknown option -${OPTARG}"
+ ;;
+ esac
+ done
+ shift $((${OPTIND} - 1))
+
+ [ ${#} -ge 1 ] || utils_usage_error "No command specified"
+
+ local exit_code=0
+
+ local command="${1}"; shift
+ case "${command}" in
+ auto|clean|etcupdate|fetch|kernel|modules|sets|postinstall)
+ sysupgrade_set_defaults
+ config_load "${config_file}"
+ sysupgrade_auto_config
+ "sysupgrade_$(echo "${command}" | tr - _)" "${@}" \
+ || exit_code="${?}"
+ ;;
+
+ config)
+ sysupgrade_set_defaults
+ config_load "${config_file}"
+ "sysupgrade_$(echo "${command}" | tr - _)" "${@}" \
+ || exit_code="${?}"
+ ;;
+
+ *)
+ utils_usage_error "Unknown command ${command}"
+ ;;
+ esac
+
+ return "${exit_code}"
+}
diff --git a/sysutils/sysupgrade/files/sysupgrade_test.sh b/sysutils/sysupgrade/files/sysupgrade_test.sh
new file mode 100644
index 00000000000..6c9b2b1dc88
--- /dev/null
+++ b/sysutils/sysupgrade/files/sysupgrade_test.sh
@@ -0,0 +1,1055 @@
+# Copyright 2012 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * 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.
+# * Neither the name of Google Inc. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT
+# OWNER 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.
+
+
+# Creates a fake program that records its invocations for later processing.
+#
+# The fake program, when invoked, will append its arguments to a commands.log
+# file in the test case's work directory.
+#
+# \param binary The path to the program to create.
+# \param delegate If set to 'yes', execute the real program afterwards.
+create_mock_binary() {
+ local binary="${1}"; shift
+ local delegate=no
+ [ ${#} -eq 0 ] || { delegate="${1}"; shift; }
+
+ cat >"${binary}" <<EOF
+#! /bin/sh
+
+logfile="${HOME}/commands.log"
+echo "Command: \${0##*/}" >>"\${logfile}"
+echo "Directory: \$(pwd)" >>"\${logfile}"
+for arg in "\${@}"; do
+ echo "Arg: \${arg}" >>"\${logfile}"
+done
+ echo >>"\${logfile}"
+EOF
+
+ if [ "${delegate}" = yes ]; then
+ cat >>"${binary}" <<EOF
+PATH="${PATH}"
+exec "\${0##*/}" "\${@}"
+EOF
+ fi
+
+ chmod +x "${binary}"
+}
+
+
+# Generates a NetBSD release dir with fake tarballs.
+#
+# Each of the generated sets contains a single file named '<set>.cookie'.
+# Additionally, if the set starts with kern-, a 'netbsd' file will also be
+# inside it.
+#
+# \param releasedir Path to the release directory.
+# \param ... Names of the sets to create under releasedir/binary/sets/.
+create_mock_release() {
+ local releasedir="${1}"; shift
+
+ mkdir -p "${releasedir}/binary/sets"
+
+ for set_name in "${@}"; do
+ local files=
+
+ echo "File from ${set_name}" >"${set_name}.cookie"
+ files="${files} ${set_name}.cookie"
+
+ case "${set_name}" in
+ kern-*)
+ echo "File from ${set_name}" >netbsd
+ files="${files} netbsd"
+ ;;
+ esac
+
+ tar czf "${releasedir}/binary/sets/${set_name}.tgz" ${files}
+ rm ${files}
+ done
+}
+
+
+atf_test_case config__builtins
+config__builtins_body() {
+ cat >expout <<EOF
+AUTOCLEAN = yes
+CACHEDIR = @SYSUPGRADE_CACHEDIR@
+DESTDIR is undefined
+ETCUPDATE = yes
+KERNEL = AUTO
+POSTINSTALL_AUTOFIX is undefined
+RELEASEDIR is undefined
+SETS = AUTO
+EOF
+ atf_check -o file:expout sysupgrade -c /dev/null config
+}
+
+
+atf_test_case config__default_file
+config__default_file_body() {
+ mkdir system
+ export SYSUPGRADE_ETCDIR="$(pwd)/system"
+
+ echo "KERNEL=abc" >"system/sysupgrade.conf"
+ atf_check -o match:"KERNEL = abc" sysupgrade config
+}
+
+
+atf_test_case config__explicit_file
+config__explicit_file_body() {
+ mkdir system
+ export SYSUPGRADE_ETCDIR="$(pwd)/system"
+
+ echo "KERNEL=abc" >"system/sysupgrade.conf"
+ echo "SETS='d e'" >"my-file.conf"
+ atf_check -o not-match:"KERNEL = abc" -o match:"SETS = d e" \
+ sysupgrade -c ./my-file.conf config
+}
+
+
+atf_test_case config__auto__kernel__found
+config__auto__kernel__found_body() {
+ cat >config <<EOF
+#! /bin/sh
+if [ "\${1}" != "-x" ]; then
+ echo "-x not passed to config"
+ exit 1
+fi
+if [ "\${2}" != "$(pwd)/root/netbsd" ]; then
+ echo "Invalid path passed to config: \${2}"
+ exit 1
+fi
+
+cat "\${2}"
+EOF
+ chmod +x config
+ PATH="$(pwd):${PATH}"
+
+ mkdir root
+ cat >root/netbsd <<EOF
+### START CONFIG FILE "ABCDE"
+these are some contents
+### END CONFIG FILE "ABCDE"
+EOF
+ atf_check -o match:"KERNEL = ABCDE" \
+ sysupgrade -c /dev/null \
+ -o DESTDIR="$(pwd)/root" -o KERNEL="AUTO" config -a
+}
+
+
+atf_test_case config__auto__kernel__not_found
+config__auto__kernel__not_found_body() {
+
+ atf_check -o match:"KERNEL is undefined" \
+ sysupgrade -c /dev/null \
+ -o DESTDIR="$(pwd)/root" -o KERNEL="AUTO" config -a
+}
+
+
+atf_test_case config__auto__kernel__fail
+config__auto__kernel__fail_body() {
+ cat >config <<EOF
+#! /bin/sh
+exit 1
+EOF
+ chmod +x config
+ PATH="$(pwd):${PATH}"
+
+ mkdir root
+ touch root/netbsd
+
+ cat >experr <<EOF
+sysupgrade: E: Failed to determine kernel name; please set KERNEL explicitly
+EOF
+ atf_check -s exit:1 -e file:experr sysupgrade -c /dev/null \
+ -o DESTDIR="$(pwd)/root" -o KERNEL="AUTO" config -a
+}
+
+
+atf_test_case config__auto__sets__found
+config__auto__sets__found_body() {
+ mkdir -p root/etc/mtree
+ touch root/etc/mtree/set.first
+ touch root/etc/mtree/set.second
+ touch root/etc/mtree/set.third
+ atf_check -o match:"SETS = first second third" \
+ sysupgrade -c /dev/null \
+ -o DESTDIR="$(pwd)/root" -o SETS="AUTO" config -a
+}
+
+
+atf_test_case config__auto__sets__not_found
+config__auto__sets__not_found_body() {
+
+ atf_check -o match:"SETS is undefined" \
+ sysupgrade -c /dev/null \
+ -o DESTDIR="$(pwd)/root" -o SETS="AUTO" config -a
+}
+
+
+atf_test_case config__not_found
+config__not_found_body() {
+ mkdir .sysupgrade
+ mkdir system
+ export SYSUPGRADE_ETCDIR="$(pwd)/system"
+
+ cat >experr <<EOF
+sysupgrade: E: Configuration file foobar does not exist
+EOF
+ atf_check -s exit:1 -o empty -e file:experr sysupgrade -c foobar config
+}
+
+
+atf_test_case config__overrides
+config__overrides_body() {
+ cat >custom.conf <<EOF
+CACHEDIR=/tmp/cache
+KERNEL=foo
+EOF
+
+ cat >expout <<EOF
+AUTOCLEAN = yes
+CACHEDIR = /tmp/cache2
+DESTDIR = /tmp/destdir
+ETCUPDATE = yes
+KERNEL is undefined
+POSTINSTALL_AUTOFIX is undefined
+RELEASEDIR is undefined
+SETS = AUTO
+EOF
+ atf_check -o file:expout sysupgrade -c custom.conf -o KERNEL= \
+ -o CACHEDIR=/tmp/cache2 -o DESTDIR=/tmp/destdir config
+}
+
+
+atf_test_case config__too_many_args
+config__too_many_args_body() {
+ cat >experr <<EOF
+sysupgrade: E: config does not take any arguments
+Type 'man sysupgrade' for help
+EOF
+ atf_check -s exit:1 -e file:experr sysupgrade -c /dev/null config foo
+}
+
+
+atf_test_case fetch__ftp
+fetch__ftp_body() {
+ # TODO(jmmv): It would be nice if this test used an actual FTP server, just
+ # like the http test below. Unfortunately, the ftpd shipped by NetBSD does
+ # not provide an easy mechanism to run it as non-root (e.g. no easy way to
+ # select on which port to serve).
+ create_mock_binary ftp
+cat >>ftp <<EOF
+for arg in "\${@}"; do
+ case "\${arg}" in
+ -o*) touch \$(echo "\${arg}" | sed -e s,^-o,,) ;;
+ esac
+done
+EOF
+ PATH="$(pwd):${PATH}"
+
+ SYSUPGRADE_CACHEDIR="$(pwd)/a/b/c"; export SYSUPGRADE_CACHEDIR
+ atf_check -o ignore -e ignore sysupgrade -c /dev/null \
+ -o RELEASEDIR="ftp://example.net/pub/NetBSD/X.Y/a-machine" \
+ -o SETS="a foo" fetch
+
+ cat >expout <<EOF
+Command: ftp
+Directory: $(pwd)
+Arg: -R
+Arg: -o$(pwd)/a/b/c/a.tgz.tmp
+Arg: ftp://example.net/pub/NetBSD/X.Y/a-machine/binary/sets/a.tgz
+
+Command: ftp
+Directory: $(pwd)
+Arg: -R
+Arg: -o$(pwd)/a/b/c/foo.tgz.tmp
+Arg: ftp://example.net/pub/NetBSD/X.Y/a-machine/binary/sets/foo.tgz
+
+Command: ftp
+Directory: $(pwd)
+Arg: -R
+Arg: -o$(pwd)/a/b/c/kern-GENERIC.tgz.tmp
+Arg: ftp://example.net/pub/NetBSD/X.Y/a-machine/binary/sets/kern-GENERIC.tgz
+
+EOF
+ atf_check -o file:expout cat commands.log
+}
+
+
+atf_test_case fetch__http cleanup
+fetch__http_head() {
+ atf_set "require.progs" "/usr/libexec/httpd"
+}
+fetch__http_body() {
+ create_mock_release www/a-machine base comp etc kern-GENERIC tests text
+
+ /usr/libexec/httpd -b -s -d -I 30401 -P "$(pwd)/httpd.pid" "$(pwd)/www" \
+ || atf_fail "Failed to start test HTTP server"
+
+ SYSUPGRADE_CACHEDIR="$(pwd)/cache"; export SYSUPGRADE_CACHEDIR
+ atf_check -o ignore -e ignore sysupgrade -c /dev/null \
+ -o RELEASEDIR="http://localhost:30401/a-machine" \
+ -o SETS="base etc text" fetch
+
+ for set_name in base etc kern-GENERIC text; do
+ [ -e "cache/${set_name}.tgz" ] || atf_fail "${set_name} not fetched"
+ done
+ for set_name in comp tests; do
+ [ ! -e "cache/${set_name}.tgz" ] || atf_fail "${set_name} fetched"
+ done
+
+ kill -9 "$(cat httpd.pid)"
+ rm -f httpd.pid
+}
+fetch__http_cleanup() {
+ if [ -f httpd.pid ]; then
+ echo "Killing stale HTTP server"
+ kill -9 "$(cat httpd.pid)"
+ fi
+}
+
+
+atf_test_case fetch__local
+fetch__local_body() {
+ create_mock_release release base comp etc kern-GENERIC tests text
+
+ SYSUPGRADE_CACHEDIR="$(pwd)/cache"; export SYSUPGRADE_CACHEDIR
+ atf_check -o ignore -e ignore sysupgrade -c /dev/null \
+ -o RELEASEDIR="$(pwd)/release" -o SETS="base etc text" fetch
+
+ for set_name in base etc kern-GENERIC text; do
+ [ -e "cache/${set_name}.tgz" ] || atf_fail "${set_name} not fetched"
+ done
+ for set_name in comp tests; do
+ [ ! -e "cache/${set_name}.tgz" ] || atf_fail "${set_name} fetched"
+ done
+}
+
+
+atf_test_case fetch__no_kernel
+fetch__no_kernel_body() {
+ create_mock_release release base comp kern-GENERIC
+
+ SYSUPGRADE_CACHEDIR="$(pwd)/cache"; export SYSUPGRADE_CACHEDIR
+ atf_check -o ignore -e ignore sysupgrade -c /dev/null \
+ -o KERNEL= -o RELEASEDIR="$(pwd)/release" -o SETS="base comp" fetch
+
+ for set_name in base comp; do
+ [ -e "cache/${set_name}.tgz" ] || atf_fail "${set_name} not fetched"
+ done
+ [ ! -e "cache/kern-GENERIC.tgz" ] || atf_fail "kern-GENERIC fetched"
+}
+
+
+atf_test_case fetch__unknown
+fetch__unknown_body() {
+ cat >experr <<EOF
+sysupgrade: E: Don't know how to fetch from ./release; must be an absolute path or an FTP/HTTP site
+EOF
+
+ SYSUPGRADE_CACHEDIR="$(pwd)/cache"; export SYSUPGRADE_CACHEDIR
+ atf_check -s exit:1 -e file:experr sysupgrade -c /dev/null \
+ -o RELEASEDIR="./release" -o SETS="base etc text" fetch
+ [ ! -d cache ] || atf_fail "cache directory created even during errors"
+}
+
+
+atf_test_case fetch__explicit
+fetch__explicit_body() {
+ create_mock_release release base
+
+ SYSUPGRADE_CACHEDIR="$(pwd)/cache"; export SYSUPGRADE_CACHEDIR
+ atf_check -o ignore -e ignore sysupgrade -c /dev/null \
+ -o RELEASEDIR="$(pwd)/foo" -o KERNEL= -o SETS=base \
+ fetch "$(pwd)/release"
+
+ [ -e "cache/base.tgz" ] || atf_fail "base not fetched"
+}
+
+
+atf_test_case fetch__too_many_args
+fetch__too_many_args_body() {
+ cat >experr <<EOF
+sysupgrade: E: fetch takes zero or one arguments
+Type 'man sysupgrade' for help
+EOF
+ atf_check -s exit:1 -e file:experr sysupgrade -c /dev/null fetch foo bar
+}
+
+
+atf_test_case kernel__skip
+kernel__skip_body() {
+ cat >experr <<EOF
+sysupgrade: I: Skipping kernel installation (KERNEL not set)
+EOF
+ atf_check -s exit:0 -e file:experr sysupgrade -c /dev/null -o KERNEL= kernel
+}
+
+
+atf_test_case kernel__from_config
+kernel__from_config_body() {
+ mkdir root
+ echo "my old kernel" >root/netbsd
+
+ create_mock_release release kern-FOOBAR
+ SYSUPGRADE_CACHEDIR="$(pwd)/release/binary/sets"; export SYSUPGRADE_CACHEDIR
+
+ atf_check -s exit:0 \
+ -e match:"Upgrading kernel using FOOBAR in $(pwd)/root/" \
+ -e match:"Backing up" \
+ sysupgrade -c /dev/null \
+ -o DESTDIR="$(pwd)/root" -o KERNEL=FOOBAR kernel
+
+ atf_check -o match:"File from kern-FOOBAR" cat root/netbsd
+ atf_check -o match:"my old kernel" cat root/onetbsd
+}
+
+
+atf_test_case kernel__from_arg
+kernel__from_arg_body() {
+ mkdir root
+ echo "my old kernel" >root/netbsd
+
+ create_mock_release release kern-FOOBAR kern-OTHER
+ SYSUPGRADE_CACHEDIR="$(pwd)/release/binary/sets"; export SYSUPGRADE_CACHEDIR
+
+ atf_check -s exit:0 \
+ -e match:"Upgrading kernel using OTHER in $(pwd)/root/" \
+ -e match:"Backing up" \
+ sysupgrade -c /dev/null \
+ -o DESTDIR="$(pwd)/root" -o KERNEL=FOOBAR kernel OTHER
+
+ atf_check -o match:"File from kern-OTHER" cat root/netbsd
+ atf_check -o match:"my old kernel" cat root/onetbsd
+}
+
+
+atf_test_case kernel__no_backup
+kernel__no_backup_body() {
+ mkdir root
+
+ create_mock_release release kern-FOOBAR
+ SYSUPGRADE_CACHEDIR="$(pwd)/release/binary/sets"; export SYSUPGRADE_CACHEDIR
+
+ atf_check -s exit:0 -e not-match:"Backing up" sysupgrade -c /dev/null \
+ -o DESTDIR="$(pwd)/root" -o KERNEL=FOOBAR kernel
+
+ atf_check -o match:"File from kern-FOOBAR" cat root/netbsd
+ [ ! -f root/onetbsd ] || "onetbsd backup created, but not expected"
+}
+
+
+atf_test_case kernel__missing_set
+kernel__missing_set_body() {
+ mkdir root
+ echo "my old kernel" >root/netbsd
+
+ SYSUPGRADE_CACHEDIR="$(pwd)"; export SYSUPGRADE_CACHEDIR
+
+ cat >experr <<EOF
+sysupgrade: E: Cannot find kern-GENERIC; did you run 'sysupgrade fetch' first?
+EOF
+ atf_check -s exit:1 -e file:experr sysupgrade -c /dev/null \
+ -o DESTDIR="$(pwd)/root" -o KERNEL=GENERIC kernel
+
+ atf_check -o match:"my old kernel" cat root/netbsd
+}
+
+
+atf_test_case kernel__too_many_args
+kernel__too_many_args_body() {
+ cat >experr <<EOF
+sysupgrade: E: kernel takes zero or one arguments
+Type 'man sysupgrade' for help
+EOF
+ atf_check -s exit:1 -e file:experr sysupgrade -c /dev/null kernel A B
+}
+
+
+atf_test_case modules__skip
+modules__skip_body() {
+ cat >experr <<EOF
+sysupgrade: I: Skipping modules installation (modules not in SETS)
+EOF
+ atf_check -s exit:0 -e file:experr sysupgrade -c /dev/null \
+ -o SETS="base comp modules2" modules
+}
+
+
+atf_test_case modules__install
+modules__install_body() {
+ create_mock_release release base modules
+ SYSUPGRADE_CACHEDIR="$(pwd)/release/binary/sets"; export SYSUPGRADE_CACHEDIR
+
+ atf_check -s exit:0 -e match:"Upgrading kernel modules" \
+ sysupgrade -c /dev/null -o DESTDIR="$(pwd)/root" \
+ -o SETS="base modules tests" modules
+
+ [ -f root/modules.cookie ] || atf_fail "modules not extracted"
+ [ ! -f root/base.cookie ] || atf_fail "base should not have been extracted"
+}
+
+
+atf_test_case modules__too_many_args
+modules__too_many_args_body() {
+ cat >experr <<EOF
+sysupgrade: E: modules does not take any arguments
+Type 'man sysupgrade' for help
+EOF
+ atf_check -s exit:1 -e file:experr sysupgrade -c /dev/null modules A
+}
+
+
+atf_test_case sets__from_config
+sets__from_config_body() {
+ create_mock_release release base etc comp
+ SYSUPGRADE_CACHEDIR="$(pwd)/release/binary/sets"; export SYSUPGRADE_CACHEDIR
+
+ expected_sets="base comp"
+ unexpected_sets="etc kern-GENERIC modules xetc"
+
+ atf_check -s exit:0 -e ignore sysupgrade -c /dev/null \
+ -o DESTDIR="$(pwd)/root" \
+ -o SETS="${expected_sets} ${unexpected_sets}" \
+ sets
+
+ for set_name in ${expected_sets}; do
+ [ -f "root/${set_name}.cookie" ] \
+ || atf_fail "Expected set ${set_name} not extracted"
+ done
+
+ for set_name in ${unexpected_sets}; do
+ [ ! -f "root/${set_name}.cookie" ] \
+ || atf_fail "Unexpected set ${set_name} extracted"
+ done
+}
+
+
+atf_test_case sets__from_args
+sets__from_args_body() {
+ create_mock_release release base etc comp
+ SYSUPGRADE_CACHEDIR="$(pwd)/release/binary/sets"; export SYSUPGRADE_CACHEDIR
+
+ expected_sets="base comp"
+ unexpected_sets="etc kern-GENERIC modules xetc"
+
+ atf_check -s exit:0 -e ignore sysupgrade -c /dev/null \
+ -o DESTDIR="$(pwd)/root" -o SETS="foo bar baz" \
+ sets ${expected_sets} ${unexpected_sets}
+
+ for set_name in ${expected_sets}; do
+ [ -f "root/${set_name}.cookie" ] \
+ || atf_fail "Expected set ${set_name} not extracted"
+ done
+
+ for set_name in ${unexpected_sets}; do
+ [ ! -f "root/${set_name}.cookie" ] \
+ || atf_fail "Unexpected set ${set_name} extracted"
+ done
+}
+
+
+atf_test_case etcupdate__skip__none
+etcupdate__skip__none_body() {
+ cat >experr <<EOF
+sysupgrade: I: Skipping etcupdate (no etc sets in SETS)
+EOF
+ atf_check -s exit:0 -e file:experr sysupgrade -c /dev/null \
+ -o SETS="base comp etcfoo" etcupdate
+}
+
+
+atf_test_case etcupdate__skip__no_etc
+etcupdate__skip__no_etc_body() {
+ cat >experr <<EOF
+sysupgrade: I: Skipping etcupdate (required etc not in SETS)
+EOF
+ atf_check -s exit:0 -e file:experr sysupgrade -c /dev/null \
+ -o SETS="base xetc xfonts" etcupdate
+}
+
+
+atf_test_case etcupdate__skip__destdir
+etcupdate__skip__destdir_body() {
+ cat >experr <<EOF
+sysupgrade: I: Skipping etcupdate (DESTDIR upgrades not supported)
+EOF
+ atf_check -s exit:0 -e file:experr sysupgrade -c /dev/null \
+ -o DESTDIR="$(pwd)" -o SETS="etc" etcupdate
+}
+
+
+atf_test_case etcupdate__some_etcs
+etcupdate__some_etcs_body() {
+ create_mock_binary etcupdate
+ PATH="$(pwd):${PATH}"
+
+ create_mock_release release base etc comp xbase xetc
+ SYSUPGRADE_CACHEDIR="$(pwd)/release/binary/sets"; export SYSUPGRADE_CACHEDIR
+
+ atf_check -s exit:0 -o ignore -e ignore sysupgrade -c /dev/null \
+ -o RELEASEDIR="$(pwd)/release" \
+ -o SETS="base etc comp xbase xetc" etcupdate
+
+ cat >expout <<EOF
+Command: etcupdate
+Directory: $(pwd)
+Arg: -a
+Arg: -l
+Arg: -s$(pwd)/release/binary/sets/etc.tgz
+Arg: -s$(pwd)/release/binary/sets/xetc.tgz
+
+EOF
+ atf_check -o file:expout cat commands.log
+}
+
+
+atf_test_case etcupdate__missing_etc
+etcupdate__missing_etc_body() {
+ cat >experr <<EOF
+sysupgrade: E: Cannot find etc; did you run 'sysupgrade fetch' first?
+EOF
+ atf_check -s exit:1 -e file:experr sysupgrade -c /dev/null \
+ -o RELEASEDIR="$(pwd)/missing" -o SETS="etc" etcupdate
+}
+
+
+atf_test_case etcupdate__too_many_args
+etcupdate__too_many_args_body() {
+ cat >experr <<EOF
+sysupgrade: E: etcupdate does not take any arguments
+Type 'man sysupgrade' for help
+EOF
+ atf_check -s exit:1 -e file:experr sysupgrade -c /dev/null etcupdate foo
+}
+
+
+atf_test_case postinstall__skip__none
+postinstall__skip__none_body() {
+ cat >experr <<EOF
+sysupgrade: I: Skipping postinstall (no etc sets in SETS)
+EOF
+ atf_check -s exit:0 -e file:experr sysupgrade -c /dev/null \
+ -o SETS="base comp etcfoo" postinstall
+}
+
+
+atf_test_case postinstall__skip__no_etc
+postinstall__skip__no_etc_body() {
+ cat >experr <<EOF
+sysupgrade: I: Skipping postinstall (required etc not in SETS)
+EOF
+ atf_check -s exit:0 -e file:experr sysupgrade -c /dev/null \
+ -o SETS="base xetc xfonts" postinstall
+}
+
+
+atf_test_case postinstall__some_etcs
+postinstall__some_etcs_body() {
+ create_mock_binary postinstall
+ PATH="$(pwd):${PATH}"
+
+ create_mock_release release base etc comp xbase xetc
+ SYSUPGRADE_CACHEDIR="$(pwd)/release/binary/sets"; export SYSUPGRADE_CACHEDIR
+
+ atf_check -s exit:0 -o ignore -e ignore sysupgrade -c /dev/null \
+ -o RELEASEDIR="$(pwd)/release" \
+ -o SETS="base etc comp xbase xetc" postinstall
+
+ cat >expout <<EOF
+Command: postinstall
+Directory: $(pwd)
+Arg: -d/
+Arg: -s$(pwd)/release/binary/sets/etc.tgz
+Arg: -s$(pwd)/release/binary/sets/xetc.tgz
+Arg: check
+
+EOF
+ atf_check -o file:expout cat commands.log
+}
+
+
+atf_test_case postinstall__destdir
+postinstall__destdir_body() {
+ create_mock_binary postinstall
+ PATH="$(pwd):${PATH}"
+
+ create_mock_release release base etc
+ SYSUPGRADE_CACHEDIR="$(pwd)/release/binary/sets"; export SYSUPGRADE_CACHEDIR
+
+ atf_check -s exit:0 -o ignore -e ignore sysupgrade -c /dev/null \
+ -o DESTDIR="$(pwd)/root" \
+ -o RELEASEDIR="$(pwd)/release" \
+ -o SETS="base etc" postinstall
+
+ cat >expout <<EOF
+Command: postinstall
+Directory: $(pwd)
+Arg: -d$(pwd)/root/
+Arg: -s$(pwd)/release/binary/sets/etc.tgz
+Arg: check
+
+EOF
+ atf_check -o file:expout cat commands.log
+}
+
+
+atf_test_case postinstall__autofix
+postinstall__autofix_body() {
+ create_mock_binary postinstall
+ PATH="$(pwd):${PATH}"
+
+ create_mock_release release etc
+ SYSUPGRADE_CACHEDIR="$(pwd)/release/binary/sets"; export SYSUPGRADE_CACHEDIR
+
+ atf_check -s exit:0 -o ignore -e ignore sysupgrade -c /dev/null \
+ -o POSTINSTALL_AUTOFIX="first second" \
+ -o RELEASEDIR="$(pwd)/release" \
+ -o SETS="etc" postinstall
+
+ cat >expout <<EOF
+Command: postinstall
+Directory: $(pwd)
+Arg: -d/
+Arg: -s$(pwd)/release/binary/sets/etc.tgz
+Arg: fix
+Arg: first
+Arg: second
+
+Command: postinstall
+Directory: $(pwd)
+Arg: -d/
+Arg: -s$(pwd)/release/binary/sets/etc.tgz
+Arg: check
+
+EOF
+ atf_check -o file:expout cat commands.log
+}
+
+
+atf_test_case postinstall__missing_etc
+postinstall__missing_etc_body() {
+ cat >experr <<EOF
+sysupgrade: E: Cannot find etc; did you run 'sysupgrade fetch' first?
+EOF
+ atf_check -s exit:1 -e file:experr sysupgrade -c /dev/null \
+ -o RELEASEDIR="$(pwd)/missing" -o SETS="etc" postinstall
+}
+
+
+atf_test_case postinstall__explicit_args
+postinstall__explicit_args_body() {
+ create_mock_binary postinstall
+ PATH="$(pwd):${PATH}"
+
+ create_mock_release release base etc
+ SYSUPGRADE_CACHEDIR="$(pwd)/release/binary/sets"; export SYSUPGRADE_CACHEDIR
+
+ atf_check -s exit:0 -o ignore -e ignore sysupgrade -c /dev/null \
+ -o RELEASEDIR="$(pwd)/release" -o SETS="base etc" postinstall fix a b c
+
+ cat >expout <<EOF
+Command: postinstall
+Directory: $(pwd)
+Arg: -d/
+Arg: -s$(pwd)/release/binary/sets/etc.tgz
+Arg: fix
+Arg: a
+Arg: b
+Arg: c
+
+EOF
+ atf_check -o file:expout cat commands.log
+}
+
+
+atf_test_case clean__nothing
+clean__nothing_body() {
+ SYSUPGRADE_CACHEDIR="$(pwd)/cache"; export SYSUPGRADE_CACHEDIR
+ mkdir "${SYSUPGRADE_CACHEDIR}"
+ atf_check -e match:"Cleaning downloaded files" sysupgrade -c /dev/null clean
+ [ -d cache ] || atf_fail "Cache directory should not have been deleted"
+}
+
+
+atf_test_case clean__only_tgzs
+clean__only_tgzs_body() {
+ SYSUPGRADE_CACHEDIR="$(pwd)/cache"; export SYSUPGRADE_CACHEDIR
+ mkdir "${SYSUPGRADE_CACHEDIR}"
+ touch cache/foo.tgz
+ touch cache/foo.tgz.tmp
+ touch cache/bar.gz
+ atf_check -e match:"Cleaning downloaded files" sysupgrade -c /dev/null clean
+ [ ! -f cache/foo.tgz ] || atf_fail "tgz not deleted"
+ [ ! -f cache/foo.tgz.tmp ] || atf_fail "Temporary tgz not deleted"
+ [ -f cache/bar.gz ] || atf_fail "Non-tgz files deleted"
+ [ -d cache ] || atf_fail "Cache directory should not have been deleted"
+}
+
+
+atf_test_case clean__too_many_args
+clean__too_many_args_body() {
+ cat >experr <<EOF
+sysupgrade: E: clean does not take any arguments
+Type 'man sysupgrade' for help
+EOF
+ atf_check -s exit:1 -e file:experr sysupgrade -c /dev/null clean foo
+}
+
+
+atf_test_case auto__simple
+auto__simple_body() {
+ create_mock_binary postinstall
+ create_mock_binary etcupdate
+ PATH="$(pwd):${PATH}"
+
+ create_mock_release release base comp etc kern-CUSTOM modules
+
+ mkdir root
+ echo "old kernel" >root/netbsd
+
+ cat >sysupgrade.conf <<EOF
+CACHEDIR="$(pwd)/cache"
+KERNEL="CUSTOM"
+RELEASEDIR="$(pwd)/release"
+SETS="base comp etc modules"
+EOF
+
+ atf_check \
+ -e match:"Linking local" \
+ -e match:"Upgrading kernel using" \
+ -e match:"Upgrading kernel modules" \
+ -e match:"Upgrading base system" \
+ -e match:"Skipping etcupdate.*DESTDIR" \
+ -e match:"Performing postinstall checks" \
+ -e match:"Cleaning downloaded files" \
+ sysupgrade -c sysupgrade.conf -d "$(pwd)/root" auto
+
+ atf_check -o inline:"File from base\n" cat root/base.cookie
+ atf_check -o inline:"File from comp\n" cat root/comp.cookie
+ atf_check -o inline:"File from modules\n" cat root/modules.cookie
+ atf_check -o inline:"File from kern-CUSTOM\n" cat root/netbsd
+ atf_check -o inline:"old kernel\n" cat root/onetbsd
+
+ [ ! -f root/etc.cookie ] || atf_fail "etc extracted by mistake"
+
+ [ ! -f cache/base.tgz ] || atf_fail "Cache should have been cleaned"
+}
+
+
+atf_test_case auto__custom_releasedir
+auto__custom_releasedir_body() {
+ create_mock_binary postinstall
+ create_mock_binary etcupdate
+ PATH="$(pwd):${PATH}"
+
+ create_mock_release release2 base etc
+
+ cat >sysupgrade.conf <<EOF
+CACHEDIR="$(pwd)/cache"
+KERNEL=
+RELEASEDIR="$(pwd)/release"
+SETS="base etc"
+EOF
+
+ atf_check \
+ -e match:"Linking local" \
+ -e not-match:"Upgrading kernel using" \
+ -e not-match:"Upgrading kernel modules" \
+ -e match:"Upgrading base system" \
+ -e match:"Skipping etcupdate.*DESTDIR" \
+ -e match:"Performing postinstall checks" \
+ -e match:"Cleaning downloaded files" \
+ sysupgrade -c sysupgrade.conf -d "$(pwd)/root" \
+ auto "$(pwd)/release2"
+
+ atf_check -o inline:"File from base\n" cat root/base.cookie
+
+ [ ! -f root/etc.cookie ] || atf_fail "etc extracted by mistake"
+ [ ! -f root/onetbsd ] || atf_fail "Spurious kernel backup created"
+}
+
+
+atf_test_case auto__skip_etcupdate
+auto__skip_etcupdate_body() {
+ create_mock_binary postinstall
+ PATH="$(pwd):${PATH}"
+
+ create_mock_release release base etc
+
+ cat >sysupgrade.conf <<EOF
+CACHEDIR="$(pwd)/cache"
+ETCUPDATE=no
+RELEASEDIR="$(pwd)/release"
+SETS="base etc"
+EOF
+
+ atf_check -e not-match:"etcupdate" \
+ sysupgrade -c sysupgrade.conf -d "$(pwd)/root" auto
+}
+
+
+atf_test_case auto__skip_clean
+auto__skip_clean_body() {
+ PATH="$(pwd):${PATH}"
+
+ create_mock_release release2 base
+
+ cat >sysupgrade.conf <<EOF
+AUTOCLEAN=no
+CACHEDIR="$(pwd)/cache"
+KERNEL=
+RELEASEDIR="$(pwd)/release"
+SETS="base"
+EOF
+
+ atf_check \
+ -e match:"Linking local" \
+ -e not-match:"Upgrading kernel using" \
+ -e not-match:"Upgrading kernel modules" \
+ -e match:"Upgrading base system" \
+ -e match:"Skipping etcupdate" \
+ -e match:"Skipping postinstall" \
+ -e not-match:"Cleaning downloaded files" \
+ sysupgrade -c sysupgrade.conf -d "$(pwd)/root" \
+ auto "$(pwd)/release2"
+
+ atf_check -o inline:"File from base\n" cat root/base.cookie
+
+ [ ! -f root/etc.cookie ] || atf_fail "etc extracted by mistake"
+ [ ! -f root/onetbsd ] || atf_fail "Spurious kernel backup created"
+
+ [ -f cache/base.tgz ] || atf_fail "Cache should not have been cleaned"
+}
+
+
+atf_test_case auto__too_many_args
+auto__too_many_args_body() {
+ cat >experr <<EOF
+sysupgrade: E: auto takes zero or one arguments
+Type 'man sysupgrade' for help
+EOF
+ atf_check -s exit:1 -e file:experr sysupgrade -c /dev/null auto a b
+}
+
+
+atf_test_case no_command
+no_command_body() {
+ cat >experr <<EOF
+sysupgrade: E: No command specified
+Type 'man sysupgrade' for help
+EOF
+ atf_check -s exit:1 -e file:experr sysupgrade
+}
+
+
+atf_test_case unknown_command
+unknown_command_body() {
+ cat >experr <<EOF
+sysupgrade: E: Unknown command foo
+Type 'man sysupgrade' for help
+EOF
+ atf_check -s exit:1 -e file:experr sysupgrade foo
+}
+
+
+atf_test_case unknown_flag
+unknown_flag_body() {
+ cat >experr <<EOF
+sysupgrade: E: Unknown option -Z
+Type 'man sysupgrade' for help
+EOF
+ atf_check -s exit:1 -e file:experr sysupgrade -Z
+}
+
+
+atf_init_test_cases() {
+ atf_add_test_case config__builtins
+ atf_add_test_case config__default_file
+ atf_add_test_case config__explicit_file
+ atf_add_test_case config__auto__kernel__found
+ atf_add_test_case config__auto__kernel__not_found
+ atf_add_test_case config__auto__kernel__fail
+ atf_add_test_case config__auto__sets__found
+ atf_add_test_case config__auto__sets__not_found
+ atf_add_test_case config__not_found
+ atf_add_test_case config__overrides
+ atf_add_test_case config__too_many_args
+
+ atf_add_test_case fetch__ftp
+ atf_add_test_case fetch__http
+ atf_add_test_case fetch__local
+ atf_add_test_case fetch__no_kernel
+ atf_add_test_case fetch__unknown
+ atf_add_test_case fetch__explicit
+ atf_add_test_case fetch__too_many_args
+
+ atf_add_test_case kernel__skip
+ atf_add_test_case kernel__from_config
+ atf_add_test_case kernel__from_arg
+ atf_add_test_case kernel__no_backup
+ atf_add_test_case kernel__missing_set
+ atf_add_test_case kernel__too_many_args
+
+ atf_add_test_case modules__skip
+ atf_add_test_case modules__install
+ atf_add_test_case modules__too_many_args
+
+ atf_add_test_case sets__from_config
+ atf_add_test_case sets__from_args
+
+ atf_add_test_case etcupdate__skip__none
+ atf_add_test_case etcupdate__skip__no_etc
+ atf_add_test_case etcupdate__skip__destdir
+ atf_add_test_case etcupdate__some_etcs
+ atf_add_test_case etcupdate__missing_etc
+ atf_add_test_case etcupdate__too_many_args
+
+ atf_add_test_case postinstall__skip__none
+ atf_add_test_case postinstall__skip__no_etc
+ atf_add_test_case postinstall__some_etcs
+ atf_add_test_case postinstall__destdir
+ atf_add_test_case postinstall__autofix
+ atf_add_test_case postinstall__missing_etc
+ atf_add_test_case postinstall__explicit_args
+
+ atf_add_test_case clean__nothing
+ atf_add_test_case clean__only_tgzs
+ atf_add_test_case clean__too_many_args
+
+ atf_add_test_case auto__simple
+ atf_add_test_case auto__custom_releasedir
+ atf_add_test_case auto__skip_etcupdate
+ atf_add_test_case auto__skip_clean
+ atf_add_test_case auto__too_many_args
+
+ atf_add_test_case no_command
+ atf_add_test_case unknown_command
+ atf_add_test_case unknown_flag
+}
diff --git a/sysutils/sysupgrade/files/utils.subr b/sysutils/sysupgrade/files/utils.subr
new file mode 100644
index 00000000000..2d63bb78d1f
--- /dev/null
+++ b/sysutils/sysupgrade/files/utils.subr
@@ -0,0 +1,125 @@
+# Copyright 2012 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * 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.
+# * Neither the name of Google Inc. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT
+# OWNER 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.
+
+# \file utils.subr
+# Miscellaneous utility functions and global variables.
+
+
+# Base name of the running script.
+Utils_ProgName="${0##*/}"
+
+
+# Prints a runtime error and exits.
+#
+# \param ... The message to print. Can be provided as multiple words and, in
+# that case, they are joined together by a single whitespace.
+utils_error() {
+ echo "${Utils_ProgName}: E: $*" 1>&2
+ exit 1
+}
+
+
+# Prints an informational message.
+#
+# \param ... The message to print. Can be provided as multiple words and, in
+# that case, they are joined together by a single whitespace.
+utils_info() {
+ echo "${Utils_ProgName}: I: $*" 1>&2
+}
+
+
+# Prints a runtime warning.
+#
+# \param ... The message to print. Can be provided as multiple words and, in
+# that case, they are joined together by a single whitespace.
+utils_warning() {
+ echo "${Utils_ProgName}: W: $*" 1>&2
+}
+
+
+# Prints an usage error and exits.
+#
+# \param ... The message to print. Can be provided as multiple words and, in
+# that case, they are joined together by a single whitespace.
+utils_usage_error() {
+ echo "${Utils_ProgName}: E: $*" 1>&2
+ echo "Type 'man ${Utils_ProgName}' for help" 1>&2
+ exit 1
+}
+
+
+# Executes a command with logging.
+#
+# \param ... The command to execute.
+#
+# \return The exit code of the executed command.
+utils_run() {
+ utils_info "Running '${@}' in $(pwd)"
+ local ret=0
+ "${@}" || ret="${?}"
+ if [ ${ret} -eq 0 ]; then
+ utils_info "Command finished successfully"
+ else
+ utils_warning "Command failed with code ${ret}"
+ fi
+ return "${ret}"
+}
+
+
+# Looks for an item in a list of items
+#
+# \param item The item to look for.
+# \param ... Contents of the list in which to search for the item.
+#
+# \return 0 if the item is found, 1 otherwise.
+utils_contains() {
+ local item="${1}"; shift
+
+ while [ ${#} -gt 0 ]; do
+ [ "${1}" != "${item}" ] || return 0
+ shift
+ done
+ return 1
+}
+
+
+# Filters a list based on a pattern.
+#
+# \param pattern Pattern to use for filtering.
+# \param ... Contents of the list to filter.
+#
+# \post The matched elements are printed on stdout.
+utils_filter() {
+ local pattern="${1}"; shift
+
+ for arg in "${@}"; do
+ case "${arg}" in
+ ${pattern}) echo "${arg}" ;;
+ esac
+ done
+}
diff --git a/sysutils/sysupgrade/files/utils_test.sh b/sysutils/sysupgrade/files/utils_test.sh
new file mode 100644
index 00000000000..48c7570236d
--- /dev/null
+++ b/sysutils/sysupgrade/files/utils_test.sh
@@ -0,0 +1,186 @@
+# Copyright 2012 Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * 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.
+# * Neither the name of Google Inc. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT
+# OWNER 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.
+
+
+atf_test_case global_progname
+global_progname_body() {
+ [ "${Utils_ProgName}" = utils_test ] \
+ || atf_fail "Invalid value in Utils_ProgName"
+}
+
+
+atf_test_case error
+error_body() {
+ if ( utils_error "This is" "a message"; echo "not seen" ) >out 2>err; then
+ atf_fail "utils_error did not exit with an error"
+ else
+ grep "utils_test: E: This is a message" err >/dev/null \
+ || atf_fail "Expected error message not found"
+ [ ! -s out ] || atf_fail "Unexpected output in stdout"
+ fi
+}
+
+
+atf_test_case info
+info_body() {
+ ( utils_info "This is" "a message"; echo "continuing" ) >out 2>err
+ grep "utils_test: I: This is a message" err >/dev/null \
+ || atf_fail "Expected info message not found"
+ grep "continuing" out >/dev/null || atf_fail "Execution aborted"
+}
+
+
+atf_test_case usage_error
+usage_error_body() {
+ if ( utils_usage_error "This is" "a message"; echo "not seen" ) >out 2>err
+ then
+ atf_fail "utils_usage_error did not exit with an error"
+ else
+ grep "utils_test: E: This is a message" err >/dev/null \
+ || atf_fail "Expected error message not found"
+ grep "Type 'man utils_test' for help" err >/dev/null \
+ || atf_fail "Expected instructional message not found"
+ [ ! -s out ] || atf_fail "Unexpected output in stdout"
+ fi
+}
+
+
+atf_test_case warning
+warning_body() {
+ ( utils_warning "This is" "a message"; echo "continuing" ) >out 2>err
+ grep "utils_test: W: This is a message" err >/dev/null \
+ || atf_fail "Expected info message not found"
+ grep "continuing" out >/dev/null || atf_fail "Execution aborted"
+}
+
+
+atf_test_case run__ok
+run__ok_body() {
+ cat >helper.sh <<EOF
+#! /bin/sh
+echo "This exits cleanly:" "\${@}"
+exit 0
+EOF
+ chmod +x helper.sh
+
+ utils_run ./helper.sh one two three >out 2>err \
+ || atf_fail "Got an unexpected error code"
+
+ cat >expout <<EOF
+This exits cleanly: one two three
+EOF
+ atf_check -o file:expout cat out
+
+ cat >experr <<EOF
+utils_test: I: Running './helper.sh one two three' in $(pwd)
+utils_test: I: Command finished successfully
+EOF
+ atf_check -o file:experr cat err
+}
+
+
+atf_test_case run__fail
+run__fail_body() {
+ cat >helper.sh <<EOF
+#! /bin/sh
+echo "This exits with an error:" "\${@}"
+exit 42
+EOF
+ chmod +x helper.sh
+
+ code=0
+ utils_run ./helper.sh one two three >out 2>err || code="${?}"
+ [ ${code} -eq 42 ] \
+ || atf_fail "Did not get the expected error code; got ${code}"
+
+ cat >expout <<EOF
+This exits with an error: one two three
+EOF
+ atf_check -o file:expout cat out
+
+ cat >experr <<EOF
+utils_test: I: Running './helper.sh one two three' in $(pwd)
+utils_test: W: Command failed with code 42
+EOF
+ atf_check -o file:experr cat err
+}
+
+
+atf_test_case contains__yes
+contains__yes_body() {
+ items="bar foo baz"
+ utils_contains foo ${items} || atf_fail "Element not found in list"
+}
+
+
+atf_test_case contains__no
+contains__no_body() {
+ items="bar foo baz"
+ ! utils_contains fo ${items} || atf_fail "Element found in list"
+}
+
+
+atf_test_case filter__no_items
+filter__no_items_body() {
+ atf_check_equal "" "$(utils_filter '*')"
+}
+
+
+atf_test_case filter__no_results
+filter__no_results_body() {
+ items="abc a1 foo a2 a3 bar"
+ atf_check_equal "" "$(utils_filter '*a' ${items})"
+}
+
+
+atf_test_case filter__some_results
+filter__some_results_body() {
+ items="abc a1 foo a2 a3 bar"
+ atf_check_equal "a1 a2 a3" "$(utils_filter 'a[0-9]*' ${items})"
+}
+
+
+atf_init_test_cases() {
+ atf_add_test_case global_progname
+
+ atf_add_test_case error
+ atf_add_test_case info
+ atf_add_test_case warning
+
+ atf_add_test_case usage_error
+
+ atf_add_test_case run__ok
+ atf_add_test_case run__fail
+
+ atf_add_test_case contains__yes
+ atf_add_test_case contains__no
+
+ atf_add_test_case filter__no_items
+ atf_add_test_case filter__no_results
+ atf_add_test_case filter__some_results
+}