xref: /freebsd/tools/boot/install-boot.sh (revision 0957b409)
1#!/bin/sh
2
3# $FreeBSD$
4
5#
6# Installs/updates the necessary boot blocks for the desired boot environment
7#
8# Lightly tested.. Intended to be installed, but until it matures, it will just
9# be a boot tool for regression testing.
10
11# insert code here to guess what you have -- yikes!
12
13# Minimum size of FAT filesystems, in KB.
14fat32min=33292
15fat16min=2100
16
17die() {
18    echo $*
19    exit 1
20}
21
22doit() {
23    echo $*
24    eval $*
25}
26
27find-part() {
28    dev=$1
29    part=$2
30
31    gpart show $dev | tail +2 | awk '$4 == "'$part'" { print $3; }'
32}
33
34get_uefi_bootname() {
35
36    case ${TARGET:-$(uname -m)} in
37        amd64) echo bootx64 ;;
38        arm64) echo bootaa64 ;;
39        i386) echo bootia32 ;;
40        arm) echo bootarm ;;
41        *) die "machine type $(uname -m) doesn't support UEFI" ;;
42    esac
43}
44
45make_esp_file() {
46    local file sizekb loader device mntpt fatbits efibootname
47
48    file=$1
49    sizekb=$2
50    loader=$3
51
52    if [ "$sizekb" -ge "$fat32min" ]; then
53        fatbits=32
54    elif [ "$sizekb" -ge "$fat16min" ]; then
55        fatbits=16
56    else
57        fatbits=12
58    fi
59
60    dd if=/dev/zero of="${file}" bs=1k count="${sizekb}"
61    device=$(mdconfig -a -t vnode -f "${file}")
62    newfs_msdos -F "${fatbits}" -c 1 -L EFISYS "/dev/${device}" > /dev/null 2>&1
63    mntpt=$(mktemp -d /tmp/stand-test.XXXXXX)
64    mount -t msdosfs "/dev/${device}" "${mntpt}"
65    mkdir -p "${mntpt}/EFI/BOOT"
66    efibootname=$(get_uefi_bootname)
67    cp "${loader}" "${mntpt}/EFI/BOOT/${efibootname}.efi"
68    umount "${mntpt}"
69    rmdir "${mntpt}"
70    mdconfig -d -u "${device}"
71}
72
73make_esp_device() {
74    local dev file mntpt fstype efibootname kbfree loadersize efibootfile
75    local isboot1 existingbootentryloaderfile bootorder bootentry
76
77    # ESP device node
78    dev=$1
79    file=$2
80
81    mntpt=$(mktemp -d /tmp/stand-test.XXXXXX)
82
83    # See if we're using an existing (formatted) ESP
84    fstype=$(fstyp "${dev}")
85
86    if [ "${fstype}" != "msdosfs" ]; then
87        newfs_msdos -F 32 -c 1 -L EFISYS "${dev}" > /dev/null 2>&1
88    fi
89
90    mount -t msdosfs "${dev}" "${mntpt}"
91    if [ $? -ne 0 ]; then
92        die "Failed to mount ${dev} as an msdosfs filesystem"
93    fi
94
95    echo "Mounted ESP ${dev} on ${mntpt}"
96
97    efibootname=$(get_uefi_bootname)
98    kbfree=$(df -k "${mntpt}" | tail -1 | cut -w -f 4)
99    loadersize=$(stat -f %z "${file}")
100    loadersize=$((loadersize / 1024))
101
102    # Check if /EFI/BOOT/BOOTxx.EFI is the FreeBSD boot1.efi
103    # If it is, remove it to avoid leaving stale files around
104    efibootfile="${mntpt}/EFI/BOOT/${efibootname}.efi"
105    if [ -f "${efibootfile}" ]; then
106        isboot1=$(strings "${efibootfile}" | grep "FreeBSD EFI boot block")
107
108        if [ -n "${isboot1}" ] && [ "$kbfree" -lt "${loadersize}" ]; then
109            echo "Only ${kbfree}KB space remaining: removing old FreeBSD boot1.efi file /EFI/BOOT/${efibootname}.efi"
110            rm "${efibootfile}"
111            rmdir "${mntpt}/EFI/BOOT"
112        else
113            echo "${kbfree}KB space remaining on ESP: renaming old boot1.efi file /EFI/BOOT/${efibootname}.efi /EFI/BOOT/${efibootname}-old.efi"
114            mv "${efibootfile}" "${mntpt}/EFI/BOOT/${efibootname}-old.efi"
115        fi
116    fi
117
118    if [ ! -f "${mntpt}/EFI/freebsd/loader.efi" ] && [ "$kbfree" -lt "$loadersize" ]; then
119        umount "${mntpt}"
120	rmdir "${mntpt}"
121        echo "Failed to update the EFI System Partition ${dev}"
122        echo "Insufficient space remaining for ${file}"
123        echo "Run e.g \"mount -t msdosfs ${dev} /mnt\" to inspect it for files that can be removed."
124        die
125    fi
126
127    mkdir -p "${mntpt}/EFI/freebsd"
128
129    # Keep a copy of the existing loader.efi in case there's a problem with the new one
130    if [ -f "${mntpt}/EFI/freebsd/loader.efi" ] && [ "$kbfree" -gt "$((loadersize * 2))" ]; then
131        cp "${mntpt}/EFI/freebsd/loader.efi" "${mntpt}/EFI/freebsd/loader-old.efi"
132    fi
133
134    echo "Copying loader to /EFI/freebsd on ESP"
135    cp "${file}" "${mntpt}/EFI/freebsd/loader.efi"
136
137    existingbootentryloaderfile=$(efibootmgr -v | grep "${mntpt}//EFI/freebsd/loader.efi")
138
139    if [ -z "$existingbootentryloaderfile" ]; then
140        # Try again without the double forward-slash in the path
141        existingbootentryloaderfile=$(efibootmgr -v | grep "${mntpt}/EFI/freebsd/loader.efi")
142    fi
143
144    if [ -z "$existingbootentryloaderfile" ]; then
145        echo "Creating UEFI boot entry for FreeBSD"
146        efibootmgr --create --label FreeBSD --loader "${mntpt}/EFI/freebsd/loader.efi" > /dev/null
147        if [ $? -ne 0 ]; then
148            die "Failed to create new boot entry"
149        fi
150
151        # When creating new entries, efibootmgr doesn't mark them active, so we need to
152        # do so. It doesn't make it easy to find which entry it just added, so rely on
153        # the fact that it places the new entry first in BootOrder.
154        bootorder=$(efivar --name 8be4df61-93ca-11d2-aa0d-00e098032b8c-BootOrder --print --no-name --hex | head -1)
155        bootentry=$(echo "${bootorder}" | cut -w -f 3)$(echo "${bootorder}" | cut -w -f 2)
156        echo "Marking UEFI boot entry ${bootentry} active"
157        efibootmgr --activate "${bootentry}" > /dev/null
158    else
159        echo "Existing UEFI FreeBSD boot entry found: not creating a new one"
160    fi
161
162    umount "${mntpt}"
163    rmdir "${mntpt}"
164    echo "Finished updating ESP"
165}
166
167make_esp() {
168    local file loaderfile
169
170    file=$1
171    loaderfile=$2
172
173    if [ -f "$file" ]; then
174        make_esp_file ${file} ${fat32min} ${loaderfile}
175    else
176        make_esp_device ${file} ${loaderfile}
177    fi
178}
179
180make_esp_mbr() {
181    dev=$1
182    dst=$2
183
184    s=$(find-part $dev "!239")
185    if [ -z "$s" ] ; then
186	s=$(find-part $dev "efi")
187	if [ -z "$s" ] ; then
188	    die "No ESP slice found"
189    	fi
190    fi
191    make_esp /dev/${dev}s${s} ${dst}/boot/loader.efi
192}
193
194make_esp_gpt() {
195    dev=$1
196    dst=$2
197
198    idx=$(find-part $dev "efi")
199    if [ -z "$idx" ] ; then
200	die "No ESP partition found"
201    fi
202    make_esp /dev/${dev}p${idx} ${dst}/boot/loader.efi
203}
204
205boot_nogeli_gpt_ufs_legacy() {
206    dev=$1
207    dst=$2
208
209    idx=$(find-part $dev "freebsd-boot")
210    if [ -z "$idx" ] ; then
211	die "No freebsd-boot partition found"
212    fi
213    doit gpart bootcode -b ${gpt0} -p ${gpt2} -i $idx $dev
214}
215
216boot_nogeli_gpt_ufs_uefi() {
217    make_esp_gpt $1 $2
218}
219
220boot_nogeli_gpt_ufs_both() {
221    boot_nogeli_gpt_ufs_legacy $1 $2 $3
222    boot_nogeli_gpt_ufs_uefi $1 $2 $3
223}
224
225boot_nogeli_gpt_zfs_legacy() {
226    dev=$1
227    dst=$2
228
229    idx=$(find-part $dev "freebsd-boot")
230    if [ -z "$idx" ] ; then
231	die "No freebsd-boot partition found"
232    fi
233    doit gpart bootcode -b ${gpt0} -p ${gptzfs2} -i $idx $dev
234}
235
236boot_nogeli_gpt_zfs_uefi() {
237    make_esp_gpt $1 $2
238}
239
240boot_nogeli_gpt_zfs_both() {
241    boot_nogeli_gpt_zfs_legacy $1 $2 $3
242    boot_nogeli_gpt_zfs_uefi $1 $2 $3
243}
244
245boot_nogeli_mbr_ufs_legacy() {
246    dev=$1
247    dst=$2
248
249    doit gpart bootcode -b ${mbr0} ${dev}
250    s=$(find-part $dev "freebsd")
251    if [ -z "$s" ] ; then
252	die "No freebsd slice found"
253    fi
254    doit gpart bootcode -p ${mbr2} ${dev}s${s}
255}
256
257boot_nogeli_mbr_ufs_uefi() {
258    make_esp_mbr $1 $2
259}
260
261boot_nogeli_mbr_ufs_both() {
262    boot_nogeli_mbr_ufs_legacy $1 $2 $3
263    boot_nogeli_mbr_ufs_uefi $1 $2 $3
264}
265
266boot_nogeli_mbr_zfs_legacy() {
267    dev=$1
268    dst=$2
269
270    # search to find the BSD slice
271    s=$(find-part $dev "freebsd")
272    if [ -z "$s" ] ; then
273	die "No BSD slice found"
274    fi
275    idx=$(find-part ${dev}s${s} "freebsd-zfs")
276    if [ -z "$idx" ] ; then
277	die "No freebsd-zfs slice found"
278    fi
279    # search to find the freebsd-zfs partition within the slice
280    # Or just assume it is 'a' because it has to be since it fails otherwise
281    doit gpart bootcode -b ${dst}/boot/mbr ${dev}
282    dd if=${dst}/boot/zfsboot of=/tmp/zfsboot1 count=1
283    doit gpart bootcode -b /tmp/zfsboot1 ${dev}s${s}	# Put boot1 into the start of part
284    sysctl kern.geom.debugflags=0x10		# Put boot2 into ZFS boot slot
285    doit dd if=${dst}/boot/zfsboot of=/dev/${dev}s${s}a skip=1 seek=1024
286    sysctl kern.geom.debugflags=0x0
287}
288
289boot_nogeli_mbr_zfs_uefi() {
290    make_esp_mbr $1 $2
291}
292
293boot_nogeli_mbr_zfs_both() {
294    boot_nogeli_mbr_zfs_legacy $1 $2 $3
295    boot_nogeli_mbr_zfs_uefi $1 $2 $3
296}
297
298boot_geli_gpt_ufs_legacy() {
299    boot_nogeli_gpt_ufs_legacy $1 $2 $3
300}
301
302boot_geli_gpt_ufs_uefi() {
303    boot_nogeli_gpt_ufs_uefi $1 $2 $3
304}
305
306boot_geli_gpt_ufs_both() {
307    boot_nogeli_gpt_ufs_both $1 $2 $3
308}
309
310boot_geli_gpt_zfs_legacy() {
311    boot_nogeli_gpt_zfs_legacy $1 $2 $3
312}
313
314boot_geli_gpt_zfs_uefi() {
315    boot_nogeli_gpt_zfs_uefi $1 $2 $3
316}
317
318boot_geli_gpt_zfs_both() {
319    boot_nogeli_gpt_zfs_both $1 $2 $3
320}
321
322# GELI+MBR is not a valid configuration
323boot_geli_mbr_ufs_legacy() {
324    exit 1
325}
326
327boot_geli_mbr_ufs_uefi() {
328    exit 1
329}
330
331boot_geli_mbr_ufs_both() {
332    exit 1
333}
334
335boot_geli_mbr_zfs_legacy() {
336    exit 1
337}
338
339boot_geli_mbr_zfs_uefi() {
340    exit 1
341}
342
343boot_geli_mbr_zfs_both() {
344    exit 1
345}
346
347boot_nogeli_vtoc8_ufs_ofw() {
348    dev=$1
349    dst=$2
350
351    # For non-native builds, ensure that geom_part(4) supports VTOC8.
352    kldload geom_part_vtoc8.ko
353    doit gpart bootcode -p ${vtoc8} ${dev}
354}
355
356usage() {
357	printf 'Usage: %s -b bios [-d destdir] -f fs [-g geli] [-h] [-o optargs] -s scheme <bootdev>\n' "$0"
358	printf 'Options:\n'
359	printf ' bootdev       device to install the boot code on\n'
360	printf ' -b bios       bios type: legacy, uefi or both\n'
361	printf ' -d destdir    destination filesystem root\n'
362	printf ' -f fs         filesystem type: ufs or zfs\n'
363	printf ' -g geli       yes or no\n'
364	printf ' -h            this help/usage text\n'
365	printf ' -o optargs    optional arguments\n'
366	printf ' -s scheme     mbr or gpt\n'
367	exit 0
368}
369
370srcroot=/
371
372# Note: we really don't support geli boot in this script yet.
373geli=nogeli
374
375while getopts "b:d:f:g:ho:s:" opt; do
376    case "$opt" in
377	b)
378	    bios=${OPTARG}
379	    ;;
380	d)
381	    srcroot=${OPTARG}
382	    ;;
383	f)
384	    fs=${OPTARG}
385	    ;;
386	g)
387	    case ${OPTARG} in
388		[Yy][Ee][Ss]|geli) geli=geli ;;
389		*) geli=nogeli ;;
390	    esac
391	    ;;
392	o)
393	    opts=${OPTARG}
394	    ;;
395	s)
396	    scheme=${OPTARG}
397	    ;;
398
399	?|h)
400            usage
401            ;;
402    esac
403done
404
405if [ -n "${scheme}" ] && [ -n "${fs}" ] && [ -n "${bios}" ]; then
406    shift $((OPTIND-1))
407    dev=$1
408fi
409
410# For gpt, we need to install pmbr as the primary boot loader
411# it knows about
412gpt0=${srcroot}/boot/pmbr
413gpt2=${srcroot}/boot/gptboot
414gptzfs2=${srcroot}/boot/gptzfsboot
415
416# For MBR, we have lots of choices, but select mbr, boot0 has issues with UEFI
417mbr0=${srcroot}/boot/mbr
418mbr2=${srcroot}/boot/boot
419
420# VTOC8
421vtoc8=${srcroot}/boot/boot1
422
423# sanity check here
424
425# Check if we've been given arguments. If not, this script is probably being
426# sourced, so we shouldn't run anything.
427if [ -n "${dev}" ]; then
428	eval boot_${geli}_${scheme}_${fs}_${bios} $dev $srcroot $opts || echo "Unsupported boot env: ${geli}-${scheme}-${fs}-${bios}"
429fi
430