xref: /freebsd/tools/boot/install-boot.sh (revision a3b72d89c7028a2254381e4d2b126416dee3fbb5)
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
297# ZFS+MBR+BIOS is not a supported configuration
298boot_nogeli_mbr_zfs_legacy() {
299    exit 1
300}
301
302boot_nogeli_mbr_zfs_uefi() {
303    make_esp_mbr $1 $2
304}
305
306boot_nogeli_mbr_zfs_both() {
307    boot_nogeli_mbr_zfs_uefi $1 $2 $3
308}
309
310boot_geli_gpt_ufs_legacy() {
311    boot_nogeli_gpt_ufs_legacy $1 $2 $3
312}
313
314boot_geli_gpt_ufs_uefi() {
315    boot_nogeli_gpt_ufs_uefi $1 $2 $3
316}
317
318boot_geli_gpt_ufs_both() {
319    boot_nogeli_gpt_ufs_both $1 $2 $3
320}
321
322boot_geli_gpt_zfs_legacy() {
323    boot_nogeli_gpt_zfs_legacy $1 $2 $3
324}
325
326boot_geli_gpt_zfs_uefi() {
327    boot_nogeli_gpt_zfs_uefi $1 $2 $3
328}
329
330boot_geli_gpt_zfs_both() {
331    boot_nogeli_gpt_zfs_both $1 $2 $3
332}
333
334# GELI+MBR is not a valid configuration
335boot_geli_mbr_ufs_legacy() {
336    exit 1
337}
338
339boot_geli_mbr_ufs_uefi() {
340    exit 1
341}
342
343boot_geli_mbr_ufs_both() {
344    exit 1
345}
346
347boot_geli_mbr_zfs_legacy() {
348    exit 1
349}
350
351boot_geli_mbr_zfs_uefi() {
352    exit 1
353}
354
355boot_geli_mbr_zfs_both() {
356    exit 1
357}
358
359usage() {
360	printf 'Usage: %s -b bios [-d destdir] -f fs [-g geli] [-h] [-o optargs] -s scheme <bootdev>\n' "$0"
361	printf 'Options:\n'
362	printf ' bootdev       device to install the boot code on\n'
363	printf ' -b bios       bios type: legacy, uefi or both\n'
364	printf ' -d destdir    destination filesystem root\n'
365	printf ' -f fs         filesystem type: ufs or zfs\n'
366	printf ' -g geli       yes or no\n'
367	printf ' -h            this help/usage text\n'
368	printf ' -u            Run commands such as efibootmgr to update the\n'
369	printf '               currently running system\n'
370	printf ' -o optargs    optional arguments\n'
371	printf ' -s scheme     mbr or gpt\n'
372	exit 0
373}
374
375srcroot=/
376
377# Note: we really don't support geli boot in this script yet.
378geli=nogeli
379
380while getopts "b:d:f:g:ho:s:u" opt; do
381    case "$opt" in
382	b)
383	    bios=${OPTARG}
384	    ;;
385	d)
386	    srcroot=${OPTARG}
387	    ;;
388	f)
389	    fs=${OPTARG}
390	    ;;
391	g)
392	    case ${OPTARG} in
393		[Yy][Ee][Ss]|geli) geli=geli ;;
394		*) geli=nogeli ;;
395	    esac
396	    ;;
397	u)
398	    updatesystem=1
399	    ;;
400	o)
401	    opts=${OPTARG}
402	    ;;
403	s)
404	    scheme=${OPTARG}
405	    ;;
406
407	?|h)
408            usage
409            ;;
410    esac
411done
412
413if [ -n "${scheme}" ] && [ -n "${fs}" ] && [ -n "${bios}" ]; then
414    shift $((OPTIND-1))
415    dev=$1
416fi
417
418# For gpt, we need to install pmbr as the primary boot loader
419# it knows about
420gpt0=${srcroot}/boot/pmbr
421gpt2=${srcroot}/boot/gptboot
422gptzfs2=${srcroot}/boot/gptzfsboot
423
424# For MBR, we have lots of choices, but select mbr, boot0 has issues with UEFI
425mbr0=${srcroot}/boot/mbr
426mbr2=${srcroot}/boot/boot
427
428# sanity check here
429
430# Check if we've been given arguments. If not, this script is probably being
431# sourced, so we shouldn't run anything.
432if [ -n "${dev}" ]; then
433	eval boot_${geli}_${scheme}_${fs}_${bios} $dev $srcroot $opts || echo "Unsupported boot env: ${geli}-${scheme}-${fs}-${bios}"
434fi
435