2## @brief Logging, Datei-Operationen, DNS- und NTP-Dienste, Dictionary-Dateien, PID- und Lock-Behandlung, Berichte 
    3# Beginn der Doku-Gruppe 
    7# Quelldatei für Standardwerte des Kern-Pakets 
    8ON_CORE_DEFAULTS_FILE=
"${IPKG_INSTROOT:-}/usr/share/opennet/core.defaults" 
    9# Pfad zur dnsmasq-Server-Datei zur dynamischen Aktualisierung durch Dienste-Erkennung 
   10DNSMASQ_SERVERS_FILE_DEFAULT=
"${IPKG_INSTROOT:-}/var/run/dnsmasq.servers" 
   11# DNS-Suffix, das vorrangig von den via olsrd publizierten Nameservern ausgeliefert werden soll 
   13# Dateiname für erstellte Zusammenfassungen 
   14REPORTS_FILE=
"${IPKG_INSTROOT:-}/tmp/on_report.tar.gz" 
   15# Basis-Verzeichnis für Log-Dateien 
   16LOG_BASE_DIR=
"${IPKG_INSTROOT:-}/var/log" 
   17# maximum length of message lines (logger seems to resctrict lines incl. timestamp to 512 characters) 
   19# Verzeichnis für auszuführende Aktionen 
   20SCHEDULING_DIR=
"${IPKG_INSTROOT:-}/var/run/on-scheduling.d" 
   21# beim ersten Pruefen wird der Debug-Modus ermittelt 
   23# Notfall-DNS-Eintrag, falls wir noch keine nameservice-Nachrichten erhalten haben 
   24# aktuelle UGW-Server, sowie der DNS-Server von FoeBuD (https: 
   25FALLBACK_DNS_SERVERS=
"192.168.0.246 192.168.0.247 192.168.0.248 85.214.20.141" 
   26# fuer Insel-UGWs benoetigen wir immer einen korrekten NTP-Server, sonst schlaegt die mesh-Verbindung fehl 
   27# aktuelle UGW-Server, sowie der openwrt-Pool 
   28FALLBACK_NTP_SERVERS=
"192.168.0.246 192.168.0.247 192.168.0.248 0.openwrt.pool.ntp.org" 
   29LATEST_STABLE_FIRMWARE_BASE_URL=
"https://downloads.opennet-initiative.de/openwrt/stable/latest" 
   30LATEST_STABLE_FIRMWARE_VERSION_INFO_URL=
"$LATEST_STABLE_FIRMWARE_BASE_URL/version.txt" 
   31LATEST_STABLE_FIRMWARE_UPGRADE_MAP_URL=
"$LATEST_STABLE_FIRMWARE_BASE_URL/device-upgrade-map.csv" 
   32CRON_LOCK_FILE=/var/run/on-cron.lock
 
   33CRON_LOCK_MAX_AGE_MINUTES=15
 
   34CRON_LOCK_WAIT_TIMEOUT_SECONDS=30
 
   35OPENNET_API_URL=
"https://api.opennet-initiative.de/api/v1" 
   38# Aufteilung ueberlanger Zeilen 
   40    local line_length=
"$1" 
   41    # ersetze alle whitespace-Zeichen durch Nul 
   42    # Gib anschliessend soviele Token wie moeglich aus, bis die Zeilenlaenge erreicht ist. 
   43    tr 
'\n\t ' '\0' | xargs -0 -s 
"$line_length" echo
 
   48## @param message Debug-Nachricht 
   49## @brief Debug-Meldungen ins syslog schreiben 
   50## @details Die Debug-Nachrichten landen im syslog (siehe ``logread``). 
   51## Falls das aktuelle Log-Level bei ``info`` oder niedriger liegt, wird keine Nachricht ausgegeben. 
   53    # bei der ersten Ausfuehrung dauerhaft speichern 
   54    [ -z 
"$DEBUG_ENABLED" ] && \
 
   55        DEBUG_ENABLED=
$(uci_is_true 
"$(uci_get on-core.settings.debug false)" && echo 1 || echo 0)
 
   56    [ 
"$DEBUG_ENABLED" = 
"0" ] || echo 
"$1" | _split_lines 
"$LOG_MESSAGE_LENGTH" | logger -t 
"$(basename "$0
")[$$]" 
   61## @param message Log-Nachricht 
   62## @brief Informationen und Fehlermeldungen ins syslog schreiben 
   63## @details Die Nachrichten landen im syslog (siehe ``logread``). 
   64## Die info-Nachrichten werden immer ausgegeben, da es kein höheres Log-Level als "debug" gibt.
 
   66    echo 
"$1" | _split_lines 
"$LOG_MESSAGE_LENGTH" | logger -t 
"$(basename "$0
")[$$]" 
   71## @param message Fehlermeldung 
   72## @brief Die Fehlermeldungen werden in die Standard-Fehlerausgabe und ins syslog geschrieben 
   73## @details Jede Meldung wird mit "ERROR" versehen, damit diese Meldungen von
 
   74##   "get_potential_error_messages" erkannt werden.
 
   75## Die error-Nachrichten werden immer ausgegeben, da es kein höheres Log-Level als "debug" gibt.
 
   77    echo 
"$1" | _split_lines 
"$LOG_MESSAGE_LENGTH" | logger -s -t 
"$(basename "$0
")[$$]" "[ERROR] $1" 
   81## @fn append_to_custom_log() 
   82## @param log_name Name des Log-Ziels 
   83## @param event die Kategorie der Meldung (up/down/???) 
   84## @param msg die textuelle Beschreibung des Ereignis (z.B. "connection with ... closed")
 
   85## @brief Hänge eine neue Nachricht an ein spezfisches Protokoll an. 
   86## @details Die Meldungen werden beispielsweise von den konfigurierten openvpn-up/down-Skripten gesendet. 
   93    echo 
"$(date) openvpn [$event]: $msg" >>
"$logfile" 
   94    # Datei kuerzen, falls sie zu gross sein sollte 
   96    filesize=
$(get_filesize 
"$logfile")
 
   97    [ 
"$filesize" -gt 10000 ] && sed -i 
"1,30d" "$logfile" 
  102## @fn get_custom_log_filename() 
  103## @param log_name Name des Log-Ziels 
  104## @brief Liefere den Inhalt eines spezifischen Logs (z.B. das OpenVPN-Verbindungsprotokoll) zurück. 
  105## @returns Zeilenweise Ausgabe der Protokollereignisse (aufsteigend nach Zeitstempel sortiert). 
  108    # der Aufrufer darf sich darauf verlassen, dass er in die Datei schreiben kann 
  109    mkdir -p 
"$LOG_BASE_DIR" 
  110    echo 
"$LOG_BASE_DIR/${log_name}.log" 
  114## @fn get_custom_log_content() 
  115## @param log_name Name des Log-Ziels 
  116## @brief Liefere den Inhalt eines spezifischen Logs (z.B. das OpenVPN-Verbindungsprotokoll) zurück. 
  117## @returns Zeilenweise Ausgabe der Protokollereignisse (aufsteigend nach Zeitstempel sortiert). 
  122    [ -e 
"$logfile" ] || 
return 0
 
  127## @fn update_file_if_changed() 
  128## @param target_filename Name der Zieldatei 
  129## @brief Aktualisiere eine Datei, falls sich ihr Inhalt geändert haben sollte. 
  130## @details Der neue Inhalt der Datei wird auf der Standardeingabe erwartet. 
  131##   Im Falle der Gleichheit von aktuellem Inhalt und zukünftigem Inhalt wird 
  132##   keine Schreiboperation ausgeführt. Der Exitcode gibt an, ob eine Schreiboperation 
  133##   durchgeführt wurde. 
  134## @return exitcode=0 (Erfolg) falls die Datei geändert werden musste 
  135## @return exitcode=1 (Fehler) falls es keine Änderung gab 
  137    local target_filename=
"$1" 
  140    if [ -e 
"$target_filename" ] && echo 
"$content" | cmp -s - 
"$target_filename"; then
 
  141        # the content did not change 
  142        trap 
"" EXIT && 
return 1
 
  146        dirname=
$(dirname 
"$target_filename")
 
  147        [ -
d "$dirname" ] || mkdir -p 
"$dirname" 
  148        echo 
"$content" > 
"$target_filename" 
  154## @fn update_dns_servers() 
  155## @brief Übertrage die Liste der als DNS-Dienst announcierten Server in die dnsmasq-Konfiguration. 
  156## @details Die Liste der DNS-Server wird in die separate dnsmasq-Servers-Datei geschrieben (siehe @sa DNSMASQ_SERVERS_FILE_DEFAULT). 
  157##   Die Server-Datei wird nur bei Änderungen neu geschrieben. Dasselbe gilt für den Neustart des Diensts. 
  158##   Diese Funktion sollte via olsrd-nameservice-Trigger oder via cron-Job ausgeführt werden. 
  160    trap 
