324 lines
7.4 KiB
Bash
324 lines
7.4 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" -a ! -e "${k_dir}/symvers.xz" ] || {
|
|
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" -o -e "/lib/modules/$k/symvers.xz" ] || {
|
|
debug " \"/lib/modules/$k/symvers.[gx]z\"" \
|
|
"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
|