From 8d4af8db9705eb96bac82c3aa6a18afe331510df Mon Sep 17 00:00:00 2001 From: Tim Gover Date: Mon, 28 Sep 2020 10:31:42 +0100 Subject: [PATCH 01/13] rpi-eeprom-update: Use multiple sources for BOARD_INFO Try device-tree, then cpuinfo and failing that raw OTP. --- rpi-eeprom-update | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/rpi-eeprom-update b/rpi-eeprom-update index a5ffecd..7496974 100755 --- a/rpi-eeprom-update +++ b/rpi-eeprom-update @@ -268,7 +268,15 @@ getBootloaderUpdateVersion() { } checkDependencies() { - BOARD_INFO="$(od -v -An -t x1 /sys/firmware/devicetree/base/system/linux,revision | tr -d ' \n')" + + if [ -f "/sys/firmware/devicetree/base/system/linux,revision" ]; then + BOARD_INFO="$(od -v -An -t x1 /sys/firmware/devicetree/base/system/linux,revision | tr -d ' \n')" + elif grep -q Revision /proc/cpuinfo; then + BOARD_INFO="$(sed -n '/^Revision/s/^.*: \(.*\)/\1/p' < /proc/cpuinfo)" + else + BOARD_INFO="$(vcgencmd otp_dump | grep '30:' | sed 's/.*://')" + fi + if [ $(((0x$BOARD_INFO >> 23) & 1)) -ne 0 ] && [ $(((0x$BOARD_INFO >> 12) & 15)) -eq 3 ]; then echo "BCM2711 detected" else From 29fe479af9c3fad2ac4c1b1d65aedf88b6f97626 Mon Sep 17 00:00:00 2001 From: Tim Gover Date: Thu, 24 Sep 2020 14:56:32 +0100 Subject: [PATCH 02/13] rpi-eeprom-update: Restrict package checksums to EEPROM images. Only check EEPROM image binaries because it's annoying if rpi-eeprom-update errors due to the release notes or the script changing. --- rpi-eeprom-update | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/rpi-eeprom-update b/rpi-eeprom-update index 7496974..34e180b 100755 --- a/rpi-eeprom-update +++ b/rpi-eeprom-update @@ -71,8 +71,12 @@ VL805_UPDATE_VERSION= # The update actions selected by the version check ACTION_UPDATE_BOOTLOADER=0 ACTION_UPDATE_VL805=0 +CHECKSUMS='' cleanup() { + if [ -f "${CHECKSUMS}" ]; then + rm -f "${CHECKSUMS}" + fi if [ -f "${TMP_EEPROM_IMAGE}" ]; then rm -f "${TMP_EEPROM_IMAGE}" fi @@ -183,15 +187,17 @@ applyRecoveryUpdate() } applyUpdate() { - checksums_file="/var/lib/dpkg/info/rpi-eeprom.md5sums" + package_checksums_file="/var/lib/dpkg/info/rpi-eeprom.md5sums" + CHECKSUMS=$(mktemp) + cat "${package_checksums_file}" | grep -E '\.bin$' > "${CHECKSUMS}" [ "$(id -u)" = "0" ] || die "* Must be run as root - try 'sudo rpi-eeprom-update'" - if [ "${IGNORE_DPKG_CHECKSUMS}" = 0 ] && [ -f "${checksums_file}" ]; then + if [ "${IGNORE_DPKG_CHECKSUMS}" = 0 ] && [ -f "${CHECKSUMS}" ]; then ( cd / - if ! md5sum -c "${checksums_file}" > /dev/null 2>&1; then - md5sum -c "${checksums_file}" + if ! md5sum -c "${CHECKSUMS}" > /dev/null 2>&1; then + md5sum -c "${CHECKSUMS}" die "rpi-eeprom checksums failed - try reinstalling this package" fi ) From a554034c1d441a2b3cc53b517bc9745b06f732a2 Mon Sep 17 00:00:00 2001 From: Tim Gover Date: Thu, 24 Sep 2020 14:57:39 +0100 Subject: [PATCH 03/13] rpi-eeprom-update: Add -l option to resolve the latest bootloader image Add the -l option to return the latest bootloader EEPROM image. This will be used by rpi-eeprom-config to provide a more convenient mechanism for quick config changes. --- rpi-eeprom-update | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/rpi-eeprom-update b/rpi-eeprom-update index 34e180b..1221b92 100755 --- a/rpi-eeprom-update +++ b/rpi-eeprom-update @@ -394,6 +394,8 @@ Options: -h Display help text and exit -i Ignore package checksums - for rpi-eeprom developers. -j Write status information using JSON notation + -l Returns the full path to the latest available EEPROM image file according + to the FIRMWARE_RELEASE_STATUS and FIRMWARE_IMAGE_DIR settings. -m Write status information to the given file when run without -a or -f -r Removes temporary EEPROM update files from the boot partition. -u Install the specified VL805 (USB EEPROM) image file. @@ -694,7 +696,7 @@ MACHINE_OUTPUT="" JSON_OUTPUT="no" IGNORE_DPKG_CHECKSUMS=$LOCAL_MODE -while getopts A:adhif:m:ju:r option; do +while getopts A:adhilf:m:ju:r option; do case "${option}" in A) if [ "${OPTARG}" = "bootloader" ]; then @@ -716,6 +718,11 @@ while getopts A:adhif:m:ju:r option; do ;; j) JSON_OUTPUT="yes" ;; + l) + getBootloaderUpdateVersion + echo "${BOOTLOADER_UPDATE_IMAGE}" + exit 0 + ;; m) MACHINE_OUTPUT="${OPTARG}" ;; h) usage From 1fe54409b84bbdbeabd9f7ac4fbe7931e0eb64d8 Mon Sep 17 00:00:00 2001 From: Tim Gover Date: Thu, 24 Sep 2020 15:17:42 +0100 Subject: [PATCH 04/13] rpi-eeprom-config: Add new option to apply config and update in one operation * Add -a/--apply parameter which provides a one shot image for applying a new configuration to the latest bootloader image and installs it via rpi-eeprom-update. * Print the live configuration if no arguments are specified. * Add short flags instead of requiring verbose names. * Make the errors more human readable instead of Exceptions --- rpi-eeprom-config | 190 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 155 insertions(+), 35 deletions(-) diff --git a/rpi-eeprom-config b/rpi-eeprom-config index af0d63b..7e62e62 100755 --- a/rpi-eeprom-config +++ b/rpi-eeprom-config @@ -1,12 +1,17 @@ #!/usr/bin/env python -# rpi-eeprom-config -# Utility for reading and writing the configuration file in the -# Raspberry Pi 4 bootloader EEPROM image. +""" +rpi-eeprom-config +""" import argparse +import atexit +import os +import subprocess import struct import sys +import tempfile +import time IMAGE_SIZE = 512 * 1024 @@ -22,18 +27,97 @@ MAGIC_MASK = 0xfffff00f FILE_MAGIC = 0x55aaf11f # id for modifiable file, currently only bootconf.txt FILE_HDR_LEN = 20 FILENAME_LEN = 12 +TEMP_DIR = None + +def exit_handler(): + """ + Delete any temporary files. + """ + if TEMP_DIR is not None and os.path.exists(TEMP_DIR): + tmp_image = '%s/pieeprom.upd' % TEMP_DIR + if os.path.exists(tmp_image): + os.remove(tmp_image) + os.rmdir(TEMP_DIR) + +def exit_error(msg): + """ + Trapped a fatal arror, output message to stderr and exit with non-zero + return code. + """ + sys.stderr.write("ERROR: %s\n" % msg) + sys.exit(1) + +def shell_cmd(args): + start = time.time() + arg_str = ' '.join(args) + result = subprocess.Popen(args, stdout=subprocess.PIPE, stdin=subprocess.PIPE) + + while time.time() - start < 5: + if result.poll() is not None: + break + + if result.poll is None: + exit_error("%s timeout" % arg_str) + + if result.returncode != 0: + exit_error("%s failed: %d\n %s\n %s\n" % + (arg_str, result.returncode, result.stdout.read(), result.stderr.read())) + else: + return result.stdout.read() + +def get_latest_eeprom(): + """ + Returns the path of the latest EEPROM image file if it exists. + """ + latest = shell_cmd(['rpi-eeprom-update', '-l']).rstrip() + if not os.path.exists(latest): + exit_error("EEPROM image '%s' not found" % latest) + return latest + +def apply_update(config): + """ + Applies the config file to the latest available EEPROM image and spawns + rpi-eeprom-update to schedule the update at the next reboot. + """ + global TEMP_DIR + latest = get_latest_eeprom() + TEMP_DIR = tempfile.mkdtemp() + tmp_update = "%s/%s" % (TEMP_DIR, 'pieeprom.upd') + image = BootloaderImage(latest, tmp_update) + image.write(config) + + sys.stdout.write("Updating bootloader EEPROM\n image: %s\nconfig: %s\n" % (latest, config)) + args = ['sudo', 'rpi-eeprom-update', '-d', '-f', tmp_update] + resp = shell_cmd(args) + sys.stdout.write(resp) + +def read_current_config(): + """ + Reads the configuration used by the current bootloader. + """ + result = shell_cmd(['vcgencmd', 'bootloader_config']) + if result is None: + exit_error('Failed to read the current bootloader configuration') + return result class BootloaderImage(object): def __init__(self, filename, output): + """ + Instantiates a Bootloader image writer with a source eeprom (filename) + and optionally an output filename. + """ self._filename = filename - self._bytes = bytearray(open(filename, 'rb').read()) + try: + self._bytes = bytearray(open(filename, 'rb').read()) + except IOError as err: + exit_error("Failed to read \'%s\'\n%s\n" % (filename, str(err))) self._out = None if output is not None: self._out = open(output, 'wb') if len(self._bytes) != IMAGE_SIZE: - raise Exception("%s: Expected size %d bytes actual size %d bytes" % - (filename, IMAGE_SIZE, len(self._bytes))) + exit_error("%s: Expected size %d bytes actual size %d bytes" % + (filename, IMAGE_SIZE, len(self._bytes))) def find_config(self): offset = 0 @@ -51,7 +135,7 @@ class BootloaderImage(object): offset += 8 + length # length + type offset = (offset + 7) & ~7 - raise Exception('Bootloader config not found') + raise Exception('EEPROM parse error: Bootloader config not found') def write(self, new_config): hdr_offset, length = self.find_config() @@ -59,12 +143,13 @@ class BootloaderImage(object): new_len = len(new_config_bytes) + FILENAME_LEN + 4 if len(new_config_bytes) > MAX_BOOTCONF_SIZE: raise Exception("Config is too large (%d bytes). The maximum size is %d bytes." - % (len(new_config_bytes), MAX_BOOTCONF_SIZE)) + % (len(new_config_bytes), MAX_BOOTCONF_SIZE)) if hdr_offset + len(new_config_bytes) + FILE_HDR_LEN > IMAGE_SIZE: raise Exception('EEPROM image size exceeded') struct.pack_into('>L', self._bytes, hdr_offset + 4, new_len) - struct.pack_into(("%ds" % len(new_config_bytes)), self._bytes, hdr_offset + 4 + FILE_HDR_LEN, new_config_bytes) + struct.pack_into(("%ds" % len(new_config_bytes)), self._bytes, + hdr_offset + 4 + FILE_HDR_LEN, new_config_bytes) # If the new config is smaller than the old config then set any old # data which is now unused to all ones (erase value) @@ -97,34 +182,69 @@ class BootloaderImage(object): sys.stdout.write(config_bytes) def main(): - parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter, \ - description='Bootloader EEPROM configuration tool for the Raspberry Pi 4. \ -\n\nThere are 3 operating modes: \ -\n\n1. Output the bootloader configuration stored in an EEPROM image file to \ -the screen (STDOUT): specify only the name of an EEPROM image file using the \ -\'eeprom\' option. \ -\n\n2. Output the bootloader configuration stored in an EEPROM image file to a \ -file: specify the EEPROM image file using the \'eeprom\' option, and the output \ -file using the \'--out\' option.\ -\n\n3. Insert a new bootloader configuration into an EEPROM image file: specify \ -the source EEPROM image file using the \'eeprom\' option and the bootloader \ -configuration file using the \'--config\' option. A new file which is a \ -combination of the EEPROM image file, together with the new bootloader \ -configuration file will be created - specify its name using the \'--out\' option. \ -The new bootloader configuration will replace any configuration present in the \ -source EEPROM image.\ -\n\nBootloader EEPROM images are contained in the \'rpi-eeprom-images\' package,\ - which installs them to the /lib/firmware/raspberrypi/bootloader directory.') - parser.add_argument('--config', help='Name of bootloader configuration file') - parser.add_argument('--out', help='Name of output file') - parser.add_argument('eeprom', help='Name of EEPROM file to use as input') + """ + Utility for reading and writing the configuration file in the + Raspberry Pi 4 bootloader EEPROM image. + """ + description = """\ +Bootloader EEPROM configuration tool for the Raspberry Pi 4. +Operating modes: + +1. Output the current bootloader to configuration STDOUT if no arguments are + specified OR the given output file if --out is specified. + + rpi-eeprom-config (--out boot.conf) + +2. Extracts the configuration file from the given \'eeprom\' file and outputs + the result to STDOUT or the output file if --output is specified. + + rpi-eeprom-config pieeprom.bin (--out boot.conf) + +3. Writes a new EEPROM image replacing the configuration file with the contents + of the file specified by --config. + + rpi-eeprom-config --config boot.conf --out newimage.bin pieeprom.bin + + The new image file can be installed via rpi-eeprom-update + rpi-eeprom-update -d -f newimage.bin + +4. Applies a given config file to the latest available EEPROM image and + invokes rpi-eeprom-update to update the bootloader when the system is + rebooted. + + rpi-eeprom-config --apply boot.conf + + The latest available image is determined by querying \'rpi-eeprom-update -l\' + and depends on the rpi-eeprom-update configuration. + +Bootloader EEPROM images are contained in the \'rpi-eeprom-images\' package, +which installs them to the /lib/firmware/raspberrypi/bootloader directory.' +""" + parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter, + description=description) + + parser.add_argument('-a', '--apply', required=False, + help='Updates the bootloader to the given config plus latest available EEPROM release.') + parser.add_argument('-c', '--config', help='Name of bootloader configuration file', required=False) + parser.add_argument('-o', '--out', help='Name of output file', required=False) + parser.add_argument('eeprom', nargs='?', help='Name of EEPROM file to use as input') args = parser.parse_args() - image = BootloaderImage(args.eeprom, args.out) - if args.config is not None: - image.write(args.config) - else: - image.read() + if args.apply is not None: + if not os.path.exists(args.apply): + exit_error("config file '%s' not found" % args.apply) + apply_update(args.apply) + elif args.eeprom is not None: + image = BootloaderImage(args.eeprom, args.out) + if args.config is not None: + if not os.path.exists(args.config): + exit_error("config file '%s' not found" % args.config) + image.write(args.config) + else: + image.read() + elif args.config is None and args.eeprom is None: + sys.stdout.write(read_current_config()) if __name__ == '__main__': + atexit.register(exit_handler) main() From 55ece6bab5f45b238dc643b3c70f340126618fca Mon Sep 17 00:00:00 2001 From: Tim Gover Date: Thu, 24 Sep 2020 20:55:21 +0100 Subject: [PATCH 05/13] rpi-eeprom-config: Add --edit for interactive editor style operation --- rpi-eeprom-config | 61 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 54 insertions(+), 7 deletions(-) diff --git a/rpi-eeprom-config b/rpi-eeprom-config index 7e62e62..8893acc 100755 --- a/rpi-eeprom-config +++ b/rpi-eeprom-config @@ -37,8 +37,16 @@ def exit_handler(): tmp_image = '%s/pieeprom.upd' % TEMP_DIR if os.path.exists(tmp_image): os.remove(tmp_image) + tmp_conf = '%s/boot.conf' % TEMP_DIR + if os.path.exists(tmp_conf): + os.remove(tmp_conf) os.rmdir(TEMP_DIR) +def create_tempdir(): + global TEMP_DIR + if TEMP_DIR is None: + TEMP_DIR = tempfile.mkdtemp() + def exit_error(msg): """ Trapped a fatal arror, output message to stderr and exit with non-zero @@ -79,18 +87,41 @@ def apply_update(config): Applies the config file to the latest available EEPROM image and spawns rpi-eeprom-update to schedule the update at the next reboot. """ - global TEMP_DIR latest = get_latest_eeprom() - TEMP_DIR = tempfile.mkdtemp() + create_tempdir() tmp_update = "%s/%s" % (TEMP_DIR, 'pieeprom.upd') image = BootloaderImage(latest, tmp_update) image.write(config) + config_str = open(config).read() - sys.stdout.write("Updating bootloader EEPROM\n image: %s\nconfig: %s\n" % (latest, config)) + sys.stdout.write("Updating bootloader EEPROM\n image: %s\nconfig: %s\n%s\n" % + (latest, config, config_str)) args = ['sudo', 'rpi-eeprom-update', '-d', '-f', tmp_update] resp = shell_cmd(args) sys.stdout.write(resp) +def edit_config(): + """ + Implements something like visudo for editing EEPROM configs. + """ + if 'EDITOR' not in os.environ: + exit_error('EDITOR environment variable not defined') + create_tempdir() + current_config = read_current_config() + tmp_conf = "%s/boot.conf" % TEMP_DIR + out = open(tmp_conf, 'w') + out.write(current_config) + out.close() + cmd = "\'%s\' \'%s\'" % (os.environ['EDITOR'], tmp_conf) + result = os.system(cmd) + if result != 0: + exit_error("Aborting update because \'%s\' exited with code %d." % (cmd, result)) + + new_config = open(tmp_conf, 'r').read() + if len(new_config.splitlines()) < 2: + exit_error("Aborting update because \'%s\' appears to be empty." % tmp_conf) + apply_update(tmp_conf) + def read_current_config(): """ Reads the configuration used by the current bootloader. @@ -217,8 +248,17 @@ Operating modes: The latest available image is determined by querying \'rpi-eeprom-update -l\' and depends on the rpi-eeprom-update configuration. -Bootloader EEPROM images are contained in the \'rpi-eeprom-images\' package, -which installs them to the /lib/firmware/raspberrypi/bootloader directory.' +5. Launches the default text editor ($EDITOR) to edit the current EEPROM + configuration and then invokes rpi-eeprom-update to apply the updated + configuration to the latest available EEPROM image and schedule an update + at the next reboot. + +N.B. The 'current' EEPROM configuration is read via 'vcgencmd bootloader_config' +and is the configuration that the system was booted with. It does NOT reflect +any pending updates. + +See 'rpi-eeprom-update -h' for more information about the available EEPROM +images. """ parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter, description=description) @@ -226,11 +266,14 @@ which installs them to the /lib/firmware/raspberrypi/bootloader directory.' parser.add_argument('-a', '--apply', required=False, help='Updates the bootloader to the given config plus latest available EEPROM release.') parser.add_argument('-c', '--config', help='Name of bootloader configuration file', required=False) + parser.add_argument('-e', '--edit', action='store_true', default=False, help='Edit the current EEPROM config') parser.add_argument('-o', '--out', help='Name of output file', required=False) parser.add_argument('eeprom', nargs='?', help='Name of EEPROM file to use as input') args = parser.parse_args() - if args.apply is not None: + if args.edit: + edit_config() + elif args.apply is not None: if not os.path.exists(args.apply): exit_error("config file '%s' not found" % args.apply) apply_update(args.apply) @@ -243,7 +286,11 @@ which installs them to the /lib/firmware/raspberrypi/bootloader directory.' else: image.read() elif args.config is None and args.eeprom is None: - sys.stdout.write(read_current_config()) + current_config = read_current_config() + if args.out is not None: + open(args.out, 'w').write(current_config) + else: + sys.stdout.write(current_config) if __name__ == '__main__': atexit.register(exit_handler) From ccd8444501f843b093537433e0757da8a769dbe2 Mon Sep 17 00:00:00 2001 From: Tim Gover Date: Fri, 25 Sep 2020 09:58:07 +0100 Subject: [PATCH 06/13] Implement review comments Update --apply --edit to allow the eeprom image to be specified. Add some error checking around chmod in rpi-eeprom-update TODO: Test this on NFS --- rpi-eeprom-config | 69 ++++++++++++++++++++++++++--------------------- rpi-eeprom-update | 10 ++++--- 2 files changed, 46 insertions(+), 33 deletions(-) diff --git a/rpi-eeprom-config b/rpi-eeprom-config index 8893acc..46a2ea5 100755 --- a/rpi-eeprom-config +++ b/rpi-eeprom-config @@ -34,10 +34,10 @@ def exit_handler(): Delete any temporary files. """ if TEMP_DIR is not None and os.path.exists(TEMP_DIR): - tmp_image = '%s/pieeprom.upd' % TEMP_DIR + tmp_image = os.path.join(TEMP_DIR, 'pieeprom.upd') if os.path.exists(tmp_image): os.remove(tmp_image) - tmp_conf = '%s/boot.conf' % TEMP_DIR + tmp_conf = os.path.join(TEMP_DIR, 'boot.conf') if os.path.exists(tmp_conf): os.remove(tmp_conf) os.rmdir(TEMP_DIR) @@ -56,15 +56,20 @@ def exit_error(msg): sys.exit(1) def shell_cmd(args): + """ + Executes a shell command waits for completion returning STDOUT. If an + error occurs then exit and output the subprocess stdout, stderr messages + for debug. + """ start = time.time() arg_str = ' '.join(args) - result = subprocess.Popen(args, stdout=subprocess.PIPE, stdin=subprocess.PIPE) + result = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) while time.time() - start < 5: if result.poll() is not None: break - if result.poll is None: + if result.poll() is None: exit_error("%s timeout" % arg_str) if result.returncode != 0: @@ -82,25 +87,28 @@ def get_latest_eeprom(): exit_error("EEPROM image '%s' not found" % latest) return latest -def apply_update(config): +def apply_update(config, eeprom=None): """ Applies the config file to the latest available EEPROM image and spawns rpi-eeprom-update to schedule the update at the next reboot. """ - latest = get_latest_eeprom() + if eeprom is not None: + eeprom_image = eeprom + else: + eeprom_image = get_latest_eeprom() create_tempdir() - tmp_update = "%s/%s" % (TEMP_DIR, 'pieeprom.upd') - image = BootloaderImage(latest, tmp_update) + tmp_update = os.path.join(TEMP_DIR, 'pieeprom.upd') + image = BootloaderImage(eeprom_image, tmp_update) image.write(config) config_str = open(config).read() sys.stdout.write("Updating bootloader EEPROM\n image: %s\nconfig: %s\n%s\n" % - (latest, config, config_str)) + (eeprom_image, config, config_str)) args = ['sudo', 'rpi-eeprom-update', '-d', '-f', tmp_update] resp = shell_cmd(args) sys.stdout.write(resp) -def edit_config(): +def edit_config(eeprom=None): """ Implements something like visudo for editing EEPROM configs. """ @@ -108,7 +116,7 @@ def edit_config(): exit_error('EDITOR environment variable not defined') create_tempdir() current_config = read_current_config() - tmp_conf = "%s/boot.conf" % TEMP_DIR + tmp_conf = os.path.join(TEMP_DIR, 'boot.conf') out = open(tmp_conf, 'w') out.write(current_config) out.close() @@ -120,7 +128,7 @@ def edit_config(): new_config = open(tmp_conf, 'r').read() if len(new_config.splitlines()) < 2: exit_error("Aborting update because \'%s\' appears to be empty." % tmp_conf) - apply_update(tmp_conf) + apply_update(tmp_conf, eeprom) def read_current_config(): """ @@ -221,15 +229,15 @@ def main(): Bootloader EEPROM configuration tool for the Raspberry Pi 4. Operating modes: -1. Output the current bootloader to configuration STDOUT if no arguments are +1. Outputs the current bootloader configuration to STDOUT if no arguments are specified OR the given output file if --out is specified. - rpi-eeprom-config (--out boot.conf) + rpi-eeprom-config [--out boot.conf] 2. Extracts the configuration file from the given \'eeprom\' file and outputs the result to STDOUT or the output file if --output is specified. - rpi-eeprom-config pieeprom.bin (--out boot.conf) + rpi-eeprom-config pieeprom.bin [--out boot.conf] 3. Writes a new EEPROM image replacing the configuration file with the contents of the file specified by --config. @@ -239,23 +247,24 @@ Operating modes: The new image file can be installed via rpi-eeprom-update rpi-eeprom-update -d -f newimage.bin -4. Applies a given config file to the latest available EEPROM image and - invokes rpi-eeprom-update to update the bootloader when the system is - rebooted. +4. Applies a given config file an EEPROM image and invokes rpi-eeprom-update + to schedule an update of the bootloader when the system is rebooted. - rpi-eeprom-config --apply boot.conf + rpi-eeprom-config --apply boot.conf [pieeprom.bin] - The latest available image is determined by querying \'rpi-eeprom-update -l\' - and depends on the rpi-eeprom-update configuration. + If the \'eeprom\' argument is not specified then the latest available image + is selected by calling \'rpi-eeprom-update -l\'. -5. Launches the default text editor ($EDITOR) to edit the current EEPROM - configuration and then invokes rpi-eeprom-update to apply the updated - configuration to the latest available EEPROM image and schedule an update - at the next reboot. +5. The `--edit` parameter behaves the same as `--apply` except that instead of + applying a predefined configuration file the default text editor ($EDITOR) is + launched with the contents of the current EEPROM configuration. -N.B. The 'current' EEPROM configuration is read via 'vcgencmd bootloader_config' -and is the configuration that the system was booted with. It does NOT reflect -any pending updates. + N.B. The 'current' EEPROM configuration is read via 'vcgencmd bootloader_config' + and is the configuration that the system was booted with. It does not reflect any + pending updates so invoking this a second time without rebooting would discard + any previous edits. + + rpi-eeprom-config --edit [pieeprom.bin] See 'rpi-eeprom-update -h' for more information about the available EEPROM images. @@ -272,11 +281,11 @@ images. args = parser.parse_args() if args.edit: - edit_config() + edit_config(args.eeprom) elif args.apply is not None: if not os.path.exists(args.apply): exit_error("config file '%s' not found" % args.apply) - apply_update(args.apply) + apply_update(args.apply, args.eeprom) elif args.eeprom is not None: image = BootloaderImage(args.eeprom, args.out) if args.config is not None: diff --git a/rpi-eeprom-update b/rpi-eeprom-update index 1221b92..a26e884 100755 --- a/rpi-eeprom-update +++ b/rpi-eeprom-update @@ -170,16 +170,20 @@ applyRecoveryUpdate() || die "Failed to copy ${TMP_EEPROM_IMAGE} to ${BOOTFS}" # For NFS mounts ensure that the files are readable to the TFTP user - chmod -f go+r "${BOOTFS}/pieeprom.upd" "${BOOTFS}/pieeprom.sig" + chmod -f go+r "${BOOTFS}/pieeprom.upd" "${BOOTFS}/pieeprom.sig" \ + || die "Failed to set permissions on eeprom update files" fi if [ -n "${VL805_UPDATE_IMAGE}" ]; then sha256sum "${VL805_UPDATE_IMAGE}" | awk '{print $1}' > "${BOOTFS}/vl805.sig" \ || die "Failed to create ${BOOTFS}/vl805.sig" - cp -f "${VL805_UPDATE_IMAGE}" "${BOOTFS}/vl805.bin" + + cp -f "${VL805_UPDATE_IMAGE}" "${BOOTFS}/vl805.bin" \ + || die "Failed to copy ${VL805_UPDATE_IMAGE} to ${BOOTFS/vl805.bin}" # For NFS mounts ensure that the files are readable to the TFTP user - chmod -f go+r "${BOOTFS}/vl805.bin" "${BOOTFS}/vl805.sig" + chmod -f go+r "${BOOTFS}/vl805.bin" "${BOOTFS}/vl805.sig" \ + || die "Failed to set permissions on eeprom update files" fi cp -f "${RECOVERY_BIN}" "${BOOTFS}/recovery.bin" \ From ca647a6b9036592f6d9a3fe8145dff0c45bbf601 Mon Sep 17 00:00:00 2001 From: Tim Gover Date: Fri, 25 Sep 2020 17:33:49 +0100 Subject: [PATCH 07/13] Fix doc comments and ignore package checksums --- rpi-eeprom-config | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/rpi-eeprom-config b/rpi-eeprom-config index 46a2ea5..075d605 100755 --- a/rpi-eeprom-config +++ b/rpi-eeprom-config @@ -104,7 +104,11 @@ def apply_update(config, eeprom=None): sys.stdout.write("Updating bootloader EEPROM\n image: %s\nconfig: %s\n%s\n" % (eeprom_image, config, config_str)) - args = ['sudo', 'rpi-eeprom-update', '-d', '-f', tmp_update] + + # Ignore APT package checksums so that this doesn't fail when used + # with EEPROMs with configs delivered outside of APT. + # The checksums are really just a safety check for automatic updates. + args = ['sudo', 'rpi-eeprom-update', '-d', '-i', '-f', tmp_update] resp = shell_cmd(args) sys.stdout.write(resp) @@ -112,15 +116,18 @@ def edit_config(eeprom=None): """ Implements something like visudo for editing EEPROM configs. """ - if 'EDITOR' not in os.environ: - exit_error('EDITOR environment variable not defined') + # Default to nano if $EDITOR is not defined. + editor='nano' + if 'EDITOR' in os.environ: + editor=os.environ['EDITOR'] + create_tempdir() current_config = read_current_config() tmp_conf = os.path.join(TEMP_DIR, 'boot.conf') out = open(tmp_conf, 'w') out.write(current_config) out.close() - cmd = "\'%s\' \'%s\'" % (os.environ['EDITOR'], tmp_conf) + cmd = "\'%s\' \'%s\'" % (editor, tmp_conf) result = os.system(cmd) if result != 0: exit_error("Aborting update because \'%s\' exited with code %d." % (cmd, result)) @@ -247,7 +254,7 @@ Operating modes: The new image file can be installed via rpi-eeprom-update rpi-eeprom-update -d -f newimage.bin -4. Applies a given config file an EEPROM image and invokes rpi-eeprom-update +4. Applies a given config file to an EEPROM image and invokes rpi-eeprom-update to schedule an update of the bootloader when the system is rebooted. rpi-eeprom-config --apply boot.conf [pieeprom.bin] From b6c6b03add8d202a04733d8e20de06b96fa2be11 Mon Sep 17 00:00:00 2001 From: Tim Gover Date: Fri, 25 Sep 2020 19:31:48 +0100 Subject: [PATCH 08/13] rpi-eeprom-update: Add -b flag to output BOOTFS path Add an API for external scripts to safely determine which the directory the EEPROM image update files will be written to. --- rpi-eeprom-update | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/rpi-eeprom-update b/rpi-eeprom-update index a26e884..6c251d5 100755 --- a/rpi-eeprom-update +++ b/rpi-eeprom-update @@ -147,6 +147,7 @@ applyRecoveryUpdate() [ -n "${BOOTLOADER_UPDATE_IMAGE}" ] || [ -n "${VL805_UPDATE_IMAGE}" ] || die "No update images specified" findBootFS + echo "BOOTFS ${BOOTFS}" # A '.sig' file is created so that recovery.bin can check that the # EEPROM image has not been created (e.g. SD card corruption). @@ -388,6 +389,7 @@ retained. Options: -a Automatically install bootloader and USB (VLI) EEPROM updates. -A Specify which type of EEPROM to automatically update (vl805 or bootloader) + -b Outputs the path that pending EEPROM updates will be written to. -d Use the default bootloader config, or if a file is specified using the -f flag use the config in that file. This option only applies when a bootloader EEPROM update is needed; if the bootloader EEPROM is up-to-date @@ -513,9 +515,7 @@ findBootFS() # If BOOTFS is not a directory or doesn't contain any .elf files then # it's probably not the boot partition. [ -d "${BOOTFS}" ] || die "BOOTFS: \"${BOOTFS}\" is not a directory" - if [ "$(find "${BOOTFS}/" -name "*.elf" | wc -l)" -gt 0 ]; then - echo "BOOTFS ${BOOTFS}" - else + if [ "$(find "${BOOTFS}/" -name "*.elf" | wc -l)" = 0 ]; then echo "WARNING: BOOTFS: \"${BOOTFS}\" contains no .elf files. Please check boot directory" fi } @@ -700,7 +700,7 @@ MACHINE_OUTPUT="" JSON_OUTPUT="no" IGNORE_DPKG_CHECKSUMS=$LOCAL_MODE -while getopts A:adhilf:m:ju:r option; do +while getopts A:abdhilf:m:ju:r option; do case "${option}" in A) if [ "${OPTARG}" = "bootloader" ]; then @@ -714,6 +714,11 @@ while getopts A:adhilf:m:ju:r option; do a) AUTO_UPDATE_BOOTLOADER=1 AUTO_UPDATE_VL805=1 ;; + b) + findBootFS + echo ${BOOTFS} + exit 0 + ;; d) OVERWRITE_CONFIG=1 ;; f) BOOTLOADER_UPDATE_IMAGE="${OPTARG}" From 5ab94e88f2e1b44a39a994041f93fe3541728be2 Mon Sep 17 00:00:00 2001 From: Tim Gover Date: Fri, 25 Sep 2020 20:16:11 +0100 Subject: [PATCH 09/13] rpi-eeprom-config: Update --edit to read config from pending updates Use the config from the pending update if there is one so that it's possible to make multiple edits before rebooting. --- rpi-eeprom-config | 63 +++++++++++++++++++++++++++++++---------------- 1 file changed, 42 insertions(+), 21 deletions(-) diff --git a/rpi-eeprom-config b/rpi-eeprom-config index 075d605..7090a13 100755 --- a/rpi-eeprom-config +++ b/rpi-eeprom-config @@ -87,23 +87,24 @@ def get_latest_eeprom(): exit_error("EEPROM image '%s' not found" % latest) return latest -def apply_update(config, eeprom=None): +def apply_update(config, eeprom=None, config_src=None): """ Applies the config file to the latest available EEPROM image and spawns rpi-eeprom-update to schedule the update at the next reboot. """ if eeprom is not None: - eeprom_image = eeprom + eeprom_image = eeprom else: - eeprom_image = get_latest_eeprom() + eeprom_image = get_latest_eeprom() create_tempdir() tmp_update = os.path.join(TEMP_DIR, 'pieeprom.upd') image = BootloaderImage(eeprom_image, tmp_update) image.write(config) config_str = open(config).read() - - sys.stdout.write("Updating bootloader EEPROM\n image: %s\nconfig: %s\n%s\n" % - (eeprom_image, config, config_str)) + if config_src is None: + config_src = '' + sys.stdout.write("Updating bootloader EEPROM\n image: %s\nconfig_src: %s\nconfig: %s\n%s\n" % + (eeprom_image, config_src, config, config_str)) # Ignore APT package checksums so that this doesn't fail when used # with EEPROMs with configs delivered outside of APT. @@ -117,12 +118,31 @@ def edit_config(eeprom=None): Implements something like visudo for editing EEPROM configs. """ # Default to nano if $EDITOR is not defined. - editor='nano' + editor = 'nano' if 'EDITOR' in os.environ: - editor=os.environ['EDITOR'] + editor = os.environ['EDITOR'] + + config_src = '' + if eeprom is None: + # If an EEPROM has not been specified but there is a pending + # update then use that as the current EEPROM image. + bootfs = shell_cmd(['rpi-eeprom-update', '-b']).rstrip() + pending = os.path.join(bootfs, 'pieeprom.upd') + if os.path.exists(pending): + config_src = pending + image = BootloaderImage(pending) + current_config = image.get_config() + else: + config_src = 'vcgencmd bootloader_config' + current_config = read_current_config() + else: + # If an EEPROM image is specified OR there is pending update + # then get the current config from there. + image = BootloaderImage(eeprom) + config_src = eeprom + current_config = image.get_config() create_tempdir() - current_config = read_current_config() tmp_conf = os.path.join(TEMP_DIR, 'boot.conf') out = open(tmp_conf, 'w') out.write(current_config) @@ -135,19 +155,16 @@ def edit_config(eeprom=None): new_config = open(tmp_conf, 'r').read() if len(new_config.splitlines()) < 2: exit_error("Aborting update because \'%s\' appears to be empty." % tmp_conf) - apply_update(tmp_conf, eeprom) + apply_update(tmp_conf, eeprom, config_src) def read_current_config(): """ Reads the configuration used by the current bootloader. """ - result = shell_cmd(['vcgencmd', 'bootloader_config']) - if result is None: - exit_error('Failed to read the current bootloader configuration') - return result + return shell_cmd(['vcgencmd', 'bootloader_config']) class BootloaderImage(object): - def __init__(self, filename, output): + def __init__(self, filename, output=None): """ Instantiates a Bootloader image writer with a source eeprom (filename) and optionally an output filename. @@ -214,10 +231,14 @@ class BootloaderImage(object): else: sys.stdout.write(self._bytes) - def read(self): + def get_config(self): hdr_offset, length = self.find_config() offset = hdr_offset + 4 + FILE_HDR_LEN config_bytes = self._bytes[offset:offset+length-FILENAME_LEN-4] + return config_bytes + + def read(self): + config_bytes = self.get_config() if self._out is not None: self._out.write(config_bytes) self._out.close() @@ -266,10 +287,10 @@ Operating modes: applying a predefined configuration file the default text editor ($EDITOR) is launched with the contents of the current EEPROM configuration. - N.B. The 'current' EEPROM configuration is read via 'vcgencmd bootloader_config' - and is the configuration that the system was booted with. It does not reflect any - pending updates so invoking this a second time without rebooting would discard - any previous edits. + The configuration file will be taken from: + * The `eeprom` file - if specified. + * The current pending update - typically /boot/pieeprom.upd + * The cached bootloader configuration 'vcgencmd bootloader_config' rpi-eeprom-config --edit [pieeprom.bin] @@ -292,7 +313,7 @@ images. elif args.apply is not None: if not os.path.exists(args.apply): exit_error("config file '%s' not found" % args.apply) - apply_update(args.apply, args.eeprom) + apply_update(args.apply, args.eeprom, args.apply) elif args.eeprom is not None: image = BootloaderImage(args.eeprom, args.out) if args.config is not None: From 6ab4179bae91bf0cc944e8ec3adc936cef7935a6 Mon Sep 17 00:00:00 2001 From: Tim Gover Date: Mon, 28 Sep 2020 22:03:53 +0100 Subject: [PATCH 10/13] rpi-eeprom-config: Trap errors when deleting previous update files --- rpi-eeprom-update | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/rpi-eeprom-update b/rpi-eeprom-update index 6c251d5..f693e8d 100755 --- a/rpi-eeprom-update +++ b/rpi-eeprom-update @@ -631,14 +631,16 @@ removePreviousUpdates() if [ "$(id -u)" = "0" ]; then findBootFS - # Remove any stale recovery.bin files or EEPROM images - # N.B. recovery.bin is normally ignored by the ROM if is not a valid - # executable but it's best to not have the file at all. - rm -f "${BOOTFS}/recovery.bin" - rm -f "${BOOTFS}/pieeprom.bin" "${BOOTFS}/pieeprom.upd" "${BOOTFS}/pieeprom.sig" - rm -f "${BOOTFS}/vl805.bin" "${BOOTFS}/vl805.sig" - # Case insensitive for FAT bootfs - find "${BOOTFS}" -maxdepth 1 -type f -follow -iname "recovery.*" -regex '.*\.[0-9][0-9][0-9]$' -exec rm -f {} \; + ( + # Remove any stale recovery.bin files or EEPROM images + # N.B. recovery.bin is normally ignored by the ROM if is not a valid + # executable but it's best to not have the file at all. + rm -f "${BOOTFS}/recovery.bin" + rm -f "${BOOTFS}/pieeprom.bin" "${BOOTFS}/pieeprom.upd" "${BOOTFS}/pieeprom.sig" + rm -f "${BOOTFS}/vl805.bin" "${BOOTFS}/vl805.sig" + # Case insensitive for FAT bootfs + find "${BOOTFS}" -maxdepth 1 -type f -follow -iname "recovery.*" -regex '.*\.[0-9][0-9][0-9]$' -exec rm -f {} \; + ) || die "Failed to remove previous update files" fi } From e63f3dcfc3e7c87a678e1153cc0f0257cf521ed6 Mon Sep 17 00:00:00 2001 From: Tim Gover Date: Wed, 30 Sep 2020 11:39:35 +0100 Subject: [PATCH 11/13] Require 'sudo' for --edit or --apply instead of spawing sudo command --- rpi-eeprom-config | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/rpi-eeprom-config b/rpi-eeprom-config index 7090a13..bf9d0a1 100755 --- a/rpi-eeprom-config +++ b/rpi-eeprom-config @@ -109,7 +109,7 @@ def apply_update(config, eeprom=None, config_src=None): # Ignore APT package checksums so that this doesn't fail when used # with EEPROMs with configs delivered outside of APT. # The checksums are really just a safety check for automatic updates. - args = ['sudo', 'rpi-eeprom-update', '-d', '-i', '-f', tmp_update] + args = ['rpi-eeprom-update', '-d', '-i', '-f', tmp_update] resp = shell_cmd(args) sys.stdout.write(resp) @@ -278,7 +278,10 @@ Operating modes: 4. Applies a given config file to an EEPROM image and invokes rpi-eeprom-update to schedule an update of the bootloader when the system is rebooted. - rpi-eeprom-config --apply boot.conf [pieeprom.bin] + Since this command launches rpi-eeprom-update to schedule the EERPOM update + it must be run as root. + + sudo rpi-eeprom-config --apply boot.conf [pieeprom.bin] If the \'eeprom\' argument is not specified then the latest available image is selected by calling \'rpi-eeprom-update -l\'. @@ -287,12 +290,15 @@ Operating modes: applying a predefined configuration file the default text editor ($EDITOR) is launched with the contents of the current EEPROM configuration. + Since this command launches rpi-eeprom-update to schedule the EERPOM update + it must be run as root. + The configuration file will be taken from: * The `eeprom` file - if specified. * The current pending update - typically /boot/pieeprom.upd * The cached bootloader configuration 'vcgencmd bootloader_config' - rpi-eeprom-config --edit [pieeprom.bin] + sudo rpi-eeprom-config --edit [pieeprom.bin] See 'rpi-eeprom-update -h' for more information about the available EEPROM images. @@ -308,6 +314,10 @@ images. parser.add_argument('eeprom', nargs='?', help='Name of EEPROM file to use as input') args = parser.parse_args() + + if (args.edit or args.apply is not None) and os.getuid() != 0: + exit_error("--edit/--apply must be run as root") + if args.edit: edit_config(args.eeprom) elif args.apply is not None: From d34f62ee3de98caaf7c973e802f9ab99a77fc8ed Mon Sep 17 00:00:00 2001 From: Tim Gover Date: Thu, 1 Oct 2020 20:07:53 +0100 Subject: [PATCH 12/13] rpi-eeprom-update: Move .bin filter for checksums into subshell Move all package checksum related operations to the subshell and check the old rpi-eeprom-images packages if rpi-eeprom contains no .bin files. The rpi-eeprom-config --edit and --apply option passes the -i flag so checksums should have been skipped anyway. --- rpi-eeprom-update | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/rpi-eeprom-update b/rpi-eeprom-update index f693e8d..b48f287 100755 --- a/rpi-eeprom-update +++ b/rpi-eeprom-update @@ -192,20 +192,26 @@ applyRecoveryUpdate() } applyUpdate() { - package_checksums_file="/var/lib/dpkg/info/rpi-eeprom.md5sums" - CHECKSUMS=$(mktemp) - cat "${package_checksums_file}" | grep -E '\.bin$' > "${CHECKSUMS}" - [ "$(id -u)" = "0" ] || die "* Must be run as root - try 'sudo rpi-eeprom-update'" - if [ "${IGNORE_DPKG_CHECKSUMS}" = 0 ] && [ -f "${CHECKSUMS}" ]; then + if [ "${IGNORE_DPKG_CHECKSUMS}" = 0 ]; then ( + package_info_dir="/var/lib/dpkg/info/" + package_checksums_file="${package_info_dir}/rpi-eeprom.md5sums" + + if ! grep -qE '\.bin$' "${package_info_dir}/rpi-eeprom.md5sums"; then + # Try the old rpi-eeprom-images package + package_checksums_file="${package_info_dir}/rpi-eeprom-images.md5sums" + fi + + CHECKSUMS=$(mktemp) + cat "${package_checksums_file}" | grep -E '\.bin$' > "${CHECKSUMS}" cd / if ! md5sum -c "${CHECKSUMS}" > /dev/null 2>&1; then md5sum -c "${CHECKSUMS}" die "rpi-eeprom checksums failed - try reinstalling this package" fi - ) + ) || die "Unable to validate EEPROM image package checksums" fi if [ "${USE_FLASHROM}" = 0 ]; then From 84e461581466463be457a58e20c3292b0e5323f7 Mon Sep 17 00:00:00 2001 From: Tim Gover Date: Sun, 4 Oct 2020 09:55:16 +0100 Subject: [PATCH 13/13] rpi-eeprom-config: Update help for --edit Add -E to sudo in the example to preserve the environment. Remove the redundant escaping of single quotes now that the help is enclosed in triple quotes. --- rpi-eeprom-config | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/rpi-eeprom-config b/rpi-eeprom-config index bf9d0a1..ad417bd 100755 --- a/rpi-eeprom-config +++ b/rpi-eeprom-config @@ -262,7 +262,7 @@ Operating modes: rpi-eeprom-config [--out boot.conf] -2. Extracts the configuration file from the given \'eeprom\' file and outputs +2. Extracts the configuration file from the given 'eeprom' file and outputs the result to STDOUT or the output file if --output is specified. rpi-eeprom-config pieeprom.bin [--out boot.conf] @@ -283,22 +283,25 @@ Operating modes: sudo rpi-eeprom-config --apply boot.conf [pieeprom.bin] - If the \'eeprom\' argument is not specified then the latest available image - is selected by calling \'rpi-eeprom-update -l\'. + If the 'eeprom' argument is not specified then the latest available image + is selected by calling 'rpi-eeprom-update -l'. -5. The `--edit` parameter behaves the same as `--apply` except that instead of - applying a predefined configuration file the default text editor ($EDITOR) is - launched with the contents of the current EEPROM configuration. +5. The '--edit' parameter behaves the same as '--apply' except that instead of + applying a predefined configuration file a text editor is launched with the + contents of the current EEPROM configuration. Since this command launches rpi-eeprom-update to schedule the EERPOM update it must be run as root. The configuration file will be taken from: - * The `eeprom` file - if specified. + * The 'eeprom' file - if specified. * The current pending update - typically /boot/pieeprom.upd * The cached bootloader configuration 'vcgencmd bootloader_config' - sudo rpi-eeprom-config --edit [pieeprom.bin] + sudo -E rpi-eeprom-config --edit [pieeprom.bin] + + The default text editor is nano and may be overriden by setting the 'EDITOR' + environment variable and passing '-E' to 'sudo' to preserve the environment. See 'rpi-eeprom-update -h' for more information about the available EEPROM images.