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