| Index: src/platform/installer/chromeos-install
 | 
| diff --git a/src/platform/installer/chromeos-install b/src/platform/installer/chromeos-install
 | 
| index 5824c9cfe824d61db382b7bc24c2a34ec2cf82ff..dcd65ed900e07383b37327ecb4a494543bc3b2cd 100755
 | 
| --- a/src/platform/installer/chromeos-install
 | 
| +++ b/src/platform/installer/chromeos-install
 | 
| @@ -1,16 +1,18 @@
 | 
| -#!/bin/sh
 | 
| -
 | 
| +#!/bin/sh -eu
 | 
|  # 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.
 | 
|  
 | 
| -# This is used to force partitions to be larger than they may be
 | 
| -# On the source install media
 | 
| -INSTALL_ROOT_PART_SIZE=$((1024 * 1024))  # 1024 byte blocks, thus 1 gig
 | 
| +# Load functions and constants for chromeos-install.
 | 
| +. "$(dirname "$0")/chromeos-common.sh"
 | 
|  
 | 
| -if [ "$USER_ID" -ne 1000 ]
 | 
| +
 | 
| +# You can't be root when you start this, because we want to force you to give
 | 
| +# the root password as confirmation that you're allowed to do it. We don't care
 | 
| +# what other account you're using; you'll still need to sudo before this works.
 | 
| +if [ $(id -u) == "0" ]
 | 
|  then
 | 
|    echo ""
 | 
|    echo "Note: You must be the 'chronos' user to run this script."
 | 
| @@ -26,65 +28,33 @@ then
 | 
|    exit 1
 | 
|  fi
 | 
|  
 | 
| -DST=/dev/sda
 | 
| -if [ -n "$1" ]
 | 
| -then
 | 
| -  DST="$1"
 | 
| -fi
 | 
| +# Default destination is /dev/sda.
 | 
| +DST=${1:-/dev/sda}
 | 
|  
 | 
| -SKIP_CHECK_SOURCE_REMOVABLE="false"
 | 
| -if [ "$2" = "true" ]
 | 
| -then
 | 
| -  SKIP_CHECK_SOURCE_REMOVABLE="true"
 | 
| -fi
 | 
| +# Check for a removeable source.
 | 
| +SKIP_CHECK_SOURCE_REMOVABLE=${2:-false}
 | 
|  
 | 
| -# First find the root device that we are installing from and verify it.
 | 
| -CMDLINE=`cat /proc/cmdline`
 | 
| -SRC_PARTITION=`echo "$CMDLINE" | sed \
 | 
| -  's/.*root=\([-/=\.[:alpha:][:digit:]]*\).*/\1/g'`
 | 
| -if [ ! -b "$SRC_PARTITION" ]
 | 
| -then
 | 
| -  # Try looking up by label
 | 
| -  LABEL=`echo "$CMDLINE" | sed \
 | 
| -    's/.*root=LABEL=\([-\.[:alpha:][:digit:]]*\).*/\1/g'`
 | 
| -  if [ "$LABEL" = "$CMDLINE" ]
 | 
| -  then
 | 
| -    echo "Error: Unable to find root."
 | 
| -    exit 1
 | 
| -  fi
 | 
| -  echo "Using source USB device with label: $LABEL"
 | 
| -  SRC_PARTITION=`readlink -f /dev/disk/by-label/$LABEL`
 | 
| -fi
 | 
| -if [ ! -b "$SRC_PARTITION" ]
 | 
| -then
 | 
| -  echo "Error: Unable to find USB partition: $SRC_PARTITION"
 | 
| -  exit 1
 | 
| -fi
 | 
| -SRC=`echo "$SRC_PARTITION" | sed 's/\(\/dev\/[[:alpha:]]*\)[[:digit:]]*/\1/g'`
 | 
| -if [ ! -b "$SRC" ]
 | 
| -then
 | 
| -  echo "Error: Unable to find USB device: $SRC"
 | 
| -  exit 1
 | 
| -fi
 | 
