88 lines
2.9 KiB
Shell
Executable file
88 lines
2.9 KiB
Shell
Executable file
#!/bin/bash
|
|
|
|
# Return whole seconds and a fractional part without floating-point rounding.
|
|
# Long multiplication keeps the range checks exact even for tiny fractions.
|
|
interval_duration() {
|
|
local value=$1 whole fraction multiplier carry=0 product='' digit index
|
|
if [[ ! $value =~ ^([0-9]+([.][0-9]*)?|[.][0-9]+)([smhd]?)$ ]]; then
|
|
echo 'SLEEP_TIME must be a positive duration in seconds, minutes, hours or days.' >&2
|
|
return 1
|
|
fi
|
|
case ${BASH_REMATCH[3]} in
|
|
''|s) multiplier=1 ;;
|
|
m) multiplier=60 ;;
|
|
h) multiplier=3600 ;;
|
|
d) multiplier=86400 ;;
|
|
esac
|
|
value=${BASH_REMATCH[1]}
|
|
whole=${value%%.*}
|
|
whole=${whole#"${whole%%[!0]*}"}
|
|
whole=${whole:-0}
|
|
if (( ${#whole} > 10 )); then
|
|
echo 'SLEEP_TIME must represent between 1 and 2147483647 seconds.' >&2
|
|
return 1
|
|
fi
|
|
fraction=''
|
|
[[ $value != *.* ]] || fraction=${value#*.}
|
|
for ((index = ${#fraction} - 1; index >= 0; index--)); do
|
|
digit=$((10#${fraction:index:1} * multiplier + carry))
|
|
product="$((digit % 10))$product"
|
|
carry=$((digit / 10))
|
|
done
|
|
whole=$((10#$whole * multiplier + carry))
|
|
product=${product%"${product##*[!0]}"}
|
|
if (( whole < 1 || whole > 2147483647 )) || { (( whole == 2147483647 )) && [[ -n $product ]]; }; then
|
|
echo 'SLEEP_TIME must represent between 1 and 2147483647 seconds.' >&2
|
|
return 1
|
|
fi
|
|
printf '%s %s\n' "$whole" "${product:-0}"
|
|
}
|
|
|
|
# /proc/uptime advances independently of wall-clock adjustments, in 10 ms ticks.
|
|
interval_now() {
|
|
local uptime _idle whole fraction
|
|
read -r uptime _idle < /proc/uptime || return 1
|
|
whole=${uptime%.*}
|
|
fraction=${uptime#*.}00
|
|
printf '%s\n' "$((10#$whole * 100 + 10#${fraction:0:2}))"
|
|
}
|
|
|
|
interval_budget() {
|
|
local duration
|
|
duration=$(interval_duration "$1") || return 1
|
|
printf '%s\n' "${duration%% *}"
|
|
}
|
|
|
|
interval_deadline() {
|
|
local duration whole fraction ticks now
|
|
duration=$(interval_duration "$1") || return 1
|
|
read -r whole fraction <<< "$duration"
|
|
fraction+='00'
|
|
ticks=$((whole * 100 + 10#${fraction:0:2}))
|
|
# Round up fractions and the current clock tick to preserve the minimum wait.
|
|
[[ ${fraction:2} != *[1-9]* ]] || ticks=$((ticks + 1))
|
|
now=$(interval_now) || return 1
|
|
printf '%s\n' "$((now + ticks + 1))"
|
|
}
|
|
|
|
interval_wait() {
|
|
local deadline=$1 now remaining delay
|
|
[[ $deadline =~ ^[0-9]{1,15}$ ]] || return 1
|
|
while true; do
|
|
now=$(interval_now) || return 1
|
|
remaining=$((10#$deadline - now))
|
|
(( remaining > 0 )) || return 0
|
|
printf -v delay '%d.%02d' "$((remaining / 100))" "$((remaining % 100))"
|
|
sleep "$delay" || return 1
|
|
done
|
|
}
|
|
|
|
if [[ ${BASH_SOURCE[0]} == "$0" ]]; then
|
|
case ${1:-} in
|
|
budget|deadline|wait)
|
|
[[ $# == 2 ]] || exit 1
|
|
"interval_$1" "$2"
|
|
;;
|
|
*) echo 'Expected budget, deadline or wait and a value.' >&2; exit 1 ;;
|
|
esac
|
|
fi
|