| #!/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." |