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