]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tools/boot/install-boot.sh
ident(1): Normalizing date format
[FreeBSD/FreeBSD.git] / tools / boot / install-boot.sh
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.
14 fat32min=33292
15 fat16min=2100
16
17 die() {
18     echo $*
19     exit 1
20 }
21
22 doit() {
23     echo $*
24     eval $*
25 }
26
27 find-part() {
28     dev=$1
29     part=$2
30
31     gpart show $dev | tail +2 | awk '$4 == "'$part'" { print $3; }'
32 }
33
34 get_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
46 make_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
74 make_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
176 make_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
189 make_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
203 make_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
214 boot_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
225 boot_nogeli_gpt_ufs_uefi() {
226     make_esp_gpt $1 $2
227 }
228
229 boot_nogeli_gpt_ufs_both() {
230     boot_nogeli_gpt_ufs_legacy $1 $2 $3
231     boot_nogeli_gpt_ufs_uefi $1 $2 $3
232 }
233
234 boot_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
245 boot_nogeli_gpt_zfs_uefi() {
246     make_esp_gpt $1 $2
247 }
248
249 boot_nogeli_gpt_zfs_both() {
250     boot_nogeli_gpt_zfs_legacy $1 $2 $3
251     boot_nogeli_gpt_zfs_uefi $1 $2 $3
252 }
253
254 boot_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
266 boot_nogeli_mbr_ufs_uefi() {
267     make_esp_mbr $1 $2
268 }
269
270 boot_nogeli_mbr_ufs_both() {
271     boot_nogeli_mbr_ufs_legacy $1 $2 $3
272     boot_nogeli_mbr_ufs_uefi $1 $2 $3
273 }
274
275 boot_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
298 boot_nogeli_mbr_zfs_uefi() {
299     make_esp_mbr $1 $2
300 }
301
302 boot_nogeli_mbr_zfs_both() {
303     boot_nogeli_mbr_zfs_legacy $1 $2 $3
304     boot_nogeli_mbr_zfs_uefi $1 $2 $3
305 }
306
307 boot_geli_gpt_ufs_legacy() {
308     boot_nogeli_gpt_ufs_legacy $1 $2 $3
309 }
310
311 boot_geli_gpt_ufs_uefi() {
312     boot_nogeli_gpt_ufs_uefi $1 $2 $3
313 }
314
315 boot_geli_gpt_ufs_both() {
316     boot_nogeli_gpt_ufs_both $1 $2 $3
317 }
318
319 boot_geli_gpt_zfs_legacy() {
320     boot_nogeli_gpt_zfs_legacy $1 $2 $3
321 }
322
323 boot_geli_gpt_zfs_uefi() {
324     boot_nogeli_gpt_zfs_uefi $1 $2 $3
325 }
326
327 boot_geli_gpt_zfs_both() {
328     boot_nogeli_gpt_zfs_both $1 $2 $3
329 }
330
331 # GELI+MBR is not a valid configuration
332 boot_geli_mbr_ufs_legacy() {
333     exit 1
334 }
335
336 boot_geli_mbr_ufs_uefi() {
337     exit 1
338 }
339
340 boot_geli_mbr_ufs_both() {
341     exit 1
342 }
343
344 boot_geli_mbr_zfs_legacy() {
345     exit 1
346 }
347
348 boot_geli_mbr_zfs_uefi() {
349     exit 1
350 }
351
352 boot_geli_mbr_zfs_both() {
353     exit 1
354 }
355
356 boot_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
365 usage() {
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
381 srcroot=/
382
383 # Note: we really don't support geli boot in this script yet.
384 geli=nogeli
385
386 while 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
417 done
418
419 if [ -n "${scheme}" ] && [ -n "${fs}" ] && [ -n "${bios}" ]; then
420     shift $((OPTIND-1))
421     dev=$1
422 fi
423
424 # For gpt, we need to install pmbr as the primary boot loader
425 # it knows about 
426 gpt0=${srcroot}/boot/pmbr
427 gpt2=${srcroot}/boot/gptboot
428 gptzfs2=${srcroot}/boot/gptzfsboot
429
430 # For MBR, we have lots of choices, but select mbr, boot0 has issues with UEFI
431 mbr0=${srcroot}/boot/mbr
432 mbr2=${srcroot}/boot/boot
433
434 # VTOC8
435 vtoc8=${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.
441 if [ -n "${dev}" ]; then
442         eval boot_${geli}_${scheme}_${fs}_${bios} $dev $srcroot $opts || echo "Unsupported boot env: ${geli}-${scheme}-${fs}-${bios}"
443 fi