OLD | NEW |
1 #!/bin/bash | 1 #!/bin/bash |
2 | 2 |
3 # Copyright (c) 2010 The Chromium OS Authors. All rights reserved. | 3 # 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 | 4 # Use of this source code is governed by a BSD-style license that can be |
5 # found in the LICENSE file. | 5 # found in the LICENSE file. |
6 | 6 |
7 # Script to modify a pristine/dev Chrome OS image to be used for recovery | 7 # This script modifies a base image to act as a recovery installer. |
| 8 # If no kernel image is supplied, it will build a devkeys signed recovery |
| 9 # kernel. Alternatively, a signed recovery kernel can be used to |
| 10 # create a Chromium OS recovery image. |
8 | 11 |
| 12 # Load common constants. This should be the first executable line. |
| 13 # The path to common.sh should be relative to your script's location. |
| 14 . "$(dirname "$0")/common.sh" |
| 15 |
| 16 # Load functions and constants for chromeos-install |
| 17 . "$(dirname "$0")/chromeos-common.sh" |
| 18 |
| 19 # For update_partition_table |
9 . "$(dirname "$0")/resize_stateful_partition.sh" | 20 . "$(dirname "$0")/resize_stateful_partition.sh" |
10 | 21 |
11 # Script must be run inside the chroot. | 22 |
12 restart_in_chroot_if_needed $* | 23 # We need to be in the chroot to emerge test packages. |
| 24 assert_inside_chroot |
13 | 25 |
14 get_default_board | 26 get_default_board |
15 | 27 |
16 # Constants | 28 DEFINE_string board "$DEFAULT_BOARD" "Board for which the image was built" b |
17 TEMP_IMG=$(mktemp "/tmp/temp_img.XXXXXX") | 29 DEFINE_integer statefulfs_sectors 4096 \ |
18 RECOVERY_IMAGE="recovery_image.bin" | 30 "Number of sectors to use for the stateful filesystem" |
| 31 # Skips the build steps and just does the kernel swap. |
| 32 DEFINE_string kernel_image "" \ |
| 33 "Path to a pre-built recovery kernel" |
| 34 DEFINE_string kernel_outfile "" \ |
| 35 "Filename and path to emit the kernel outfile to. \ |
| 36 If empty, emits to IMAGE_DIR." |
| 37 DEFINE_string image "" "Path to the image to use" |
| 38 DEFINE_string to "" \ |
| 39 "Path to the image to create. If empty, defaults to \ |
| 40 IMAGE_DIR/recovery_image.bin." |
| 41 DEFINE_boolean kernel_image_only $FLAGS_FALSE \ |
| 42 "Emit the recovery kernel image only" |
| 43 DEFINE_boolean sync_keys $FLAGS_TRUE \ |
| 44 "Update the kernel to be installed with the vblock from stateful" |
| 45 DEFINE_integer jobs -1 \ |
| 46 "How many packages to build in parallel at maximum." j |
| 47 DEFINE_string build_root "/build" \ |
| 48 "The root location for board sysroots." |
19 | 49 |
20 DEFINE_string board "$DEFAULT_BOARD" "Board for which the image was built" | 50 DEFINE_string rootfs_hash "/tmp/rootfs.hash" \ |
21 DEFINE_string image "" "Location of the rootfs raw image file" | 51 "Path where the rootfs hash should be stored." |
22 DEFINE_string output "${RECOVERY_IMAGE}" \ | 52 |
23 "(optional) output image name. Default: ${RECOVERY_IMAGE}" | 53 # Keep in sync with build_image. |
| 54 DEFINE_string keys_dir "/usr/share/vboot/devkeys" \ |
| 55 "Directory containing the signing keys." |
24 | 56 |
25 # Parse command line | 57 # Parse command line |
26 FLAGS "$@" || exit 1 | 58 FLAGS "$@" || exit 1 |
27 eval set -- "${FLAGS_ARGV}" | 59 eval set -- "${FLAGS_ARGV}" |
28 | 60 |
| 61 EMERGE_CMD="emerge" |
| 62 EMERGE_BOARD_CMD="emerge-${FLAGS_board}" |
| 63 |
29 # No board, no default and no image set then we can't find the image | 64 # No board, no default and no image set then we can't find the image |
30 if [ -z $FLAGS_image ] && [ -z $FLAGS_board ] ; then | 65 if [ -z $FLAGS_image ] && [ -z $FLAGS_board ] ; then |
31 setup_board_warning | 66 setup_board_warning |
32 die "mod_image_for_recovery failed. No board set and no image set" | 67 die "mod_image_for_recovery failed. No board set and no image set" |
33 fi | 68 fi |
34 | 69 |
35 # We have a board name but no image set. Use image at default location | 70 # We have a board name but no image set. Use image at default location |
36 if [ -z $FLAGS_image ] ; then | 71 if [ -z $FLAGS_image ] ; then |
37 IMAGES_DIR="${DEFAULT_BUILD_ROOT}/images/${FLAGS_board}" | 72 IMAGES_DIR="${DEFAULT_BUILD_ROOT}/images/${FLAGS_board}" |
38 FILENAME="chromiumos_image.bin" | 73 FILENAME="chromiumos_image.bin" |
39 FLAGS_image="${IMAGES_DIR}/$(ls -t $IMAGES_DIR 2>&-| head -1)/${FILENAME}" | 74 FLAGS_image="${IMAGES_DIR}/$(ls -t $IMAGES_DIR 2>&-| head -1)/${FILENAME}" |
40 fi | 75 fi |
41 | 76 |
42 # Turn path into an absolute path. | 77 # Turn path into an absolute path. |
43 FLAGS_image=$(eval readlink -f ${FLAGS_image}) | 78 FLAGS_image=`eval readlink -f ${FLAGS_image}` |
44 | 79 |
45 # Abort early if we can't find the image | 80 # Abort early if we can't find the image |
46 if [ ! -f $FLAGS_image ] ; then | 81 if [ ! -f $FLAGS_image ] ; then |
47 echo "No image found at $FLAGS_image" | 82 echo "No image found at $FLAGS_image" |
48 exit 1 | 83 exit 1 |
49 fi | 84 fi |
50 | 85 |
| 86 # What cross-build are we targeting? |
| 87 . "${FLAGS_build_root}/${FLAGS_board}/etc/make.conf.board_setup" |
| 88 # Figure out ARCH from the given toolchain. |
| 89 # TODO: Move to common.sh as a function after scripts are switched over. |
| 90 TC_ARCH=$(echo "${CHOST}" | awk -F'-' '{ print $1 }') |
| 91 case "${TC_ARCH}" in |
| 92 arm*) |
| 93 ARCH="arm" |
| 94 error "ARM recovery mode is still in the works. Use a normal image for now." |
| 95 ;; |
| 96 *86) |
| 97 ARCH="x86" |
| 98 ;; |
| 99 *) |
| 100 error "Unable to determine ARCH from toolchain: ${CHOST}" |
| 101 exit 1 |
| 102 esac |
| 103 |
| 104 get_install_vblock() { |
| 105 # If it exists, we need to copy the vblock over to stateful |
| 106 # This is the real vblock and not the recovery vblock. |
| 107 local stateful_offset=$(partoffset "$FLAGS_image" 1) |
| 108 local stateful_mnt=$(mktemp -d) |
| 109 local out=$(mktemp) |
| 110 |
| 111 set +e |
| 112 sudo mount -o ro,loop,offset=$((stateful_offset * 512)) \ |
| 113 "$FLAGS_image" $stateful_mnt |
| 114 sudo cp "$stateful_mnt/vmlinuz_hd.vblock" "$out" |
| 115 sudo chown $USER "$out" |
| 116 |
| 117 sudo umount -d "$stateful_mnt" |
| 118 rmdir "$stateful_mnt" |
| 119 set -e |
| 120 echo "$out" |
| 121 } |
| 122 |
| 123 emerge_recovery_kernel() { |
| 124 echo "Emerging custom recovery initramfs and kernel" |
| 125 local emerge_flags="-uDNv1 --usepkg=n --selective=n" |
| 126 |
| 127 $EMERGE_BOARD_CMD \ |
| 128 $emerge_flags --binpkg-respect-use=y \ |
| 129 chromeos-initramfs || die "no initramfs" |
| 130 USE="initramfs" $EMERGE_BOARD_CMD \ |
| 131 $emerge_flags --binpkg-respect-use=y \ |
| 132 virtual/kernel |
| 133 } |
| 134 |
| 135 create_recovery_kernel_image() { |
| 136 local sysroot="${FLAGS_build_root}/${FLAGS_board}" |
| 137 local vmlinuz="$sysroot/boot/vmlinuz" |
| 138 local root_dev=$(sudo losetup -f) |
| 139 local root_offset=$(partoffset "$FLAGS_image" 3) |
| 140 local root_size=$(partsize "$FLAGS_image" 3) |
| 141 |
| 142 sudo losetup \ |
| 143 -o $((root_offset * 512)) \ |
| 144 --sizelimit $((root_size * 512)) \ |
| 145 "$root_dev" \ |
| 146 "$FLAGS_image" |
| 147 |
| 148 trap "sudo losetup -d $root_dev" EXIT |
| 149 |
| 150 cros_root=/dev/sd%D%P |
| 151 if [[ "${ARCH}" = "arm" ]]; then |
| 152 cros_root='/dev/${devname}${rootpart}' |
| 153 fi |
| 154 if grep -q enable_rootfs_verification "${IMAGE_DIR}/boot.desc"; then |
| 155 cros_root=/dev/dm-0 |
| 156 fi |
| 157 # TODO(wad) LOAD FROM IMAGE KERNEL AND NOT BOOT.DESC |
| 158 local verity_args=$(grep -- '--verity_' "${IMAGE_DIR}/boot.desc") |
| 159 # Convert the args to the right names and clean up extra quoting. |
| 160 # TODO(wad) just update these everywhere |
| 161 verity_args=$(echo $verity_args | sed \ |
| 162 -e 's/verity_algorithm/verity_hash_alg/g' \ |
| 163 -e 's/verity_depth/verity_tree_depth/g' \ |
| 164 -e 's/"//g') |
| 165 |
| 166 # Tie the installed recovery kernel to the final kernel. If we don't |
| 167 # do this, a normal recovery image could be used to drop an unsigned |
| 168 # kernel on without a key-change check. |
| 169 # Doing this here means that the kernel and initramfs creation can |
| 170 # be done independently from the image to be modified as long as the |
| 171 # chromeos-recovery interfaces are the same. It allows for the signer |
| 172 # to just compute the new hash and update the kernel command line during |
| 173 # recovery image generation. (Alternately, it means an image can be created, |
| 174 # modified for recovery, then passed to a signer which can then sign both |
| 175 # partitions appropriately without needing any external dependencies.) |
| 176 local kern_offset=$(partoffset "$FLAGS_image" 2) |
| 177 local kern_size=$(partsize "$FLAGS_image" 2) |
| 178 local kern_tmp=$(mktemp) |
| 179 local kern_hash= |
| 180 |
| 181 dd if="$FLAGS_image" bs=512 count=$kern_size skip=$kern_offset of="$kern_tmp" |
| 182 # We're going to use the real signing block. |
| 183 if [ $FLAGS_sync_keys -eq $FLAGS_TRUE ]; then |
| 184 dd if="$INSTALL_VBLOCK" of="$kern_tmp" conv=notrunc |
| 185 fi |
| 186 local kern_hash=$(sha1sum "$kern_tmp" | cut -f1 -d' ') |
| 187 rm "$kern_tmp" |
| 188 |
| 189 # TODO(wad) add FLAGS_boot_args support too. |
| 190 ${SCRIPTS_DIR}/build_kernel_image.sh \ |
| 191 --arch="${ARCH}" \ |
| 192 --to="$RECOVERY_KERNEL_IMAGE" \ |
| 193 --hd_vblock="$RECOVERY_KERNEL_VBLOCK" \ |
| 194 --vmlinuz="$vmlinuz" \ |
| 195 --working_dir="${IMAGE_DIR}" \ |
| 196 --boot_args="panic=60 cros_recovery kern_b_hash=$kern_hash" \ |
| 197 --keep_work \ |
| 198 --rootfs_image=${root_dev} \ |
| 199 --rootfs_hash=${FLAGS_rootfs_hash} \ |
| 200 --root=${cros_root} \ |
| 201 --keys_dir="${FLAGS_keys_dir}" \ |
| 202 --nouse_dev_keys \ |
| 203 ${verity_args} |
| 204 sudo rm "$FLAGS_rootfs_hash" |
| 205 sudo losetup -d "$root_dev" |
| 206 trap - RETURN |
| 207 |
| 208 # Update the EFI System Partition configuration so that the kern_hash check |
| 209 # passes. |
| 210 local efi_dev=$(sudo losetup -f) |
| 211 local efi_offset=$(partoffset "$FLAGS_image" 12) |
| 212 local efi_size=$(partsize "$FLAGS_image" 12) |
| 213 |
| 214 sudo losetup \ |
| 215 -o $((efi_offset * 512)) \ |
| 216 --sizelimit $((efi_size * 512)) \ |
| 217 "$efi_dev" \ |
| 218 "$FLAGS_image" |
| 219 local efi_dir=$(mktemp -d) |
| 220 trap "sudo losetup -d $efi_dev && rmdir \"$efi_dir\"" EXIT |
| 221 sudo mount "$efi_dev" "$efi_dir" |
| 222 sudo sed -i -e "s/cros_legacy/cros_legacy kern_b_hash=$kern_hash/g" \ |
| 223 "$efi_dir/syslinux/usb.A.cfg" || true |
| 224 # This will leave the hash in the kernel for all boots, but that should be |
| 225 # safe. |
| 226 sudo sed -i -e "s/cros_efi/cros_efi kern_b_hash=$kern_hash/g" \ |
| 227 "$efi_dir/efi/boot/grub.cfg" || true |
| 228 sudo umount "$efi_dir" |
| 229 sudo losetup -d "$efi_dev" |
| 230 rmdir "$efi_dir" |
| 231 trap - EXIT |
| 232 } |
| 233 |
| 234 install_recovery_kernel() { |
| 235 local kern_a_offset=$(partoffset "$RECOVERY_IMAGE" 2) |
| 236 local kern_a_size=$(partsize "$RECOVERY_IMAGE" 2) |
| 237 local kern_b_offset=$(partoffset "$RECOVERY_IMAGE" 4) |
| 238 local kern_b_size=$(partsize "$RECOVERY_IMAGE" 4) |
| 239 # Backup original kernel to KERN-B |
| 240 dd if="$RECOVERY_IMAGE" of="$RECOVERY_IMAGE" bs=512 \ |
| 241 count=$kern_a_size \ |
| 242 skip=$kern_a_offset \ |
| 243 seek=$kern_b_offset \ |
| 244 conv=notrunc |
| 245 |
| 246 # We're going to use the real signing block. |
| 247 if [ $FLAGS_sync_keys -eq $FLAGS_TRUE ]; then |
| 248 dd if="$INSTALL_VBLOCK" of="$RECOVERY_IMAGE" bs=512 \ |
| 249 seek=$kern_b_offset \ |
| 250 conv=notrunc |
| 251 fi |
| 252 |
| 253 # Install the recovery kernel as primary. |
| 254 dd if="$RECOVERY_KERNEL_IMAGE" of="$RECOVERY_IMAGE" bs=512 \ |
| 255 seek=$kern_a_offset \ |
| 256 count=$kern_a_size \ |
| 257 conv=notrunc |
| 258 |
| 259 # Repeat for the legacy bioses. |
| 260 # Replace vmlinuz.A with the recovery version |
| 261 local sysroot="${FLAGS_build_root}/${FLAGS_board}" |
| 262 local vmlinuz="$sysroot/boot/vmlinuz" |
| 263 local esp_offset=$(partoffset "$RECOVERY_IMAGE" 12) |
| 264 local esp_mnt=$(mktemp -d) |
| 265 set +e |
| 266 local failed=0 |
| 267 sudo mount -o loop,offset=$((esp_offset * 512)) "$RECOVERY_IMAGE" "$esp_mnt" |
| 268 sudo cp "$vmlinuz" "$esp_mnt/syslinux/vmlinuz.A" || failed=1 |
| 269 sudo umount -d "$esp_mnt" |
| 270 rmdir "$esp_mnt" |
| 271 set -e |
| 272 if [ $failed -eq 1 ]; then |
| 273 echo "Failed to copy recovery kernel to ESP" |
| 274 return 1 |
| 275 fi |
| 276 return 0 |
| 277 } |
| 278 |
| 279 maybe_resize_stateful() { |
| 280 # Rebuild the image with a 1 sector stateful partition |
| 281 local err=0 |
| 282 local small_stateful=$(mktemp) |
| 283 dd if=/dev/zero of="$small_stateful" bs=512 \ |
| 284 count=${FLAGS_statefulfs_sectors} |
| 285 trap "rm $small_stateful" RETURN |
| 286 # Don't bother with ext3 for such a small image. |
| 287 /sbin/mkfs.ext2 -F -b 4096 "$small_stateful" |
| 288 |
| 289 # If it exists, we need to copy the vblock over to stateful |
| 290 # This is the real vblock and not the recovery vblock. |
| 291 local new_stateful_mnt=$(mktemp -d) |
| 292 |
| 293 set +e |
| 294 sudo mount -o loop $small_stateful $new_stateful_mnt |
| 295 sudo cp "$INSTALL_VBLOCK" "$new_stateful_mnt/vmlinuz_hd.vblock" |
| 296 sudo mkdir "$new_stateful_mnt/var" |
| 297 sudo umount -d "$new_stateful_mnt" |
| 298 rmdir "$new_stateful_mnt" |
| 299 set -e |
| 300 |
| 301 # Create a recovery image of the right size |
| 302 # TODO(wad) Make the developer script case create a custom GPT with |
| 303 # just the kernel image and stateful. |
| 304 update_partition_table "$FLAGS_image" "$small_stateful" 4096 "$RECOVERY_IMAGE" |
| 305 return $err |
| 306 } |
| 307 |
| 308 # main process begins here. |
| 309 |
| 310 # Make sure this is really what the user wants, before nuking the device |
| 311 echo "Creating recovery image ${FLAGS_to} from ${FLAGS_image} . . . " |
| 312 |
| 313 set -e |
51 set -u | 314 set -u |
52 set -e | 315 |
53 | |
54 # Constants | |
55 IMAGE_DIR="$(dirname "$FLAGS_image")" | 316 IMAGE_DIR="$(dirname "$FLAGS_image")" |
56 | 317 IMAGE_NAME="$(basename "$FLAGS_image")" |
57 # Creates a dev recovery image using an existing dev install shim | 318 RECOVERY_IMAGE="${FLAGS_to:-$IMAGE_DIR/recovery_image.bin}" |
58 # If successful, content of --payload_dir is copied to a directory named | 319 RECOVERY_KERNEL_IMAGE=\ |
59 # "dev_payload" under the root of stateful partition. | 320 "${FLAGS_kernel_outfile:-${IMAGE_DIR}/recovery_vmlinuz.image}" |
60 create_recovery_image() { | 321 RECOVERY_KERNEL_VBLOCK="${RECOVERY_KERNEL_IMAGE}.vblock" |
61 local src_img=$1 # base image | 322 STATEFUL_DIR="$IMAGE_DIR/stateful_partition" |
62 local src_state=$(mktemp "/tmp/src_state.XXXXXX") | 323 SCRIPTS_DIR=$(dirname "$0") |
63 local stateful_offset=$(partoffset ${src_img} 1) | 324 |
64 local stateful_count=$(partsize ${src_img} 1) | 325 # Mounts gpt image and sets up var, /usr/local and symlinks. |
65 | 326 # If there's a dev payload, mount stateful |
66 dd if="${src_img}" of="${src_state}" conv=notrunc bs=512 \ | 327 # offset=$(partoffset "${FLAGS_from}/${filename}" 1) |
67 skip=${stateful_offset} count=${stateful_count} | 328 # sudo mount ${ro_flag} -o loop,offset=$(( offset * 512 )) \ |
68 | 329 # "${FLAGS_from}/${filename}" "${FLAGS_stateful_mountpt}" |
69 # Mount original stateful partition to figure out its actual size | 330 # If not, resize stateful to 1 sector. |
70 local src_loop_dev=$(get_loop_dev) | 331 # |
71 trap "cleanup_loop_dev ${src_loop_dev}" EXIT | 332 |
72 | 333 if [ $FLAGS_kernel_image_only -eq $FLAGS_TRUE -a \ |
73 # Setup loop dev | 334 -n "$FLAGS_kernel_image" ]; then |
74 sudo losetup $src_loop_dev $src_state | 335 die "Cannot use --kernel_image_only with --kernel_image" |
75 local block_size=$(sudo /sbin/dumpe2fs $src_loop_dev | grep "Block size:" \ | 336 fi |
76 | tr -d ' ' | cut -f2 -d:) | 337 |
77 echo "block_size = $block_size" | 338 INSTALL_VBLOCK=$(get_install_vblock) |
78 local min_size=$(sudo /sbin/resize2fs -P $src_loop_dev | tr -d ' ' \ | 339 if [ -z "$INSTALL_VBLOCK" ]; then |
79 | cut -f2 -d:) | 340 die "Could not copy the vblock from stateful." |
80 echo "min_size = $min_size $block_size blocks" | 341 fi |
81 | 342 |
82 # Add 20%, convert to 512-byte sectors and round up to 2Mb boundary | 343 if [ -z "$FLAGS_kernel_image" ]; then |
83 local min_sectors=$(roundup $(((min_size * block_size * 120) / (512 * 100)))) | 344 emerge_recovery_kernel |
84 echo "min_sectors = ${min_sectors} 512-byte blocks" | 345 create_recovery_kernel_image |
85 sudo e2fsck -fp "${src_loop_dev}" | |
86 # Resize using 512-byte sectors | |
87 sudo /sbin/resize2fs $src_loop_dev ${min_sectors}s | |
88 | |
89 # Delete the loop | |
90 trap - EXIT | |
91 cleanup_loop_dev ${src_loop_dev} | |
92 | |
93 # Truncate the image at the new size | |
94 dd if=/dev/zero of=$src_state bs=512 seek=$min_sectors count=0 | |
95 | |
96 # Mount and touch .recovery # Soon not to be needed :/ | |
97 local new_mnt=$(mktemp -d "/tmp/src_mnt.XXXXXX") | |
98 mkdir -p "${new_mnt}" | |
99 local new_loop_dev=$(get_loop_dev) | |
100 trap "cleanup_loop_dev ${new_loop_dev} && rmdir ${new_mnt} && \ | |
101 rm -f ${src_state}" EXIT | |
102 sudo mount -o loop=${new_loop_dev} "${src_state}" "${new_mnt}" | |
103 trap "umount_from_loop_dev ${new_mnt} && rm -f ${src_state}" EXIT | |
104 sudo touch "${new_mnt}/.recovery" | |
105 | |
106 (update_partition_table $src_img $src_state $min_sectors $TEMP_IMG) | |
107 # trap handler will handle unmount and clean up of loop device and temp files | |
108 } | |
109 | |
110 # Main | |
111 DST_PATH="${IMAGE_DIR}/${FLAGS_output}" | |
112 echo "Making a copy of original image ${FLAGS_image}" | |
113 (create_recovery_image $FLAGS_image) | |
114 | |
115 if [ -n ${TEMP_IMG} ] && [ -f ${TEMP_IMG} ]; then | |
116 mv -f $TEMP_IMG $DST_PATH | |
117 echo "Recovery image created at ${DST_PATH}" | |
118 else | 346 else |
119 echo "Failed to create recovery image" | 347 RECOVERY_KERNEL_IMAGE="$FLAGS_kernel_image" |
120 fi | 348 fi |
| 349 echo "Kernel emitted: $RECOVERY_KERNEL_IMAGE." |
| 350 |
| 351 if [ $FLAGS_kernel_image_only -eq $FLAGS_TRUE ]; then |
| 352 echo "Kernel emitted. Stopping there." |
| 353 rm "$INSTALL_VBLOCK" |
| 354 exit 0 |
| 355 fi |
| 356 |
| 357 rm "$RECOVERY_IMAGE" || true # Start fresh :) |
| 358 |
| 359 trap "rm \"$RECOVERY_IMAGE\" && rm \"$INSTALL_VBLOCK\"" EXIT |
| 360 |
| 361 maybe_resize_stateful # Also copies the image |
| 362 |
| 363 install_recovery_kernel |
| 364 |
| 365 print_time_elapsed |
| 366 trap - EXIT |
OLD | NEW |