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