blob: 9b6ad866181a1aa6d9be439c98854c736a52be3f [file] [log] [blame]
#!/bin/bash
# Copyright (c) 2014 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.
set -e
# TTY which will get a copy of bash traces, and at the end a pager to
# look through the trace if things fail.
TRACE_TTY=4
# Dump bash trace to /var/log/factory_install.log.
LOG_FILE=/var/log/factory_install.log
mkdir -p $(dirname "$LOG_FILE")
exec {LOG_FD}>"$LOG_FILE"
tail -f "$LOG_FILE" > /dev/tty"$TRACE_TTY" &
export BASH_XTRACEFD="$LOG_FD"
. "$(dirname "$0")/chromeos-common.sh"
. "$(dirname "$0")/ping_shopfloor.sh"
. "/opt/google/memento_updater/memento_updater_logging.sh"
. "/opt/google/memento_updater/find_omaha.sh"
# Tail memento output to both our stdout and the log file, since
# there's no better way to display of download/install for individual
# partitions.
tail -F "$MEMENTO_AU_LOG" 2>/dev/null | tee -a "$LOG_FILE" &
# Definition of ChromeOS partition layout
DST_FACTORY_KERNEL_PART=2
DST_FACTORY_PART=3
DST_RELEASE_KERNEL_PART=4
DST_RELEASE_PART=5
DST_OEM_PART=8
DST_EFI_PART=12
DST_STATE_PART=1
# Override this if we need to perform additional commands
COMPLETE_SCRIPT=""
# Override this if we want to install with a board different from installer
BOARD=""
# Override this if we want to install with a different omaha server.
OMAHA="$(findLSBValue CHROMEOS_AUSERVER)"
RELEASE_ONLY="$(findLSBValue RELEASE_ONLY)"
# Global variables
DST_DRIVE=""
EC_PRESENT=0
DEVSW_PRESENT=1
NETBOOT_RAMFS=""
# Supported actions (a set of lowercase characters)
SUPPORTED_ACTIONS=irsvz
# Each action x is implemented in an action_$x handler (e.g.,
# action_i); see the handlers for more information about what
# each option is.
# Define our own logging function. The one from memento_updater writes to
# a file, which may cause a race condition if we tail it.
log() {
echo "$*"
}
# Change color in tty1 by ANSI escape sequence code
colorize() {
set +x
local code="$1"
case "$code" in
"red" )
code="1;31"
;;
"green" )
code="1;32"
;;
"yellow" )
code="1;33"
;;
"white" )
code="0;37"
;;
"boldwhite" )
code="1;37"
;;
esac
printf "\033[%sm" "$code"
set -x
}
# Error message for any unexpected error.
on_die() {
set +x
kill_bg_jobs
colorize red
echo
log "ERROR: Factory installation has been stopped."
log "See tty$TRACE_TTY (Ctrl-Alt-F$TRACE_TTY) for detailed information."
log "(The F$TRACE_TTY key is $TRACE_TTY keys to the right of the 'esc' key.)"
busybox openvt -c "$TRACE_TTY" sh -c "clear; exec secure_less.sh < $LOG_FILE"
}
kill_bg_jobs() {
pids=$(jobs -p)
# Disown all background jobs to avoid terminated messages
disown -a
# Kill the jobs
echo "$pids" | xargs -r kill -9 2>/dev/null || true
}
exit_success() {
trap - EXIT
kill_bg_jobs
exit 0
}
trap on_die EXIT
die() {
set +x
colorize red
set +x
log "ERROR: $*"
kill_bg_jobs
exit 1
}
config_tty() {
stty opost
}
clear_fwwp() {
log "Firmware Write Protect disabled, clearing status registers."
if [ $EC_PRESENT -eq 1 ]; then
flashrom -p ec --wp-disable
fi
flashrom -p host --wp-disable
log "WP registers should be cleared now"
}
ensure_fwwp_consistency() {
local ec_wp main_wp
if [ $EC_PRESENT -eq 0 ]; then
return
fi
ec_wp="$(flashrom -p ec --wp-status 2>/dev/null)" || return
main_wp="$(flashrom -p host --wp-status 2>/dev/null)"
ec_wp="$(echo "$ec_wp" | sed -nr 's/WP.*(disabled|enabled).$/\1/pg')"
main_wp="$(echo "$main_wp" | sed -nr 's/WP.*(disabled|enabled).$/\1/pg')"
if [ "$ec_wp" != "$main_wp" ]; then
die "Inconsist firmware write protection status: main=$main_wp, ec=$ec_wp."\
"Please disable Hardware Write Protection and restart again."
fi
}
check_fwwp() {
flashrom -p host --wp-status 2>/dev/null |
grep -q "write protect is enabled"
}
clear_tpm() {
log "Clearing TPM"
# Reset TPM. tcsd needs to have not been run because it locks the TPM.
tpmc ppon
tpmc clear
tpmc enable
tpmc activate
local firmware_index="0x1007"
local firmware_struct_version="1"
local firmware_flags="0"
local firmware_fw_versions="1 0 1 0"
local firmware_reserved="0 0 0 0"
tpmc write $firmware_index $firmware_struct_version $firmware_flags \
$firmware_fw_versions $firmware_reserved
local kernel_index="0x1008"
local kernel_struct_version="1"
local kernel_uid="4c 57 52 47"
local kernel_kernel_versions="1 0 1 0"
local kernel_reserved="0 0 0 0"
tpmc write $kernel_index $kernel_struct_version $kernel_uid \
$kernel_kernel_versions $kernel_reserved
log "Done clearing TPM"
}
set_time() {
log "Setting time from:"
# Extract only the server and port.
local time_server_url="$(echo "$OMAHA" | sed "s|/update||; s|http://||")"
log " Server $time_server_url."
local result="$(htpdate -s -t "$time_server_url" 2>&1)"
if ! echo "$result" | grep -Eq "(failed|unavailable)"; then
log "Success, time set to $(date)"
hwclock -w 2>/dev/null
return 0
fi
log "Failed to set time: $(echo "$result" | grep -E "(failed|unavailable)")"
return 1
}
check_ethernet_status() {
local link i
link=$(ip -f link addr | sed 'N;s/\n/ /' | grep -w 'ether' |
cut -d ' ' -f 2 | sed 's/://')
for i in $link; do
if ip -f inet addr show $i | grep -q inet; then
if ! iw $i info >/dev/null 2>&1; then
log "$(ip -f inet addr show $i | grep inet)"
return 0
fi
fi
done
return 1
}
reset_chromeos_device() {
log "Clearing NVData."
if ! mosys nvram clear; then
# Not every platforms really need this - OK if nvram is not cleared.
log "Warning: NVData not cleared."
fi
if grep -q cros_netboot /proc/cmdline; then
log "Device is network booted."
return
fi
if crossystem "mainfw_type?nonchrome"; then
# Non-ChromeOS firmware devices can stop now.
log "Device running Non-ChromeOS firmware."
return
fi
log "Checking for Firmware Write Protect"
# Check for physical firmware write protect. We'll only
# clear this stuff if the case is open.
if [ "$(crossystem wpsw_cur)" = "0" ]; then
# Clear software firmware write protect.
clear_fwwp
fi
log "Checking if TPM should be cleared (for version and owner)"
# To clear TPM, we need it unlocked (only in recovery boot).
# Booting with USB in developer mode (Ctrl-U) does not work.
if crossystem "mainfw_type?recovery"; then
clear_tpm
else
mainfw_type="$(crossystem mainfw_type)"
colorize yellow
log " - System was not booted in recovery mode (current: $mainfw_type).
WARNING: TPM won't be cleared. To enforce clearing TPM, make sure you are
using correct image signed with same key (MP, Pre-MP, or DEV), turn on
developer switch if you haven't, then hold recovery button and reboot the
system again. Ctrl-U won't clear TPM.
"
# Alert for a while
sleep 3
fi
ensure_fwwp_consistency
}
is_fixed_drive() {
# Check if a device node is a fixed drive.
local dev="$1"
[ -b "$dev" ] &&
[ "$(cat /sys/block/$(basename $dev)/removable 2>/dev/null)" = "0" ]
}
get_dst_drive() {
# x86 can use either MMC or SSD as boot disk. If we have internal SD card
# slot without USB bridge (/dev/mmcblk0) and SSD drive (/dev/sda) at the same
# time, it has to check removable attribute for picking the correct
# destination drive.
local dev
local match_list
DST_DRIVE=""
case "$(crossystem arch)" in
x86 )
match_list="/dev/mmcblk0 /dev/sda"
;;
arm )
match_list="/dev/mmcblk0"
;;
* )
die "Failed to auto detect architecture."
;;
esac
for dev in $match_list; do
if is_fixed_drive "$dev"; then
DST_DRIVE=$dev
break
fi
done
[ -n "$DST_DRIVE" ] || die "Cannot find fixed drive from $match_list."
}
lightup_screen() {
# Light up screen in case you can't see our splash image.
local script="/usr/sbin/lightup_screen"
if [ -x "$script" ]; then
$script
else
log "$script does not exist or not executable"
fi
}
load_modules() {
# Required kernel modules might not be loaded. Load them now.
modprobe i2c-dev || true
}
prepare_disk() {
log "Factory Install: Setting partition table"
local pmbr_code="/root/.pmbr_code"
[ -r $pmbr_code ] || die "Missing $pmbr_code; please rebuild image."
. "/usr/sbin/write_gpt.sh"
write_base_table "${DST_DRIVE}" "${pmbr_code}" 2>&1
# Informs OS of partition table changes. The autoupdater has trouble with loop
# devices.
log "Reloading partition table changes..."
sync
sleep 3 # Wait for sync to take place.
partprobe "$DST_DRIVE"
log "Done preparing disk"
}
select_board() {
# Prompt the user if USER_SELECT is true.
local user_select="$(findLSBValue USER_SELECT | tr '[:upper:]' '[:lower:]')"
if [ "$user_select" = "true" ]; then
echo -n "Enter the board you want to install (ex: x86-mario): "
read BOARD
fi
}
find_var() {
# Check kernel commandline for a specific key value pair.
# Usage: omaha=$(find_var omahaserver)
# Assume values are space separated, keys are unique within the commandline,
# and that keys and values do not contain spaces.
local key="$1"
for item in $(cat /proc/cmdline); do
if echo "$item" | grep -q "$key"; then
echo "$item" | cut -d'=' -f2
return 0
fi
done
return 1
}
override_from_firmware() {
# Check for Omaha URL or Board type from kernel commandline.
# OMAHA and BOARD are override env variables used when calling
# memento_updater.
local omaha=""
if omaha="$(find_var omahaserver)"; then
log " Kernel cmdline OMAHA override to $omaha"
OMAHA="$omaha"
fi
local board=""
if board="$(find_var cros_board)"; then
log " Kernel cmdline BOARD override to $board"
BOARD="$board"
fi
}
override_from_board() {
# Call into any board specific configuration settings we may need.
# These will be provided by chromeos-bsp-[board] build in the private overlay.
local lastboard="$BOARD"
if [ -f "/usr/sbin/board_customize_install.sh" ]; then
. /usr/sbin/board_customize_install.sh
fi
# Let's notice if BOARD has changed and print a message.
if [ "$lastboard" != "$BOARD" ]; then
colorize red
log " Private overlay customization BOARD override to $BOARD"
sleep 1
fi
}
override_from_tftp() {
# Check for Omaha URL from tftp server.
# OMAHA is override env variables used when calling memento_updater.
local omaha=""
local tftp=""
local omahaserver_config="omahaserver.conf"
local tftp_output=""
# Use board specific config if $BOARD is not null.
[ -z $BOARD ] || omahaserver_config="omahaserver_$BOARD.conf"
tftp_output="/tmp/${omahaserver_config}"
if tftp="$(find_var tftpserverip)"; then
log "override_from_tftp: kernel cmdline tftpserverip $tftp"
# Get omahaserver_config from tftp server.
# Use busybox tftp command with options: "-g: Get file",
# "-r FILE: Remote FILE" and "-l FILE: local FILE".
rm -rf "$tftp_output"
tftp -g -r $omahaserver_config -l $tftp_output $tftp || true
if [ -f $tftp_output ]; then
OMAHA="$(cat $tftp_output)"
log "override_from_tftp: OMAHA override to $OMAHA"
fi
fi
}
overrides() {
override_from_firmware
override_from_board
}
bringup_network() {
# Probe USB Ethernet devices.
local module
local module_name
for module in $(find /lib/modules/ -name "*.ko"); do
module_name="$(basename ${module%.ko})"
modprobe ${module_name}
done
# Try to bring up network and get an IP address.
ifconfig eth0 up
udhcpc -t 5 -f -q -s /etc/udhcpc.script
}
disable_release_partition() {
# Release image is not allowed to boot unless the factory test is passed
# otherwise the wipe and final verification can be skipped.
# TODO(hungte) do this in memento_updater or postinst may be better
if ! cgpt add -i "$DST_RELEASE_KERNEL_PART" -P 0 -T 0 -S 0 "$DST_DRIVE"
then
# Destroy kernels otherwise the system is still bootable.
dst="$(make_partition_dev $DST_DRIVE $DST_RELEASE_KERNEL_PART)"
dd if=/dev/zero of=$dst bs=1M count=1
dst="$(make_partition_dev $DST_DRIVE $DST_FACTORY_KERNEL_PART)"
dd if=/dev/zero of=$dst bs=1M count=1
die "Failed to lock release image. Destroy all kernels."
fi
}
run_postinst() {
local install_dev="$1"
local mount_point="$(mktemp -d)"
local result=0
mount -t ext2 -o ro "$install_dev" "$mount_point"
IS_FACTORY_INSTALL=1 "$mount_point"/postinst \
"$install_dev" 2>&1 || result="$?"
umount "$install_dev" || true
rmdir "$mount_point" || true
return $result
}
run_netboot_postinst()
{
# For netboot, handle post-processing channels in chroot in installed
# environment.
INSTALLED_ROOT="$1"
INSTALLED_STATEFUL="$2"
HWID_SCRIPT="$3"
FIRMWARE_SCRIPT="$4"
CHROOT_PATH="$(mktemp -d /tmp/chroot_XXXXXXXX)"
POST_PROCESS_SCRIPT=/mnt/stateful_partition/netboot_postinst.sh
HWID_COPIED_PATH=/mnt/stateful_partition/hwid_bundle.sh
FIRMWARE_COPIED_PATH=/mnt/stateful_partition/firmware_install
mount -o ro -t ext2 "$INSTALLED_ROOT" "$CHROOT_PATH"
mount "$INSTALLED_STATEFUL" "$CHROOT_PATH"/mnt/stateful_partition
if [ -s "$HWID_SCRIPT" ]; then
cp "$HWID_SCRIPT" "${CHROOT_PATH}${HWID_COPIED_PATH}"
fi
if [ -s "$FIRMWARE_SCRIPT" ]; then
cp "$FIRMWARE_SCRIPT" "${CHROOT_PATH}${FIRMWARE_COPIED_PATH}"
fi
cp -p /bin/netboot_postinst.sh "${CHROOT_PATH}${POST_PROCESS_SCRIPT}"
chroot "$CHROOT_PATH" "$POST_PROCESS_SCRIPT"
umount "$CHROOT_PATH"/mnt/stateful_partition || true
umount "$CHROOT_PATH" || true
sync
}
run_firmware_update() {
local install_drive="$1"
local install_dev=""
local mount_point="$(mktemp -d)"
local result=0
local updater="$(findLSBValue FACTORY_INSTALL_FIRMWARE)"
local stateful_updater="${updater#/mnt/stateful_partition/}"
local mount_opt=""
# If there's nothing assigned, we should load firmware from release rootfs;
# otherwise follow the assigned location (currently only stateful partition is
# supported).
if [ -z "$updater" ]; then
updater="$mount_point/usr/sbin/chromeos-firmwareupdate"
install_dev="$(make_partition_dev "$install_drive" "$DST_RELEASE_PART")"
mount_opt="-t ext2 -o ro"
elif [ "$updater" != "$stateful_updater" ]; then
updater="$mount_point/$stateful_updater"
install_dev="$(make_partition_dev "$install_drive" "$DST_STATE_PART")"
# Stateful partition may be ext4 and turned off ext2 compatibility mode.
mount_opt="-o ro"
else
die "Unknown firmware updater location: $updater"
fi
log "Running firmware updater from $install_dev ($updater)..."
mount $mount_opt "$install_dev" "$mount_point"
# If write protection is disabled, perform factory (RO+RW) firmware setup;
# otherwise run updater in recovery (RW only) mode.
if ! check_fwwp; then
"$updater" --force --mode=factory_install 2>&1 ||
result="$?"
else
# We need to recover the firmware and then enable developer firmware.
"$updater" --force --mode=recovery 2>&1 || result="$?"
# For two-stop firmware, todev is a simple crossystem call; but for other
# old firmware (alex/zgb), todev will perform flashrom and then reboot.
# So this must be the very end command.
"$updater" --force --mode=todev 2>&1 || result="$?"
fi
umount "$mount_point" || true
rmdir "$mount_point" || true
return $result
}
save_omaha_url() {
log "Save active Omaha server URL to stateful partition"
local stateful_dev="$1"
local mount_point="$(mktemp -d)"
local output_file="$mount_point/dev_image/etc/lsb-factory"
mount "$stateful_dev" "$mount_point"
mkdir -p "$(dirname "$output_file")"
echo "FACTORY_OMAHA_URL=$OMAHA" >> "$output_file"
# Sometimes lsb-factory is not written yet before umount.
sync;sync;sync;
umount "$mount_point" || true
rmdir "$mount_point" || true
}
omaha_greetings() {
local message="$1"
local uuid="$2"
if [ -n "$OMAHA" ]; then
wget -q "${OMAHA%/update}/greetings/${message}/${uuid}" \
-O /dev/null 2>/dev/null || true
fi
}
factory_on_complete() {
if [ ! -s "$COMPLETE_SCRIPT" ]; then
return 0
fi
log "Executing completion script... ($COMPLETE_SCRIPT)"
if ! sh "$COMPLETE_SCRIPT" "$DST_DRIVE" 2>&1; then
die "Failed running completion script $COMPLETE_SCRIPT."
fi
log "Completion script executed successfully."
}
disable_dev_switch() {
# Turn off developer mode on devices without physical developer switch.
if [ $DEVSW_PRESENT -eq 0 ]; then
crossystem disable_dev_request=1
# When physical switch exists, force user to turn it off.
elif [ $DEVSW_PRESENT -eq 1 -a "$(crossystem devsw_cur)" = "1" ]; then
while [ "$(crossystem devsw_cur)" = "1" ]; do
log "Please turn off developer switch to continue"
sleep 5
done
fi
}
factory_reset() {
# Turn off developer mode on devices without physical developer switch.
if [ $DEVSW_PRESENT -eq 0 ]; then
crossystem disable_dev_request=1
fi
log "Performing factory reset"
if ! /usr/sbin/factory_reset.sh; then
die "Factory reset failed."
fi
log "Done."
# TODO(hungte) shutdown or reboot once we decide the default behavior.
exit_success
}
factory_disk_wipe() {
log "Performing factory disk wipe"
if ! /usr/sbin/factory_reset.sh wipe; then
die "Factory disk wipe failed."
fi
log "Done."
exit_success
}
factory_install_usb() {
local i=""
local src_offset="$(findLSBValue FACTORY_INSTALL_USB_OFFSET)"
local src_drive="$(findLSBValue REAL_USB_DEV)"
# REAL_USB_DEV is optional on systems without initramfs (ex, ARM).
[ -n "$src_drive" ] || src_drive="$(rootdev -s 2>/dev/null)"
[ -n "$src_drive" ] || die "Unknown media source. Please define REAL_USB_DEV."
# Finds the real drive from sd[a-z][0-9]* or mmcblk[0-9]*p[0-9]*
src_drive="${src_drive%[0-9]*}"
src_drive="$(echo $src_drive | sed 's/\(mmcblk[0-9]*\)p/\1/')"
colorize yellow
for i in EFI OEM STATE FACTORY FACTORY_KERNEL RELEASE RELEASE_KERNEL; do
# The source media must have exactly the same layout.
local part="$(eval 'echo $DST_'${i}'_PART')"
local src="$(make_partition_dev $src_drive $part)"
local dst="$(make_partition_dev $DST_DRIVE $part)"
# Factory/Release may be shifted on source media.
if [ -n "$src_offset" ]; then
case "$i" in
FACTORY* | RELEASE* )
src="$(make_partition_dev $src_drive $((part + src_offset)) )"
true
;;
esac
fi
# Detect file system size
local dd_param="bs=1M"
local count="$(dumpe2fs -h "$src" 2>/dev/null |
grep "^Block count" |
sed 's/.*: *//')"
if [ -n "$count" ]; then
local bs="$(dumpe2fs -h "$src" 2>/dev/null |
grep "^Block size" |
sed 's/.*: *//')"
# Optimize copy speed, with restriction: bs<10M
while [ "$(( (count > 0) &&
(count % 2 == 0) &&
(bs / 1048576 < 10) ))" = "1" ]; do
count="$((count / 2))"
bs="$((bs * 2))"
done
dd_param="bs=$bs count=$count"
fi
log "Copying: [$i] $src -> $dst ($dd_param)"
# TODO(hungte) Detect copy failure
pv -B 1M "$src" |
dd $dd_param of="$dst" iflag=fullblock oflag=dsync
done
# Run postinst on release partition to validate rootfs and create
# verity table.
run_postinst "$(make_partition_dev "$DST_DRIVE" "$DST_RELEASE_PART")"
# Disable release partition and activate factory partition
disable_release_partition
run_postinst "$(make_partition_dev "$DST_DRIVE" "$DST_FACTORY_PART")"
run_firmware_update "$DST_DRIVE"
}
factory_install_omaha() {
local i=""
local result=""
local return_code=""
local dst=""
local dst_arg=""
local src_url=""
local src_checksum=""
local ping_result=""
# Channels defined by memento updater
FACTORY_CHANNEL_ARG='--force_track=factory-channel'
RELEASE_CHANNEL_ARG='--force_track=release-channel'
OEM_CHANNEL_ARG='--force_track=oempartitionimg-channel'
EFI_CHANNEL_ARG='--force_track=efipartitionimg-channel'
STATE_CHANNEL_ARG='--force_track=stateimg-channel'
FIRMWARE_CHANNEL_ARG='--force_track=firmware-channel'
HWID_CHANNEL_ARG='--force_track=hwid-channel'
COMPLETE_CHANNEL_ARG='--force_track=complete-channel'
# Special channels for execution
DST_FIRMWARE_PART="$(mktemp "/tmp/fw_XXXXXXXX")"
DST_HWID_PART="$(mktemp "/tmp/hwid_XXXXXXXX")"
DST_COMPLETE_PART="$(mktemp "/tmp/complete_XXXXXXXX")"
# Ping shopfloor server
ping_result=$(ping_shopfloor 2>/dev/null) || return_code="$?"
if [ "$return_code" = "" ]; then
eval "$ping_result"
fi
# Generate the uuid for current install session
uuid="$(uuidgen 2>/dev/null)" || uuid="Not_Applicable"
# Say hello to miniomaha server
omaha_greetings "hello" "${uuid}"
# Install the partitions
for i in EFI OEM STATE RELEASE FACTORY FIRMWARE HWID COMPLETE; do
if [ "$i" = "FACTORY" -a -n "$RELEASE_ONLY" ]; then
# Ignore factory image in release only mode
continue
fi
# DST_*_PART can be a numeric partition number or plain file.
local part="$(eval 'echo $DST_'${i}'_PART')"
if [ -z "$part" ]; then
die "INVALID CHANNEL: $i."
elif echo "$part" | grep -qs "^[0-9]*$"; then
dst="$(make_partition_dev $DST_DRIVE $part)"
dst_arg=""
else
dst="$part"
dst_arg="--nocheck_block_device"
fi
log "Factory Install: Installing $i image to $dst"
local channel_arg="$(eval 'echo $'${i}'_CHANNEL_ARG')"
local kpart="none"
if [ "$i" = "FACTORY" -o "$i" = "RELEASE" ]; then
# Set up kernel partition
kpart=""
fi
local extra_arg="--skip_postinst"
if [ "$i" = "FACTORY" ]; then
# Do postinst after update
extra_arg=""
fi
if [ "$i" = "RELEASE" -a -n "$RELEASE_ONLY" ]; then
# Do postinst after update in release only mode
extra_arg=""
fi
if [ -n "$BOARD" ]; then
extra_arg="$extra_arg --board=$BOARD"
fi
if [ -n "$OMAHA" ]; then
extra_arg="$extra_arg --omaha_url=$OMAHA"
fi
if [ "$SHOPFLOOR_INSTALL" = "1" ]; then
src_url="$(eval 'echo $SRC_'${i}'_URL')"
src_checksum="$(eval 'echo $SRC_'${i}'_CHECKSUM')"
extra_arg="$extra_arg --install_url=$src_url"
extra_arg="$extra_arg --install_url_checksum=$src_checksum"
fi
return_code=0
result="$(IS_FACTORY_INSTALL=1 \
/opt/google/memento_updater/memento_updater.sh \
--dst_partition "$dst" --kernel_partition "$kpart" \
--allow_removable_boot $channel_arg $dst_arg $extra_arg)" ||
return_code="$?"
if [ "$i" = "RELEASE" -a -z "$RELEASE_ONLY" ]; then
# Disable release partition when we are not in release only mode.
disable_release_partition
fi
# Check the result
if [ "$return_code" != "0" ]; then
# memento update has encountered a fatal error.
die "Factory install of target $dst has failed with error $return_code."
elif [ "$result" != "UPDATED" ]; then
if [ "$i" = "RELEASE" -a -n "$RELEASE_ONLY" ]; then
# Only updating the primary root/kernel partition is strictly required.
# If the omahaserver is configured to not update others that's fine.
# In release only mode, release image is required.
die "AU failed."
elif [ "$i" = "FACTORY" -a -z "$RELEASE_ONLY" ]; then
# Otherwise, factory image is required.
die "AU failed."
fi
fi
done
# Notify miniomaha server all downloads (including postinst) are completed.
omaha_greetings "download_complete" "${uuid}"
# Post-processing channels in self-executable file.
if [ -n "$NETBOOT_RAMFS" ]; then
run_netboot_postinst \
"$(make_partition_dev $DST_DRIVE 3)" \
"$(make_partition_dev $DST_DRIVE 1)" \
"${DST_HWID_PART}" \
"${DST_FIRMWARE_PART}"
else
if [ -s "$DST_FIRMWARE_PART" ]; then
log "Execute firmware-install script"
dst="$DST_FIRMWARE_PART"
dst_arg="--force --mode=factory_install"
# In release only mode, we want to run firmware updater in recovery mode.
if [ -n "$RELEASE_ONLY" ]; then
dst_arg="--force --mode=recovery"
fi
if ! sh "$dst" $dst_arg 2>&1; then
die "Firmware updating failed."
fi
fi
if [ -s "$DST_HWID_PART" ]; then
log "Execute HWID component list updater script"
dst="$DST_HWID_PART"
dst_arg="$(make_partition_dev $DST_DRIVE $DST_STATE_PART)"
if ! sh "$dst" "$dst_arg" 2>&1; then
die "HWID component list updating failed."
fi
fi
fi
# Update omaha information into stateful partition.
save_omaha_url "$(make_partition_dev $DST_DRIVE $DST_STATE_PART)" \
2>&1 || die "Failed to save OMAHA server information."
# After post processing, notify miniomaha server a installation session
# has been successfully completed.
omaha_greetings "goodbye" "${uuid}"
if [ -s "$DST_COMPLETE_PART" ]; then
log "Found completion script."
COMPLETE_SCRIPT="$DST_COMPLETE_PART"
fi
# In release only mode, disable developer switch in the end to avoid
# entering developer mode after restart.
if [ -n "$RELEASE_ONLY" ]; then
disable_dev_switch
fi
}
test_ec_flash_presence() {
# If "flashrom -p ec --get-size" command succeeds (returns 0),
# then EC flash chip is present in system. Otherwise, assume EC flash is not
# present or supported.
if flashrom -p ec --get-size >/dev/null 2>&1; then
EC_PRESENT=1
else
EC_PRESENT=0
fi
}
test_devsw_presence() {
local VBSD_HONOR_VIRT_DEV_SWITCH="0x400"
local vdat_flags="$(crossystem vdat_flags || echo 0)"
if [ "$((vdat_flags & VBSD_HONOR_VIRT_DEV_SWITCH))" = "0" ]; then
DEVSW_PRESENT=1
else
DEVSW_PRESENT=0
fi
}
board_pre_install() {
BOARD_PRE_INSTALL=/usr/sbin/board_factory_pre_install.sh
if [ -x "${BOARD_PRE_INSTALL}" ]; then
echo "Running board specific pre-install: ${BOARD_PRE_INSTALL}"
${BOARD_PRE_INSTALL} || exit 1
fi
}
board_post_install() {
BOARD_POST_INSTALL=/usr/sbin/board_factory_post_install.sh
if [ -x "${BOARD_POST_INSTALL}" ]; then
echo "Running board specific post-install: ${BOARD_POST_INSTALL}"
${BOARD_POST_INSTALL} || exit 1
fi
}
# Echoes "on" or "off" based on the value of a crossystem Boolean flag.
crossystem_on_or_off() {
local value
if value="$(crossystem "$1" 2>/dev/null)"; then
case "$value" in
"0")
echo off
;;
"1")
echo on
;;
*)
echo "$value"
;;
esac
else
echo "(unknown)"
fi
}
# Echoes "yes" or "no" based on a Boolean argument (0 or 1).
bool_to_yes_or_no() {
[ "$1" = 1 ] && echo yes || echo no
}
# Prints a header (a title, plus all the info in print_device_info)
print_header() {
colorize boldwhite
echo CrOS Factory Shim
colorize white
echo -----------------
print_device_info
}
# Prints various information about the device.
print_device_info() {
echo "Factory shim version: $(findLSBValue CHROMEOS_RELEASE_DESCRIPTION)"
local bios_version="$(crossystem ro_fwid 2>/dev/null)"
echo "BIOS version: ${bios_version:-(unknown)}"
for type in RO RW; do
echo -n "EC $type version: "
ectool version | grep "^$type version" | sed -e 's/[^:]*: *//'
done
echo
echo System time: "$(date)"
local hwid="$(crossystem hwid 2>/dev/null)"
echo "HWID: ${hwid:-(not set)}"
echo -n "Dev mode: $(crossystem_on_or_off devsw_boot); "
echo -n "Recovery mode: $(crossystem_on_or_off recoverysw_boot); "
echo -n "HW write protect: $(crossystem_on_or_off wpsw_boot); "
echo "SW write protect: $(crossystem_on_or_off sw_wpsw_boot)"
echo -n "EC present: $(bool_to_yes_or_no "$EC_PRESENT"); "
echo "Dev switch present: $(bool_to_yes_or_no "$DEVSW_PRESENT")"
echo
}
# Displays a line in the menu. Used in the menu function.
#
# Args:
# $1: Single-character option name ("I" for install)
# $2: Brief description
# $3: Further explanation
menu_line() {
echo -n " "
colorize boldwhite
echo -n "$1 "
colorize white
printf "%-22s%s\n" "$2" "$3"
}
# Displays a menu, saving the action (one of $SUPPORTED_ACTIONS, always
# lowercase) in the "ACTION" variable. If no valid action is chosen,
# ACTION will be empty.
menu() {
# Clear up terminal
stty sane echo
# Enable cursor (if tput is available)
tput cnorm 2>/dev/null || true
echo
echo
echo Please select an action and press Enter.
echo
menu_line I "Install" "Performs a network or USB install"
menu_line R "Reset" "Performs a factory reset; finalized devices only"
menu_line S "Shell" "Opens bash; available only with developer firmware"
menu_line V "View configuration" "Shows crossystem, VPD, etc."
menu_line Z "Zero (wipe) storage" "Makes device completely unusable"
echo
read -p 'action> ' ACTION
echo
ACTION="$(echo "$ACTION" | tr A-Z a-z)"
if echo "$ACTION" | grep -q "^[$SUPPORTED_ACTIONS]$"; then
return
fi
echo "Invalid action; please select an action from the menu."
ACTION=
}
main() {
if [ "$(id -u)" -ne 0 ]; then
echo "You must run this as root."
exit 1
fi
if [ -n "$RELEASE_ONLY" ]; then
# Change the partition in release only mode
DST_RELEASE_KERNEL_PART=2
DST_RELEASE_PART=3
fi
config_tty
log "Starting Factory Installer."
lightup_screen
load_modules
colorize white
clear
board_pre_install
test_ec_flash_presence
test_devsw_presence
local install_from_omaha="1"
if [ "$(findLSBValue FACTORY_INSTALL_FROM_USB)" = "1" ]; then
install_from_omaha=""
fi
if [ "$(findLSBValue NETBOOT_RAMFS)" = "1" ]; then
NETBOOT_RAMFS="1"
fi
# Check for any configuration overrides.
overrides
# In netboot, automatically perform the I action; but first give the
# user the chance to press any key to display the menu.
if [ "$NETBOOT_RAMFS" = 1 ]; then
clear
print_header
log "Netbooting. Will automatically perform 'install' action."
log "Or press any key to show menu instead..."
local prevent_install=false
local timeout_secs=3
for i in $(seq $timeout_secs -1 1); do
# Read with timeout doesn't reliably work multiple times without
# a subshell.
if ( read -N 1 -p "Press any key within $i sec> " -t 1 ); then
echo
prevent_install=true
break
fi
echo
done
if ! $prevent_install; then
# No key pressed: perform the install action (which should
# never return)
action_i
fi
fi
while true; do
# Display the header and UI.
clear
print_header
menu
if [ -n "$ACTION" ]; then
# Perform the selected action.
action_${ACTION}
fi
colorize white
read -N 1 -p "Press any key to continue> "
done
}
#
# Action handlers
#
# I = Install.
action_i() {
reset_chromeos_device
if [ -n "$install_from_omaha" ]; then
colorize yellow
log "Waiting for ethernet connectivity to install"
while true; do
if [ -n "$NETBOOT_RAMFS" ]; then
# For initramfs network boot, there is no upstart job. We have to
# bring up network interface and get IP address from DHCP on our own.
# The network interface may not be ready, so let's ignore any
# error here.
bringup_network || true
fi
if check_ethernet_status; then
break
else
sleep 1
fi
done
# Check for OMAHA override from tftp server.
override_from_tftp
# TODO(hungte) how to set time in RMA?
set_time || die "Please check if the server is configured correctly."
fi
colorize green
get_dst_drive
prepare_disk
select_board
if [ -n "$install_from_omaha" ]; then
factory_install_omaha
else
factory_install_usb
fi
log "Factory Installer Complete."
factory_on_complete
board_post_install
# Default action after installation: reboot.
trap - EXIT
sleep 3
if [ -n "$NETBOOT_RAMFS" ]; then
# There is no 'shutdown' and 'init' in initramfs.
busybox reboot -f
else
shutdown -r now
fi
# sleep indefinitely to avoid re-spawning rather than shutting down
sleep 1d
}
# R = Factory reset.
action_r() {
if [ -n "$NETBOOT_RAMFS" ]; then
# factory_reset.sh script is not available in netboot mode.
colorize red
log "Not available in netboot."
return
fi
# First check to make sure that the factory software has been wiped.
MOUNT_POINT=/tmp/stateful
mkdir -p /tmp/stateful
get_dst_drive
mount -o ro "$(make_partition_dev $DST_DRIVE $DST_STATE_PART)" "$MOUNT_POINT"
local factory_exists=false
[ -e $MOUNT_POINT/dev_image/factory ] && factory_exists=true
umount "$MOUNT_POINT"
if $factory_exists; then
colorize red
log "Factory software is still installed (device has not been finalized)."
log "Unable to perform factory reset."
return
fi
reset_chromeos_device
factory_reset
}
# S = Shell.
action_s() {
if ! crossystem "mainfw_type?developer" 2>/dev/null; then
colorize red
echo "Developer firmware is not enabled; unable to open a shell."
return
fi
log "Trying to bring up network..."
if bringup_network 2>/dev/null; then
colorize green
log "Network enabled."
colorize white
else
colorize yellow
log "Unable to bring up network (or it's already up). Proceeding anyway."
colorize white
fi
echo Entering shell.
bash
}
# V = View configuration.
action_v() {
(
print_device_info
for partition in RO_VPD RW_VPD; do
echo
echo "$partition contents:"
vpd -i "$partition" -l || true
done
echo
echo "crossystem:"
crossystem || true
echo
echo "lsb-factory:"
cat /mnt/stateful_partition/dev_image/etc/lsb-factory || true
) 2>&1 | secure_less.sh
}
# Z = Zero
action_z() {
if [ -n "$NETBOOT_RAMFS" ]; then
# factory_reset.sh script is not available in netboot mode.
colorize red
log "Not available in netboot."
return
fi
colorize red
echo "!!"
echo "!! You are about to wipe the entire internal disk."
echo "!! After this, the device will not boot anymore, and you"
echo "!! need a recovery USB disk to bring it back to life."
echo "!!"
echo "!! Type 'yes' to do this, or anything else to cancel."
echo "!!"
colorize white
local yes_or_no
read -p "Wipe the internal disk? (yes/no)> " yes_or_no
if [ "$yes_or_no" = yes ]; then
factory_disk_wipe
else
echo "You did not type 'yes'. Cancelled."
fi
}
main "$@"