'error_trap update_dns_servers "$*"' EXIT
 
  164    # wenn wir eine VPN-Tunnel-Verbindung aufgebaut haben, sollten wir DNS-Anfragen über diese Crypto-Verbindung lenken 
  165    local preferred_servers
 
  167    use_dns=
$(uci_get on-core.settings.use_olsrd_dns)
 
  168    # return if we should not use DNS servers provided via olsrd 
  169    uci_is_false 
"$use_dns" && 
return 0
 
  172    servers_file=
$(uci_get 
"dhcp.@dnsmasq[0].serversfile")
 
  173    # aktiviere die "dnsmasq-serversfile"-Direktive, falls noch nicht vorhanden
 
  174    if [ -z 
"$servers_file" ]; then
 
  175        servers_file=
"$DNSMASQ_SERVERS_FILE_DEFAULT" 
  176        uci set 
"dhcp.@dnsmasq[0].serversfile=$servers_file" 
  177        uci commit 
"dhcp.@dnsmasq[0]" 
  181    # wir sortieren alphabetisch - Naehe ist uns egal
 
  186            [ -n 
"$port" ] && [ 
"$port" != 
"53" ] && host=
"$host#$port" 
  187            # Host nur schreiben, falls kein bevorzugter Host gefunden wurde 
  188            [ -z 
"$preferred_servers" ] && echo 
"server=$host" 
  189            # Die interne Domain soll vorranging von den via olsrd verbreiteten DNS-Servern bedient werden. 
  190            # Dies ist vor allem fuer UGW-Hosts wichtig, die über eine zweite DNS-Quelle (lokaler uplink) 
  192            echo 
"server=/$INTERN_DNS_DOMAIN/$host" 
  194        # eventuell bevorzugte Hosts einfuegen 
  195        for host in $preferred_servers; 
do 
  199    # falls keine DNS-Namen bekannt sind, dann verwende eine (hoffentlich gueltige) Notfall-Option
 
  200    [ -z 
"$server_config" ] && server_config=
$(echo 
"$FALLBACK_DNS_SERVERS" | tr 
' ' '\n' | sed 
's/^/server=/')
 
  202    # es gab eine Aenderung 
  204    # Konfiguration neu einlesen 
  205    service dnsmasq restart 2>/dev/
null || 
true 
  209## @fn update_ntp_servers() 
  210## @brief Übertrage die Liste der als NTP-Dienst announcierten Server in die sysntpd-Konfiguration. 
  211## @details Die Liste der NTP-Server wird in die uci-Konfiguration geschrieben. 
  212##   Die uci-Konfiguration wird nur bei Änderungen neu geschrieben. Dasselbe gilt für den Neustart des Diensts. 
  213##   Diese Funktion sollte via olsrd-nameservice-Trigger oder via cron-Job ausgeführt werden. 
  216    trap 
'error_trap update_ntp_servers "$*"' EXIT
 
  220    local preferred_servers
 
  221    local previous_entries
 
  223    previous_entries=
$(uci_get 
"system.ntp.server")
 
  224    use_ntp=
$(uci_get 
"on-core.settings.use_olsrd_ntp")
 
  225    # return if we should not use NTP servers provided via olsrd 
  226    uci_is_false 
"$use_ntp" && 
return 
  228    # schreibe die Liste der NTP-Server neu
 
  229    # wir sortieren alphabetisch - Naehe ist uns egal
 
  230    if [ -n 
"$preferred_servers" ]; then
 
  231        for host in $preferred_servers; 
do 
  238            [ -n 
"$port" ] && [ 
"$port" != 
"123" ] && host=
"$host:$port" 
  242    # Wir wollen keine leere Liste zurücklassen (z.B. bei einem UGW ohne Mesh-Anbindung). 
  243    # Also alte Werte wiederherstellen, sowie zusaetzlich die default-Server. 
  244    # Vor allem fuer den https-Download der UGW-Server-Liste benoetigen wir eine korrekte Uhrzeit. 
  245    [ -z 
"$(uci_get "system.ntp.server
")" ] && \
 
  246        for host in $previous_entries $FALLBACK_NTP_SERVERS; 
do uci_add_list "system.ntp.server" "$host"; 
done 
  251## @fn add_banner_event() 
  252## @param event Ereignistext 
  253## @param timestamp [optional] Der Zeitstempel-Text kann bei Bedarf vorgegeben werden. 
  254## @brief Füge ein Ereignis zum dauerhaften Ereignisprotokoll (/etc/banner) hinzu. 
  255## @details Ein Zeitstempel, sowie hübsche Formatierung wird automatisch hinzugefügt. 
  257    trap 
'error_trap add_banner_event "$*"' EXIT
 
  259    # verwende den optionalen zweiten Parameter oder den aktuellen Zeitstempel 
  260    local timestamp=
"${2:-}" 
  261    [ -z 
"$timestamp" ] && timestamp=
$(date)
 
  262    local line=
" - $timestamp - $event -" 
  263    # Steht unser Text schon im Banner? Ansonsten hinzufuegen ... 
  264    # bis einschliesslich Version v0.5.0 war "clean_restart_log" das Schluesselwort
 
  265    # ab v0.5.1 verwenden wir "system events" 
  266    if ! grep -qE 
'(clean_restart_log|system events)' /etc/banner; then
 
  267        echo 
" ------------------- system events -------------------" >>/etc/banner
 
  269    # die Zeile auffuellen 
  270    while [ 
"${#line}" -lt 54 ]; 
do line=
"$line-"; 
done 
  271    echo 
"$line" >>/etc/banner
 
  276## @fn update_mesh_interfaces() 
  277## @brief Update mesh interfaces, routing daemons and policy routing 
  278## @details This function should be called whenever the list of interfaces changes. 
  280    update_olsr_interfaces
 
  281    if is_function_available update_olsr2_interfaces; then
 
  282        update_olsr2_interfaces
 
  287## @fn clean_restart_log() 
  288## @brief Alle Log-Einträge aus der banner-Datei entfernen. 
  290    awk 
