blob: a0499d5abf776f9f70d1816f94da627a036c1c3c [file] [log] [blame]
#!/bin/bash
# Copyright 2015 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
umask 022
# Dump bash trace to default log file.
: "${LOG_FILE:=/var/log/factory_install.log}"
: "${LOG_TTY:=}"
if [ -n "${LOG_FILE}" ]; then
mkdir -p "$(dirname "${LOG_FILE}")"
# shellcheck disable=SC2093 disable=SC1083
exec {LOG_FD}>>"${LOG_FILE}"
export BASH_XTRACEFD="${LOG_FD}"
fi
. "/usr/share/misc/storage-info-common.sh"
# Include after other common include, side effect on GPT variable.
. "/usr/sbin/write_gpt.sh"
. "$(dirname "$0")/factory_common.sh"
. "$(dirname "$0")/factory_cros_payload.sh"
. "$(dirname "$0")/factory_tpm.sh"
normalize_server_url() {
local removed_protocol="${1#http://}"
local removed_path="${removed_protocol%%/*}"
echo "http://${removed_path}"
}
# Variables from dev_image/etc/lsb-factory that developers may want to override.
#
# - Override this if we want to install with a board different from installer
BOARD="$(findLSBValue CHROMEOS_RELEASE_BOARD)"
# - Override this if we want to install with a different factory server.
OMAHA="$(normalize_server_url "$(findLSBValue CHROMEOS_AUSERVER)")"
# - Override this for the default action if no keys are pressed before timeout.
DEFAULT_ACTION="$(findLSBValue FACTORY_INSTALL_DEFAULT_ACTION)"
# - Override this to enable/disable the countdown before doing default action.
ACTION_COUNTDOWN="$(findLSBValue FACTORY_INSTALL_ACTION_COUNTDOWN)"
# - Override this to 'false' to prevent waiting for prompt after installation.
COMPLETE_PROMPT="$(findLSBValue FACTORY_INSTALL_COMPLETE_PROMPT |
tr 'A-Z' 'a-z')"
# Variables prepared by image_tool or netboot initramfs code.
NETBOOT_RAMFS="$(findLSBValue NETBOOT_RAMFS)"
FACTORY_INSTALL_FROM_USB="$(findLSBValue FACTORY_INSTALL_FROM_USB)"
RMA_AUTORUN="$(findLSBValue RMA_AUTORUN)"
# Global variables
DST_DRIVE=""
EC_PRESENT=0
COMPLETE_SCRIPT=""
# The ethernet interface to be used. It will be determined in
# check_ethernet_status and be passed to cros_payload_get_server_json_path as an
# environment variable.
ETH_INTERFACE=""
# Definition of ChromeOS partition layout
DST_FACTORY_KERNEL_PART=2
DST_FACTORY_ROOTFS_PART=3
DST_RELEASE_KERNEL_PART=4
DST_RELEASE_ROOTFS_PART=5
DST_STATE_PART=1
# Supported actions (a set of lowercase characters)
# 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.
SUPPORTED_ACTIONS=bcdeimrstuvyz
SUPPORTED_ACTIONS_BOARD=
# Supported actions when RSU is required.
# We only support [e] RMA unlock to do RSU, and [u] Update Cr50 in case a device
# has an older Cr50 version that doesn't support RSU.
SUPPORTED_ACTIONS_RSU_REQUIRED=eu
# These two arrays are shared b/w get_dlc_image_from_dlc_factory_cach and
# verify_dlc_hash.
DLC_IMG_PATHS=()
DLC_IDS=()
DLC_COUNT=0
TOTAL_DLC_PREALLOCATE_SIZE=0
# Define our own logging function.
log() {
echo "$*"
}
# Change color 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
}
# Checks if 'cros_debug' is enabled.
is_allow_debug() {
grep -qw "cros_debug" /proc/cmdline
}
# Checks if complete prompt is disabled.
is_complete_prompt_disabled() {
grep -qw "nocompleteprompt" /proc/cmdline
}
# Checks if the system has been boot by Ctrl-U.
is_dev_firmware() {
crossystem "mainfw_type?developer" 2>/dev/null
}
is_netboot() {
grep -qw cros_netboot /proc/cmdline
}
explain_cros_debug() {
log "To debug with a shell, boot factory shim in developer firmware (Ctrl-U)
or add 'cros_debug' to your kernel command line:
- Factory shim: Add cros_debug into --boot_args or make_dev_ssd
- Netboot: Change kernel command line config file on TFTP."
}
# Error message for any unexpected error.
on_die() {
set +x
kill_bg_jobs
colorize red
echo
log "ERROR: Factory installation has been stopped."
if [ -n "${LOG_TTY}" ]; then
local tty_num="${LOG_TTY##*[^0-9]}"
log "See ${LOG_TTY} (Ctrl-Alt-F${tty_num}) for detailed information."
log "(The F${tty_num} key is ${tty_num} keys to the right of 'esc' key.)"
else
log "See ${LOG_FILE} for detailed information."
fi
# Open a terminal if the kernel command line allows debugging.
if is_allow_debug; then
while true; do sh; done
else
explain_cros_debug
fi
colorize white
read -N 1 -p "Press any key to reboot... "
reboot
exit 1
}
kill_bg_jobs() {
local 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
}
is_rsu_required() {
log "Waiting for UserDataAuth service"
gdbus wait --system org.chromium.UserDataAuth
log "Checking if RSU is required."
local flags
local FWMP_DEV_DISABLE_CCD_UNLOCK="0x40"
flags="$(cryptohome --action=get_firmware_management_parameters |
grep -o "flags=0x[0-9a-f]\+" | cut -d = -f 2)"
# FWMP_DEV_DISABLE_CCD_UNLOCK (0x40) is set during enterprise enrollment if
# dev mode is blocked by the policy. We can use this flag to determine whether
# RSU required.
#
# According to platform/cr50/board/cr50/factory_mode.c, Cr50 allows factory
# mode when the following conditions are met:
# - HWWP signal is deasserted (battery disconnected, or pins shorted)
# - FWMP allows CCD unlock. FWMP_DEV_DISABLE_CCD_UNLOCK (0x40) is not set
# - CCD password is not set
#
# Assuming that CCD password is not set, if FWMP_DEV_DISABLE_CCD_UNLOCK is not
# set, RSU is not required and we can enter factory mode by disconnecting the
# battery or shorting pins.
if [ -z "${flags}" ] ||
[ $(( flags & FWMP_DEV_DISABLE_CCD_UNLOCK )) -eq 0 ]; then
return 1
fi
return 0
}
# Checks if hardware write protection is enabled.
check_hwwp() {
crossystem 'wpsw_cur?1' 2>/dev/null
}
# Checks if firmware software write protection is enabled.
# Args
# target: a "flashrom -p" descriptor. Defaults to "host".
check_fw_swwp() {
local target="${1:-host}"
if [ "${target}" = "ec" ] && [ "${EC_PRESENT}" -eq 0 ]; then
echo "There is no EC, skip checking EC SWWP"
return 0
fi
flashrom -p "${target}" --wp-status 2>/dev/null |
grep -q "write protect is enabled"
}
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 --wp-range 0,0
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 "Inconsistent firmware write protection status: " \
"main=${main_wp}, ec=${ec_wp}." \
"Please disable Hardware Write Protection and restart again."
fi
}
is_pvt_phase() {
# If there is any error then pvt phase is returned as a safer default
# option.
local bid_flags="0x$(tpm_get_board_id_flags)"
# If board phase is not 0x0 then this is a development board.
local pre_pvt=$(( bid_flags & 0x7F ))
if (( pre_pvt > 0 )); then
return ${pre_pvt}
fi
return 0
}
config_tty() {
stty opost
# Turn off VESA blanking
setterm -blank 0 -powersave off -powerdown 0
}
echo_huge_ok() {
cat <<HERE
###### ###### ######
################## ###### ######
#################### ###### ######
######### ######### ###### ######
######## ######## ###### ######
####### ####### ###### ######
###### ###### ###### ######
###### ###### ###### ######
###### ###### ###### ######
###### ###### ############
###### ###### ############
###### ###### ############
###### ###### ###### ######
###### ###### ###### ######
###### ###### ###### ######
####### ####### ###### ######
######## ######## ###### ######
######### ######### ###### ######
#################### ###### ######
################## ###### ######
###### ###### ######
HERE
}
set_time() {
log "Setting time from:"
# Extract only the server and port.
local time_server_port="${OMAHA#http://}"
log " Server ${time_server_port}."
local result="$(htpdate -s -t "${time_server_port}" 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
local result=1
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)"
ETH_INTERFACE=${i}
result=0
fi
fi
done
return ${result}
}
clear_block_devmode() {
# Try our best to clear block_devmode.
crossystem block_devmode=0 || true
vpd -i RW_VPD -d block_devmode -d check_enrollment || true
}
reset_chromeos_device() {
if [[ -e /dev/nvram ]]; then
log "Clearing NVData."
dd if=/dev/zero of=/dev/nvram bs=1 count="$(wc -c </dev/nvram)" \
status=none conv=nocreat || log "Failed to clear NVData (non-critical)."
fi
clear_block_devmode
if is_netboot; 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 "Request to clear TPM owner at next boot."
# No matter if whole TPM (see below) is cleared or not, we always
# want to clear TPM ownership (safe and easy) so factory test program and
# release image won't start with unknown ownership.
crossystem clear_tpm_owner_request=1 || true
log "Checking if TPM should be recovered (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
if ! chromeos-tpm-recovery; then
colorize yellow
log " - TPM recovery failed.
This is usually not a problem for devices on manufacturing line,
but if you are using factory shim to reset TPM (for antirollback issue),
there's something wrong.
"
sleep 3
else
log "TPM recovered."
fi
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
}
get_dst_drive() {
load_base_vars
DST_DRIVE="$(get_fixed_dst_drive)"
if [ -z "${DST_DRIVE}" ]; then
die "Cannot find fixed drive."
fi
}
# In some envs (such as netboot's initramfs), backlight_tool is not available,
# so if that fails we fallback to controlling backlight brightness via
# entries under /sys/class/backlight/.
set_brightness_percentage() {
local backlight_device
local max_brightness
local desired_brightness
local percentage="$1"
if backlight_tool --set_brightness_percent="${percentage}"; then
return
fi
# We set all backlight devices to the desired brightness percentage.
for backlight_device in /sys/class/backlight/*; do
# In order to set brightness to specific percentage, we do the following:
# - Turn the backlight device on (using bl_power).
# - Read max_brightness and scale that into the desired brightness.
# - Write the desired brightness to the brightness entry.
echo 0 > "${backlight_device}/bl_power" # 0 = FB_BLANK_UNBLANK (i.e.: on)
max_brightness="$(cat "${backlight_device}/max_brightness")"
desired_brightness=$((max_brightness * percentage / 100))
echo "${desired_brightness}" > "${backlight_device}/brightness"
done
}
lightup_screen() {
# Always backlight on factory install shim.
ectool forcelidopen 1 || true
# Before we try to light up the screen, we set the brightness to another one
# in case we can't set the brightness. Since we set the brightness via the
# /sys/class/../brightness, the entry is only used to set the brightness and
# does not reflect the actual brightness change (brightness may be different
# from actual_brightness). Therefore, if the entry value is already the value
# we want to set, we should first set the value to another value, otherwise
# the value will not be set eventually.
# Please refer to b/203831384 for more information.
set_brightness_percentage 80 || true
# Light up screen in case you can't see our splash image.
set_brightness_percentage 100 || true
}
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."
write_base_table "${DST_DRIVE}" "${pmbr_code}" 2>&1
reload_partitions "${DST_DRIVE}"
log "Done preparing disk"
}
ufs_init() {
local ufs_init_bin="/usr/sbin/factory_ufs"
if [ -x "${ufs_init_bin}" ]; then
${ufs_init_bin}
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"
# shellcheck disable=SC2013
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.
local omaha=""
if omaha="$(find_var omahaserver)"; then
OMAHA="$(normalize_server_url "${omaha}")"
log " Kernel cmdline OMAHA override to ${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.
# The file should be installed in factory-board/files/installer/usr/sbin/.
local lastboard="${BOARD}"
local board_customize_file="/usr/sbin/factory_install_board.sh"
if [ -f "${board_customize_file}" ]; then
. "${board_customize_file}"
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.
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="$(normalize_server_url "$(cat "${tftp_output}")")"
log "override_from_tftp: OMAHA override to ${OMAHA}"
fi
fi
}
overrides() {
override_from_firmware
override_from_board
}
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.
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
# cgpt changed partition table, so we have to make sure it's notified.
reload_partitions "${DST_DRIVE}"
}
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}
}
stateful_postinst() {
local stateful_dev="$1"
local mount_point="$(mktemp -d)"
mount "${stateful_dev}" "${mount_point}"
mkdir -p "$(dirname "${output_file}")"
# Update lsb-factory on stateful partition.
local lsb_factory="${mount_point}/dev_image/etc/lsb-factory"
if [ -z "${FACTORY_INSTALL_FROM_USB}" ]; then
log "Save active factory server URL to stateful partition lsb-factory."
echo "FACTORY_OMAHA_URL=${OMAHA}" >>"${lsb_factory}"
else
log "Clone lsb-factory to stateful partition."
cat "${LSB_FACTORY_FILE}" >>"${lsb_factory}"
fi
umount "${mount_point}" || true
rmdir "${mount_point}" || true
}
omaha_greetings() {
if [ -n "${FACTORY_INSTALL_FROM_USB}" ]; then
return
fi
local message="$1"
local uuid="$2"
curl "${OMAHA}/greetings/${message}/${uuid}" >/dev/null 2>&1 || true
}
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."
}
factory_reset() {
crossystem disable_dev_request=1
log "Performing factory reset"
if ! /usr/sbin/factory_reset.sh "${DST_DRIVE}"; then
die "Factory reset failed."
fi
log "Done."
# TODO(hungte) shutdown or reboot once we decide the default behavior.
exit_success
}
# Call reset code on the fixed driver.
#
# Assume the global variable DST_DRIVE contains the drive to operate on.
#
# Args:
# action: describe how to erase the drive.
# Allowed actions:
# - wipe: action Z
# - secure: action C
# - verify: action Y
factory_disk_action() {
local action="$1"
log "Performing factory disk ${action}"
if ! /usr/sbin/factory_reset.sh "${DST_DRIVE}" "${action}"; then
die "Factory disk ${action} failed."
fi
log "Done."
exit_success
}
enlarge_partition() {
local dev="$1"
local block_size="$(dumpe2fs -h "${dev}" | sed -n 's/Block size: *//p')"
local minimal="$(resize2fs -P "${dev}" | sed -n 's/Estimated .*: //p')"
local test_rootfs_mount_point test_rootfs_dev
test_rootfs_mount_point="$(mktemp -d)"
test_rootfs_dev="$(make_partition_dev "${DST_DRIVE}" \
"${DST_FACTORY_ROOTFS_PART}")"
mount -o ro "${test_rootfs_dev}" "${test_rootfs_mount_point}"
get_factory_installed_dlc_cnt_and_size "${test_rootfs_mount_point}"
umount "${test_rootfs_mount_point}"
rmdir "${test_rootfs_mount_point}"
if [ "${minimal}" -gt 0 ] && [ "${block_size}" -gt 0 ]; then
e2fsck -f -y "${dev}"
# DLC images will be installed to stateful partition in run time.
# We need to preserve space for them.
log "Allocate ${TOTAL_DLC_PREALLOCATE_SIZE} bytes for DLCs."
local dlc_size=$((TOTAL_DLC_PREALLOCATE_SIZE / block_size))
# Try to allocate 2G if possible for factory toolkit + run time overhead.
local reserve=$((1024 * 2097152 / block_size))
resize2fs "${dev}" "$((minimal + reserve + dlc_size))" || true
fi
}
reload_partitions() {
# Some devices, for example NVMe, may need extra time to update block device
# files via udev. We should do sync, partprobe, and then wait until partition
# device files appear again.
local drive="$1"
log "Reloading partition table changes..."
sync
# Reference: src/platform2/installer/chromeos-install#reload_partitions
udevadm settle || true # Netboot environment may not have udev.
for delay in 0 1 2 4 8; do
sleep "${delay}"
blockdev --rereadpt "${drive}" && return ||
log "Failed to reload partitions on ${drive}"
done
die "Continually failed to reload partitions on ${drive}"
}
cros_payload_get_server_json_path() {
local server_url="$1"
local eth_interface="$2"
# Try to get resource map from Umpire.
local sn="$(vpd -i RO_VPD -g serial_number)" || sn=""
local mlb_sn="$(vpd -i RO_VPD -g mlb_serial_number)" || mlb_sn=""
local mac_addr="$(ip link show ${eth_interface} | grep link/ether |
tr -s ' '| cut -d ' ' -f 3)"
local resourcemap=""
local mac="mac.${eth_interface}=${mac_addr};"
local values="sn=${sn}; mlb_sn=${mlb_sn}; board=${BOARD}; ${mac}"
local empty_values="firmware=; ec=; stage=;"
local header="X-Umpire-DUT: ${values} ${empty_values}"
local target="${server_url}/resourcemap"
# This is following Factory Server/Umpire protocol.
echo "Header: ${header}" >&2
resourcemap="$(curl -f --header "${header}" "${target}")"
if [ -z "${resourcemap}" ]; then
echo "Missing /resourcemap - please upgrade Factory Server." >&2
return 1
fi
echo "resourcemap: ${resourcemap}" >&2
# Check if multicast config exists
local json_name="$(echo "${resourcemap}" | grep "^multicast: " |
cut -d ' ' -f 2)"
if [ -z "${json_name}" ]; then
# Multicast config not found. Fallback to normal payload config.
json_name="$(echo "${resourcemap}" | grep "^payloads: " | cut -d ' ' -f 2)"
fi
if [ -n "${json_name}" ]; then
echo "res/${json_name}"
else
echo "'payloads' not in resourcemap, please upgrade Factory Server." >&2
return 1
fi
}
factory_install_cros_payload() {
local src_media="$1"
local json_path="$2"
local dlc_path="$3"
local src_mount=""
local tmp_dir="$(mktemp -d)"
local json_url="${src_media}/${json_path}"
if [ -b "${src_media}" ]; then
src_mount="$(mktemp -d)"
colorize yellow
mount -o ro "${src_media}" "${src_mount}"
json_url="${src_mount}/${json_path}"
fi
# Generate the uuid for current install session
local uuid="$(uuidgen 2>/dev/null)" || uuid="Not_Applicable"
# Say hello to server if available.
omaha_greetings "hello" "${uuid}"
if [ -n "${dlc_path}" ]; then
log "Installation will fail if factory server version is less than" \
"20211102181209!"
cros_payload install "${json_url}" "${dlc_path}" \
release_image.dlc_factory_cache
else
cros_payload install "${json_url}" "${DST_DRIVE}" test_image release_image
# Test image stateful partition may be pretty full and we may want more
# space, before installing toolkit (which may be huge).
enlarge_partition "$(make_partition_dev "${DST_DRIVE}" "${DST_STATE_PART}")"
cros_payload install "${json_url}" "${DST_DRIVE}" toolkit
# Install optional components.
cros_payload install_optional "${json_url}" "${DST_DRIVE}" \
release_image.crx_cache release_image.dlc_factory_cache hwid \
toolkit_config project_config
cros_payload install_optional "${json_url}" "${tmp_dir}" firmware complete
fi
if [ -n "${src_mount}" ]; then
umount "${src_mount}"
fi
colorize green
# Notify server that all downloads are completed.
omaha_greetings "download_complete" "${uuid}"
if [ -n "${dlc_path}" ]; then
return
fi
# Disable release partition and activate factory partition
disable_release_partition
run_postinst "$(make_partition_dev "${DST_DRIVE}" \
"${DST_FACTORY_ROOTFS_PART}")" || die "Failed running postinst script."
stateful_postinst "$(make_partition_dev "${DST_DRIVE}" "${DST_STATE_PART}")"
if [ -s "${tmp_dir}/firmware" ]; then
log "Found firmware updater."
# TODO(hungte) Check if we need to run --mode=recovery if WP is enabled.
sh "${tmp_dir}/firmware" --force --mode=factory_install ||
die "Firmware updating failed."
fi
if [ -s "${tmp_dir}/complete" ]; then
log "Found completion script."
COMPLETE_SCRIPT="${tmp_dir}/complete"
fi
# After post processing, notify server a installation session has been
# successfully completed.
omaha_greetings "goodbye" "${uuid}"
}
get_usb_dev_stateful() {
local src_dev
src_dev="$(findLSBValue REAL_USB_DEV)"
[ -n "${src_dev}" ] || src_dev="$(rootdev -s 2>/dev/null)"
[ -n "${src_dev}" ] ||
die "Unknown media source. Please define REAL_USB_DEV."
# shellcheck disable=SC2001
echo "${src_dev}" | sed 's/[0-9]\+$/1/'
}
factory_install_usb() {
local dlc_path stateful_dev mount_point json_path
dlc_path="$1"
stateful_dev="$(get_usb_dev_stateful)"
# Switch to stateful partition.
mount_point="$(mktemp -d)"
mount -o ro "${stateful_dev}" "${mount_point}"
json_path="$(cros_payload_metadata "${mount_point}")"
umount "${stateful_dev}"
rmdir "${mount_point}"
if [ -n "${json_path}" ]; then
factory_install_cros_payload "${stateful_dev}" "${json_path}" \
"${dlc_path}"
else
die "Cannot find cros_payload metadata."
fi
}
connect_to_ethernet() {
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
}
factory_install_network() {
local dlc_path="$1"
# Register to Overlord if haven't.
if [ -z "${OVERLORD_READY}" ]; then
register_to_overlord "${OMAHA}" "${TTY_FILE}" "${LOG_FILE}"
fi
# Get path of cros_payload json file from server (Umpire or Mini-Omaha).
local json_path
json_path="$(cros_payload_get_server_json_path \
"${OMAHA}" "${ETH_INTERFACE}" 2>/dev/null)"
[ -n "${json_path}" ] || die "Failed to get payload json path from server."
factory_install_cros_payload "${OMAHA}" "${json_path}" "${dlc_path}"
}
# Get the total number of factory installed DLC images and their pre-allocated
# size from `/opt/google/dlc` of rootfs.
get_factory_installed_dlc_cnt_and_size() {
local rootfs_mount_point="$1"
for d in "${rootfs_mount_point}"/opt/google/dlc/* ; do
# Read the metadata.
local metadata="${d}"/package/imageloader.json
if [ ! -f "${metadata}" ]; then
continue
fi
# A DLC is factory installed DLC if `factory_install` is true.
if [ "$(jq '."factory-install"' "${metadata}")" = "true" ]; then
DLC_COUNT=$((DLC_COUNT + 1))
# The run time overhead of factory installed DLC images is
# `2 * pre-allocated-size`. We need to allocate additional space for
# this in `enlarge_partition`.
TOTAL_DLC_PREALLOCATE_SIZE=$((TOTAL_DLC_PREALLOCATE_SIZE + \
$(jq -r '."pre-allocated-size"' "${metadata}") * 2))
fi
done
}
get_dlc_image_from_dlc_factory_cache() {
local dlc_factory_cache_dir="$1"
for d in "${dlc_factory_cache_dir}"/* ; do
local image_path="${d}"/package/dlc.img
if [ -f "${image_path}" ]; then
DLC_IMG_PATHS+=( "${image_path}" )
DLC_IDS+=( "$(basename "${d}")" )
fi
done
}
# Verify the DLC hash by calling `dlcverify`.
verify_dlc_hash() {
local release_rootfs_mount_point="$1"
log "DLCs to be verified: ${DLC_IDS[*]}"
# `dlcverify` locates at stateful partition of the shim.
local stateful_dev mount_point
stateful_dev="$(get_usb_dev_stateful)"
mount_point="$(mktemp -d)"
mount -o ro "${stateful_dev}" "${mount_point}"
local dlcverify_bin="${mount_point}"/dev_image/bin/dlcverify
for index in ${!DLC_IMG_PATHS[*]}; do
local verify_ret
verify_ret="$("${dlcverify_bin}" --id="${DLC_IDS[${index}]}" \
--image="${DLC_IMG_PATHS[${index}]}" \
--rootfs_mount="${release_rootfs_mount_point}")"
log "${verify_ret}"
if echo "${verify_ret}" | grep -q ERROR; then
die "DLC verify failed. The factory installed DLC images should be" \
"extracted from the same release image on the DUT."
fi
done
log "Verification succeeded!"
umount "${stateful_dev}"
rmdir "${mount_point}"
}
gbb_force_dev_mode() {
# Set factory-friendly gbb flags 0x39, which contains
# VB2_GBB_FLAG_DEV_SCREEN_SHORT_DELAY 0x00000001
# VB2_GBB_FLAG_FORCE_DEV_SWITCH_ON 0x00000008
# VB2_GBB_FLAG_FORCE_DEV_BOOT_USB 0x00000010
# VB2_GBB_FLAG_DISABLE_FW_ROLLBACK_CHECK 0x00000020
flashrom -p host --wp-disable --wp-range 0,0 > /dev/null 2>&1
local tmp_file cur_flags new_flags
tmp_file="$(mktemp)"
flashrom -p host -i GBB -r "${tmp_file}"
cur_flags="$(futility gbb -g --flags "${tmp_file}")" # flags: %#x
new_flags=$(( "${cur_flags#flags: }" | 0x39))
futility gbb -s --flags "${new_flags}" "${tmp_file}"
flashrom -p host -i GBB -w "${tmp_file}"
rm "${tmp_file}"
}
test_ec_flash_presence() {
# If "flashrom -p ec --flash-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 --flash-size >/dev/null 2>&1; then
EC_PRESENT=1
else
EC_PRESENT=0
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
}
command_to_yes_or_no() {
"$@" >/dev/null 2>&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_cur); "
echo "SW write protect: $(command_to_yes_or_no check_fw_swwp host)"
echo -n "EC present: $(bool_to_yes_or_no "${EC_PRESENT}"); "
if [ "${EC_PRESENT}" -eq 1 ]; then
echo -n "EC SW write protect: $(command_to_yes_or_no check_fw_swwp ec); "
fi
echo -n "$(tpm_get_info)"
echo
local description=""
if [ -s "${DESCRIPTION_FILE}" ]; then
description="$(cat "${DESCRIPTION_FILE}" 2>/dev/null)"
echo "${description}"
echo
fi
}
print_rma_info() {
local stateful_dev mount_point cros_payload_path rma_metadata_path
stateful_dev="$(get_usb_dev_stateful)"
mount_point="$(mktemp -d)"
mount -o ro "${stateful_dev}" "${mount_point}"
cros_payload_path="${mount_point}"/cros_payloads
rma_metadata_path="${cros_payload_path}"/rma_metadata.json
if [ -f "${rma_metadata_path}" ]; then
echo "RMA shim contains payloads from board:"
jq -r '.[].board' "${rma_metadata_path}"
local model
model="$(cros_config / name)" || true
echo "Board name: ${BOARD}"
echo "Model Name: ${model}"
if [ -f "${cros_payload_path}/${model}".json ]; then
echo "Find corresponding RMA payloads by model name: ${model}"
elif [ -f "${cros_payload_path}/${BOARD}".json ]; then
echo "Find corresponding RMA payloads by board name: ${BOARD}"
else
echo "No payloads found for ${model} or ${BOARD}."
echo "Did you set the wrong model/board name?"
fi
else
echo "Skip checking RMA payloads since it is not a RMA shim."
fi
umount "${mount_point}"
rmdir "${mount_point}"
}
# 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"
}
# Checks if the given action is valid and supported.
is_valid_action() {
echo "$1" | grep -q "^[${SUPPORTED_ACTIONS}${SUPPORTED_ACTIONS_BOARD}]$"
}
# Checks if the given action is valid and supported if RSU is required.
is_valid_action_when_rsu_required() {
echo "$1" | grep -q "^[${SUPPORTED_ACTIONS_RSU_REQUIRED}]$"
}
# Virtual function to show menu of board-specific actions.
menu_board() {
return 0
}
# Displays a menu, saving the action (one of ${SUPPORTED_ACTIONS} or
# ${SUPPORTED_ACTIONS_BOARD}, 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 B "Battery cutoff" "Performs a battery cutoff"
menu_line S "Shell" "Opens bash; available only with developer firmware"
menu_line V "View configuration" "Shows crossystem, VPD, etc."
menu_line D "Debug info and logs" \
"Shows useful debugging information and kernel/firmware logs"
menu_line Z "Zero (wipe) storage" "Makes device completely unusable"
menu_line C "SeCure erase" \
"Performs full storage erase, write a verification pattern"
menu_line Y "VerifY erase" \
"Verifies the storage has been erased with option C"
menu_line T "Reset TPM" "Call chromeos-tpm-recovery"
menu_line U "Update TPM firmware" "Update TPM firmware"
menu_line E "Perform RSU" "Perform RSU (RMA Server Unlock)"
menu_line M "Enable factory mode" "Enable TPM factory mode"
menu_board
echo
read -p 'action> ' ACTION
echo
# busybox tr may not have '[:upper:]'.
# shellcheck disable=SC2019 disable=SC2018
ACTION="$(echo "${ACTION}" | tr 'A-Z' 'a-z')"
if is_valid_action "${ACTION}"; then
return
fi
echo "Invalid action; please select an action from the menu."
ACTION=
}
#
# Action handlers
#
# I = Install.
action_i() {
reset_chromeos_device
log "Checking for Firmware Write Protect"
# Check for physical firmware write protect. We'll only
# clear this stuff if the case is open.
if ! check_hwwp; then
# Clear software firmware write protect.
clear_fwwp
fi
ensure_fwwp_consistency
# `print_rma_info` prints the information about the usb shim.
# Skip printing when using netboot.
if ! is_netboot; then
print_rma_info
fi
if [ -z "${FACTORY_INSTALL_FROM_USB}" ]; then
colorize yellow
connect_to_ethernet
# Check for factory server 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
ufs_init
get_dst_drive
prepare_disk
if [ -n "${FACTORY_INSTALL_FROM_USB}" ]; then
factory_install_usb
else
factory_install_network
fi
sync
# Some installation procedure may clear or reset NVdata, so we want to ensure
# TPM will be cleared again.
crossystem clear_tpm_owner_request=1 || true
# The gbb flag which forces the dev switch on (0x8) is set when
# (1) installing the firmware-updater and (2) doing RSU using cr50-reset.sh.
# However, if the factory bundle does not contain firmware-updater and at the
# same time the hwwp is disabled via removing battery + action_m, then the
# gbb flag will not be set. Therefore, after installation, the DUT will try
# to boot into test image under normal mode. This results in 0x43 (see
# b/199803466 for more info.) Though user can enable developer mode and boot
# into test image, we decide to make it more user-friendly by setting the
# gbb flag here.
log "Setting user-friendly gbb flags 0x39..."
gbb_force_dev_mode
colorize green
echo_huge_ok
log "Factory Installer Complete."
sync
factory_on_complete
# Both kernel command line and lsb-factory can disable complete prompt.
if is_complete_prompt_disabled || [ "${COMPLETE_PROMPT}" = "false" ] \
|| [ "${TTY}" = /dev/null ]; then
sleep 3
else
printf "Press Enter to restart... "
head -c 1 >/dev/null
fi
# Default action after installation: reboot.
trap - EXIT
# TPM factory mode can only be enabled when hardware write protection is
# disabled. Assume we only do netboot in factory, so that in netboot
# environment we don't need to enable factory mode because the device should
# already be in factory mode.
# TODO(chenghan) Figure out the use case of netboot besides factory process.
if [ -z "${NETBOOT_RAMFS}" ] && ! check_hwwp; then
# Enable factory mode if it's supported.
# Enabling factory mode would trigger a reboot automatically and be halt
# inside this function until reboots.
tpm_enable_factory_mode
fi
# Try to do EC reboot. If it fails, do normal reboot.
if [ -n "${NETBOOT_RAMFS}" ]; then
# There is no 'shutdown' and 'init' in initramfs.
ectool reboot_ec cold at-shutdown && busybox poweroff -f ||
busybox reboot -f
else
ectool reboot_ec cold at-shutdown && shutdown -h now || 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.
local state_mount_point state_dev
state_mount_point=/tmp/stateful
mkdir -p /tmp/stateful
get_dst_drive
state_dev="$(make_partition_dev "${DST_DRIVE}" "${DST_STATE_PART}")"
state_dev="$(get_path_to_lvm_stateful "${state_dev}")"
log "Device path to stateful partition: ${state_dev}"
# Mount as rw since we might need to copy factory installed DLC images
# to the stateful partition.
mount -o rw "${state_dev}" "${state_mount_point}"
local factory_exists=false
[ -e "${state_mount_point}"/dev_image/factory ] && factory_exists=true
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
check_fw_swwp host && check_fw_swwp ec || ! is_pvt_phase || \
die "SW write protect is not enabled in the device with PVT phase."
# Then check if the release rootfs has factory installed DLC metadata.
# If so, we copy the DLC images from network or RMA shim.
local release_rootfs_mount_point release_dev
release_rootfs_mount_point=/tmp/release_rootfs
mkdir -p "${release_rootfs_mount_point}"
release_dev="$(make_partition_dev "${DST_DRIVE}" \
"${DST_RELEASE_ROOTFS_PART}")"
mount -o ro "${release_dev}" "${release_rootfs_mount_point}"
get_factory_installed_dlc_cnt_and_size "${release_rootfs_mount_point}"
log "Number of factory installed DLC metadata: ${DLC_COUNT}"
if [ "${DLC_COUNT}" != 0 ]; then
# Install the factory installed DLC images to dlc_path.
local dlc_path=/tmp/dlc
mkdir -p "${dlc_path}"
log "Get factory installed DLC images from usb/network..."
if [ -n "${FACTORY_INSTALL_FROM_USB}" ]; then
factory_install_usb "${dlc_path}"
else
connect_to_ethernet
factory_install_network "${dlc_path}"
fi
# Unzip the DLC and verify their hashes.
log "Unpack factory installed DLC images to ${dlc_path}"
tar -xpvf "${dlc_path}"/release_image.dlc_factory_cache -C "${dlc_path}"
get_dlc_image_from_dlc_factory_cache \
"${dlc_path}"/unencrypted/dlc-factory-images
if [ "${DLC_COUNT}" != "${#DLC_IMG_PATHS[@]}" ]; then
die "Expected to have ${DLC_COUNT} DLCs. Current: ${#DLC_IMG_PATHS[@]}."
fi
verify_dlc_hash "${release_rootfs_mount_point}"
log "Remove existing factory installed DLC images on stateful partition..."
local dlc_stateful="${state_mount_point}"/unencrypted/dlc-factory-images
if [ -d "${dlc_stateful}" ]; then
rm -rf "${dlc_stateful}"
fi
log "Install the DLCs to stateful partition..."
mv "${dlc_path}"/unencrypted/dlc-factory-images \
"${state_mount_point}"/unencrypted
else
log "No factory installed DLC metadata found. Skip copying..."
fi
umount "${state_mount_point}"
reset_chromeos_device
factory_reset
}
# B = Battery cutoff.
action_b() {
crossystem disable_dev_request=1
/usr/share/cutoff/cutoff.sh
}
# S = Shell.
action_s() {
if ! is_allow_debug && ! is_dev_firmware; then
colorize red
echo "Cannot open a shell (need devfw [Ctrl-U] or cros_debug build)."
explain_cros_debug
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 || true
}
# 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
}
# D = Debug info and logs.
action_d() {
/usr/sbin/factory_debug.sh
}
# Confirm and erase the fixed drive.
#
# Identify the fixed drive, ask confirmation and call
# factory_disk_action function.
#
# Args:
# action: describe how to erase the drive.
erase_drive() {
local action="$1"
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
get_dst_drive
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_action "${action}"
else
echo "You did not type 'yes'. Cancelled."
fi
}
# Z = Zero
action_z() {
erase_drive wipe
}
# C = SeCure
action_c() {
erase_drive secure
}
# Y = VerifY
action_y() {
if [ -n "${NETBOOT_RAMFS}" ]; then
# factory_reset.sh script is not available in netboot mode.
colorize red
log "Not available in netboot."
return
fi
get_dst_drive
factory_disk_action verify
}
# T = Reset TPM
action_t() {
chromeos-tpm-recovery
}
# U = Update TPM firmware
action_u() {
tpm_update_firmware
}
# E = Perform RSU
action_e() {
tpm_perform_rsu
}
# M = Enable TPM factory mode
action_m() {
tpm_enable_factory_mode
}
try_set_default_action_rsu() {
if is_rsu_required; then
log "RSU is required. " \
"Set default action to (E) Perform RSU."
DEFAULT_ACTION=e
return 0
elif check_hwwp; then
log "Hardware write protection on. " \
"Set default action to (E) Perform RSU."
DEFAULT_ACTION=e
return 0
fi
return 1
}
set_default_action_install() {
log "Hardware write protection off. " \
"Set default action to (I) Install."
DEFAULT_ACTION=i
return 0
}
read_default_rma_autorun_tpm_support_option() {
try_set_default_action_rsu || set_default_action_install
}
read_default_option() {
# Read default options
if [ "${NETBOOT_RAMFS}" = 1 ]; then
log "Netbooting. Set default action to (I) Install."
DEFAULT_ACTION=i
elif [ "${RMA_AUTORUN}" = "true" ]; then
case "$(tpm_check_rsu_support)" in
unsupported)
if check_hwwp; then
log "Hardware write protection on. " \
"RSU is not supported, please disable hardware write protect."
DEFAULT_ACTION=""
else
log "Hardware write protection off. " \
"Set default action to (I) Install."
DEFAULT_ACTION=i
fi
;;
supported)
read_default_rma_autorun_tpm_support_option
;;
need_update)
log "TPM version is old. Set default action to (U) Update TPM."
DEFAULT_ACTION=u
;;
esac
fi
}
main() {
if [ "$(id -u)" -ne 0 ]; then
echo "You must run this as root."
exit 1
fi
config_tty || true # Never abort if TTY has problems.
log "Starting Factory Installer."
# TODO: do we still need this call now that the kernel was tweaked to
# provide a good light level by default?
lightup_screen
load_modules
colorize white
clear
test_ec_flash_presence
# Check for any configuration overrides.
overrides
read_default_option
# Validity check default action for mandantory RSU.
if [ -n "${DEFAULT_ACTION}" ]; then
log "Default action: [${DEFAULT_ACTION}]."
if [ "$(tpm_check_rsu_support)" = "supported" ] && is_rsu_required &&
! is_valid_action_when_rsu_required "${DEFAULT_ACTION}"; then
log "Action [${DEFAULT_ACTION}] is invalid when RSU is required."
log "Only support ${SUPPORTED_ACTIONS_RSU_REQUIRED}."
log "Will fallback to normal menu..."
DEFAULT_ACTION=""
sleep 3
elif ! is_valid_action "${DEFAULT_ACTION}"; then
log "Action [${DEFAULT_ACTION}] is invalid."
log "Only support ${SUPPORTED_ACTIONS}${SUPPORTED_ACTIONS_BOARD}."
log "Will fallback to normal menu..."
DEFAULT_ACTION=""
sleep 3
fi
fi
while true; do
clear
print_header
local do_default_action=false
if [ -n "${DEFAULT_ACTION}" ]; then
do_default_action=true
log "Will automatically perform action [${DEFAULT_ACTION}]."
if [ "${ACTION_COUNTDOWN}" = "true" ]; then
# Give the user the chance to press any key to display the menu.
log "Press any key to show menu instead..."
local timeout_secs=3
for i in $(seq ${timeout_secs} -1 1); do
# Read with timeout doesn't reliably work multiple times without
# a sub shell.
if ( read -N 1 -p "Press any key within ${i} sec> " -t 1 ); then
echo
do_default_action=false
break
fi
echo
done
fi
fi
if ${do_default_action}; then
# Default action is set and no key pressed: perform the default action.
"action_${DEFAULT_ACTION}"
else
if [ "$(tpm_check_rsu_support)" = "supported" ] && is_rsu_required; then
# RSU is required.
colorize yellow
echo
echo "This device has FWMP and blocks developer mode."
echo "It is possibly a managed device."
echo "Please ask the admin to deprovision the device or perform"
echo "RSU (RMA Server Unlock)."
echo
colorize white
# Perform RSU.
echo "Defaulting to RSU"
sleep 2
action_e
else
# Display the menu for the user to select an option.
menu
if [ -n "${ACTION}" ]; then
# Perform the selected action.
"action_${ACTION}"
fi
fi
fi
colorize white
read -N 1 -p "Press any key to continue> "
done
}
main "$@"