You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
290 lines
6.4 KiB
290 lines
6.4 KiB
#! /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|list}]" \ |
|
"[--kernel KERNELVER]* [--verbose] [--dry-run]" \ |
|
"[--cleanup intel_ucode caveats_ucode]" \ |
|
"[--skip-common] [--skip-kernel-specific]" >&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 |
|
remove_cleanup=0 |
|
cleanup_intel= |
|
cleanup_caveats= |
|
skip_common=0 |
|
skip_caveats=0 |
|
|
|
while [ 1 -le "$#" ]; do |
|
case "$1" in |
|
-C|--skip-common) |
|
skip_common=1 |
|
;; |
|
-K|--skip-kernel-specific) |
|
skip_caveats=1 |
|
;; |
|
-a|--action) |
|
shift |
|
action="$1" |
|
;; |
|
-k|--kernel) |
|
shift |
|
kernel="$kernel $1" |
|
;; |
|
-v|--verbose) |
|
verbose=1 |
|
verbose_opt="-v" |
|
;; |
|
-n|--dry-run) |
|
dry_run=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|list) |
|
# 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 |
|
|
|
kernel=$(printf "%s" "$kernel" | xargs -n 1 | sort -u) |
|
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 |
|
[ 0 -eq "$skip_common" ] || break |
|
|
|
[ ! -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|list) |
|
debug " Removing old files from ${FW_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 |
|
|
|
[ "xlist" != "x$action" ] || { |
|
echo "$name" |
|
continue |
|
} |
|
|
|
$cmd rm -f $verbose_opt "$name" |
|
done |
|
[ "xlist" = "x$action" ] || { |
|
$cmd rmdir -p $verbose_opt \ |
|
"${FW_DIR}/${INTEL_UCODE_DIR}" 2>/dev/null \ |
|
|| true |
|
} |
|
;; |
|
esac |
|
|
|
# Adding new ones |
|
case "$action" in |
|
add|refresh) |
|
debug " Creating symlinks in ${FW_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 -fs '"$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 |
|
[ 0 -eq "$skip_caveats" ] || break |
|
|
|
debug "Processing data directory \"$i\"..." |
|
|
|
for k in $(echo "$kernel"); do |
|
debug " Processing kernel version \"$k\"" |
|
{ |
|
out=$($check_caveats -k "$k" -c "$i" $verbose_opt) |
|
ret="$?" |
|
} || : |
|
paths=$(printf "%s" "$out" | sed -n 's/^paths //p') |
|
ignore=$(printf "%s" "$out" | sed -n 's/^skip_cfgs //p') |
|
|
|
[ -z "$ignore" ] || { |
|
debug " Configuration is ignored, skipping" |
|
continue |
|
} |
|
|
|
case "$action" in |
|
remove|refresh|list) |
|
[ "xlist" = "x$action" ] || \ |
|
debug " Removing \"$paths\" (part of $action)..." |
|
|
|
for p in $(printf "%s" "$paths"); do |
|
find "$DATA_DIR/$i" -path "$DATA_DIR/$i/$p" \ |
|
-printf "%P\n" |
|
done | while read -r path; do |
|
[ -e "$FW_DIR/$k/readme-$i" ] || { |
|
debug " \"$FW_DIR/$k/readme-$i\"" \ |
|
"is not found, skipping" \ |
|
"\"$paths\" removal" |
|
|
|
break |
|
} |
|
|
|
if [ "xlist" = "x$action" ]; then |
|
echo "$FW_DIR/$k/$path" |
|
else |
|
debug " Removing \"$FW_DIR/$k/$path\"" |
|
$cmd rm -f $verbose_opt "$FW_DIR/$k/$path" |
|
$cmd rmdir -p $verbose_opt \ |
|
"$FW_DIR/$k/$(dirname $path)" 2>/dev/null \ |
|
|| true |
|
fi |
|
done |
|
|
|
if [ -e "$FW_DIR/$k/readme-$i" ]; then |
|
if [ "xlist" = "x$action" ]; then |
|
echo "$FW_DIR/$k/readme-$i" |
|
else |
|
$cmd rm -f $verbose_opt \ |
|
"$FW_DIR/$k/readme-$i" |
|
$cmd rmdir -p $verbose_opt \ |
|
"$FW_DIR/$k" 2>/dev/null || true |
|
fi |
|
fi |
|
;; |
|
esac |
|
|
|
[ 0 -eq "$ret" ] || { |
|
debug " Checking for caveats failed" \ |
|
"(kernel version \"$k\"), skipping" |
|
continue |
|
} |
|
|
|
[ -n "$paths" ] || { |
|
debug " List of paths to add is empty, skipping" |
|
continue |
|
} |
|
|
|
case "$action" in |
|
add|refresh) |
|
debug " Adding $paths (part of $action)..." |
|
|
|
[ -e "/boot/symvers-$k.gz" ] || { |
|
debug " \"/boot/symvers-$k.gz\"" \ |
|
"does not exist, skipping" |
|
continue |
|
} |
|
|
|
for p in $(printf "%s" "$paths"); do |
|
find "$DATA_DIR/$i" -path "$DATA_DIR/$i/$p" \ |
|
-printf "%P\n" |
|
done | while read -r path; do |
|
[ ! -e "$FW_DIR/$k/$path" ] || { |
|
debug " $FW_DIR/$k/$path already" \ |
|
"exists, skipping" |
|
continue |
|
} |
|
|
|
debug " Adding \"$FW_DIR/$k/$path\"" |
|
$cmd mkdir -p $verbose_opt \ |
|
"$(dirname "$FW_DIR/$k/$path")" |
|
$cmd ln -fs $verbose_opt "$DATA_DIR/$i/$path" \ |
|
"$FW_DIR/$k/$path" |
|
done |
|
|
|
if [ -e "$FW_DIR/$k/readme-$i" ]; then |
|
debug " $FW_DIR/$k/readme-$i already" \ |
|
"exists, skipping creation" |
|
else |
|
$cmd cp $verbose_opt "$DATA_DIR/$i/readme" \ |
|
"$FW_DIR/$k/readme-$i" |
|
fi |
|
;; |
|
remove) |
|
esac |
|
done |
|
done
|
|
|