'{if ($1 != "-") print}' /etc/banner >/tmp/banner
 
  291    mv /tmp/banner /etc/banner
 
  296## @fn _get_file_dict_value() 
  297## @param key das Schlüsselwort 
  298## @brief Auslesen eines Werts aus einem Schlüssel/Wert-Eingabestrom 
  299## @returns Den zum gegebenen Schlüssel gehörenden Wert aus dem Schlüssel/Wert-Eingabestrom 
  300##   Falls kein passender Schlüssel gefunden wurde, dann ist die Ausgabe leer. 
  301## @details Jede Zeile der Standardeingabe enthält einen Feldnamen und einen Wert - beide sind durch 
  302##   ein beliebiges whitespace-Zeichen getrennt. 
  303##   Dieses Dateiformat wird beispielsweise für die Dienst-Zustandsdaten verwendet. 
  304##   Zusätzlich ist diese Funktion auch zum Parsen von openvpn-Konfigurationsdateien geeignet. 
  308## @fn _get_file_dict_keys() 
  309## @brief Liefere alle Schlüssel aus einem Schlüssel/Wert-Eingabestrom. 
  310## @returns Liste aller Schlüssel aus dem Schlüssel/Wert-Eingabestrom. 
  311## @sa _get_file_dict_value 
  312_get_file_dict_keys() { sed 
's/[ \t].*//' "$@" 2>/dev/
null || 
true; }
 
  315## @fn _set_file_dict_value() 
  316## @param field das Schlüsselwort 
  317## @param value der neue Wert 
  318## @brief Ersetzen oder Einfügen eines Werts in einen Schlüssel/Wert-Eingabestrom. 
  319## @sa _get_file_dict_value 
  320_set_file_dict_value() {
 
  321    local status_file=
"$1" 
  324    [ -z 
"$field" ] && 
msg_error "Ignoring empty key for _set_file_dict_value" && 
return 
  325    # Filtere bisherige Zeilen mit dem key heraus. 
  326    # Fuege anschliessend die Zeile mit dem neuen Wert an. 
  327    # Die Sortierung sorgt fuer gute Vergleichbarkeit, um die Anzahl der 
  328    # Schreibvorgaenge (=Wahrscheinlichkeit von gleichzeitigem Zugriff) zu reduzieren. 
  330        grep -v -w -s 
"$field" "$status_file" || 
true 
  331        echo 
"$field $new_value" 
  336## @fn get_on_core_default() 
  337## @param key Name des Schlüssels 
  338## @brief Liefere einen der default-Werte der aktuellen Firmware zurück (Paket on-core). 
  339## @details Die default-Werte werden nicht von der Konfigurationsverwaltung uci verwaltet. 
  340##   Somit sind nach jedem Upgrade imer die neuesten Standard-Werte verfügbar. 
  341get_on_core_default() {
 
  347## @fn get_on_firmware_version() 
  348## @brief Liefere die aktuelle Firmware-Version zurück. 
  349## @returns Die zurückgelieferte Zeichenkette beinhaltet den Versionsstring (z.B. "0.5.0").
 
  350## @details Per Konvention entspricht die Version jedes Firmware-Pakets der Firmware-Version. 
  351##   Um locking-Probleme zu vermeiden, lesen wir den Wert direkt aus der control-Datei des Pakets. 
  352##   Das ist nicht schoen - aber leider ist die lock-Datei nicht konfigurierbar. 
  353get_on_firmware_version() {
 
  354    trap 
'error_trap get_on_firmware_version "$*"' EXIT
 
  355    local status_file=
"${IPKG_INSTROOT:-}/usr/lib/opkg/info/on-core.control" 
  356    [ -e 
"$status_file" ] || 
return 0
 
  357    awk 
'{if (/^Version:/) print $2;}' <
"$status_file" 
  361## @fn get_on_firmware_version_latest_stable() 
  362## @brief Liefere die aktuellste bekannte stabile Firmware-Version zurück. 
  363get_on_firmware_version_latest_stable() {
 
  364    trap 
'error_trap get_on_firmware_version_latest "$*"' EXIT
 
  366    if version=
$(http_request 
"$LATEST_STABLE_FIRMWARE_VERSION_INFO_URL"); then
 
  369        # In den ersten Minuten nach dem Anschalten ist typischerweise noch keine 
  370        # Netzwerkverbindung verfügbar - also melden wir keine Fehler. 
  371        if [ 
"$(get_uptime_minutes)" -gt 10 ]; then
 
  372            msg_error "Failed to retrieve firmware version. Maybe there is no network connection." 
  378get_on_firmware_version_latest_stable_if_outdated() {
 
  379    trap 
'error_trap get_on_firmware_version_latest_stable_if_outdated "$*"' EXIT
 
  380    local latest_stable_version
 
  381    local current_numeric_version
 
  382    local most_recent_version
 
  383    latest_stable_version=
$(get_on_firmware_version_latest_stable)
 
  384    [ -z 
"$latest_stable_version" ] && 
return 0
 
  385    current_numeric_version=
$(get_on_firmware_version | sed 
's/-unstable-/-/')
 
  386    most_recent_version=
$(printf 
'%s\n' "$latest_stable_version" "$current_numeric_version" | sort -V | tail -1)
 
  387    if [ "$most_recent_version" != "$current_numeric_version" ]; then
 
  388        echo "$most_recent_version"
 
  393## @fn get_on_firmware_upgrade_image_url() 
  394## @brief Versuche die URL eines Aktualisierungs-Image zu ermitteln. 
  395## @details Die Ausgabe ist leer, falls keine neuere stabile Version bekannt ist oder falls die 
  396##    Device-Image-Map keinen Eintrag für das Geräte-Modell enthält. 
  397get_on_firmware_upgrade_image_url() {
 
  398    trap 
'error_trap get_on_firmware_upgrade_image_url "$*"' EXIT
 
  400    local device_model_id
 
  401    local upgrade_image_path
 
  402    if upgrade_map=
$(http_request 
"$LATEST_STABLE_FIRMWARE_UPGRADE_MAP_URL"); then
 
  403        device_model_id=
$(get_device_model_id)
 
  404        if [ -n 
"$device_model_id" ]; then
 
  405            upgrade_image_path=
$(echo 
"$upgrade_map" | awk 
'{ if ($1 == "'"$device_model_id"'") print $2; }')
 
  406            if [ -n 
"$upgrade_image_path" ]; then
 
  407                echo 
"$LATEST_STABLE_FIRMWARE_BASE_URL/$upgrade_image_path" 
  414## @fn get_device_model_id() 
  415## @brief Ermittle die OpenWrt-Bezeichnung des Gerätemodells 
  416get_device_model_id() {
 
  417    trap 
'error_trap get_device_model_id "$*"' EXIT
 
  418    # siehe /etc/board.json: model->id 
  420        . /usr/share/libubox/jshn.sh
 
  421        $(jshn -R /etc/board.json)
 
  430## @param on_id die ID des AP - z.B. "1.96" oder "2.54"
 
  431## @param on_ipschema siehe "get_on_core_default on_ipschema" 
  432## @param interface_number 0..X (das WLAN-Interface ist typischerweise Interface #0) 
  433## @attention Manche Aufrufende verlassen sich darauf, dass *on_id_1* und 
  434##   *on_id_2* nach dem Aufruf verfügbar sind (also _nicht_ als "local" 
  435##   Variablen deklariert wurden). 
  438    local on_ipschema=
"$2" 
  439    local interface_number=
"$3" 
  442    # das "on_ipschema" erwartet die Variable "no"
 
  443    # shellcheck disable=SC2034 
  444    local no=
"$interface_number" 
  445    echo 
"$on_id" | grep -q 
'\.' || on_id=1.$on_id
 
  446    # shellcheck disable=SC2034 
  447    on_id_1=
$(echo 
"$on_id" | cut -
d . -f 1)
 
  448    # shellcheck disable=SC2034 
  449    on_id_2=
$(echo 
"$on_id" | cut -
d . -f 2)
 
  450    eval echo 
"$on_ipschema" 
  455## @brief Liefere die aktuell konfigurierte Main-IP zurück. 
  456## @returns Die aktuell konfigurierte Main-IP des AP oder die voreingestellte IP. 
  457## @attention Seiteneffekt: die Variablen "on_id_1" und "on_id_2" sind anschließend verfügbar.
 
  462    on_id=
$(uci_get on-core.settings.on_id 
"$(get_on_core_default on_id_preset)")
 
  463    ipschema=
$(get_on_core_default on_ipschema)
 
  464    get_on_ip "$on_id" "$ipschema" 0
 
  468## @fn run_with_cron_lock() 
  469## @details Führe eine Aktion aus, falls das Lock für Cron-Jobs übernommen werden konnte 
  470## @param command alle Parameter werden als auszuführendes Kommando interpretiert 
  471run_with_cron_lock() {
 
  473    # Der Timeout ist nötig, weil alle cron-Jobs gleichzeitig gestartet werden. Somit treffen 
  474    # der minütige und der fünf-minütige cron-Job aufeinandern und möchten dasselbe Lock 
  475    # halten. Die maximale Wartezeit löst wahrscheinlich die meisten Konflikte. 
  476    if acquire_lock 
"$CRON_LOCK_FILE" "$CRON_LOCK_MAX_AGE_MINUTES" "$CRON_LOCK_WAIT_TIMEOUT_SECONDS"; then
 
  481        rm -f 
"$CRON_LOCK_FILE" 
  489    local max_age_minutes=
"$2" 
  490    # Fehlerfall: die Lock-Datei existiert und ist nicht alt genug 
  491    [ ! -e 
"$lock_file" ] || is_file_timestamp_older_minutes 
"$lock_file" "$max_age_minutes" 
  496## @brief Prüfe ob eine Lock-Datei existiert und nicht veraltet ist. 
  497## @details Die folgenden Zustände werden behandelt: 
  498##    A) die Datei existiert, ist jedoch veraltet -> Erfolg, Zeitstempel der Datei aktualisieren 
  499##    B) die Datei existiert und ist noch nicht veraltet -> Fehlschlag 
  500##    C) die Datei existiert nicht -> Erfolg, Datei wird angelegt 
  501##    Warte notfalls einen Timeout ab, bis das Lock frei wird. 
  502## @returns Erfolg (Lock erhalten) oder Misserfolg (Lock ist bereits vergeben) 
  505    local max_age_minutes=
"$2" 
  508    timeout_limit=
$(( 
$(date +%s) + timeout ))
 
  509    while ! is_lock_available 
"$lock_file" "$max_age_minutes"; 
do 
  510        if [ 
"$(date +%s)" -ge 
"$timeout_limit" ]; then
 
  511            msg_info "Failed to acquire lock file: $lock_file" 
  512            trap 
"" EXIT && 
return 1
 
  514        sleep 
"$(( $(get_random 10) + 1 ))" 
  521# Pruefe ob eine PID-Datei existiert und ob die enthaltene PID zu einem Prozess 
  522# mit dem angegebenen Namen (nur Dateiname - ohne Pfad) verweist. 
  523# Parameter PID-Datei: vollstaendiger Pfad 
  524# Parameter Prozess-Name: Dateiname ohne Pfad 
  526    trap 
'error_trap check_pid_file "$*"' EXIT
 
  528    local process_name=
"$2" 
  530    local current_process
 
  531    if [ -z 
"$pid_file" ] || [ ! -e 
"$pid_file" ]; then trap 
"" EXIT && 
return 1; fi
 
  532    pid=
$(sed 
's/[^0-9]//g' "$pid_file")
 
  533    # leere/kaputte PID-Datei 
  534    [ -z 
"$pid" ] && trap 
"" EXIT && 
return 1
 
  535    # Prozess-Datei ist kein symbolischer Link? 
  536    [ ! -L 
"/proc/$pid/exe" ] && trap 
"" EXIT && 
return 1
 
  537    current_process=
$(readlink 
"/proc/$pid/exe")
 
  538    [ 
"$process_name" != 
"$(basename "$current_process
")" ] && trap 
"" EXIT && 
return 1
 
  543## @fn apply_changes() 
  544## @param configs Einer oder mehrere uci-Sektionsnamen. 
  545## @brief Kombination von uci-commit und anschliessender Inkraftsetzung fuer verschiedene uci-Sektionen. 
  546## @details Dienst-, Netzwerk- und Firewall-Konfigurationen werden bei Bedarf angewandt. 
  547##   Zuerst werden alle uci-Sektionen commited und anschliessend werden die Trigger ausgefuehrt. 
  550    # Zuerst werden alle Änderungen committed und anschließend die (veränderten) Konfiguration 
  551    # für den Aufruf der hook-Skript verwandt. 
  552    for config in 
"$@"; 
do 
  553        # Opennet-Module achten auch auf nicht-uci-Aenderungen 
  554        if echo 
"$config" | grep -q 
"^on-"; then
 
  555            uci -q commit 
"$config" || 
true 
  557        elif [ -z 
"$(uci -q changes "$config
")" ]; then
 
  564    done | grep -v 
"^$" | sort | uniq | 
while read -r config; 
do 
  565        run_parts 
"${IPKG_INSTROOT:-}/usr/lib/opennet/hooks.d" "$config" 
  571# Setzen einer Opennet-ID. 
  573# 2) IPs fuer alle Opennet-Interfaces setzen 
  574# 3) Main-IP in der olsr-Konfiguration setzen 
  575# 4) IP des Interface "free" setzen
 
  577    trap 
'error_trap set_opennet_id "$*"' EXIT
 
  586    # ID normalisieren (AP7 -> AP1.7) 
  587    echo 
"$new_id" | grep -q 
'\.' || new_id=1.$new_id
 
  588    # ON_ID in on-core-Settings setzen 
  589    prepare_on_uci_settings
 
  590    uci set 
"on-core.settings.on_id=$new_id" 
  591    apply_changes on-core
 
  592    # Hostnamen konfigurieren 
  593    find_all_uci_sections system system | 
while read -r uci_prefix; 
do 
  594        uci set 
"${uci_prefix}.hostname=AP-$(echo "$new_id
" | tr . -)" 
  597    # IP-Adressen konfigurieren 
  598    ipschema=
$(get_on_core_default on_ipschema)
 
  599    netmask=
$(get_on_core_default on_netmask)
 
  600    main_ipaddr=
$(get_on_ip 
"$new_id" "$ipschema" 0)
 
  601    for network in 
$(get_sorted_opennet_interfaces); 
do 
  602        uci_prefix=network.$network
 
  603        [ 
"$(uci_get "${uci_prefix}.proto
")" != 
"static" ] && 
continue 
  604        ipaddr=
$(get_on_ip 
"$new_id" "$ipschema" "$if_counter")
 
  605        uci set 
"${uci_prefix}.ipaddr=$ipaddr" 
  606        uci set 
"${uci_prefix}.netmask=$netmask" 
  607        if_counter=
$((if_counter + 1))
 
  609    # OLSR-MainIP konfigurieren 
  610    olsr_set_main_ip 
"$main_ipaddr" 
  611    apply_changes olsrd network
 
  615# Durchsuche eine Schluessel-Wert-Liste nach einem Schluessel und liefere den dazugehoerigen Wert zurueck. 
  618# Der Separator ist konfigurierbar. 
  619# Die Liste wird auf der Standardeingabe erwartet. 
  620# Der erste und einzige Parameter ist der gewuenschte Schluessel. 
  621get_from_key_value_list() {
 
  622    local search_key=
"$1" 
  626    { sed 
's/[ \t]\+/\n/g'; echo; } | 
while read -r key_value; 
do 
  627        key=
$(echo 
"$key_value" | cut -f 1 -
d "$separator")
 
  628        [ 
"$key" = 
"$search_key" ] && echo 
"$key_value" | cut -f 2- -
d "$separator" && 
break 
  635## @fn replace_in_key_value_list() 
  636## @param search_key der Name des Schlüsselworts 
  637## @param separator der Name des Trennzeichens zwischen Wert und Schlüssel 
  638## @brief Ermittle aus einer mit Tabulatoren oder Leerzeichen getrennten Liste von Schlüssel-Wert-Paaren den Inhalt des Werts zu einem Schlüssel. 
  639## @returns die korrigierte Schlüssel-Wert-Liste wird ausgegeben (eventuell mit veränderten Leerzeichen oder Tabulatoren) 
  640replace_in_key_value_list() {
 
  641    local search_key=
"$1" 
  644    awk 
'BEGIN { found=0; FS="'"$separator"'"; OFS=":"; RS="[ \t]"; ORS=" "; } 
  645        { if ($1 == "'"$search_key"'") { print "'"$search_key"'", '"$value"'; found=1; } else { print $0; } } 
  646        END { if (found == 0) print "'"$search_key"'", '"$value"' };' 
  650# Wandle einen uebergebenene Parameter in eine Zeichenkette um, die sicher als Dateiname verwendet werden kann 
  652    echo 
"$1" | sed 
's/[^a-zA-Z0-9._\-]/_/g' 
  656## @fn get_uptime_minutes() 
  657## @brief Ermittle die seit dem Systemstart vergangene Zeit in Minuten 
  658## @details Diese Zeit ist naturgemäß nicht für die Speicherung an Orten geeignet, die einen reboot überleben. 
  659get_uptime_minutes() {
 
  660    awk 
'{print int($1/60)}' /proc/uptime
 
  664## @fn is_file_timestamp_older_minutes() 
  665## @brief Prüfe ob die Datei älter ist als die angegebene Zahl von Minuten. 
  666## @details Alle Fehlerfälle (Datei existiert nicht, Zeitstempel liegt in der Zukunft, ...) werden 
  667##   als "veraltet" gewertet.
 
  668## @returns True, falls die Datei existiert und älter als angegeben ist - ansonsten "False" 
  669is_file_timestamp_older_minutes() {
 
  670    trap 
'error_trap is_file_timestamp_older_minutes "$*"' EXIT
 
  672    local limit_minutes=
"$2" 
  673    [ -e 
"$filename" ] || 
return 0
 
  676    file_timestamp=
$(date --reference 
"$filename" +%s 2>/dev/
null | awk 
'{ print int($1/60) }')
 
  677    # Falls die Datei zwischendurch geloescht wurde, ist das Lock nun frei. 
  678    [ -z 
"$file_timestamp" ] && 
return 0
 
  679    timestamp_now=
$(date +%s | awk 
'{ print int($1/60) }')
 
  682    #   * Zeitstempel in der Zukunft 
  683    #   * Zeitstempel älter als erlaubt 
  684    if [ -z 
"$file_timestamp" ] \
 
  685            || [ 
"$file_timestamp" -gt 
"$timestamp_now" ] \
 
  686            || [ 
"$((file_timestamp + limit_minutes))" -lt 
"$timestamp_now" ]; then
 
  689        trap 
"" EXIT && 
return 1
 
  694## @fn is_timestamp_older_minutes() 
  695## @param timestamp_minute der zu prüfende Zeitstempel (in Minuten seit dem Systemstart) 
  696## @param difference zulässige Zeitdifferenz zwischen jetzt und dem Zeitstempel 
  697## @brief Prüfe, ob ein gegebener Zeitstempel älter ist, als die vorgegebene Zeitdifferenz. 
  698## @returns Exitcode Null (Erfolg), falls der gegebene Zeitstempel mindestens 'difference' Minuten zurückliegt. 
  699# Achtung: Zeitstempel aus der Zukunft oder leere Zeitstempel gelten immer als veraltet. 
  700is_timestamp_older_minutes() {
 
  701    local timestamp_minute=
"$1" 
  702    local difference=
"$2" 
  703    [ -z 
"$timestamp_minute" ] && 
return 0
 
  705    now=
"$(get_uptime_minutes)" 
  707    [ 
"$now" -ge 
"$((timestamp_minute + difference))" ] && 
return 0
 
  708    # timestamp in future -> invalid -> let's claim it is too old 
  709    [ 
"$now" -lt 
"$timestamp_minute" ] && \
 
  710        msg_info 
"WARNING: Timestamp from future found: $timestamp_minute (minutes since epoch)" && \
 
  712    trap 
"" EXIT && 
return 1
 
  716## @fn get_uptime_seconds() 
  717## @brief Ermittle die Anzahl der Sekunden seit dem letzten Bootvorgang. 
  718get_uptime_seconds() {
 
  719    cut -f 1 -
d . /proc/uptime
 
  723## @fn run_delayed_in_background() 
  724## @param delay Verzögerung in Sekunden 
  725## @param command alle weiteren Token werden als Kommando und Parameter interpretiert und mit Verzögerung ausgeführt. 
  726## @brief Führe eine Aktion verzögert im Hintergrund aus. 
  727run_delayed_in_background() {
 
  730    (sleep 
"$delay" && 
"$@") </dev/
null >/dev/
null 2>&1 &
 
  735## @brief Ermittle die Größe einer Datei in Bytes. 
  736## @param filename Name der zu untersuchenden Datei. 
  739    wc -c 
"$filename" | awk 
'{ print $1 }' 
  744# Der Name der erzeugten tar-Datei wird als Ergebnis ausgegeben. 
  746    trap 
'error_trap generate_report "$*"' EXIT
 
  752    temp_dir=
$(mktemp -
d)
 
  753    reports_dir=
"$temp_dir/report" 
  756    # die Skripte duerfen davon ausgehen, dass wir uns im Zielverzeichnis befinden 
  757    mkdir -p 
"$reports_dir" 
  759    find /usr/lib/opennet/reports -type f | sort | 
while read -r fname; 
do 
  760        [ ! -x 
"$fname" ] && 
msg_info "skipping non-executable report script: $fname" && 
continue 
  761        "$fname" || 
msg_error "reports script failed: $fname" 
  763    # "tar" unterstuetzt "-c" nicht - also komprimieren wir separat
 
  764    tar cC 
"$temp_dir" "report" | gzip >
"$tar_file" 
  766    mv 
"$tar_file" "$REPORTS_FILE" 
  770## @fn get_potential_error_messages() 
  771## @param max_lines die Angabe einer maximalen Anzahl von Zeilen ist optional - andernfalls werden alle Meldungen ausgegeben 
  772## @brief Filtere aus allen zugänglichen Quellen mögliche Fehlermeldungen. 
  773## @details Falls diese Funktion ein nicht-leeres Ergebnis zurückliefert, kann dies als Hinweis für den 
  774##   Nutzer verwendet werden, auf dass er einen Fehlerbericht einreicht. 
  775get_potential_error_messages() {
 
  776    local max_lines=
"${1:-}" 
  778    # 1) get_service_as_csv 
  779    #    Wir ignorieren "get_service_as_csv"-Meldungen - diese werden durch asynchrone Anfragen des
 
  780    #    Web-Interface ausgeloest, die beim vorzeitigen Abbruch des Seiten-Lade-Vorgangs mit 
  781    #    einem Fehler enden. 
  782    filters=
"${filters}|trapped.*get_service_as_csv" 
  783    # 2) openvpn.*Error opening configuration file 
  784    #    Beim Booten des Systems wurde die openvpn-Config-Datei, die via uci referenziert ist, noch 
  785    #    nicht erzeugt. Beim naechsten cron-Lauf wird dieses Problem behoben. 
  786    filters=
"${filters}|openvpn.*Error opening configuration file" 
  787    # 3) openvpn(...)[...]: Exiting due to fatal error 
  788    #    Das Verzeichnis /var/etc/openvpn/ existiert beim Booten noch nicht. 
  789    filters=
"${filters}|openvpn.*Exiting due to fatal error" 
  790    # 4) openvpn(...)[...]: SIGUSR1[soft,tls-error] received, process restarting 
  791    #    Diese Meldung taucht bei einem Verbindungsabbruch auf. Dieses Ereignis ist nicht 
  792    #    ungewoehnlich und wird mittels des Verbindungsprotokolls bereits hinreichend gewuerdigt 
  793    filters=
"${filters}|openvpn.*soft,tls-error" 
  794    # 5) openvpn(...)[...]: TLS Error: TLS handshake failed 
  795    #    Diese Meldung deutet einen fehlgeschlagenen Verbindungsversuch an. Dies ist nicht 
  796    #    ungewoehnlich (beispielsweise auch fuer Verbindungstests). 
  797    filters=
"${filters}|openvpn.*TLS Error" 
  798    # 6) olsrd: /etc/rc.d/S65olsrd: startup-error: check via: '/usr/sbin/olsrd -f "/var/etc/olsrd.conf" -nofork'
 
  799    #    Falls noch kein Interface vorhanden ist (z.B. als wifi-Client), dann taucht diese Meldung 
  801    filters=
"${filters}|olsrd.*startup-error" 
  803    #    Beim Booten tauchen Fehlermeldungen aufgrund nicht konfigurierter Netzwerk-Interfaces auf. 
  804    #    TODO: ucarp nur noch als nachinstallierbares Paket markieren (erfordert Aenderung der Makefile-Erzeugung) 
  805    filters=
"${filters}|ucarp" 
  806    # 8) olsrd: /etc/rc.d/S65olsrd: ERROR: there is already an IPv4 instance of olsrd running (pid: '1099'), not starting. 
  807    #    Dieser Fehler tritt auf, wenn der olsrd_check einen olsrd-Neustart ausloest, obwohl er schon laeuft. 
  808    filters=
"${filters}|olsrd: ERROR: there is already an IPv4 instance of olsrd running" 
  809    # 9) openvpn(...)[...]: Authenticate/Decrypt packet error 
  810    #    Paketverschiebungen nach dem Verbindungsaufbau - anscheinend unproblematisch. 
  811    filters=
"${filters}|openvpn.*Authenticate/Decrypt packet error" 
  812    # 10) olsrd: ... olsrd_setup_smartgw_rules() Warning: kmod-ipip is missing. 
  813    #    olsrd gibt beim Starten generell diese Warnung aus. Wir koennen sie ignorieren. 
  814    filters=
"${filters}|olsrd.*olsrd_setup_smartgw_rules" 
  815    # 11) olsrd: ... olsrd_write_interface() Warning: Interface '...' not found, skipped 
  816    #    Falls das wlan-Interface beim Bootvorgang noch nicht aktiv ist, wenn olsrd startet, dann erscheint diese 
  818    filters=
"${filters}|olsrd.*Interface.*not found" 
  819    # 12) dropbear[...]: Exit (root): Error reading: Connection reset by peer 
  820    #    Verbindungsverlust einer ssh-Verbindung. Dies darf passieren. 
  821    filters=
"${filters}|dropbear.*Connection reset by peer" 
  822    # 13) cron-error: nc.*: short write 
  823    #    Falls die Routen via nc während eines olsrd-Neustarts ausgelesen werden, reisst eventuell die Socket- 
  824    #    Verbindung ab - dies ist akzeptabel. 
  825    filters=
"${filters}|nc: short write" 
  826    # 14) openvpn(___service_name___)[...]: write UDPv4: Network is unreachable 
  827    #    Beispielsweise bei einem olsrd-Neustart reisst die Verbindung zum UGW-Server kurz ab. 
  828    filters=
"${filters}|openvpn.*Network is unreachable" 
  829    # 15) wget: can't connect to remote host 
  830    #    Eine frühe Geschwindigkeitsmessung (kurz nach dem Booten) darf fehlschlagen. 
  831    filters=
"${filters}|wget: can.t connect to remote host" 
  832    # 16) openvpn(...)[...]: Options error: Unrecognized option or missing parameter(s) in [PUSH-OPTIONS]:11: explicit-exit-notify (2.3.6) 
  833    #    OpenVPN-Versionen, die ohne die "--extras"-Option gebaut wurden, unterstuetzen keine exit-Notification.
 
  834    #    Dies ist unproblematisch - es ist eher eine Sache der Höflichkeit.. 
  835    filters=
"${filters}|openvpn.*Options error.*explicit-exit-notify" 
  836    # 17) ddns-scripts[...]: myddns_ipv4: ... 
  837    #    ddns meldet leidet beim Starten einen Fehler, solange es unkonfiguriert ist. 
  838    filters=
"${filters}|ddns-scripts.*myddns_ipv[46]" 
  839    # 18) Collected errors: 
  840    #    opkg-Paketinstallationen via Web-Interface erzeugen gelegentlich Fehlermeldungen (z.B. Entfernung 
  841    #    abhängiger Pakete), die dem Nutzer im Web-Interface angezeigt werden. Diese Fehlermeldungen landen 
  842    #    zusätzlich auch im log-Buffer. Da der Nutzer sie bereits gesehen haben dürfte, können wir sie ignorieren 
  843    #    (zumal die konkreten Fehlermeldungen erst in den folgenden Zeilen zu finden und somit schlecht zu filtern 
  845    filters=
"${filters}|Collected errors:" 
  846    # 19) uhttpd[...]: sh: write error: Broken pipe 
  847    #    http-Requests die von seiten des Browser abgebrochen wurden 
  848    filters=
"${filters}|uhttpd.*: sh: write error: Broken pipe" 
  849    # 20) __main__ get_variable ... 
  850    #    Der obige "Broken pipe"-Fehler unterbricht dabei auch die akuell laufende Funktion - dies ist
 
  851    #    sehr häufig die Variablen-Auslesung (seltsamerweise). 
  852    filters=
"${filters}|__main__ get_variable " 
  853    # 21) ERROR: Linux route add command failed 
  854    #    Beim Aufbau er OpenVPN-Verbindung scheint gelegentlich noch eine alte Route verblieben zu sein. 
  855    #    Diese Meldung ist wohl irrelevant. 
  856    filters=
"${filters}|ERROR: Linux route add command failed" 
  857    # 22) ... cannot open proc entry /proc/sys/net/ipv4/conf/none/ ... 
  858    #    olsrd2 versucht auf /proc/-Eintraege zuzugreifen, bevor der Name des Netzwerk-Interface 
  859    #    feststeht ("none"). Ignorieren.
 
  860    filters=
"${filters}|cannot open proc entry /proc/sys/net/ipv4/conf/none/" 
  861    # 23) RTNETLINK answers: Network is unreachable 
  862    #    bei einem OpenVPN-Verbindungsaufbau gehen die ersten Pakete verloren 
  863    filters=
"${filters}|RTNETLINK answers: Network is unreachable" 
  864    # 24) olsrd2: wrote '/var/run/olsrd2_dev' 
  865    #    beim OLSRD2-Start wird diese Meldung auf stderr ausgegeben 
  866    filters=
"${filters}|olsrd2: wrote .*olsrd2_dev'" 
  867    # 25) nl80211 not found 
  868    #    Während der initialen wireless-Konfigurationsermittlung beim ersten Boot-Vorgang wird 
  869    #    "iw" aufgerufen, auch wenn eventuell kein wifi-Interface vorhanden ist. In diesem Fall
 
  870    #    wird der obige Hinweis ausgegeben. 
  871    filters=
"${filters}|nl80211 not found" 
  872    # 26) OLSRd2[...]: WARN(os_interface) ...: Error, cannot open proc entry /proc/sys/net/ipv4/conf/on_wifi_1/... No such file or directory 
  873    #    olsrd2 versucht auf /proc/-Eintraege mittels des Namens eines logischen 
  874    #    Netzwerk-Interface (z.B. "on_eth_0") zuzugreifen, obwohl das System nur die physischen
 
  876    filters=
"${filters}|cannot open proc entry /proc/sys/net/ipv4/conf/on_" 
  877    # 27) OLSRd2[...]: WARN(os_interface) ...: WARNING! Could not disable the IP spoof filter 
  878    #    Im Anschluss an den obigen (26) Fehlversuch, fuer ein logisches Netzwerk-Interface den 
  879    #    rp_filter zu deaktivieren, wird diese Warnung ausgegeben. Sie ist nicht relevant. 
  880    filters=
"${filters}"'|WARN\(os_interface\).*Could not disable (the IP spoof filter|ICMP redirects)' 
  881    # 28) MediaTek Nand driver init, version v2.1 Fix AHB virt2phys error 
  882    #    Im Boot-Log des EdgeRouter (ERX) taucht diese informative Meldung auf, die leider das 
  883    #    Wort "error" enthält.
 
  884    filters=
"${filters}"'|MediaTek Nand driver init, version v.* Fix AHB virt2phys error' 
  885    # 29) ath10k_pci 0000:00:00.0: Direct firmware load for ath10k/pre-cal-pci-0000:00:00.0.bin failed with error -2 
  886    #    Die Nanostation AC loco schreibt folgende Fehlermeldung ins Kernel-Log. 
  887    #    Das WLAN-Interface funktioniert jedoch scheinbar problemlos. 
  888    filters=
"${filters}"'|ath10k_pci 0000:00:00.0: Direct firmware load for ath10k/pre-cal-pci-0000:00:00.0.bin failed with error -2' 
  889    # 30) kern.err kernel: [..] print_req_error: I/O error, dev loop0, sector 490 
  890    #    Beim Booten eines APU-Geräts tritt wohl die obige Fehlermeldung auf. Sie ist 
  891    #    sicherlich irrelevant. Siehe https: 
  892    filters=
"${filters}"'|kern.err kernel:.*print_req_error: I/O error, dev loop0, sector ' 
  893    # System-Fehlermeldungen (inkl. "trapped")
 
  894    # Frühzeitig Broken-Pipe-Fehler ("uhttpd[...]: sh: write error: Broken pipe") sowie die darauffolgende
 
  895    # Zeile entfernen. Diese Fehler treten auf, wenn der Nutzer das Laden der Webseite unterbricht (z.B. 
  896    # durch frühe Auswahl einer neuen URL). 
  897    prefilter=
"uhttpd.*: sh: write error: Broken pipe" 
  898    # "sed /FOO/{N;d;}" löscht die Muster-Zeile, sowie die direkt nachfolgende
 
  899    logread | sed 
"/$prefilter/{N;d;}" | grep -iE 
"(error|crash)" | grep -vE 
"(${filters#|})" | 
if [ -z 
"$max_lines" ]; then
 
  900        # alle Einträge ausgeben 
  903        # nur die letzten Einträge ausliefern 
  909# Ersetze eine Zeile durch einen neuen Inhalt. Falls das Zeilenmuster nicht vorhanden ist, wird eine neue Zeile eingefuegt. 
  910# Dies entspricht der Funktionalitaet des "lineinfile"-Moduls von ansible.
 
  911# Parameter filename: der Dateiname 
  912# Parameter pattern: Suchmuster der zu ersetzenden Zeile 
  913# Parameter new_line: neue Zeile 
  915    trap 
'error_trap line_in_file "$*"' EXIT
 
  920    # Datei existiert nicht? Einfach mit dieser Zeile erzeugen. 
  921    [ ! -e 
"$filename" ] && echo 
"$new_line" >
"$filename" && 
return 0
 
  922    # Datei einlesen - zum Muster passende Zeilen austauschen - notfalls neue Zeile anfuegen 
  924        while read -r line; 
do 
  925            if echo 
"$line" | grep -q 
"$pattern"; then
 
  926                [ -n 
"$new_line" ] && echo 
"$new_line" 
  927                # die Zeile nicht erneut schreiben - alle zukuenftigen Vorkommen loeschen 
  933        # die neue Zeile hinzufuegen, falls das Muster in der alten Datei nicht vorhanden war 
  934        grep -q 
"$pattern" "$filename" || echo 
"$new_line" 
  939# Pruefe, ob eine Liste ein bestimmtes Element enthaelt 
  940# Die Listenelemente sind durch beliebigen Whitespace getrennt. 
  945    for token in $list; 
do 
  946        [ 
"$token" = 
"$target" ] && 
return 0
 
  949    # kein passendes Token gefunden 
  950    trap 
"" EXIT && 
return 1
 
  954# Liefere den Inhalt einer Variable zurueck. 
  955# Dies ist beispielsweise fuer lua-Skripte nuetzlich, da diese nicht den shell-Namensraum teilen. 
  956# Paramter: Name der Variable 
  959    eval 
"echo \"\$$var_name\"" 
  963# Pruefe, ob die angegebene Funktion definiert ist. 
  964# Dies ersetzt opkg-basierte Pruefungen auf installierte opennet-Firmware-Pakete. 
  965is_function_available() {
 
  967    # "ash" liefert leider nicht den korrekten Wert "function" nach einem Aufruf von "type -t".
 
  968    # Also verwenden wir die Textausgabe von "type".
 
  969    # Die Fehlerausgabe von type wird ignoriert - im Falle der bash gibt es sonst unnoetige Ausgaben. 
  970    type 
"$func_name" 2>/dev/
null | grep -q 
"function$" && 
return 0
 
  971    trap 
"" EXIT && 
return 1
 
  976## @brief Liefere eine Zufallszahl innerhalb des gegebenen Bereichs. 
  977## @returns Eine zufällige Ganzzahl. 
  981    # Setze eine "1" vor eine zufällige Anzahl von Ziffern (vermeide Oktal-Zahl-Behandlung).
 
  982    # Begrenze die Anzahl von Ziffern, um Rundungen in awk zu vermeiden. 
  983    random_number=
"1$(dd if=/dev/urandom bs=10 count=1 2>/dev/null | md5sum | tr -dc "0123456789
" | cut -c 1-6)" 
  984    printf 
"%d %d" "$range" "$random_number" | awk 
'{print $2 % $1; }' 
  988## @fn get_local_bias_numer() 
  989## @brief Ermittle eine lokale einzigartige Zahl, die als dauerhaft unveränderlich angenommen werden kann. 
  990## @returns Eine (initial zufällig ermittelte) Zahl zwischen 0 und 10^8-1, die unveränderlich zu diesem AP gehört.  
  991## @details Für ein paar gleichrangige Sortierungen (z.B. verwendete 
  992##   UGW-Gegenstellen) benötigen wir ein lokales Salz, um strukturelle 
  993##   Bevorzugungen zu vermeiden. 
  994get_local_bias_number() {
 
  995    trap 
'error_trap get_local_bias_number "$*"' EXIT
 
  997    bias=
$(uci_get on-core.settings.local_bias_number)
 
  998    # der Bias-Wert ist schon vorhanden - wir liefern ihn aus 
  999    if [ -z 
"$bias" ]; then
 
 1000        # wir müssen einen Bias-Wert erzeugen: beliebige gehashte Inhalte ergeben eine akzeptable Zufallszahl 
 1001        bias=
$(get_random 100000000)
 
 1002        uci set 
"on-core.settings.local_bias_number=$bias" 
 1005    echo -n 
"$bias" && 
return 0
 
 1009## @fn system_service_check() 
 1010## @brief Prüfe ob ein Dienst läuft und ob seine PID-Datei aktuell ist. 
 1011## @param executable Der vollständige Pfad zu dem auszuführenden Programm. 
 1012## @param pid_file Der Name einer PID-Datei, die von diesem Prozess verwaltet wird. 
 1013## @deteils Dabei wird die 'service_check'-Funktion aus der openwrt-Shell-Bibliothek genutzt. 
 1014system_service_check() {
 
 1015    local executable=
"$1" 
 1018    # shellcheck disable=SC2034 source=openwrt/package/base-files/files/lib/functions/network.sh 
 1019    result=
$(set +eu; . /lib/functions/service.sh; SERVICE_PID_FILE=
"$pid_file"; service_check 
"$executable" && echo 
"ok"; set -eu)
 
 1020    [ -n 
"$result" ] && 
return 0
 
 1021    trap 
"" EXIT && 
return 1
 
 1025## @fn get_memory_size() 
 1026## @brief Ermittle die Größe des Arbeitsspeichers in Megabyte. 
 1027## @returns Der Rückgabewert (in Megabyte) ist etwas kleiner als der physische Arbeitsspeicher (z.B. 126 statt 128 MB). 
 1030    memsize_kb=
$(grep 
"^MemTotal:" /proc/meminfo | sed 
's/[^0-9]//g')
 
 1031    echo 
$((memsize_kb / 1024))
 
 1035# Liefere alle Dateien in einem Verzeichnis zurück, die entsprechend der "run-parts"-Funktionalität
 
 1036# beachtet werden sollten. 
 1037_get_parts_dir_files() {
 
 1038    local parts_dir=
"$1" 
 1040    # Abbruch, falls es das Verzeichnis nicht gibt 
 1041    [ -e 
"$parts_dir" ] || 
return 0
 
 1042    # ignoriere Dateinamen mit ungueltigen Zeichen (siehe 'man run-parts') 
 1043    find 
"$parts_dir" -maxdepth 1 | grep 
'/[a-zA-Z0-9_-]\+$' | sort | 
while read -r fname; 
do 
 1044        # ignoriere verwaiste symlinks 
 1045        [ -f 
"$fname" ] || 
continue 
 1046        # ignoriere Dateien ohne Ausführungsrechte 
 1047        [ -x 
"$fname" ] || 
continue 
 1054## @brief Führe alle Skripte aus, die in einem bestimmten Verzeichnis liegen und gewissen Konventionen genügen. 
 1055## @param rundir Verzeichnis, das die auszuführenden Skripte enthält 
 1056## @param weitere Paramter (falls erforderlich) 
 1057## @details Die Namenskonventionen und das Verhalten entspricht dem verbreiteten 'run-parts'-Werkzeug. 
 1058##     Die Dateien müssen ausführbar sein. 
 1060    trap 
'error_trap run_parts "$*"' EXIT
 
 1064    _get_parts_dir_files 
"$rundir" | 
while read -r fname; 
do 
 1065        msg_debug "on-run-parts: executing $fname" 
 1066        # ignoriere Fehler bei der Ausfuehrung 
 1067        "$fname" "$@" || 
true 
 1072## @fn schedule_parts() 
 1073## @brief Plant die Ausführung aller Skripte, die in einem bestimmten Verzeichnis liegen und gewissen Konventionen genügen. 
 1074## @param rundir Verzeichnis, das die auszuführenden Skripte enthält 
 1075## @param suffix optionaler Suffix wird ungefiltert an jeden auszufühenden Dateinamen gehängt (z.B. '2>&1 | logger -t cron-error') 
 1076## @details Die Namenskonventionen und das Verhalten entspricht dem verbreiteten 'run-parts'-Werkzeug. 
 1077##     Die Dateien müssen ausführbar sein. 
 1079    trap 
'error_trap schedule_parts "$*"' EXIT
 
 1081    local suffix=
"${2:-}" 
 1082    _get_parts_dir_files 
"$rundir" | 
while read -r fname; 
do 
 1083        if [ -n 
"$suffix" ]; then
 
 1084            echo 
"$fname $suffix" 
 1092## @fn run_scheduled_tasks() 
 1093## @brief Führe die zwischenzeitlich für die spätere Ausführung vorgemerkten Aufgaben aus. 
 1094## @details Unabhängig vom Ausführungsergebnis wird das Skript anschließend gelöscht. 
 1095run_scheduled_tasks() {
 
 1096    trap 
'error_trap run_scheduled_tasks "$*"' EXIT
 
 1100    [ -
d "$SCHEDULING_DIR" ] || 
return 0
 
 1101    # keine Ausführung, falls noch mindestens ein alter Task aktiv ist 
 1102    running_tasks=
$(find 
"$SCHEDULING_DIR" -type f -name 
"*.running" | 
while read -r fname; 
do 
 1103        # veraltete Dateien werden geloescht und ignoriert 
 1104        # wir müssen uns an dem langsamsten Cron-Job orientieren: 
 1105        #   - MTU-Test für UGWs: ca. 5 Minuten 
 1106        #   - update_olsr_services: mehr als 5 Minuten 
 1107        is_file_timestamp_older_minutes 
"$fname" 30 && rm -f 
"$fname" && 
continue 
 1108        # nicht-veraltete Dateien fuehren zum Abbruch der Funktion 
 1109        msg_info "Skipping 'run_scheduled_task' due to an ongoing operation: $(tail -1 "$fname
")" 
 1112    [ -n 
"$running_tasks" ] && 
return 0
 
 1113    # die ältesten Dateien zuerst ausführen
 
 1114    find 
"$SCHEDULING_DIR" -type f | grep -v 
'\.running$' | xargs -r ls -tr | 
while read -r fname; 
do 
 1115        temp_fname=
"${fname}.running" 
 1116        # zuerst schnell wegbewegen, damit wir keine Ereignisse verpassen 
 1117        # Im Fehlerfall (eine race condition) einfach beim naechsten Eintrag weitermachen. 
 1118        mv 
"$fname" "$temp_fname" 2>/dev/
null || 
continue 
 1119        { /bin/sh 
"$temp_fname" | logger -t 
"on-scheduled"; } 2>&1 | logger -t 
"on-scheduled-error ($fname)" 
 1125## @fn schedule_task() 
 1126## @brief Erzeuge ein Start-Skript für die baldige Ausführung einer Aktion. 
 1127## @details Diese Methode sollte für Aufgaben verwendet werden, die nicht unmittelbar ausgeführt 
 1128##   werden müssen und im Zweifelsfall nicht parallel ablaufen sollen (ressourcenschonend). 
 1130    trap 
'error_trap schedule_task "$*"' EXIT
 
 1131    local script_content
 
 1133    script_content=
$(cat -)
 
 1134    # wir sorgen fuer die Wiederverwendung des Dateinamens, um doppelte Ausführungen zu verhindern 
 1135    unique_key=
$(echo 
"$script_content" | md5sum | awk 
'{ print $1 }')
 
 1136    local target_file=
"$SCHEDULING_DIR/$unique_key" 
 1137    # das Skript existiert? Nichts zu tun ... 
 1138    [ -e 
"$target_file" ] && 
return 0
 
 1139    mkdir -p 
"$SCHEDULING_DIR" 
 1140    echo 
"$script_content" >
"$target_file" 
 1144## @fn schedule_parts() 
 1145## @brief Merke alle Skripte in einem Verzeichnis für die spätere Ausführung via 'run_scheduled_tasks' vor. 
 1146## @details Die Namenskonventionen und das Verhalten entspricht dem verbreiteten 'run-parts'-Werkzeug. 
 1147##     Die Dateien müssen ausführbar sein. 
 1149    trap 
'error_trap schedule_parts "$*"' EXIT
 
 1150    local schedule_dir=
"$1" 
 1152    _get_parts_dir_files 
"$schedule_dir" | 
while read -r fname; 
do 
 1153        msg_debug "on-schedule-parts: scheduling $fname" 
 1154        # ignoriere Fehler bei der Ausfuehrung 
 1155        echo 
"$fname" | schedule_task
 
 1160## @fn read_data_bytes() 
 1161## @brief Bytes von einem Blockdevice lesen 
 1162## @param source das Quell-Blockdevice (oder die Datei) 
 1163## @param size die Anzahl der zu uebertragenden Bytes 
 1164## @param transfer_blocksize die Blockgroesse bei der Uebertragung (Standard: 65536) 
 1165## @details Die verwendete Uebertragung in grossen Bloecken ist wesentlich schneller als das byteweise EinlesenaKopie.sh_backup 
 1166##   Der abschliessende unvollstaendige Block wird byteweise eingelesen. 
 1169    local transfer_blocksize=
"${2:-65536}" 
 1170    # "conv=sync" ist fuer die "yes"-Quelle erforderlich - sonst fehlt gelegentlich der letzte Block.
 
 1171    # Es scheint sich dazu bei um eine race-condition zu handeln. 
 1172    dd 
"bs=$transfer_blocksize" "count=$((size / transfer_blocksize))" conv=sync 2>/dev/
null 
 1173    [ 
"$((size % transfer_blocksize))" -ne 0 ] && dd bs=1 
"count=$((size % transfer_blocksize))" 2>/dev/
null 
 1178## @fn get_flash_backup() 
 1179## @brief Erzeuge einen rohen Dump des Flash-Speichers. Dieser ermöglicht den Austausch des Flash-Speichers. 
 1180## @param include_private Kopiere neben den nur-Lese-Bereichen auch die aktuelle Konfiguration inkl. eventueller privater Daten. 
 1181## @details Alle mtd-Partition bis auf den Kernel und die Firmware werden einzeln kopiert und dann komprimiert. 
 1182##   Beispiel-Layout einer Ubiquiti Nanostation: 
 1183##     dev:    size   erasesize  name 
 1184##     mtd0: 00040000 00010000 "u-boot" 
 1185##     mtd1: 00010000 00010000 "u-boot-env" 
 1186##     mtd2: 00760000 00010000 "firmware" 
 1187##     mtd3: 00102625 00010000 "kernel" 
 1188##     mtd4: 0065d9db 00010000 "rootfs" 
 1189##     mtd5: 00230000 00010000 "rootfs_data" 
 1190##     mtd6: 00040000 00010000 "cfg" 
 1191##     mtd7: 00010000 00010000 "EEPROM" 
 1192##   Dabei ignorieren wir bei Bedarf "rootfs_data" (beschreibbarer Bereich der Firmware). 
 
 1194    trap 
'error_trap get_flash_backup "$*"' EXIT
 
 1195    local include_private=
"${1:-}" 
 1200    # shellcheck disable=SC2034 
 1201    grep 
'^mtd[0-9]\+:' /proc/mtd | 
while read -r name size blocksize label; 
do 
 1202        # abschliessenden Doppelpunkt entfernen 
 1204        # hexadezimal-Zahl umrechnen 
 1205        size=
$(echo | awk 
"{print 0x$size }")
 
 1206        # Anfuehrungszeichen entfernen 
 1207        label=
$(echo 
"$label" | cut -f 2 -
d '"')
 
 1208        # Firmware-Partitionen ueberspringen 
 1209        if [ 
"$label" = 
"rootfs" ]; then
 
 1210            local rootfs_device=
"/dev/$name" 
 1211            local rootfs_full_size=
"$size" 
 1212        elif [ 
"$label" = 
"rootfs_data" ]; then
 
 1213            # schreibe das komplette rootfs _ohne_ das aktuelle rootfs_data 
 1214            echo >&2 
"Read: root-RO $((rootfs_full_size - size))" 
 1215            # Transfer blockweise vornehmen - byteweise dauert es zu lang 
 1216            read_data_bytes 
"($((rootfs_full_size - size)))" <
"$rootfs_device" 
 1217            if [ -z 
"$include_private" ]; then
 
 1218                echo >&2 
"Read: root-zero ($size)" 
 1221                tr 
'\0' '\377' </dev/zero | read_data_bytes 
"$size" 
 1223                echo >&2 
"Read: root-RW ($size)" 
 1224                # auch das private rootfs-Dateisystem (inkl. Schluessel, Passworte, usw.) auslesen 
 1225                read_data_bytes 
"$size" <
"/dev/$name" 
 1227        elif [ 
"$label" = 
"firmware" ]; then
 
 1228            echo >&2 
"Skip: $label ($size)" 
 1229            # ignoriere die meta-Partition (kernel + rootfs) 
 1232            echo >&2 
"Read: $label ($size)" 
 1239## @fn has_flash_or_filesystem_error_indicators() 
 1240## @brief Prüfe ob typische Indikatoren (vor allem im Kernel-Log) vorliegen, die auf einen Flash-Defekt hinweisen. 
 1241has_flash_or_filesystem_error_indicators() {
 
 1242    trap 
'error_trap get_flash_backup "$*"' EXIT
 
 1243    dmesg | grep -q 
"jffs2.*CRC" && 
return 0
 
 1244    dmesg | grep -q 
"SQUASHFS error" && 
return 0
 
 1245    # keine Hinweise gefunden -> wir liefern "nein" 
 1246    trap 
"" EXIT && 
return 1
 
 1250## @fn request_field_from_api() 
 1251## @brief Request the content of a field from an API endpoint. 
 1252## @details The response is stored in memory - thus this function should not be used for huge 
 1254request_field_from_api() {
 
 1256    local json_query=
"$2" 
 1258    # wget returns a non-zero exitcode in case of an HTTP error status 
 1259    data=
$(wget -q -O - 
"$OPENNET_API_URL/$path" || 
true)
 
 1260    [ -z 
"$data" ] && 
return 
 1261    # the "jsonfilter" call returns with an error, if the wanted field does not exist
 
 1262    echo 
"$data" | jsonfilter -e 
"$json_query" || 
true 
 1266get_main_ip_for_ip() {
 
 1268    result=
$(request_field_from_api 
"/accesspoint/$ip" "@.main_ip")
 
 1269    [ -z 
"$result" ] && result=
$(request_field_from_api 
"/interface/$ip/accesspoint/" "@.main_ip")
 
 1274get_name_for_main_ip() {
 
 1276    echo 
"$main_ip" | sed -E 
's/^192\.168\.(\d+)\.(\d+)$/AP\1-\2/' 
 1280get_location_for_main_ip() {
 
 1282    request_field_from_api 
"/accesspoint/$main_ip" "@.post_address" 
 1286# Request a URL via https or http. 
 1291# Ende der Doku-Gruppe 
get_custom_log_content(log_name)
Liefere den Inhalt eines spezifischen Logs (z.B. das OpenVPN-Verbindungsprotokoll) zurück.
 
append_to_custom_log(log_name, event)
Hänge eine neue Nachricht an ein spezfisches Protokoll an.
 
_get_file_dict_value(key)
Auslesen eines Werts aus einem Schlüssel/Wert-Eingabestrom.
 
update_mesh_interfaces()
Update mesh interfaces, routing daemons and policy routing.
 
update_ntp_servers()
Übertrage die Liste der als NTP-Dienst announcierten Server in die sysntpd-Konfiguration.
 
msg_debug(message)
Debug-Meldungen ins syslog schreiben.
 
msg_error(message)
Die Fehlermeldungen werden in die Standard-Fehlerausgabe und ins syslog geschrieben.
 
clean_restart_log()
Alle Log-Einträge aus der banner-Datei entfernen.
 
update_file_if_changed(target_filename)
Aktualisiere eine Datei, falls sich ihr Inhalt geändert haben sollte.
 
msg_info(message)
Informationen und Fehlermeldungen ins syslog schreiben.
 
get_custom_log_filename(log_name)
Liefere den Inhalt eines spezifischen Logs (z.B. das OpenVPN-Verbindungsprotokoll) zurück.
 
add_banner_event(event, timestamp)
Füge ein Ereignis zum dauerhaften Ereignisprotokoll (/etc/banner) hinzu.
 
get_mig_tunnel_servers(stype)
Ermittle die Server für den gewünschen Dienst, die via Tunnel erreichbar sind.
 
filter_enabled_services()
Filtere aus einer Reihe eingehender Dienste diejenigen heraus, die nicht manuell ausgeblendet wurden.
 
get_services(service_type)
Liefere alle Dienste zurueck, die dem angegebenen Typ zugeordnet sind. Falls kein Typ angegben wird,...
 
get_service_value(key, default)
Auslesen eines Werts aus der Service-Datenbank.
 
filter_reachable_services()
Filtere aus einer Reihe eingehender Dienste diejenigen heraus, die erreichbar sind.
 
uci_replace_list()
Replace the items in a list. Wanted items are expected via stdin (one per line, uci_path).
 
uci_add_list(uci_path, new_item)
Füge einen neuen Wert zu einer UCI-Liste hinzu und achte dabei auf Einmaligkeit.
 
if[-d "/etc/on-firewall.d/"]
 
set eu grep root::etc shadow exit if command v chpasswd dev null
 
set eu on function print_services services log for dir in etc on services d var on services volatile d