blob: ae3252d31a08b8140cf83b3d0473e0895df52a2d [file] [edit]
#!/bin/sh
# 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.
# This script is called after an AutoUpdate or USB install in an install root
# chroot. The first argument is the partition where the new rootfs is
# installed. The rootfs should be updated w/ the new bootloader config.
# Load helper functions
. /usr/sbin/chromeos-common.sh || exit 1
. /usr/share/misc/shflags || exit 1
DEFINE_boolean debug ${FLAGS_FALSE} "Show debug output. Default: false"
DEFINE_boolean postcommit ${FLAGS_FALSE} "Obsolete option -- do nothing."
DEFINE_string src_version "0.0.0.0" "Current OS version"
# WARNING: do NOT change this default value without consulting adlr/nsanders.
# If you want to temporary enable firmware update for testing, create a tag file
# in /root/.force_update_firmware.
FLAGS_firmware_default=${FLAGS_FALSE}
if [ -f "/root/.force_update_firmware" ]; then
FLAGS_firmware_default=${FLAGS_TRUE}
fi
DEFINE_boolean update_firmware $FLAGS_firmware_default "Enable firmware update."
# Parse command line
FLAGS "$@" || exit 1
eval set -- "${FLAGS_ARGV}"
set -e
SUB_CMD_DEBUG_FLAG=""
if [ "$FLAGS_debug" -eq "${FLAGS_TRUE}" ]; then
set -x
SUB_CMD_DEBUG_FLAG="--debug"
fi
# Update syslinux configuration file and install syslinux itself.
INSTALL_DEV="$1"
# Assert that this script is running in an install root chroot. The assertions
# fail if /proc is not bind mounted, or if the script is running inside a chroot
# on the same device, or if the new root is not really a root (inode != 2).
[ $(stat -c %d /proc/1/root) -ne $(stat -c %d /) ] || exit 1
# If the root filesystem isn't squashfs, we need to check its root inode number.
# The root inode number of extN is always 2, but the root inode number in
# squashfs isn't constant.
if [ "$(stat -f -c %T /)" != "squashfs" ]; then
[ $(stat -c %i /) -eq 2 ] || exit 1
fi
# Old update_engine runs this script twice, the second time passing
# --postcommit. The second run is essentially a no-op so exit gracefully.
#
# TODO(petkov): Remove support for this option when we stop caring about clients
# older than 0.9.128.8.
[ "${FLAGS_postcommit}" -eq "${FLAGS_FALSE}" ] || exit 0
# Find whole disk device.
ROOT_DEV=$(get_block_dev_from_partition_dev ${INSTALL_DEV})
NEW_PART_NUM=${INSTALL_DEV##*/*[a-z]}
NEW_K_DEV=$(make_partition_dev ${ROOT_DEV} $(( $NEW_PART_NUM - 1)) )
case ${NEW_PART_NUM} in
"3" )
BOOT_SLOT="A"
;;
"5" )
BOOT_SLOT="B"
;;
* )
# Not a valid boot location.
echo "Not a valid target parition number: ${NEW_PART_NUM}"
exit 1
;;
esac
# For legacy and efi boot, we only have one boot-capable partition.
LEGACY_PART_NUM=12
if [ -z "${IS_FACTORY_INSTALL}" -a \
-z "${IS_RECOVERY_INSTALL}" -a \
-z "${IS_INSTALL}" ]; then
IS_UPDATE=1
fi
# Turn on/off x86-specific stuff
IS_INTEL_ARCH=$(uname -m | grep "^i.86\|^x86_..\$" | wc -l)
# Usage: if version_less A.B.C.D W.X.Y.Z; then...
version_less() {
local a=$(printf '%010d%010d%010d%010d' $(echo "$1" | sed 's/\./ /g'))
local b=$(printf '%010d%010d%010d%010d' $(echo "$2" | sed 's/\./ /g'))
[ $a \< $b ]
}
get_board() {
grep ^CHROMEOS_RELEASE_BOARD= /etc/lsb-release | cut -d = -f 2-
}
MAKE_DEV_READONLY=${FLAGS_FALSE}
if [ -n "$IS_UPDATE" ] && version_less "${FLAGS_src_version}" 0.10.156.2; then
echo "Patching new rootfs"
dd if=/dev/zero of="${INSTALL_DEV}" bs=1 count=2 conv=notrunc seek=1400 ||
true
MAKE_DEV_READONLY=${FLAGS_TRUE}
fi
update_firmware() {
# See if we need to update firmware. NOTE: we must activate new firmware
# only after new kernel is actived (installed and made bootable),
# otherwise new firmware with all old kernels may lead to recovery screen
# (due to new key).
FIRMWARE_UPDATE_SCRIPT="/usr/sbin/chromeos-firmwareupdate"
if [ ${FLAGS_update_firmware} -eq "${FLAGS_TRUE}" -a \
-x "${FIRMWARE_UPDATE_SCRIPT}" ]; then
FIRMWARE_UPDATE_MODE=""
if [ -n "${IS_FACTORY_INSTALL}" ]; then
# factory-mode (--force must be the first parameter when being invoked)
# Factory install shim will invoke similiar command in memento_updater.sh,
# so if you modify any parameters here, please remember to also update
# memento_updater.sh.
FIRMWARE_UPDATE_MODE="--force --mode=factory_install"
elif [ -n "${IS_RECOVERY_INSTALL}" ]; then
# recovery-mode
FIRMWARE_UPDATE_MODE="--mode=recovery"
elif [ -n "${IS_INSTALL}" ]; then
# installation from chromeos-install
FIRMWARE_UPDATE_MODE="--mode=recovery"
else
# Default: background update by Update Engine.
# "--background-update" tells it not to perform any dangerous
# thing that would require/cause reboot, for example re-writing
# EC firmware. Those tasks will be done in next boot.
FIRMWARE_UPDATE_MODE="--mode=autoupdate"
# FIXME(wfrichar): This is platform-specific. See below.
RECHECK_AFTER_UPDATE="true"
fi
FIRMWARE_UPDATE_ARGS=" $FIRMWARE_UPDATE_MODE "
echo "Updating firmware ($FIRMWARE_UPDATE_ARGS)"
env INSTALL_ROOT=/ "${FIRMWARE_UPDATE_SCRIPT}" ${FIRMWARE_UPDATE_ARGS}
FW_RC="$?"
# Next step after postinst may take a lot of time (eg, disk wiping)
# and people may confuse that as 'firmware update takes a long wait',
# we explicitly prompt here.
if [ "$FW_RC" -eq 0 ]; then
echo "Firmware update complete."
elif [ "$FW_RC" -eq 3 ]; then
echo "Firmware can't update b/c booted from B"
return 3
else
return 1 # error
fi
fi
return 0 # success
}
echo "Postinst running"
# If this FS was mounted read-write, we can't do deltas from it. Mark the
# FS as such
(touch "/.nodelta" || true) > /dev/null 2>&1
echo " Set boot target to ${INSTALL_DEV}:" \
"Partition ${NEW_PART_NUM}, Slot ${BOOT_SLOT}"
# Install syslinux files on EFI partition
if [ -z "${IS_INSTALL}" -a "${IS_INTEL_ARCH}" -eq "1" ]; then
EFI_MNT=$(mktemp -d /tmp/EFI.XXXXXX)
EFI_PART=$(make_partition_dev "${ROOT_DEV}" "${LEGACY_PART_NUM}")
# TODO: scary hack to work around disk corruption
# Note, we mean to run fsck.vfat twice, fsck.vfat returns status 1 for
# "Recoverable errors have been detected or dosfsck has discovered an
# internal inconsistency." If the first fsck fixes errors the first time,
# the second fsck will succeed. Otherwise, nuke it from orbit.
if ! "/sbin/fsck.vfat" -a "${EFI_PART}"; then
if ! "/sbin/fsck.vfat" -a "${EFI_PART}"; then
echo "Rebuilding FAT filessytem."
"/usr/sbin/mkfs.vfat" "${EFI_PART}"
fi
fi
mount "${EFI_PART}" "${EFI_MNT}"
set +e
# Copy over the templates, but don't clobber anything.
# Clobbering is boot_slot specific and handled by setimage below.
cp -nR /boot/syslinux "${EFI_MNT}"
ERR=${?}
echo "Updating syslinux"
# Update all legacy bootloaders.
if [ "${ERR}" -eq "0" ]; then
syslinux_cfg="/boot/syslinux/root.${BOOT_SLOT}.cfg"
grub_cfg="/boot/efi/boot/grub.cfg"
ROOT_DEV_PARAM=""
if [ -n "$IS_UPDATE" ]; then
ROOT_DEV_PARAM="--rootfs_image=$INSTALL_DEV --kernel_image=$NEW_K_DEV"
fi
/usr/sbin/chromeos-setimage ${BOOT_SLOT} \
--dst=${ROOT_DEV} \
--update_syslinux_cfg="${syslinux_cfg}" \
--update_grub_cfg="${grub_cfg}" \
--esp_mounted_at="${EFI_MNT}" \
--update_vmlinuz=/boot/vmlinuz \
${SUB_CMD_DEBUG_FLAG} ${ROOT_DEV_PARAM}
ERR=${?}
fi
umount "${EFI_MNT}"
rmdir "${EFI_MNT}"
if [ "${ERR}" -ne "0" ]; then
echo "Failed to copy syslinux files in and make them bootable."
exit 1
fi
set -e
/usr/bin/syslinux "${EFI_PART}"
fi
# This export is so that locate_gpt will look within this install partition to
# find that copy of the cgpt program.
export DEFAULT_CHROOT_DIR=/
locate_gpt # sets $GPT to cgpt executable (or fails and exits immediately)
echo "Updating the root filesystem hashes and legacy bootloaders"
ROOT_DEV_PARAM=""
if [ -n "$IS_UPDATE" ]; then
ROOT_DEV_PARAM="--rootfs_image=$INSTALL_DEV --kernel_image=$NEW_K_DEV"
fi
if [ ${IS_INTEL_ARCH} -eq 1 ]; then
syslinux_cfg="/boot/syslinux/root.${BOOT_SLOT}.cfg"
grub_cfg="/boot/efi/boot/grub.cfg"
/usr/sbin/chromeos-setimage ${BOOT_SLOT} \
--dst=${ROOT_DEV} \
--update_syslinux_cfg="${syslinux_cfg}" \
--update_grub_cfg="${grub_cfg}" \
--update_vmlinuz=/boot/vmlinuz \
${SUB_CMD_DEBUG_FLAG} ${ROOT_DEV_PARAM}
else
/usr/sbin/chromeos-setimage ${BOOT_SLOT} \
--dst=${ROOT_DEV} \
${SUB_CMD_DEBUG_FLAG} ${ROOT_DEV_PARAM}
fi
echo "Updating Partition Table Attributes..."
NEW_KERN_NUM=$(( $NEW_PART_NUM - 1))
# Make updated partition highest priority for next reboot
"$GPT" prioritize -i $NEW_KERN_NUM $ROOT_DEV
# Set kernel as successful=1 except when autoupdating
new_as_successful=0
if [ -n "${IS_FACTORY_INSTALL}" -o \
-n "${IS_RECOVERY_INSTALL}" -o \
-n "${IS_INSTALL}" ]; then
new_as_successful=1
fi
"$GPT" add -i $NEW_KERN_NUM -S $new_as_successful -T 6 $ROOT_DEV
# Set up gpt boot selection for legacy devices.
# flush linux caches; seems to be necessary
sync
echo 3 > /proc/sys/vm/drop_caches
echo "Updating gpt PMBR target for legacy BIOS"
# Configure the PMBR to boot the new image.
echo "marking part $LEGACY_PART_NUM bootable (new)"
"$GPT" boot -i $LEGACY_PART_NUM ${ROOT_DEV} 2>&1
if [ ${MAKE_DEV_READONLY} -eq ${FLAGS_TRUE} ]; then
echo "Making dev ${INSTALL_DEV} read-only"
hdparm -r 1 "${INSTALL_DEV}" || true
fi
# At this point in the script, the new partition has been marked bootable
# and a reboot will boot into it. Thus, it's important that any future
# errors in this script do not cause this script to return failure unless
# in factory mode.
if [ -z "${IS_FACTORY_INSTALL}" ]; then
set +e
fi
# We have a new image, making the ureadahead pack files
# out-of-date. Delete the files so that ureadahead will
# regenerate them on the next reboot.
# WARNING: This doesn't work with upgrade from USB, rather than full
# install/recovery. We don't have support for it as it'll increase the
# complexity here, and only developers do upgrade from USB.
rm -f /var/lib/ureadahead/*
# Create a file indicating that the install is completed. The file
# will be used in /sbin/chromeos_startup to run tasks on the next boot.
# See comments above about removing ureadahead files.
touch /mnt/stateful_partition/.install_completed
FW_RC=0
update_firmware || FW_RC="$?"
if [ "$FW_RC" -ne 0 ]; then
# Note: This will only rollback the ChromeOS verified boot target.
# The assumption is that systems running firmware autoupdate
# are not running legacy (non-ChromeOS) firmware. If the firmware
# updater crashes or writes corrupt data rather than gracefully
# failing, we'll probably need to recover with a recovery image.
echo "Firmware update failed (error code: $FW_RC)."
echo "Rolling back update due to failure installing required firmware."
"$GPT" add -i $NEW_KERN_NUM -P 0 -S 0 -T 0 $ROOT_DEV || true
exit "$FW_RC"
fi