blob: ceb0e3fb4ba7ea17a8a8bf45fbcc39e81f084a46 [file] [log] [blame]
#!/bin/sh -u
# Copyright (c) 2010 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.
#
# A script to install from removable media to hard disk.
# Load functions and constants for chromeos-install.
. "$(dirname "$0")/chromeos-common.sh" || exit 1
. /usr/lib/shflags || exit 1
DEFINE_string dst "" "Destination device"
DEFINE_boolean skip_src_removable ${FLAGS_FALSE} \
"Skip check to ensure source is removable"
DEFINE_boolean skip_dst_removable ${FLAGS_FALSE} \
"Skip check to ensure destination is not removable"
DEFINE_boolean skip_rootfs ${FLAGS_FALSE} \
"Skip installing the rootfs; Only set up partition table"
DEFINE_boolean run_as_root ${FLAGS_FALSE} \
"Allow root to run this script (Careful, it won't prompt for a password!)"
DEFINE_boolean yes ${FLAGS_FALSE} \
"Answer yes to everything"
DEFINE_boolean skip_vblock ${FLAGS_FALSE} \
"Skip copying the HD vblock to install destination. Default: False"
DEFINE_boolean preserve_stateful ${FLAGS_FALSE} \
"Don't create a new filesystem for the stateful partition. Be careful \
using this option as this may make the stateful partition not mountable. \
Default: False."
DEFINE_string arch "" \
"Architecture for this image, must be one of \"ARM\" or \"INTEL\". If
unset auto-detect."
DEFINE_string payload_image "" "Path to a Chromium OS image to install onto \
the device's hard drive. Default: (empty)."
DEFINE_boolean use_payload_kern_b ${FLAGS_FALSE} \
"Copy KERN-B instead of KERN-A from payload_image."
DEFINE_string gpt_layout "" "Path to a script for pre-defined GPT partition \
layout. Default: (empty)."
DEFINE_string pmbr_code "" "Path to PMBR code to be installed. Default: (empty)"
DEFINE_boolean debug ${FLAGS_FALSE} "Show debug output. Default: false"
DEFINE_boolean large_test_partitions ${FLAGS_FALSE} \
"Make partitions 9 and 10 large (for filesystem testing). Default: false"
# Parse command line
FLAGS "$@" || exit 1
eval set -- "${FLAGS_ARGV}"
set -e
SUB_CMD_DEBUG_FLAG=""
if [ "$FLAGS_debug" -eq "${FLAGS_TRUE}" ]; then
set -x
SUB_CMD_DEBUG_FLAG="--debug"
fi
# Don't run this as root
dont_run_as_root
# Determine our architecture
if [ -z "$FLAGS_arch" ]; then
# Is there a better x86 test?
if uname -m | grep -q "^i.86\$"; then
ARCH="INTEL"
elif [ $(uname -m ) = "x86_64" ]; then
ARCH="INTEL"
elif [ $(uname -m ) = "armv7l" ]; then
ARCH="ARM"
else
echo "Error: Failed to auto detect architecture" >&2
exit 1
fi
else
if ! echo "$FLAGS_arch" | grep -qE '^(INTEL|ARM)$'; then
echo "Error: Unknown architecture '$FLAGS_arch'." >& 2
exit 1
fi
ARCH="$FLAGS_arch"
fi
fast_dd() {
# Usage: fast_dd <block size> <count> <seek> <skip> other dd args
local user_block_size="$1"
shift
local user_count="$1"
shift
local user_seek="$1"
shift
local user_skip="$1"
shift
local ideal_block_size=$((2 * 1024 * 1024)) # 2 MiB
if [ $(($ideal_block_size % $user_block_size)) -eq 0 ]; then
local factor=$(($ideal_block_size / $user_block_size))
if [ $(($user_count % $factor)) -eq 0 -a \
$(($user_seek % $factor)) -eq 0 -a \
$(($user_skip % $factor)) -eq 0 ]; then
local count_arg=""
if [ "$user_count" -ne 0 ]; then
count_arg="count=$(($user_count / $factor))"
fi
sudo dd $* bs="$ideal_block_size" seek=$(($user_seek / $factor)) \
skip=$(($user_skip / $factor)) $count_arg
return
fi
fi
# Give up and do the user's slow dd
echo
echo WARNING: DOING A SLOW dd OPERATION. PLEASE FIX
echo
local count_arg=""
if [ "$user_count" -ne 0 ]; then
count_arg="count=$user_count"
fi
sudo dd $* bs="$user_block_size" seek="$user_seek" skip="$user_skip" \
$count_arg
}
# Find root partition of the block device that we are installing from
get_root_device() {
rootdev -s
}
# Check for optional payload image
if [ "$FLAGS_skip_rootfs" -eq "$FLAGS_TRUE" -a -s "$FLAGS_gpt_layout" ]; then
# Usually this is used for partition setup.
SRC=""
ROOT=""
elif [ -z "$FLAGS_payload_image" ]; then
# Find root partition of the root block device
SRC=$(get_block_dev_from_partition_dev $(get_root_device))
ROOT=""
if [ "$FLAGS_skip_src_removable" -eq "${FLAGS_FALSE}" ]; then
if [ "$(cat /sys/block/${SRC#/dev/}/removable)" != "1" ]; then
echo "Error: Source does not look like a removable device: $SRC"
exit 1
fi
fi
else
if [ ! -e "$FLAGS_payload_image" ]; then
echo "Error: No payload image found at $FLAGS_payload_image"
exit 1
fi
# Needed to copy PMBR code off image
SRC="$FLAGS_payload_image"
ROOT="$(mktemp -d)"
fi
# Find our destination device
if [ -z "$FLAGS_dst" ]; then
if [ "$ARCH" = "INTEL" ]; then
DST=/dev/sda
else
DST=/dev/mmcblk0
fi
else
DST="$FLAGS_dst"
fi
# Check out the dst device.
if [ ! -b "$DST" ]; then
echo "Error: Unable to find destination block device: $DST"
exit 1
fi
DST_REMOVABLE=$(cat /sys/block/${DST#/dev/}/removable)
if [ $? -ne 0 ]; then
echo "Error: Invalid destination device (must be whole device): $DST"
exit 1
fi
if [ "$FLAGS_skip_dst_removable" -eq "${FLAGS_FALSE}" ]; then
if [ "$DST_REMOVABLE" != "0" ]; then
echo "Error: Attempt to install to a removeable device: $DST"
exit 1
fi
fi
if [ "$DST" = "$SRC" ]; then
echo "Error: src and dst the same: $SRC = $DST"
exit 1
fi
# Ask for root password to be sure.
echo "This will install from '$SRC' to '$DST'. If you are sure this is"
echo "what you want then feel free to enter the root password to proceed."
sudo -K
echo "This will erase all data at this destination: $DST"
if [ "${FLAGS_yes}" -eq "$FLAGS_FALSE" ]; then
read -p "Are you sure (y/N)? " SURE
if [ "$SURE" != "y" ]; then
echo "Ok, better safe than sorry; you answered '$SURE'."
exit 1
fi
fi
##############################################################################
# Helpful constants and functions.
PMBRCODE=/tmp/gptmbr.bin
TMPFILE=/tmp/install-temp-file
TMPMNT=/tmp/install-mount-point
mkdir -p ${TMPMNT}
# Like mount but keeps track of the current mounts so that they can be cleaned
# up automatically.
tracked_mount() {
local last_arg
eval last_arg=\$$#
MOUNTS="${last_arg}${MOUNTS:+ }${MOUNTS:-}"
sudo mount "$@"
}
# Unmount with tracking.
tracked_umount() {
# dash doesnt support ${//} expansions
local new_mounts
for mount in $MOUNTS; do
if [ "$mount" != "$1" ]; then
new_mounts="${new_mounts:-}${new_mounts+ }$mount"
fi
done
MOUNTS=${new_mounts:-}
sudo umount "$1"
}
# Create a loop device on the given file at a specified (sector) offset.
# Remember the loop device using the global variable LOOP_DEV.
# Invoke as: command
# Args: FILE OFFSET
loop_offset_setup() {
local filename=$1
local offset=$2
LOOP_DEV=$(sudo losetup -f)
if [ -z "$LOOP_DEV" ]
then
echo "No free loop device. Free up a loop device or reboot. Exiting."
exit 1
fi
LOOPS="${LOOP_DEV}${LOOPS:+ }${LOOPS:-}"
sudo losetup -o $(($offset * 512)) ${LOOP_DEV} ${filename}
}
# Delete the current loop device.
loop_offset_cleanup() {
# dash doesnt support ${//} expansions
local new_loops
for loop in $LOOPS; do
if [ "$loop" != "$LOOP_DEV" ]; then
new_loops="${new_loops:-}${new_loops+ }$loop"
fi
done
LOOPS=${new_loops:-}
# losetup -a doesn't always show every active device, so we'll always try to
# delete what we think is the active one without checking first. Report
# success no matter what.
sudo losetup -d ${LOOP_DEV} || /bin/true
}
# Mount the existing loop device at the mountpoint in $TMPMNT.
# Args: optional 'readwrite'. If present, mount read-write, otherwise read-only.
mount_on_loop_dev() {
local rw_flag=${1-readonly}
local mount_flags=""
if [ "${rw_flag}" != "readwrite" ]; then
mount_flags="-o ro"
fi
tracked_mount ${mount_flags} ${LOOP_DEV} ${TMPMNT}
}
# Unmount loop-mounted device.
umount_from_loop_dev() {
mount | grep -q " on ${TMPMNT} " && tracked_umount ${TMPMNT}
}
# Check if all arguments are non-empty values
check_non_empty_values() {
local value
for value in "$@"; do
if [ -z "$value" ]; then
return ${FLAGS_FALSE}
fi
done
return ${FLAGS_TRUE}
}
# Undo all mounts and loops.
cleanup() {
set +e
local mount_point
for mount_point in ${MOUNTS:-}; do
sudo umount "$mount_point" || /bin/true
done
MOUNTS=""
local loop_dev
for loop_dev in ${LOOPS:-}; do
sudo losetup -d "$loop_dev" || /bin/true
done
LOOPS=""
if [ ! -z "$ROOT" ]; then
rmdir "$ROOT"
fi
}
trap cleanup EXIT
##############################################################################
KERNEL_IMG_OFFSET=0
ROOTFS_IMG_OFFSET=0
OEM_IMG_OFFSET=0
ESP_IMG_OFFSET=0
# What do we expect & require to have on the source device?
if [ -r "$FLAGS_gpt_layout" ]; then
. $FLAGS_gpt_layout
# verify all values are loaded
if ! check_non_empty_values "$STATEFUL_IMG_SECTORS" \
"$KERNEL_IMG_SECTORS" "$ROOTFS_IMG_SECTORS" \
"$OEM_IMG_SECTORS" "$ESP_IMG_SECTORS"; then
echo "Error: invalid GPT layout in ${FLAGS_gpt_layout}."
exit 1
fi
elif [ -z "$FLAGS_payload_image" ]; then
STATEFUL_IMG=$(make_partition_dev ${SRC} 1)
KERNEL_IMG=$(make_partition_dev ${SRC} 2)
ROOTFS_IMG=$(make_partition_dev ${SRC} 3)
OEM_IMG=$(make_partition_dev ${SRC} 8)
ESP_IMG=$(make_partition_dev ${SRC} 12)
STATEFUL_IMG_SECTORS=$(numsectors $STATEFUL_IMG)
KERNEL_IMG_SECTORS=$(numsectors $KERNEL_IMG)
ROOTFS_IMG_SECTORS=$(numsectors $ROOTFS_IMG)
OEM_IMG_SECTORS=$(numsectors $OEM_IMG)
ESP_IMG_SECTORS=$(numsectors $ESP_IMG)
else
KERNEL_IMG=${FLAGS_payload_image}
ROOTFS_IMG=${FLAGS_payload_image}
OEM_IMG=${FLAGS_payload_image}
ESP_IMG=${FLAGS_payload_image}
locate_gpt
STATEFUL_IMG_SECTORS=$(partsize "${FLAGS_payload_image}" 1)
KERNEL_IMG_SECTORS=$(partsize "${FLAGS_payload_image}" 2)
ROOTFS_IMG_SECTORS=$(partsize "${FLAGS_payload_image}" 3)
OEM_IMG_SECTORS=$(partsize "${FLAGS_payload_image}" 8)
ESP_IMG_SECTORS=$(partsize "${FLAGS_payload_image}" 12)
STATEFUL_IMG_OFFSET=$(partoffset "${FLAGS_payload_image}" 1)
KERNEL_IMG_OFFSET=$(partoffset "${FLAGS_payload_image}" 2)
ROOTFS_IMG_OFFSET=$(partoffset "${FLAGS_payload_image}" 3)
OEM_IMG_OFFSET=$(partoffset "${FLAGS_payload_image}" 8)
ESP_IMG_OFFSET=$(partoffset "${FLAGS_payload_image}" 12)
if [ ${FLAGS_use_payload_kern_b} -eq ${FLAGS_TRUE} ]; then
KERNEL_IMG_SECTORS=$(partsize "${FLAGS_payload_image}" 4)
KERNEL_IMG_OFFSET=$(partoffset "${FLAGS_payload_image}" 4)
fi
# Mount the src image
loop_offset_setup "${FLAGS_payload_image}" $STATEFUL_IMG_OFFSET
STATEFUL_IMG_LOOP=$LOOP_DEV
loop_offset_setup "${FLAGS_payload_image}" $ROOTFS_IMG_OFFSET
ROOTFS_IMG_LOOP=$LOOP_DEV
tracked_mount -o ro "$ROOTFS_IMG_LOOP" "$ROOT"
tracked_mount -o ro "$STATEFUL_IMG_LOOP" "$ROOT"/mnt/stateful_partition
tracked_mount --bind "$ROOT"/mnt/stateful_partition/var "$ROOT"/var
fi
if [ -n "${FLAGS_pmbr_code}" ]; then
PMBRCODE="${FLAGS_pmbr_code}"
elif [ "$ARCH" = "ARM" ]; then
PMBRCODE=/dev/zero
else
# Steal the PMBR code from the source MBR to put on the dest MBR, for booting
# on legacy-BIOS devices.
sudo dd bs=512 count=1 if=$SRC of=$PMBRCODE
fi
# Creates the GPT with default rootfs size (the "default" parameter).
install_gpt ${DST} ${ROOTFS_IMG_SECTORS} ${STATEFUL_IMG_SECTORS} \
${PMBRCODE} ${ESP_IMG_SECTORS} false default \
${FLAGS_large_test_partitions}
# TODO(tgao): add support for arm recovery
if [ "$FLAGS_skip_rootfs" -eq "$FLAGS_TRUE" ]; then
echo Done installing partitons.
exit 0
fi
# Install the content.
echo "Copying kernel..."
fast_dd 512 ${KERNEL_IMG_SECTORS} ${START_KERN_A} ${KERNEL_IMG_OFFSET} \
if=${KERNEL_IMG} of=${DST} conv=notrunc,fsync
# Copy kernel verification block to target HD
copy_kernel_vblock() {
local ret=0
if [ -s $VERIFY_BLOB ]; then
fast_dd 512 0 ${START_KERN_A} 0 if=${VERIFY_BLOB} of=${DST} conv=notrunc
fast_dd 512 0 ${START_KERN_B} 0 if=${VERIFY_BLOB} of=${DST} conv=notrunc
ret=1
fi
echo ${ret}
}
# The recovery image is signed with different keys from the hard-disk image. We
# need to copy the hard disk verification block from the stateful partition so
# that reboot from HD can be verified.
VERIFY_BLOB="${ROOT}/mnt/stateful_partition/vmlinuz_hd.vblock"
if [ "$FLAGS_skip_vblock" -eq "$FLAGS_FALSE" ]; then
VBLOCK_RET=$(copy_kernel_vblock)
if [ "${VBLOCK_RET}" -eq "1" ]; then
echo "Copied kernel verification blob from ${VERIFY_BLOB}"
else
echo "Error: kernel verification blob not found in stateful partition"
exit 1
fi
fi
echo "Copying rootfs..."
# We can no longer update the label on the rootfs because that changes bits
# that will break both the delta updater and verified boot. We must do a
# straight copy now. The GPT labels and UUIDs are the only mutable naming
# areas we have after a build.
fast_dd 512 ${ROOTFS_IMG_SECTORS} ${START_ROOTFS_A} ${ROOTFS_IMG_OFFSET} \
if=${ROOTFS_IMG} of=${DST} conv=notrunc
echo "Copying OEM customization..."
fast_dd 512 ${OEM_IMG_SECTORS} ${START_OEM} ${OEM_IMG_OFFSET} \
if=${OEM_IMG} of=${DST} conv=notrunc
echo "Copying ESP..."
fast_dd 512 ${ESP_IMG_SECTORS} ${START_ESP} ${ESP_IMG_OFFSET} \
if=${ESP_IMG} of=${DST} conv=notrunc
# If postinst fails, we should still clear stateful.
if [ "${FLAGS_preserve_stateful}" -eq "${FLAGS_FALSE}" ]; then
echo "Clearing the stateful partition..."
loop_offset_setup $DST $START_STATEFUL
sudo mkfs.ext3 -F -b 4096 -L "H-STATE" ${LOOP_DEV} \
$(($NUM_STATEFUL_SECTORS / 8))
# Need to synchronize before releasing loop device, otherwise calling
# loop_offset_cleanup may return "device busy" error.
sync
loop_offset_cleanup
fi
# We can't guarantee that the kernel will see the new partition table, so
# we can't use it directly. We could force the kernel to reload it with an
# ioctl, but then we might have the UI mounting and displaying any old
# filesystems left over from the last install, and we don't want that either.
# So any access that we need to do to the destination partitions will have
# to go through loop devices.
# Now run the postinstall script on one new rootfs. Note that even though
# we're passing the new destination partition number as an arg, the postinst
# script had better not try to access it, for the reasons we just gave.
loop_offset_setup ${DST} ${START_ROOTFS_A}
mount_on_loop_dev
sudo IS_INSTALL="1" ${TMPMNT}/postinst $(make_partition_dev ${DST} 3) \
${SUB_CMD_DEBUG_FLAG}
umount_from_loop_dev
loop_offset_cleanup
#
# Install the stateful partition content
#
# In general, the system isn't allowed to depend on anything
# being in the stateful partition at startup. We make some
# exceptions for dev images (only), as enumerated below:
#
# var/db/pkg
# var/lib/portage
# These are included to support gmerge.
#
# dev_image
# This provides tools specifically chosen to be mounted at
# /usr/local as development only tools.
#
# Every exception added makes the dev image different from
# the release image, which could mask bugs. Make sure every
# item you add here is well justified.
#
echo "Installing the stateful partition..."
loop_offset_setup $DST $START_STATEFUL
if [ -f "${ROOT}"/root/.dev_mode ]; then
mount_on_loop_dev readwrite
DIRLIST="
var/db/pkg
var/lib/portage
dev_image
"
for DIR in ${DIRLIST}; do
if [ ! -d "${ROOT}/mnt/stateful_partition/${DIR}" ]; then
continue
fi
PARENT=$(dirname ${DIR})
sudo mkdir -p ${TMPMNT}/${PARENT}
sudo cp -fpru "${ROOT}/mnt/stateful_partition/${DIR}" ${TMPMNT}/${DIR}
done
umount_from_loop_dev
fi
loop_offset_cleanup
if grep "vogue" "$ROOT"/etc/lsb-release > /dev/null; then
sudo uboot-env.py -f /dev/mtdblock0 -o 0x7c000 -s 0x4000 \
--set bootcmd="mmc read 0 C0008000 0 1; autoscr C0008000"
fi
# Force data to disk before we declare done.
sync
echo "------------------------------------------------------------"
echo ""
echo "Installation to '$DST' complete."
echo "Please shutdown, remove the USB device, cross your fingers, and reboot."