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