Changeset View
Standalone View
usr.sbin/efi-update-loader/efi-update-loader.sh
- This file was added.
Property | Old Value | New Value |
---|---|---|
File Mode | null | 100755 |
#!/bin/sh | |||||
# | |||||
# SPDX-License-Identifier: BSD-2-Clause-FreeBSD | |||||
# | |||||
# Copyright (c) 2019 Rebecca Cran <bcran@FreeBSD.org>. | |||||
# | |||||
# 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 "$@" | |||||
imp: [ -n ${verbose} ] && echo $*
should suffice. You don't need the extra stuff $@ does, nor the… | |||||
Done Inline ActionsExcept since the script is set -e, that then needs a || true to avoid the statement causing an error. bcran: Except since the script is `set -e`, that then needs a `|| true` to avoid the statement causing… | |||||
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 ;; | |||||
Done Inline ActionsI'd be tempted to line the echos up with the echo below. imp: I'd be tempted to line the echos up with the echo below.
| |||||
Done Inline ActionsIn the latest revision they look lined up in QtCreator, but for some reason they're wrong here - I'm using tabs, not spaces. bcran: In the latest revision they look lined up in QtCreator, but for some reason they're wrong here… | |||||
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 | |||||
boot1asbootefi=$(grep "FreeBSD EFI boot block" "${mntpt}/EFI/BOOT/${efibootname}.efi") || true | |||||
Done Inline ActionsDoes this cope with boot1.ef? imp: Does this cope with boot1.ef?
| |||||
Done Inline ActionsWas "boot1.ef" a typo? It copes with boot1.efi, but not currently loader.efi. Since we'll want to keep this file, I'll update it to check for strings that are in loader.efi too. bcran: Was "boot1.ef" a typo? It copes with boot1.efi, but not currently loader.efi. Since we'll want… | |||||
if [ -n "${boot1asbootefi}" ]; then | |||||
Done Inline ActionsYou can return 0 here. imp: You can return 0 here.
| |||||
echo "true" | |||||
fi | |||||
fi | |||||
Done Inline Actionsand return 1 here. imp: and return 1 here. | |||||
} | |||||
# 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 | |||||
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))) | |||||
label=$(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 | |||||
label=$(g${class} status -s 2> /dev/null | grep "${label}" | awk '{print $3}') || true | |||||
if [ -n "${label}" ]; then | |||||
break | |||||
fi | |||||
done | |||||
fi | |||||
for l in ${label}; do | |||||
dev=$(geom label status -s | grep "${l}" | awk '{print $3}') | |||||
if [ -n "${dev}" ]; then | |||||
d="${dev}" | |||||
else | |||||
d="${l}" | |||||
fi | |||||
disk="${disk} $(echo "${d}" | awk -F '[ps]+[0-9]+' '{print $1}')" | |||||
done | |||||
for d in ${disk}; do | |||||
idx=$(gpart show "${d}" 2> /dev/null | grep efi | awk '{print $3}') | |||||
if [ -n "${idx}" ]; then | |||||
if [ -e "/dev/${d}p${idx}" ]; then | |||||
esps="${esps} /dev/${d}p${idx}" | |||||
elif [ -e "/dev/${d}s${idx}" ]; then | |||||
esps="${esps} /dev/${d}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) | |||||
Done Inline ActionsThis should take into an account an ESP we're copying to already having been mounted. For some images, it'll be premounted at /boot/msdos (or something relevant, like /boot/efi or /efi) and we probably should just use that mountpoint rather than mounting/unmounting it. We'll need to be careful about not unmounting it in cleanup, too, if that's the case. kevans: This should take into an account an ESP we're copying to already having been mounted. For some… | |||||
Done Inline ActionsGood point. I've fixed this in the latest revision and tested on a Beaglebone Black where the ESP is mounted on /boot/msdos. bcran: Good point. I've fixed this in the latest revision and tested on a Beaglebone Black where the… | |||||
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 | |||||
Done Inline ActionsYou could test directly EFI/FreeBSD and mkdir -p manu: You could test directly EFI/FreeBSD and mkdir -p | |||||
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 | |||||
Done Inline ActionsIf you make the changes I suggested, this can turn into a simple if bootefi_is_freebsd; then which seems less convoluted to me. imp: If you make the changes I suggested, this can turn into a simple
if bootefi_is_freebsd; then… | |||||
# 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. | |||||
Done Inline ActionsThis will break system that don't have persistant variable, for those we need to use EFI/BOOT/boot${arch}.efi manu: This will break system that don't have persistant variable, for those we need to use… | |||||
Done Inline ActionsIn the latest revision of the script I've changed it so that if /EFI/BOOT/BOOT${arch}.efi exists, it will copy loader.efi to that file. bcran: In the latest revision of the script I've changed it so that if /EFI/BOOT/BOOT${arch}.efi… | |||||
if [ -f "${mntpt}/EFI/FreeBSD/loader.efi" ]; then | |||||
existing_ldr_size=$(stat -f %z "${mntpt}/EFI/FreeBSD/loader.efi") | |||||
Not Done Inline ActionsI think with this approach, you'll need to also set some local indicator that esppath should be cleared out just after you don't unmount/rmdir the mntpt. Otherwise, subsequent copyloader will suddenly fail as we can't clobber the already-mounted ESP that we don't want to touch. This is likely really really edge-casey, though... kevans: I think with this approach, you'll need to also set some local indicator that esppath should be… | |||||
Done Inline ActionsGood point. bcran: Good point. | |||||
existing_ldr_size=$((existing_ldr_size / 1024)) | |||||
Done Inline Actionswhat if it was mounted RO? tsoome: what if it was mounted RO? | |||||
Done Inline ActionsI guess we should keep track of auto-detected ESP vs. user-specified ESP. If user-specified it, we should probably re-mount it R/W and then return it to R/O when we're done, no? kevans: I guess we should keep track of auto-detected ESP vs. user-specified ESP. If user-specified it… | |||||
Done Inline ActionsYeah, we probably should do that, instead of erroring out if it's mounted RO. bcran: Yeah, we probably should do that, instead of erroring out if it's mounted RO. | |||||
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}" | |||||
Done Inline ActionsOn some system you will not have efirt for variable, can efibootmgr test this ? manu: On some system you will not have efirt for variable, can efibootmgr test this ? | |||||
Done Inline ActionsIt doesn't today, but we can do the right thing with efivar if uboot's EFI rejects permanent changes to variables. imp: It doesn't today, but we can do the right thing with efivar if uboot's EFI rejects permanent… | |||||
Done Inline ActionsI think we also need to test this with bhyve+UEFI, but I suspect (IIRC) both implementations accept the UEFI vars and they just don't persist. kevans: I think we also need to test this with bhyve+UEFI, but I suspect (IIRC) both implementations… | |||||
Done Inline ActionsSo in that case we definitely do _not_ want to remove the EFI/BOOT/BOOT{$arch}.efi file. bcran: So in that case we definitely do _not_ want to remove the EFI/BOOT/BOOT{$arch}.efi file.
And… | |||||
} | |||||
usage() { | |||||
printf 'usage: %s -d [espdev] -l [loader]\n' "${progname}" | |||||
printf '\t-d ESP device\n' | |||||
printf '\t-l FreeBSD EFI loader\n' | |||||
printf '\t-v 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 | |||||
Done Inline ActionsThis block should be redundant now; we've guaranteed just above that we either have non-zero user supplied ${esps} or we failed to autodetect ESP and bailed out with the "Error: could not detect ESP containing FreeBSD loader to update" kevans: This block should be redundant now; we've guaranteed just above that we either have non-zero… | |||||
Done Inline ActionsThanks, I've fixed it in the latest diff. bcran: Thanks, I've fixed it in the latest diff. |
[ -n ${verbose} ] && echo $*
should suffice. You don't need the extra stuff $@ does, nor the extra quotes, nor even the if.