blob: 104afe789d01f6da737ad6ece12d04fdc9015c02 [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
# 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}")
exec {LOG_FD}>>"${LOG_FILE}"
export BASH_XTRACEFD="${LOG_FD}"
fi
. "/usr/sbin/write_gpt.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="$(findLSBValue CHROMEOS_RELEASE_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=""
INSTALL_FROM_OMAHA="1"
# Supported actions (a set of lowercase characters)
SUPPORTED_ACTIONS=cdirsvyz
# 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.
# The default action if no keys were pressed before timeout.
DEFAULT_ACTION=""
# The ethernet interface to be used. It will be determined in
# check_ethernet_status and be passed to ping_shopfloor as an
# environment variable.
ETH_INTERFACE=""
# Mount point for installer resources.
RESOURCES_MOUNT_POINT="$(mktemp -d)"
USB_INSTALL_OFFSET="$(findLSBValue FACTORY_INSTALL_USB_OFFSET)"
# 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 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."
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 grep -qw "cros_debug" /proc/cmdline 2>/dev/null; then
while true; do sh; done
else
log "To debug with a shell, add 'cros_debug' to your kernel command line."
log " - Factory netboot: Change kernel command line config file on TFTP."
log " - Factory install shim: Add cros_debug into --boot_args."
fi
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
}
config_tty() {
stty opost
# Turn off VESA blanking
setterm -blank 0 -powersave off -powerdown 0
}
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 "Inconsistent firmware write protection status: " \
"main=${main_wp}, ec=${ec_wp}." \
"Please disable Hardware Write Protection and restart again."
fi
}
# Checks if firmware software write protection is enabled.
# Args
# target: a "flashrom -p" descriptor. Defaults to "host".
check_fwwp() {
local target="${1:-host}"
# Note "crossystem sw_wpsw_boot" only works on Baytrail systems, so we have to
# use flashrom for better compatibility.
flashrom -p "${target}" --wp-status 2>/dev/null |
grep -q "write protect is enabled"
}
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
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() {
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
clear_block_devmode
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 "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
ensure_fwwp_consistency
}
get_dst_drive() {
load_base_vars
DST_DRIVE="$(get_fixed_dst_drive)"
if [ -z "${DST_DRIVE}" ]; then
die "Cannot find fixed drive."
fi
}
lightup_screen() {
# Light up screen in case you can't see our splash image.
backlight_tool --set_brightness_percent=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
# 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 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
}
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 is 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 "${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
}
# When install from USB drive, the partition mapping from source USB drive
# to the destination internal storage is as below.
# src_partition: dst_partition
# ----------------------------
# 1: 1 [stateful]
# 2: None [install-kernel] for bootstrap, not installed to destination
# 3: None [install-rootfs] for bootstrap, not installed to destination
# 4: 2 [factory-kernel] src_offset=2
# 5: 3 [factory-rootfs] src_offset=2
# 6: 4 [release-kernel] src_offset=2
# 7: 5 [release-rootfs] src_offset=2
# 8: 8 [oem]
# 12: 12 [efi]
# ----------------------------
process_usb_install_mapping() {
local callback="$1"
shift
local param="$@" i
for i in EFI OEM STATE FACTORY FACTORY_KERNEL RELEASE RELEASE_KERNEL; do
local var="DST_${i}_PART"
local part="${!var}"
local src_offset=""
case "${i}" in
FACTORY* | RELEASE* )
src_offset="${USB_INSTALL_OFFSET}"
;;
esac
# Early return when callback function fails.
"${callback}" "${part}" "${src_offset}" ${param} || return 1
done
}
# Callback of process_usb_install_mapping.
# Copy a partition to DST_DRIVE by given partition number, src_offset
# and src_drive.
dst_drive_partition_copy() {
local part="$1"
local src_offset="$2"
local src_drive="$3"
local src="$(make_partition_dev "${src_drive}" "$((part + src_offset))")"
local dst="$(make_partition_dev "${DST_DRIVE}" "${part}")"
# 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
}
# Callback of process_usb_install_mapping.
# Verify a image resource by given partition number, src_offset and
# resources_dir.
verify_resources_checksum() {
local part="$1"
local src_offset="$2"
local resources_dir="$3"
local resources_file=""
part=$(( part + src_offset ))
resources_file="${resources_dir}/${part}.gz"
[ -f "${resources_file}" ] || return 1
# The config file contains the checksum of each image file.
# For example:
# 1_checksum = HSQdstR/U3hoBpDtnHVEfdNgm/Y=
# 4_checksum = 3pt+EALoyH6hUmDbk735NY/MfAQ=
# 5_checksum = ZyDaPXjzJJK8i0hkIcsD4D+o2DE=
# 6_checksum = U/uLMDAkR3aP9oY/eD44K4ZReSg=
# 7_checksum = twHsO9+o8m0mn9KL3pKhzJATsHQ=
# 8_checksum = twHsO9+o8m0mn9KL3pKhzJATsHQ=
# 12_checksum = twHsO9+o8m0mn9KL3pKhzJATsHQ=
local config="${resources_dir}/config"
local expected_checksum="$(grep "${part}_checksum" "${config}" |
cut -d" " -f3)"
local actual_checksum="$(openssl sha1 -binary "${resources_file}" |
openssl base64)"
if [ "${actual_checksum}" = "${expected_checksum}" ]; then
return 0
else
return 1
fi
}
# Callback of process_usb_install_mapping.
# Install a image resource to a partition on DST_DRIVE by given
# partition number, src_offset and resources_dir.
dst_drive_resources_install() {
local part="$1"
local src_offset="$2"
local resources_dir="$3"
local resource_file="${resources_dir}/$(( part + src_offset )).gz"
local dst="$(make_partition_dev "${DST_DRIVE}" "${part}")"
log "Installing: ${resource_file} -> ${dst}"
pv -B 1M "${resource_file}" | gunzip -c |
dd bs=1M of="${dst}" iflag=fullblock oflag=dsync
}
mount_installer_resources() {
local resources_drive="$1"
# Resources are stored in partition 1.
local resources_dev="$(make_partition_dev "${resources_drive}" 1)"
local resources_dir="${RESOURCES_MOUNT_POINT}/installer_resources"
mount -t ext4 -o ro "${resources_dev}" "${RESOURCES_MOUNT_POINT}"
if [ -d "${resources_dir}" ]; then
echo "${resources_dir}"
else
echo ""
fi
}
append_lsb_from_dst_drive() {
local lsb_file="dev_image/etc/lsb-factory"
local dst_path="/mnt/stateful_partition/${lsb_file}"
local state_dev="$(make_partition_dev "${DST_DRIVE}" 1)"
local state_mount="$(mktemp -d)"
local src_path="${state_mount}/${lsb_file}"
if ! mount "${state_dev}" "${state_mount}"; then
die "Failed to mount ${state_dev}!!"
fi
if [ -f "${src_path}" ]; then
cat "${src_path}" >> "${dst_path}"
else
die "Failed to find ${src_path}!!"
fi
umount "${state_mount}" || true
rmdir "${state_mount}" || true
}
factory_install_usb_resources() {
local installer_resources_dir="$1"
local kern_guid="$(findLSBValue KERN_ARG_KERN_GUID)"
local board_resources_dir="${installer_resources_dir}/${kern_guid}"
[ -n "${kern_guid}" ] || die "Kernel GUID not found in kernel cmdline."
[ -d "${board_resources_dir}" ] || die "Board resources not found."
# Verify resources checksum before installation.
log "Verifying resources checksum..."
process_usb_install_mapping \
verify_resources_checksum "${board_resources_dir}" ||
die "Resources checksum verification failed."
log "Done verifying checksum, start installation..."
# Install from board_resources_dir.
process_usb_install_mapping \
dst_drive_resources_install "${board_resources_dir}"
# Load board specific lsb-factory in DST_DRIVE for post installations.
# For example, FACTORY_INSTALL_FIRMWARE in lsb-factory.
append_lsb_from_dst_drive
}
factory_install_usb() {
local i="" installer_resources_dir=""
local src_drive="$(findLSBValue REAL_USB_DEV)"
# REAL_USB_DEV is optional on systems without initramfs (e.g., 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 by removing the partition numbers at the end.
# For example: sd[a-z][0-9]* -> sd[a-z]
# mmcblk[0-9]*p[0-9]* -> mmcblk[0-9]*
src_drive="$(echo "${src_drive}" | sed 's/[0-9]\+$//')"
src_drive="$(echo "${src_drive}" | sed 's/\(mmcblk[0-9]*\)p/\1/')"
colorize yellow
installer_resources_dir="$(mount_installer_resources "${src_drive}")"
if [ -z "${installer_resources_dir}" ]; then
# Install from src_drive if installer resources are not found.
process_usb_install_mapping dst_drive_partition_copy "${src_drive}"
else
log "Installer resources found on USB stick."
# Install from installer resources.
factory_install_usb_resources "${installer_resources_dir}"
fi
umount "${RESOURCES_MOUNT_POINT}" || true
# 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=""
local var
# 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=$(ETH_INTERFACE=${ETH_INTERFACE} 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"
# Register to Overlord if haven't.
[ -n "${OVERLORD_READY}" ] ||
register_to_overlord "${OMAHA}" "${TTY_FILE}" "${LOG_FILE}"
# 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.
var="DST_${i}_PART"
local part="${!var}"
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}"
var="${i}_CHANNEL_ARG"
local channel_arg="${!var}"
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
var="SRC_${i}_URL"
src_url="${!var}"
var="SRC_${i}_CHECKSUM"
src_checksum="${!var}"
if [ -z "$src_url" ]; then
continue;
fi
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} -ne 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
}
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_boot); "
echo "SW write protect: $(command_to_yes_or_no check_fwwp host)"
echo -n "EC present: $(bool_to_yes_or_no "${EC_PRESENT}"); "
if [ "${EC_PRESENT}" = "1" ]; then
echo -n "EC SW write protect: $(command_to_yes_or_no check_fwwp ec); "
fi
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"
}
# Checks if the given action is valid and supported.
is_valid_action() {
echo "$1" | grep -q "^[${SUPPORTED_ACTIONS}]$"
}
# 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 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"
echo
read -p 'action> ' ACTION
echo
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=
}
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 || 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
board_pre_install
test_ec_flash_presence
test_devsw_presence
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
# Read default options
if [ "${NETBOOT_RAMFS}" = 1 ]; then
log "Netbooting. Set default action to (I)nstall."
DEFAULT_ACTION=i
else
DEFAULT_ACTION="$(findLSBValue FACTORY_INSTALL_DEFAULT_ACTION)"
fi
# Sanity check default action
if [ -n "${DEFAULT_ACTION}" ]; then
clear
print_header
log "Default action: [${DEFAULT_ACTION}]."
if ! is_valid_action "${DEFAULT_ACTION}"; then
log "Action [${DEFAULT_ACTION}] is invalid."
log "Only support ${SUPPORTED_ACTIONS}. Will fallback to normal menu..."
DEFAULT_ACTION=""
sleep 3
fi
fi
# Automatically perform the default action; but first give the user the chance
# to press any key to display the menu.
if [ -n "${DEFAULT_ACTION}" ]; then
log "Will automatically perform action [${DEFAULT_ACTION}]."
log "Or press any key to show menu instead..."
local prevent_default_action=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 sub shell.
if ( read -N 1 -p "Press any key within ${i} sec> " -t 1 ); then
echo
prevent_default_action=true
break
fi
echo
done
if ! ${prevent_default_action}; then
# No key pressed: perform the default action (which should never return)
action_${DEFAULT_ACTION}
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."
sync
sleep 3
factory_on_complete
board_post_install
# 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
# Default action after installation: reboot.
trap - EXIT
sync
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 || 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() {
(
echo "## Information in this commend:"
echo "## dev_debug_vboot"
echo "## date"
echo "## crossystem --all"
echo "## rootdev -s"
echo "## la -aCF /root"
echo "## ls -aCF /mnt/stateful_partition"
echo "## cgpt show /dev/mmcblk0"
echo "## cgpt show /dev/sda"
echo "## etc..."
echo "## firmware event log"
echo "## /sys/firmware/log"
echo "## kernel log"
echo "## SMART log"
echo "## checksums"
echo
echo "## dev_debug_vboot:"
dev_debug_vboot || true
echo
echo "## debug_vboot_noisy.log:"
cat /var/log/debug_vboot_noisy.log || true
echo
echo "## firmware event log:"
mosys eventlog list || true
echo
echo "## /sys/firmware/log:"
cat /sys/firmware/log || true
echo
echo "## kernel log:"
dmesg || true
echo
echo "## SMART log:"
smartctl -x /dev/sda || true
echo
echo "## checksums:"
md5sum /dev/mmcblk0p2 /dev/mmcblk0p4 || true
md5sum /dev/sda2 /dev/sda4 || true
) 2>&1 | secure_less.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
}
main "$@"