#!/bin/bash # Copyright (c) 2011 The Chromium OS Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. # Load common CrOS utilities. Inside the chroot this file is installed in # /usr/lib/crosutils. Outside the chroot we find it relative to the script's # location. . "$(dirname "$0")/common.sh" || { echo "Unable to load common.sh"; exit 1; } # Script must run inside the chroot. assert_inside_chroot # May not be run as root. assert_not_root_user DEFINE_string version "" \ "Assume current chroot version is this." DEFINE_boolean force_latest "${FLAGS_FALSE}" \ "Assume latest version and recreate the version file" DEFINE_boolean skipfirst "${FLAGS_FALSE}" \ "Skip the first upgrade. This may be dangerous." FLAGS "$@" || exit 1 VERSION_FILE=/etc/cros_chroot_version UPGRADE_D="$(dirname ${0})/chroot_version_hooks.d" function update_version() { sudo touch ${VERSION_FILE} sudo chown ${USER} ${VERSION_FILE} echo "${1}" > "${VERSION_FILE}" } ###################################################################### # Sanity checks: if [ -n "${FLAGS_version}" ] && \ ( [ "${FLAGS_skipfirst}" == "${FLAGS_TRUE}" ] || \ [ "${FLAGS_force_latest}" == "${FLAGS_TRUE}" ] ); then error "The option --version cannot be combined with either" error "--skipfirst or --force_latest." exit 1 fi if [ "${FLAGS_skipfirst}" == "${FLAGS_TRUE}" ] && [ "${FLAGS_force_latest}" == "${FLAGS_TRUE}" ]; then error "--skipfirst and --force_latest cannot be combined." exit 1 fi # Latest version is the version of last upgrade.d file. # Name format is ${number}_${short_description} # Versions must be -n sorted, that is, the first continuous sequence # of numbers is what counts. 12_ is before 111_, etc. LATEST_VERSION=$( ls "${UPGRADE_D}" | grep "^[0-9]*_" | \ sort -n | tail -n 1 | cut -f1 -d'_' ) if [ "${FLAGS_force_latest}" == "${FLAGS_TRUE}" ]; then update_version "${LATEST_VERSION}" exit 0 fi # If the file does not exist at all, chroot is old and does not have a version. # default goes here if ! [ -f "${VERSION_FILE}" ]; then info "Chroot of unknown version, initializing to 0" update_version 0 fi CHROOT_VERSION=$(cat "${VERSION_FILE}") # Check if version is a number. if ! [ "${CHROOT_VERSION}" -ge "0" ] &> /dev/null; then error "Your chroot version file ${VERSION_FILE} is bogus: ${CHROOT_VERSION}" exit 1 fi if [ "${FLAGS_skipfirst}" == "${FLAGS_TRUE}" ]; then if [ "${CHROOT_VERSION}" -lt "${LATEST_VERSION}" ]; then # if the new one is latest, this becomes noop CHROOT_VERSION=$(expr ${CHROOT_VERSION} + 1) update_version "${CHROOT_VERSION}" else error "Nothing to skip" exit 1 fi fi if [ -n "${FLAGS_version}" ]; then # Check if it's a number. if ! [ "${FLAGS_version}" -ge "0" ] &> /dev/null; then error "Trying to force invalid version: ${FLAGS_version}" exit 1 fi if [ "${FLAGS_version}" -gt "${LATEST_VERSION}" ]; then error "Forcing nonexistant version: ${FLAGS_version}" exit 1 fi CHROOT_VERSION="${FLAGS_version}" fi if [ "${LATEST_VERSION}" -gt "${CHROOT_VERSION}" ]; then info "Old chroot version (${CHROOT_VERSION}) found, running upgrade hooks" pushd "${UPGRADE_D}" 1> /dev/null for n in $(seq "$(expr ${CHROOT_VERSION} + 1)" "${LATEST_VERSION}"); do hook=(${n}_*) # Sanity check: if there are multiple ${n}_* files, then CL's landed # at the same time and people didn't notice. Let's notice for them. if [ ${#hook[@]} -gt 1 ]; then error "Fatal: Upgrade ${n} has multiple hooks:" error " ${hook[*]}" error "Connor MacLeod knows: There can be only one." exit 1 fi hook=${hook[0]} # Deprecation check; Deprecation can be done by removing old upgrade # scripts and causing too old chroots to have to start over. # Upgrades have to form a continuous sequence. if ! [ -f ${hook} ]; then error "Fatal: Upgrade ${n} doesn't exist." error "Your chroot is so old, that some updates have been deprecated!" error "You need to re-create it!" exit 1 fi info "Rollup ${hook}" # Attempt the upgrade. # NOTE: We source the upgrade scripts because: # 1) We can impose set -something on them. # 2) They can reuse local variables and functions (fe. from common.sh) # Side effect is that the scripts have to be internally enclosed in # a code block, otherwise simply running "exit" in any of them would # terminate the master script, so we call it in a subshell. if ! ( source ${hook} ); then error "Fatal: failed to upgrade ${n}!" exit 1 fi # Each upgrade is atomic. If a middle upgrade fails, we won't retry # all the ones that passed on a previous run. update_version "${n}" done popd 1> /dev/null fi