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 "$*"; fi | |||||
} | |||||
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… | |||||
# 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 ;; | |||||
*) | |||||
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… | |||||
echo "machine type $(uname -m) doesn't support UEFI" | |||||
exit 1 | |||||
;; | |||||
esac | |||||
} | |||||
clean_up() { | |||||
trap 1 2 15 EXIT | |||||
if [ -z "${mntpt}" ]; then exit 1; fi | |||||
if mount | grep -q "${mntpt}" ; then | |||||
umount "${mntpt}" | |||||
fi | |||||
if [ -d "${mntpt}" ]; then rmdir "${mntpt}"; fi | |||||
echo "Something went wrong. The ESP(s) used were: ${esps}" | |||||
echo "Any backups of the previous loader.efi and $(get_uefi_bootname).efi are in /tmp/espback.*" | |||||
} | |||||
# Determine whether /EFI/BOOT/BOOTxxx.efi is the FreeBSD boot1.efi or loader.efi executable. | |||||
bootefi_is_freebsd() { | |||||
efibootname=$(get_uefi_bootname) | |||||
if [ -f "${mntpt}/EFI/BOOT/${efibootname}.efi" ]; then | |||||
# "FreeBSD EFI boot block" is contained in boot1.efi; | |||||
# "FreeBSD/${arch} EFI loader" is contained in loader.efi | |||||
loaderstring="FreeBSD/$(uname -m) EFI loader" | |||||
boot1string="FreeBSD EFI boot block" | |||||
if grep -q -e "${boot1string}" -e "${loaderstring}" "${mntpt}/EFI/BOOT/${efibootname}.efi"; then | |||||
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… | |||||
return 0 | |||||
Done Inline ActionsYou can return 0 here. imp: You can return 0 here.
| |||||
fi | |||||
fi | |||||
Done Inline Actionsand return 1 here. imp: and return 1 here. | |||||
return 1 | |||||
} | |||||
# 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}" | |||||
kevansUnsubmitted 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… | |||||
bcranAuthorUnsubmitted 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… | |||||
ldrsize=$(stat -f %z "${loader}") | |||||
ldrsize=$((ldrsize / 1024)) | |||||
efibootname=$(get_uefi_bootname) | |||||
# Check we have enough space to install /EFI/BOOT/BOOT${arch}.efi | |||||
if bootefi_is_freebsd; then | |||||
existing_bootefi_size=$(stat -f %z "${mntpt}/EFI/BOOT/${efibootname}.efi") | |||||
existing_bootefi_size=$((existing_bootefi_size / 1024)) | |||||
spaceneeded=$((existing_bootefi_size - ldrsize)) | |||||
fi | |||||
Done Inline ActionsYou could test directly EFI/FreeBSD and mkdir -p manu: You could test directly EFI/FreeBSD and mkdir -p | |||||
if bootefi_is_freebsd && [ $(($(get_freespace_on_esp))) -lt ${spaceneeded} ]; then | |||||
echo "Error: Insufficient space on ESP ${espdev} to install ${loader} as /EFI/FreeBSD/loader.efi" | |||||
echo "Need ${spaceneeded}KB, but only $(($(get_freespace_on_esp))) remains." | |||||
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… | |||||
exit 1 | |||||
fi | |||||
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… | |||||
# Check we have enough space to install /EFI/FreeBSD/loader.efi | |||||
if [ -f "${mntpt}/EFI/FreeBSD/loader.efi" ]; then | |||||
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=$(stat -f %z "${mntpt}/EFI/FreeBSD/loader.efi") | |||||
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. | |||||
existing_ldr_size=$((existing_ldr_size / 1024)) | |||||
spaceneeded=$((existing_ldr_size - ldrsize)) | |||||
fi | |||||
if [ -f "${mntpt}/EFI/FreeBSD/loader.efi" ] && [ $(($(get_freespace_on_esp))) -lt ${spaceneeded} ]; then | |||||
echo "Error: Insufficient space on ESP ${espdev} to install ${loader} to /EFI/BOOT/${efibootname}.efi." | |||||
echo "Need ${spaceneeded}KB, but only $(($(get_freespace_on_esp))) remains." | |||||
exit 1 | |||||
fi | |||||
backdir=$(mktemp -d /tmp/espback.XXXXXX) | |||||
# Make backups and copy the new loader | |||||
if [ -f "${mntpt}/EFI/FreeBSD/loader.efi" ] && ! cmp -sz "${loader}" "${mntpt}/EFI/FreeBSD/loader.efi"; then | |||||
cp "${mntpt}/EFI/FreeBSD/loader.efi" "${backdir}" | |||||
eval vout "Copying ${loader} to /EFI/FreeBSD/loader.efi" | |||||
cp "${loader}" "${mntpt}/EFI/FreeBSD/loader.efi" | |||||
[ -f "${mntpt}/EFI/FreeBSD/loader-old.efi" ] && rm "${mntpt}/EFI/FreeBSD/loader-old.efi" | |||||
elif [ -f "${mntpt}/EFI/FreeBSD/loader.efi" ]; then | |||||
eval vout "${loader} is the same as /EFI/FreeBSD/loader.efi. Nothing to do." | |||||
fi | |||||
if bootefi_is_freebsd && ! cmp -sz "${loader}" "${mntpt}/EFI/BOOT/${efibootname}.efi"; then | |||||
cp "${mntpt}/EFI/BOOT/${efibootname}.efi" "${backdir}/${efibootname}.efi" | |||||
eval vout "Copying ${loader} to /EFI/BOOT/${efibootname}.efi" | |||||
cp "${loader}" "${mntpt}/EFI/BOOT/${efibootname}.efi" | |||||
[ -f "${mntpt}/EFI/BOOT/${efibootname}-old.efi" ] && rm "${mntpt}/EFI/BOOT/${efibootname}-old.efi" | |||||
elif bootefi_is_freebsd; then | |||||
eval vout "${loader} is the same as /EFI/BOOT/${efibootname}.efi. Nothing to do." | |||||
fi | |||||
# Copy backups to the ESP | |||||
if [ -f "${backdir}/loader.efi" ]; then | |||||
sz=$(stat -f %z "${backdir}/loader.efi") | |||||
sz=$((sz / 1024)) | |||||
if [ $(($(get_freespace_on_esp))) -gt ${sz} ]; then | |||||
eval vout "Backing up previous /EFI/FreeBSD/loader.efi to /EFI/FreeBSD/loader-old.efi" | |||||
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… | |||||
cp "${backdir}/loader.efi" "${mntpt}/EFI/FreeBSD/loader-old.efi" | |||||
else | |||||
eval vout "Skipping backup of previous /EFI/FreeBSD/loader.efi due to insufficient disk space." | |||||
fi | |||||
fi | |||||
if [ -f "${backdir}/${efibootname}.efi" ]; then | |||||
sz=$(stat -f %z "${backdir}/${efibootname}.efi") | |||||
sz=$((sz / 1024)) | |||||
if [ $(($(get_freespace_on_esp))) -gt ${sz} ]; then | |||||
eval vout "Backing up previous /EFI/BOOT/${efibootname}.efi to /EFI/BOOT/${efibootname}-old.efi" | |||||
cp "${backdir}/${efibootname}.efi" "${mntpt}/EFI/BOOT/${efibootname}-old.efi" | |||||
else | |||||
eval vout "Skipping backup of previous /EFI/BOOT/${efibootname}.efi due to insufficient disk space." | |||||
fi | |||||
fi | |||||
umount "${mntpt}" | |||||
rmdir "${mntpt}" | |||||
if [ -f "${backdir}/loader.efi" ]; then rm "${backdir}/loader.efi"; fi | |||||
if [ -f "${backdir}/${efibootname}.efi" ]; then rm "${backdir}/${efibootname}.efi"; fi | |||||
if [ -d "${backdir}" ]; then rmdir "${backdir}"; fi | |||||
} | |||||
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 "${esps}" ]; 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 | |||||
kevansUnsubmitted 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… | |||||
bcranAuthorUnsubmitted Done Inline ActionsThanks, I've fixed it in the latest diff. bcran: Thanks, I've fixed it in the latest diff. | |||||
eval usage | |||||
exit 1 | |||||
fi | |||||
for esp in ${esps}; do | |||||
eval copyloader "${esp}" "${loader}" | |||||
done | |||||
trap 1 2 15 EXIT |
[ -n ${verbose} ] && echo $*
should suffice. You don't need the extra stuff $@ does, nor the extra quotes, nor even the if.