#!/bin/sh /etc/rc.common # Keeps the scheduled-measurement cron entry in step with UCI. There is no # daemon here: measurements are one-shot runs of librespeed-run, and the rpcd # interface needs no service of its own. START=95 USE_PROCD=1 CRONTAB=/etc/crontabs/root RUN=/usr/libexec/librespeed-run AGG=/usr/libexec/librespeed-aggregate # Builds the cron time fields from UCI. For sub-daily intervals `hours` # restricts when measurements may run; for a daily one it is the window a # random time is drawn from, drawn here at sync time. That keeps the herd # apart -- every router lands on its own minute -- without any process # sleeping through the night, at the cost of re-drawing on every reload. # Every comma-separated part must be a number or an ascending range within # [0, max]; the caller already reduced the alphabet to digits, commas and # dashes. valid_field() { local val="$1" max="$2" part a b oifs="$IFS" IFS=',' for part in $val; do IFS="$oifs" a=${part%-*}; b=${part#*-} # Each half on its own: concatenated, '5-' and '-5' hide their empty # side and the arithmetic below would print a shell error where the # caller promises a silent fallback. case "$a" in ''|*[!0-9]*) return 1 ;; esac case "$b" in ''|*[!0-9]*) return 1 ;; esac [ "$a" -le "$max" ] && [ "$b" -le "$max" ] && [ "$a" -le "$b" ] || return 1 done IFS="$oifs" return 0 } cron_expr() { local interval="$1" days="$2" hours="$3" local start end m h v case "$interval" in *m) v=${interval%m} case "$v" in ''|0|*[!0-9]*) return 1 ;; esac [ "$v" -le 59 ] || return 1 echo "*/$v ${hours:-*} * * $days" ;; *h) v=${interval%h} case "$v" in ''|0|*[!0-9]*) return 1 ;; esac [ "$v" -le 23 ] || return 1 if [ -n "$hours" ]; then echo "0 $hours/$v * * $days" else echo "0 */$v * * $days" fi ;; 1d|24h) start=${hours%-*}; end=${hours#*-} [ -n "$hours" ] || { start=0; end=23; } # A window wrapping midnight (22-4) is not supported; treat it as the # single starting hour rather than guessing. [ "$end" -ge "$start" ] 2>/dev/null || end=$start # Time xor pid as the seed: seconds-granular srand() alone would give # two syncs in the same second the same draw. set -- $(awk -v a="$start" -v b="$end" -v s="$(( $(date +%s) ^ $$ ))" 'BEGIN { srand(s) printf "%d %d", int(rand() * 60), a + int(rand() * (b - a + 1)) }') echo "$1 $2 * * $days" ;; *) return 1 ;; esac } restart_cron() { # Unconditional: on a stock system the crontab dir starts empty, so crond # is not running at all -- restart starts a stopped procd service, and a # guard on "running" would mean the schedule never fires until reboot. /etc/init.d/cron restart >/dev/null 2>&1 return 0 } sync_cron() { local enabled interval days hours expr hist_enabled archive_path config_load librespeed config_get_bool enabled schedule enabled 0 config_get interval schedule interval 1d config_get days schedule days '*' config_get hours schedule hours '' config_get_bool hist_enabled history enabled 1 config_get archive_path history archive_path '' # These end up verbatim in root's crontab, so anything unexpected becomes # the harmless default rather than a cron field it was not meant to be. # Values are checked as well as characters: '99' is made of digits, but # as an hour it would turn a daily schedule into an hourly one. case "$days" in *[!0-9,-]*|'') days='*' ;; esac if [ "$days" != '*' ] && ! valid_field "$days" 6; then days='*' fi case "$hours" in *[!0-9-]*) hours='' ;; esac if [ -n "$hours" ] && ! valid_field "$hours" 23; then hours='' fi mkdir -p "${CRONTAB%/*}" touch "$CRONTAB" sed -i "\\#$RUN#d;\\#$AGG#d" "$CRONTAB" # The day completes at midnight; five past, it is reduced to one archive # line. A missed run costs nothing to catch up: raw lives in RAM, so after # a power cut there is nothing left to aggregate anyway. if [ "$hist_enabled" = 1 ] && [ -n "$archive_path" ]; then echo "5 0 * * * $AGG" >> "$CRONTAB" fi if [ "$enabled" = 1 ]; then if expr=$(cron_expr "$interval" "$days" "$hours"); then echo "$expr $RUN" >> "$CRONTAB" else logger -t librespeed "unsupported schedule interval '$interval'" fi fi restart_cron } start_service() { sync_cron } stop_service() { sed -i "\\#$RUN#d;\\#$AGG#d" "$CRONTAB" restart_cron } service_triggers() { procd_add_reload_trigger "librespeed" } reload_service() { sync_cron }