| -SRC_DEV=${SRC#/dev/}
 | 
| -REMOVABLE=`cat /sys/block/$SRC_DEV/removable`
 | 
| +# First find the root partition that we are installing from and verify it.
 | 
| +ROOTDEV=$(rootdev)
 | 
| +# Remove numbers at end of rootfs device.
 | 
| +SRC=${ROOTDEV%%[0-9]*}
 | 
| +REMOVABLE=$(cat /sys/block/${SRC#/dev/}/removable)
 | 
|  if [ "$SKIP_CHECK_SOURCE_REMOVABLE" = "false" -a "$REMOVABLE" != "1" ]
 | 
|  then
 | 
| -  echo "Error: Source does not look like a removable device: $SRC_DEV"
 | 
| +  echo "Error: Source does not look like a removable device: $SRC"
 | 
|    exit 1
 | 
|  fi
 | 
|  
 | 
|  # Check out the dst device.
 | 
|  if [ ! -b "$DST" ]
 | 
|  then
 | 
| -  echo "Error: Unable to find destination device: $DST"
 | 
| +  echo "Error: Unable to find destination block device: $DST"
 | 
|    exit 1
 | 
|  fi
 | 
| -DST_DEV=${DST#/dev/}
 | 
| -REMOVABLE=`cat /sys/block/$DST_DEV/removable`
 | 
| +REMOVABLE=$(cat /sys/block/${DST#/dev/}/removable)
 | 
|  if [ $? -ne 0 ]
 | 
|  then
 | 
| -  echo "Error: Invalid destiation device (must be whole device): $DST"
 | 
| +  echo "Error: Invalid destination device (must be whole device): $DST"
 | 
|    exit 1
 | 
|  fi
 | 
|  if [ "$REMOVABLE" != "0" ]
 | 
| @@ -96,142 +66,183 @@ 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 "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  # Force them to enter a password
 | 
| -
 | 
| -# Verify sizes. Since we use sfdisk, this needs to be done after we start
 | 
| -# asking for the root password.
 | 
| -SIZE=`sudo sfdisk -l "$SRC" 2>/dev/null | grep "$SRC_PARTITION" | grep '*' | awk '{ print $6 }'`
 | 
| -if [ $INSTALL_ROOT_PART_SIZE -lt $SIZE ]; then
 | 
| -  echo "Install partition size too small: $INSTALL_ROOT_PART_SIZE vs $SIZE"
 | 
| -  echo "(1024 byte blocks)"
 | 
| -  exit 1
 | 
| -fi
 | 
| -SIZE=$INSTALL_ROOT_PART_SIZE
 | 
| -SIZE=$((SIZE * 1024))
 | 
| -if [ $SIZE -eq 0 ]
 | 
| -then
 | 
| -  echo "Error: Unable to get system partition size."
 | 
| -  exit 1
 | 
| -fi
 | 
| -SIZE_NEEDED=$((SIZE * 4))  # Assume 2 system images, swap, and user space.
 | 
| -DST_SIZE=`cat /sys/block/$DST_DEV/size`
 | 
| -DST_SIZE=$((DST_SIZE * 512))
 | 
| -if [ $DST_SIZE -lt $SIZE_NEEDED ]
 | 
| -then
 | 
| -  echo "Error: Destination device is too small ($DST_SIZE vs $SIZE_NEEDED)"
 | 
| +sudo -K
 | 
| +
 | 
| +echo "This will erase all data at this destination: $DST"
 | 
| +read -p "Are you sure (y/N)? " SURE
 | 
| +if [ "$SURE" != "y" ] ; then
 | 
| +  echo "Ok, better safe than sorry; you answered '$SURE'."
 | 
|    exit 1
 | 
|  fi
 | 
|  
 | 
| -# Location to temporarily mount the new system image to fix things up.
 | 
| -ROOTFS_DIR="/tmp/chromeos_hd_install"
 | 
| +##############################################################################
 | 
| +# Helpful constants and functions.
 | 
| +
 | 
| +PMBRCODE=/tmp/gptmbr.bin
 | 
| +TMPFILE=/tmp/install-temp-file
 | 
| +TMPMNT=/tmp/install-mount-point
 | 
| +mkdir -p ${TMPMNT}
 | 
|  
 | 
| -# From now on we die on error. We set up a failure handler and continue.
 | 
| -error_handler() {
 | 
| -  ! sudo umount "$ROOTFS_DIR"
 | 
| +# 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
 | 
|  
 | 
| -  echo "Sorry, an error occurred after we messed with the hard disk."
 | 
| -  echo "The chromeos image was NOT installed successfully and there is "
 | 
| -  echo "a chance that you will not be able to boot off the netbook hard disk."
 | 
| +  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
 | 
| +
 | 
| +  sudo losetup -o $(($offset * 512)) ${LOOP_DEV} ${filename}
 | 
|  }
 | 
| -set -e
 | 
| -trap error_handler EXIT
 | 
| -
 | 
| -# Set up the partition table. This is different from the USB partition table
 | 
| -# because the user paritition expands to fill the space on the disk.
 | 
| -# NOTE: We currently create an EFI partition rather than swap since the
 | 
| -# eeepc can take advantage of this to speed POST time.
 | 
| -sudo dd if="$SRC" of="$DST" bs=512 count=1
 | 
| -
 | 
| -PARTITION_NUM_SECTORS=$((SIZE / 512))
 | 
| -# size of the device in 512 bytes sectors:
 | 
| -DEVICE_NUM_SECTORS=$(cat /sys/block/${DST#/dev/}/size)
 | 
| -
 | 
| -sudo sfdisk --force -uS "$DST" <<EOF
 | 
| -,$(($DEVICE_NUM_SECTORS - (3 * $PARTITION_NUM_SECTORS) - 1)),L,-,
 | 
| -,$PARTITION_NUM_SECTORS,ef,-,
 | 
| -,$PARTITION_NUM_SECTORS,L,*,
 | 
| -,$PARTITION_NUM_SECTORS,L,-,
 | 
| -;
 | 
| -EOF
 | 
| -sync
 | 
|  
 | 
| -# Tell kernel to update partition devices based on the new MBR:
 | 
| -sudo sfdisk -R "$DST"
 | 
| +# Delete the current loop device.
 | 
| +loop_offset_cleanup() {
 | 
| +  # 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
 | 
| +}
 | 
|  
 | 
| -# Wait a bit and make sure that the partition device shows up.
 | 
| -DST_PARTITION="${DST}3"
 | 
| -sleep 5
 | 
| -if [ ! -b "$DST_PARTITION" ]
 | 
| -then
 | 
| -  echo "Error: Destination system partition was not created: $DST_PARTITION"
 | 
| -  exit 1
 | 
| -fi
 | 
| +# Mount the existing loop device at the mountpoint in $TMPMNT.
 | 
| +# Args: none
 | 
| +mount_on_loop_dev() {
 | 
| +  sudo mount ${LOOP_DEV} ${TMPMNT}
 | 
| +}
 | 
|  
 | 
| -# Copy the system image. We sync first to try to make the copy as safe as we
 | 
| -# can. We skip the first block which contains the filesystem label (aka 
 | 
| -# volume name). It's 16 bytes in length at 120 bytes in the superblock,
 | 
| -# which is itself at offset 1024 of the device.
 | 
| -echo ""
 | 
| -echo "Copying the system image. This might take a while..."
 | 
| -sync
 | 
| -sudo dd if="$SRC_PARTITION" of="$DST_PARTITION" bs=1M seek=1 skip=1
 | 
| -# Copy all but the first 2 kibibytes now
 | 
| -sudo dd if="$SRC_PARTITION" of="$DST_PARTITION" bs=1024 seek=2 skip=2 count=1022
 | 
| +# Unmount loop-mounted device.
 | 
| +umount_from_loop_dev() {
 | 
| +  mount | grep -q " on ${TMPMNT} " && sudo umount ${TMPMNT}
 | 
| +}
 | 
|  
 | 
| -LABEL=$(sudo /sbin/e2label "$SRC_PARTITION")
 | 
| +# Undo both mount and loop.
 | 
| +my_cleanup() {
 | 
| +  umount_from_loop_dev
 | 
| +  loop_offset_cleanup
 | 
| +}
 | 
|  
 | 
| -# Check new file system label. We skip the first char and prefix with 'H'
 | 
| -NEW_LABEL=`expr substr "$LABEL" 2 ${#LABEL}`
 | 
| -NEW_LABEL="H${NEW_LABEL}"
 | 
| -if [ ${#NEW_LABEL} -gt 15 ]
 | 
| -then
 | 
| -  echo "New label " "$NEW_LABEL is too long (greater than 15 bytes)"
 | 
| -fi
 | 
| +# Copy the SRC_PARTITION onto the DST_DEV device at START_OFFSET and label it,
 | 
| +# assuming it's an ext2-based filesystem.
 | 
| +# Invoke as: command
 | 
| +# Args: SRC_PARTITION DST_DEV START_OFFSET LABEL
 | 
| +install_rootfs() {
 | 
| +  local src_partition=$1
 | 
| +  local dst_dev=$2
 | 
| +  local start_offset=$3
 | 
| +  local label=$4
 | 
| +  
 | 
| +  # We copy the system image a bit strangely here because we have observed
 | 
| +  # cases in installing to hard disk where people ctrl-c the operation and then
 | 
| +  # are not longer able to properly boot a USB image. This is because when
 | 
| +  # booting from USB we set the root device by label, so if you partially copy
 | 
| +  # the FS here without updating the label then a subsequent USB boot may try
 | 
| +  # to use the wrong device and fail.
 | 
| +  sync
 | 
| +
 | 
| +  echo "image $label..."
 | 
| +
 | 
| +  # Copy first 2 kilobytes of the root image to a temp file, set the label,
 | 
| +  # and then copy it to the dest.
 | 
| +  # NOTE: This hack won't work if we stop using an ext-based rootfs.
 | 
| +  local superblock_offset=1024
 | 
| +  local label_field_offset=120
 | 
| +  local label_field_length=16
 | 
| +  sudo dd if=${src_partition} of=${TMPFILE} bs=512 count=4
 | 
| +  sudo dd if=/dev/zero of=${TMPFILE} bs=1                \
 | 
| +    seek=$((superblock_offset + label_field_offset)) \
 | 
| +    count=$label_field_length conv=notrunc
 | 
| +  echo -n "${label}" | sudo dd of=${TMPFILE} \
 | 
| +    seek=$((superblock_offset + label_field_offset)) \
 | 
| +    bs=1 count=$((label_field_length - 1)) conv=notrunc
 | 
| +  # Copy the new label superblock to the dest.
 | 
| +  sudo dd if=${TMPFILE} of=${dst_dev} bs=512 seek=${start_offset} conv=notrunc
 | 
| +
 | 
| +  # Copy the rest of the source to the dest.
 | 
| +  sudo dd if=${src_partition} of=${dst_dev} conv=notrunc \
 | 
| +    bs=512 skip=4 seek=$((${start_offset} + 4))
 | 
| +
 | 
| +  sync
 | 
| +}
 | 
|  
 | 
| -# Copy first 2 kibibytes of the partition to a temp file
 | 
| -TEMP_FILE=/tmp/install_part_head
 | 
| -sudo dd if="$SRC_PARTITION" of="$TEMP_FILE" bs=1024 count=2
 | 
| -
 | 
| -# Manually update the label. First zero it, then write the new label.
 | 
| -SUPERBLOCK_OFFSET=1024
 | 
| -LABEL_FIELD_OFFSET=120
 | 
| -LABEL_FIELD_LENGTH=16
 | 
| -sudo dd if=/dev/zero of="$TEMP_FILE" bs=1 \
 | 
| -    seek=$(( $SUPERBLOCK_OFFSET + $LABEL_FIELD_OFFSET )) \
 | 
| -    count=$LABEL_FIELD_LENGTH conv=notrunc
 | 
| -echo -n "$NEW_LABEL" | sudo dd of="$TEMP_FILE" \
 | 
| -    seek=$(( $SUPERBLOCK_OFFSET + $LABEL_FIELD_OFFSET )) \
 | 
| -    bs=1 count=15 conv=notrunc
 | 
| -
 | 
| -# Copy the temp file into the new partition
 | 
| -sudo dd if="$TEMP_FILE" of="$DST_PARTITION"
 | 
| -sudo rm -f "$TEMP_FILE"
 | 
| -sync
 | 
| +##############################################################################
 | 
| +
 | 
| +# What do we expect & require to have on the source device?
 | 
| +STATEFUL_IMG=${SRC}1
 | 
| +KERNEL_IMG=${SRC}2
 | 
| +ROOTFS_IMG=${SRC}3
 | 
| +
 | 
| +# Steal the PMBR code from the source MBR to put on the dest MBR, for booting
 | 
| +# on legacy-BIOS devices.
 | 
| +sudo dd if=$SRC of=$PMBRCODE bs=512 count=1
 | 
| +
 | 
| +# Create the GPT.
 | 
| +install_gpt $DST $ROOTFS_IMG $KERNEL_IMG $STATEFUL_IMG $PMBRCODE
 | 
| +
 | 
| +# Install the content.
 | 
| +echo "Copying kernel..."
 | 
| +sudo dd if=${KERNEL_IMG} of=${DST} conv=notrunc bs=512 seek=${START_KERN_A}
 | 
| +sudo dd if=${KERNEL_IMG} of=${DST} conv=notrunc bs=512 seek=${START_KERN_B}
 | 
| +
 | 
| +echo "Copying rootfs..."
 | 
| +install_rootfs ${ROOTFS_IMG} ${DST} ${START_ROOTFS_A} "H-ROOT-A"
 | 
| +install_rootfs ${ROOTFS_IMG} ${DST} ${START_ROOTFS_B} "H-ROOT-B"
 | 
| +
 | 
| +# 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 in each 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}
 | 
| +trap loop_offset_cleanup EXIT
 | 
| +mount_on_loop_dev
 | 
| +trap my_cleanup EXIT
 | 
| +sudo ${TMPMNT}/postinst ${DST}3
 | 
| +umount_from_loop_dev
 | 
| +trap loop_offset_cleanup EXIT
 | 
| +loop_offset_cleanup
 | 
| +trap - EXIT
 | 
|  
 | 
| -# Mount root partition
 | 
| -mkdir -p "$ROOTFS_DIR"
 | 
| -sudo mount "$DST_PARTITION" "$ROOTFS_DIR"
 | 
| -# run postinst script
 | 
| -sudo "$ROOTFS_DIR"/postinst "$DST_PARTITION"
 | 
| -sudo umount "$ROOTFS_DIR"
 | 
| +loop_offset_setup ${DST} ${START_ROOTFS_B}
 | 
| +trap loop_offset_cleanup EXIT
 | 
| +mount_on_loop_dev
 | 
| +trap my_cleanup EXIT
 | 
| +sudo ${TMPMNT}/postinst ${DST}5
 | 
| +umount_from_loop_dev
 | 
| +trap loop_offset_cleanup EXIT
 | 
| +loop_offset_cleanup
 | 
| +trap - EXIT
 | 
|  
 | 
| -# set up stateful partition
 | 
| -STATEFUL_PARTITION="${DST}1"
 | 
| -sudo mkfs.ext3 "$STATEFUL_PARTITION"
 | 
| -sudo tune2fs -L "H-STATE" "$STATEFUL_PARTITION"
 | 
| +echo "Installing the stateful partition..."
 | 
| +loop_offset_setup $DST $START_STATEFUL
 | 
| +trap loop_offset_cleanup EXIT
 | 
| +sudo mkfs.ext3 -F -b 4096 -L "H-STATE" ${LOOP_DEV} \
 | 
| +  $(($NUM_STATEFUL_SECTORS / 8))
 | 
|  
 | 
|  # Install dev image into the stateful partition
 | 
|  # TODO(sosa@chromium.org) - Remove old autotest support
 | 
|  if [ -f /root/.dev_mode ] || [ -d /mnt/stateful_partition/dev_image ] ; then
 | 
| -  STATEFUL_DIR=/tmp/stateful_partition_on_hd
 | 
| -  mkdir -p "$STATEFUL_DIR"
 | 
| -  sudo mount "$STATEFUL_PARTITION" "$STATEFUL_DIR"
 | 
| -  sudo cp -fpru /mnt/stateful_partition/dev_image "$STATEFUL_DIR/dev_image"
 | 
| -  sudo umount "$STATEFUL_DIR"
 | 
| +  mount_on_loop_dev
 | 
| +  trap my_cleanup EXIT
 | 
| +  sudo cp -fpru /mnt/stateful_partition/dev_image "$TMPMNT/dev_image"
 | 
| +  umount_from_loop_dev
 | 
| +  trap loop_offset_cleanup EXIT
 | 
|  fi
 | 
| +loop_offset_cleanup
 | 
| +trap - EXIT
 | 
|  
 | 
|  # Force data to disk before we declare done.
 | 
|  sync
 | 
| @@ -240,5 +251,3 @@ echo "------------------------------------------------------------"
 | 
|  echo ""
 | 
|  echo "Installation to '$DST' complete."
 | 
|  echo "Please shutdown, remove the USB device, cross your fingers, and reboot."
 | 
| -
 | 
| -trap - EXIT
 | 
| 
 |