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 |