diff --git a/net/wifischedule/net/usr/bin/wifi_schedule.sh b/net/wifischedule/net/usr/bin/wifi_schedule.sh index 9adadd212f..468216c0d0 100755 --- a/net/wifischedule/net/usr/bin/wifi_schedule.sh +++ b/net/wifischedule/net/usr/bin/wifi_schedule.sh @@ -1,11 +1,12 @@ #!/bin/sh # Copyright (c) 2016, prpl Foundation +# Copyright ANNO DOMINI 2024 Jan Chren (rindeal) # # Permission to use, copy, modify, and/or distribute this software for any purpose with or without # fee is hereby granted, provided that the above copyright notice and this permission notice appear # in all copies. -# +# # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE # INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE # FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM @@ -16,187 +17,212 @@ set -o pipefail -SCRIPT=$0 +SCRIPT="$0" LOCKFILE=/tmp/wifi_schedule.lock LOGFILE=/tmp/log/wifi_schedule.log LOGGING=0 #default is off PACKAGE=wifi_schedule GLOBAL=${PACKAGE}.@global[0] + +_arith_is() { [ "$1" -ne "0" ] ;} + +_join_by_char() +{ + local IFS="$1" + shift + printf "%s" "$*" +} + _log() { - if [ ${LOGGING} -eq 1 ]; then - local ts=$(date) - echo "$ts $@" >> ${LOGFILE} - fi + [ "${LOGGING}" -eq 1 ] || return + local ts=$(date) + echo "$ts $@" >> "${LOGFILE}" } _exit() { local rc=$1 - lock -u ${LOCKFILE} + lock -u "${LOCKFILE}" exit ${rc} } -_cron_restart() -{ - /etc/init.d/cron restart > /dev/null -} - -_add_cron_script() -{ - (crontab -l ; echo "$1") | sort | uniq | crontab - - _cron_restart -} - -_rm_cron_script() -{ - crontab -l | grep -v "$1" | sort | uniq | crontab - - _cron_restart -} - -_get_uci_value_raw() -{ - local value - value=$(uci get $1 2> /dev/null) - local rc=$? - echo ${value} - return ${rc} -} +_get_uci_value_raw() { uci get "$1" 2> /dev/null ;} _get_uci_value() { - local value - value=$(_get_uci_value_raw $1) + _get_uci_value_raw "$1" local rc=$? if [ ${rc} -ne 0 ]; then - _log "Could not determine UCI value $1" - return 1 + _log "Could not determine UCI value '$1'" fi - echo ${value} + return ${rc} } -_format_dow_list() +_cfg_global_is_enabled() { - local dow=$1 - local flist="" - local day - for day in ${dow} - do - if [ ! -z ${flist} ]; then - flist="${flist}," - fi - flist="${flist}${day:0:3}" - done - echo ${flist} + local enabled=$(_get_uci_value "${GLOBAL}.enabled") + test "${enabled}" -eq 1 } +_cfg_global_is_unload_modules_enabled() +{ + local unload_modules=$(_get_uci_value_raw "${GLOBAL}.unload_modules") || return 1 + test "${unload_modules}" -eq 1 +} -_enable_wifi_schedule() +_cfg_list_entries() +{ + uci show "${PACKAGE}" 2> /dev/null | awk -F= '$2 == "entry"' | cut -s -d. -f2 | cut -d= -f1 +} + +_cfg_entry_is_enabled() +{ + local enabled=$(_get_uci_value "${PACKAGE}.${entry}.enabled") + test "${enabled}" -eq 1 +} + +_cfg_entry_is_now_within_timewindow() { local entry=$1 - local starttime - local stoptime - starttime=$(_get_uci_value ${PACKAGE}.${entry}.starttime) || _exit 1 - stoptime=$(_get_uci_value ${PACKAGE}.${entry}.stoptime) || _exit 1 + local starttime=$( _get_uci_value "${PACKAGE}.${entry}.starttime" ) || return 1 + local stoptime=$( _get_uci_value "${PACKAGE}.${entry}.stoptime" ) || return 1 + local daysofweek=$(_get_uci_value "${PACKAGE}.${entry}.daysofweek") || return 1 + + # check if day of week matches today + echo "$daysofweek" | grep -q "$(date +%A)" || return 1 + + local nowts=$( date -u +%s -d "$(date "+%H:%M")") + local startts=$(date -u +%s -d "$starttime") + local stopts=$( date -u +%s -d "$stoptime") + # add a day if stopts goes past midnight + stopts=$(( stopts < startts ? stopts + 86400 : stopts )) + + _arith_is $(( nowts >= startts && nowts < stopts )) +} + +_cfg_can_wifi_run_now() +{ + local entry + for entry in $(_cfg_list_entries) + do + test -n "${entry}" || continue + _cfg_entry_is_enabled "${entry}" || continue + _cfg_entry_is_now_within_timewindow "$entry" && return 0 + done + return 1 +} + +_cron_restart() { service cron restart > /dev/null ;} + +_crontab_add_entry_raw() { (crontab -l ; printf "%s\n" "$1") | crontab - ;} + +_crontab_rm_script_entries_by_arg() +{ + # this loop will create regexp that looks like this: + # + # ^\b${SCRIPT}\b\s+\b${@}\b + # + local regex="(:?^|[[:space:]])${SCRIPT}" + local arg + for arg in "$@" + do + regex="${regex}[[:space:]]{1,}${arg}" + done + regex="${regex}(:?$|[[:space:]])" + + crontab -l | awk -v cmd_field_n=6 -v regex="${regex}" ' + { + is_blank_or_comment = $0 ~ /^[[:space:]]*(:?#.*)?$/ + is_env_var = $0 ~ /^[[:space:]]*[a-zA-Z_]{1,}[a-zA-Z0-9_]{0,}[[:space:]]*=.*$/ + # find index of cmdline start + match($0, "[[:space:]]*([^[:space:]]+[[:space:]]+){" cmd_field_n - 1 "}") + cmdline = substr($0, RLENGTH + 1) + if ( is_blank_or_comment || is_env_var || cmdline !~ regex ) + { + print + next + } + }' | crontab - +} + +_crontab_format_dow_field() +{ + _join_by_char "," $(printf "%.3s\n" $@) +} + +_crontab_add_from_cfg_entry() +{ + local entry=$1 + local starttime=$( _get_uci_value "${PACKAGE}.${entry}.starttime" ) || return 1 + local stoptime=$( _get_uci_value "${PACKAGE}.${entry}.stoptime" ) || return 1 + local daysofweek=$( _get_uci_value "${PACKAGE}.${entry}.daysofweek") || return 1 + local forcewifidown=$(_get_uci_value "${PACKAGE}.${entry}.forcewifidown") + local starthh=$(expr substr "$starttime" 1 2) startmm=$(expr substr "$starttime" 4 2) + local stophh=$( expr substr "$stoptime" 1 2) stopmm=$( expr substr "$stoptime" 4 2) - local dow - dow=$(_get_uci_value_raw ${PACKAGE}.${entry}.daysofweek) || _exit 1 - - local fdow=$(_format_dow_list "$dow") - local forcewifidown - forcewifidown=$(_get_uci_value ${PACKAGE}.${entry}.forcewifidown) local stopmode="stop" - if [ $forcewifidown -eq 1 ]; then + if [ "$forcewifidown" -eq 1 ]; then stopmode="forcestop" fi + local fdow=$(_crontab_format_dow_field $daysofweek) - local stop_cron_entry="$(echo ${stoptime} | awk -F':' '{print $2, $1}') * * ${fdow} ${SCRIPT} ${stopmode}" # ${entry}" - _add_cron_script "${stop_cron_entry}" - - if [[ $starttime != $stoptime ]] - then - local start_cron_entry="$(echo ${starttime} | awk -F':' '{print $2, $1}') * * ${fdow} ${SCRIPT} start" # ${entry}" - _add_cron_script "${start_cron_entry}" + if [ "$starttime" != "$stoptime" ] + then + _crontab_add_entry_raw "$startmm $starthh * * ${fdow} ${SCRIPT} start ${entry}" fi + _crontab_add_entry_raw "$stopmm $stophh * * ${fdow} ${SCRIPT} ${stopmode} ${entry}" + return 0 } -_is_earlier() +_crontab_reset_from_cfg() { - local hhmm=$1 - local ret=1 - if [[ $(date +%H) -lt ${hhmm:0:2} ]] - then - ret=0 - fi - if [[ $(date +%H) -eq ${hhmm:0:2} && $(date +%M) -lt ${hhmm:3:4} ]] - then - ret=0 - fi - echo $ret + _crontab_rm_script_entries_by_arg + + _cfg_global_is_enabled || return + + local entry + for entry in $(_cfg_list_entries) + do + test -n "${entry}" || continue + _cfg_entry_is_enabled "${entry}" || continue + _crontab_add_from_cfg_entry "${entry}" + done } -# returns 0 if now() is in $entry -_check_startup_timewindow() -{ - local entry=$1 - local starttime - local stoptime - local dow - starttime=$(_get_uci_value ${PACKAGE}.${entry}.starttime) || _exit 1 - stoptime=$(_get_uci_value ${PACKAGE}.${entry}.stoptime) || _exit 1 - dow=$(_get_uci_value_raw ${PACKAGE}.${entry}.daysofweek) || _exit 1 - - echo $dow | grep $(date +%A) > /dev/null 2>&1 - rc=$? - - if [[ $rc -eq 0 && $(date +%H) -ge ${starttime:0:2} && $(date +%M) -ge ${starttime:3:4} && $(_is_earlier $stoptime) -eq 0 ]] - then - echo 0 - else - echo 1 - fi -} - -_get_wireless_interfaces() -{ - iwinfo | grep ESSID | cut -f 1 -s -d" " -} - - get_module_list() { local mod_list local _if - for _if in $(_get_wireless_interfaces) + for _if in $(_wifi_get_interfaces) do - local mod=$(basename $(readlink -f /sys/class/net/${_if}/device/driver)) - local mod_dep=$(modinfo ${mod} | awk '{if ($1 ~ /depends/) print $2}') - mod_list=$(echo -e "${mod_list}\n${mod},${mod_dep}" | sort | uniq) + local mod=$(basename "$(readlink -f /sys/class/net/${_if}/device/driver)") + local mod_dep=$(modinfo "${mod}" | awk '$1 ~ /^depends:/ { print $2 }') + mod_list=$(echo -e "${mod_list}\n${mod},${mod_dep}" | sort -u) done - echo $mod_list | tr ',' ' ' + echo "$mod_list" | tr ',' ' ' } save_module_list_uci() { local list=$(get_module_list) uci set ${GLOBAL}.modules="${list}" - uci commit ${PACKAGE} + uci commit "${PACKAGE}" } _unload_modules() { - local list=$(_get_uci_value ${GLOBAL}.modules) - local retries - retries=$(_get_uci_value ${GLOBAL}.modules_retries) || _exit 1 + local list=$(_get_uci_value "${GLOBAL}.modules") + local retries=$(_get_uci_value "${GLOBAL}.modules_retries") || return 1 _log "unload_modules ${list} (retries: ${retries})" local i=0 - while [[ ${i} -lt ${retries} && "${list}" != "" ]] - do - i=$(($i+1)) + while [ $i -lt "$retries" -a -n "$list" ] + do + : $(( ++i )) local mod local first=0 for mod in ${list} @@ -205,7 +231,7 @@ _unload_modules() list="" first=1 fi - rmmod ${mod} > /dev/null 2>&1 + rmmod "${mod}" > /dev/null 2>&1 if [ $? -ne 0 ]; then list="$list $mod" fi @@ -213,17 +239,15 @@ _unload_modules() done } - _load_modules() { - local list=$(_get_uci_value ${GLOBAL}.modules) - local retries - retries=$(_get_uci_value ${GLOBAL}.modules_retries) || _exit 1 + local list=$( _get_uci_value "${GLOBAL}.modules") || return 1 + local retries=$(_get_uci_value "${GLOBAL}.modules_retries") || return 1 _log "load_modules ${list} (retries: ${retries})" local i=0 - while [[ ${i} -lt ${retries} && "${list}" != "" ]] - do - i=$(($i+1)) + while [ $i -lt "${retries}" -a -n "${list}" ] + do + : $(( ++i )) local mod local first=0 for mod in ${list} @@ -232,8 +256,8 @@ _load_modules() list="" first=1 fi - modprobe ${mod} > /dev/null 2>&1 - rc=$? + modprobe "${mod}" > /dev/null 2>&1 + rc=$? if [ $rc -ne 255 ]; then list="$list $mod" fi @@ -241,143 +265,122 @@ _load_modules() done } -_create_cron_entries() +_uci_wireless_radio_set_disabled_to() { - local entries=$(uci show ${PACKAGE} 2> /dev/null | awk -F'.' '{print $2}' | grep -v '=' | grep -v '@global\[0\]' | uniq | sort) - local _entry - for entry in ${entries} - do - local status - status=$(_get_uci_value ${PACKAGE}.${entry}.enabled) || _exit 1 - if [ ${status} -eq 1 ] - then - _enable_wifi_schedule ${entry} - fi - done -} - -_should_wifi_enabled() -{ - - local enable_wifi=0 - local entries=$(uci show ${PACKAGE} 2> /dev/null | awk -F'.' '{print $2}' | grep -v '=' | grep -v '@global\[0\]' | uniq | sort) - local _entry - for _entry in ${entries} + local status=$1 + _arith_is $(( status == 0 || status == 1 )) || return 1 + for radio in $(uci show wireless | awk -F= '$2 == "wifi-device" {print $1}' | awk -F. '{print $2}') do - local status - status=$(_get_uci_value ${PACKAGE}.${_entry}.enabled) || _exit 1 - if [ ${status} -eq 1 ] - then - enable_wifi=$(_check_startup_timewindow $_entry) - fi + uci set "wireless.${radio}.disabled=${status}" done - echo ${enable_wifi} + uci commit + /sbin/wifi } -startup() +_uci_enable_all_radio_devices() { _uci_wireless_radio_set_disabled_to 0 ;} +_uci_disable_all_radio_devices() { _uci_wireless_radio_set_disabled_to 1 ;} + +_wifi_get_interfaces() { - _log "startup" - local global_enabled=$(_get_uci_value ${GLOBAL}.enabled) || _exit 1 - if [ ${global_enabled} -eq 1 ]; then - local _enable_wifi=$(_should_wifi_enabled) - if [ ${_enable_wifi} -eq 0 ]; then - _log "enable wifi" - enable_wifi - else - _log "disable wifi" - disable_wifi - fi - fi + iwinfo | grep ESSID | cut -f 1 -d" " -s } -check_cron_status() +wifi_disable() { - local global_enabled - global_enabled=$(_get_uci_value ${GLOBAL}.enabled) || _exit 1 - _rm_cron_script "${SCRIPT}" - if [ ${global_enabled} -eq 1 ]; then - _create_cron_entries - fi -} - -disable_wifi() -{ - _rm_cron_script "${SCRIPT} recheck" - _set_status_wifi_uci 1 - local unload_modules - unload_modules=$(_get_uci_value_raw ${GLOBAL}.unload_modules) || _exit 1 - if [[ "${unload_modules}" == "1" ]]; then + _crontab_rm_script_entries_by_arg "recheck" + _cron_restart + _uci_disable_all_radio_devices + if _cfg_global_is_unload_modules_enabled + then _unload_modules - fi + fi } -soft_disable_wifi() +wifi_soft_disable() { - local _disable_wifi=0 #0: disable wifi, 1: do not disable wifi - local iwinfo=/usr/bin/iwinfo - if [ ! -e ${iwinfo} ]; then - _log "${iwinfo} not available, skipping" + if ! command -v iwinfo 2>/dev/null ; then + _log "iwinfo not available, skipping" return 1 fi - local ignore_stations=$(_get_uci_value_raw ${GLOBAL}.ignore_stations) - [ -n "${ignore_stations}" ] && _log "Ignoring station(s) ${ignore_stations}" + local has_assoc=false + local mac_filter='([[:xdigit:]]{1,2}:){5}[[:xdigit:]]{1,2}' + + local ignore_stations=$(_get_uci_value_raw "${GLOBAL}.ignore_stations") + local ignore_stations_filter=$( _join_by_char "|" ${ignore_stations} ) # check if no stations are associated local _if - for _if in $(_get_wireless_interfaces) + for _if in $(_wifi_get_interfaces) do - local stations=$(${iwinfo} ${_if} assoclist | grep -o -E '([[:xdigit:]]{1,2}:){5}[[:xdigit:]]{1,2}') + local stations ignored_stations + stations=$(iwinfo "${_if}" assoclist | grep -o -E "${mac_filter}") if [ -n "${ignore_stations}" ]; then - stations=$(echo "${stations}" | grep -vwi -E "${ignore_stations// /|}") + local all_stations="$stations" + stations=$(printf "%s\n" ${stations} | grep -vwi -E "${ignore_stations_filter}") + ignored_stations="$( printf "%s\n" $all_stations $stations | sort | uniq -u )" fi - if [ -n "${stations}" ]; then - _disable_wifi=1 - _log "Station(s) $(echo ${stations}) associated on ${_if}" + test -n "${stations}" || continue + + has_assoc=true + + _log "Clients connected on '${_if}': $(_join_by_char ' ' ${stations})" + if test -n "${ignored_stations}" + then + _log "Clients ignored on '${_if}': $(_join_by_char ' ' ${ignored_stations})" fi done - local _wifi_enabled=$(_should_wifi_enabled) - if [[ ${_disable_wifi} -eq 0 && ${_wifi_enabled} -eq 1 ]]; then - _log "No stations associated, disable wifi." - disable_wifi - elif [[ ${_disable_wifi} -eq 0 && ${_wifi_enabled} -eq 0 ]]; then - _log "Do not disable wifi since there is an allow timeframe, skip rechecking." - _rm_cron_script "${SCRIPT} recheck" + _crontab_rm_script_entries_by_arg "recheck" + + if [ "$has_assoc" = "false" ] + then + if _cfg_can_wifi_run_now + then + _log "Do not disable wifi since there is an allow timewindow, skip rechecking." + else + _log "No stations associated, disable wifi." + wifi_disable + fi else _log "Could not disable wifi due to associated stations, retrying..." - local recheck_interval=$(_get_uci_value ${GLOBAL}.recheck_interval) - _add_cron_script "*/${recheck_interval} * * * * ${SCRIPT} recheck" + local recheck_interval=$(_get_uci_value "${GLOBAL}.recheck_interval") + if [ -n "${recheck_interval}" -a "${recheck_interval}" -gt 0 ] ; then + _crontab_add_entry_raw "*/${recheck_interval} * * * * /bin/nice -n 19 ${SCRIPT} recheck" + fi fi + + _cron_restart } -_set_status_wifi_uci() +wifi_enable() { - local status=$1 - local radios=$(uci show wireless | grep radio | awk -F'.' '{print $2}' | grep -v '[=|@]' | sort | uniq) - for radio in ${radios} - do - uci set wireless.${radio}.disabled=${status} - done - uci commit -} - -enable_wifi() -{ - _rm_cron_script "${SCRIPT} recheck" - local unload_modules - unload_modules=$(_get_uci_value_raw ${GLOBAL}.unload_modules) || _exit 1 - if [[ "${unload_modules}" == "1" ]]; then + _crontab_rm_script_entries_by_arg "recheck" + _cron_restart + if _cfg_global_is_unload_modules_enabled + then _load_modules fi - _set_status_wifi_uci 0 - /sbin/wifi + _uci_enable_all_radio_devices +} + +wifi_startup() +{ + _cfg_global_is_enabled || return + + if _cfg_can_wifi_run_now + then + _log "enable wifi" + wifi_enable + else + _log "disable wifi" + wifi_disable + fi } usage() { - echo "" echo "$0 cron|start|startup|stop|forcestop|recheck|getmodules|savemodules|help" echo "" echo " UCI Config File: /etc/config/${PACKAGE}" @@ -396,8 +399,8 @@ usage() _cleanup() { - lock -u ${LOCKFILE} - rm ${LOCKFILE} + lock -u "${LOCKFILE}" + rm "${LOCKFILE}" } ############################################################################### @@ -405,20 +408,21 @@ _cleanup() ############################################################################### trap _cleanup EXIT -LOGGING=$(_get_uci_value ${GLOBAL}.logging) || _exit 1 -_log ${SCRIPT} $1 $2 -lock ${LOCKFILE} +LOGGING=$(_get_uci_value "${GLOBAL}.logging") || _exit 1 +_log "${SCRIPT}" "$@" +lock "${LOCKFILE}" case "$1" in - cron) - check_cron_status - startup + cron) + _crontab_reset_from_cfg + _cron_restart + wifi_startup ;; - start) enable_wifi ;; - startup) startup ;; - forcestop) disable_wifi ;; - stop) soft_disable_wifi ;; - recheck) soft_disable_wifi ;; + start) wifi_enable ;; + startup) wifi_startup ;; + forcestop) wifi_disable ;; + stop) wifi_soft_disable ;; + recheck) wifi_soft_disable ;; getmodules) get_module_list ;; savemodules) save_module_list_uci ;; help|--help|-h|*) usage ;;