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 mntpt 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 dd if=/dev/zero of="${file}" bs=1k count="${sizekb}" 61 device=$(mdconfig -a -t vnode -f "${file}") 62 newfs_msdos -F "${fatbits}" -c 1 -L EFISYS "/dev/${device}" > /dev/null 2>&1 63 mntpt=$(mktemp -d /tmp/stand-test.XXXXXX) 64 mount -t msdosfs "/dev/${device}" "${mntpt}" 65 mkdir -p "${mntpt}/EFI/BOOT" 66 efibootname=$(get_uefi_bootname) 67 cp "${loader}" "${mntpt}/EFI/BOOT/${efibootname}.efi" 68 umount "${mntpt}" 69 rmdir "${mntpt}" 70 mdconfig -d -u "${device}" 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 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 162 umount "${mntpt}" 163 rmdir "${mntpt}" 164 echo "Finished updating ESP" 165} 166 167make_esp() { 168 local file loaderfile 169 170 file=$1 171 loaderfile=$2 172 173 if [ -f "$file" ]; then 174 make_esp_file ${file} ${fat32min} ${loaderfile} 175 else 176 make_esp_device ${file} ${loaderfile} 177 fi 178} 179 180make_esp_mbr() { 181 dev=$1 182 dst=$2 183 184 s=$(find-part $dev "!239") 185 if [ -z "$s" ] ; then 186 s=$(find-part $dev "efi") 187 if [ -z "$s" ] ; then 188 die "No ESP slice found" 189 fi 190 fi 191 make_esp /dev/${dev}s${s} ${dst}/boot/loader.efi 192} 193 194make_esp_gpt() { 195 dev=$1 196 dst=$2 197 198 idx=$(find-part $dev "efi") 199 if [ -z "$idx" ] ; then 200 die "No ESP partition found" 201 fi 202 make_esp /dev/${dev}p${idx} ${dst}/boot/loader.efi 203} 204 205boot_nogeli_gpt_ufs_legacy() { 206 dev=$1 207 dst=$2 208 209 idx=$(find-part $dev "freebsd-boot") 210 if [ -z "$idx" ] ; then 211 die "No freebsd-boot partition found" 212 fi 213 doit gpart bootcode -b ${gpt0} -p ${gpt2} -i $idx $dev 214} 215 216boot_nogeli_gpt_ufs_uefi() { 217 make_esp_gpt $1 $2 218} 219 220boot_nogeli_gpt_ufs_both() { 221 boot_nogeli_gpt_ufs_legacy $1 $2 $3 222 boot_nogeli_gpt_ufs_uefi $1 $2 $3 223} 224 225boot_nogeli_gpt_zfs_legacy() { 226 dev=$1 227 dst=$2 228 229 idx=$(find-part $dev "freebsd-boot") 230 if [ -z "$idx" ] ; then 231 die "No freebsd-boot partition found" 232 fi 233 doit gpart bootcode -b ${gpt0} -p ${gptzfs2} -i $idx $dev 234} 235 236boot_nogeli_gpt_zfs_uefi() { 237 make_esp_gpt $1 $2 238} 239 240boot_nogeli_gpt_zfs_both() { 241 boot_nogeli_gpt_zfs_legacy $1 $2 $3 242 boot_nogeli_gpt_zfs_uefi $1 $2 $3 243} 244 245boot_nogeli_mbr_ufs_legacy() { 246 dev=$1 247 dst=$2 248 249 doit gpart bootcode -b ${mbr0} ${dev} 250 s=$(find-part $dev "freebsd") 251 if [ -z "$s" ] ; then 252 die "No freebsd slice found" 253 fi 254 doit gpart bootcode -p ${mbr2} ${dev}s${s} 255} 256 257boot_nogeli_mbr_ufs_uefi() { 258 make_esp_mbr $1 $2 259} 260 261boot_nogeli_mbr_ufs_both() { 262 boot_nogeli_mbr_ufs_legacy $1 $2 $3 263 boot_nogeli_mbr_ufs_uefi $1 $2 $3 264} 265 266boot_nogeli_mbr_zfs_legacy() { 267 dev=$1 268 dst=$2 269 270 # search to find the BSD slice 271 s=$(find-part $dev "freebsd") 272 if [ -z "$s" ] ; then 273 die "No BSD slice found" 274 fi 275 idx=$(find-part ${dev}s${s} "freebsd-zfs") 276 if [ -z "$idx" ] ; then 277 die "No freebsd-zfs slice found" 278 fi 279 # search to find the freebsd-zfs partition within the slice 280 # Or just assume it is 'a' because it has to be since it fails otherwise 281 doit gpart bootcode -b ${dst}/boot/mbr ${dev} 282 dd if=${dst}/boot/zfsboot of=/tmp/zfsboot1 count=1 283 doit gpart bootcode -b /tmp/zfsboot1 ${dev}s${s} # Put boot1 into the start of part 284 sysctl kern.geom.debugflags=0x10 # Put boot2 into ZFS boot slot 285 doit dd if=${dst}/boot/zfsboot of=/dev/${dev}s${s}a skip=1 seek=1024 286 sysctl kern.geom.debugflags=0x0 287} 288 289boot_nogeli_mbr_zfs_uefi() { 290 make_esp_mbr $1 $2 291} 292 293boot_nogeli_mbr_zfs_both() { 294 boot_nogeli_mbr_zfs_legacy $1 $2 $3 295 boot_nogeli_mbr_zfs_uefi $1 $2 $3 296} 297 298boot_geli_gpt_ufs_legacy() { 299 boot_nogeli_gpt_ufs_legacy $1 $2 $3 300} 301 302boot_geli_gpt_ufs_uefi() { 303 boot_nogeli_gpt_ufs_uefi $1 $2 $3 304} 305 306boot_geli_gpt_ufs_both() { 307 boot_nogeli_gpt_ufs_both $1 $2 $3 308} 309 310boot_geli_gpt_zfs_legacy() { 311 boot_nogeli_gpt_zfs_legacy $1 $2 $3 312} 313 314boot_geli_gpt_zfs_uefi() { 315 boot_nogeli_gpt_zfs_uefi $1 $2 $3 316} 317 318boot_geli_gpt_zfs_both() { 319 boot_nogeli_gpt_zfs_both $1 $2 $3 320} 321 322# GELI+MBR is not a valid configuration 323boot_geli_mbr_ufs_legacy() { 324 exit 1 325} 326 327boot_geli_mbr_ufs_uefi() { 328 exit 1 329} 330 331boot_geli_mbr_ufs_both() { 332 exit 1 333} 334 335boot_geli_mbr_zfs_legacy() { 336 exit 1 337} 338 339boot_geli_mbr_zfs_uefi() { 340 exit 1 341} 342 343boot_geli_mbr_zfs_both() { 344 exit 1 345} 346 347boot_nogeli_vtoc8_ufs_ofw() { 348 dev=$1 349 dst=$2 350 351 # For non-native builds, ensure that geom_part(4) supports VTOC8. 352 kldload geom_part_vtoc8.ko 353 doit gpart bootcode -p ${vtoc8} ${dev} 354} 355 356usage() { 357 printf 'Usage: %s -b bios [-d destdir] -f fs [-g geli] [-h] [-o optargs] -s scheme <bootdev>\n' "$0" 358 printf 'Options:\n' 359 printf ' bootdev device to install the boot code on\n' 360 printf ' -b bios bios type: legacy, uefi or both\n' 361 printf ' -d destdir destination filesystem root\n' 362 printf ' -f fs filesystem type: ufs or zfs\n' 363 printf ' -g geli yes or no\n' 364 printf ' -h this help/usage text\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:" 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 o) 393 opts=${OPTARG} 394 ;; 395 s) 396 scheme=${OPTARG} 397 ;; 398 399 ?|h) 400 usage 401 ;; 402 esac 403done 404 405if [ -n "${scheme}" ] && [ -n "${fs}" ] && [ -n "${bios}" ]; then 406 shift $((OPTIND-1)) 407 dev=$1 408fi 409 410# For gpt, we need to install pmbr as the primary boot loader 411# it knows about 412gpt0=${srcroot}/boot/pmbr 413gpt2=${srcroot}/boot/gptboot 414gptzfs2=${srcroot}/boot/gptzfsboot 415 416# For MBR, we have lots of choices, but select mbr, boot0 has issues with UEFI 417mbr0=${srcroot}/boot/mbr 418mbr2=${srcroot}/boot/boot 419 420# VTOC8 421vtoc8=${srcroot}/boot/boot1 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