| #!/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 |