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