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