mirror of
https://github.com/linuxboot/heads.git
synced 2024-12-21 22:07:52 +00:00
55b3fcfe1a
Multiple traps overwrite each other. While no tpmr functions have more than one trap right now, it is fragile, and the quoting is complex due to double expansion. Use at_exit to add exit handlers that accumulate and do not require special quoting. Signed-off-by: Jonathon Hall <jonathon.hall@puri.sm>
613 lines
16 KiB
Bash
Executable File
613 lines
16 KiB
Bash
Executable File
#!/bin/bash
|
|
# Shell functions for most initialization scripts
|
|
. /etc/ash_functions
|
|
|
|
# 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
|
|
"$@"
|
|
}
|
|
|
|
recovery() {
|
|
TRACE "Under /etc/functions:recovery"
|
|
echo >&2 "!!!!! $*"
|
|
|
|
# Remove any temporary secret files that might be hanging around
|
|
# but recreate the directory so that new tools can use it.
|
|
|
|
#safe to always be true. Otherwise "set -e" would make it exit here
|
|
shred -n 10 -z -u /tmp/secret/* 2> /dev/null || true
|
|
rm -rf /tmp/secret
|
|
mkdir -p /tmp/secret
|
|
|
|
# ensure /tmp/config exists for recovery scripts that depend on it
|
|
touch /tmp/config
|
|
|
|
if [ "$CONFIG_TPM" = "y" ]; then
|
|
tpmr extend -ix 4 -ic recovery
|
|
fi
|
|
|
|
while [ true ]
|
|
do
|
|
echo >&2 "!!!!! Starting recovery shell"
|
|
sleep 1
|
|
|
|
if [ -x /bin/setsid ]; then
|
|
/bin/setsid -c /bin/sh
|
|
else
|
|
/bin/sh
|
|
fi
|
|
done
|
|
}
|
|
|
|
pause_recovery() {
|
|
TRACE "Under /etc/functions:pause_recovery"
|
|
read -p $'!!! Hit enter to proceed to recovery shell !!!\n'
|
|
recovery $*
|
|
}
|
|
|
|
pcrs() {
|
|
if [ "$CONFIG_TPM2_TOOLS" = "y" ]; then
|
|
tpm2 pcrread sha256
|
|
elif [ "$CONFIG_TPM" = "y" ]; then
|
|
head -8 /sys/class/tpm/tpm0/pcrs
|
|
fi
|
|
}
|
|
|
|
confirm_totp()
|
|
{
|
|
TRACE "Under /etc/functions:confirm_totp"
|
|
prompt="$1"
|
|
last_half=X
|
|
unset totp_confirm
|
|
|
|
while true; do
|
|
|
|
# update the TOTP code every thirty seconds
|
|
date=`date "+%Y-%m-%d %H:%M:%S"`
|
|
seconds=`date "+%s"`
|
|
half=`expr \( $seconds % 60 \) / 30`
|
|
if [ "$CONFIG_TPM" != "y" ]; then
|
|
TOTP="NO TPM"
|
|
elif [ "$half" != "$last_half" ]; then
|
|
last_half=$half;
|
|
TOTP=`unseal-totp` \
|
|
|| recovery "TOTP code generation failed"
|
|
fi
|
|
|
|
echo -n "$date $TOTP: "
|
|
|
|
# read the first character, non-blocking
|
|
read \
|
|
-t 1 \
|
|
-n 1 \
|
|
-s \
|
|
-p "$prompt" \
|
|
totp_confirm \
|
|
&& break
|
|
|
|
# nothing typed, redraw the line
|
|
echo -ne '\r'
|
|
done
|
|
|
|
# clean up with a newline
|
|
echo
|
|
}
|
|
|
|
enable_usb()
|
|
{
|
|
TRACE "Under /etc/functions:enable_usb"
|
|
#insmod ehci_hcd prior of uhdc_hcd and ohci_hcd to suppress dmesg warning
|
|
if ! lsmod | grep -q ehci_hcd; then
|
|
insmod /lib/modules/ehci-hcd.ko \
|
|
|| die "ehci_hcd: module load failed"
|
|
fi
|
|
if [ "$CONFIG_LINUX_USB_COMPANION_CONTROLLER" = y ]; then
|
|
if ! lsmod | grep -q uhci_hcd; then
|
|
insmod /lib/modules/uhci-hcd.ko \
|
|
|| die "uhci_hcd: module load failed"
|
|
fi
|
|
if ! lsmod | grep -q ohci_hcd; then
|
|
insmod /lib/modules/ohci-hcd.ko \
|
|
|| die "ohci_hcd: module load failed"
|
|
fi
|
|
if ! lsmod | grep -q ohci_pci; then
|
|
insmod /lib/modules/ohci-pci.ko \
|
|
|| die "ohci_pci: module load failed"
|
|
fi
|
|
fi
|
|
if ! lsmod | grep -q ehci_pci; then
|
|
insmod /lib/modules/ehci-pci.ko \
|
|
|| die "ehci_pci: module load failed"
|
|
fi
|
|
if ! lsmod | grep -q xhci_hcd; then
|
|
insmod /lib/modules/xhci-hcd.ko \
|
|
|| die "xhci_hcd: module load failed"
|
|
fi
|
|
if ! lsmod | grep -q xhci_pci; then
|
|
insmod /lib/modules/xhci-pci.ko \
|
|
|| die "xhci_pci: module load failed"
|
|
sleep 2
|
|
fi
|
|
|
|
if [ "$CONFIG_USB_KEYBOARD" = y ]; then
|
|
if ! lsmod | grep -q usbhid; then
|
|
insmod /lib/modules/usbhid.ko \
|
|
|| die "usbhid: module load failed"
|
|
fi
|
|
fi
|
|
|
|
}
|
|
|
|
list_usb_storage()
|
|
{
|
|
TRACE "Under /etc/functions:list_usb_storage"
|
|
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
|
|
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.
|
|
# 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
|
|
# No partition table, include this device
|
|
echo "$b"
|
|
else
|
|
# Has a partition table, include partitions
|
|
ls -1 "$b"* | awk 'NR!=1 {print $0}'
|
|
fi
|
|
done
|
|
}
|
|
|
|
confirm_gpg_card()
|
|
{
|
|
TRACE "Under /etc/functions:confirm_gpg_card"
|
|
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" ] \
|
|
; then
|
|
die "gpg card not confirmed"
|
|
fi
|
|
|
|
# setup the USB so we can reach the GPG card
|
|
enable_usb
|
|
|
|
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
|
|
gpg --card-status > /dev/null
|
|
if [ $? -ne 0 ]; then
|
|
# 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"
|
|
fi
|
|
# restore prev errexit state
|
|
if [ "$errexit" = "on" ]; then
|
|
set -e
|
|
fi
|
|
}
|
|
|
|
# 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
|
|
return 0;
|
|
fi
|
|
|
|
read -s -p "TPM Owner password: " tpm_password
|
|
echo # new line after password prompt
|
|
}
|
|
|
|
check_tpm_counter()
|
|
{
|
|
TRACE "Under /etc/functions:check_tpm_counter"
|
|
LABEL=${2:-3135106223}
|
|
tpm_password="$3"
|
|
# if the /boot.hashes file already exists, read the TPM counter ID
|
|
# from it.
|
|
if [ -r "$1" ]; then
|
|
TPM_COUNTER=`grep counter- "$1" | cut -d- -f2`
|
|
else
|
|
warn "$1 does not exist; creating new TPM counter"
|
|
prompt_tpm_password
|
|
tpmr counter_create \
|
|
-pwdo "$tpm_password" \
|
|
-pwdc '' \
|
|
-la $LABEL \
|
|
| tee /tmp/counter \
|
|
|| die "Unable to create TPM counter"
|
|
TPM_COUNTER=`cut -d: -f1 < /tmp/counter`
|
|
fi
|
|
|
|
if [ -z "$TPM_COUNTER" ]; then
|
|
die "$1: TPM Counter not found?"
|
|
fi
|
|
}
|
|
|
|
read_tpm_counter()
|
|
{
|
|
TRACE "Under /etc/functions:read_tpm_counter"
|
|
tpmr counter_read -ix "$1" | tee "/tmp/counter-$1" \
|
|
|| die "Counter read failed"
|
|
}
|
|
|
|
increment_tpm_counter()
|
|
{
|
|
TRACE "Under /etc/functions:increment_tpm_counter"
|
|
tpmr counter_increment -ix "$1" -pwdc '' \
|
|
| tee /tmp/counter-$1 \
|
|
|| die "Counter increment failed"
|
|
}
|
|
|
|
check_config() {
|
|
TRACE "Under /etc/functions:check_config"
|
|
if [ ! -d /tmp/kexec ]; then
|
|
mkdir /tmp/kexec \
|
|
|| die 'Failed to make kexec tmp dir'
|
|
else
|
|
rm -rf /tmp/kexec/* \
|
|
|| die 'Failed to empty kexec tmp dir'
|
|
fi
|
|
|
|
if [ ! -r $1/kexec.sig ]; then
|
|
return
|
|
fi
|
|
|
|
if [ `find $1/kexec*.txt | wc -l` -eq 0 ]; then
|
|
return
|
|
fi
|
|
|
|
if [ "$2" != "force" ]; then
|
|
if ! sha256sum `find $1/kexec*.txt` | gpgv $1/kexec.sig - ; then
|
|
die 'Invalid signature on kexec boot params'
|
|
fi
|
|
fi
|
|
|
|
echo "+++ Found verified kexec boot params"
|
|
cp $1/kexec*.txt /tmp/kexec \
|
|
|| die "Failed to copy kexec boot params to tmp"
|
|
}
|
|
|
|
replace_config() {
|
|
TRACE "Under /etc/functions:replace_config"
|
|
CONFIG_FILE=$1
|
|
CONFIG_OPTION=$2
|
|
NEW_SETTING=$3
|
|
|
|
touch $CONFIG_FILE
|
|
# 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
|
|
|
|
# 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}
|
|
rm -f ${CONFIG_FILE}.tmp
|
|
}
|
|
combine_configs() {
|
|
TRACE "Under /etc/functions:combine_configs"
|
|
cat /etc/config* > /tmp/config
|
|
}
|
|
|
|
update_checksums()
|
|
{
|
|
TRACE "Under /etc/functions:update_checksums"
|
|
# ensure /boot mounted
|
|
if ! grep -q /boot /proc/mounts ; then
|
|
mount -o ro /boot \
|
|
|| recovery "Unable to mount /boot"
|
|
fi
|
|
|
|
# remount RW
|
|
mount -o rw,remount /boot
|
|
|
|
# sign and auto-roll config counter
|
|
extparam=
|
|
if [ "$CONFIG_TPM" = "y" ];then
|
|
if [ "$CONFIG_IGNORE_ROLLBACK" != "y" ]; then
|
|
extparam=-r
|
|
fi
|
|
fi
|
|
if ! kexec-sign-config -p /boot -u $extparam ; then
|
|
rv=1
|
|
else
|
|
rv=0
|
|
fi
|
|
|
|
# switch back to ro mode
|
|
mount -o ro,remount /boot
|
|
|
|
return $rv
|
|
}
|
|
|
|
print_tree() {
|
|
TRACE "Under /etc/functions:print_tree"
|
|
find ./ ! -path './kexec*' -print0 | sort -z
|
|
}
|
|
|
|
# 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.
|
|
# 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=""
|
|
local echar_hex="$(echo -n "$echar" | xxd -p -c1)"
|
|
[ ${#echar_hex} -eq 2 ] || die "Invalid escape character $echar passed to escape_zero(). Programming error?!"
|
|
|
|
echo -e -n "$prefix"
|
|
xxd -p -c1 | tr -d '\n' |
|
|
{
|
|
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
|
|
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"
|
|
;;
|
|
"$echar_hex")
|
|
echo -n "$echar$echar"
|
|
;;
|
|
#interpreted characters:
|
|
2[0-9a-f]|3[0-9a-f]|4[0-9a-f]|5[0-9abd-f]|6[0-9a-f]|7[0-9a-e])
|
|
echo -e -n '\x'"$REPLY"
|
|
;;
|
|
# All others are escaped
|
|
*)
|
|
echo -n "${echar}x$REPLY"
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
}
|
|
|
|
# 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() {
|
|
TRACE "Under /etc/functions:assert_signable"
|
|
# ensure /boot mounted
|
|
if ! grep -q /boot /proc/mounts ; then
|
|
mount -o ro /boot || die "Unable to mount /boot"
|
|
fi
|
|
|
|
find /boot -print0 > /tmp/signable.ref
|
|
local del='\001-\037\134\177-\377'
|
|
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
|
|
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"
|
|
fi
|
|
rm -f /tmp/signable.*
|
|
}
|
|
|
|
verify_checksums()
|
|
{
|
|
TRACE "Under /etc/functions:verify_checksums"
|
|
local boot_dir="$1"
|
|
local gui="${2:-y}"
|
|
|
|
(
|
|
set +e -o pipefail
|
|
local ret=0
|
|
cd "$boot_dir" || ret=1
|
|
sha256sum -c "$TMP_HASH_FILE" > /tmp/hash_output || ret=1
|
|
|
|
# also make sure that the file & directory structure didn't change
|
|
# (sha256sum won't detect added files)
|
|
print_tree > /tmp/tree_output || ret=1
|
|
if ! cmp -s "$TMP_TREE_FILE" /tmp/tree_output &> /dev/null ; then
|
|
ret=1
|
|
[[ "$gui" != "y" ]] && exit "$ret"
|
|
# 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, ...
|
|
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
|
|
rm -f "${TMP_TREE_FILE}.user"
|
|
rm -f /tmp/tree_output.user
|
|
fi
|
|
exit $ret
|
|
)
|
|
return $?
|
|
}
|
|
|
|
# detect and set /boot device
|
|
# mount /boot if successful
|
|
detect_boot_device()
|
|
{
|
|
TRACE "Under /etc/functions:detect_boot_device"
|
|
# unmount /boot to be safe
|
|
cd / && umount /boot 2>/dev/null
|
|
|
|
# check $CONFIG_BOOT_DEV if set/valid
|
|
if [ -e "$CONFIG_BOOT_DEV" ]; then
|
|
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
|
|
fi
|
|
fi
|
|
|
|
# generate list of possible boot devices
|
|
fdisk -l | grep "Disk /dev/" | cut -f2 -d " " | cut -f1 -d ":" > /tmp/disklist
|
|
|
|
# filter out extraneous options
|
|
> /tmp/boot_device_list
|
|
for i in `cat /tmp/disklist`; do
|
|
# remove block device from list if numeric partitions exist, since not bootable
|
|
DEV_NUM_PARTITIONS=$((`ls -1 $i* | wc -l`-1))
|
|
if [ ${DEV_NUM_PARTITIONS} -eq 0 ]; then
|
|
echo $i >> /tmp/boot_device_list
|
|
else
|
|
ls $i* | tail -${DEV_NUM_PARTITIONS} >> /tmp/boot_device_list
|
|
fi
|
|
done
|
|
|
|
# iterate thru possible options and check for grub dir
|
|
for i in `cat /tmp/boot_device_list`; do
|
|
umount /boot 2>/dev/null
|
|
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
|
|
fi
|
|
done
|
|
|
|
# no valid boot device found
|
|
echo "Unable to locate /boot files on any mounted disk"
|
|
umount /boot 2>/dev/null
|
|
return 1
|
|
}
|
|
|
|
calc()
|
|
{
|
|
awk "BEGIN { print "$*" }";
|
|
}
|
|
|
|
print_battery_health()
|
|
{
|
|
if [ -d /sys/class/power_supply/BAT* ]; then
|
|
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'})
|
|
echo "$battery_health"
|
|
fi
|
|
}
|
|
|
|
print_battery_charge()
|
|
{
|
|
if [ -d /sys/class/power_supply/BAT* ]; then
|
|
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
|
|
}
|
|
|
|
generate_random_mac_address()
|
|
{
|
|
#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}'
|
|
}
|
|
|
|
# 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
|
|
cmd_pos="$((cmd_pos-1))"
|
|
cmd_len="${AT_EXIT_HANDLERS[$cmd_pos]}"
|
|
cmd_pos="$((cmd_pos-cmd_len))"
|
|
"${AT_EXIT_HANDLERS[@]:$cmd_pos:$cmd_len}"
|
|
done
|
|
}
|
|
trap run_at_exit_handlers EXIT
|