| OLD | NEW |
| 1 #!/bin/sh | 1 #!/bin/sh -eu |
| 2 | |
| 3 # Copyright (c) 2010 The Chromium OS Authors. All rights reserved. | 2 # Copyright (c) 2010 The Chromium OS Authors. All rights reserved. |
| 4 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 5 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 6 | 5 # |
| 7 # A script to install from removable media to hard disk. | 6 # A script to install from removable media to hard disk. |
| 8 | 7 |
| 9 # This is used to force partitions to be larger than they may be | 8 # Load functions and constants for chromeos-install. |
| 10 # On the source install media | 9 . "$(dirname "$0")/chromeos-common.sh" |
| 11 INSTALL_ROOT_PART_SIZE=$((1024 * 1024)) # 1024 byte blocks, thus 1 gig | |
| 12 | 10 |
| 13 if [ "$USER_ID" -ne 1000 ] | 11 |
| 12 # You can't be root when you start this, because we want to force you to give |
| 13 # the root password as confirmation that you're allowed to do it. We don't care |
| 14 # what other account you're using; you'll still need to sudo before this works. |
| 15 if [ $(id -u) == "0" ] |
| 14 then | 16 then |
| 15 echo "" | 17 echo "" |
| 16 echo "Note: You must be the 'chronos' user to run this script." | 18 echo "Note: You must be the 'chronos' user to run this script." |
| 17 echo "" | 19 echo "" |
| 18 echo "Usage: $0 [destination_device] [skip_source_removable_check]" | 20 echo "Usage: $0 [destination_device] [skip_source_removable_check]" |
| 19 echo "" | 21 echo "" |
| 20 echo "This will install the usb image to your machine's hard disk." | 22 echo "This will install the usb image to your machine's hard disk." |
| 21 echo "By default, it will attempt to install to '/dev/sda'." | 23 echo "By default, it will attempt to install to '/dev/sda'." |
| 22 echo "First 'su chronos' and then run the script. It will ask" | 24 echo "First 'su chronos' and then run the script. It will ask" |
| 23 echo "for the root password before messing with your hard disk." | 25 echo "for the root password before messing with your hard disk." |
| 24 echo "Skips checking whether the source device is removable if second" | 26 echo "Skips checking whether the source device is removable if second" |
| 25 echo "argument is 'true'." | 27 echo "argument is 'true'." |
| 26 exit 1 | 28 exit 1 |
| 27 fi | 29 fi |
| 28 | 30 |
| 29 DST=/dev/sda | 31 # Default destination is /dev/sda. |
| 30 if [ -n "$1" ] | 32 DST=${1:-/dev/sda} |
| 31 then | |
| 32 DST="$1" | |
| 33 fi | |
| 34 | 33 |
| 35 SKIP_CHECK_SOURCE_REMOVABLE="false" | 34 # Check for a removeable source. |
| 36 if [ "$2" = "true" ] | 35 SKIP_CHECK_SOURCE_REMOVABLE=${2:-false} |
| 37 then | |
| 38 SKIP_CHECK_SOURCE_REMOVABLE="true" | |
| 39 fi | |
| 40 | 36 |
| 41 # First find the root device that we are installing from and verify it. | 37 # First find the root partition that we are installing from and verify it. |
| 42 CMDLINE=`cat /proc/cmdline` | 38 ROOTDEV=$(rootdev) |
| 43 SRC_PARTITION=`echo "$CMDLINE" | sed \ | 39 # Remove numbers at end of rootfs device. |
| 44 's/.*root=\([-/=\.[:alpha:][:digit:]]*\).*/\1/g'` | 40 SRC=${ROOTDEV%%[0-9]*} |
| 45 if [ ! -b "$SRC_PARTITION" ] | 41 REMOVABLE=$(cat /sys/block/${SRC#/dev/}/removable) |
| 46 then | |
| 47 # Try looking up by label | |
| 48 LABEL=`echo "$CMDLINE" | sed \ | |
| 49 's/.*root=LABEL=\([-\.[:alpha:][:digit:]]*\).*/\1/g'` | |
| 50 if [ "$LABEL" = "$CMDLINE" ] | |
| 51 then | |
| 52 echo "Error: Unable to find root." | |
| 53 exit 1 | |
| 54 fi | |
| 55 echo "Using source USB device with label: $LABEL" | |
| 56 SRC_PARTITION=`readlink -f /dev/disk/by-label/$LABEL` | |
| 57 fi | |
| 58 if [ ! -b "$SRC_PARTITION" ] | |
| 59 then | |
| 60 echo "Error: Unable to find USB partition: $SRC_PARTITION" | |
| 61 exit 1 | |
| 62 fi | |
| 63 SRC=`echo "$SRC_PARTITION" | sed 's/\(\/dev\/[[:alpha:]]*\)[[:digit:]]*/\1/g'` | |
| 64 if [ ! -b "$SRC" ] | |
| 65 then | |
| 66 echo "Error: Unable to find USB device: $SRC" | |
| 67 exit 1 | |
| 68 fi | |
| 69 SRC_DEV=${SRC#/dev/} | |
| 70 REMOVABLE=`cat /sys/block/$SRC_DEV/removable` | |
| 71 if [ "$SKIP_CHECK_SOURCE_REMOVABLE" = "false" -a "$REMOVABLE" != "1" ] | 42 if [ "$SKIP_CHECK_SOURCE_REMOVABLE" = "false" -a "$REMOVABLE" != "1" ] |
| 72 then | 43 then |
| 73 echo "Error: Source does not look like a removable device: $SRC_DEV" | 44 echo "Error: Source does not look like a removable device: $SRC" |
| 74 exit 1 | 45 exit 1 |
| 75 fi | 46 fi |
| 76 | 47 |
| 77 # Check out the dst device. | 48 # Check out the dst device. |
| 78 if [ ! -b "$DST" ] | 49 if [ ! -b "$DST" ] |
| 79 then | 50 then |
| 80 echo "Error: Unable to find destination device: $DST" | 51 echo "Error: Unable to find destination block device: $DST" |
| 81 exit 1 | 52 exit 1 |
| 82 fi | 53 fi |
| 83 DST_DEV=${DST#/dev/} | 54 REMOVABLE=$(cat /sys/block/${DST#/dev/}/removable) |
| 84 REMOVABLE=`cat /sys/block/$DST_DEV/removable` | |
| 85 if [ $? -ne 0 ] | 55 if [ $? -ne 0 ] |
| 86 then | 56 then |
| 87 echo "Error: Invalid destiation device (must be whole device): $DST" | 57 echo "Error: Invalid destination device (must be whole device): $DST" |
| 88 exit 1 | 58 exit 1 |
| 89 fi | 59 fi |
| 90 if [ "$REMOVABLE" != "0" ] | 60 if [ "$REMOVABLE" != "0" ] |
| 91 then | 61 then |
| 92 echo "Error: Attempt to install to a removeable device: $DST" | 62 echo "Error: Attempt to install to a removeable device: $DST" |
| 93 exit 1 | 63 exit 1 |
| 94 fi | 64 fi |
| 95 if [ "$DST" = "$SRC" ]; then | 65 if [ "$DST" = "$SRC" ]; then |
| 96 echo "Error: src and dst the same: $SRC = $DST" | 66 echo "Error: src and dst the same: $SRC = $DST" |
| 97 exit 1 | 67 exit 1 |
| 98 fi | 68 fi |
| 69 |
| 99 # Ask for root password to be sure. | 70 # Ask for root password to be sure. |
| 100 echo "This will install from '$SRC' to '$DST'. If you are sure this is " | 71 echo "This will install from '$SRC' to '$DST'. If you are sure this is" |
| 101 echo "what you want then feel free to enter the root password to proceed." | 72 echo "what you want then feel free to enter the root password to proceed." |
| 102 sudo -K # Force them to enter a password | 73 sudo -K |
| 103 | 74 |
| 104 # Verify sizes. Since we use sfdisk, this needs to be done after we start | 75 echo "This will erase all data at this destination: $DST" |
| 105 # asking for the root password. | 76 read -p "Are you sure (y/N)? " SURE |
| 106 SIZE=`sudo sfdisk -l "$SRC" 2>/dev/null | grep "$SRC_PARTITION" | grep '*' | awk
'{ print $6 }'` | 77 if [ "$SURE" != "y" ] ; then |
| 107 if [ $INSTALL_ROOT_PART_SIZE -lt $SIZE ]; then | 78 echo "Ok, better safe than sorry; you answered '$SURE'." |
| 108 echo "Install partition size too small: $INSTALL_ROOT_PART_SIZE vs $SIZE" | |
| 109 echo "(1024 byte blocks)" | |
| 110 exit 1 | |
| 111 fi | |
| 112 SIZE=$INSTALL_ROOT_PART_SIZE | |
| 113 SIZE=$((SIZE * 1024)) | |
| 114 if [ $SIZE -eq 0 ] | |
| 115 then | |
| 116 echo "Error: Unable to get system partition size." | |
| 117 exit 1 | |
| 118 fi | |
| 119 SIZE_NEEDED=$((SIZE * 4)) # Assume 2 system images, swap, and user space. | |
| 120 DST_SIZE=`cat /sys/block/$DST_DEV/size` | |
| 121 DST_SIZE=$((DST_SIZE * 512)) | |
| 122 if [ $DST_SIZE -lt $SIZE_NEEDED ] | |
| 123 then | |
| 124 echo "Error: Destination device is too small ($DST_SIZE vs $SIZE_NEEDED)" | |
| 125 exit 1 | 79 exit 1 |
| 126 fi | 80 fi |
| 127 | 81 |
| 128 # Location to temporarily mount the new system image to fix things up. | 82 ############################################################################## |
| 129 ROOTFS_DIR="/tmp/chromeos_hd_install" | 83 # Helpful constants and functions. |
| 130 | 84 |
| 131 # From now on we die on error. We set up a failure handler and continue. | 85 PMBRCODE=/tmp/gptmbr.bin |
| 132 error_handler() { | 86 TMPFILE=/tmp/install-temp-file |
| 133 ! sudo umount "$ROOTFS_DIR" | 87 TMPMNT=/tmp/install-mount-point |
| 88 mkdir -p ${TMPMNT} |
| 134 | 89 |
| 135 echo "Sorry, an error occurred after we messed with the hard disk." | 90 # Create a loop device on the given file at a specified (sector) offset. |
| 136 echo "The chromeos image was NOT installed successfully and there is " | 91 # Remember the loop device using the global variable LOOP_DEV. |
| 137 echo "a chance that you will not be able to boot off the netbook hard disk." | 92 # Invoke as: command |
| 93 # Args: FILE OFFSET |
| 94 loop_offset_setup() { |
| 95 local filename=$1 |
| 96 local offset=$2 |
| 97 |
| 98 LOOP_DEV=$(sudo losetup -f) |
| 99 if [ -z "$LOOP_DEV" ] |
| 100 then |
| 101 echo "No free loop device. Free up a loop device or reboot. Exiting." |
| 102 exit 1 |
| 103 fi |
| 104 |
| 105 sudo losetup -o $(($offset * 512)) ${LOOP_DEV} ${filename} |
| 138 } | 106 } |
| 139 set -e | |
| 140 trap error_handler EXIT | |
| 141 | 107 |
| 142 # Set up the partition table. This is different from the USB partition table | 108 # Delete the current loop device. |
| 143 # because the user paritition expands to fill the space on the disk. | 109 loop_offset_cleanup() { |
| 144 # NOTE: We currently create an EFI partition rather than swap since the | 110 # losetup -a doesn't always show every active device, so we'll always try to |
| 145 # eeepc can take advantage of this to speed POST time. | 111 # delete what we think is the active one without checking first. Report |
| 146 sudo dd if="$SRC" of="$DST" bs=512 count=1 | 112 # success no matter what. |
| 113 sudo losetup -d ${LOOP_DEV} || /bin/true |
| 114 } |
| 147 | 115 |
| 148 PARTITION_NUM_SECTORS=$((SIZE / 512)) | 116 # Mount the existing loop device at the mountpoint in $TMPMNT. |
| 149 # size of the device in 512 bytes sectors: | 117 # Args: none |
| 150 DEVICE_NUM_SECTORS=$(cat /sys/block/${DST#/dev/}/size) | 118 mount_on_loop_dev() { |
| 119 sudo mount ${LOOP_DEV} ${TMPMNT} |
| 120 } |
| 151 | 121 |
| 152 sudo sfdisk --force -uS "$DST" <<EOF | 122 # Unmount loop-mounted device. |
| 153 ,$(($DEVICE_NUM_SECTORS - (3 * $PARTITION_NUM_SECTORS) - 1)),L,-, | 123 umount_from_loop_dev() { |
| 154 ,$PARTITION_NUM_SECTORS,ef,-, | 124 mount | grep -q " on ${TMPMNT} " && sudo umount ${TMPMNT} |
| 155 ,$PARTITION_NUM_SECTORS,L,*, | 125 } |
| 156 ,$PARTITION_NUM_SECTORS,L,-, | |
| 157 ; | |
| 158 EOF | |
| 159 sync | |
| 160 | 126 |
| 161 # Tell kernel to update partition devices based on the new MBR: | 127 # Undo both mount and loop. |
| 162 sudo sfdisk -R "$DST" | 128 my_cleanup() { |
| 129 umount_from_loop_dev |
| 130 loop_offset_cleanup |
| 131 } |
| 163 | 132 |
| 164 # Wait a bit and make sure that the partition device shows up. | 133 # Copy the SRC_PARTITION onto the DST_DEV device at START_OFFSET and label it, |
| 165 DST_PARTITION="${DST}3" | 134 # assuming it's an ext2-based filesystem. |
| 166 sleep 5 | 135 # Invoke as: command |
| 167 if [ ! -b "$DST_PARTITION" ] | 136 # Args: SRC_PARTITION DST_DEV START_OFFSET LABEL |
| 168 then | 137 install_rootfs() { |
| 169 echo "Error: Destination system partition was not created: $DST_PARTITION" | 138 local src_partition=$1 |
| 170 exit 1 | 139 local dst_dev=$2 |
| 171 fi | 140 local start_offset=$3 |
| 141 local label=$4 |
| 142 |
| 143 # We copy the system image a bit strangely here because we have observed |
| 144 # cases in installing to hard disk where people ctrl-c the operation and then |
| 145 # are not longer able to properly boot a USB image. This is because when |
| 146 # booting from USB we set the root device by label, so if you partially copy |
| 147 # the FS here without updating the label then a subsequent USB boot may try |
| 148 # to use the wrong device and fail. |
| 149 sync |
| 172 | 150 |
| 173 # Copy the system image. We sync first to try to make the copy as safe as we | 151 echo "image $label..." |
| 174 # can. We skip the first block which contains the filesystem label (aka | |
| 175 # volume name). It's 16 bytes in length at 120 bytes in the superblock, | |
| 176 # which is itself at offset 1024 of the device. | |
| 177 echo "" | |
| 178 echo "Copying the system image. This might take a while..." | |
| 179 sync | |
| 180 sudo dd if="$SRC_PARTITION" of="$DST_PARTITION" bs=1M seek=1 skip=1 | |
| 181 # Copy all but the first 2 kibibytes now | |
| 182 sudo dd if="$SRC_PARTITION" of="$DST_PARTITION" bs=1024 seek=2 skip=2 count=1022 | |
| 183 | 152 |
| 184 LABEL=$(sudo /sbin/e2label "$SRC_PARTITION") | 153 # Copy first 2 kilobytes of the root image to a temp file, set the label, |
| 154 # and then copy it to the dest. |
| 155 # NOTE: This hack won't work if we stop using an ext-based rootfs. |
| 156 local superblock_offset=1024 |
| 157 local label_field_offset=120 |
| 158 local label_field_length=16 |
| 159 sudo dd if=${src_partition} of=${TMPFILE} bs=512 count=4 |
| 160 sudo dd if=/dev/zero of=${TMPFILE} bs=1 \ |
| 161 seek=$((superblock_offset + label_field_offset)) \ |
| 162 count=$label_field_length conv=notrunc |
| 163 echo -n "${label}" | sudo dd of=${TMPFILE} \ |
| 164 seek=$((superblock_offset + label_field_offset)) \ |
| 165 bs=1 count=$((label_field_length - 1)) conv=notrunc |
| 166 # Copy the new label superblock to the dest. |
| 167 sudo dd if=${TMPFILE} of=${dst_dev} bs=512 seek=${start_offset} conv=notrunc |
| 185 | 168 |
| 186 # Check new file system label. We skip the first char and prefix with 'H' | 169 # Copy the rest of the source to the dest. |
| 187 NEW_LABEL=`expr substr "$LABEL" 2 ${#LABEL}` | 170 sudo dd if=${src_partition} of=${dst_dev} conv=notrunc \ |
| 188 NEW_LABEL="H${NEW_LABEL}" | 171 bs=512 skip=4 seek=$((${start_offset} + 4)) |
| 189 if [ ${#NEW_LABEL} -gt 15 ] | |
| 190 then | |
| 191 echo "New label " "$NEW_LABEL is too long (greater than 15 bytes)" | |
| 192 fi | |
| 193 | 172 |
| 194 # Copy first 2 kibibytes of the partition to a temp file | 173 sync |
| 195 TEMP_FILE=/tmp/install_part_head | 174 } |
| 196 sudo dd if="$SRC_PARTITION" of="$TEMP_FILE" bs=1024 count=2 | |
| 197 | 175 |
| 198 # Manually update the label. First zero it, then write the new label. | 176 ############################################################################## |
| 199 SUPERBLOCK_OFFSET=1024 | |
| 200 LABEL_FIELD_OFFSET=120 | |
| 201 LABEL_FIELD_LENGTH=16 | |
| 202 sudo dd if=/dev/zero of="$TEMP_FILE" bs=1 \ | |
| 203 seek=$(( $SUPERBLOCK_OFFSET + $LABEL_FIELD_OFFSET )) \ | |
| 204 count=$LABEL_FIELD_LENGTH conv=notrunc | |
| 205 echo -n "$NEW_LABEL" | sudo dd of="$TEMP_FILE" \ | |
| 206 seek=$(( $SUPERBLOCK_OFFSET + $LABEL_FIELD_OFFSET )) \ | |
| 207 bs=1 count=15 conv=notrunc | |
| 208 | 177 |
| 209 # Copy the temp file into the new partition | 178 # What do we expect & require to have on the source device? |
| 210 sudo dd if="$TEMP_FILE" of="$DST_PARTITION" | 179 STATEFUL_IMG=${SRC}1 |
| 211 sudo rm -f "$TEMP_FILE" | 180 KERNEL_IMG=${SRC}2 |
| 212 sync | 181 ROOTFS_IMG=${SRC}3 |
| 213 | 182 |
| 214 # Mount root partition | 183 # Steal the PMBR code from the source MBR to put on the dest MBR, for booting |
| 215 mkdir -p "$ROOTFS_DIR" | 184 # on legacy-BIOS devices. |
| 216 sudo mount "$DST_PARTITION" "$ROOTFS_DIR" | 185 sudo dd if=$SRC of=$PMBRCODE bs=512 count=1 |
| 217 # run postinst script | |
| 218 sudo "$ROOTFS_DIR"/postinst "$DST_PARTITION" | |
| 219 sudo umount "$ROOTFS_DIR" | |
| 220 | 186 |
| 221 # set up stateful partition | 187 # Create the GPT. |
| 222 STATEFUL_PARTITION="${DST}1" | 188 install_gpt $DST $ROOTFS_IMG $KERNEL_IMG $STATEFUL_IMG $PMBRCODE |
| 223 sudo mkfs.ext3 "$STATEFUL_PARTITION" | 189 |
| 224 sudo tune2fs -L "H-STATE" "$STATEFUL_PARTITION" | 190 # Install the content. |
| 191 echo "Copying kernel..." |
| 192 sudo dd if=${KERNEL_IMG} of=${DST} conv=notrunc bs=512 seek=${START_KERN_A} |
| 193 sudo dd if=${KERNEL_IMG} of=${DST} conv=notrunc bs=512 seek=${START_KERN_B} |
| 194 |
| 195 echo "Copying rootfs..." |
| 196 install_rootfs ${ROOTFS_IMG} ${DST} ${START_ROOTFS_A} "H-ROOT-A" |
| 197 install_rootfs ${ROOTFS_IMG} ${DST} ${START_ROOTFS_B} "H-ROOT-B" |
| 198 |
| 199 # We can't guarantee that the kernel will see the new partition table, so we |
| 200 # can't use it directly. We could force the kernel to reload it with an ioctl, |
| 201 # but then we might have the UI mounting and displaying any old filesystems |
| 202 # left over from the last install, and we don't want that either. So any access |
| 203 # that we need to do to the destination partitions will have to go through loop |
| 204 # devices. |
| 205 |
| 206 # Now run the postinstall script in each new rootfs. Note that even though |
| 207 # we're passing the new destination partition number as an arg, the postinst |
| 208 # script had better not try to access it, for the reasons we just gave. |
| 209 loop_offset_setup ${DST} ${START_ROOTFS_A} |
| 210 trap loop_offset_cleanup EXIT |
| 211 mount_on_loop_dev |
| 212 trap my_cleanup EXIT |
| 213 sudo ${TMPMNT}/postinst ${DST}3 |
| 214 umount_from_loop_dev |
| 215 trap loop_offset_cleanup EXIT |
| 216 loop_offset_cleanup |
| 217 trap - EXIT |
| 218 |
| 219 loop_offset_setup ${DST} ${START_ROOTFS_B} |
| 220 trap loop_offset_cleanup EXIT |
| 221 mount_on_loop_dev |
| 222 trap my_cleanup EXIT |
| 223 sudo ${TMPMNT}/postinst ${DST}5 |
| 224 umount_from_loop_dev |
| 225 trap loop_offset_cleanup EXIT |
| 226 loop_offset_cleanup |
| 227 trap - EXIT |
| 228 |
| 229 echo "Installing the stateful partition..." |
| 230 loop_offset_setup $DST $START_STATEFUL |
| 231 trap loop_offset_cleanup EXIT |
| 232 sudo mkfs.ext3 -F -b 4096 -L "H-STATE" ${LOOP_DEV} \ |
| 233 $(($NUM_STATEFUL_SECTORS / 8)) |
| 225 | 234 |
| 226 # Install dev image into the stateful partition | 235 # Install dev image into the stateful partition |
| 227 # TODO(sosa@chromium.org) - Remove old autotest support | 236 # TODO(sosa@chromium.org) - Remove old autotest support |
| 228 if [ -f /root/.dev_mode ] || [ -d /mnt/stateful_partition/dev_image ] ; then | 237 if [ -f /root/.dev_mode ] || [ -d /mnt/stateful_partition/dev_image ] ; then |
| 229 STATEFUL_DIR=/tmp/stateful_partition_on_hd | 238 mount_on_loop_dev |
| 230 mkdir -p "$STATEFUL_DIR" | 239 trap my_cleanup EXIT |
| 231 sudo mount "$STATEFUL_PARTITION" "$STATEFUL_DIR" | 240 sudo cp -fpru /mnt/stateful_partition/dev_image "$TMPMNT/dev_image" |
| 232 sudo cp -fpru /mnt/stateful_partition/dev_image "$STATEFUL_DIR/dev_image" | 241 umount_from_loop_dev |
| 233 sudo umount "$STATEFUL_DIR" | 242 trap loop_offset_cleanup EXIT |
| 234 fi | 243 fi |
| 244 loop_offset_cleanup |
| 245 trap - EXIT |
| 235 | 246 |
| 236 # Force data to disk before we declare done. | 247 # Force data to disk before we declare done. |
| 237 sync | 248 sync |
| 238 | 249 |
| 239 echo "------------------------------------------------------------" | 250 echo "------------------------------------------------------------" |
| 240 echo "" | 251 echo "" |
| 241 echo "Installation to '$DST' complete." | 252 echo "Installation to '$DST' complete." |
| 242 echo "Please shutdown, remove the USB device, cross your fingers, and reboot." | 253 echo "Please shutdown, remove the USB device, cross your fingers, and reboot." |
| 243 | |
| 244 trap - EXIT | |
| OLD | NEW |