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 loader device stagedir fatbits efibootname 46 47 file=$1 48 sizekb=$2 49 loader=$3 50 51 if [ "$sizekb" -ge "$fat32min" ]; then 52 fatbits=32 53 elif [ "$sizekb" -ge "$fat16min" ]; then 54 fatbits=16 55 else 56 fatbits=12 57 fi 58 59 stagedir=$(mktemp -d /tmp/stand-test.XXXXXX) 60 mkdir -p "${stagedir}/EFI/BOOT" 61 efibootname=$(get_uefi_bootname) 62 cp "${loader}" "${stagedir}/EFI/BOOT/${efibootname}.efi" 63 makefs -t msdos \ 64 -o fat_type=${fatbits} \ 65 -o sectors_per_cluster=1 \ 66 -o volume_label=EFISYS \ 67 -s ${sizekb}k \ 68 "${file}" "${stagedir}" 69 rm -rf "${stagedir}" 70} 71 72make_esp_device() { 73 local dev file mntpt fstype efibootname kbfree loadersize efibootfile 74 local isboot1 existingbootentryloaderfile bootorder bootentry 75 76 # ESP device node 77 dev=$1 78 file=$2 79 80 mntpt=$(mktemp -d /tmp/stand-test.XXXXXX) 81 82 # See if we're using an existing (formatted) ESP 83 fstype=$(fstyp "${dev}") 84 85 if [ "${fstype}" != "msdosfs" ]; then 86 newfs_msdos -F 32 -c 1 -L EFISYS "${dev}" > /dev/null 2>&1 87 fi 88 89 mount -t msdosfs "${dev}" "${mntpt}" 90 if [ $? -ne 0 ]; then 91 die "Failed to mount ${dev} as an msdosfs filesystem" 92 fi 93 94 echo "Mounted ESP ${dev} on ${mntpt}" 95 96 efibootname=$(get_uefi_bootname) 97 kbfree=$(df -k "${mntpt}" | tail -1 | cut -w -f 4) 98 loadersize=$(stat -f %z "${file}") 99 loadersize=$((loadersize / 1024)) 100 101 # Check if /EFI/BOOT/BOOTxx.EFI is the FreeBSD boot1.efi 102 # If it is, remove it to avoid leaving stale files around 103 efibootfile="${mntpt}/EFI/BOOT/${efibootname}.efi" 104 if [ -f "${efibootfile}" ]; then 105 isboot1=$(strings "${efibootfile}" | grep "FreeBSD EFI boot block") 106 107 if [ -n "${isboot1}" ] && [ "$kbfree" -lt "${loadersize}" ]; then 108 echo "Only ${kbfree}KB space remaining: removing old FreeBSD boot1.efi file /EFI/BOOT/${efibootname}.efi" 109 rm "${efibootfile}" 110 rmdir "${mntpt}/EFI/BOOT" 111 else 112 echo "${kbfree}KB space remaining on ESP: renaming old boot1.efi file /EFI/BOOT/${efibootname}.efi /EFI/BOOT/${efibootname}-old.efi" 113 mv "${efibootfile}" "${mntpt}/EFI/BOOT/${efibootname}-old.efi" 114 fi 115 fi 116 117 if [ ! -f "${mntpt}/EFI/freebsd/loader.efi" ] && [ "$kbfree" -lt "$loadersize" ]; then 118 umount "${mntpt}" 119 rmdir "${mntpt}" 120 echo "Failed to update the EFI System Partition ${dev}" 121 echo "Insufficient space remaining for ${file}" 122 echo "Run e.g \"mount -t msdosfs ${dev} /mnt\" to inspect it for files that can be removed." 123 die 124 fi 125 126 mkdir -p "${mntpt}/EFI/freebsd" 127 128 # Keep a copy of the existing loader.efi in case there's a problem with the new one 129 if [ -f "${mntpt}/EFI/freebsd/loader.efi" ] && [ "$kbfree" -gt "$((loadersize * 2))" ]; then 130 cp "${mntpt}/EFI/freebsd/loader.efi" "${mntpt}/EFI/freebsd/loader-old.efi" 131 fi 132 133 echo "Copying loader to /EFI/freebsd on ESP" 134 cp "${file}" "${mntpt}/EFI/freebsd/loader.efi" 135 136 if [ -n "${updatesystem}" ]; then 137 existingbootentryloaderfile=$(efibootmgr -v | grep "${mntpt}//EFI/freebsd/loader.efi") 138 139 if [ -z "$existingbootentryloaderfile" ]; then 140 # Try again without the double forward-slash in the path 141 existingbootentryloaderfile=$(efibootmgr -v | grep "${mntpt}/EFI/freebsd/loader.efi") 142 fi 143 144 if [ -z "$existingbootentryloaderfile" ]; then 145 echo "Creating UEFI boot entry for FreeBSD" 146 efibootmgr --create --label FreeBSD --loader "${mntpt}/EFI/freebsd/loader.efi" > /dev/null 147 if [ $? -ne 0 ]; then 148 die "Failed to create new boot entry" 149 fi 150 151 # When creating new entries, efibootmgr doesn't mark them active, so we need to 152 # do so. It doesn't make it easy to find which entry it just added, so rely on 153 # the fact that it places the new entry first in BootOrder. 154 bootorder=$(efivar --name 8be4df61-93ca-11d2-aa0d-00e098032b8c-BootOrder --print --no-name --hex | head -1) 155 bootentry=$(echo "${bootorder}" | cut -w -f 3)$(echo "${bootorder}" | cut -w -f 2) 156 echo "Marking UEFI boot entry ${bootentry} active" 157 efibootmgr --activate "${bootentry}" > /dev/null 158 else 159 echo "Existing UEFI FreeBSD boot entry found: not creating a new one" 160 fi 161 else 162 # Configure for booting from removable media 163 if [ ! -d "${mntpt}/EFI/BOOT" ]; then 164 mkdir -p "${mntpt}/EFI/BOOT" 165 fi 166 cp "${file}" "${mntpt}/EFI/BOOT/${efibootname}.efi" 167 fi 168 169 umount "${mntpt}" 170 rmdir "${mntpt}" 171 echo "Finished updating ESP" 172} 173 174make_esp() { 175 local file loaderfile 176 177 file=$1 178 loaderfile=$2 179 180 if [ -f "$file" ]; then 181 make_esp_file ${file} ${fat32min} ${loaderfile} 182 else 183 make_esp_device ${file} ${loaderfile} 184 fi 185} 186 187make_esp_mbr() { 188 dev=$1 189 dst=$2 190 191 s=$(find_part $dev "!239") 192 if [ -z "$s" ] ; then 193 s=$(find_part $dev "efi") 194 if [ -z "$s" ] ; then 195 die "No ESP slice found" 196 fi 197 fi 198 make_esp /dev/${dev}s${s} ${dst}/boot/loader.efi 199} 200 201make_esp_gpt() { 202 dev=$1 203 dst=$2 204 205 idx=$(find_part $dev "efi") 206 if [ -z "$idx" ] ; then 207 die "No ESP partition found" 208 fi 209 make_esp /dev/${dev}p${idx} ${dst}/boot/loader.efi 210} 211 212boot_nogeli_gpt_ufs_legacy() { 213 dev=$1 214 dst=$2 215 216 idx=$(find_part $dev "freebsd-boot") 217 if [ -z "$idx" ] ; then 218 die "No freebsd-boot partition found" 219 fi 220 doit gpart bootcode -b ${gpt0} -p ${gpt2} -i $idx $dev 221} 222 223boot_nogeli_gpt_ufs_uefi() { 224 make_esp_gpt $1 $2 225} 226 227boot_nogeli_gpt_ufs_both() { 228 boot_nogeli_gpt_ufs_legacy $1 $2 $3 229 boot_nogeli_gpt_ufs_uefi $1 $2 $3 230} 231 232boot_nogeli_gpt_zfs_legacy() { 233 dev=$1 234 dst=$2 235 236 idx=$(find_part $dev "freebsd-boot") 237 if [ -z "$idx" ] ; then 238 die "No freebsd-boot partition found" 239 fi 240 doit gpart bootcode -b ${gpt0} -p ${gptzfs2} -i $idx $dev 241} 242 243boot_nogeli_gpt_zfs_uefi() { 244 make_esp_gpt $1 $2 245} 246 247boot_nogeli_gpt_zfs_both() { 248 boot_nogeli_gpt_zfs_legacy $1 $2 $3 249 boot_nogeli_gpt_zfs_uefi $1 $2 $3 250} 251 252boot_nogeli_mbr_ufs_legacy() { 253 dev=$1 254 dst=$2 255 256 doit gpart bootcode -b ${mbr0} ${dev} 257 s=$(find_part $dev "freebsd") 258 if [ -z "$s" ] ; then 259 die "No freebsd slice found" 260 fi 261 doit gpart bootcode -p ${mbr2} ${dev}s${s} 262} 263 264boot_nogeli_mbr_ufs_uefi() { 265 make_esp_mbr $1 $2 266} 267 268boot_nogeli_mbr_ufs_both() { 269 boot_nogeli_mbr_ufs_legacy $1 $2 $3 270 boot_nogeli_mbr_ufs_uefi $1 $2 $3 271} 272 273boot_nogeli_mbr_zfs_legacy() { 274 dev=$1 275 dst=$2 276 277 # search to find the BSD slice 278 s=$(find_part $dev "freebsd") 279 if [ -z "$s" ] ; then 280 die "No BSD slice found" 281 fi 282 idx=$(find_part ${dev}s${s} "freebsd-zfs") 283 if [ -z "$idx" ] ; then 284 die "No freebsd-zfs slice found" 285 fi 286 # search to find the freebsd-zfs partition within the slice 287 # Or just assume it is 'a' because it has to be since it fails otherwise 288 doit gpart bootcode -b ${dst}/boot/mbr ${dev} 289 dd if=${dst}/boot/zfsboot of=/tmp/zfsboot1 count=1 290 doit gpart bootcode -b /tmp/zfsboot1 ${dev}s${s} # Put boot1 into the start of part 291 sysctl kern.geom.debugflags=0x10 # Put boot2 into ZFS boot slot 292 doit dd if=${dst}/boot/zfsboot of=/dev/${dev}s${s}a skip=1 seek=1024 293 sysctl kern.geom.debugflags=0x0 294} 295 296boot_nogeli_mbr_zfs_uefi() { 297 make_esp_mbr $1 $2 298} 299 300boot_nogeli_mbr_zfs_both() { 301 boot_nogeli_mbr_zfs_legacy $1 $2 $3 302 boot_nogeli_mbr_zfs_uefi $1 $2 $3 303} 304 305boot_geli_gpt_ufs_legacy() { 306 boot_nogeli_gpt_ufs_legacy $1 $2 $3 307} 308 309boot_geli_gpt_ufs_uefi() { 310 boot_nogeli_gpt_ufs_uefi $1 $2 $3 311} 312 313boot_geli_gpt_ufs_both() { 314 boot_nogeli_gpt_ufs_both $1 $2 $3 315} 316 317boot_geli_gpt_zfs_legacy() { 318 boot_nogeli_gpt_zfs_legacy $1 $2 $3 319} 320 321boot_geli_gpt_zfs_uefi() { 322 boot_nogeli_gpt_zfs_uefi $1 $2 $3 323} 324 325boot_geli_gpt_zfs_both() { 326 boot_nogeli_gpt_zfs_both $1 $2 $3 327} 328 329# GELI+MBR is not a valid configuration 330boot_geli_mbr_ufs_legacy() { 331 exit 1 332} 333 334boot_geli_mbr_ufs_uefi() { 335 exit 1 336} 337 338boot_geli_mbr_ufs_both() { 339 exit 1 340} 341 342boot_geli_mbr_zfs_legacy() { 343 exit 1 344} 345 346boot_geli_mbr_zfs_uefi() { 347 exit 1 348} 349 350boot_geli_mbr_zfs_both() { 351 exit 1 352} 353 354usage() { 355 printf 'Usage: %s -b bios [-d destdir] -f fs [-g geli] [-h] [-o optargs] -s scheme <bootdev>\n' "$0" 356 printf 'Options:\n' 357 printf ' bootdev device to install the boot code on\n' 358 printf ' -b bios bios type: legacy, uefi or both\n' 359 printf ' -d destdir destination filesystem root\n' 360 printf ' -f fs filesystem type: ufs or zfs\n' 361 printf ' -g geli yes or no\n' 362 printf ' -h this help/usage text\n' 363 printf ' -u Run commands such as efibootmgr to update the\n' 364 printf ' currently running system\n' 365 printf ' -o optargs optional arguments\n' 366 printf ' -s scheme mbr or gpt\n' 367 exit 0 368} 369 370srcroot=/ 371 372# Note: we really don't support geli boot in this script yet. 373geli=nogeli 374 375while getopts "b:d:f:g:ho:s:u" opt; do 376 case "$opt" in 377 b) 378 bios=${OPTARG} 379 ;; 380 d) 381 srcroot=${OPTARG} 382 ;; 383 f) 384 fs=${OPTARG} 385 ;; 386 g) 387 case ${OPTARG} in 388 [Yy][Ee][Ss]|geli) geli=geli ;; 389 *) geli=nogeli ;; 390 esac 391 ;; 392 u) 393 updatesystem=1 394 ;; 395 o) 396 opts=${OPTARG} 397 ;; 398 s) 399 scheme=${OPTARG} 400 ;; 401 402 ?|h) 403 usage 404 ;; 405 esac 406done 407 408if [ -n "${scheme}" ] && [ -n "${fs}" ] && [ -n "${bios}" ]; then 409 shift $((OPTIND-1)) 410 dev=$1 411fi 412 413# For gpt, we need to install pmbr as the primary boot loader 414# it knows about 415gpt0=${srcroot}/boot/pmbr 416gpt2=${srcroot}/boot/gptboot 417gptzfs2=${srcroot}/boot/gptzfsboot 418 419# For MBR, we have lots of choices, but select mbr, boot0 has issues with UEFI 420mbr0=${srcroot}/boot/mbr 421mbr2=${srcroot}/boot/boot 422 423# sanity check here 424 425# Check if we've been given arguments. If not, this script is probably being 426# sourced, so we shouldn't run anything. 427if [ -n "${dev}" ]; then 428 eval boot_${geli}_${scheme}_${fs}_${bios} $dev $srcroot $opts || echo "Unsupported boot env: ${geli}-${scheme}-${fs}-${bios}" 429fi 430