1#!/bin/bash 2# 3# Copyright (C) 2018 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16# 17 18set -e 19set -u 20 21SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P) 22 23usage() { 24 echo -n "usage: $0 [-h] [-s bullseye|bullseye-cuttlefish|bullseye-rockpi] " 25 echo -n "[-a i386|amd64|armhf|arm64] -k /path/to/kernel " 26 echo -n "-i /path/to/initramfs.gz [-d /path/to/dtb:subdir] " 27 echo "[-m http://mirror/debian] [-n rootfs] [-r initrd] [-e]" 28 exit 1 29} 30 31mirror=http://ftp.debian.org/debian 32suite=bullseye 33arch=amd64 34 35embed_kernel_initrd_dtb= 36dtb_subdir= 37ramdisk= 38rootfs= 39dtb= 40 41while getopts ":hs:a:m:n:r:k:i:d:e" opt; do 42 case "${opt}" in 43 h) 44 usage 45 ;; 46 s) 47 if [[ "${OPTARG%-*}" != "bullseye" ]]; then 48 echo "Invalid suite: ${OPTARG}" >&2 49 usage 50 fi 51 suite="${OPTARG}" 52 ;; 53 a) 54 arch="${OPTARG}" 55 ;; 56 m) 57 mirror="${OPTARG}" 58 ;; 59 n) 60 rootfs="${OPTARG}" 61 ;; 62 r) 63 ramdisk="${OPTARG}" 64 ;; 65 k) 66 kernel="${OPTARG}" 67 ;; 68 i) 69 initramfs="${OPTARG}" 70 ;; 71 d) 72 dtb="${OPTARG%:*}" 73 if [ "${OPTARG#*:}" != "${dtb}" ]; then 74 dtb_subdir="${OPTARG#*:}/" 75 fi 76 ;; 77 e) 78 embed_kernel_initrd_dtb=1 79 ;; 80 \?) 81 echo "Invalid option: ${OPTARG}" >&2 82 usage 83 ;; 84 :) 85 echo "Invalid option: ${OPTARG} requires an argument" >&2 86 usage 87 ;; 88 esac 89done 90 91# Disable Debian's "persistent" network device renaming 92cmdline="net.ifnames=0 rw 8250.nr_uarts=2 PATH=/usr/sbin:/usr/bin" 93 94# Pass down embedding option, if specified 95if [ -n "${embed_kernel_initrd_dtb}" ]; then 96 cmdline="${cmdline} embed_kernel_initrd_dtb=${embed_kernel_initrd_dtb}" 97fi 98 99case "${arch}" in 100 i386) 101 cmdline="${cmdline} console=ttyS0 exitcode=/dev/ttyS1" 102 machine="pc-i440fx-2.8,accel=kvm" 103 qemu="qemu-system-i386" 104 cpu="max" 105 ;; 106 amd64) 107 cmdline="${cmdline} console=ttyS0 exitcode=/dev/ttyS1" 108 machine="pc-i440fx-2.8,accel=kvm" 109 qemu="qemu-system-x86_64" 110 cpu="max" 111 ;; 112 armhf) 113 cmdline="${cmdline} console=ttyAMA0 exitcode=/dev/ttyS0" 114 machine="virt,gic-version=2" 115 qemu="qemu-system-arm" 116 cpu="cortex-a15" 117 ;; 118 arm64) 119 cmdline="${cmdline} console=ttyAMA0 exitcode=/dev/ttyS0" 120 machine="virt,gic-version=2" 121 qemu="qemu-system-aarch64" 122 cpu="cortex-a53" # "max" is too slow 123 ;; 124 *) 125 echo "Invalid arch: ${OPTARG}" >&2 126 usage 127 ;; 128esac 129 130if [[ -z "${rootfs}" ]]; then 131 rootfs="rootfs.${arch}.${suite}.$(date +%Y%m%d)" 132fi 133rootfs=$(realpath "${rootfs}") 134 135if [[ -z "${ramdisk}" ]]; then 136 ramdisk="initrd.${arch}.${suite}.$(date +%Y%m%d)" 137fi 138ramdisk=$(realpath "${ramdisk}") 139 140if [[ -z "${kernel}" ]]; then 141 echo "$0: Path to kernel image must be specified (with '-k')" 142 usage 143elif [[ ! -e "${kernel}" ]]; then 144 echo "$0: Kernel image not found at '${kernel}'" 145 exit 2 146fi 147 148if [[ -z "${initramfs}" ]]; then 149 echo "Path to initial ramdisk image must be specified (with '-i')" 150 usage 151elif [[ ! -e "${initramfs}" ]]; then 152 echo "Initial ramdisk image not found at '${initramfs}'" 153 exit 3 154fi 155 156# Sometimes it isn't obvious when the script fails 157failure() { 158 echo "Filesystem generation process failed." >&2 159 rm -f "${rootfs}" "${ramdisk}" 160} 161trap failure ERR 162 163# Import the package list for this release 164packages=$(cpp "${SCRIPT_DIR}/rootfs/${suite}.list" | grep -v "^#" | xargs | tr -s ' ' ',') 165 166# For the debootstrap intermediates 167tmpdir=$(mktemp -d) 168tmpdir_remove() { 169 echo "Removing temporary files.." >&2 170 sudo rm -rf "${tmpdir}" 171} 172trap tmpdir_remove EXIT 173 174workdir="${tmpdir}/_" 175mkdir "${workdir}" 176chmod 0755 "${workdir}" 177sudo chown root:root "${workdir}" 178 179# Run the debootstrap first 180cd "${workdir}" 181sudo debootstrap --arch="${arch}" --variant=minbase --include="${packages}" \ 182 --foreign "${suite%-*}" . "${mirror}" 183 184# Copy some bootstrapping scripts into the rootfs 185sudo cp -a "${SCRIPT_DIR}"/rootfs/*.sh root/ 186sudo cp -a "${SCRIPT_DIR}"/rootfs/net_test.sh sbin/net_test.sh 187sudo chown root:root sbin/net_test.sh 188 189# Extract the ramdisk to bootstrap with to / 190lz4 -lcd "${initramfs}" | sudo cpio -idum lib/modules/* 191 192# Create /host, for the pivot_root and 9p mount use cases 193sudo mkdir host 194 195# Leave the workdir, to build the filesystem 196cd - 197 198# For the initial ramdisk, and later for the final rootfs 199mount=$(mktemp -d) 200mount_remove() { 201 rmdir "${mount}" 202 tmpdir_remove 203} 204trap mount_remove EXIT 205 206# The initial ramdisk filesystem must be <=512M, or QEMU's -initrd 207# option won't touch it 208initrd=$(mktemp) 209initrd_remove() { 210 rm -f "${initrd}" 211 mount_remove 212} 213trap initrd_remove EXIT 214truncate -s 512M "${initrd}" 215mke2fs -F -t ext3 -L ROOT "${initrd}" 216 217# Mount the new filesystem locally 218sudo mount -o loop -t ext3 "${initrd}" "${mount}" 219image_unmount() { 220 sudo umount "${mount}" 221 initrd_remove 222} 223trap image_unmount EXIT 224 225# Copy the patched debootstrap results into the new filesystem 226sudo cp -a "${workdir}"/* "${mount}" 227sudo rm -rf "${workdir}" 228 229# Unmount the initial ramdisk 230sudo umount "${mount}" 231trap initrd_remove EXIT 232 233# Copy the initial ramdisk to the final rootfs name and extend it 234sudo cp -a "${initrd}" "${rootfs}" 235truncate -s 2G "${rootfs}" 236e2fsck -p -f "${rootfs}" || true 237resize2fs "${rootfs}" 238 239# Create another fake block device for initrd.img writeout 240raw_initrd=$(mktemp) 241raw_initrd_remove() { 242 rm -f "${raw_initrd}" 243 initrd_remove 244} 245trap raw_initrd_remove EXIT 246truncate -s 64M "${raw_initrd}" 247 248# Complete the bootstrap process using QEMU and the specified kernel 249${qemu} -machine "${machine}" -cpu "${cpu}" -m 2048 >&2 \ 250 -kernel "${kernel}" -initrd "${initrd}" -no-user-config -nodefaults \ 251 -no-reboot -display none -nographic -serial stdio -parallel none \ 252 -smp 8,sockets=8,cores=1,threads=1 \ 253 -object rng-random,id=objrng0,filename=/dev/urandom \ 254 -device virtio-rng-pci-non-transitional,rng=objrng0,id=rng0,max-bytes=1024,period=2000 \ 255 -drive file="${rootfs}",format=raw,if=none,aio=threads,id=drive-virtio-disk0 \ 256 -device virtio-blk-pci-non-transitional,scsi=off,drive=drive-virtio-disk0 \ 257 -drive file="${raw_initrd}",format=raw,if=none,aio=threads,id=drive-virtio-disk1 \ 258 -device virtio-blk-pci-non-transitional,scsi=off,drive=drive-virtio-disk1 \ 259 -chardev file,id=exitcode,path=exitcode \ 260 -device pci-serial,chardev=exitcode \ 261 -append "root=/dev/ram0 ramdisk_size=524288 init=/root/stage1.sh ${cmdline}" 262[[ -s exitcode ]] && exitcode=$(cat exitcode | tr -d '\r') || exitcode=2 263rm -f exitcode 264if [ "${exitcode}" != "0" ]; then 265 echo "Second stage debootstrap failed (err=${exitcode})" 266 exit "${exitcode}" 267fi 268 269# Fix up any issues from the unclean shutdown 270e2fsck -p -f "${rootfs}" || true 271 272# New workdir for the initrd extraction 273workdir="${tmpdir}/initrd" 274mkdir "${workdir}" 275chmod 0755 "${workdir}" 276sudo chown root:root "${workdir}" 277 278# Change into workdir to repack initramfs 279cd "${workdir}" 280 281# Process the initrd to remove kernel-specific metadata 282kernel_version=$(basename $(lz4 -lcd "${raw_initrd}" | sudo cpio -idumv 2>&1 | grep usr/lib/modules/ - | head -n1)) 283sudo rm -rf usr/lib/modules 284sudo mkdir -p usr/lib/modules 285 286# Debian symlinks /usr/lib to /lib, but we'd prefer the other way around 287# so that it more closely matches what happens in Android initramfs images. 288# This enables 'cat ramdiskA.img ramdiskB.img >ramdiskC.img' to "just work". 289sudo rm -f lib 290sudo mv usr/lib lib 291sudo ln -s /lib usr/lib 292 293# Repack the ramdisk to the final output 294find * | sudo cpio -H newc -o --quiet | lz4 -lc9 >"${ramdisk}" 295 296# Pack another ramdisk with the combined artifacts, for boot testing 297cat "${ramdisk}" "${initramfs}" >"${initrd}" 298 299# Leave workdir to boot-test combined initrd 300cd - 301 302# Mount the new filesystem locally 303sudo mount -o loop -t ext3 "${rootfs}" "${mount}" 304image_unmount2() { 305 sudo umount "${mount}" 306 raw_initrd_remove 307} 308trap image_unmount2 EXIT 309 310# Embed the kernel and dtb images now, if requested 311if [ -n "${embed_kernel_initrd_dtb}" ]; then 312 if [ -n "${dtb}" ]; then 313 sudo mkdir -p "${mount}/boot/dtb/${dtb_subdir}" 314 sudo cp -a "${dtb}" "${mount}/boot/dtb/${dtb_subdir}" 315 sudo chown -R root:root "${mount}/boot/dtb/${dtb_subdir}" 316 fi 317 sudo cp -a "${kernel}" "${mount}/boot/vmlinuz-${kernel_version}" 318 sudo chown root:root "${mount}/boot/vmlinuz-${kernel_version}" 319fi 320 321# Unmount the initial ramdisk 322sudo umount "${mount}" 323trap raw_initrd_remove EXIT 324 325# Boot test the new system and run stage 3 326${qemu} -machine "${machine}" -cpu "${cpu}" -m 2048 >&2 \ 327 -kernel "${kernel}" -initrd "${initrd}" -no-user-config -nodefaults \ 328 -no-reboot -display none -nographic -serial stdio -parallel none \ 329 -smp 8,sockets=8,cores=1,threads=1 \ 330 -object rng-random,id=objrng0,filename=/dev/urandom \ 331 -device virtio-rng-pci-non-transitional,rng=objrng0,id=rng0,max-bytes=1024,period=2000 \ 332 -drive file="${rootfs}",format=raw,if=none,aio=threads,id=drive-virtio-disk0 \ 333 -device virtio-blk-pci-non-transitional,scsi=off,drive=drive-virtio-disk0 \ 334 -chardev file,id=exitcode,path=exitcode \ 335 -device pci-serial,chardev=exitcode \ 336 -netdev user,id=usernet0,ipv6=off \ 337 -device virtio-net-pci-non-transitional,netdev=usernet0,id=net0 \ 338 -append "root=LABEL=ROOT init=/root/${suite}.sh ${cmdline}" 339[[ -s exitcode ]] && exitcode=$(cat exitcode | tr -d '\r') || exitcode=2 340rm -f exitcode 341if [ "${exitcode}" != "0" ]; then 342 echo "Root filesystem finalization failed (err=${exitcode})" 343 exit "${exitcode}" 344fi 345 346# Fix up any issues from the unclean shutdown 347e2fsck -p -f "${rootfs}" || true 348 349# Mount the final rootfs locally 350sudo mount -o loop -t ext3 "${rootfs}" "${mount}" 351image_unmount3() { 352 sudo umount "${mount}" 353 raw_initrd_remove 354} 355trap image_unmount3 EXIT 356 357# Fill the rest of the space with zeroes, to optimize compression 358sudo dd if=/dev/zero of="${mount}/sparse" bs=1M 2>/dev/null || true 359sudo rm -f "${mount}/sparse" 360 361echo "Debian ${suite} for ${arch} filesystem generated at '${rootfs}'." 362echo "Initial ramdisk generated at '${ramdisk}'." 363