blob: bdecbd28db1684ab416340786210ddc114de502f [file] [log] [blame]
#!/bin/sh
# Copyright (c) 2009 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. The first argument
# is the partition where the new rootfs is installed or empty. If non-empty
# the rootfs should be updated w/ the new bootloader config. If empty, the
# rootfs is mounted-read only and should not be updated.
# Setup INSTALL_ROOT
INSTALL_ROOT=$(dirname "$0")
# Load helper functions
. "${INSTALL_ROOT}"/usr/sbin/chromeos-common.sh
. "${INSTALL_ROOT}"/usr/lib/shflags
# Process flags now, couldn't earlier since we needed INSTALL_ROOT
# to know where to load shflags from
DEFINE_string esp_part_file "" "File containing the ESP partition"
DEFINE_boolean postcommit ${FLAGS_FALSE} "Run postcommit"
# 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 ${INSTALL_ROOT}/root/.force_update_firmware
FLAGS_firmware_default=${FLAGS_FALSE}
if [ -f "${INSTALL_ROOT}/root/.force_update_firmware" ]; then
FLAGS_firmware_default=${FLAGS_TRUE}
fi
DEFINE_boolean update_firmware $FLAGS_firmware_default "Enable firmware update."
DEFINE_boolean debug ${FLAGS_FALSE} "Show debug output. Default: false"
# 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"
# 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 ]
}
src_version() {
cat /etc/lsb-release | grep ^CHROMEOS_RELEASE_VERSION | cut -d = -f 2-
}
MAKE_DEV_READONLY=${FLAGS_FALSE}
if [ -n "$IS_UPDATE" ]; then
if $(version_less $(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
MAKE_DEV_READONLY=${FLAGS_TRUE}
fi
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="${INSTALL_ROOT}/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
FIRMWARE_UPDATE_MODE="--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="${INSTALL_ROOT}" \
"${FIRMWARE_UPDATE_SCRIPT}" ${FIRMWARE_UPDATE_ARGS}
# 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 [ "$?" -eq 0 ]; then
echo "Firmware update complete."
if [ "$(mosys platform name)" = "Mario" ] &&
[ -n "${RECHECK_AFTER_UPDATE}" ]; then
echo "Check for kernel subkey changes"
# FIXME(wfrichar): This is a platform-specific workaround for
# chrome-os-partner:1654. It should probably go into the firmware
# update script instead of here, but that script doesn't have all the
# tools available to make the necessary changes.
SCRATCHDIR=$(mktemp -d /tmp/XXXXXX)
cd $SCRATCHDIR
# Note: This code only runs when we're autoupdating, so we know that
# we're running from the newly updated partition. We need to use the
# various firmware utilities from that partition too.
PATH=${DEFAULT_CHROOT_DIR}/usr/sbin:${PATH}
PATH=${DEFAULT_CHROOT_DIR}/usr/bin:${PATH}
# Extract the A & B kernel subkeys from the newly written BIOS.
flashrom -r bios.rom
dump_fmap -x bios.rom
gbb_utility -g --rootkey rootkey.vbpubk GBB_Area
vbutil_firmware --verify Firmware_A_Key \
--signpubkey rootkey.vbpubk --fv Firmware_A_Data \
--kernelkey kernel_subkey_a.vbpubk
vbutil_firmware --verify Firmware_B_Key \
--signpubkey rootkey.vbpubk --fv Firmware_B_Data \
--kernelkey kernel_subkey_b.vbpubk
# FIXME: I want to use 'cmp', but we don't have it because it pulls
# in all the locale stuff and is too big. When/if we switch to
# busybox, we should use cmp here instead.
hash_a=$(md5sum kernel_subkey_a.vbpubk | cut -f1 -d' ')
hash_b=$(md5sum kernel_subkey_b.vbpubk | cut -f1 -d' ')
if [ "$hash_a" != "$hash_b" ]; then
echo "Kernel subkeys are different. Using original boot order."
guid=$(sed 's/.*kern_guid=\([0-9a-fA-F-]\+\).*/\1/' /proc/cmdline)
kern_dev=$($sudo "$GPT" find -1 -u $guid)
base_dev=$(get_block_dev_from_partition_dev $kern_dev)
kern_num=$(get_partition_number $kern_dev)
"$GPT" prioritize $base_dev -i $kern_num
fi
cd - >/dev/null 2>&1
rm -rf $SCRATCHDIR
echo "Kernel subkey check completed."
fi
else
return 1 # error
fi
fi
return 0 # success
}
if [ "${FLAGS_postcommit}" -eq "${FLAGS_FALSE}" ]; then
# Pre-commit. Returning an error here will prevent ever booting into the
# installed system.
echo "Postinst running"
# If this FS was mounted read-write, we can't do deltas from it. Mark the
# FS as such
(touch "${INSTALL_ROOT}/.nodelta" || true) > /dev/null 2>&1
echo " Set boot target to ${INSTALL_DEV}:" \
"Partition ${NEW_PART_NUM}, Slot ${BOOT_SLOT}"
# TODO(rkc): This is a bit of a hack - we're depending on the fact that our
# build machine will always be Intel based - if it isn't, VM builds will fail
# Install syslinux files on EFI partition
if [ -z "${IS_INSTALL}" -a "${IS_INTEL_ARCH}" -eq "1" ]; then
EFI_MNT=$(mktemp -d /tmp/EFI.XXXXXX)
if [ -z "${FLAGS_esp_part_file}" ]; then
EFI_PART=$(make_partition_dev "${ROOT_DEV}" "${LEGACY_PART_NUM}")
else
EFI_PART=$(losetup -f)
if [ -z "${EFI_PART}" ]; then
# It's okay to die here; this parameter will never be given on live
# system - this is only in case of mounted image postinst runs
die "No free loop device. Free up a loop device or reboot. Exiting."
fi
losetup "${EFI_PART}" "${FLAGS_esp_part_file}"
fi
# 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 ! "${INSTALL_ROOT}/sbin/fsck.vfat" -a "${EFI_PART}"; then
if ! "${INSTALL_ROOT}/sbin/fsck.vfat" -a "${EFI_PART}"; then
echo "Rebuilding FAT filessytem."
"${INSTALL_ROOT}/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 "${INSTALL_ROOT}"/boot/syslinux "${EFI_MNT}"
ERR=${?}
echo "Updating syslinux"
# Update all legacy bootloaders.
if [ "${ERR}" -eq "0" ]; then
syslinux_cfg="${INSTALL_ROOT}/boot/syslinux/root.${BOOT_SLOT}.cfg"
grub_cfg="${INSTALL_ROOT}/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
"${INSTALL_ROOT}"/usr/sbin/chromeos-setimage ${BOOT_SLOT} \
--dst=${ROOT_DEV} --run_as_root \
--install_root="${INSTALL_ROOT}" \
--update_syslinux_cfg="${syslinux_cfg}" \
--update_grub_cfg="${grub_cfg}" \
--esp_mounted_at="${EFI_MNT}" \
--update_vmlinuz=${INSTALL_ROOT}/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
mount -o bind /dev "${INSTALL_ROOT}"/dev
set +e
mount -o bind /proc "${INSTALL_ROOT}"/proc
ERR=${?}
if [ "${ERR}" -ne "0" ]; then
umount "${INSTALL_ROOT}"/dev
echo "Failed to bind mount /proc"
exit 1
fi
mount -o bind /tmp "${INSTALL_ROOT}"/tmp
ERR=${?}
if [ "${ERR}" -ne "0" ]; then
umount "${INSTALL_ROOT}"/dev
umount "${INSTALL_ROOT}"/proc
echo "Failed to bind mount /tmp"
exit 1
fi
chroot "${INSTALL_ROOT}" /usr/bin/syslinux "${EFI_PART}"
ERR=${?}
umount "${INSTALL_ROOT}"/tmp
umount "${INSTALL_ROOT}"/proc
umount "${INSTALL_ROOT}"/dev
if (expr match "${EFI_PART}" "/dev/loop*" >/dev/null); then
losetup -d "${EFI_PART}"
fi
set -e
if [ "${ERR}" -ne "0" ]; then
echo "Failed to run syslinux on: ${EFI_PART}"
exit 1
fi
fi
# This export is so that locate_gpt will look within this install
# partition to find that copy of the cgpt program.
# Note, this only works when running $INSTALL_ROOT/postinst.
export DEFAULT_CHROOT_DIR=$(dirname "$0")
locate_gpt # sets $GPT to cgpt executable (or fails and exits immediately)
# If we're working on a real machine, not a mounted image, only then
# run the gpt tool to manipulate the partitions - if we're touching
# the ESP from a file, it means we are not on the target hardware, making
# the GPT and firmware scripts inapplicable nor are we auto-updating
if [ -z "${FLAGS_esp_part_file}" ]; then
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
# Ensure we use a template from the INSTALL_ROOT for both grub
# and syslinux otherwise we can never update them with new rootfs
# vboot configuration data.
syslinux_cfg="${INSTALL_ROOT}/boot/syslinux/root.${BOOT_SLOT}.cfg"
grub_cfg="${INSTALL_ROOT}/boot/efi/boot/grub.cfg"
"${INSTALL_ROOT}"/usr/sbin/chromeos-setimage ${BOOT_SLOT} \
--dst=${ROOT_DEV} --run_as_root \
--install_root="${INSTALL_ROOT}" \
--update_syslinux_cfg="${syslinux_cfg}" \
--update_grub_cfg="${grub_cfg}" \
--update_vmlinuz=${INSTALL_ROOT}/boot/vmlinuz \
${SUB_CMD_DEBUG_FLAG} ${ROOT_DEV_PARAM}
else
"${INSTALL_ROOT}"/usr/sbin/chromeos-setimage ${BOOT_SLOT} \
--dst=${ROOT_DEV} --run_as_root \
--install_root="${INSTALL_ROOT}" \
${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}"
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
# TODO: support firmware update for ARM
if [ ${IS_INTEL_ARCH} -eq 1 ] && ! update_firmware; then
# Note: This will only rollback the ChromeOS verified boot target.
# The assumption is that systems running firmware autoupdate
# are running H2C Bios. 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."
echo "Rolling back update due to failure installing required firmware."
"$GPT" add -i $NEW_KERN_NUM -P 0 -S 0 -T 0 $ROOT_DEV
exit 1
fi
fi
else
# Post-commit. At this point an unexpected reboot may boot the installed
# system, but returning an error here will cause the updater to try to
# not boot the installed system, instead keeping the existing system.
echo -n # this is a noop, required for 'sh'
# We are deprecating post-commit postinst, so do not put anything in here.
echo "Updating gpt PMBR target for legacy BIOS (post-commit)"
# I know we aren't supposed to put anything here, but here is the
# justification:
# The updater in 0.7.42.* sets the rootfs bootable after postinst (precommit)
# is run, and then runs postinst (postcommit). Later versions of the updater
# neither mark partitions bootable, nor run postinst (postcommit). In those
# newer versions, this code won't be run, but it won't need to be run.
# If we didn't care about clients as old as 0.7.42.*, we could leave this
# area empty. Unfortunately, we can't update old updaters.
# At some point, when we don't care about clients that old (0.7.42.*),
# We should remove this whole code path in this file.
if [ -x "${INSTALL_ROOT}"/usr/bin/cgpt ]; then
echo "marking part $LEGACY_PART_NUM bootable (new)"
"${INSTALL_ROOT}"/usr/bin/cgpt boot -i $LEGACY_PART_NUM ${ROOT_DEV} 2>&1
else
# The old gpt tool requires a -b arg to specify the PMBR bootcode. We don't
# want to change the code, so we have to extract it, then put it back.
dd if=${ROOT_DEV} bs=512 count=1 of=/tmp/oldpmbr.bin
echo "marking part $LEGACY_PART_NUM bootable (old)"
gpt -S boot -i $LEGACY_PART_NUM -b /tmp/oldpmbr.bin ${ROOT_DEV} 2>&1
fi
fi