1#!/bin/bash
2#
3# This file is subject to the terms and conditions of the GNU General Public
4# License.  See the file "COPYING" in the main directory of this archive
5# for more details.
6#
7# Copyright (C) 2017 by Changbin Du <changbin.du@intel.com>
8#
9# Adapted from code in arch/x86/boot/Makefile by H. Peter Anvin and others
10#
11# "make fdimage/fdimage144/fdimage288/hdimage/isoimage"
12# script for x86 architecture
13#
14# Arguments:
15#   $1  - fdimage format
16#   $2  - target image file
17#   $3  - kernel bzImage file
18#   $4  - mtools configuration file
19#   $5  - kernel cmdline
20#   $6+ - initrd image file(s)
21#
22# This script requires:
23#   bash
24#   syslinux
25#   genisoimage
26#   mtools (for fdimage* and hdimage)
27#   edk2/OVMF (for hdimage)
28#
29# Otherwise try to stick to POSIX shell commands...
30#
31
32# Use "make V=1" to debug this script
33case "${KBUILD_VERBOSE}" in
34*1*)
35        set -x
36        ;;
37esac
38
39# Exit the top-level shell with an error
40topshell=$$
41trap 'exit 1' USR1
42die() {
43	echo ""        1>&2
44	echo " *** $*" 1>&2
45	echo ""        1>&2
46	kill -USR1 $topshell
47}
48
49# Verify the existence and readability of a file
50verify() {
51	if [ ! -f "$1" -o ! -r "$1" ]; then
52		die "Missing file: $1"
53	fi
54}
55
56diskfmt="$1"
57FIMAGE="$2"
58FBZIMAGE="$3"
59MTOOLSRC="$4"
60KCMDLINE="$5"
61shift 5				# Remaining arguments = initrd files
62
63export MTOOLSRC
64
65# common options for dd
66dd='dd iflag=fullblock'
67
68# Make sure the files actually exist
69verify "$FBZIMAGE"
70
71declare -a FDINITRDS
72irdpfx=' initrd='
73initrdopts_syslinux=''
74initrdopts_efi=''
75for f in "$@"; do
76	if [ -f "$f" -a -r "$f" ]; then
77	    FDINITRDS=("${FDINITRDS[@]}" "$f")
78	    fname="$(basename "$f")"
79	    initrdopts_syslinux="${initrdopts_syslinux}${irdpfx}${fname}"
80	    irdpfx=,
81	    initrdopts_efi="${initrdopts_efi} initrd=${fname}"
82	fi
83done
84
85# Read a $3-byte littleendian unsigned value at offset $2 from file $1
86le() {
87	local n=0
88	local m=1
89	for b in $(od -A n -v -j $2 -N $3 -t u1 "$1"); do
90		n=$((n + b*m))
91		m=$((m * 256))
92	done
93	echo $n
94}
95
96# Get the EFI architecture name such that boot{name}.efi is the default
97# boot file name. Returns false with no output if the file is not an
98# EFI image or otherwise unknown.
99efiarch() {
100	[ -f "$1" ] || return
101	[ $(le "$1" 0 2) -eq 23117 ] || return		# MZ magic
102	peoffs=$(le "$1" 60 4)				# PE header offset
103	[ $peoffs -ge 64 ] || return
104	[ $(le "$1" $peoffs 4) -eq 17744 ] || return	# PE magic
105	case $(le "$1" $((peoffs+4+20)) 2) in		# PE type
106		267)	;;				# PE32
107		523)	;;				# PE32+
108		*) return 1 ;;				# Invalid
109	esac
110	[ $(le "$1" $((peoffs+4+20+68)) 2) -eq 10 ] || return # EFI app
111	case $(le "$1" $((peoffs+4)) 2) in		# Machine type
112		 332)	echo i386	;;
113		 450)	echo arm	;;
114		 512)	echo ia64	;;
115		20530)	echo riscv32	;;
116		20580)	echo riscv64	;;
117		20776)	echo riscv128	;;
118		34404)	echo x64	;;
119		43620)	echo aa64	;;
120	esac
121}
122
123# Get the combined sizes in bytes of the files given, counting sparse
124# files as full length, and padding each file to cluster size
125cluster=16384
126filesizes() {
127	local t=0
128	local s
129	for s in $(ls -lnL "$@" 2>/dev/null | awk '/^-/{ print $5; }'); do
130		t=$((t + ((s+cluster-1)/cluster)*cluster))
131	done
132	echo $t
133}
134
135# Expand directory names which should be in /usr/share into a list
136# of possible alternatives
137sharedirs() {
138	local dir file
139	for dir in /usr/share /usr/lib64 /usr/lib; do
140		for file; do
141			echo "$dir/$file"
142			echo "$dir/${file^^}"
143		done
144	done
145}
146efidirs() {
147	local dir file
148	for dir in /usr/share /boot /usr/lib64 /usr/lib; do
149		for file; do
150			echo "$dir/$file"
151			echo "$dir/${file^^}"
152		done
153	done
154}
155
156findsyslinux() {
157	local f="$(find -L $(sharedirs syslinux isolinux) \
158		    -name "$1" -readable -type f -print -quit 2>/dev/null)"
159	if [ ! -f "$f" ]; then
160		die "Need a $1 file, please install syslinux/isolinux."
161	fi
162	echo "$f"
163	return 0
164}
165
166findovmf() {
167	local arch="$1"
168	shift
169	local -a names=(-false)
170	local name f
171	for name; do
172		names=("${names[@]}" -or -iname "$name")
173	done
174	for f in $(find -L $(efidirs edk2 ovmf) \
175			\( "${names[@]}" \) -readable -type f \
176			-print 2>/dev/null); do
177		if [ "$(efiarch "$f")" = "$arch" ]; then
178			echo "$f"
179			return 0
180		fi
181	done
182	die "Need a $1 file for $arch, please install EDK2/OVMF."
183}
184
185do_mcopy() {
186	if [ ${#FDINITRDS[@]} -gt 0 ]; then
187		mcopy "${FDINITRDS[@]}" "$1"
188	fi
189	if [ -n "$efishell" ]; then
190		mmd "$1"EFI "$1"EFI/Boot
191		mcopy "$efishell" "$1"EFI/Boot/boot${kefiarch}.efi
192	fi
193	if [ -n "$kefiarch" ]; then
194		echo linux "$KCMDLINE$initrdopts_efi" | \
195			mcopy - "$1"startup.nsh
196	fi
197	echo default linux "$KCMDLINE$initrdopts_syslinux" | \
198		mcopy - "$1"syslinux.cfg
199	mcopy "$FBZIMAGE" "$1"linux
200}
201
202genbzdisk() {
203	verify "$MTOOLSRC"
204	mformat -v 'LINUX_BOOT' a:
205	syslinux "$FIMAGE"
206	do_mcopy a:
207}
208
209genfdimage144() {
210	verify "$MTOOLSRC"
211	$dd if=/dev/zero of="$FIMAGE" bs=1024 count=1440 2>/dev/null
212	mformat -v 'LINUX_BOOT' v:
213	syslinux "$FIMAGE"
214	do_mcopy v:
215}
216
217genfdimage288() {
218	verify "$MTOOLSRC"
219	$dd if=/dev/zero of="$FIMAGE" bs=1024 count=2880 2>/dev/null
220	mformat -v 'LINUX_BOOT' w:
221	syslinux "$FIMAGE"
222	do_mcopy w:
223}
224
225genhdimage() {
226	verify "$MTOOLSRC"
227	mbr="$(findsyslinux mbr.bin)"
228	kefiarch="$(efiarch "$FBZIMAGE")"
229	if [ -n "$kefiarch" ]; then
230		# The efishell provides command line handling
231		efishell="$(findovmf $kefiarch shell.efi shell${kefiarch}.efi)"
232		ptype='-T 0xef'	# EFI system partition, no GPT
233	fi
234	sizes=$(filesizes "$FBZIMAGE" "${FDINITRDS[@]}" "$efishell")
235	# Allow 1% + 2 MiB for filesystem and partition table overhead,
236	# syslinux, and config files; this is probably excessive...
237	megs=$(((sizes + sizes/100 + 2*1024*1024 - 1)/(1024*1024)))
238	$dd if=/dev/zero of="$FIMAGE" bs=$((1024*1024)) count=$megs 2>/dev/null
239	mpartition -I -c -s 32 -h 64 $ptype -b 64 -a p:
240	$dd if="$mbr" of="$FIMAGE" bs=440 count=1 conv=notrunc 2>/dev/null
241	mformat -v 'LINUX_BOOT' -s 32 -h 64 -c $((cluster/512)) -t $megs h:
242	syslinux --offset $((64*512)) "$FIMAGE"
243	do_mcopy h:
244}
245
246geniso() {
247	tmp_dir="$(dirname "$FIMAGE")/isoimage"
248	rm -rf "$tmp_dir"
249	mkdir "$tmp_dir"
250	isolinux=$(findsyslinux isolinux.bin)
251	ldlinux=$(findsyslinux  ldlinux.c32)
252	cp "$isolinux" "$ldlinux" "$tmp_dir"
253	cp "$FBZIMAGE" "$tmp_dir"/linux
254	echo default linux "$KCMDLINE" > "$tmp_dir"/isolinux.cfg
255	if [ ${#FDINITRDS[@]} -gt 0 ]; then
256		cp "${FDINITRDS[@]}" "$tmp_dir"/
257	fi
258	genisoimage -J -r -appid 'LINUX_BOOT' -input-charset=utf-8 \
259		    -quiet -o "$FIMAGE" -b isolinux.bin \
260		    -c boot.cat -no-emul-boot -boot-load-size 4 \
261		    -boot-info-table "$tmp_dir"
262	isohybrid "$FIMAGE" 2>/dev/null || true
263	rm -rf "$tmp_dir"
264}
265
266rm -f "$FIMAGE"
267
268case "$diskfmt" in
269	bzdisk)     genbzdisk;;
270	fdimage144) genfdimage144;;
271	fdimage288) genfdimage288;;
272	hdimage)    genhdimage;;
273	isoimage)   geniso;;
274	*)          die "Unknown image format: $diskfmt";;
275esac
276