2023-02-08 21:01:48 +00:00
#!/bin/bash
2017-04-12 10:48:38 +00:00
# Shell functions for most initialization scripts
2023-03-07 15:10:32 +00:00
. /etc/ash_functions
2023-02-18 17:58:43 +00:00
2023-03-07 19:00:57 +00:00
# Print <hidden> or <empty> depending on whether $1 is empty. Useful to mask an
# optional password parameter.
mask_param() {
if [ -z "$1" ]; then
echo "<empty>"
else
echo "<hidden>"
fi
}
# Trace a command with DEBUG, then execute it.
# A password parameter can be masked by passing --mask-position N before the
# command to execute, the debug trace will just indicate whether the password
# was empty or nonempty (which is important when use of a password is optional).
# N=0 is the name of the command to be executed, N=1 is its first parameter,
# etc.
DO_WITH_DEBUG() {
if [ "$1" == "--mask-position" ]; then
mask_position="$2"
shift
shift
DEBUG_ARGS=("$@")
DEBUG_ARGS[$mask_position]="$(mask_param "${DEBUG_ARGS[$mask_position]}")"
DEBUG "${DEBUG_ARGS[@]}"
else
DEBUG "$@"
fi
"$@"
}
2017-04-12 10:48:38 +00:00
pcrs() {
tpm2-tools: Change sense of CONFIG_TPM to mean any TPM, not just TPM1.
Most logic throughout Heads doesn't need to know TPM1 versus TPM2 (and
shouldn't, the differences should be localized). Some checks were
incorrect and are fixed by this change. Most checks are now unchanged
relative to master.
There are not that many places outside of tpmr that need to
differentiate TPM1 and TPM2. Some of those are duplicate code that
should be consolidated (seal-hotpkey, unseal-totp, unseal-hotp), and
some more are probably good candidates for abstracting in tpmr so the
business logic doesn't have to know TPM1 vs. TPM2.
Previously, CONFIG_TPM could be variously 'y', 'n', or empty. Now it
is always 'y' or 'n', and 'y' means "any TPM". Board configs are
unchanged, setting CONFIG_TPM2_TOOLS=y implies CONFIG_TPM=y so this
doesn't have to be duplicated and can't be mistakenly mismatched.
There were a few checks for CONFIG_TPM = n that only coincidentally
worked for TPM2 because CONFIG_TPM was empty (not 'n'). This test is
now OK, but the checks were also cleaned up to '!= "y"' for robustness.
Signed-off-by: Jonathon Hall <jonathon.hall@puri.sm>
2023-02-22 21:30:07 +00:00
if [ "$CONFIG_TPM2_TOOLS" = "y" ]; then
2022-08-25 18:43:31 +00:00
tpm2 pcrread sha256
tpm2-tools: Change sense of CONFIG_TPM to mean any TPM, not just TPM1.
Most logic throughout Heads doesn't need to know TPM1 versus TPM2 (and
shouldn't, the differences should be localized). Some checks were
incorrect and are fixed by this change. Most checks are now unchanged
relative to master.
There are not that many places outside of tpmr that need to
differentiate TPM1 and TPM2. Some of those are duplicate code that
should be consolidated (seal-hotpkey, unseal-totp, unseal-hotp), and
some more are probably good candidates for abstracting in tpmr so the
business logic doesn't have to know TPM1 vs. TPM2.
Previously, CONFIG_TPM could be variously 'y', 'n', or empty. Now it
is always 'y' or 'n', and 'y' means "any TPM". Board configs are
unchanged, setting CONFIG_TPM2_TOOLS=y implies CONFIG_TPM=y so this
doesn't have to be duplicated and can't be mistakenly mismatched.
There were a few checks for CONFIG_TPM = n that only coincidentally
worked for TPM2 because CONFIG_TPM was empty (not 'n'). This test is
now OK, but the checks were also cleaned up to '!= "y"' for robustness.
Signed-off-by: Jonathon Hall <jonathon.hall@puri.sm>
2023-02-22 21:30:07 +00:00
elif [ "$CONFIG_TPM" = "y" ]; then
head -8 /sys/class/tpm/tpm0/pcrs
2022-08-25 18:43:31 +00:00
fi
2017-04-12 10:48:38 +00:00
}
2017-04-29 17:40:34 +00:00
2023-08-31 16:07:39 +00:00
confirm_totp() {
2023-02-20 16:01:17 +00:00
TRACE "Under /etc/functions:confirm_totp"
2017-07-18 17:44:02 +00:00
prompt="$1"
2017-04-29 17:40:34 +00:00
last_half=X
2017-07-18 17:44:02 +00:00
unset totp_confirm
2017-04-29 17:40:34 +00:00
while true; do
# update the TOTP code every thirty seconds
2023-08-31 16:07:39 +00:00
date=$(date "+%Y-%m-%d %H:%M:%S")
seconds=$(date "+%s")
half=$(expr \( $seconds % 60 \) / 30)
tpm2-tools: Change sense of CONFIG_TPM to mean any TPM, not just TPM1.
Most logic throughout Heads doesn't need to know TPM1 versus TPM2 (and
shouldn't, the differences should be localized). Some checks were
incorrect and are fixed by this change. Most checks are now unchanged
relative to master.
There are not that many places outside of tpmr that need to
differentiate TPM1 and TPM2. Some of those are duplicate code that
should be consolidated (seal-hotpkey, unseal-totp, unseal-hotp), and
some more are probably good candidates for abstracting in tpmr so the
business logic doesn't have to know TPM1 vs. TPM2.
Previously, CONFIG_TPM could be variously 'y', 'n', or empty. Now it
is always 'y' or 'n', and 'y' means "any TPM". Board configs are
unchanged, setting CONFIG_TPM2_TOOLS=y implies CONFIG_TPM=y so this
doesn't have to be duplicated and can't be mistakenly mismatched.
There were a few checks for CONFIG_TPM = n that only coincidentally
worked for TPM2 because CONFIG_TPM was empty (not 'n'). This test is
now OK, but the checks were also cleaned up to '!= "y"' for robustness.
Signed-off-by: Jonathon Hall <jonathon.hall@puri.sm>
2023-02-22 21:30:07 +00:00
if [ "$CONFIG_TPM" != "y" ]; then
2017-07-18 17:44:02 +00:00
TOTP="NO TPM"
elif [ "$half" != "$last_half" ]; then
2023-08-31 16:07:39 +00:00
last_half=$half
TOTP=$(unseal-totp) ||
recovery "TOTP code generation failed"
2017-04-29 17:40:34 +00:00
fi
echo -n "$date $TOTP: "
# read the first character, non-blocking
read \
-t 1 \
-n 1 \
-s \
2017-07-18 17:44:02 +00:00
-p "$prompt" \
2023-08-31 16:07:39 +00:00
totp_confirm &&
break
2017-04-29 17:40:34 +00:00
# nothing typed, redraw the line
echo -ne '\r'
done
# clean up with a newline
echo
}
2023-08-22 18:34:29 +00:00
2023-08-31 16:07:39 +00:00
reseal_tpm_disk_decryption_key() {
2023-08-22 18:34:29 +00:00
TRACE "Under /etc/functions:reseal_tpm_disk_decryption_key"
2023-08-31 16:07:39 +00:00
#For robustness, exit early if TPM Disk Unlock Key is prohibited in board configs
if [ "$CONFIG_TPM_DISK_UNLOCK_KEY" != "y" ]; then
DEBUG "TPM Disk Unlock Key is prohibited in board configs"
return
else
DEBUG "TPM Disk Unlock Key is allowed in board configs. Continuing"
fi
if ! grep -q /boot /proc/mounts; then
mount -o ro /boot ||
recovery "Unable to mount /boot"
2023-08-22 18:34:29 +00:00
fi
if [ -s /boot/kexec_key_devices.txt ] || [ -s /boot/kexec_key_lvm.txt ]; then
2023-08-31 16:07:39 +00:00
warn "A TPM Disk Unlock Key previously sealed is now invalid since firmware measurements cannot be unsealed"
echo "Renewing LUKS Disk Unlock Key to be unsealed by TPM Disk Unlock Key passphrase"
2023-08-22 18:34:29 +00:00
while ! kexec-seal-key /boot; do
2023-08-31 16:07:39 +00:00
warn "Recovery Disk Encryption key passphrase invalid. Try again!"
2023-08-22 18:34:29 +00:00
done
2023-08-31 16:07:39 +00:00
warn "LUKS header hash changed under /boot/kexec_luks_hdr_hash.txt"
echo "Updating checksums and signing all files under /boot/kexec.sig"
2023-08-22 18:34:29 +00:00
while ! update_checksums; do
2023-08-31 16:07:39 +00:00
warn "Checksums were not signed. Bad GPG PIN provided?"
warn "Please update checksums and provide a valid GPG PIN"
2023-08-22 18:34:29 +00:00
done
warn "Rebooting in 3 seconds to enable booting default boot option"
sleep 3
reboot
else
DEBUG "No TPM disk decryption key to reseal"
fi
}
2017-07-04 23:49:14 +00:00
2022-11-09 19:02:03 +00:00
# Enable USB storage (if not already enabled), and wait for storage devices to
# be detected. If USB storage was already enabled, no wait occurs, this would
# have happened already when USB storage was enabled.
2023-08-31 16:07:39 +00:00
enable_usb_storage() {
2022-11-09 19:02:03 +00:00
if ! lsmod | grep -q usb_storage; then
timeout=0
echo "Scanning for USB storage devices..."
2023-08-31 16:07:39 +00:00
insmod /lib/modules/usb-storage.ko >/dev/null 2>&1 ||
die "usb_storage: module load failed"
while [[ $(list_usb_storage | wc -l) -eq 0 ]]; do
2022-11-09 19:02:03 +00:00
[[ $timeout -ge 8 ]] && break
sleep 1
2023-08-31 16:07:39 +00:00
timeout=$(($timeout + 1))
2022-11-09 19:02:03 +00:00
done
fi
}
2023-08-31 16:07:39 +00:00
list_usb_storage() {
2023-02-20 16:01:17 +00:00
TRACE "Under /etc/functions:list_usb_storage"
2023-07-19 22:48:03 +00:00
# List all USB storage devices, including partitions unless we received argument stating we want drives only
# The output is a list of device names, one per line.
if [ "$1" = "disks" ]; then
DEBUG "Listing USB storage devices (disks only) since list_usb_storage was called with 'disks' argument"
else
DEBUG "Listing USB storage devices (including partitions)"
fi
2022-11-02 15:03:30 +00:00
stat -c %N /sys/block/sd* 2>/dev/null | grep usb |
cut -f1 -d ' ' |
sed "s/[']//g" |
while read b; do
# Ignore devices of size 0, such as empty SD card
# readers on laptops attached via USB.
if [ "$(cat "$b/size")" -gt 0 ]; then
2023-07-19 22:48:03 +00:00
DEBUG "USB storage device of size greater then 0: $b"
2022-11-02 15:03:30 +00:00
echo "$b"
fi
done |
sed "s|/sys/block|/dev|" |
while read b; do
# If the device has a partition table, ignore it and
# include the partitions instead - even if the kernel
# hasn't detected the partitions yet. Such a device is
# never usable directly, and this allows the "wait for
# disks" loop in mount-usb to correctly wait for the
# partitions.
2023-01-17 20:24:11 +00:00
# This check: [ $(fdisk -l "$b" | wc -l) -eq 5 ]
# covers the case of a device without partition table but
# formatted as fat32, which contains a sortof partition table.
# this causes fdisk to not print the invalid partition table
# message and instead it'll print an empty table with header.
# In both cases the output is 5 lines: 3 about device info,
# 1 empty line and the 5th will be the table header or the
# unvalid message.
DISK_DATA=$(fdisk -l "$b")
if echo "$DISK_DATA" | grep -q "doesn't contain a valid partition table" || [ $(echo "$DISK_DATA" | wc -l) -eq 5 ]; then
2022-11-02 15:03:30 +00:00
# No partition table, include this device
2023-07-19 22:48:03 +00:00
DEBUG "USB storage device without partition table: $b"
echo "$b"
#Bypass the check for partitions if we want only disks
elif [ "$1" = "disks" ]; then
# disks only were requested, so we don't list partitions
DEBUG "USB storage device with partition table: $b"
DEBUG "We asked for disks only, so we don't want to list partitions"
2022-11-02 15:03:30 +00:00
echo "$b"
else
# Has a partition table, include partitions
2023-07-19 22:48:03 +00:00
DEBUG "USB storage device with partition table: $b"
2022-11-02 15:03:30 +00:00
ls -1 "$b"* | awk 'NR!=1 {print $0}'
fi
done
}
2023-08-31 16:07:39 +00:00
confirm_gpg_card() {
2023-02-20 16:01:17 +00:00
TRACE "Under /etc/functions:confirm_gpg_card"
2017-12-06 08:04:27 +00:00
read \
-n 1 \
-p "Please confirm that your GPG card is inserted [Y/n]: " \
card_confirm
echo
if [ "$card_confirm" != "y" \
-a "$card_confirm" != "Y" \
-a -n "$card_confirm" ] \
2023-08-31 16:07:39 +00:00
; then
2017-12-06 08:04:27 +00:00
die "gpg card not confirmed"
fi
# setup the USB so we can reach the GPG card
enable_usb
2017-07-04 23:49:14 +00:00
2019-07-09 21:46:14 +00:00
echo -e "\nVerifying presence of GPG card...\n"
# ensure we don't exit without retrying
errexit=$(set -o | grep errexit | awk '{print $2}')
set +e
2023-08-31 16:07:39 +00:00
gpg --card-status >/dev/null
2019-07-09 21:46:14 +00:00
if [ $? -ne 0 ]; then
2023-08-31 16:07:39 +00:00
# prompt for reinsertion and try a second time
read -n1 -r -p \
"Can't access GPG key; remove and reinsert, then press Enter to retry. " \
ignored
# restore prev errexit state
if [ "$errexit" = "on" ]; then
set -e
fi
# retry card status
gpg --card-status >/dev/null ||
die "gpg card read failed"
2019-07-09 21:46:14 +00:00
fi
# restore prev errexit state
if [ "$errexit" = "on" ]; then
2023-08-31 16:07:39 +00:00
set -e
2019-07-09 21:46:14 +00:00
fi
2017-07-04 23:49:14 +00:00
}
2017-07-08 20:59:37 +00:00
2023-03-10 20:36:24 +00:00
# Prompt for an owner password if it is not already set in tpm_password. Sets
# tpm_password. Tools should optionally accept a TPM password on the command
# line, since some flows need it multiple times and only one prompt is ideal.
prompt_tpm_password() {
if [ -n "$tpm_password" ]; then
2023-08-31 16:07:39 +00:00
return 0
2023-03-10 20:36:24 +00:00
fi
read -s -p "TPM Owner password: " tpm_password
echo # new line after password prompt
}
2017-07-08 20:59:37 +00:00
2023-03-13 17:33:30 +00:00
# Prompt for a new owner password when resetting the TPM. Returned in
# key_password. The password must be 1-32 characters and must be entered twice,
# the script will loop until this is met.
prompt_new_owner_password() {
local key_password2
key_password=1
key_password2=2
while [ "$key_password" != "$key_password2" ] || [ "${#key_password}" -gt 32 ] || [ -z "$key_password" ]; do
read -s -p "New TPM owner passphrase (2 words suggested, 1-32 characters max): " key_password
echo
read -s -p "Repeat chosen TPM owner passphrase: " key_password2
echo
if [ "$key_password" != "$key_password2" ]; then
echo "Passphrases entered do not match. Try again!"
echo
fi
done
}
2023-08-31 16:07:39 +00:00
check_tpm_counter() {
TRACE "Under /etc/functions:check_tpm_counter"
LABEL=${2:-3135106223}
tpm_password="$3"
2017-07-08 20:59:37 +00:00
# if the /boot.hashes file already exists, read the TPM counter ID
# from it.
if [ -r "$1" ]; then
2023-08-31 16:07:39 +00:00
TPM_COUNTER=$(grep counter- "$1" | cut -d- -f2)
2017-07-08 20:59:37 +00:00
else
2018-06-19 19:27:27 +00:00
warn "$1 does not exist; creating new TPM counter"
2023-03-10 20:36:24 +00:00
prompt_tpm_password
2022-08-25 18:43:31 +00:00
tpmr counter_create \
2017-07-08 20:59:37 +00:00
-pwdo "$tpm_password" \
-pwdc '' \
2023-08-31 16:07:39 +00:00
-la $LABEL |
tee /tmp/counter ||
die "Unable to create TPM counter"
TPM_COUNTER=$(cut -d: -f1 </tmp/counter)
2017-07-08 20:59:37 +00:00
fi
if [ -z "$TPM_COUNTER" ]; then
die "$1: TPM Counter not found?"
fi
}
2023-08-31 16:07:39 +00:00
read_tpm_counter() {
2023-02-20 16:01:17 +00:00
TRACE "Under /etc/functions:read_tpm_counter"
2023-08-31 16:07:39 +00:00
tpmr counter_read -ix "$1" | tee "/tmp/counter-$1" ||
die "Counter read failed"
2017-07-08 20:59:37 +00:00
}
2023-08-31 16:07:39 +00:00
increment_tpm_counter() {
2023-02-20 16:01:17 +00:00
TRACE "Under /etc/functions:increment_tpm_counter"
2023-08-31 16:07:39 +00:00
tpmr counter_increment -ix "$1" -pwdc '' |
tee /tmp/counter-$1 ||
die "Counter increment failed"
2017-07-08 20:59:37 +00:00
}
check_config() {
2023-02-20 16:01:17 +00:00
TRACE "Under /etc/functions:check_config"
2017-07-08 20:59:37 +00:00
if [ ! -d /tmp/kexec ]; then
2023-08-31 16:07:39 +00:00
mkdir /tmp/kexec ||
die 'Failed to make kexec tmp dir'
2017-07-08 20:59:37 +00:00
else
2023-08-31 16:07:39 +00:00
rm -rf /tmp/kexec/* ||
die 'Failed to empty kexec tmp dir'
2017-07-08 20:59:37 +00:00
fi
2023-06-21 18:36:28 +00:00
if [ ! -r $1/kexec.sig -a "$CONFIG_BASIC" != "y" ]; then
2017-07-08 20:59:37 +00:00
return
fi
2023-08-31 16:07:39 +00:00
if [ $(find $1/kexec*.txt | wc -l) -eq 0 ]; then
2017-07-08 20:59:37 +00:00
return
fi
2018-03-14 17:24:14 +00:00
if [ "$2" != "force" ]; then
2023-08-31 16:07:39 +00:00
if ! sha256sum $(find $1/kexec*.txt) | gpgv $1/kexec.sig -; then
2018-03-14 17:24:14 +00:00
die 'Invalid signature on kexec boot params'
fi
2017-07-08 20:59:37 +00:00
fi
echo "+++ Found verified kexec boot params"
2023-08-31 16:07:39 +00:00
cp $1/kexec*.txt /tmp/kexec ||
die "Failed to copy kexec boot params to tmp"
2017-07-08 20:59:37 +00:00
}
2018-04-22 00:21:37 +00:00
2022-11-03 18:13:16 +00:00
# Replace a file in a ROM (add it if the file does not exist)
replace_rom_file() {
ROM="$1"
ROM_FILE="$2"
NEW_FILE="$3"
2023-08-31 16:07:39 +00:00
if (cbfs.sh -o "$ROM" -l | grep -q "$ROM_FILE"); then
2022-11-03 18:13:16 +00:00
cbfs.sh -o "$ROM" -d "$ROM_FILE"
fi
cbfs.sh -o "$ROM" -a "$ROM_FILE" -f "$NEW_FILE"
}
2018-12-06 23:24:28 +00:00
replace_config() {
2023-02-20 16:01:17 +00:00
TRACE "Under /etc/functions:replace_config"
2018-12-06 23:41:20 +00:00
CONFIG_FILE=$1
CONFIG_OPTION=$2
NEW_SETTING=$3
2018-12-06 23:24:28 +00:00
2018-12-06 23:41:20 +00:00
touch $CONFIG_FILE
2023-08-31 16:07:39 +00:00
# first pull out the existing option from the global config and place in a tmp file
awk "gsub(\"^export ${CONFIG_OPTION}=.*\",\"export ${CONFIG_OPTION}=\\\"${NEW_SETTING}\\\"\")" /tmp/config >${CONFIG_FILE}.tmp
awk "gsub(\"^${CONFIG_OPTION}=.*\",\"${CONFIG_OPTION}=\\\"${NEW_SETTING}\\\"\")" /tmp/config >>${CONFIG_FILE}.tmp
2018-12-06 23:24:28 +00:00
2023-08-31 16:07:39 +00:00
# then copy any remaining settings from the existing config file, minus the option you changed
grep -v "^export ${CONFIG_OPTION}=" ${CONFIG_FILE} | grep -v "^${CONFIG_OPTION}=" >>${CONFIG_FILE}.tmp || true
sort ${CONFIG_FILE}.tmp | uniq >${CONFIG_FILE}
2019-02-22 01:17:16 +00:00
rm -f ${CONFIG_FILE}.tmp
2018-12-06 23:24:28 +00:00
}
2019-07-05 22:04:00 +00:00
2023-03-25 14:29:12 +00:00
# Set a config variable in a specific file to a given value - replace it if it
# exists, or add it. If added, the variable will be exported.
2022-11-03 18:13:16 +00:00
set_config() {
CONFIG_FILE="$1"
CONFIG_OPTION="$2"
NEW_SETTING="$3"
2023-08-31 16:07:39 +00:00
2022-11-03 18:13:16 +00:00
if grep -q "$CONFIG_OPTION" "$CONFIG_FILE"; then
replace_config "$CONFIG_FILE" "$CONFIG_OPTION" "$NEW_SETTING"
else
2023-06-30 13:50:10 +00:00
echo "export $CONFIG_OPTION=\"$NEW_SETTING\"" >>"$CONFIG_FILE"
2022-11-03 18:13:16 +00:00
fi
}
2023-03-25 14:29:12 +00:00
# Set a value in config.user, re-combine configs, and update configs in the
# environment.
set_user_config() {
CONFIG_OPTION="$1"
NEW_SETTING="$2"
set_config /etc/config.user "$CONFIG_OPTION" "$NEW_SETTING"
combine_configs
. /tmp/config
}
2023-06-30 13:50:10 +00:00
# Load a config value to a variable, defaulting to empty. Does not fail if the
# config is not set (since it would expand to empty by default).
2023-08-31 16:07:39 +00:00
load_config_value() {
local config_name="$1"
if grep -q "$config_name=" /tmp/config; then
grep "$config_name=" /tmp/config | tail -n1 | cut -f2 -d '=' | tr -d '"'
fi
2022-01-07 19:30:57 +00:00
}
2023-07-05 14:18:06 +00:00
# Generate a secret for TPM-less HOTP by reading the ROM. Output is the
# sha256sum of the ROM (binary, not printable), which can be truncated to the
# supported secret length.
2019-11-25 18:25:11 +00:00
secret_from_rom_hash() {
local ROM_IMAGE="/tmp/coreboot-notpm.rom"
echo -e "\nTPM not detected; measuring ROM directly\n" 1>&2
2023-07-03 20:59:23 +00:00
# Read the ROM if we haven't read it yet
if [ ! -f "${ROM_IMAGE}" ]; then
flash.sh -r "${ROM_IMAGE}" >/dev/null 2>&1 || return 1
2019-11-25 18:25:11 +00:00
fi
2023-07-03 20:59:23 +00:00
2023-07-05 14:18:06 +00:00
sha256sum "${ROM_IMAGE}" | cut -f1 -d ' ' | fromhex_plain
2019-11-25 18:25:11 +00:00
}
2023-08-31 16:07:39 +00:00
update_checksums() {
2023-02-20 16:01:17 +00:00
TRACE "Under /etc/functions:update_checksums"
2019-07-05 22:04:00 +00:00
# ensure /boot mounted
2023-08-31 16:07:39 +00:00
if ! grep -q /boot /proc/mounts; then
mount -o ro /boot ||
recovery "Unable to mount /boot"
2019-07-05 22:04:00 +00:00
fi
2020-10-08 13:16:08 +00:00
2019-07-05 22:04:00 +00:00
# remount RW
2020-07-13 22:22:40 +00:00
mount -o rw,remount /boot
2019-07-05 22:04:00 +00:00
# sign and auto-roll config counter
extparam=
2023-08-31 16:07:39 +00:00
if [ "$CONFIG_TPM" = "y" ]; then
2022-08-25 18:43:31 +00:00
if [ "$CONFIG_IGNORE_ROLLBACK" != "y" ]; then
extparam=-r
fi
2019-07-05 22:04:00 +00:00
fi
2023-08-31 16:07:39 +00:00
if ! kexec-sign-config -p /boot -u $extparam; then
2021-09-24 20:05:14 +00:00
rv=1
else
rv=0
2019-11-13 23:28:12 +00:00
fi
2019-07-05 22:04:00 +00:00
# switch back to ro mode
mount -o ro,remount /boot
2021-09-24 20:05:14 +00:00
return $rv
2019-07-05 22:04:00 +00:00
}
2019-08-19 22:07:22 +00:00
2022-12-31 17:41:24 +00:00
print_tree() {
2023-02-20 16:01:17 +00:00
TRACE "Under /etc/functions:print_tree"
2023-01-08 13:02:13 +00:00
find ./ ! -path './kexec*' -print0 | sort -z
}
2023-01-14 12:14:09 +00:00
# Escape zero-delimited standard input to safely display it to the user in e.g.
# `whiptail`, `less`, `echo`, `cat`. Doesn't produce shell-escaped output.
# Most printable characters are passed verbatim (exception: \).
# These escapes are used to replace their corresponding characters: #n#r#t#v#b
# Other characters are rendered as hexadecimal escapes.
2023-01-08 13:02:13 +00:00
# escape_zero [prefix] [escape character]
# prefix: \0 in the input will result in \n[prefix]
# escape character: character to use for escapes (default: #); \ may be interpreted by `whiptail`
escape_zero() {
local prefix="$1"
local echar="${2:-#}"
local todo=""
2023-01-14 12:14:09 +00:00
local echar_hex="$(echo -n "$echar" | xxd -p -c1)"
[ ${#echar_hex} -eq 2 ] || die "Invalid escape character $echar passed to escape_zero(). Programming error?!"
2023-01-08 13:02:13 +00:00
echo -e -n "$prefix"
2023-01-14 12:14:09 +00:00
xxd -p -c1 | tr -d '\n' |
2023-08-31 16:07:39 +00:00
{
while IFS= read -r -n2 -d ''; do
if [ -n "$todo" ]; then
#REPLY == " " is EOF
[[ "$REPLY" == " " ]] && echo '' || echo -e -n "$todo"
todo=""
fi
case "$REPLY" in
2023-01-08 13:02:13 +00:00
00)
todo="\n$prefix"
;;
08)
echo -n "${echar}b"
;;
09)
echo -n "${echar}t"
;;
0a)
echo -n "${echar}n"
;;
0b)
echo -n "${echar}v"
;;
0d)
echo -n "${echar}r"
;;
2023-01-14 12:14:09 +00:00
"$echar_hex")
echo -n "$echar$echar"
2023-01-08 13:02:13 +00:00
;;
2023-01-14 12:14:09 +00:00
#interpreted characters:
2023-08-31 16:07:39 +00:00
2[0-9a-f] | 3[0-9a-f] | 4[0-9a-f] | 5[0-9abd-f] | 6[0-9a-f] | 7[0-9a-e])
2023-01-08 13:02:13 +00:00
echo -e -n '\x'"$REPLY"
;;
# All others are escaped
2023-01-14 12:14:09 +00:00
*)
2023-01-08 13:02:13 +00:00
echo -n "${echar}x$REPLY"
;;
2023-08-31 16:07:39 +00:00
esac
done
}
2022-12-31 17:41:24 +00:00
}
2023-01-12 16:31:31 +00:00
# Currently heads doesn't support signing file names with certain characters
# due to https://bugs.busybox.net/show_bug.cgi?id=14226. Also, certain characters
# may be intepreted by `whiptail`, `less` et al (e.g. \n, \b, ...).
assert_signable() {
2023-02-20 16:01:17 +00:00
TRACE "Under /etc/functions:assert_signable"
2023-01-12 16:31:31 +00:00
# ensure /boot mounted
2023-08-31 16:07:39 +00:00
if ! grep -q /boot /proc/mounts; then
2023-01-12 16:31:31 +00:00
mount -o ro /boot || die "Unable to mount /boot"
fi
2023-08-31 16:07:39 +00:00
find /boot -print0 >/tmp/signable.ref
2023-01-12 16:31:31 +00:00
local del='\001-\037\134\177-\377'
2023-08-31 16:07:39 +00:00
LC_ALL=C tr -d "$del" </tmp/signable.ref >/tmp/signable.del || die "Failed to execute tr."
if ! cmp -s "/tmp/signable.ref" "/tmp/signable.del" &>/dev/null; then
2023-01-14 09:27:42 +00:00
local user_out="/tmp/hash_output_mismatches"
local add="Please investigate!"
[ -f "$user_out" ] && add="Please investigate the following relative paths to /boot (where # are sanitized invalid characters):"$'\n'"$(cat "$user_out")"
recovery "Some /boot file names contain characters that are currently not supported by heads: $del"$'\n'"$add"
2023-01-12 16:31:31 +00:00
fi
rm -f /tmp/signable.*
}
2023-08-31 16:07:39 +00:00
verify_checksums() {
2023-02-20 16:01:17 +00:00
TRACE "Under /etc/functions:verify_checksums"
2022-12-31 17:41:24 +00:00
local boot_dir="$1"
2023-01-12 16:18:52 +00:00
local gui="${2:-y}"
2022-12-31 17:41:24 +00:00
(
set +e -o pipefail
local ret=0
cd "$boot_dir" || ret=1
2023-08-31 16:07:39 +00:00
sha256sum -c "$TMP_HASH_FILE" >/tmp/hash_output || ret=1
2022-12-31 17:41:24 +00:00
# also make sure that the file & directory structure didn't change
# (sha256sum won't detect added files)
2023-08-31 16:07:39 +00:00
print_tree >/tmp/tree_output || ret=1
if ! cmp -s "$TMP_TREE_FILE" /tmp/tree_output &>/dev/null; then
2023-01-08 13:02:13 +00:00
ret=1
2023-01-12 16:18:52 +00:00
[[ "$gui" != "y" ]] && exit "$ret"
2023-01-08 13:02:13 +00:00
# produce a diff that can safely be presented to the user
# this is relatively hard as file names may e.g. contain backslashes etc.,
# which are interpreted by whiptail, less, ...
2023-08-31 16:07:39 +00:00
escape_zero "(new) " <"$TMP_TREE_FILE" >"${TMP_TREE_FILE}.user"
escape_zero "(new) " </tmp/tree_output >/tmp/tree_output.user
diff "${TMP_TREE_FILE}.user" /tmp/tree_output.user | grep -E '^\+\(new\).*$' | sed -r 's/^\+\(new\)/(new)/g' >>/tmp/hash_output
2023-01-08 13:02:13 +00:00
rm -f "${TMP_TREE_FILE}.user"
rm -f /tmp/tree_output.user
fi
2022-12-31 17:41:24 +00:00
exit $ret
)
return $?
}
2019-08-19 22:07:22 +00:00
# detect and set /boot device
# mount /boot if successful
2023-08-31 16:07:39 +00:00
detect_boot_device() {
2023-02-20 16:01:17 +00:00
TRACE "Under /etc/functions:detect_boot_device"
2019-08-19 22:07:22 +00:00
# unmount /boot to be safe
2020-07-13 22:22:40 +00:00
cd / && umount /boot 2>/dev/null
2019-08-19 22:07:22 +00:00
# check $CONFIG_BOOT_DEV if set/valid
if [ -e "$CONFIG_BOOT_DEV" ]; then
2019-11-13 23:36:29 +00:00
if mount -o ro $CONFIG_BOOT_DEV /boot >/dev/null 2>&1; then
if ls -d /boot/grub* >/dev/null 2>&1; then
# CONFIG_BOOT_DEV is valid device and contains an installed OS
return 0
fi
2019-08-19 22:07:22 +00:00
fi
fi
# generate list of possible boot devices
2023-08-31 16:07:39 +00:00
fdisk -l | grep "Disk /dev/" | cut -f2 -d " " | cut -f1 -d ":" >/tmp/disklist
2020-10-18 18:46:57 +00:00
2019-08-19 22:07:22 +00:00
# filter out extraneous options
2023-08-31 16:07:39 +00:00
>/tmp/boot_device_list
for i in $(cat /tmp/disklist); do
2019-08-19 22:07:22 +00:00
# remove block device from list if numeric partitions exist, since not bootable
2023-08-31 16:07:39 +00:00
DEV_NUM_PARTITIONS=$(($(ls -1 $i* | wc -l) - 1))
2019-08-19 22:07:22 +00:00
if [ ${DEV_NUM_PARTITIONS} -eq 0 ]; then
2023-08-31 16:07:39 +00:00
echo $i >>/tmp/boot_device_list
2019-08-19 22:07:22 +00:00
else
2023-08-31 16:07:39 +00:00
ls $i* | tail -${DEV_NUM_PARTITIONS} >>/tmp/boot_device_list
2019-08-19 22:07:22 +00:00
fi
done
2020-10-18 18:46:57 +00:00
2019-08-19 22:07:22 +00:00
# iterate thru possible options and check for grub dir
2023-08-31 16:07:39 +00:00
for i in $(cat /tmp/boot_device_list); do
2019-08-19 22:07:22 +00:00
umount /boot 2>/dev/null
2019-11-13 23:36:29 +00:00
if mount -o ro $i /boot >/dev/null 2>&1; then
if ls -d /boot/grub* >/dev/null 2>&1; then
CONFIG_BOOT_DEV="$i"
return 0
fi
2019-08-19 22:07:22 +00:00
fi
done
# no valid boot device found
echo "Unable to locate /boot files on any mounted disk"
umount /boot 2>/dev/null
return 1
}
2021-10-19 18:48:03 +00:00
2023-08-31 16:07:39 +00:00
scan_boot_options() {
2022-11-16 19:24:28 +00:00
local bootdir config option_file
bootdir="$1"
config="$2"
option_file="$3"
if [ -r $option_file ]; then rm $option_file; fi
2023-08-31 16:07:39 +00:00
for i in $(find $bootdir -name "$config"); do
DO_WITH_DEBUG kexec-parse-boot "$bootdir" "$i" >>$option_file
2022-11-16 19:24:28 +00:00
done
# FC29/30+ may use BLS format grub config files
# https://fedoraproject.org/wiki/Changes/BootLoaderSpecByDefault
# only parse these if $option_file is still empty
if [ ! -s $option_file ] && [ -d "$bootdir/loader/entries" ]; then
2023-08-31 16:07:39 +00:00
for i in $(find $bootdir -name "$config"); do
kexec-parse-bls "$bootdir" "$i" "$bootdir/loader/entries" >>$option_file
2022-11-16 19:24:28 +00:00
done
fi
}
2023-08-31 16:07:39 +00:00
calc() {
awk "BEGIN { print "$*" }"
2021-10-19 18:48:03 +00:00
}
2023-07-05 14:18:06 +00:00
# truncate a file to a size only if it is longer (busybox truncate lacks '<' and
# always sets the file size)
truncate_max_bytes() {
local bytes="$1"
local file="$2"
if [ "$(stat -c %s "$file")" -gt "$bytes" ]; then
truncate -s "$bytes" "$file"
fi
}
# Busybox xxd -p pads the last line with spaces to 60 columns, which not only
# trips up many scripts, it's very difficult to diagnose by looking at the
# output. Delete line breaks and spaces to really get plain hex output.
tohex_plain() {
xxd -p | tr -d '\n '
}
# Busybox xxd -p -r silently truncates lines longer than 60 hex chars.
# Shorter lines are OK, spaces are OK, and even splitting a byte across lines is
# allowed, so just fold the text to maximum 60 column lines.
# Note that also unlike GNU xxd, non-hex chars in input corrupt the output (GNU
# xxd ignores them).
fromhex_plain() {
fold -w 60 | xxd -p -r
}
2023-08-31 16:07:39 +00:00
print_battery_health() {
2021-10-26 20:26:11 +00:00
if [ -d /sys/class/power_supply/BAT* ]; then
2023-08-31 16:07:39 +00:00
battery_health=$(calc $(cat /sys/class/power_supply/BAT*/charge_full)/$(cat /sys/class/power_supply/BAT*/charge_full_design)*100 | awk -F "." {'print $1'})
2021-10-19 18:48:03 +00:00
echo "$battery_health"
fi
}
2023-08-31 16:07:39 +00:00
print_battery_charge() {
2021-10-26 20:26:11 +00:00
if [ -d /sys/class/power_supply/BAT* ]; then
2023-08-31 16:07:39 +00:00
battery_charge=$(calc $(cat /sys/class/power_supply/BAT*/charge_now)/$(cat /sys/class/power_supply/BAT*/charge_full)*100 | awk -F "." {'print $1'})
echo "$battery_charge"
fi
2021-10-19 18:48:03 +00:00
}
2022-04-29 14:24:02 +00:00
2023-08-31 16:07:39 +00:00
generate_random_mac_address() {
2022-04-29 14:24:02 +00:00
#Borrowed from https://stackoverflow.com/questions/42660218/bash-generate-random-mac-address-unicast
hexdump -n 6 -ve '1/1 "%.2x "' /dev/urandom | awk -v a="2,6,a,e" -v r="$RANDOM" 'BEGIN{srand(r);}NR==1{split(a,b,",");r=int(rand()*4+1);printf "%s%s:%s:%s:%s:%s:%s\n",substr($1,0,1),b[r],$2,$3,$4,$5,$6}'
}
2023-03-10 22:50:43 +00:00
# Add a command to be invoked at exit. (Note that trap EXIT replaces any
# existing handler.) Commands are invoked in reverse order, so they can be used
# to clean up resources, etc.
# The parameters are all executed as-is and do _not_ require additional quoting
# (unlike trap). E.g.:
# at_exit shred "$file" #<-- file is expanded when calling at_exit, no extra quoting needed
at_exit() {
AT_EXIT_HANDLERS+=("$@") # Command and args
AT_EXIT_HANDLERS+=("$#") # Number of elements in this command
}
# Array of all exit handler command arguments with lengths of each command at
# the end. For example:
# at_exit echo hello
# at_exit echo a b c
# results in:
# AT_EXIT_HANDLERS=(echo hello 2 echo a b c 4)
AT_EXIT_HANDLERS=()
# Each handler is an array AT_EXIT_HANDLER_{i}
run_at_exit_handlers() {
local cmd_pos cmd_len
cmd_pos="${#AT_EXIT_HANDLERS[@]}"
# Silence trace if there are no handlers, this is common and occurs a lot
[ "$cmd_pos" -gt 0 ] && DEBUG "Running at_exit handlers"
while [ "$cmd_pos" -gt 0 ]; do
2023-08-31 16:07:39 +00:00
cmd_pos="$((cmd_pos - 1))"
2023-03-10 22:50:43 +00:00
cmd_len="${AT_EXIT_HANDLERS[$cmd_pos]}"
2023-08-31 16:07:39 +00:00
cmd_pos="$((cmd_pos - cmd_len))"
2023-03-10 22:50:43 +00:00
"${AT_EXIT_HANDLERS[@]:$cmd_pos:$cmd_len}"
done
}
trap run_at_exit_handlers EXIT