1#!/bin/bash 2# 3# Copyright (C) 2022 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 "usage: $0 [-h] -i input.raw -o output.iso" 25 exit 1 26} 27 28input= 29output= 30 31while getopts ":hi:o:" opt; do 32 case "${opt}" in 33 h) 34 usage 35 ;; 36 i) 37 input="${OPTARG}" 38 ;; 39 o) 40 output="${OPTARG}" 41 ;; 42 \?) 43 echo "Invalid option: ${OPTARG}" >&2 44 usage 45 ;; 46 :) 47 echo "Invalid option: ${OPTARG} requires an argument" >&2 48 usage 49 ;; 50 esac 51done 52 53if [[ -z "${input}" ]]; then 54 echo "Must specify input file!" 55 usage 56fi 57 58if [[ -z "${output}" ]]; then 59 echo "Must specify output file!" 60 usage 61fi 62 63grub_cmdline="ro net.ifnames=0 console=ttyAMA0 loglevel=4" 64grub_rootfs="LABEL=install" 65 66# Validate format of the input disk 67/sbin/sgdisk -p "${input}" | grep -q "Disk identifier (GUID)" || \ 68 ( echo "${input} is not a GUID partitioned disk!" && exit 2 ) 69partitions="$(/sbin/sgdisk -p "${input}" | \ 70 grep -m1 -A2 "Number Start (sector)" | tail -n2)" 71( IFS=$'\n' 72for line in $partitions; do 73 IFS=' ' read -r -a partition <<< "$line" 74 if [[ "${partition[0]}" = "1" && "${partition[5]}" != "EF00" ]]; then 75 echo "${input} partition 1 is not an ESP!" && exit 3 76 fi 77 if [[ "${partition[0]}" = "2" && "${partition[6]}" != "rootfs" ]]; then 78 echo "${input} partition 2 is not rootfs!" && exit 4 79 fi 80done ) 81 82failure() { 83 echo "ISO generation process failed." >&2 84 rm -f "${output}" 85} 86trap failure ERR 87 88mount=$(mktemp -d) 89mount_remove() { 90 rmdir "${mount}" 91} 92trap mount_remove EXIT 93 94workdir=$(mktemp -d) 95workdir_remove() { 96 rm -rf "${workdir}" 97 mount_remove 98} 99trap workdir_remove EXIT 100 101# Build a grub.cfg for CD booting 102cat >"${workdir}"/grub.cfg <<EOF 103set timeout=0 104menuentry "Linux" { 105 linux /vmlinuz ${grub_cmdline} root=${grub_rootfs} 106 initrd /initrd.img 107} 108EOF 109 110# Build harddisk install script 111cat >"${workdir}"/install.sh << EOF 112#!/bin/sh 113set -e 114set -u 115SCRIPT_DIR=\$(CDPATH= cd -- "\$(dirname -- "\${0}")" && pwd -P) 116if [ "\${1#*nvme}" != "\${1}" ]; then 117 partition=p 118else 119 partition= 120fi 121sgdisk --load-backup="\${SCRIPT_DIR}"/gpt.img \${1} 122sgdisk --delete=2 \${1} 123sgdisk --new=2:129M:0 --typecode=2:8305 --change-name=2:rootfs --attributes=2:set:2 \${1} 124partx -v --update \${1} 125dd if="\${SCRIPT_DIR}"/esp.img of=\${1}\${partition}1 bs=16M 126mkfs.ext4 -L ROOT -U \$(cat \${SCRIPT_DIR}/rootfs_uuid) \${1}\${partition}2 127mount \${1}\${partition}2 /media 128tar -C /media -Spxf \${SCRIPT_DIR}/rootfs.tar.xz 129umount /media 130EOF 131chmod a+x "${workdir}"/install.sh 132 133cat >"${workdir}"/override-getty.conf <<EOF 134[Service] 135ExecStart= 136ExecStart=-/sbin/agetty -a root --noclear tty1 \$TERM 137EOF 138 139cat >"${workdir}"/override-serial-getty.conf <<EOF 140[Service] 141ExecStart= 142ExecStart=-/sbin/agetty -a root --keep-baud 115200,57600,38400,9600 ttyAMA0 \$TERM 143EOF 144 145# Back up the GPT so we can restore it when installing 146/sbin/sgdisk --backup="${workdir}"/gpt.img "${input}" >/dev/null 147 148efi_partition_num=1 149efi_partition_start=$(partx -g -o START -s -n ${efi_partition_num} "${input}" | xargs) 150efi_partition_end=$(partx -g -o END -s -n ${efi_partition_num} "${input}" | xargs) 151efi_partition_offset=$((${efi_partition_start} * 512)) 152efi_partition_num_sectors=$((${efi_partition_end} - ${efi_partition_start} + 1)) 153 154# Back up the ESP so we can restore it when installing 155touch "${workdir}"/esp.img 156dd if="${input}" of="${workdir}"/esp.img bs=512 skip=${efi_partition_start} count=${efi_partition_num_sectors} status=none >/dev/null 157 158# Determine the architecture of the disk from the portable GRUB image path 159sudo mount -o loop,offset=${efi_partition_offset} "${input}" "${mount}" 160unmount() { 161 sudo umount "${mount}" 162} 163trap unmount EXIT 164grub_blob=$(cd "${mount}" && echo EFI/Boot/*) 165case "${grub_blob}" in 166 EFI/Boot/BOOTAA64.EFI) 167 grub_arch=arm64-efi 168 grub_cd=gcdaa64.efi 169 ;; 170 EFI/Boot/BOOTIA64.EFI) 171 grub_arch=x86_64-efi 172 grub_cd=gcdx64.efi 173 ;; 174 *) 175 echo "Unknown GRUB architecture for ${grub_blob}!" 176 exit 5 177 ;; 178esac 179sudo umount "${mount}" 180 181root_partition_num=2 182root_partition_start=$(partx -g -o START -s -n ${root_partition_num} "${input}" | xargs) 183root_partition_end=$(partx -g -o END -s -n ${root_partition_num} "${input}" | xargs) 184root_partition_offset=$((${root_partition_start} * 512)) 185 186# Mount original rootfs and remove previous patching, then tar 187rootfs_uuid=$(/sbin/blkid --probe -s UUID -o value -O ${root_partition_offset} "${input}") 188sudo mount -o loop,offset=${root_partition_offset} "${input}" "${mount}" 189trap unmount EXIT 190sudo rm -f "${mount}"/root/esp.img "${mount}"/root/gpt.img 191sudo rm -f "${mount}"/root/rootfs.tar.xz 192sudo rm -f "${mount}"/root/rootfs_uuid 193sudo rm -f "${mount}"/boot/grub/eltorito.img 194sudo rm -f "${mount}"/boot/grub/${grub_arch}/grub.cfg 195sudo rm -rf "${mount}"/tmp/* 196sudo rm -rf "${mount}"/var/tmp/* 197( cd "${mount}" && sudo tar -SJcpf "${workdir}"/rootfs.tar.xz * ) 198 199# Prepare a new ESP for the ISO's El Torito image 200mkdir -p "${workdir}/EFI/Boot" 201cp "${mount}/usr/lib/grub/${grub_arch}/monolithic/${grub_cd}" \ 202 "${workdir}/${grub_blob}" 203truncate -s 4M "${workdir}"/eltorito.img 204/sbin/mkfs.msdos -n SYSTEM -F 12 -M 0xf8 -h 0 -s 4 -g 64/32 -S 512 \ 205 "${workdir}"/eltorito.img >/dev/null 206mmd -i "${workdir}"/eltorito.img EFI EFI/Boot 207mcopy -o -i "${workdir}"/eltorito.img -s "${workdir}/EFI" :: 208 209# Build ISO from rootfs 210sudo cp "${workdir}"/esp.img "${workdir}"/gpt.img "${mount}"/root 211sudo cp "${workdir}"/rootfs.tar.xz "${workdir}"/install.sh "${mount}"/root 212echo -n "${rootfs_uuid}" | sudo tee "${mount}"/root/rootfs_uuid >/dev/null 213sudo cp "${workdir}"/eltorito.img "${mount}"/boot/grub 214sudo cp "${workdir}"/grub.cfg "${mount}"/boot/grub/${grub_arch}/grub.cfg 215sudo mkdir -p "${mount}"/etc/systemd/system/getty@tty1.service.d 216sudo cp "${workdir}"/override-getty.conf "${mount}"/etc/systemd/system/getty@tty1.service.d/override.conf 217sudo mkdir -p "${mount}"/etc/systemd/system/serial-getty@ttyAMA0.service.d 218sudo cp "${workdir}"/override-serial-getty.conf "${mount}"/etc/systemd/system/serial-getty@ttyAMA0.service.d/override.conf 219sudo chown root:root \ 220 "${mount}"/root/esp.img "${mount}"/root/gpt.img \ 221 "${mount}"/boot/grub/eltorito.img \ 222 "${mount}"/boot/grub/${grub_arch}/grub.cfg 223sudo mv "${mount}"/usr "${mount}"/usr_o 224sudo mkzftree "${mount}"/usr_o "${mount}"/usr 225sudo rm -rf "${mount}"/usr_o 226sudo mv "${mount}"/root "${mount}"/root_o 227sudo mkzftree "${mount}"/root_o "${mount}"/root 228sudo rm -rf "${mount}"/root_o 229sudo mv "${mount}"/var "${mount}"/var_o 230sudo mkzftree "${mount}"/var_o "${mount}"/var 231sudo rm -rf "${mount}"/var_o 232rm -f "${output}" 233touch "${output}" 234sudo xorriso \ 235 -as mkisofs -r -checksum_algorithm_iso sha256,sha512 -V install "${mount}" \ 236 -o "${output}" -e boot/grub/eltorito.img -no-emul-boot \ 237 -append_partition 2 0xef "${workdir}"/eltorito.img \ 238 -z \ 239 -partition_cyl_align all 240 241echo "Output ISO generated at '${output}'." 242