Files
pawlet_rpi4/mkimg.sh
oxmc 7da1ee2db5 mkimg.sh: round --slim's shrunk partition sizes up to whole MB
shrink_ext4_image's returned byte size wasn't MB-aligned (ext4 block size
is 4096 bytes, not 1048576), but the GPT partition sizing later does
SIZE / 1048576 with integer division -- truncating down, making the
partition up to ~1MB smaller than the actual shrunk file being dd'd into
it. Confirmed on real hardware: dd failed with "No space left on device"
on system/system_ext/vendor despite --slim's own reported sizes matching
what got written to the GPT table. Round up to the next MB boundary
before truncating so the later division is always exact.
2026-08-16 05:06:43 -07:00

227 lines
9.7 KiB
Bash
Executable File

#!/bin/bash
#
# Copyright (C) 2021-2022 KonstaKANG
#
# SPDX-License-Identifier: Apache-2.0
#
exit_with_error() {
echo $@
exit 1
}
# --slim: shrink system/system_ext/vendor to their actual used size instead
# of the fixed BoardConfig.mk partition sizes (8GB/4GB/2GB regardless of real
# content). userdata/metadata stay at their current small fixed sizes either
# way — growing userdata to fill whatever the real target device has left is
# a separate, deferred piece of work (first-boot on-device growth, matching
# how Raspberry Pi OS does it), not something this script can do at
# build/image-creation time since the real device size isn't known yet.
SLIM=0
if [ "$1" == "--slim" ]; then
SLIM=1
fi
# Shrinks a copy of an ext4 image to its minimum size with resize2fs -M,
# leaving the copy at $2 truncated to exactly that size. Prints the new size
# in bytes on stdout so the caller can size the GPT partition to match.
shrink_ext4_image() {
local src=$1 dst=$2
cp --sparse=always "${src}" "${dst}"
sudo e2fsck -fy "${dst}" > /dev/null 2>&1
local fsck_rc=$?
# 0 = clean, 1 = errors corrected — both fine to proceed from. Anything
# higher means e2fsck couldn't fully fix it; don't silently ship that.
if [ ${fsck_rc} -gt 1 ]; then
exit_with_error "e2fsck failed on ${src} (exit ${fsck_rc}), refusing to shrink it."
fi
sudo resize2fs -M "${dst}" > /dev/null
local blocks=$(sudo dumpe2fs -h "${dst}" 2>/dev/null | awk -F: '/Block count/{gsub(/ /,"",$2); print $2}')
local bsize=$(sudo dumpe2fs -h "${dst}" 2>/dev/null | awk -F: '/Block size/{gsub(/ /,"",$2); print $2}')
# Small safety margin on top of resize2fs's own minimum, same rationale as
# the 32MB GPT overhead below — leaves a little slack for e.g. journal
# replay on first mount rather than shipping the absolute tightest fit.
# Then round UP to a whole MiB: the GPT partition gets sized in MB later
# (SIZE / 1048576, integer division), and if this byte count isn't already
# MB-aligned that truncates the partition *smaller* than this file, and dd
# runs out of space partway through the copy.
local raw=$(( blocks * bsize + 16777216 ))
local newsize=$(( ((raw + 1048575) / 1048576) * 1048576 ))
sudo truncate -s ${newsize} "${dst}"
echo ${newsize}
}
if [ -z ${TARGET_PRODUCT} ]; then
exit_with_error "TARGET_PRODUCT environment variable is not set. Run lunch first."
fi
if [ -z ${ANDROID_PRODUCT_OUT} ]; then
exit_with_error "ANDROID_PRODUCT_OUT environment variable is not set. Run lunch first."
fi
for PARTITION in "boot" "system" "system_ext" "vendor"; do
if [ ! -f ${ANDROID_PRODUCT_OUT}/${PARTITION}.img ]; then
exit_with_error "Partition image not found. Run 'make ${PARTITION}image' first."
fi
done
VERSION=RaspberryVanillaAOSP16
DATE=$(date +%Y%m%d)
TARGET=$(echo ${TARGET_PRODUCT} | sed 's/^aosp_//')
if [ ${SLIM} -eq 1 ]; then
IMGNAME=${VERSION}-${DATE}-${TARGET}-slim.img
else
IMGNAME=${VERSION}-${DATE}-${TARGET}.img
fi
# Partition layout (A/B slots):
# p1: boot_a FAT32 128MB — active boot slot (kernel + ramdisk + dtb)
# p2: boot_b FAT32 128MB — inactive boot slot (copy of boot_a initially)
# p3: recovery FAT32 128MB — TWRP recovery
# p4: misc raw 4MB — A/B slot tracking (written by system app / boot control)
# p5: system ext4 8GB (--slim: shrunk to actual used size + margin)
# p6: system_ext ext4 4GB (--slim: shrunk to actual used size + margin)
# p7: vendor ext4 2.25GB (--slim: shrunk to actual used size + margin)
# p8: metadata ext4 64MB
# p9: userdata ext4 128MB
BOOT_A_SIZE=134217728 # 128MB
BOOT_B_SIZE=134217728 # 128MB
RECOVERY_SIZE=134217728 # 128MB
MISC_SIZE=4194304 # 4MB
SYSTEM_SIZE=8589934592 # 8GB
SYSTEM_EXT_SIZE=4294967296 # 4GB
VENDOR_SIZE=2415919104 # 2304M (2.25GB) -- must match BoardConfig.mk's
# BOARD_VENDORIMAGE_PARTITION_SIZE
METADATA_SIZE=67108864 # 64MB
USERDATA_SIZE=134217728 # 128MB
if [ -f ${ANDROID_PRODUCT_OUT}/${IMGNAME} ]; then
exit_with_error "${ANDROID_PRODUCT_OUT}/${IMGNAME} already exists!"
fi
SYSTEM_SRC=${ANDROID_PRODUCT_OUT}/system.img
SYSTEM_EXT_SRC=${ANDROID_PRODUCT_OUT}/system_ext.img
VENDOR_SRC=${ANDROID_PRODUCT_OUT}/vendor.img
if [ ${SLIM} -eq 1 ]; then
echo "--slim: shrinking system/system_ext/vendor to their actual used size..."
SLIM_TMPDIR=$(mktemp -d)
SYSTEM_SRC=${SLIM_TMPDIR}/system.img
SYSTEM_EXT_SRC=${SLIM_TMPDIR}/system_ext.img
VENDOR_SRC=${SLIM_TMPDIR}/vendor.img
SYSTEM_SIZE=$(shrink_ext4_image ${ANDROID_PRODUCT_OUT}/system.img ${SYSTEM_SRC})
SYSTEM_EXT_SIZE=$(shrink_ext4_image ${ANDROID_PRODUCT_OUT}/system_ext.img ${SYSTEM_EXT_SRC})
VENDOR_SIZE=$(shrink_ext4_image ${ANDROID_PRODUCT_OUT}/vendor.img ${VENDOR_SRC})
echo " system: $((SYSTEM_SIZE / 1048576))MB (was $((8589934592 / 1048576))MB)"
echo " system_ext: $((SYSTEM_EXT_SIZE / 1048576))MB (was $((4294967296 / 1048576))MB)"
echo " vendor: $((VENDOR_SIZE / 1048576))MB (was $((2147483648 / 1048576))MB)"
fi
# 32MB GPT overhead + alignment
TOTAL_SIZE=$((BOOT_A_SIZE + BOOT_B_SIZE + RECOVERY_SIZE + MISC_SIZE + SYSTEM_SIZE + SYSTEM_EXT_SIZE + VENDOR_SIZE + METADATA_SIZE + USERDATA_SIZE + 33554432))
echo "Creating image file ${ANDROID_PRODUCT_OUT}/${IMGNAME}..."
sudo fallocate -l ${TOTAL_SIZE} ${ANDROID_PRODUCT_OUT}/${IMGNAME}
sync
echo "Creating GPT partitions..."
sudo sgdisk -Z ${ANDROID_PRODUCT_OUT}/${IMGNAME}
sudo sgdisk -o ${ANDROID_PRODUCT_OUT}/${IMGNAME}
BOOT_A_MB=$((BOOT_A_SIZE / 1048576))
BOOT_B_MB=$((BOOT_B_SIZE / 1048576))
RECOVERY_MB=$((RECOVERY_SIZE / 1048576))
MISC_MB=$((MISC_SIZE / 1048576))
SYSTEM_MB=$((SYSTEM_SIZE / 1048576))
SYSTEM_EXT_MB=$((SYSTEM_EXT_SIZE / 1048576))
VENDOR_MB=$((VENDOR_SIZE / 1048576))
METADATA_MB=$((METADATA_SIZE / 1048576))
USERDATA_MB=$((USERDATA_SIZE / 1048576))
sudo sgdisk -n 1:2048:+${BOOT_A_MB}M -c 1:"boot_a" -t 1:0700 ${ANDROID_PRODUCT_OUT}/${IMGNAME}
sudo sgdisk -n 2:0:+${BOOT_B_MB}M -c 2:"boot_b" -t 2:0700 ${ANDROID_PRODUCT_OUT}/${IMGNAME}
sudo sgdisk -n 3:0:+${RECOVERY_MB}M -c 3:"recovery" -t 3:0700 ${ANDROID_PRODUCT_OUT}/${IMGNAME}
sudo sgdisk -n 4:0:+${MISC_MB}M -c 4:"misc" -t 4:8300 ${ANDROID_PRODUCT_OUT}/${IMGNAME}
sudo sgdisk -n 5:0:+${SYSTEM_MB}M -c 5:"system" -t 5:8300 ${ANDROID_PRODUCT_OUT}/${IMGNAME}
sudo sgdisk -n 6:0:+${SYSTEM_EXT_MB}M -c 6:"system_ext" -t 6:8300 ${ANDROID_PRODUCT_OUT}/${IMGNAME}
sudo sgdisk -n 7:0:+${VENDOR_MB}M -c 7:"vendor" -t 7:8300 ${ANDROID_PRODUCT_OUT}/${IMGNAME}
sudo sgdisk -n 8:0:+${METADATA_MB}M -c 8:"metadata" -t 8:8300 ${ANDROID_PRODUCT_OUT}/${IMGNAME}
sudo sgdisk -n 9:0:+${USERDATA_MB}M -c 9:"userdata" -t 9:8300 ${ANDROID_PRODUCT_OUT}/${IMGNAME}
# Mark boot_a as bootable
sudo sgdisk -A 1:set:2 ${ANDROID_PRODUCT_OUT}/${IMGNAME}
sync
LOOPDEV=$(sudo kpartx -av ${ANDROID_PRODUCT_OUT}/${IMGNAME} | awk 'NR==1{ sub(/p[0-9]+$/, "", $3); print $3 }')
if [ -z ${LOOPDEV} ]; then
exit_with_error "Unable to find loop device!"
fi
echo "Image mounted as /dev/${LOOPDEV}"
sleep 2
echo "Copying boot_a (slot A)..."
sudo dd if=${ANDROID_PRODUCT_OUT}/boot.img of=/dev/mapper/${LOOPDEV}p1 bs=1M
echo "Copying boot_b (slot B — clone of A)..."
sudo dd if=${ANDROID_PRODUCT_OUT}/boot.img of=/dev/mapper/${LOOPDEV}p2 bs=1M
# Stamp each boot partition with its A/B slot suffix in cmdline.txt.
# The boot_control HAL reads ro.boot.slot_suffix (set from this cmdline
# parameter by init) to know which slot is currently running.
TMPMNT=$(mktemp -d)
echo "Stamping androidboot.slot_suffix=_a in boot_a/cmdline.txt..."
sudo mount -t vfat /dev/mapper/${LOOPDEV}p1 ${TMPMNT}
if [ -f ${TMPMNT}/cmdline.txt ]; then
CMDLINE=$(sudo cat ${TMPMNT}/cmdline.txt | tr -d '\n' | sed 's/ androidboot\.slot_suffix=[^ ]*//g')
printf '%s androidboot.slot_suffix=_a\n' "${CMDLINE}" | sudo tee ${TMPMNT}/cmdline.txt > /dev/null
else
echo "androidboot.slot_suffix=_a" | sudo tee ${TMPMNT}/cmdline.txt > /dev/null
fi
sudo umount ${TMPMNT}
echo "Stamping androidboot.slot_suffix=_b in boot_b/cmdline.txt..."
sudo mount -t vfat /dev/mapper/${LOOPDEV}p2 ${TMPMNT}
if [ -f ${TMPMNT}/cmdline.txt ]; then
CMDLINE=$(sudo cat ${TMPMNT}/cmdline.txt | tr -d '\n' | sed 's/ androidboot\.slot_suffix=[^ ]*//g')
printf '%s androidboot.slot_suffix=_b\n' "${CMDLINE}" | sudo tee ${TMPMNT}/cmdline.txt > /dev/null
else
echo "androidboot.slot_suffix=_b" | sudo tee ${TMPMNT}/cmdline.txt > /dev/null
fi
sudo umount ${TMPMNT}
rmdir ${TMPMNT}
if [ -f ${ANDROID_PRODUCT_OUT}/recovery.img ]; then
echo "Copying recovery..."
sudo dd if=${ANDROID_PRODUCT_OUT}/recovery.img of=/dev/mapper/${LOOPDEV}p3 bs=1M
else
echo "No recovery.img found — leaving recovery partition empty."
fi
echo "Zeroing misc..."
sudo dd if=/dev/zero of=/dev/mapper/${LOOPDEV}p4 bs=1M count=${MISC_MB}
echo "Copying system..."
sudo dd if=${SYSTEM_SRC} of=/dev/mapper/${LOOPDEV}p5 bs=1M
echo "Copying system_ext..."
sudo dd if=${SYSTEM_EXT_SRC} of=/dev/mapper/${LOOPDEV}p6 bs=1M
echo "Copying vendor..."
sudo dd if=${VENDOR_SRC} of=/dev/mapper/${LOOPDEV}p7 bs=1M
echo "Formatting metadata..."
sudo mkfs.ext4 /dev/mapper/${LOOPDEV}p8 -I 512 -L metadata
echo "Formatting userdata..."
sudo mkfs.ext4 /dev/mapper/${LOOPDEV}p9 -I 512 -L userdata
sync
sudo kpartx -dv "${ANDROID_PRODUCT_OUT}/${IMGNAME}"
# kpartx -dv already tears down the loop device itself once the last
# partition mapping is removed, so an unconditional losetup -d here always
# fails ("No such device or address") — only detach if it's somehow still
# attached.
if losetup "/dev/${LOOPDEV}" > /dev/null 2>&1; then
sudo losetup -d "/dev/${LOOPDEV}"
fi
sudo chown ${USER}:${USER} ${ANDROID_PRODUCT_OUT}/${IMGNAME}
if [ ${SLIM} -eq 1 ]; then
sudo rm -rf ${SLIM_TMPDIR}
fi
echo "Done, created ${ANDROID_PRODUCT_OUT}/${IMGNAME}!"
exit 0