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