Index: usr.sbin/efi-update-loader/efi-update-loader.8 =================================================================== --- /dev/null +++ usr.sbin/efi-update-loader/efi-update-loader.8 @@ -0,0 +1,71 @@ +\" +.\" Copyright (c) 2019 Rebecca Cran . +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd March 16, 2019 +.Dt EFI-UPDATE-LOADER 8 +.Os +.Sh NAME +.Nm efi-update-loader +.Nd Update an EFI System Partition with a FreeBSD loader +.Sh SYNOPSIS +.Nm +.Op Fl d Ar device +.Op Fl l Ar loader +.Op Fl v +.Sh DESCRIPTION +This program updates the +.Dq Unified Extensible Firmware Interface +.Pq UEFI +.Dq EFI System Partition +.Pq ESP +with a new FreeBSD loader.efi file. + +The ESP is a FAT12, FAT16 or FAT32 filesystem that is used by the UEFI system +firmware to load and run OS boot loaders that are compiled as +.Dq Portable Executable +.Pq PE +binaries. The UEFI Boot Manager, which can be configured with the +.Xr efibootmgr 8 +utility, contains entries which point to the location of the boot loader +within the ESP. +.Pp +The following options are available: +.Bl -tag -width indent +.It Fl d Ar device +Specify the device to use as the ESP. +.It Fl l Ar loader +Specify the file to install as the FreeBSD boot loader. This should normally +be /boot/loader.efi, but could also for example be /boot/loader_4th.efi etc. +.It Fl v +Enable verbose output +.Sh SEE ALSO +.Xr efibootmgr 8 , +.Xr efivar 8 +.Sh HISTORY +The +.Nm +utility first appeared in +.Fx 13.0 Index: usr.sbin/efi-update-loader/efi-update-loader.sh =================================================================== --- /dev/null +++ usr.sbin/efi-update-loader/efi-update-loader.sh @@ -0,0 +1,279 @@ +#!/bin/sh +# +# SPDX-License-Identifier: BSD-2-Clause-FreeBSD +# +# Copyright (c) 2019 Rebecca Cran . +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# $FreeBSD$ + +set -e + +vout() { + if [ -n "${verbose}" ]; then + echo "$@" + fi +} + + +# Find the name of the default boot loader on the current architecture. +# This file is in the /EFI/BOOT directory on the ESP. +get_uefi_bootname() { + + case ${TARGET:-$(uname -m)} in + amd64) echo bootx64 ;; + arm64) echo bootaa64 ;; + i386) echo bootia32 ;; + arm) echo bootarm ;; + *) + echo "machine type $(uname -m) doesn't support UEFI" + exit 1 + ;; + esac +} + + +clean_up() { + if [ -z "${mntpt}" ]; then + exit 1 + fi + + if mount | grep -q "${mntpt}" ; then + umount "${mntpt}" + fi + + if [ -d "${mntpt}" ]; then + rmdir "${mntpt}" + fi +} + + +# Determine whether /EFI/BOOT/BOOTxxx.efi is the FreeBSD boot1.efi executable. +bootefi_is_freebsd() { + + efibootname=$(get_uefi_bootname) + + if [ -f "${mntpt}/EFI/BOOT/${efibootname}.efi" ]; then + if grep -q "FreeBSD EFI boot block" "${mntpt}/EFI/BOOT/${efibootname}.efi"; then + echo "true" + fi + fi +} + + +# Find all ESPs on the same disk(s) as the root filesystem. +detect_esps() { + mnt=/ + fsname=$(df "${mnt}" | tail -1 | cut -d ' ' -f 1) + fstype= + + if df -t ufs "${mnt}" > /dev/null ; then + fstype=ufs + elif df -t zfs "${mnt}" > /dev/null ; then + fstype=zfs + else + echo "Unsupported filesystem type" + exit 1 + fi + + if [ "${fstype}" = "zfs" ]; then + fslabel=$(df "${mnt}" | tail -1 | awk -F '[ /]' '{print $1}') + zpool=$(zpool list -Hv "${fslabel}") + totallines=$(echo "${zpool}" | wc -l) + devlines=$(echo "${zpool}" | tail -$((totallines - 1))) + labels=$(echo "${devlines}" | awk '{print $1}') + elif [ "${fstype}" = "ufs" ]; then + label=$(echo "${fsname}" | cut -c 6-256) + + for class in mirror raid raid3 vinum stripe virstor; do + labels=$(geom ${class} status -s 2> /dev/null | grep "${label}" | awk '{print $3}') || true + if [ -n "${labels}" ]; then + break + fi + done + fi + + for lbl in ${labels}; do + lblout=$(geom label status -s | grep "${lbl}" | awk '{print $3}') + if [ -n "${lblout}" ]; then + dev="${lblout}" + else + dev="${lbl}" + fi + + # Get the disk name from the partition/slice name + disks="${disks} $(echo "${dev}" | awk -F '[ps]+[0-9]+' '{print $1}')" + done + + for disk in ${disks}; do + idx=$(gpart show "${disk}" 2> /dev/null | grep efi | awk '{print $3}') + if [ -n "${idx}" ]; then + if [ -e "/dev/${disk}p${idx}" ]; then + esps="${esps} /dev/${disk}p${idx}" + elif [ -e "/dev/${disk}s${idx}" ]; then + esps="${esps} /dev/${disk}s${idx}" + fi + fi + done + + if [ -n "${verbose}" ]; then + echo -n "Found ESP(S): " + for esp in ${esps}; do + echo -n "${esp} " + done + echo + fi +} + + +# Return the free disk space, in KB, on the mounted ESP. +get_freespace_on_esp() { + df -k "${mntpt}" | tail -1 | awk '{print $4}' +} + + +copyloader() { + mntpt=$(mktemp -d /tmp/esp.XXXXXX) + espdev=$1 + + mount -t msdosfs "${espdev}" "${mntpt}" + + ldrsize=$(stat -f %z "${loader}") + # Convert to KB + ldrsize=$((ldrsize / 1024)) + + efibootname=$(get_uefi_bootname) + + if [ ! -d "${mntpt}/EFI" ]; then + mkdir "${mntpt}/EFI" + fi + + if [ ! -d "${mntpt}/EFI/FreeBSD" ]; then + mkdir "${mntpt}/EFI/FreeBSD" + fi + + # If /EFI/BOOT/BOOTxxx.efi is the FreeBSD boot1.efi, we need to move it out + # of the way so we can subsequently boot from /EFI/FreeBSD/loader.efi + if [ -n "$(bootefi_is_freebsd)" ]; then + echo "Moving FreeBSD bootx64.efi to /EFI/FreeBSD/${efibootname}-old.efi" + mv "${mntpt}/EFI/BOOT/${efibootname}.efi" "${mntpt}/EFI/FreeBSD/${efibootname}-old.efi" + fi + + # If we already have /EFI/FreeBSD/loader.efi, we need to fetch its file size + # to calculate if we have enough space left to back it up. + if [ -f "${mntpt}/EFI/FreeBSD/loader.efi" ]; then + existing_ldr_size=$(stat -f %z "${mntpt}/EFI/FreeBSD/loader.efi") + existing_ldr_size=$((existing_ldr_size / 1024)) + fi + + # Now, we decide if we need to, or can, back up the existing loader.efi + # We'll back it up if we have enough space left, and it's difference from + # the new loader.efi + if [ -f "${mntpt}/EFI/FreeBSD/loader.efi" ] && ! cmp -sz "${loader}" "${mntpt}/EFI/FreeBSD/loader.efi"; then + # Check we have enough free space + if [ ! -e "${mntpt}/EFI/FreeBSD/loader-old.efi" ] && [ $(($(get_freespace_on_esp))) -lt ${existing_ldr_size} ] ; then + echo "Insufficient space on ESP to back up loader.efi; skipping backup" + else + eval vout "Backing up /EFI/FreeBSD/loader.efi to /EFI/FreeBSD/loader-old.efi" + cp "${mntpt}/EFI/FreeBSD/loader.efi" "${mntpt}/EFI/FreeBSD/loader-old.efi" + fi + fi + + if [ -e "${mntpt}/EFI/FreeBSD/loader.efi" ]; then + spaceneeded=$((existing_ldr_size - ldrsize)) + else + spaceneeded=${ldrsize} + fi + + if [ $(($(get_freespace_on_esp))) -lt ${spaceneeded} ]; then + echo "Error: Insufficient space on ESP ${espdev} to install ${loader}." + eval clean_up + exit 1 + fi + + eval vout "Copying ${loader} to /EFI/FreeBSD/loader.efi" + cp "${loader}" "${mntpt}/EFI/FreeBSD/loader.efi" + + if ! efibootmgr -v | grep -qi "EFI/FreeBSD/loader.efi" ; then + echo "Boot Option for FreeBSD doesn't exist: it's recommended to run efibootmgr. e.g.:" + echo " efibootmgr -c -a -L FreeBSD -l /mnt/EFI/FreeBSD/loader.efi" + fi + + umount "${mntpt}" + rmdir "${mntpt}" +} + + +usage() { + printf 'usage: %s [-d device] [-l loader] [-v]\n' "${progname}" + printf '\t-d device\tEFI System Partition (ESP) device name\n' + printf '\t-l loader\tFreeBSD EFI loader (default is /boot/loader.efi)\n' + printf '\t-v \t\tEnable verbose output\n' + exit 0 +} + + +progname=$0 +loader=/boot/loader.efi + +while getopts "vd:l:h" opt; do + case "$opt" in + d) + esps=${OPTARG} + ;; + l) + loader=${OPTARG} + ;; + v) + verbose=1 + ;; + ?) + usage + ;; + esac +done + +trap clean_up 1 2 15 EXIT + +# If the user didn't specify a device to update, look for any ESPs on the same +# disk(s) as the root filesystem. +if [ -z "${espdev}" ]; then + + eval detect_esps + + if [ -z "${esps}" ]; then + echo "Error: could not detect ESP containing FreeBSD loader to update" + exit 1 + fi +fi + +if [ -z "${esps}" ]; then + eval usage + exit 1 +fi + +for esp in ${esps}; do + eval copyloader "${esp}" "${loader}" +done + +trap 1 2 15 EXIT