• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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