microcode_ctl/SOURCES/update_ucode

324 lines
7.3 KiB
Bash

#! /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
export LC_ALL=C
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; }
# Calls find only if the first argument exists and is a directory.
# Avoids spurious "find: '...' No such file or directory" for the directories
# that may not exist.
find_d() { [ \! -d "$1" ] || find "$@"; }
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_d /lib/modules/ -name '[2-9].*' -print)
for k_dir in $kvers; do
k="${k_dir#/lib/modules/}"
[ ! -e "${k_dir}/symvers.gz" ] || {
debug " Adding $k (from /lib/modules)"
kernel="$kernel $k"
}
done
kvers=$(find_d /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_d "${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" ] || {
# Removing possible dangling symlinks
find_d "${FW_DIR}/${INTEL_UCODE_DIR}" \
-maxdepth 1 -mindepth 1 \
-type l -printf '%p\n' \
| while read -r fname; do
[ -e "$fname" ] || {
debug " Removing danging symlink \"$fname\""
$cmd rm -f $verbose_opt "$fname"
}
done
$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_d "$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 "/lib/modules/$k/symvers.gz" ] || {
debug " \"/lib/modules/$k/symvers.gz\"" \
"does not exist, skipping"
continue
}
for p in $(printf "%s" "$paths"); do
find_d "$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
# Removing possible dangling symlinks in kernel-specific directories
debug "Checking for dangling symlinks..."
for k in $(echo "$kernel"); do
debug " Processing kernel version \"$k\""
find_d "${FW_DIR}/${k}" \
-mindepth 1 -type l -printf '%p\n' \
| while read -r fname; do
[ -e "$fname" ] || {
debug " Removing danging symlink \"$fname\""
$cmd rm -f $verbose_opt "$fname"
}
done
done