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.
227 lines
9.7 KiB
Bash
Executable File
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 |