install-boot.sh 11 KB

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