blob: aa92ab9c77ac6527c4cee81c88f338ceb6ba77de [file] [log] [blame]
#!/bin/bash
# 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 is the autoupdater for Memento. When called it consults Omaha to see
# if there's an update available. If so, it downloads it to the other
# partition on the Memento USB stick, then alters the MBR and partitions
# as needed so the next reboot will boot into the newly installed partition.
# Care is taken to ensure that when this exits the USB stick is configured
# to boot into the same partition as before or into the new partition,
# however there may be a small time window when this is not the case. Such a
# window should be about 1 second or less, and we tolerate that since this
# is for testing and not a real autoupdate solution for the long run.
source `dirname "$0"`/memento_updater_logging.sh || exit 1
. /usr/share/misc/shflags || exit 1
DEFINE_boolean force_update $FLAGS_FALSE \
"Force update"
DEFINE_string omaha_url "" \
"Use target autoupdate server for Omaha protocol."
DEFINE_string install_url "" \
"Skip Omaha; Install image at this URL."
DEFINE_string install_url_checksum "" \
"When using --install_url, the corresponding checksum"
DEFINE_string dst_partition "" \
"If set, force installation onto the partition given."
DEFINE_boolean allow_removable_boot $FLAGS_FALSE \
"Run even if booted from removable media."
DEFINE_string force_track "" \
"If set, force a given track to be sent to Omaha"
DEFINE_string kernel_partition "" \
"If set, force a given kernel partition. If set to 'none', install \
the image directly into just the rootfs partition, rather than both. \
If not set, installs to kernel partition based on rootfs partition."
DEFINE_boolean skip_postinst $FLAGS_FALSE \
"Skip running postinst script."
DEFINE_boolean check_block_device $FLAGS_TRUE \
"Check if destination is a block device."
DEFINE_string board "" \
"The board type to download from the server."
# Parse command line
FLAGS "$@" || exit 1
eval set -- "${FLAGS_ARGV}"
# make sure we're root
if [ $(id -u) != "0" ]
then
echo run this script as root
exit 1
fi
# check that this script doesn't run concurrently
PID_FILE=/tmp/memento_updater_lock
if [[ -f "$PID_FILE" && ! -d /proc/`cat $PID_FILE` ]]
then
# process holding lock file is dead. clean up lockfile
rm -rf "$PID_FILE"
fi
# Make sure we're not booted from USB, unless allowed by the flag.
if [ "${FLAGS_allow_removable_boot}" = "${FLAGS_FALSE}" ]; then
ROOTDEV=$(rootdev)
# Remove numbers at end of rootfs device.
SRC=$(echo $ROOTDEV | sed -re 's/p?[0-9]+$//')
REMOVABLE=$(cat /sys/block/${SRC#/dev/}/removable)
if [ "$REMOVABLE" = "1" ]; then
log not updating because we booted from USB
exit 1
fi
fi
if [ -z "${FLAGS_dst_partition}" ]; then
# make sure update hasn't already completed
UPDATED_COMPLETED_FILE="/tmp/memento_autoupdate_completed"
if [ -f "$UPDATED_COMPLETED_FILE" ]
then
exit 0
fi
fi
if ( set -o noclobber; echo "$$" > "$PID_FILE") 2> /dev/null;
then
true
else
log "Failed to acquire lockfile: $PID_FILE."
log "Held by $(cat $PID_FILE)"
exit 1
fi
# remove lockfile when we exit
trap 'RC=$?; rm -f "$PID_FILE"; log Memento AutoUpdate terminating; exit $RC' \
INT TERM EXIT
log Memento AutoUpdate starting
# See if we're forcing an update from a specific URL
if [ -z "$FLAGS_install_url" ]
then
# abort if autoupdates have been disabled, but only when an update image
# isn't forced
UPDATES_DISABLED_FILE="/var/local/disable_software_update"
if [ -f "$UPDATES_DISABLED_FILE" ]
then
log Updates disabled. Aborting.
exit 0
fi
# check w/ omaha to see if there's an update
EXTRA_PING_ARGS=""
if [ -n "${FLAGS_force_track}" ]; then
EXTRA_PING_ARGS="${EXTRA_PING_ARGS} --track=${FLAGS_force_track}"
fi
if [ ${FLAGS_force_update} -eq ${FLAGS_TRUE} ]; then
EXTRA_PING_ARGS="${EXTRA_PING_ARGS} --app_version=ForcedUpdate"
fi
if [ -n "${FLAGS_omaha_url}" ]; then
EXTRA_PING_ARGS="${EXTRA_PING_ARGS} --omaha_url=${FLAGS_omaha_url}"
fi
if [ -n "${FLAGS_board}" ]; then
EXTRA_PING_ARGS="${EXTRA_PING_ARGS} --board=${FLAGS_board}"
fi
OMAHA_CHECK_OUTPUT=$(`dirname "$0"`/ping_omaha.sh ${EXTRA_PING_ARGS})
OMAHA_RC=$?
if [ "$OMAHA_RC" != "0" ]; then
log "Omaha connect failed."
exit 1
fi
IMG_URL=$(echo "$OMAHA_CHECK_OUTPUT" | grep '^URL=' | cut -d = -f 2-)
CHECKSUM=$(echo "$OMAHA_CHECK_OUTPUT" | grep '^HASH=' | cut -d = -f 2-)
else
if [ -z "$FLAGS_install_url_checksum" ]; then
log Specified --install_url, but not --install_url_checksum. Aborting.
exit 1
fi
log User forced an update from: "$FLAGS_install_url" checksum: \
"$FLAGS_install_url_checksum"
IMG_URL="$FLAGS_install_url"
CHECKSUM="$FLAGS_install_url_checksum"
fi
APP_VERSION=$(echo "$OMAHA_CHECK_OUTPUT" | grep '^APP_VERSION=' | \
cut -d = -f 2-)
if [[ -z "$IMG_URL" || -z "$CHECKSUM" ]]
then
log no update
exit 0
fi
# TODO(adlr): make sure we have enough space for the download. This script is
# already correct if we don't have space, but it would be nice to fail
# fast.
log Update Found: $IMG_URL checksum: $CHECKSUM
# Figure out which partition I'm on, and which to download to. If rootdev
# fails, we must be on ramdisk.
LOCAL_DEV=$(rootdev) || LOCAL_DEV=initramfs
# We install onto the other partition so if we end in 3, other ends in 5, and
# vice versa
if [ -n "${FLAGS_dst_partition}" ]; then
INSTALL_DEV="${FLAGS_dst_partition}"
else
if [ "$LOCAL_DEV" = "initramfs" ]; then
log "Booted from initramfs, and no dst_partition specified!"
exit 1
fi
INSTALL_DEV=$(echo $LOCAL_DEV | tr '35' '53')
fi
NEW_PART_NUM=${INSTALL_DEV##*/*[a-z]}
# The kernel needs to be installed to its own partition.
# partitions 2&3 are image A, partitions 4&5 are image B.
if [ -z "${FLAGS_kernel_partition}" ]; then
KINSTALL_DEV=$(echo $INSTALL_DEV | tr '35' '24')
else
KINSTALL_DEV="${FLAGS_kernel_partition}"
fi
if [ "$KINSTALL_DEV" = "$INSTALL_DEV" ]; then
log "kernel install partition the same as rootfs install partition!"
log " (${KINSTALL_DEV})"
exit 1
fi
# Do some device sanity checks.
if [ "$LOCAL_DEV" != "initramfs" -a ! -b "$LOCAL_DEV" ]
then
log "didnt find good local device. local: $LOCAL_DEV install: $INSTALL_DEV"
exit 1
fi
if [ "${FLAG_check_block_device}" = "${FLAGS_TRUE}" -a ! -b "$INSTALL_DEV" ]
then
log "didnt find good install device. local: $LOCAL_DEV install: $INSTALL_DEV"
exit 1
fi
if [ "$LOCAL_DEV" == "$INSTALL_DEV" ]
then
log local and installation device are the same: "$LOCAL_DEV"
exit 1
fi
log Booted from "$LOCAL_DEV" and installing onto "$INSTALL_DEV"
# Make sure installation device is unmounted.
if [ "$INSTALL_DEV" == ""$(grep "^$INSTALL_DEV " /proc/mounts | \
cut -d ' ' -f 1 | uniq) ]
then
# Drive is mounted, must unmount.
log unmounting "$INSTALL_DEV"
umount "$INSTALL_DEV"
# Check if it's still mounted for some strange reason.
if [ "$INSTALL_DEV" == ""$(grep "^$INSTALL_DEV " /proc/mounts | \
cut -d ' ' -f 1 | uniq) ]
then
log unable to unmount "$INSTALL_DEV", which is where i need to write to
exit 1
fi
fi
# Download file to the device.
log downloading image. this may take a while
# wget - fetch file, send to stdout
# tee - save a copy off to device, also send to stdout
# openssl - calculate the sha1 hash of stdin, send checksum to stdout
# tr - convert trailing newline to a space
# pipestatus - append return codes for all prior commands. should all be 0
CHECKSUM_FILE="/tmp/memento_autoupdate_checksum"
# Generally we pipe to split_write to write to two devices, but if
# KINSTALL_DEV is 'none' we write directly to a specific output device.
WRITE_COMMAND='cat > "$INSTALL_DEV"'
if [ "$KINSTALL_DEV" != "none" ]; then
WRITE_COMMAND='"$(dirname "$0")"/split_write "$KINSTALL_DEV" "$INSTALL_DEV"'
fi
COMMAND='wget --progress=dot:mega -O - --load-cookies <(echo "$COOKIES") \
"$IMG_URL" 2>> "$MEMENTO_AU_LOG" | \
tee >(openssl sha1 -binary | openssl base64 > "$CHECKSUM_FILE") | \
gzip -d | '${WRITE_COMMAND}' ; echo ${PIPESTATUS[*]}'
RETURNED_CODES=$(eval "$COMMAND")
EXPECTED_CODES="0 0 0 0"
CALCULATED_CS=$(cat "$CHECKSUM_FILE")
rm -f "$CHECKSUM_FILE"
if [[ ("$CALCULATED_CS" == "$CHECKSUM") && \
("$RETURNED_CODES" == "$EXPECTED_CODES") ]]
then
# wonderful
log download success
else
# either checksum mismatch or ran out of space.
log checksum mismatch or other error \
calculated checksum: "$CALCULATED_CS" reference checksum: "$CHECKSUM" \
return codes: "$RETURNED_CODES" expected codes: "$EXPECTED_CODES"
# zero-out installation partition
dd if=/dev/zero of=$INSTALL_DEV bs=4096 count=1
exit 1
fi
# Return 0 if $1 > $2.
# $1 and $2 are in "a.b.c.d" format where a, b, c, and d are base 10.
function version_number_greater_than {
# Replace periods with spaces and strip off leading 0s (lest numbers be
# interpreted as octal). Strip underscores.
REPLACED_A=$(echo "$1" | sed -r -e 's/(^|\.)0*/ /g' -e 's/_//g')
REPLACED_B=$(echo "$2" | sed -r -e 's/(^|\.)0*/ /g' -e 's/_//g')
EXPANDED_A=$(printf '%020d%020d%020d%020d' $REPLACED_A)
EXPANDED_B=$(printf '%020d%020d%020d%020d' $REPLACED_B)
# This is a string compare:
[[ "$EXPANDED_A" > "$EXPANDED_B" ]]
}
# it's best not to interrupt the script from this point on out, since it
# should really be doing these things atomically. hopefully this part will
# run rather quickly.
# $1 is return code, $2 is command
function abort_update_if_cmd_failed_long {
if [ "$1" -ne "0" ]
then
log "$2 failed with error code $1 . aborting update"
exit 1
fi
}
function abort_update_if_cmd_failed {
abort_update_if_cmd_failed_long "$?" "!!"
}
if [ $FLAGS_skip_postinst -eq $FLAGS_FALSE ]; then
# tell the new image to make itself "ready"
log running postinst on the downloaded image
MOUNTPOINT=/tmp/newpart
mkdir -p "$MOUNTPOINT"
mount -o ro "$INSTALL_DEV" "$MOUNTPOINT"
# Check version of new software if not forcing a dst partition
if [ -z "${FLAGS_dst_partition}" ]; then
NEW_VERSION=$(grep ^GOOGLE_RELEASE "$MOUNTPOINT"/etc/lsb-release | \
cut -d = -f 2-)
if [ "x$NEW_VERSION" = "x" ]
then
log "Can't find new version number. aborting update"
umount "$MOUNTPOINT"
rmdir "$MOUNTPOINT"
exit 1
else
# See if it's newer than us
if [ "${FLAGS_force_update}" != "${FLAGS_TRUE}" ] &&
version_number_greater_than "$APP_VERSION" "$NEW_VERSION"
then
log "Can't upgrade to older version: " "$NEW_VERSION"
umount "$MOUNTPOINT"
rmdir "$MOUNTPOINT"
exit 1
fi
fi
fi
"$MOUNTPOINT"/postinst "$INSTALL_DEV" 2>&1 | cat \
>> "$MEMENTO_AU_LOG"
[ "${PIPESTATUS[*]}" = "0 0" ]
POSTINST_RETURN_CODE=$?
umount "$MOUNTPOINT"
rmdir "$MOUNTPOINT"
# If it failed, don't update MBR but just to be safe, zero out a page of
# install device.
abort_update_if_cmd_failed_long "$POSTINST_RETURN_CODE" "$MOUNTPOINT"/postinst
# postinstall on new partition succeeded.
fi
if [ -z "${FLAGS_dst_partition}" ]; then
# mark update as complete so we don't try to update again
touch "$UPDATED_COMPLETED_FILE"
fi
# Flush linux caches; seems to be necessary
sync
echo 3 > /proc/sys/vm/drop_caches
# tell user to reboot
log Autoupdate applied. You should now reboot
echo UPDATED