Opennet Firmware
 Alle Dateien Funktionen Variablen Gruppen Seiten
core.sh
gehe zur Dokumentation dieser Datei
1 ## @defgroup core Kern
2 ## @brief Logging, Datei-Operationen, DNS- und NTP-Dienste, Dictionary-Dateien, PID- und Lock-Behandlung, Berichte
3 # Beginn der Doku-Gruppe
4 ## @{
5 
6 
7 ## @var Quelldatei für Standardwerte des Kern-Pakets
8 ON_CORE_DEFAULTS_FILE=/usr/share/opennet/core.defaults
9 ## @var Quelldatei für Standardwerte des Nutzer-VPN-Pakets
10 ON_OPENVPN_DEFAULTS_FILE=/usr/share/opennet/openvpn.defaults
11 ## @var Quelldatei für Standardwerte des Hotspot-Pakets
12 ON_WIFIDOG_DEFAULTS_FILE=/usr/share/opennet/wifidog.defaults
13 ## @var Pfad zur dnsmasq-Server-Datei zur dynamischen Aktualisierung durch Dienste-Erkennung
14 DNSMASQ_SERVERS_FILE_DEFAULT=/var/run/dnsmasq.servers
15 ## @var Dateiname für erstellte Zusammenfassungen
16 REPORTS_FILE=/tmp/on_report.tar.gz
17 ## @var Basis-Verzeichnis für Log-Dateien
18 LOG_BASE_DIR=/var/log
19 # beim ersten Pruefen wird der Debug-Modus ermittelt
20 DEBUG_ENABLED=
21 
22 
23 
24 ## @fn msg_debug()
25 ## @param message Debug-Nachricht
26 ## @brief Debug-Meldungen ins syslog schreiben
27 ## @details Die Debug-Nachrichten landen im syslog (siehe ``logread``).
28 ## Falls das aktuelle Log-Level bei ``info`` oder niedriger liegt, wird keine Nachricht ausgegeben.
29 msg_debug() {
30  # bei der ersten Ausfuehrung dauerhaft speichern
31  [ -z "$DEBUG_ENABLED" ] && \
32  DEBUG_ENABLED=$(uci_is_true "$(uci_get on-core.settings.debug false)" && echo 1 || echo 0)
33  [ "$DEBUG_ENABLED" = "0" ] || logger -t "$(basename "$0")[$$]" "$1"
34 }
35 
36 
37 ## @fn msg_info()
38 ## @param message Log-Nachricht
39 ## @brief Informationen und Fehlermeldungen ins syslog schreiben
40 ## @details Die Nachrichten landen im syslog (siehe ``logread``).
41 ## Die info-Nachrichten werden immer ausgegeben, da es kein höheres Log-Level gibt.
42 msg_info() {
43  logger -t "$(basename "$0")[$$]" "$1"
44 }
45 
46 
47 ## @fn append_to_custom_log()
48 ## @brief Hänge eine neue Nachricht an ein spezfisches Protokoll an.
49 ## @param log_name Name des Log-Ziels
50 ## @param event die Kategorie der Meldung (up/down/???)
51 ## @param msg die textuelle Beschreibung des Ereignis (z.B. "connection with ... closed")
52 ## @details Die Meldungen werden beispielsweise von den konfigurierten openvpn-up/down-Skripten gesendet.
54  local log_name="$1"
55  local event="$2"
56  local msg="$3"
57  local logfile="$LOG_BASE_DIR/${log_name}.log"
58  echo "$(date) openvpn [$event]: $msg" >>"$logfile"
59  # Datei kuerzen, falls sie zu gross sein sollte
60  local filesize=$(get_filesize "$logfile")
61  [ "$filesize" -gt 10000 ] && sed -i "1,30d" "$logfile"
62  return 0
63 }
64 
65 
66 ## @fn get_custom_log()
67 ## @brief Liefere den Inhalt eines spezifischen Logs (z.B. das OpenVPN-Verbindungsprotokoll) zurück.
68 ## @param log_name Name des Log-Ziels
69 ## @returns Zeilenweise Ausgabe der Protokollereignisse (aufsteigend nach Zeitstempel sortiert).
71  local log_name="$1"
72  local logfile="$LOG_BASE_DIR/${log_name}.log"
73  [ -e "$logfile" ] && cat "$logfile" || true
74 }
75 
76 
77 ## @fn update_file_if_changed()
78 ## @param filename Name der Zieldatei
79 ## @brief Aktualisiere eine Datei, falls sich ihr Inhalt geändert haben sollte.
80 ## @details Der neue Inhalt der Datei wird auf der Standardeingabe erwartet.
81 ## Im Falle der Gleichheit von aktuellem Inhalt und zukünftigem Inhalt wird
82 ## keine Schreiboperation ausgeführt. Der Exitcode gibt an, ob eine Schreiboperation
83 ## durchgeführt wurde.
84 ## @return exitcode=0 (Erfolg) falls die Datei geändert werden musste
85 ## @return exitcode=1 (Fehler) falls es keine Änderung gab
87  local target_filename="$1"
88  local content="$(cat -)"
89  if [ -e "$target_filename" ] && echo "$content" | cmp -s - "$target_filename"; then
90  # the content did not change
91  trap "" $GUARD_TRAPS && return 1
92  else
93  # updated content
94  echo "$content" > "$target_filename"
95  return 0
96  fi
97 }
98 
99 
100 ## @fn update_dns_servers()
101 ## @brief Übertrage die Liste der als DNS-Dienst announcierten Server in die dnsmasq-Konfiguration.
102 ## @details Die Liste der DNS-Server wird in die separate dnsmasq-Servers-Datei geschrieben (siehe @sa DNSMASQ_SERVERS_FILE_DEFAULT).
103 ## Die Server-Datei wird nur bei Änderungen neu geschrieben. Dasselbe gilt für den Neustart des Diensts.
104 ## Diese Funktion sollte via olsrd-nameservice-Trigger oder via cron-Job ausgeführt werden.
106  trap "error_trap update_dns_servers '$*'" $GUARD_TRAPS
107  local host
108  local port
109  local service
110  local use_dns="$(uci_get on-core.settings.use_olsrd_dns)"
111  # return if we should not use DNS servers provided via olsrd
112  uci_is_false "$use_dns" && return 0
113  local servers_file=$(uci_get "dhcp.@dnsmasq[0].serversfile")
114  # aktiviere die "dnsmasq-serversfile"-Direktive, falls noch nicht vorhanden
115  if [ -z "$servers_file" ]; then
116  servers_file=$DNSMASQ_SERVERS_FILE_DEFAULT
117  uci set "dhcp.@dnsmasq[0].serversfile=$servers_file"
118  uci commit "dhcp.@dnsmasq[0]"
119  reload_config
120  fi
121  # wir sortieren alphabetisch - Naehe ist uns egal
122  get_services "dns" | filter_reachable_services | filter_enabled_services | sort | while read service; do
123  host=$(get_service_value "$service" "host")
124  port=$(get_service_value "$service" "port")
125  [ -n "$port" -a "$port" != "53" ] && host="$host#$port"
126  echo "server=$host"
127  done | update_file_if_changed "$servers_file" || return 0
128  # es gab eine Aenderung
129  msg_info "updating DNS servers"
130  # Konfiguration neu einlesen
131  killall -s HUP dnsmasq 2>/dev/null || true
132 }
133 
134 
135 ## @fn update_ntp_servers()
136 ## @brief Übertrage die Liste der als NTP-Dienst announcierten Server in die sysntpd-Konfiguration.
137 ## @details Die Liste der NTP-Server wird in die uci-Konfiguration geschrieben.
138 ## Die uci-Konfiguration wird nur bei Änderungen neu geschrieben. Dasselbe gilt für den Neustart des Diensts.
139 ## Diese Funktion sollte via olsrd-nameservice-Trigger oder via cron-Job ausgeführt werden.
140 ## @sa http://wiki.openwrt.org/doc/uci/system#remote_time_ntp
142  trap "error_trap update_ntp_servers '$*'" $GUARD_TRAPS
143  local host
144  local port
145  local service
146  local use_ntp="$(uci_get on-core.settings.use_olsrd_ntp)"
147  # return if we should not use NTP servers provided via olsrd
148  uci_is_false "$use_ntp" && return
149  # schreibe die Liste der NTP-Server neu
150  uci_delete system.ntp.server
151  # wir sortieren alphabetisch - Naehe ist uns egal
152  get_services "ntp" | filter_reachable_services | filter_enabled_services | sort | while read service; do
153  host=$(get_service_value "$service" "host")
154  port=$(get_service_value "$service" "port")
155  [ -n "$port" -a "$port" != "123" ] && host="$host:$port"
156  uci_add_list "system.ntp.server" "$host"
157  done
158  apply_changes system
159 }
160 
161 
162 ## @fn add_banner_event()
163 ## @brief Füge ein Ereignis zum dauerhaften Ereignisprotokoll (/etc/banner) hinzu.
164 ## @param event Ereignistext
165 ## @param timestamp [optional] Der Zeitstempel-Text kann bei Bedarf vorgegeben werden.
166 ## @details Ein Zeitstempel, sowie hübsche Formatierung wird automatisch hinzugefügt.
168  trap "error_trap add_banner_event '$*'" $GUARD_TRAPS
169  local event=$1
170  # verwende den optionalen zweiten Parameter oder den aktuellen Zeitstempel
171  local timestamp="${2:-$(date)}"
172  local line=" - $timestamp - $event -"
173  (
174  # Steht unser Text schon im Banner? Ansonsten hinzufuegen ...
175  # bis einschliesslich Version v0.5.0 war "clean_restart_log" das Schluesselwort
176  # ab v0.5.1 verwenden wir "system events"
177  if ! grep -qE '(clean_restart_log|system events)' /etc/banner; then
178  echo " ------------------- system events -------------------"
179  fi
180  # die Zeile auffuellen
181  while [ "${#line}" -lt 54 ]; do line="$line-"; done
182  echo "$line"
183  ) >>/etc/banner
184  sync
185 }
186 
187 
188 clean_restart_log() {
189  awk '{if ($1 != "-") print}' /etc/banner >/tmp/banner
190  mv /tmp/banner /etc/banner
191  sync
192 }
193 
194 
195 ## @fn _get_file_dict_value()
196 ## @brief Auslesen eines Werts aus einer Schlüssel/Wert-Datei
197 ## @param status_file der Name der Schlüssel/Wert-Datei
198 ## @param field das Schlüsselwort
199 ## @returns Den zum gegebenen Schlüssel gehörenden Wert aus der Schlüssel/Wert-Datei.
200 ## Falls kein passender Schlüssel gefunden wurde, dann ist die Ausgabe leer.
201 ## @details Jede Zeile dieser Datei enthält einen Feldnamen und einen Wert - beide sind durch
202 ## ein beliebiges whitespace-Zeichen getrennt.
203 ## Dieses Dateiformat wird beispielsweise für die Dienst-Zustandsdaten verwendet.
204 ## Zusätzlich ist diese Funktion auch zum Parsen von openvpn-Konfigurationsdateien geeignet.
205 _get_file_dict_value() { local key="$1"; shift; sed -n "s/^$key[ \t]\+//p" "$@" 2>/dev/null || true; }
206 
207 
208 ## @fn _get_file_dict_keys()
209 ## @brief Liefere alle Schlüssel aus einer Schlüssel/Wert-Datei.
210 ## @param status_files Namen der Schlüssel/Wert-Dateien
211 ## @returns Liste aller Schlüssel aus der Schlüssel/Wert-Datei.
212 ## @sa _get_file_dict_value
213 _get_file_dict_keys() { sed 's/[ \t].*//' "$@" 2>/dev/null || true; }
214 
215 
216 ## @fn _set_file_dict_value()
217 ## @brief Schreiben eines Werts in eine Schlüssel/Wert-Datei
218 ## @param status_file der Name der Schlüssel/Wert-Datei
219 ## @param field das Schlüsselwort
220 ## @param value der neue Wert
221 ## @sa _get_file_dict_value
222 _set_file_dict_value() {
223  local status_file=$1
224  local field=$2
225  local new_value=$3
226  local fieldname
227  local value=$(_get_file_dict_value "$field" "$status_file")
228  # Wert ist korrekt? Wir sind fertig ...
229  [ "$value" = "$new_value" ] && return 0
230  # Filtere bisherige Zeilen mit dem key heraus.
231  # Fuege anschliessend die Zeile mit dem neuen Wert an.
232  # Die Sortierung sorgt fuer gute Vergleichbarkeit, um die Anzahl der
233  # Schreibvorgaenge (=Wahrscheinlichkeit von gleichzeitigem Zugriff) zu reduzieren.
234  (
235  grep -v -w -s "$field" "$status_file"
236  echo "$field $new_value"
237  ) | sort | update_file_if_changed "$status_file" || true
238 }
239 
240 
241 ## @fn get_on_core_default()
242 ## @brief Liefere einen der default-Werte der aktuellen Firmware zurück (Paket on-core).
243 ## @param key Name des Schlüssels
244 ## @details Die default-Werte werden nicht von der Konfigurationsverwaltung uci verwaltet.
245 ## Somit sind nach jedem Upgrade imer die neuesten Standard-Werte verfügbar.
246 get_on_core_default() {
247  _get_file_dict_value "$1" "$ON_CORE_DEFAULTS_FILE"
248 }
249 
250 
251 ## @fn get_on_firmware_version()
252 ## @brief Liefere die aktuelle Firmware-Version zurück.
253 ## @returns Die zurückgelieferte Zeichenkette beinhaltet den Versionsstring (z.B. "0.5.0").
254 ## @details Per Konvention entspricht die Version jedes Firmware-Pakets der Firmware-Version.
255 get_on_firmware_version() {
256  opkg status on-core | awk '{if (/Version/) print $2;}'
257 }
258 
259 
260 ## @fn get_on_ip()
261 ## @param on_id die ID des AP - z.B. "1.96" oder "2.54"
262 ## @param on_ipschema siehe "get_on_core_default on_ipschema"
263 ## @param interface_number 0..X (das WLAN-Interface ist typischerweise Interface #0)
264 ## @attention Manche Aufrufende verlassen sich darauf, dass *on_id_1* und
265 ## *on_id_2* nach dem Aufruf verfügbar sind (also _nicht_ als "local"
266 ## Variablen deklariert wurden).
267 get_on_ip() {
268  local on_id=$1
269  local on_ipschema=$2
270  local no=$3
271  echo "$on_id" | grep -q "\." || on_id=1.$on_id
272  on_id_1=$(echo "$on_id" | cut -d . -f 1)
273  on_id_2=$(echo "$on_id" | cut -d . -f 2)
274  echo $(eval echo $on_ipschema)
275 }
276 
277 
278 ## @fn get_main_ip()
279 ## @brief Liefere die aktuell konfigurierte Main-IP zurück.
280 ## @returns Die aktuell konfigurierte Main-IP des AP oder die voreingestellte IP.
281 ## @attention Seiteneffekt: die Variablen "on_id_1" und "on_id_2" sind anschließend verfügbar.
282 ## @sa get_on_ip
283 get_main_ip() {
284  local on_id=$(uci_get on-core.settings.on_id "$(get_on_core_default on_id_preset)")
285  local ipschema=$(get_on_core_default on_ipschema)
286  get_on_ip "$on_id" "$ipschema" 0
287 }
288 
289 
290 # check if a given lock file:
291 # A) exists, but it is outdated (determined by the number of minutes given as second parameter)
292 # B) exists, but is fresh
293 # C) does not exist
294 # A + C return success and create that file
295 # B return failure and do not touch that file
296 aquire_lock() {
297  local lock_file=$1
298  local max_age_minutes=$2
299  [ ! -e "$lock_file" ] && touch "$lock_file" && return 0
300  local file_timestamp=$(get_file_modification_timestamp_minutes "$lock_file")
301  # too old? We claim it for ourself.
302  is_timestamp_older_minutes "$file_timestamp" "$max_age_minutes" && touch "$lock_file" && return 0
303  # lockfile is too young
304  trap "" $GUARD_TRAPS && return 1
305 }
306 
307 
308 clean_stale_pid_file() {
309  local pid_file=$1
310  [ -e "$pid_file" ] || return 0
311  local pid=$(cat "$pid_file" | sed 's/[^0-9]//g')
312  [ -z "$pid" ] && msg_debug "removing broken PID file: $pid_file" && rm "$pid_file" && return 0
313  [ ! -e "/proc/$pid" ] && msg_debug "removing stale PID file: $pid_file" && rm "$pid_file" && return 0
314  return 0
315 }
316 
317 
318 # Pruefe ob eine PID-Datei existiert und ob die enthaltene PID zu einem Prozess
319 # mit dem angegebenen Namen (nur Dateiname - ohne Pfad) verweist.
320 # Parameter PID-Datei: vollstaendiger Pfad
321 # Parameter Prozess-Name: Dateiname ohne Pfad
322 check_pid_file() {
323  trap "error_trap check_pid_file '$*'" $GUARD_TRAPS
324  local pid_file="$1"
325  local process_name="$2"
326  local pid
327  local current_process
328  [ -z "$pid_file" -o ! -e "$pid_file" ] && trap "" $GUARD_TRAPS && return 1
329  pid=$(cat "$pid_file" | sed 's/[^0-9]//g')
330  # leere/kaputte PID-Datei
331  [ -z "$pid" ] && trap "" $GUARD_TRAPS && return 1
332  # Prozess-Datei ist kein symbolischer Link?
333  [ ! -L "/proc/$pid/exe" ] && trap "" $GUARD_TRAPS && return 1
334  current_process=$(readlink "/proc/$pid/exe")
335  [ "$process_name" != "$(basename "$current_process")" ] && trap "" $GUARD_TRAPS && return 1
336  return 0
337 }
338 
339 
340 apply_changes() {
341  local config=$1
342  # keine Aenderungen?
343  # "on-core" achtet auch auf nicht-uci-Aenderungen (siehe PERSISTENT_SERVICE_STATUS_DIR)
344  [ -z "$(uci changes "$config")" -a "$config" != "on-core" ] && return 0
345  uci commit "$config"
346  case "$config" in
347  system|network|firewall|dhcp)
348  reload_config || true
349  ;;
350  olsrd)
351  /etc/init.d/olsrd restart || true
352  ;;
353  openvpn)
354  /etc/init.d/openvpn reload || true
355  ;;
356  on-usergw)
357  # TODO: verwenden wir ueberhaupt eine uci-Konfiguration?
358  /etc/init.d/openvpn reload || true
359  reload_config || true
360  ;;
361  on-core)
364  ;;
365  on-openvpn)
366  # es ist nichts zu tun
367  ;;
368  *)
369  msg_info "no handler defined for applying config changes for '$config'"
370  ;;
371  esac
372  return 0
373 }
374 
375 
376 # Setzen einer Opennet-ID.
377 # 1) Hostnamen setzen
378 # 2) IPs fuer alle Opennet-Interfaces setzen
379 # 3) Main-IP in der olsr-Konfiguration setzen
380 # 4) IP des Interface "free" setzen
381 # 5) DHCP-Redirect fuer wifidog setzen
382 set_opennet_id() {
383  trap "error_trap set_opennet_id '$*'" $GUARD_TRAPS
384  local new_id=$1
385  local network
386  local uci_prefix
387  local ipaddr
388  local main_ipaddr
389  local free_ipaddr
390  local ipschema
391  local netmask
392  local if_counter=0
393  # ID normalisieren (AP7 -> AP1.7)
394  echo "$new_id" | grep -q "\." || new_id=1.$new_id
395  # ON_ID in on-core-Settings setzen
396  prepare_on_uci_settings
397  uci set "on-core.settings.on_id=$new_id"
398  apply_changes on-core
399  # Hostnamen konfigurieren
400  find_all_uci_sections system system | while read uci_prefix; do
401  uci set "${uci_prefix}.hostname=AP-$(echo "$new_id" | tr . -)"
402  done
403  apply_changes system
404  # IP-Adressen konfigurieren
405  ipschema=$(get_on_core_default on_ipschema)
406  netmask=$(get_on_core_default on_netmask)
407  main_ipaddr=$(get_on_ip "$new_id" "$ipschema" 0)
408  for network in $(get_sorted_opennet_interfaces); do
409  uci_prefix=network.$network
410  [ "$(uci_get "${uci_prefix}.proto")" != "static" ] && continue
411  ipaddr=$(get_on_ip "$new_id" "$ipschema" "$if_counter")
412  uci set "${uci_prefix}.ipaddr=$ipaddr"
413  uci set "${uci_prefix}.netmask=$netmask"
414  : $((if_counter++))
415  done
416  # OLSR-MainIP konfigurieren
417  olsr_set_main_ip "$main_ipaddr"
418  apply_changes olsrd
419  # wifidog-Interface konfigurieren
420  if is_function_available "get_on_wifidog_default"; then
421  ipschema=$(get_on_wifidog_default free_ipschema)
422  netmask=$(get_on_wifidog_default free_netmask)
423  free_ipaddr=$(get_on_ip "$new_id" "$ipschema" 0)
424  uci_prefix="network.$NETWORK_FREE"
425  uci set "${uci_prefix}=interface"
426  uci set "${uci_prefix}.proto=static"
427  uci set "${uci_prefix}.ipaddr=$free_ipaddr"
428  uci set "${uci_prefix}.netmask=$netmask"
429  fi
430  apply_changes network
431  # DHCP-Forwards fuer wifidog
432  # Ziel ist beispielsweise folgendes Setup:
433  # firewall.@redirect[0]=redirect
434  # firewall.@redirect[0].src=opennet
435  # firewall.@redirect[0].proto=udp
436  # firewall.@redirect[0].src_dport=67
437  # firewall.@redirect[0].target=DNAT
438  # firewall.@redirect[0].src_port=67
439  # firewall.@redirect[0].dest_ip=10.3.1.210
440  # firewall.@redirect[0].src_dip=192.168.1.210
441  find_all_uci_sections firewall redirect "src=$ZONE_MESH" proto=udp src_dport=67 src_port=67 target=DNAT | while read uci_prefix; do
442  uci set "${uci_prefix}.name=DHCP-Forward Opennet"
443  uci set "${uci_prefix}.dest_ip=$free_ipaddr"
444  uci set "${uci_prefix}.src_dip=$main_ipaddr"
445  done
446  apply_changes firewall
447 }
448 
449 
450 # Durchsuche eine Schluessel-Wert-Liste nach einem Schluessel und liefere den dazugehoerigen Wert zurueck.
451 # Beispiel:
452 # foo=bar baz=nux
453 # Der Separator ist konfigurierbar.
454 # Die Liste wird auf der Standardeingabe erwartet.
455 # Der erste und einzige Parameter ist der gewuenschte Schluessel.
456 get_from_key_value_list() {
457  local search_key="$1"
458  local separator="$2"
459  local key_value
460  local key
461  sed 's/[ \t]\+/\n/g' | while read key_value; do
462  key=$(echo "$key_value" | cut -f 1 -d "$separator")
463  [ "$key" = "$search_key" ] && echo "$key_value" | cut -f 2- -d "$separator" && break || true
464  done
465  return 0
466 }
467 
468 
469 ## @fn replace_in_key_value_list()
470 ## @brief Ermittle aus einer mit Tabulatoren oder Leerzeichen getrennten Liste von Schlüssel-Wert-Paaren den Inhalt des Werts zu einem Schlüssel.
471 ## @param search_key der Name des Schlüsselworts
472 ## @param separator der Name des Trennzeichens zwischen Wert und Schlüssel
473 ## @returns die korrigierte Schlüssel-Wert-Liste wird ausgegeben (eventuell mit veränderten Leerzeichen oder Tabulatoren)
474 replace_in_key_value_list() {
475  local search_key="$1"
476  local separator="$2"
477  local value="$3"
478  local key_value
479  sed 's/[ \t]\+/\n/g' | while read key_value; do
480  key=$(echo "$key_value" | cut -f 1 -d "$separator")
481  if [ "$key" = "$search_key" ]; then
482  # nicht ausgeben, falls der Wert leer ist
483  [ -n "$value" ] && echo -n " ${key_value}${separator}${value}" || true
484  else
485  echo -n " $key_value"
486  fi
487  done | sed 's/^ //'
488  return 0
489 }
490 
491 
492 # Wandle einen uebergebenene Parameter in eine Zeichenkette um, die sicher als Dateiname verwendet werden kann
493 get_safe_filename() {
494  echo "$1" | sed 's/[^a-zA-Z0-9._\-]/_/g'
495 }
496 
497 
498 get_time_minute() {
499  date +%s | awk '{print int($1/60)}'
500 }
501 
502 
503 get_file_modification_timestamp_minutes() {
504  local filename="$1"
505  date --reference "$filename" +%s | awk '{ print int($1/60) }'
506 }
507 
508 
509 # Achtung: Zeitstempel aus der Zukunft gelten immer als veraltet.
510 is_timestamp_older_minutes() {
511  local timestamp_minute="$1"
512  local difference="$2"
513  local now="$(get_time_minute)"
514  # it is older
515  [ "$now" -ge "$((timestamp_minute+difference))" ] && return 0
516  # timestamp in future -> invalid -> let's claim it is too old
517  [ "$now" -lt "$timestamp_minute" ] && \
518  msg_info "WARNING: Timestamp from future found: $timestamp_minute (minutes since epoch)" && \
519  return 0
520  trap "" $GUARD_TRAPS && return 1
521 }
522 
523 
524 ## @fn get_uptime_seconds()
525 ## @brief Ermittle die Anzahl der Sekunden seit dem letzten Bootvorgang.
526 get_uptime_seconds() {
527  cut -f 1 -d . /proc/uptime
528 }
529 
530 
531 ## @fn run_delayed_in_background()
532 ## @brief Führe eine Aktion verzögert im Hintergrund aus.
533 ## @param delay Verzögerung in Sekunden
534 ## @param command alle weiteren Token werden als Kommando und Parameter interpretiert und mit Verzögerung ausgeführt.
535 run_delayed_in_background() {
536  local delay="$1"
537  shift
538  (sleep "$delay" && "$@") </dev/null >/dev/null 2>&1 &
539 }
540 
541 
542 ## @fn get_filesize()
543 ## @brief Ermittle die Größe einer Datei in Bytes.
544 ## @params filename Name der zu untersuchenden Datei.
545 get_filesize() {
546  local filename="$1"
547  wc -c "$filename" | awk '{ print $1 }'
548 }
549 
550 
551 # Bericht erzeugen
552 # Der Name der erzeugten tar-Datei wird als Ergebnis ausgegeben.
553 generate_report() {
554  trap "error_trap generate_report '$*'" $GUARD_TRAPS
555  local fname
556  local pid
557  local temp_dir=$(mktemp -d)
558  local reports_dir="$temp_dir/report"
559  local tar_file=$(mktemp)
560  msg_debug "Creating a report"
561  # die Skripte duerfen davon ausgehen, dass wir uns im Zielverzeichnis befinden
562  mkdir -p "$reports_dir"
563  cd "$reports_dir"
564  find /usr/lib/opennet/reports -type f | while read fname; do
565  [ ! -x "$fname" ] && msg_info "skipping non-executable report script: $fname" && continue
566  "$fname" || msg_info "ERROR: reports script failed: $fname"
567  done
568  cd "$temp_dir"
569  tar czf "$tar_file" "report"
570  rm -r "$temp_dir"
571  mv "$tar_file" "$REPORTS_FILE"
572 }
573 
574 
575 # Filtere aus den zugaenglichen Quellen moegliche Fehlermeldungen.
576 # Falls diese Funktion ein nicht-leeres Ergebnis zurueckliefert, dann kann dies als Hinweis fuer den
577 # Nutzer verwendet werden, auf dass er einen Fehlerbericht einreicht.
578 get_potential_error_messages() {
579  local filters=
580  # 1) get_service_as_csv
581  # Wir ignorieren "get_service_as_csv"-Meldungen - diese werden durch asynchrone Anfragen des
582  # Web-Interface ausgeloest, die beim vorzeitigen Abbruch des Seiten-Lade-Vorgangs mit
583  # einem Fehler enden.
584  filters="${filters}|trapped.*get_service_as_csv"
585  # 2) openvpn.*Error opening configuration file
586  # Beim Booten des Systems wurde die openvpn-Config-Datei, die via uci referenziert ist, noch
587  # nicht erzeugt. Beim naechsten cron-Lauf wird dieses Problem behoben.
588  filters="${filters}|openvpn.*Error opening configuration file"
589  # 3) openvpn(...)[...]: Exiting due to fatal error
590  # Das Verzeichnis /var/etc/openvpn/ existiert beim Booten noch nicht.
591  filters="${filters}|openvpn.*Exiting due to fatal error"
592  # 4) openvpn(...)[...]: SIGUSR1[soft,tls-error] received, process restarting
593  # Diese Meldung taucht bei einem Verbindungsabbruch auf. Dieses Ereignis ist nicht
594  # ungewoehnlich und wird mittels des Verbindungsprotokolls bereits hinreichend gewuerdigt
595  filters="${filters}|openvpn.*soft,tls-error"
596  # 5) openvpn(...)[...]: TLS Error: TLS handshake failed
597  # Diese Meldung deutet einen fehlgeschlagenen Verbindungsversuch an. Dies ist nicht
598  # ungewoehnlich (beispielsweise auch fuer Verbindungstests).
599  filters="${filters}|openvpn.*TLS Error"
600  # 6) olsrd: /etc/rc.d/S65olsrd: startup-error: check via: '/usr/sbin/olsrd -f "/var/etc/olsrd.conf" -nofork'
601  # Falls noch kein Interface vorhanden ist (z.B. als wifi-Client), dann taucht diese Meldung
602  # beim Booten auf.
603  filters="${filters}|olsrd.*startup-error"
604  # 7) ucarp
605  # Beim Booten tauchen Fehlermeldungen aufgrund nicht konfigurierter Netzwerk-Interfaces auf.
606  # TODO: ucarp nur noch als nachinstallierbares Paket markieren (erfordert Aenderung der Makefile-Erzeugung)
607  filters="${filters}|ucarp"
608  # 8) olsrd: /etc/rc.d/S65olsrd: ERROR: there is already an IPv4 instance of olsrd running (pid: '1099'), not starting.
609  # Dieser Fehler tritt auf, wenn der olsrd_check einen olsrd-Neustart ausloest, obwohl er schon laeuft.
610  filters="${filters}|olsrd: ERROR: there is already an IPv4 instance of olsrd running"
611  # 9) openvpn(...)[...]: Authenticate/Decrypt packet error
612  # Paketverschiebungen nach dem Verbindungsaufbau - anscheinend unproblematisch.
613  filters="${filters}|openvpn.*Authenticate/Decrypt packet error"
614  # 10) olsrd: ... olsrd_setup_smartgw_rules() Warning: kmod-ipip is missing.
615  # olsrd gibt beim Starten generell diese Warnung aus. Wir koennen sie ignorieren.
616  filters="${filters}|olsrd.*olsrd_setup_smartgw_rules"
617  # 11) olsrd: ... olsrd_write_interface() Warning: Interface '...' not found, skipped
618  # Falls das wlan-Interface beim Bootvorgang noch nicht aktiv ist, wenn olsrd startet, dann erscheint diese
619  # harmlose Meldung.
620  filters="${filters}|olsrd.*Interface.*not found"
621  # 12) dropbear[...]: Exit (root): Error reading: Connection reset by peer
622  # Verbindungsverlust einer ssh-Verbindung. Dies darf passieren.
623  filters="${filters}|dropbear.*Connection reset by peer"
624  # 13) cron-error: nc.*: short write
625  # Falls die Routen via nc während eines olsrd-Neustarts ausgelesen werden, reisst eventuell die Socket-
626  # Verbindung ab - dies ist akzeptabel.
627  filters="${filters}|nc: short write"
628  # 14) openvpn(___service_name___)[...]: write UDPv4: Network is unreachable
629  # Beispielsweise bei einem olsrd-Neustart reisst die Verbindung zum UGW-Server kurz ab.
630  filters="${filters}|openvpn.*Network is unreachable"
631  # 15) wget: can't connect to remote host
632  # Eine frühe Geschwindigkeitsmessung (kurz nach dem Booten) darf fehlschlagen.
633  filters="${filters}|wget: can.t connect to remote host"
634  # System-Fehlermeldungen (inkl. "trapped")
635  logread | grep -i error | grep -vE "(${filters#|})" || true
636 }
637 
638 
639 # Im openwrt-Build-Prozess wird aus bisher ungeklaerter Ursache die falsche opkg-Repository-URL gesetzt.
640 # Diese Funktion erlaubt die einfache Aenderung der opkg-URL.
641 # Parameter: URL-Bestandteile (z.B. "stable/0.5.0")
642 set_opkg_download_version() {
643  local version="$1"
644  sed -i "s#\(/openwrt\)/[^/]\+/[^/]\+/#\1/$version/#" /etc/opkg.conf
645 }
646 
647 
648 # Ersetze eine Zeile durch einen neuen Inhalt. Falls das Zeilenmuster nicht vorhanden ist, wird eine neue Zeile eingefuegt.
649 # Dies entspricht der Funktionalitaet des "lineinfile"-Moduls von ansible.
650 # Parameter filename: der Dateiname
651 # Parameter pattern: Suchmuster der zu ersetzenden Zeile
652 # Parameter new_line: neue Zeile
653 line_in_file() {
654  trap "error_trap lineinfile '$*'" $GUARD_TRAPS
655  local filename="$1"
656  local pattern="$2"
657  local new_line="$3"
658  local line
659  # Datei existiert nicht? Einfach mit dieser Zeile erzeugen.
660  [ ! -e "$filename" ] && echo "$new_line" >"$filename" && return 0
661  # Datei einlesen - zum Muster passende Zeilen austauschen - notfalls neue Zeile anfuegen
662  (
663  while read line; do
664  echo "$line" | grep -q "$pattern" && echo "$new_line" || echo "$line"
665  done <"$filename"
666  # die neue Zeile hinzufuegen, falls das Muster in der alten Datei nicht vorhanden war
667  grep -q "$pattern" "$filename" || echo "$new_line"
668  ) | update_file_if_changed "$filename" || true
669 }
670 
671 
672 ## @fn is_package_installed()
673 ## @brief Prüfe, ob ein opkg-Paket installiert ist.
674 ## @param package Name des Pakets
675 is_package_installed() {
676  local package="$1"
677  opkg list-installed | grep -q "^$package[\t ]" && return 0
678  trap "" $GUARD_TRAPS && return 1
679 }
680 
681 
682 # Pruefe, ob eine Liste ein bestimmtes Element enthaelt
683 # Die Listenelemente sind durch beliebigen Whitespace getrennt.
684 is_in_list() {
685  local target="$1"
686  local list="$2"
687  local token
688  for token in $list; do
689  [ "$token" = "$target" ] && return 0 || true
690  done
691  # kein passendes Token gefunden
692  trap "" $GUARD_TRAPS && return 1
693 }
694 
695 
696 # Liefere den Inhalt einer Variable zurueck.
697 # Dies ist beispielsweise fuer lua-Skripte nuetzlich, da diese nicht den shell-Namensraum teilen.
698 # Paramter: Name der Variable
699 get_variable() {
700  local var_name="$1"
701  eval "echo \"\$$var_name\""
702 }
703 
704 
705 # Pruefe, ob die angegebene Funktion definiert ist.
706 # Dies ersetzt opkg-basierte Pruefungen auf installierte opennet-Firmware-Pakete.
707 is_function_available() {
708  local func_name="$1"
709  # "ash" liefert leider nicht den korrekten Wert "function" nach einem Aufruf von "type -t".
710  # Also verwenden wir die Textausgabe von "type".
711  # Die Fehlerausgabe von type wird ignoriert - im Falle der bash gibt es sonst unnoetige Ausgaben.
712  type "$func_name" 2>/dev/null | grep -q "function$" && return 0
713  trap "" $GUARD_TRAPS && return 1
714 }
715 
716 
717 ## @fn get_local_bias_numer()
718 ## @brief Ermittle eine lokale einzigartige Zahl, die als dauerhaft unveränderlich angenommen werden kann.
719 ## @returns Eine (initial zufällig ermittelte) Zahl zwischen 0 und 10^8-1, die unveränderlich zu diesem AP gehört.
720 ## @details Für ein paar gleichrangige Sortierungen (z.B. verwendete
721 ## UGW-Gegenstellen) benötigen wir ein lokales Salz, um strukturelle
722 ## Bevorzugungen zu vermeiden.
723 get_local_bias_number() {
724  trap "error_trap get_local_bias_number '$*'" $GUARD_TRAPS
725  local bias=$(uci_get on-core.settings.local_bias_number)
726  # der Bias-Wert ist schon vorhanden - wir liefern ihn aus
727  if [ -z "$bias" ]; then
728  # wir müssen einen Bias-Wert erzeugen: beliebige gehashte Inhalte ergeben eine akzeptable Zufallszahl
729  bias=$( (logread; dmesg; ps; date) | md5sum | tr "abcdef" "012345" | cut -c 1-8)
730  uci set "on-core.settings.local_bias_number=$bias"
731  uci commit on-core
732  fi
733  echo -n "$bias" && return 0
734 }
735 
736 
737 ## @fn system_service_check()
738 ## @brief Prüfe ob ein Dienst läuft und ob seine PID-Datei aktuell ist.
739 ## @param executable Der vollständige Pfad zu dem auszuführenden Programm.
740 ## @param pid_file Der Name einer PID-Datei, die von diesem Prozess verwaltet wird.
741 ## @deteils Dabei wird die 'service_check'-Funktion aus der openwrt-Shell-Bibliothek genutzt.
742 system_service_check() {
743  local executable="$1"
744  local pid_file="$2"
745  . /lib/functions/service.sh
746  SERVICE_PID_FILE="$pid_file"
747  set +eu
748  service_check "$executable" && return 0
749  trap "" $GUARD_TRAPS && return 1
750 }
751 
752 
753 ## @fn get_memory_size()
754 ## @brief Ermittle die Größe des Arbeitsspeichers in Megabyte.
755 ## @returns Der Rückgabewert (in Megabyte) ist etwas kleiner als der physische Arbeitsspeicher (z.B. 126 statt 128 MB).
756 get_memory_size() {
757  local memsize_kb=$(grep "^MemTotal:" /proc/meminfo | sed 's/[^0-9]//g')
758  echo $((memsize_kb / 1024))
759 }
760 
761 
762 run_parts() {
763  trap "error_trap run_parts '$*'" $GUARD_TRAPS
764  local rundir="$1"
765  local fname
766  find "$rundir" -maxdepth 1 | while read fname; do
767  # ignoriere verwaiste symlinks
768  [ ! -f "$fname" ] && continue
769  msg_debug "on-run-parts: executing $fname"
770  # ignoriere Fehler bei der Ausfuehrung
771  "$fname" || true
772  done
773 }
774 
775 # Ende der Doku-Gruppe
776 ## @}