mod_image_for_recovery: dynamically adjust kern-A size
This CL changes the copy-and-modify flow to:
1) Build recovery kernel.
2) Create an empty recovery image with correct KERN-A and STATE size.
3) Copy partitions from source image to the recovery image.
4) Copy recovery kernel to the recovery image.
5) Update EFI partition.
This change breaks --modify_in_place because it will be implemented
by creating a temporary image and than move back to the source image
path. Not technically in-place, but should be acceptable.
BUG=chromium:873135
TEST=1) Create an recovery ramfs with >32MB junk files, build recovery image and
boot on DUT successfully.
2) Verify the script fails when a super large kernel image is
provided.
3) cros tryjob -g 2294783 puff-release-tryjob
Change-Id: If88e9f7893816ab40085d5cfeb360573682bfe57
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosutils/+/2294783
Tested-by: Ting Shen <phoenixshen@chromium.org>
Commit-Queue: Ting Shen <phoenixshen@chromium.org>
Reviewed-by: Mike Frysinger <vapier@chromium.org>
diff --git a/build_library/disk_layout_util.sh b/build_library/disk_layout_util.sh
index 2f53021..25114b5 100644
--- a/build_library/disk_layout_util.sh
+++ b/build_library/disk_layout_util.sh
@@ -38,14 +38,16 @@
}
write_partition_script() {
- local image_type=$1
- local partition_script_path=$2
+ local image_type="$1"
+ local partition_script_path="$2"
+ local adjust_part="$3"
get_disk_layout_path
local temp_script_file=$(mktemp)
sudo mkdir -p "$(dirname "${partition_script_path}")"
- cgpt_py write "${image_type}" "${DISK_LAYOUT_PATH}" \
+ cgpt_py ${adjust_part:+--adjust_part "${adjust_part}"} \
+ write "${image_type}" "${DISK_LAYOUT_PATH}" \
"${temp_script_file}"
sudo mv "${temp_script_file}" "${partition_script_path}"
sudo chmod a+r "${partition_script_path}"
@@ -524,10 +526,13 @@
build_gpt_image() {
local outdev="$1"
local disk_layout="$2"
+ local adjust_part="$3"
# Build the partition table and partition script.
- local partition_script_path="$(dirname "${outdev}")/partition_script.sh"
- write_partition_script "${disk_layout}" "${partition_script_path}"
+ local partition_script_path
+ partition_script_path="$(dirname "${outdev}")/partition_script.sh"
+ write_partition_script "${disk_layout}" "${partition_script_path}" \
+ "${adjust_part}"
run_partition_script "${outdev}" "${partition_script_path}"
# Emit the gpt scripts so we can use them from here on out.
diff --git a/build_library/disk_layout_v2.json b/build_library/disk_layout_v2.json
index d21f45d..eeec344 100644
--- a/build_library/disk_layout_v2.json
+++ b/build_library/disk_layout_v2.json
@@ -154,6 +154,13 @@
# Used for recovery images.
"recovery": [
{
+ # Kernel for Slot A, no file system.
+ # Make the partition as large as the firmware supports so we can boot
+ # larger recovery kernels. The kernel won't actually be this large.
+ "num": 2,
+ "size": "512 MiB"
+ },
+ {
# Slot B rootfs, unused on USB, but pad to 2M.
# installation will expand this to size from base.
"num": 5,
diff --git a/mod_image_for_recovery.sh b/mod_image_for_recovery.sh
index ca07f2a..dd76aa7 100755
--- a/mod_image_for_recovery.sh
+++ b/mod_image_for_recovery.sh
@@ -100,6 +100,26 @@
echo "$out"
}
+calculate_kernel_hash() {
+ local img="$1"
+
+ local partition_num_kern_a kern_offset kern_size kern_tmp
+
+ partition_num_kern_a="$(get_image_partition_number "${img}" "KERN-A")"
+ kern_offset="$(partoffset "${img}" "${partition_num_kern_a}")"
+ kern_size="$(partsize "${img}" "${partition_num_kern_a}")"
+ kern_tmp=$(mktemp)
+
+ dd if="${FLAGS_image}" bs=512 count="${kern_size}" \
+ skip="${kern_offset}" of="${kern_tmp}" 1>&2
+ # We're going to use the real signing block.
+ if [[ "${FLAGS_sync_keys}" -eq "${FLAGS_TRUE}" ]]; then
+ dd if="${INSTALL_VBLOCK}" of="${kern_tmp}" conv=notrunc 1>&2
+ fi
+ sha256sum "${kern_tmp}" | cut -f1 -d' '
+ rm "${kern_tmp}"
+}
+
create_recovery_kernel_image() {
local sysroot="$FACTORY_ROOT"
local vmlinuz="$sysroot/boot/vmlinuz"
@@ -119,24 +139,9 @@
# recovery image generation. (Alternately, it means an image can be created,
# modified for recovery, then passed to a signer which can then sign both
# partitions appropriately without needing any external dependencies.)
- local partition_num_kern_a=$(get_image_partition_number "${RECOVERY_IMAGE}" \
- "KERN-A")
- local kern_offset=$(partoffset "${RECOVERY_IMAGE}" "${partition_num_kern_a}")
- local kern_size=$(partsize "${RECOVERY_IMAGE}" "${partition_num_kern_a}")
- local kern_tmp=$(mktemp)
- local kern_hash=
- dd if="${RECOVERY_IMAGE}" bs=512 count=$kern_size \
- skip=$kern_offset of="$kern_tmp" 1>&2
- # We're going to use the real signing block.
- if [ $FLAGS_sync_keys -eq $FLAGS_TRUE ]; then
- dd if="$INSTALL_VBLOCK" of="$kern_tmp" conv=notrunc 1>&2
- fi
- local kern_hash=$(sha256sum "$kern_tmp" | cut -f1 -d' ')
- rm "$kern_tmp"
- # Force all of the file writes to complete, in case it's necessary for
- # crbug.com/954188
- sync
+ local kern_hash
+ kern_hash="$(calculate_kernel_hash "${FLAGS_image}")"
# TODO(wad) add FLAGS_boot_args support too.
${SCRIPTS_DIR}/build_kernel_image.sh \
@@ -153,19 +158,18 @@
--public="recovery_key.vbpubk" \
--private="recovery_kernel_data_key.vbprivk" \
--keyblock="recovery_kernel.keyblock" 1>&2 || die "build_kernel_image"
- #sudo mount | sed 's/^/16651 /'
- #sudo losetup -a | sed 's/^/16651 /'
- trap - RETURN
+}
+update_efi_partition() {
# Update the EFI System Partition configuration so that the kern_hash check
# passes.
- local block_size=$(get_block_size)
-
RECOVERY_DEV=$(loopback_partscan "${RECOVERY_IMAGE}")
local partition_num_efi_system=$(get_image_partition_number \
"${RECOVERY_IMAGE}" "EFI-SYSTEM")
- local efi_size=$(partsize "${RECOVERY_IMAGE}" "${partition_num_efi_system}")
+ local efi_size kern_hash
+ efi_size=$(partsize "${RECOVERY_IMAGE}" "${partition_num_efi_system}")
+ kern_hash="$(calculate_kernel_hash "${RECOVERY_IMAGE}")"
if [[ ${efi_size} -ne 0 ]]; then
local efi_dir=$(mktemp -d)
@@ -179,10 +183,8 @@
"$efi_dir/efi/boot/grub.cfg" || true
safe_umount "$efi_dir"
rmdir "$efi_dir"
- sudo losetup -d ${RECOVERY_DEV}
fi
-
- trap - RETURN
+ sudo losetup -d "${RECOVERY_DEV}"
}
install_recovery_kernel() {
@@ -202,13 +204,6 @@
return 1
fi
- # Backup original kernel to KERN-B
- dd if="$RECOVERY_IMAGE" of="$RECOVERY_IMAGE" bs=512 \
- count=$kern_a_size \
- skip=$kern_a_offset \
- seek=$kern_b_offset \
- conv=notrunc
-
# We're going to use the real signing block.
if [ $FLAGS_sync_keys -eq $FLAGS_TRUE ]; then
dd if="$INSTALL_VBLOCK" of="$RECOVERY_IMAGE" bs=512 \
@@ -216,6 +211,12 @@
conv=notrunc
fi
+ local kernel_img_bytes
+ kernel_img_bytes="$(stat -c %s "${RECOVERY_KERNEL_IMAGE}")"
+ if [[ "${kernel_img_bytes}" -gt "$(( kern_a_size * 512 ))" ]]; then
+ die "Kernel image is larger than $(( kern_a_size * 512 / 1048576 )) MiB."
+ fi
+
# Install the recovery kernel as primary.
dd if="$RECOVERY_KERNEL_IMAGE" of="$RECOVERY_IMAGE" bs=512 \
seek=$kern_a_offset \
@@ -278,34 +279,36 @@
echo $(( sectors_needed + sectors_needed / 10 ))
}
-maybe_resize_stateful() {
- # If we're not minimizing, then just copy and go.
- if [ $FLAGS_minimize_image -eq $FLAGS_FALSE ]; then
- return 0
- fi
+# Copy the given list of files from old stateful partition to new stateful
+# partition.
+# Args:
+# $1: source image filename
+# $2: destination image filename
+copy_stateful() {
+ local src_img="$1"
+ local dst_img="$2"
local old_stateful_offset old_stateful_mnt sectors_needed
local small_stateful new_stateful_mnt
# Mount the old stateful partition so we can copy selected values
# off of it.
- local partition_num_state=$(get_image_partition_number \
- "${FLAGS_image}" "STATE")
- old_stateful_offset=$(partoffset "$FLAGS_image" "${partition_num_state}")
+ local partition_num_state
+ partition_num_state=$(get_image_partition_number "${dst_img}" "STATE")
old_stateful_mnt=$(mktemp -d)
- IMAGE_DEV=$(loopback_partscan "${FLAGS_image}")
- sudo mount ${IMAGE_DEV}p${partition_num_state} $old_stateful_mnt
+ IMAGE_DEV=$(loopback_partscan "${src_img}")
+ sudo mount "${IMAGE_DEV}p${partition_num_state}" "${old_stateful_mnt}"
- sectors_needed=$(find_sectors_needed "${old_stateful_mnt}" "${WHITELIST}")
+ sectors_needed="$(cgpt show -i "${partition_num_state}" -n -s "${dst_img}")"
# Rebuild the image with stateful partition sized by sectors_needed.
small_stateful=$(mktemp)
- dd if=/dev/zero of="$small_stateful" bs=512 \
+ dd if=/dev/zero of="${small_stateful}" bs=512 \
count="${sectors_needed}" 1>&2
- trap "rm $small_stateful; sudo losetup -d ${IMAGE_DEV} || true" RETURN
+ trap "rm ${small_stateful}; sudo losetup -d ${IMAGE_DEV} || true" RETURN
# Don't bother with ext3 for such a small image.
- /sbin/mkfs.ext2 -F -b 4096 "$small_stateful" 1>&2
+ /sbin/mkfs.ext2 -F -b 4096 "${small_stateful}" 1>&2
# If it exists, we need to copy the vblock over to stateful
# This is the real vblock and not the recovery vblock.
@@ -336,24 +339,129 @@
trap - RETURN
switch_to_strict_mode
- # Create a recovery image of the right size
- # TODO(wad) Make the developer script case create a custom GPT with
- # just the kernel image and stateful.
- update_partition_table "${FLAGS_image}" "$small_stateful" \
- "${sectors_needed}" \
- "${RECOVERY_IMAGE}" 1>&2
+ local dst_start
+ dst_start="$(cgpt show -i "${partition_num_state}" -b "${dst_img}")"
+ dd if="${small_stateful}" of="${dst_img}" conv=notrunc bs=512 \
+ seek="${dst_start}" count="${sectors_needed}" status=none
+ return 0
+}
+
+# Calculates the number of sectors required for stateful partition.
+# or returns the source stateful partition size if --minimize_image not present.
+calculate_stateful_blocks() {
+ local partition_num_state
+ partition_num_state="$(get_image_partition_number "${FLAGS_image}" "STATE")"
+
+ # If --minimize_image not present, use the partition size from source image,
+ # (not recovery image, it's hard-coded to 2MiB).
+ if [[ "${FLAGS_minimize_image}" -eq "${FLAGS_FALSE}" ]]; then
+ cgpt show -i "${partition_num_state}" -n -s "${FLAGS_image}"
+ return 0
+ fi
+
+ local old_stateful_mnt
+ old_stateful_mnt="$(mktemp -d)"
+
+ IMAGE_DEV=$(loopback_partscan "${FLAGS_image}")
+ sudo mount "${IMAGE_DEV}p${partition_num_state}" "${old_stateful_mnt}"
+
+ # Print the minimum number of sectors needed.
+ find_sectors_needed "${old_stateful_mnt}" "${WHITELIST}"
+
+ # Cleanup everything.
+ safe_umount "${old_stateful_mnt}"
+ rmdir "${old_stateful_mnt}"
+ sudo losetup -d "${IMAGE_DEV}"
+
+ return 0
+}
+
+# Creates an empty image using the recovery layout and calculated stateful size.
+create_image() {
+ local dst_img="$1"
+
+ local stateful_blocks
+ stateful_blocks="$(calculate_stateful_blocks)"
+
+ # Remove dst_img first otherwise build_gpt_image won't create a new one
+ # with correct layout.
+ rm -f "${dst_img}"
+
+ # Temporarily disable set -u because there's some empty array expansion in
+ # build_gpt_image.
+ set +u
+ build_gpt_image "${dst_img}" recovery "STATE:=$(( stateful_blocks * 512 ))"
+ set -u
+}
+
+# Copy the partitions one by one from source image to destination image,
+# except that KERN-A is moved to KERN-B.
+# Args:
+# $1: source image filename
+# $2: destination image filename
+copy_partitions() {
+ local src_img="$1"
+ local dst_img="$2"
+
+ local part=0
+ while :; do
+ : $(( part += 1 ))
+ local src_start
+ src_start="$(cgpt show -i "${part}" -b "${src_img}")"
+ if [[ "${src_start}" -eq 0 ]]; then
+ # No more partitions to copy.
+ break
+ fi
+
+ # Load source partition details.
+ local size label
+ size="$(cgpt show -i "${part}" -s "${src_img}")"
+ label="$(cgpt show -i "${part}" -l "${src_img}")"
+ if [[ "${size}" -eq 0 ]]; then
+ continue
+ fi
+
+ local dst_part="${part}"
+ # Move KERN-A to KERN-B.
+ if [[ ${label} == 'KERN-A' ]]; then
+ dst_part="$(get_image_partition_number "${dst_img}" 'KERN-B')"
+ fi
+
+ local dst_start dst_size
+ dst_start="$(cgpt show -i "${dst_part}" -b "${dst_img}")"
+ dst_size="$(cgpt show -i "${dst_part}" -s "${dst_img}")"
+
+ if [[ "${label}" == 'STATE' && \
+ "${FLAGS_minimize_image}" -eq "${FLAGS_TRUE}" ]]; then
+ copy_stateful "${src_img}" "${dst_img}"
+ elif [[ ${label} == 'KERN-B' ]]; then
+ : # Skip KERN-B.
+ else
+ # Copy other partition as-is.
+ if [[ "${size}" -gt "${dst_size}" ]]; then
+ die "Partition #${part} larger than the destination partition"
+ fi
+ dd if="${src_img}" of="${dst_img}" conv=notrunc bs=512 \
+ skip="${src_start}" seek="${dst_start}" count="${size}" \
+ status=none
+ sync
+ fi
+ done
return 0
}
cleanup() {
set +e
- if [ -n "${RECOVERY_DEV}" ]; then
- sudo losetup -d ${RECOVERY_DEV}
+ if [[ -n "${RECOVERY_DEV}" ]]; then
+ sudo losetup -d "${RECOVERY_DEV}"
fi
- if [ "$FLAGS_image" != "$RECOVERY_IMAGE" ]; then
- rm "$RECOVERY_IMAGE"
+ if [[ -n "${IMAGE_DEV}" ]]; then
+ sudo losetup -d "${IMAGE_DEV}"
fi
- rm "$INSTALL_VBLOCK"
+ if [[ "${FLAGS_image}" != "${RECOVERY_IMAGE}" ]]; then
+ rm "${RECOVERY_IMAGE}"
+ fi
+ rm "${INSTALL_VBLOCK}"
}
@@ -381,29 +489,13 @@
STATEFUL_DIR="$IMAGE_DIR/stateful_partition"
SCRIPTS_DIR=${SCRIPT_ROOT}
RECOVERY_DEV=""
-
-# Mounts gpt image and sets up var, /usr/local and symlinks.
-# If there's a dev payload, mount stateful
-# offset=$(partoffset "${FLAGS_from}/${filename}" 1)
-# sudo mount ${ro_flag} -o loop,offset=$(( offset * 512 )) \
-# "${FLAGS_from}/${filename}" "${FLAGS_stateful_mountpt}"
-# If not, resize stateful to 1 sector.
-#
+IMAGE_DEV=""
if [ $FLAGS_kernel_image_only -eq $FLAGS_TRUE -a \
-n "$FLAGS_kernel_image" ]; then
die_notrace "Cannot use --kernel_image_only with --kernel_image"
fi
-if [ $FLAGS_modify_in_place -eq $FLAGS_TRUE ]; then
- if [ $FLAGS_minimize_image -eq $FLAGS_TRUE ]; then
- die_notrace "Cannot use --modify_in_place and --minimize_image together."
- fi
- RECOVERY_IMAGE="${FLAGS_image}"
-else
- cp "${FLAGS_image}" "${RECOVERY_IMAGE}"
-fi
-
echo "Creating recovery image from ${FLAGS_image}"
INSTALL_VBLOCK=$(get_install_vblock)
@@ -433,7 +525,14 @@
trap cleanup EXIT
-maybe_resize_stateful # Also copies the image if needed.
+if [[ "${FLAGS_modify_in_place}" -eq "${FLAGS_TRUE}" ]]; then
+ # Implement in-place modification by creating a temp image and copy it back
+ # to the source image path later.
+ RECOVERY_IMAGE="$(mktemp)"
+fi
+create_image "${RECOVERY_IMAGE}"
+copy_partitions "${FLAGS_image}" "${RECOVERY_IMAGE}"
+sync
if [ $FLAGS_decrypt_stateful -eq $FLAGS_TRUE ]; then
stateful_mnt=$(mktemp -d)
@@ -448,6 +547,11 @@
fi
install_recovery_kernel
+update_efi_partition
+
+if [[ "${FLAGS_modify_in_place}" -eq "${FLAGS_TRUE}" ]]; then
+ mv "${RECOVERY_IMAGE}" "${FLAGS_image}"
+fi
echo "Recovery image created at $RECOVERY_IMAGE"
command_completed