diff options
Diffstat (limited to 'sysutils')
-rw-r--r-- | sysutils/sysupgrade/DESCR | 15 | ||||
-rw-r--r-- | sysutils/sysupgrade/Makefile | 89 | ||||
-rw-r--r-- | sysutils/sysupgrade/PLIST | 9 | ||||
-rw-r--r-- | sysutils/sysupgrade/TODO | 30 | ||||
-rw-r--r-- | sysutils/sysupgrade/files/Kyuafile | 7 | ||||
-rw-r--r-- | sysutils/sysupgrade/files/config.subr | 299 | ||||
-rw-r--r-- | sysutils/sysupgrade/files/config_test.sh | 422 | ||||
-rw-r--r-- | sysutils/sysupgrade/files/default.conf | 35 | ||||
-rw-r--r-- | sysutils/sysupgrade/files/sysupgrade.8 | 387 | ||||
-rw-r--r-- | sysutils/sysupgrade/files/sysupgrade.conf.5 | 160 | ||||
-rw-r--r-- | sysutils/sysupgrade/files/sysupgrade.sh | 459 | ||||
-rw-r--r-- | sysutils/sysupgrade/files/sysupgrade_test.sh | 1055 | ||||
-rw-r--r-- | sysutils/sysupgrade/files/utils.subr | 125 | ||||
-rw-r--r-- | sysutils/sysupgrade/files/utils_test.sh | 186 |
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 +} |