#! /bin/bash -eu # Maintain kernel-version-specific symlinks in /lib/firmware based on # configuration present in /usr/share/microcode_ctl/ucode_with_caveats. # # SPDX-License-Identifier: CC0-1.0 usage() { echo "Usage: update_ucode [--action {add|remove|refresh}]" \ "[--kernel KERNELVER]* [--verbose] [--dry-run]" \ "[--trigger-dracut] [--cleanup intel_ucode caveats_ucode]" >&2 } debug() { [ 0 = "$verbose" ] || echo "$*" >&2; } MC_DIR=/usr/share/microcode_ctl INTEL_UCODE_DIR=intel-ucode DATA_DIR=/usr/share/microcode_ctl/ucode_with_caveats FW_DIR=/lib/firmware check_caveats=/usr/libexec/microcode_ctl/check_caveats action=refresh kernel= verbose=0 verbose_opt= dry_run=0 trigger_dracut=0 remove_cleanup=0 cleanup_intel= cleanup_caveats= while [ 1 -le "$#" ]; do case "$1" in -a|--action) shift action="$1" ;; -k|--kernel) shift kernel="$kernel $1" ;; -v|--verbose) verbose=1 verbose_opt="-v" ;; -n|--dry-run) dry_run=1 ;; -d|--trigger-dracut) trigger_dracut=1 ;; -c|--cleanup) remove_cleanup=1 shift cleanup_intel="$1" shift cleanup_caveats="$1" ;; *) echo "Unknown argument \"$1\"" >&2 usage exit 1 esac shift done cmd= [ 0 -eq "$dry_run" ] || cmd=echo case "$action" in add|remove|refresh) # Scan all directories in FW_DIR and all existing kernels if [ -z "$kernel" ]; then debug "No kernel versions provided, scanning..." kvers=$(find /lib/modules/ -name '[2-9].*' -print) for k_dir in $kvers; do k="${k_dir#/lib/modules/}" [ ! -e "/boot/symvers-$k.gz" ] || { debug " Adding $k (from /lib/modules)" kernel="$kernel $k" } done kvers=$(find /lib/firmware/ -name '[2-9].*' -print) for k_dir in $kvers; do k="${k_dir#/lib/firmware/}" [ ! -d "$k_dir" ] || { debug " Adding $k (from /lib/firmware)" kernel="$kernel $k" } done fi ;; *) echo "Unknown action \"$action\"" >&2 usage exit 1 ;; esac # Generic part: managing intel ucode debug "Running action \"$action\" on common Intel microcode directory" while :; do [ ! -e "/etc/microcode_ctl/intel-ucode-disallow" ] || { debug " Skipping \"$i\":" \ "\"/etc/microcode_ctl/intel-ucode-disallow\"" \ "present" break } [ ! -e "$FW_DIR/intel-ucode-disallow" ] || { debug " Found \"$FW_DIR/intel-ucode-disallow\"," \ "skipping" break } # Removing old files case "$action" in refresh|remove) debug " Removing old files from ${MC_DIR}/${INTEL_UCODE_DIR}" if [ 0 = "$remove_cleanup" ]; then find "${MC_DIR}/${INTEL_UCODE_DIR}" \ -maxdepth 1 -mindepth 1 \ -type f -printf '%f\n' else cat "$cleanup_intel" fi | while read -r fname; do name="${FW_DIR}/${INTEL_UCODE_DIR}/${fname}" # Needed in case we downgrade to a version where # no symlinks in /lib/firmware were used if [ 1 = "$remove_cleanup" ]; then [ -L "$name" ] || continue fi $cmd rm -f $verbose_opt "$name" done $cmd rmdir -p $verbose_opt "${FW_DIR}/${INTEL_UCODE_DIR}" 2>/dev/null || true ;; esac # Adding new ones case "$action" in add|refresh) if grep -q '^flags[[:space:]]*:.*hypervisor' /proc/cpuinfo; then debug " A virtualised environment has been detected" \ "(hypervisor flag is present in /proc/cpuinfo)," \ "skipping" break fi debug " Creating symlinks in ${MC_DIR}/${INTEL_UCODE_DIR}" $cmd mkdir -p $verbose_opt "${FW_DIR}/${INTEL_UCODE_DIR}" $cmd find "${MC_DIR}/${INTEL_UCODE_DIR}" -maxdepth 1 -mindepth 1 \ -type f -exec bash -c 'ln -s '"$verbose_opt"' '\''{}'\'' \ "'"${FW_DIR}/${INTEL_UCODE_DIR}/"'$(basename '\''{}'\'')"' \; ;; esac break done debug "Running action \"$action\" on kernels $kernel" if [ 0 = "$remove_cleanup" ]; then ls "$DATA_DIR" else cat "$cleanup_caveats" fi | while read -r i; do debug "Processing data directory \"$i\"..." [ -e "$DATA_DIR/$i/readme" ] || { debug " Skipping \"$i\": no readme" continue } [ -e "$DATA_DIR/$i/config" ] || { debug " Skipping \"$i\": no config" continue } [ ! -e "/etc/microcode_ctl/ucode_with_caveats/disallow-$i" ] || { debug " Skipping \"$i\":" \ "\"/etc/microcode_ctl/ucode_with_caveats/disallow-$i\"" \ "present" continue } for k in $(echo "$kernel"); do debug " Processing kernel version \"$k\"" $check_caveats -k "$k" -c "$i" $verbose_opt > /dev/null || { debug " Checking for caveats failed" \ "(kernel version \"$k\"), skipping" continue } path=$($check_caveats -k "$k" -c "$i") case "$action" in remove|refresh) debug " Removing $path (part of $action)..." if [ -e "$FW_DIR/$k/readme-$i" ]; then debug " Removing \"$FW_DIR/$k/$path\"" $cmd rm -f $verbose_opt "$FW_DIR/$k/$path" $cmd rm -f $verbose_opt "$FW_DIR/$k/readme-$i" $cmd rmdir -p $verbose_opt "$(dirname "$FW_DIR/$k/$path")" 2>/dev/null || true [ 0 -eq "$trigger_dracut" ] || { debug " Triggering dracut for kernel \"$k\"" $cmd dracut -f $verbose_opt --kver "$k" } else debug " \"$FW_DIR/$k/readme-$i\" is not found," \ "skipping \"$FW_DIR/$k/$path\" removal" fi ;; esac case "$action" in add|refresh) debug " Adding $path (part of $action)..." [ -e "/boot/symvers-$k.gz" ] || { debug " \"/boot/symvers-$k.gz\"" \ "does not exist, skipping" continue } if grep -q '^flags[[:space:]]*:.*hypervisor' /proc/cpuinfo; then debug " A virtualised environment has been detected" \ "(hypervisor flag is present in /proc/cpuinfo)," \ "skipping" break fi [ ! -e "$FW_DIR/$k/disallow-$i" ] || { debug " Found \"$FW_DIR/$k/disallow-$i\"," \ "skipping" continue } [ ! -e "$FW_DIR/$k/$path" ] || { debug " \"$FW_DIR/$k/$path\" already exists," \ "skipping" continue } debug " Adding \"$FW_DIR/$k/$path\"" $cmd mkdir -p "$(dirname "$FW_DIR/$k/$path")" $cmd ln -s "$DATA_DIR/$i/$path" "$FW_DIR/$k/$path" $cmd cp "$DATA_DIR/$i/readme" "$FW_DIR/$k/readme-$i" [ 0 -eq "$trigger_dracut" ] || { debug " Triggering dracut for kernel \"$k\"" $cmd dracut -f $verbose_opt --kver "$k" } ;; remove) esac done done