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