Ereignisprotokoll der FRITZ!Box auf Linux-Server sichern

T

tgs-bonn

Guest
Seit ich FRITZ!Boxen verwende, vermisse ich schmerzlich die Fähigkeit die Systemereignisse über das standardisierte Syslog-Protokoll an meinen zentralen Logserver (sprich: meinen Linux-PC) zu senden. Recherchen in diese Richtung ergaben, dass ohne Eingriffe in die Firmware, die den AVM-Support erlöschen lassen, da nichts zu machen ist.

Als Alternative habe ich mir angeregt durch einige Tipps im Internet das angehängte bash-Skript gestrickt, das den aktuellen Inhalt des Ereignisprotokolls von der FRITZ!Box herunterlädt und entweder auf die Standardausgabe oder in eine Datei schreibt, wahlweise als Textdatei, im JSON-Format oder den rohen HTML-Text der Druckansicht. Das Skript benötigt neben der bash lediglich die Standard-Tools curl, iconv, md5sum, jq, expr und cat. Getestet mit meiner FRITZ!Box 7490, einer 7362SL und einer 3370.
Beispielaufruf:
Code:
fritz-syslog.sh -p GeHeim -o fritzlog.txt
speichert die Ereignisliste der unter fritz.box erreichbaren FRITZ!Box im Textformat in die Datei fritzlog.txt.

Was mich noch stört, ist dass ich bei wiederholtem Aufruf immer wieder auch alte Ereignisse mitbekomme, die schon beim letzten Mal dabei waren. (Außerdem dass die Einträge in umgekehrter Reihenfolge sind, aber das lässt sich ja noch leicht beheben.) Ich würde gerne das Skript per cron periodisch aufrufen und jedesmal die neue Liste mit den früher abgerufenen Daten zusammenführen, sodass so etwas wie ein synthetisches Syslog entsteht. Wenn da jemand Ideen hat, wie das am besten zu bewerkstelligen ist, immer her damit. Bisher bin ich mir noch nicht einmal schlüssig, ob das besser mit dem Textformat oder mit JSON geht.
 

Anhänge

  • fritz-syslog-1.0.zip
    2.4 KB · Aufrufe: 168
Zuletzt bearbeitet von einem Moderator:
Verbesserungsvorschlag ... das spricht sich immer alles nur sehr langsam im Internet herum.

Wenn Du anstelle von
Code:
curl -s "${FBURL}/system/syslog.lua" -d 'stylemode=print' -d 'sid='${_SID} |
einfach eine andere URL verwendest
Code:
curl -s "${FBURL}/query.lua" -d 'mq_log=logger:status/log' -d 'sid='${_SID} |
, bleibt Dir zwar Dein "text"-Format vorenthalten, dafür erhältst Du aber auch nur genau die Log-Daten als JSON-Struktur und nicht den ganzen Ballast, der da sonst noch so in der Seite in der <pre>-section steht (die künftigen Probleme ab 06.35 mal noch gar nicht berücksichtigt, da funktioniert Dein Link u.U. schon nicht mehr).

Ansonsten würde ich einfach die Daten aus der JSON-Struktur parsen (das ist immer der Text, die Nachrichtennummer und die Kategorie) und über alle drei Felder einen Hash-Wert bilden (da reicht schon MD5), den man als Index in einer DB verwenden kann, um doppelte Einträge zu erkennen. Echte Dubletten können eigentlich nur dann auftreten, wenn die Box keine gültige Uhrzeit hat und Daten vom 01.01.1970 ausgibt.

Ansonsten das alles in eine kleine sqlite3-Datenbank geschrieben (quasi aus dem JSON-File eine kleine Datei mit SQL-Kommandos erstellt) und die Suche nach doppelten Einträgen nimmt Dir die Datenbank dann schon von sich aus ab, wenn man da einen "unique index" definiert und die Fehler beim Einfügen einfach ignoriert.

Am Ende hast Du eine DB mit einer Tabelle mit 45 Feldern (EDIT: das Datum sollte man natürlich auch als solches getrennt in einem Feld speichern und nicht im Nachrichtentext), die Du in jeder gewünschten Weise filtern, umformen und ausgeben kannst. Wird Dir das Log zu groß und zu langweilig, löschst Du einfach alle Daten in der Tabelle (truncate table) oder nimmst eine neue DB, deren Schema Du aus der alten kopieren läßt.

Das ist sicherlich nicht die platzsparendste Art der Aufbewahrung (die macht die Box schon selbst, aber da willst Du ja nicht direkt ran ... ansonsten habe ich das auch schon mal irgendwo beschrieben - da hilft event. die Suche nach "/var/.ar7events"), aber das ist ja auch eher nicht Deine Sorge. Wenn Du das als cron-Job laufen lassen willst, solltest Du Dir einen Mechanismus zum Cachen der SID überlegen. Ansonsten müllst Du Dir nur selbst das Log mit "Anmeldung von"-Einträgen zu.
 
Zuletzt bearbeitet:
Hallo PeterPawn,

danke für das hilfreiche Feedback.

Verbesserungsvorschlag ... das spricht sich immer alles nur sehr langsam im Internet herum.
Ja, ich hatte auch ziemliche Mühe, mir die Infos zusammenzugoogeln.

Wenn Du anstelle von
Code:
curl -s "${FBURL}/system/syslog.lua" -d 'stylemode=print' -d 'sid='${_SID} |
einfach eine andere URL verwendest
Code:
curl -s "${FBURL}/query.lua" -d 'mq_log=logger:status/log' -d 'sid='${_SID} |
, bleibt Dir zwar Dein "text"-Format vorenthalten, dafür erhältst Du aber auch nur genau die Log-Daten als JSON-Struktur und nicht den ganzen Ballast, der da sonst noch so in der Seite in der <pre>-section steht
Gerade das Textformat wäre mir halt das liebste. Da kann ich mit den Linux-Standardtools drauf operieren, die ich auch für die Auswertung der anderen Logs nutze.

JSON ist mir nicht so geläufig. Javascript ist nicht meine Programmierheimat und meine erste Suche nach einem simplen Kommandozeilen-Konverter JSON -> Text war nicht sehr erfolgreich.

Die von Dir vorgeschlagene Query liefert bei mir übrigens immer nur "{ }" (auf drei Zeilen verteilt). Ist da irgendwo ein Tippfehler drin?

(die künftigen Probleme ab 06.35 mal noch gar nicht berücksichtigt, da funktioniert Dein Link u.U. schon nicht mehr)
Das Problem ist mir bewusst. Das ist der Nachteil, wenn man mit undokumentierten und reverse-engineerten Schnittstellen arbeitet. Eine dokumentierte und stabile Schnittstelle wäre natürlich schöner, aber nur bis zu einer bestimmten Aufwandsgrenze. Das simple kleine Projekt "Syslog der FRITZ!Box sichern" ufert gerade verflixt aus.

Ist denn /query.lua eine solche dokumentierte und stabile Schnittstelle? Wo finde ich Informationen darüber?

Ansonsten würde ich einfach die Daten aus der JSON-Struktur parsen (das ist immer der Text, die Nachrichtennummer und die Kategorie) und über alle drei Felder einen Hash-Wert bilden (da reicht schon MD5), den man als Index in einer DB verwenden kann, um doppelte Einträge zu erkennen.
Gibt's da irgendwo ein Kochrezept für? JSON sieht nicht so aus, als ob man es mit sed, awk oder grep parsen sollte, und eine komplette Datenbankanwendung entwickeln zu müssen, nur um ein Log zu speichern, erschreckt mich ehrlich gesagt etwas.

Echte Dubletten können eigentlich nur dann auftreten, wenn die Box keine gültige Uhrzeit hat und Daten vom 01.01.1970 ausgibt.
Beim Arbeiten mit dem Textformat gibt es die leider doch. Ich musste feststellen, dass die Zeitstempel ein und desselben Log-Eintrags von Abruf zu Abruf öfter mal um eine Sekunde variieren. Ob das bei JSON auch passiert habe ich noch nicht gecheckt. (Mangels JSON-Tools.)

Wenn Du das als cron-Job laufen lassen willst, solltest Du Dir einen Mechanismus zum Cachen der SID überlegen. Ansonsten müllst Du Dir nur selbst das Log mit "Anmeldung von"-Einträgen zu.
Guter Tipp. Die Einträge sind mir auch schon aufgefallen. Wie lange gilt denn so eine SID?

Thx
T.
 
Zuletzt bearbeitet von einem Moderator:
Ich musste feststellen, dass die Zeitstempel ein und desselben Log-Eintrags von Abruf zu Abruf öfter mal um eine Sekunde variieren. Ob das bei JSON auch passiert habe ich noch nicht gecheckt. (Mangels JSON-Tools.)
Habe ich jetzt per "Wetware-Parser" verifiziert: es passiert auch im JSON-Format.:(
 
@tgs-bonn:
Die Differenzen im Zeitstempel resultieren daraus, daß die interne Speicherung dieses Wertes nur als Differenz zu einem Startwert erfolgt. Kriegt die Box dann irgendwann später eine gültige Uhrzeit, wird nur dieser Startwert geändert. Je nachdem, ob man die Hälfte einer Sekunde dann bei der Abfrage des Logs nach oben oder nach unten erwischt, wird dann gerundet und daraus ergibt sich dieses Bild.

Ich habe früher mal das Parsen einer solchen Datei in "bash" realisiert (generell dieser JSON-Strukturen von AVM):
Code:
#!/bin/bash 
#
# exit code meanings 
#
# bit 0   (1) => error encountered, read the associated error message
# bit 1   (2) => login failed
# bit 2   (4) => network communication error
# bit 3   (8) => missing required parameter or option
# bit 4  (16) => configuration file error
# bit 5  (32) => invalid call to script, read the error message for explanation, usage help was shown
# bit 6  (64) => internal error during processing
# bit 7 (128) =>

# we'll check the presence of needed commands only, not their versions
check_executables()
{
	lines=""
	cmds=""
	while read line; do
		if [ "${line:1:1}" != "#" ]; then
			name=${line%%=*}
			eval $line
			cmds="$cmds $name"
			builtin echo $line
		fi
	done
	for cmd in $cmds; do
		eval executable=\$$cmd
		if [ ! -x $executable ]; then
			builtin echo "Fatal error: file '$executable' is missing or not executable." 1>&2
			return 255
		fi
	done
	return 0
}

# our needed external commands (even if some could be available as internal bash commands - remove the absolute path for such commands)
source <(check_executables <<'###END_AUTO_CHECK###'
###BEGIN_AUTO_CHECK###
GREP="/usr/bin/grep"
SED="/usr/bin/sed"
TAIL="/usr/bin/tail"
MKTEMP="/usr/bin/mktemp"
SORT="/usr/bin/sort"
CUT="/usr/bin/cut"
CAT="/usr/bin/cat"
RM="/usr/bin/rm"
GETOPT="/usr/bin/getopt"
###END_AUTO_CHECK###
)

pj_usage()
{
	if [ "$BASH_SUBSHELL" -eq 0 ]; then
		[ -n "$*" ] && builtin echo -n -e "$*" 1>&2
		$CAT 1>&2 <<EOT
(C) 2012 PeH Consulting - Peter H.

Parse JSON output from queries to AVM Fritz!OS web server

Usage:

parseJSON [ option [...] ] JSONFILE

The following options are available:
-h, --help
    + display that help
-d, --debug
    + display some debug messages
-q, --quiet
    + do not display error messages
-s, --scalar
    + ignore JSON arrays
-o, --one-value NAME
    + return only the single scalar value for entry NAME, implies -s option
-c, --count NAME
    + count only the number of entries of array NAME
-a, --array NAME
    + parse only the array with the specified NAME
-i, --index N
    + parse only the Nth single entry of an array and handle it like a scalar list
-D, --dictionary NAME
    + create a dictionary (associative array) with the specified NAME instead of a simple list of
      key/value assignment, implies -s
EOT
		return 32
	else
		builtin echo "invalid call to $0, check your code please" 1>&2
		return 32
	fi
}

# pj_split_json jsonfile workdir
pj_split_json()
{
	local jsf tf line quot arname
	local -a jsonarray
	local -i count i start end from to
	[ -z "$1" ] && return 255 # missing file name
	jsf="$1"
	shift
	[ -z "$1" ] && return 255 # missing working directory
	tf="$1"
	shift
	mapfile -t < <($SED -e '/^\s*"[^"]*"\s*:\s*\[$/=' -e '/^\s*\],\?\s*$/=' -n $jsf) 
	count=0
	for i in ${MAPFILE[*]}; do
		if [ -z "${jsonarray[$count]}" ]; then
			jsonarray[$count]="$i"
		else
			jsonarray[$count]="${jsonarray[$count]} $i"
			(( count += 1 ))
		fi
	done
	[ -n "${jsonarray[$count]}" ] && return 2 # odd number of lines from sed
	from=1
	for (( count=0; count<${#jsonarray[*]}; count++ )); do
		start="${jsonarray[$count]%% *}"
		end="${jsonarray[$count]##* }"
		line=$($SED -e "$start p" -n $jsf)
		quot=$'\x22'
		[[ "$line" =~ ^[[:space:]]+$quot([^$quot]*)$quot.*$ ]]
		[ "$?" -ne 0 ] && continue # unable to detect JSON array name
		arname="${BASH_REMATCH[1]}"
		(( to = start - 1 ))
		$SED -e "$from,$to w $tf/scalar_append.json" -e "$start,$end w $tf/array.$arname.json" -n $jsf
		$CAT $tf/scalar_append.json >> $tf/scalar.json
		(( from = end + 1 ))
	done
	$SED -e "$from,$ w $tf/scalar_append.json" -n $jsf
	$CAT $tf/scalar_append.json >> $tf/scalar.json
	$RM -f $tf/scalar_append.json
}

# pj_parse_scalar jsonfile
pj_parse_scalar()
{
	local jfn names
	[ -z "$1" ] && return 255 # missing json file name
	jfn="$1"
	shift
	names=$($SED -e "s/^\s*\"\(\w*\)\"\s\?:\s*.*$/\1/p" -n $jfn)
	nl=$'\n'
	names="${names//$nl/ }"
	echo $names
}

# pj_parse_array_count arrayfile
pj_parse_array_count()
{
	local afn
	local -a jsonarray
	local -i count i	
	[ -z "$1" ] && return 255 # missing array file name
	afn="$1"
	shift
	mapfile -t < <($SED -e '/^\s*{$/=' -e '/^\s*},\?$/=' -n $afn)
	count=0
	for i in ${MAPFILE[*]}; do
		if [ -z "${jsonarray[$count]}" ]; then
			jsonarray[$count]="$i"
		else
			jsonarray[$count]="${jsonarray[$count]} $i"
			(( count += 1 ))
		fi
	done
	[ -n "${jsonarray[$count]}" ] && return 2 # odd number of lines from sed
	echo $count
	return 0
}

# pj_parse_array arrayfile
pj_parse_array()
{
	local afn quot arrayname 
	local -i count i entries from to
	local -a jsonarray
	[ -z "$1" ] && return 255 # missing array file name
	afn="$1"
	shift
	mapfile -t < <($SED -e '/^\s*{$/=' -e '/^\s*},\?$/=' -n $afn)
	count=0
	for i in ${MAPFILE[*]}; do
		if [ -z "${jsonarray[$count]}" ]; then
			jsonarray[$count]="$i"
		else
			jsonarray[$count]="${jsonarray[$count]} $i"
			(( count += 1 ))
		fi
	done
	[ -n "${jsonarray[$count]}" ] && return 2 # odd number of lines from sed
	entries=$count
	arrayname=$($SED -e 's/^\s*\"\(\w*\)\"\s*:\s*\[$/\1/p' -n $afn)
	quot=$'\x22'
	echo "declare -i ${arrayname}_count=$entries"
	for (( count=0; count < $entries; count++ )); do
		echo -n "declare -A ${arrayname}_${count}=( "
		set -- ${jsonarray[$count]}
		(( from=$1 + 1, to=$2 - 1 ))
		while read line; do
			if [[ "$line" =~ ^([^=]*)=$quot(.*)$quot$ ]]; then
				name="${BASH_REMATCH[1]}"
				value="${BASH_REMATCH[2]}"
				value="$(pj_bash_encode "$value")"
				echo -n "['$name']=$value "
			fi
		done < <($SED -e "$from,$to s/^\s*\"\(\w*\)\"\s*:\s*\"\(.*\)\",\?\$/\1=\"\2\"/p" -n $afn | $SORT)
		echo ")"
	done
}

# pj_parse_array_single arrayfile index
pj_parse_array_single()
{
	local afn ind range dict name names valueline quot value
	local -a jsonarray
	local -i count i start end
	[ -z "$1" ] && return 255 && echo ":" # missing array file name
	afn="$1"
	shift
	[ -z "$1" ] && return 255 && echo ":" # missing index value
	ind="$1"
	shift
	mapfile -t < <($SED -e '/^\s*{$/=' -e '/^\s*},\?$/=' -n $afn)
	count=0
	for i in ${MAPFILE[*]}; do
		if [ -z "${jsonarray[$count]}" ]; then
			jsonarray[$count]="$i"
		else
			jsonarray[$count]="${jsonarray[$count]} $i"
			(( count += 1 ))
		fi
	done
	[ -n "${jsonarray[$count]}" ] && return 2 # odd number of lines from sed
	[ $ind -ge $count ] && return 1 # index out of bounds
	tf=$($MKTEMP)
	range="${jsonarray[$ind]}"
	start="${range%% *}"
	end="${range##* }"
	(( start++, end-- ))
	$SED -e "$start,$end w $tf" -n $afn
	names=$(pj_parse_scalar $tf)
	names=$(pj_sort_names "$names")
	declare -A result
	for name in $names; do 
		valueline=$($SED -e "/^\s*\"$name\"\s\?:/ p" -n $tf)
		if [ -n "$valueline" ]; then
			quot=$'\x22'
			[[ "$valueline" =~ ^[[:space:]]*$quot$name$quot[[:space:]]?:[[:space:]]*$quot(.*)$quot,?$ ]]
			value="${BASH_REMATCH[1]}"
			result[$name]="$value"
		fi
	done
	dict="declare -A result=("
	for name in $(pj_sort_names "${!result[@]}"); do
		value="${result[$name]}"
		value="$(pj_bash_encode "$value")"
		dict="$dict ['$name']=$value"
	done
	dict="$dict )"
	echo "$dict"
	rm -f $tf >/dev/null
	return 0
}

# pj_sort_names
pj_sort_names()
{
	local tf=$(mktemp) name names
	for name in $@; do
		names="$names$name\n" 
	done
	echo -e -n "$names" | $SORT >$tf
	names=""
	while read line; do
		names="$names $line"
	done <$tf
	rm -f $tf &>/dev/null
	echo $names
}

# pj_bash_encode string
pj_bash_encode()
{
	local string
	[ -z "$1" ] && return 255 # string missing
	string="$1"
	string="${string//\$/\x24}"
	string="${string//\"/\x22}"
	string="${string//\`/\x60}"
	[ "$string" == "$1" ] && echo "\"$1\"" || echo "\$'$string'"
}

# pj_truefalse
pj_truefalse()
{
	[ "$1" -eq 0 ] && echo -n "true" || echo -n "false"
}

rc=0
myself="${0##*/}"

tf=$($MKTEMP)
options=$($GETOPT -o hdso:qD:c:a:i: --long help,debug,scalar,one-value:,quiet,dictionary:,count:,array:,index: -n ${myself} -s bash -- "$@" 2>$tf)
rc=$?

if [ $rc -ne 0 ]; then
	emsg="$($CAT $tf)"
	pj_usage "$emsg\n\n"
	rc=$?
fi
$RM -f $tf &>/dev/null

[ "$rc" -ne 0 ] && exit $rc

eval set -- "$options"

debug=1
scalar=1
onevalue=1
dictionary=1
arraycount=1
array=1
arraysingle=1
quiet=1
dictname=""
arrayname=""
arrayindex=""

while true; do
	case "$1" in
		("-o"|"--one-value")
			scalar=0
			onevalue=0
			name="$2"
			shift 2
			;;
		("-s"|"--scalar")
			scalar=0
			shift
			;;
		("-D"|"--dictionary")
			dictionary=0
			dictname="$2"
			scalar=0
			shift 2
			;;
		("-c"|"--count")
			arraycount=0
			arrayname="$2"
			shift 2
			;;
		("-a"|"--array")
			array=0
			arrayname="$2"
			shift 2
			;;
		("-i"|"--index")
			arraysingle=0
			arrayindex="$2"
			shift 2
			;;
		("-q"|"--quiet")
			quiet=0
			shift
			;;
		("-d"|"--debug")
			debug=0
			shift
			;;
		("-h"|"--help")
			pj_usage 
			exit $?
			;;
		("--")
			shift
			break
			;;
		("*")
			builtin echo -n -e "Internal error processing command line !\n\n"
			exit 64
			;;
	esac
done

if [ -z "$1" ]; then
	[ "$quiet" -eq 1 ] && echo "Missing JSONFILE parameter !" 1>&2
	exit 8
fi
jsonfile="$1"
shift

if [ -z "$name" ]; then
	names="$*"
else
	names="$name"
fi

if [ "$debug" -eq 0 ]; then
	echo "Debug display: command line parameters and options" 1>&2
	echo "JSON-file=$jsonfile" 1>&2
	echo "onevalue=$(pj_truefalse $onevalue)" 1>&2
	[ "$onevalue" -eq 0 ] && echo "onevalue-name=$name" 1>&2
	echo "scalar=$(pj_truefalse $scalar)" 1>&2
	echo "dictionary=$(pj_truefalse $dictionary)" 1>&2
	[ "$dictionary" -eq 0 ] && echo "dictionary-name=$dictname" 1>&2
	echo "count=$(pj_truefalse $arraycount)" 1>&2
	[ "$arraycount" -eq 0 ] && echo "array-name=$arrayname" 1>&2
	echo "array=$(pj_truefalse $array)" 1>&2
	[ "$array" -eq 0 ] && echo "array-name=$arrayname" 1>&2
	echo "index=$(pj_truefalse $arraysingle)" 1>&2
	[ "$arraysingle" -eq 0 ] && echo "array-index=$arrayindex" 1>&2
	echo "quiet=$(pj_truefalse $quiet)" 1>&2
	[ "$onevalue" -eq 1 ] && echo "name(s)=$names" 1>&2
	echo "Debug display: end of parameters and options" 1>&2
	echo "============================================" 1>&2
fi

if [ ! -r $jsonfile ]; then
	[ "$quiet" -eq 1 ] && echo "File '$jsonfile' not found or access is denied !" 1>&2
	exit 4 # JSON file not found or not accessible
fi

if [ "$dictionary" -eq 0 ] && [ -z "$dictname" ]; then
	[ "$quiet" -eq 1 ] && echo "Missing dictionary name after -D option !" 1>&2
	exit 8
fi

if [ "$arraycount" -eq 0 ] && [ -z "$arrayname" ]; then
	[ "$quiet" -eq 1 ] && echo "Missing array name after -c option !" 1>&2
	exit 8
fi

if [ "$array" -eq 0 ] && [ -z "$arrayname" ]; then
	[ "$quiet" -eq 1 ] && echo "Missing array name after -a option !" 1>&2
	exit 8
fi

if [ "$arraysingle" -eq 0 ] && [ -z "$arrayindex" ]; then
	[ "$quiet" -eq 1 ] && echo "Missing array index after -i option !" 1>&2
	exit 8
fi

if [ "$onevalue" -eq 0 ] && [ -n "$*" ]; then
	[ "$quiet" -eq 1 ] && echo "Ambigious value name(s) '$*' while using -o option !" 1>&2
	exit 8
fi

if [ "$array" -eq 0 ]; then
	if [ "$scalar" -eq 0 ] || [ "$onevalue" -eq 0 ] || [ "$arraycount" -eq 0 ]; then
		[ "$quiet" -eq 1 ] && echo "Options -s, -o and -c are incompatible with -a option !" 1>&2
		exit 8
	fi
fi

if [ "$arraycount" -eq 0 ]; then
	if [ "$scalar" -eq 0 ] || [ "$onevalue" -eq 0 ] || [ "$array" -eq 0 ] || [ "$dictionary" -eq 0 ]; then
		[ "$quiet" -eq 1 ] && echo "Options -s, -o, -D and -a are incompatible with -c option !" 1>&2
		exit 8
	fi
fi

if [ "$arraysingle" -eq 0 ]; then
	if [ "$array" -eq 1 ]; then
		[ "$quiet" -eq 1 ] && echo "Option -i is only valid in combination with -a option !" 1>&2
		exit 8
	fi
	if ! [[ "$arrayindex" =~ ^[[:digit:]]+$ ]]; then
		[ "$quiet" -eq 1 ] && echo "Array index value after -i option needs to be a positive number !" 1>&2
		exit 8
	fi
fi

tf=$($MKTEMP)
$CAT $jsonfile >$tf
td=$($MKTEMP -d)

pj_split_json $tf $td

if [ -z "$names" ] && [ "$array" -eq 1 ] && [ "$arraysingle" -eq 1 ] && [ "$arraycount" -eq 1 ]; then
	names=$(pj_parse_scalar $td/scalar.json)
	if [ "$scalar" -eq 1 ]; then
		for arname in $td/array.*.json; do
			if [ -r $arname ]; then
				if [[ "$arname" =~ ^$td/array\.(.*)\.json$ ]]; then
					names="$names ${BASH_REMATCH[1]}"
				fi
			fi
		done
	fi
fi

declare -A result

quot=$'\x22'
squot=$'\x27'
if [ -n "$names" ]; then
	for name in $names; do 
		irc=0
		valexists=1
		if [ "$scalar" -eq 1 ]; then
			for arname in $td/array.*.json; do
				if [[ "$arname" =~ ^$td/array\.$name\.json$ ]]; then
					if [ "$scalar" -eq 0 ]; then
						[ "$quiet" -eq 1 ] && echo "'$name' found, but it's an array !" 1>&2
						irc=2 # arrays can't be returned as scalar value
					else
						value="$($0 -a $name $jsonfile)"
						name="${name}_JSON_ARRAY"
					fi
					valexists=0
					break
				fi
			done
		fi
		if [ "$valexists" -eq 1 ]; then
			valueline=$($SED -e "/^\s*\"$name\":/ p" -n $td/scalar.json)
			if [ -z "$valueline" ]; then
				[ "$quiet" -eq 1 ] && echo "Value with name '$name' not found !" 1>&2
				irc=1 # scalar value not found
			else
				irc=0 # scalar value found, will be written to stdout
				quot=$'\x22'
				[[ "$valueline" =~ ^[[:space:]]*$quot$name$quot:[[:space:]]*$quot(.*)$quot,?$ ]]
				value="${BASH_REMATCH[1]}"
				value="$(pj_bash_encode "$value")"
				valexists=0
			fi
		fi
		if [ "$valexists" -eq 0 ]; then
			if [ "$onevalue" -eq 1 ]; then
				result[$name]="$value"
			else
				eval value="$value" 
				echo $value
				break
			fi
		fi
		[ "$irc" -gt "$rc" ] && rc=$irc
	done
else
	if [ "$arraycount" -eq 0 ] || [ "$array" -eq 0 ]; then
		if [ -r $td/array.$arrayname.json ]; then
			if [ "$arraysingle" -eq 0 ]; then
				tf2=$($MKTEMP)
				pj_parse_array_single $td/array.$arrayname.json $arrayindex >$tf2
				rc=$?
				source $tf2
				rm -f $tf2 &>/dev/null
				if [ "$rc" -ne 0 ]; then
					[ "$quiet" -eq 1 ] && echo "Array index '$arrayindex' is out of bounds !" 1>&2
					rc=1 # array index out of bounds
					array=0 # suppress output
				else
					rc=0 # entry found
					array=1 # handle it as scalar list
				fi
			elif [ "$arraycount" -eq 0 ]; then
				res=$(pj_parse_array_count $td/array.$arrayname.json)
				echo "$res"
				rc=0
				array=0 # suppress additional output
			else
				res=$(pj_parse_array $td/array.$arrayname.json)
				echo "$res"
				rc=0
				array=0 # suppress additional output
			fi
		else
			[ "$quiet" -eq 1 ] && echo "Array with name '$arrayname' not found !" 1>&2
			rc=1
		fi
	fi
fi

if [ "$dictionary" -eq 1 ]; then
	if [ "$array" -eq 1 ]; then
		names=$(pj_sort_names ${!result[@]})
		echo -n "" >$tf
		for name in $names;do
			arvalue=1
			if [ "${#name}" -ge 12 ]; then
				if [ "${name:0,-11}" == "_JSON_ARRAY" ]; then
					arvalue=0
				fi
			fi
			if [ "$arvalue" -eq 1 ]; then
				echo -n "$name=" >>$tf
				value="${result[$name]}"
				[ "$arraysingle" -eq 0 ] && value="$(pj_bash_encode "$value")"
				echo "$value" >>$tf
			else
				value="${result[$name]}"
				echo "$value" >>$tf
			fi
		done
		$CAT $tf
	fi
else
	if [ "$array" -eq 1 ]; then
		dict="declare -A $dictname=("
		names=$(pj_sort_names ${!result[@]})
		for name in $names; do
			arvalue=1
			if [ "${#name}" -ge 12 ]; then
				if [ "${name:0,-11}" == "_JSON_ARRAY" ]; then
					arvalue=0
				fi
			fi
			if [ "$arvalue" -eq 1 ]; then
				value="${result[$name]}"
				dict="$dict ['$name']=$value"
			fi
		done
		dict="$dict )"
		echo "$dict"
	fi
fi

$RM -rf $td $tf &>/dev/null

exit $rc
(ist schon etwas älter, sollte trotzdem funktionieren)

Die "Schnittstelle" mit der "query.lua" faßt AVM wohl bei dieser Änderung auf 06.35 nicht an ... sie ist ja auch eher harmlos, da dort nur Abfragen möglich sind und das Login-Erfordernis Sicherheit genug bietet (das GUI ist auch nicht sicherer). Insofern ist das mit der "query.lua" in jedem Falle "stabiler" als das Parsen aus dem HTML-Code.

Das Auswerten der Zeitstempel (das geht im einfachsten Fall mit "date") kann man auch mathematisch soweit "entschärfen", daß da insgesamt drei Werte (der eigentliche, der +1 und der -1) als identisch angenommen werden, denn der unterschiedliche Hashwert sollte sich schon aufgrund des Text-Inhalts (den man dann natürlich um den Timestamp kürzt) ergeben.

Das ist alles mit (sagen wir mal) 100-150 Zeilen zusätzlichem Shell-Code erledigt - wobei ich trotzdem auf die Sicherung der /var/.ar7events setzen würde. Der "Eingriff" in das FRITZ!OS ist minimal (im besten Falle stellt man auf einem passenden Port nur diese Datei als Ausgabe zur Verfügung bei jedem neuen Request), die Speicherung erfolgt ziemlich effektiv (der Nachrichtentext der zugehörigen Nummer wird ja erst bei der Ausgabe im GUI oder mit "eventsdump" mit den Variablen zusammengeführt) und man kann anhand des Inhalts recht gut einen "rollover" erkennen und tatsächlich nur jede Nachricht ein einziges Mal speichern (da hat man auch das Problem der einen Sekunde Abweichung nicht).

Eine SID ist ohne Abmeldung 3600 Sekunden ab dem letzten Request mit dieser SID gültig. Wenn man den minimalen Eingriff wagt (Du hast ja eine 7490, da ist der "Support-Verlust" ja praktisch nicht vorhanden, wenn Du in der alternativen Partition dieselbe Version ohne Modifikationen betreibst) und die .ar7events nur über einen Port zugänglich macht (die enthält ja keine wirklich sicherheitsrelevanten Informationen und man könnte das ja auch auf eine einzelne IP mit Berechtigung zur Abfrage beschränken), braucht man - je nach eigener Anschauung - nicht mal mehr eine Authentifizierung. Die Informationen aus diesem Log (IP- und MAC-Adressen + event. gültige Nutzernamen) kriegt ein Lauscher im LAN auch an anderen Stellen einfacher heraus - insofern enthält das (dort natürlich nicht änderbare) Log wenig bis keine wirklich neuen Informationen für einen Angreifer im LAN.


Anhang anzeigen 83089
 
Zuletzt bearbeitet:
Die von Dir vorgeschlagene Query liefert bei mir übrigens immer nur "{ }" (auf drei Zeilen verteilt). Ist da irgendwo ein Tippfehler drin?
Das habe ich jetzt erst so richtig zur Kenntnis genommen ... eine korrekte URL (getestet auf 113.06.30) sieht so aus:

http://fritz.box/query.lua?sid=<yoursid>&mq_log=logger:status/log

Da ich das nicht innerhalb des Skripts getestet habe, könnte es sein, daß sich "curl" etwas anstellt bei dem Doppelpunkt in der Querystring ... aber das kriegst Du selbst heraus. Ansonsten - sollte sich die URL mit einer gültigen SID bei Dir nicht aufrufen lassen - bräuchte ich schon genauere Angaben.

Ist denn /query.lua eine solche dokumentierte und stabile Schnittstelle? Wo finde ich Informationen darüber?
Jein. Sie ist im Quelltext der Lua-Seite halbwegs dokumentiert und greift ansonsten auch nur auf das Lua-Variableninterface zurück (das man auf der Box auch selbst verwenden kann). Allerdings ist es tatsächlich keine offiziell benannte Schnittstelle ... die existiert m.W. (zumindest bisher) aber für das Eventlog auch nicht bzw. früher hatte das "GetDeviceLog" auf der deviceinfoSCPD.xml nicht so richtig funktioniert (abgesehen vom Ausgabeformat, was mir persönlich nicht schmeckt). Vielleicht ist das mit dem Funktionieren ja inzwischen anders ... ein einfacher SOAP-Request schafft da schon Aufklärung.
 
Ich fürchte, jetzt hast Du mich endgültig abgehängt.
  • Ich habe keine Ahnung, wie ich "deviceinfoSCPD.xml" nutzen könnte und wie ich einen "einfachen SOAP-Request" mache, um aufzuklären, ob dessen "GetDeviceLog" nun richtig funktioniert oder nicht.
  • Ich habe den Quelltext der Lua-Seite nicht, weiß auch nicht wo ich ihn herbekomme, und wenn, könnte ich ihn wahrscheinlich nicht lesen, da ich kein LUA spreche.
  • Mit der Programmierung der Box selber habe ich keine Erfahrung und wollte da aktuell eigentlich auch nicht einsteigen.
  • Wie ich meine Fritzbox manipulieren müsste, damit ich diese Datei /var/.ar7events auf einem bestimmten Port abrufen kann, habe ich auch noch nicht verstanden.
  • Auch wie ich diese Datei nach dem Abruf dann interpretiere bzw. wo ihr Format dokumentiert ist, weiß ich bis jetzt nicht.
  • Wie ich analysieren kann, warum der Abruf mit query.lua nicht funktioniert, ist mir auch nicht klar.
  • Ich stelle nur fest, dass
    Code:
    curl "http://fritz.box/query.lua" -d 'mq_log=logger:status/log' -d 'sid='${_SID}
    bei meiner Fritzbox als Ergebnis immer nur eine leere Liste produziert, während
    Code:
    curl "http://fritz.box/query.lua?mq_log=logger:status/log&sid=${_SID}"
    mit derselben SID korrekt das Log im JSON-Format liefert.

Das mit den nicht zuverlässig reproduzierbaren Zeitstempeln ist natürlich äußerst unschön. Wer denkt sich denn sowas aus?? Mal sehen, wann ich die Zeit finde, da einen entsprechenden Fuzzy-Vergleich zu programmieren. Im Moment müssen die abgerufenen Logfiles halt unkonsolidiert nebeneinander auf meiner Platte liegenbleiben. Bei 30k pro Abruf ist das verschmerzbar.
Kann man nicht query.lua irgendwie sagen, dass es die Originalzeitstempel rausrücken soll, wie immer die auch aussehen (vermutlich Ticks seit Boot der Box), ohne diesen Startwertadditions- und Sekundenrundungs-Unsinn?
 
Ich habe keine Ahnung, wie ich "deviceinfoSCPD.xml" nutzen könnte und wie ich einen "einfachen SOAP-Request" mache, um aufzuklären, ob dessen "GetDeviceLog" nun richtig funktioniert oder nicht.
Das wäre die offiziell dokumentierte TR-064-Schnittstelle zum Zugriff auf das Eventlog, der Link zu den AVM-Beschreibungen steht in #6.

Ich habe den Quelltext der Lua-Seite nicht, weiß auch nicht wo ich ihn herbekomme, und wenn, könnte ich ihn wahrscheinlich nicht lesen, da ich kein LUA spreche.
Lua ist eine eher simple Sprache ... der Quelltext ist in jeder ausgepackten Firmware zugänglich. Wenn Du einen Linux-Server einsetzen kannst, um die Logs zu sichern, sollte man diesen Server auch zum Auspacken der Firmware benutzen können (das muß ja nicht auf einer FRITZ!Box passieren) und dann kann man dort eben nachsehen.

Mit der Programmierung der Box selber habe ich keine Erfahrung und wollte da aktuell eigentlich auch nicht einsteigen.
Ich sehe da keinen Zusammenhang zu einer "Programmierung der Box" ... Du hattest nach einer offiziellen Schnittstelle gefragt und die Antwort erhalten. Ja, es gibt eine solche Schnittstelle - ob die funktioniert, weiß ich nicht - und wenn Dir das dort gelieferte Format besser gefällt als JSON, dann nimm einfach die. Wenn nicht, kann man auf query.lua setzen - will man auch das nicht, wurstelt man sich eben durch die HTML-Ausgaben (wobei die sich absehbar in nicht allzu ferner Zukunft ändern).

Wie ich meine Fritzbox manipulieren müsste, damit ich diese Datei /var/.ar7events auf einem bestimmten Port abrufen kann, habe ich auch noch nicht verstanden.
Das habe ich auch noch nicht geschrieben. Aber für den bei Dir zu erreichenden Zweck reicht es ja schon, einen zusätzlichen Telnet-Daemon auf einem weiteren Port zu starten, der anstelle des Starts einer Shell einfach nur ein "cat /var/.ar7events" ausführt (ggf. in ein Wrapper-Skript verpackt). Den Start eines solchen Daemons muß man dann allerdings (wie bei jedem anderen Linux-System auch) im Rahmen von "init" organisieren. Dafür reicht eine einzelne Datei mit dem passenden Namen in /etc/init.d (und natürlich mit dem passenden Inhalt). Wie kriegt man das in ein Firmware-Image? Ganz einfach, solange wir über eine 7490 reden. Das dauert max. 10 Minuten, die Box entsprechend zu modifizieren.

Auch wie ich diese Datei nach dem Abruf dann interpretiere bzw. wo ihr Format dokumentiert ist, weiß ich bis jetzt nicht.
Wo ich das mal im Ansatz beschrieben habe, habe ich weiter oben schon ausgeführt ... sollte Dich die Suche nicht zu einem Ergebnis gebracht haben, müßtest Du - damit man die entsprechend korrigieren kann - mal die dabei verwendeten Parameter offenlegen. Ansonsten ist das Format relativ simpel ... aber das hier noch einmal ausführlich zu beschreiben, nur um dann zu hören, daß Du doch lieber einen anderen Weg wählen willst, macht ja nur begrenzt Sinn. Schon ein kurzer Hexdump offenbart das Format, wenn man etwas hin- und herrechnet in hex. Wenn Du wirklich dieses Format verwenden willst (das ist dann aber tatsächlich mit Programmierung verbunden, weil die Shell für binäre Daten eher nicht so richtig taugt), schreibe ich das auch auf - aber nur so zum Spaß ist mir das einfach zu viel.

Wie ich analysieren kann, warum der Abruf mit query.lua nicht funktioniert, ist mir auch nicht klar.
Ich halte das ganz simpel für ein Problem des Curl-Parsers. Wenn Du die Sonderzeichen (Doppelpunkt und Slash/Schrägstrich) einfach mal in HTTP-Request-Codierung (%XX) angibst, klappt das wahrscheinlich schon wieder - wenn nicht, nimm eben die Form, die funktioniert oder stelle die Reihenfolge der Parameter mal um. Auch dürfte ein simples "tcpdump" auf der Linux-Maschine schon zeigen, ob und wie "curl" da die URL falsch zusammenstellt - die ist ja in der Abfrage der FRITZ!Box problemlos zu finden.

Kann man nicht query.lua irgendwie sagen, dass es die Originalzeitstempel rausrücken soll, wie immer die auch aussehen (vermutlich Ticks seit Boot der Box), ohne diesen Startwertadditions- und Sekundenrundungs-Unsinn?
Die query.lua liefert nur die Werte, die das Variableninterface an sie selbst herausgibt. Da dieses wiederum vom ctlmgr bereitgestellt wird, ist der (mit seinen diversen Plugin-Libraries) dafür verantwortlich, wie die Daten dort aussehen. Da das gleichzeitig die Form ist, aus der die Anzeige im GUI erzeugt wird, ist dieses Ansinnen eher nicht sinnvoll (und geht auch nicht ... man kann nur noch die "Kategorien" passend filtern, wenn man die Abfrageparameter der query.lua entsprechend anpaßt.

EDIT: Wenn Dir die derzeitige Speicherung ausreicht, können wir das hier auch beenden ... ich bin ohnehin nur eingestiegen, weil Du in #1 nach Vorschlägen und anderen Ideen gefragt hast.
 
Zuletzt bearbeitet:
Hallo Peter,

sorry, falls ich Dich geärgert habe. Das war nicht meine Absicht. Meine Liste sollte Dir nur meinen (Un-)Kenntnisstand darlegen, da Du offensichtlich erheblich mehr Vorkenntnisse voraussetzst als ich habe.

Fürs erste nehme ich mit, dass ich noch sehr viel lesen muss, bevor ich Deine Vorschläge bewerten oder gar umsetzen kann. Ich danke Dir herzlich für Deine Erläuterungen und die Mühe, die Du Dir damit gemacht hast. Sie haben mir viele interessante Einblicke in das "Universum Fritzbox" gegeben. Das muss ich jetzt erst einmal verdauen und ein paar Wissenslücken schließen. Danach melde ich mich gerne wieder mit meinen Ergebnissen und Fragen.

Viele Grüße,
Tilman
 
sorry, falls ich Dich geärgert habe. Das war nicht meine Absicht.
Wenn das sich so lesen sollte, tut es mir auch leid ... das war weder die Absicht noch ist es tatsächlich der Fall, daß ich verärgert wäre.

Ich wollte nur klarstellen, daß ich Dich weniger verwirren als Dir vielmehr mögliche Alternativen aufzeigen wollte. Auch die Ausführungen zum Format der .ar7events sind genauso gemeint, wie sie da stehen ... wenn es wirklich jemanden interessiert, ist das Format (in den wichtigen Bestandteilen) schnell "entschlüsselt" und auch ziemlich leicht umzusetzen in einem eigenen Programm.

Aber meine bisherigen Erfahrungen zeigen auch, daß viele Leute schon anhand des Thread-Titels aussortieren, was ihrer Ansicht nach nicht zu ihrem gesuchten Thema gehört und daher müßte das in einem Thread "[Streng geheim] Internes Format des AVM-Eventlogs enthüllt ..." stehen, damit es ausreichend Rezipienten findet um beim nächsten Mal nicht erneut diese Frage aufzuwerfen.

Das ist mir aber zu albern (für einen Wiki-Eintrag im Freetz-Trac habe ich nicht mehr den Account) und inzwischen finde ich solche - von mir über viele Threads verteilte - Beschreibungen teilweise selbst nicht mehr wieder (und ich weiß zumindest sicher, daß ich da mal etwas geschrieben hatte, was dem "normalen Sucher" ja eher nicht bekannt ist).

Das muss ich jetzt erst einmal verdauen und ein paar Wissenslücken schließen. Danach melde ich mich gerne wieder mit meinen Ergebnissen und Fragen.
Nicht einschüchtern lassen ... ich hatte halt wegen des Linux-Servers zum Sichern der Logs die Vermutung, daß Du im Umgang mit Linux entsprechend fit bist (und das kann man zu einem großen Teil ja auch auf "embedded devices" projizieren).

Wenn ich Dich am Ende "überfahren" habe, tut es mir leid - laß Dich nicht ins Bockshorn jagen. Das ist alles keine Geheimwissenschaft und mit Deinem Ansatz (erst mal selbst Lücken füllen) unterscheidest Du Dich schon mal wohltuend von Leuten, die das in Einzelschritten aufgeschrieben und erläutert haben wollen (ohne selbst Zeit und Mühe zu investieren, während sich andere im Schweiße ihrer Füße die Finger wundschreiben sollen).

EDIT: Um die These zum "schlecht zu finden" mal zu untermauern: Hier liegt der Thread, den ich in #2 angesprochen habe.
 
Zuletzt bearbeitet:
@tgs-bonn:
Die Differenzen im Zeitstempel resultieren daraus, daß die interne Speicherung dieses Wertes nur als Differenz zu einem Startwert erfolgt. Kriegt die Box dann irgendwann später eine gültige Uhrzeit, wird nur dieser Startwert geändert. Je nachdem, ob man die Hälfte einer Sekunde dann bei der Abfrage des Logs nach oben oder nach unten erwischt, wird dann gerundet und daraus ergibt sich dieses Bild.
Leider kann die Abweichungen der Zeitstempel im Laufe der Zeit immer mal wieder vorkommen. Ich hatte sogar mal den Fall, dass sich bei jedem zweiten Aufruf von "eventsdump" die Zeitstempel hin- und her "geflippt" sind. Ich hatte seiner Zeit versucht, das Ereignislog an einen lokalen Syslog Server weiterzuleiten und da sind mir die "springenden" Zeitstempel immer beim "Diff" auf die Füße gefallen. Wenn da jemand noch eine gute Idee zu hat, würde ich mir das auch noch einmal genauer anschauen. Ich habe daher die letzten Ziffern des Zeitstempels weggeixt, was bei einem Sekundenflipp der sich auf die volle Stunde durchpropagiert auch nicht weiter.

Code:
#!/bin/sh
touch /var/tmp/eventsdump-old.log
while [ 1 == 1 ]
do
eventsdump | awk '{ print substr($0,0,13)"X:XX"substr($0,18)}' > /var/tmp/eventsdump.log
diff -a /var/tmp/eventsdump-old.log /var/tmp/eventsdump.log | sed -ne 's/^+\([0-9].*\)$/\1/pg' | sed
 -n '1!G;h;$p' | logger $1
mv /var/tmp/eventsdump.log /var/tmp/eventsdump-old.log
sleep 900
done

Ich muss dazu sagen, dass das "diff" normaler Weise nicht in der BusyBox von AVM enthalten ist, sondern von mir beim Bau des Images hinzugefügt wurde.

@PeterPan
Du als Bash / Script Experte gibt es da noch eine andere "clevere" Lösung oder hat man da auf Grund der (relativen Zeitum) Umsetzung seitens AVM keine Chance
 
@KingTutt:
Wenn das bei Dir auf der Box selbst läuft, kann man da tatsächlich mit der binären Version der .ar7events arbeiten.

Wenn man sich dort merkt, welche Einträge man bereits gesichert hat (die werden pro Systemstart durchnummeriert, die Nummer steht am Offset 12 eines jeden Eintrags und an Offset 8 der gesamten Datei), dann kann man recht leicht den gewünschten Effekt erreichen, daß nur noch neue Meldungen vom "eventsdump" ausgegeben werden bzw. man nur noch die neuen Nachrichten weiterverarbeitet. Diese neuen Nachrichten kann man dann irgendwohin sichern, dann braucht man auch kein "diff" und ist ohnehin von den Timestamps unabhängig, weil man ja die fortlaufende Nummer als "Aufsetzpunkt" verwendet.

Wenn man auf die gesamte Anzeige (und die Push-Mail-Inhalte des Eventlogs) ohnehin verzichten kann, löscht man einfach das Eventlog nach der Ausgabe der neuen Nachrichten selbst, das ginge über "ctlmgr_ctl" oder "set.lua".

Aber es geht auf der Box eben tatsächlich "intelligenter", wenn man mal vom "Polling" absieht - auch das kriegt man ggf. mit "inotifywait" oder dem "inotifyd" der Busybox in den Griff, dann reagiert man eben auf die Schreibvorgänge in die "/var/.ar7events".

Für das Auslesen einfacher Zahlen (die Offsets sind ja nichts anderes) aus einer Datei hat AVM schon ein nettes Tool im Image, das nennt sich "testvalue". Damit kann man Ein-, Zwei- oder Vier-Byte-Werte an einem vorgegebenen Offset einer Datei auslesen und diese als Dezimalzahl in Shell-Code weiterverarbeiten. Ein
Code:
lastmessge=$(testvalue /var/.ar7events 4 8)
liefert die Nummer der letzten Nachricht im Log. Schon der Test dieser Nummer gegen den letzten bekannten Wert reicht als Auskunft, ob da neue Nachrichten hinzugekommen sind und gleichzeitig als Angabe, wieviele neue Nachrichten es sind. Wenn man sich bei der Verarbeitung in der Schleife im ersten Schritt auf diesen Test beschränkt, kann man das sogar im Minutentakt (oder noch öfter) machen, ohne daß es eine Belastung wird - das bringt eine umfassendere Protokollierung auch bei einem Neustart (auch da gibt es noch einen "Trick", den AVM vorgegeben hat, dazu aber später).

Die eigentlichen Nachrichten sind dann eine doppelt verkettete Liste, jeder Eintrag hat in den ersten vier Byte den Offset seines Vorgängers und in den darauf folgenden vier Byte den Offset des Nachfolgers (0 bei der letzten Nachricht). Ab Offset 8 im Nachrichten-Eintrag steht die eigene Länge als 32-Bit-UInt und ab Offset 12 die oben erwähnte fortlaufende Nummer. Dann kommt eine Signatur (vermutlich) mit dem Wert "0xFE5AFC4B" und danach der Zeitstempel als Anzahl der Sekunden. Womit bei der Ausgabe die Summe gebildet wird (also was der Startwert des Timestamp-Counters ist), weiß ich auch nicht ... die "reine Uptime" ist dieser Zeitstempel im Eintrag schon mal nicht. Dann folgt noch die Nachrichten-ID (also die Auswahl des Templates) als 32-Bit-UInt und dann kommen schon die variablen Parameter, mit denen die Templates "befüllt" werden.

Jetzt kann man sich immer noch überlegen, was man selbst einfacher findet ... man kann entweder "eventsdump" eine geänderte Datei per chroot unterschieben (wie oben angedeutet, macht aber nur dann Sinn, wenn man die Einträge der .ar7events im "raw format" (z.B. in einer DB) speichern und irgendwann später bei Bedarf erst mit "eventsdump" mal übersetzen lassen will, wenn man eine Ausgabe braucht) oder - wenn man die Anzahl der interessanten Nachrichten errechnet aus der Differenz der letzten gemerkten Nummer und der aktuell letzten Nummer - man nimmt nur die ersten n Zeilen (das ist ja ohnehin LIFO) der Ausgabe von "eventsdump" (aber mit "sed", das "head" hat AVM inzwischen auch eingespart) und verarbeitet das im Text weiter. Auf alle Fälle würde ich eher mit "date" die Datums- und Zeitangabe parsen ... jedenfalls dann, wenn man "awk" ansonsten nicht braucht.

Um noch einmal auf die Handhabung durch AVM zurückzukommen ... in der /var/post_install ist es tatsächlich vorgesehen, daß das Eventlog bei einem Neustart ins TFFS gesichert wird (es gibt sogar eine Minor-ID dafür), aber der entscheidende Punkt ist, daß man das normalerweise fehlende Programm dafür (/sbin/eventctrl) ja durch sein eigenes Skript ersetzen kann. Aufgerufen wird das ganze in der /var/post_install so:
Code:
yourfritz_reboot_hook saveevents
## saving events to flash
if [ -x /sbin/eventctrl ] ; then
## unblock events in case of setfactorydefaults
/sbin/eventctrl -u
## add warm start event
/sbin/eventadd 651  <--- diese ID fehlt in der Regel in der htmltext.db bei normaler Firmware
## save to flash
/sbin/eventctrl -s
fi
Man muß also nur in das Image seine eigene ausführbare Skript-Datei /sbin/eventctrl einbauen und kann dann (bei einem normalen Shutdown/Reboot der Box) noch in aller Ruhe die restlichen Nachrichten aus dem Eventlog sichern, wenn man mit "-s" als Parameter aufgerufen wird. Allerdings natürlich nicht mehr auf USB-Speicher o.ä., die Volumes sind da alle schon abgehangen. Und natürlich wird bei "kernel panic" o.ä. (u.U. nicht einmal bei bestimmten Watchdogs) auch keine /var/post_install mehr abgearbeitet ...
 
Danke für die umfangreichen Ausführungen :)
Da ich nicht soo der Script Expert bin, habe ich einmal versucht, Deine Ausführungen in Pseudocode zu gießen. Die ganzen Fehler darin heraus zu bekommen wäre dann der nächste Schritt. :rolleyes:

Code:
#!/bin/sh

# erstes Auslesen nach dem Boot der Box und merken des letzten Eintrages
eventsdump | logger $1
lastmessge=$(testvalue /var/.ar7events 4 8)

while [ 1 == 1 ]
do

#warten, bis neue Eintraege vom System erzeugt wurden
inotifywait -e modify /var/.ar7eventsdir
newmessages=$(testvalue /var/.ar7events 4 8)

# Anzahl der neuen Ereignisse bestimmen
message_count=newmessages-lastmessge
if ($message_count > 0)

# nur die neuen Eintraege mit sed herausfiltern (da ist mir die sed Syntax noch nicht ganz klar ob das einfach mit $ geht)
eventsdump | sed -ne '/^$message_count:/p' | logger $1

fi
done

Natürlich musste man hoffen, dass während des eventsdump keinen neuen Nachrichten auflaufen, denn die würde inotifywait verpassen oder man müsste mit dem Parameter '-m' (Monitorring) arbeiten, aber dann ist mir unklar, wie man den Rest abarbeiten soll...
Spiegeln meine Ausführungen in etwa das wieder was Du vorgeschlagen hast?
 
Spiegeln meine Ausführungen in etwa das wieder was Du vorgeschlagen hast?
In etwa und unter der Annahme, daß das noch keine Shell-Befehle sein sollen: ja.

  • Shell-Arithmetik benötigt eine Klammerung in "$(( ... ))"
  • das Problem der zwischenzeitlichen Änderung des Eventlogs kann man durch Auslesen der aktuellen Anzahl vor und nach dem "eventsdump" umgehen, außerdem spielt es nur eine Rolle, wenn man davon ausgeht, daß das die "letzte Runde" war - ansonsten wird die zusätzliche Nachricht eben beim nächsten Mal mitgesichert
  • die Ausgabe der ersten n Zeilen mit "sed" geht am einfachsten mit Option "-n" und dem Kommando "-e 1,np" (also: sed -n -e "1,${message_count}p")
  • ich würde auf "close_write"-Events warten (modify könnte mehrfach auftreten pro neue Nachricht, weil mehrere Stellen in der Datei angepaßt werden müssen)

Vermutlich würde ich hier sogar auf den "inotifyd" der Busybox setzen ... der erlaubt den Aufruf eines Kommandos, wenn ein Event eintritt ("inotifyd $0 /var/.ar7events:w") und ich würde damit pro Event eine zusätzliche Instanz des Skripts starten, die dann natürlich als erstes auswerten muß, warum sie aufgerufen wurde - aber so bleibt das alles schön an einem Platz, nur die aktuelle höchste Nummer müßte man dann eben noch irgendwo speichern (Datei in /var/run oder /var/tmp), weil der zweite Aufruf seine eigene Umgebung kriegt (daß es nicht mehrere "worker scripts" gibt, regelt "inotifyd" alleine) -> hier ist die entscheidende Frage, ob man ohnehin eine eigene Busybox braucht/hat und der inotifyd dort gleich mit "drin" ist oder ob man die BB von AVM verwendet, dann würde ich ein (statisch gelinktes, zumindest gegen die libinotify) "inotifywait" nehmen - aber "logger" braucht ja auch schon die eigene BB, genauso wie der "syslogd", ohne den "logger" ja witzlos ist (zumindest der in der BB).

Du bräuchtest bei diesem Vorgehen nicht einmal ein zusätzliches "/sbin/eventctrl"-Skript für den "Abschluß" des Systems, wenn man mal unterstellt, daß das Schreiben der Nachricht 651 (auch wenn da eben kein sinnvoller Text dahinter liegt) ja noch eine letzte Änderung der .ar7events auslösen würde und Du dann noch einmal aufgerufen wirst ... allerdings kann das am Ende tatsächlich etwas zeitkritisch werden und auch die Frage, ob Deine Instanz des "syslogd" dann noch läuft, mußt Du selbst wissen/erkunden/entscheiden. Notfalls müßtest Du ein kurzes "sleep" am Ende der /var/post_install (die ist in der var.tar eines AVM-Images enthalten und muß dort geändert werden) einbauen.

Alternativ kannst Du natürlich auch ein Wrapper-Skript unter "/sbin/eventctrl" verlinken oder auch als "regular file" speichern, das beim Herunterfahren des Systems dann gleich noch andere Aufgaben übernehmen kann, z.B. den "inotifyd" und/oder Deinen "syslogd" stoppen oder noch eine gesonderte Nachricht mit "logger" schreiben zum Neustart oder oder oder - die Möglichkeiten sind zahlreich und das ist eine "sehr billige Möglichkeit" (halt ein wenig weiter hinten im Ablauf, wo der größte Teil der Dienste schon abgeschossen ist, was für das "reguläre Beenden" von Diensten mit USB-Zugriff etwas zu spät kommt), sich in das "shutdown" der Box einzuklinken, ohne die post_install in der var.tar eines SquashFS-Images anpassen zu müssen - da wartet dann post_install sogar, bis die Verarbeitung des Skripts beendet ist. Wenn Du das Image ohnehin anpassen mußt, sind das ja nur kleinere Ergänzungen, die nicht weiter schaden, wenn man sie ordentlich "bedingt" ausführen läßt (also immer erst prüft, ob die aufgerufenen Dateien auch existieren).

EDIT: Doch, es gibt noch ein "small gap" in der lückenlosen Überwachung, wenn genau zwischen dem Auslesen der aktuellen Anzahl mit "testvalue" und dem "eventsdump" eine weitere Nachricht geschrieben wird - das sollte also so eng wie möglich aufeinander folgen. Ansonsten erwischt das emulierte "head"-Kommando die zweite Nachricht (denn die steht ja als erste in der eventsdump-Ausgabe) und es geht eine Nachricht in der Mitte verloren. Also doch besser über einen Vergleich der Anzahl vor und nach dem "eventsdump" sicherstellen, daß da keine Nachricht hinzugekommen ist ... der vorherige Vergleich wäre - theoretisch - ohnehin umsonst, wenn man davon ausgeht, daß ein "close_write" (bzw. "w") nur dann auftritt, wenn auch eine neue Nachricht geschrieben wurde - einfach testen. Die Berechnung der auszulesenden Zeilen muß natürlich trotzdem erfolgen, notfalls eben nicht eine durchgehende Pipe verwenden, sondern eine temporäre Datei als Zwischenspeicher für die Ausgabe von "eventsdump" in Betracht ziehen. Wenn sich die Anzahl vor und nach dem "eventsdump" unterscheidet, eben einfach noch ein neues "eventsdump" machen und wieder vergleichen ... irgendwann ist das vorher/nachher schon identisch und dann erst gibt man die Daten an "logger" weiter.
 
Zuletzt bearbeitet:
Leider kann die Abweichungen der Zeitstempel im Laufe der Zeit immer mal wieder vorkommen. Ich hatte sogar mal den Fall, dass sich bei jedem zweiten Aufruf von "eventsdump" die Zeitstempel hin- und her "geflippt" sind. Ich hatte seiner Zeit versucht, das Ereignislog an einen lokalen Syslog Server weiterzuleiten und da sind mir die "springenden" Zeitstempel immer beim "Diff" auf die Füße gefallen. Wenn da jemand noch eine gute Idee zu hat, würde ich mir das auch noch einmal genauer anschauen. Ich habe daher die letzten Ziffern des Zeitstempels weggeixt, was bei einem Sekundenflipp der sich auf die volle Stunde durchpropagiert auch nicht weiter.
Auch wenn die Diskussion jetzt stark in Richtung Ausführung auf der Box abgedriftet ist, hier meine aktuelle (Not-)Lösung auf dem Linux-System, mit dem ich das Log abrufe:
Code:
mv -f ${LOGFILE} ${LOGFILE}.old || touch ${LOGFILE}.old || exit 1
${FETCHCMD} -p ${PASSWORD} | sort -k 1.7,1.8 -k 1.4,1.5 -k 1.1,1.2 -k 2 ${LOGFILE}.old - | uniq -f2 > ${LOGFILE}
In FETCHCMD steht der Pfad zu dem Skript aus #1, PASSWORD und LOGFILE sollten selbsterklärend sein.
Im Klartext: ich sortiere das alte und das neu abgerufene Log zusammen nach den Zeitstempeln und werfe dann alle Zeilen weg, deren Meldungstext mit dem der vorherigen übereinstimmt. Das funktioniert bis jetzt ganz gut. Natürlich verliere ich Einträge in den Fällen, wo tatsächlich mehrfach hintereinander dieselbe Meldung geloggt wird, aber das nehme ich vorläufig in Kauf. Weiterer Nachteil ist, dass sich die Zeitstempel alter Einträge nachträglich ändern können, aber da das Verfahren grundsätzlich den kleineren Zeitstempel behält, sollte das pro Eintrag maximal einmal vorkommen - vorausgesetzt es stecken wirklich nur Rundungsfehler dahinter und keine echte Drift.

Eine bessere Lösung wäre, im neu abgerufenen Log nach dem letzten Eintrag des alten Logs zu suchen, mit eine Sekunde Toleranz im Zeitstempel, und dann die im abgerufenen Log davor stehenden, also neu dazugekommenen Einträge an das alte Log anzuhängen. Dann verliert man nur noch Einträge, wenn innerhalb von zwei Sekunden dieselbe Meldung mehrfach geloggt wird und ein Abruf genau zwischen zwei solche Einträge fällt. Außerdem werden alte Einträge nicht neu geschrieben, was nicht nur die Zeitstempel stabil hält, sondern auch Log-Überwachungstools weniger irritiert. Aber das wird mir mit bash-Skripting zu unhandlich, dafür würde ich dann zu Perl greifen.

Angehen werde ich das eh erst, wenn ich mich für einen Abrufmechanismus entschieden habe. Falls ich bei einem lande, der JSON liefert, würde ich das Parsing dann auch gleich in Perl machen wollen - da gibt's bestimmt etwas von Ratio-, äh, CPAN. Und wenn's die /var/.ar7events wird, ist das Thema wie ich verstanden habe eh vom Tisch.
 
@kingtutt:
Bei meinen Überlegungen zum Loggen der letzten Nachrichten vor einem Neustart habe ich natürlich komplett überlesen, daß die Message 651 ja nur geschrieben wird, wenn /sbin/eventctrl existiert und ausführbar ist (das ist bei mir der Fall, daher fiel mir das nicht vorher auf).
 
Hallo Peter,

herzlichen dank für Deine erneut umgänglichen Erläuterungen. Allerdings hast Du inzwischen so viel Fachwissen dass es für mich nun schwierig wird, unter all den möglichen Optionen, überhaupt erst mal eine funktionierende ans Laufen zu bekommen. Optimieren kann man sicherlich immer noch :confused:

Wenn das letzte Event vor einen reboot nicht mehr mitgeloggt wird, kann ich das durchaus verschmerzen. Mir geht es darum, "das Meiste" mit zu bekommen. Dein vorgeschlagener Weg keine Nachricht in der Mitte zu verlieren, indem man vor und nach dem "eventsdump" die Anzahl vergleicht hat IMHO den Nachteil, dass man den Vergleich inkl. "eventsdump" in eine zusätzliche Schleife packen muss (welche so lange läuft, bis vor und hinterher identisch sind). Im falle eines fehlenden DSL sync oder vergleichbarem, könnten aber so schnell neue Meldungen im Eventlog ankommen, dass man quasi in dieser Schleife "parallel" dazu mitläuft und letztlich gar nichts davon im logger mehr ankommt (ich weiß nicht, wie ich das besser beschreiben soll).

Ich habe also zunächst einmal versucht, Deinen ersten "Kurzanmerkungen" umzusetzen.
Code:
#!/bin/sh

#Datei zum Zwischenspeichern des eventsdump
touch /var/tmp/eventsdump.log

# erstes Auslesen nach dem Boot der Box und merken des letzten Eintrages
eventsdump | logger $1
lastmessge=$(testvalue /var/.ar7events 4 8)

while [ 1 == 1 ]
do

#warten, bis neue Eintraege vom System erzeugt wurden
inotifywait -e close_write /var/.ar7eventsdir
newmessages=$(testvalue /var/.ar7events 4 8)

# Inhalt des Ereignislog zwischenspeichern (moeglichst direkt nach dem testvalue)
eventsdump > /var/tmp/eventsdump.log

# Anzahl der neuen Ereignisse bestimmen
#theoretisch koennten hier Nachrichten in der Mitte verloren gehen, wenn 'newmessages'
#nicht mehr aktuell ist, weil zwischen 'testvalue' und 'eventsdump' noch neue Events geschrieben wurden
message_count=$($(newmessages) - $(lastmessge))

# nur die neuen Eintraege mit 'sed' herausfiltern und an den logger weiterreichen
cat /var/tmp/eventsdump.log | sed -n -e "1,${message_count}p" | logger $1
lastmessge=$(newmessages)

done

Ich hoffe, dass ich jetzt erst mal alle für die Shell-Arithmetik benötigten Klammerungen richtig umgesetzt habe...? Zumindest benötigt man so kein awk, kein diff mehr und kein "Polling" mehr. Zwar fehlt der Vergleich von testvalue vor- und nach dem 'eventdump' aber dazu hatte ich ja oben bereits einen Einwand geschrieben (oder irre ich damit?).


@tgs-bonn
Deine "bessere Lösung" hätte im Gegensatz zur anderen den Vorteil, dass alle Zeilen, "deren Meldungstext mit dem der vorherigen übereinstimmt." erhalten bleiben. Für mich wäre das z.B. essentiell, denn nur so kann man z.B. bei einer DSL Störung im Nachhinein die Dauer bestimmen. Werden identische Meldungen weggeworfen, würde auch nicht auffallen, wenn ein User z.B. bei externem Dateiabruf zig mal ein falsches Passwort eingibt.

Allgemein hat aber die externe Abfrage über tr069 den Vorteil, dass man kein eigenes FW Image wie ich bauen muss.
[OT]
Aber da ich ohnehin noch einige Zusätze wie OpenVPN benötige komme ich derzeit um die eigene FW eh nicht drum herum.
@Peter Ich habe Deine Diskussion mit er13 verfolgt und in der Tat ist freetz sehr Aufwändig, wenn man z.B. nur OpenVPN zusätzlich haben will, aber derzeit läßt AVM einem leider keine andere Wahl. Ich hoffe auch noch auf eine schlankere Möglichkeit, vermute aber eher, dass die Box weiter verrammelt wird...
[/OT]
 
@kingtutt:
Code:
#!/bin/sh

#Datei zum Zwischenspeichern des eventsdump
[COLOR="#FF0000"][S]touch /var/tmp/eventsdump.log[/S]
# Wozu soll das dienen? Die Datei wird automatisch angelegt, wenn man etwas hineinschreibt.[/COLOR]

# erstes Auslesen nach dem Boot der Box und merken des letzten Eintrages
eventsdump | logger [COLOR="#FF0000"][S]$1[/S]
# Entweder Parameter oder stdin ... beides gleichzeitig protokolliert logger (der BB) nicht - auch wenn $1 vermutlich leer ist.[/COLOR]

lastmessge=$(testvalue /var/.ar7events 4 8)

while [ 1 == 1 ]
do

#warten, bis neue Eintraege vom System erzeugt wurden
inotifywait -e close_write /var/.ar7events[COLOR="#FF0000"][S]dir[/S] # Tippfehler?[/COLOR]
newmessages=$(testvalue /var/.ar7events 4 8)

# Inhalt des Ereignislog zwischenspeichern (moeglichst direkt nach dem testvalue)
eventsdump > /var/tmp/eventsdump.log

# Anzahl der neuen Ereignisse bestimmen
#theoretisch koennten hier Nachrichten in der Mitte verloren gehen, wenn 'newmessages'
#nicht mehr aktuell ist, weil zwischen 'testvalue' und 'eventsdump' noch neue Events geschrieben wurden
[COLOR="#FF0000"][S]message_count=$($(newmessages) - $(lastmessge))[/S][/COLOR]
message_count=$(( $newmessages - $lastmessge ))

[ $message_count -le 0 ] && continue # falls mal ein "close_write" ohne neue Nachrichten auftauchen sollte (z.B. beim Löschen von Events)

# nur die neuen Eintraege mit 'sed' herausfiltern und an den logger weiterreichen

[COLOR="#FF0000"][S]cat /var/tmp/eventsdump.log | sed -n -e "1,${message_count}p" | logger $1[/S][/COLOR]
sed -n -e "1,${message_count}p" /var/tmp/eventsdump.log | logger
lastmessge=$newmessages

done

EDIT: Um die Geschwindigkeit bei der Ausgabe von Meldungen brauchst Du Dir keine Gedanken zu machen. Selbst wenn da 10 Nachrichten innerhalb einer Sekunde protokolliert werden (bei fehlendem Sync wird ja sogar eher weniger statt mehr protokolliert), schafft das so ein Skript locker ... solange das Eventlog nicht sehr lang wird, das ist aber auf 40000 Byte limitiert. Wieviele Nachrichten darin Platz finden, hängt vom Inhalt der Nachrichten ab. Dieses "ständige parallele Schreiben" während Du noch liest, ist eher unwahrscheinlich. Wenn Du in der Schleife nach dem inotifywait etwas in der Art:
Code:
#!/bin/sh
binfile=/var/.ar7events
tmpfile=/var/tmp/eventsdump.log
lastmessage=-1

# saves work to change the call, if the format isn't stable and is more intuitive while reading later
get_message_count() 
{
    local num
    num=$(testvalue $binfile 4 8 2>/dev/null)
    [ $? -ne 0 ] && num=0
    echo $num
}

while true; do
    [ $lastmessage -ge 0 ] && inotifywait -e close_write $binfile 2>/dev/null 1>&2 || echo >/dev/null # something to force a return code of 0
    [ $? -ne 0 ] && exit # usally the monitored file has vanished and we want avoid looping in case of an error
    messages=$(get_message_count)
    new_messages=$(( $messages - 1 )) # force a difference first
    while [ $messages -ne $new_messages ]; do
        eventsdump >$tmpfile
        new_messages=$(get_message_count)
    done
    messages=$(( $new_messages - $lastmessage ))
    [ $messages -gt 0 ] && sed -n -e "1,${messages}p" $tmpfile | logger
    lastmessage=$new_messages
done
machst, solltest Du da auch keine Probleme bekommen ...
 
Zuletzt bearbeitet:
Hmm, wenn ich mich nicht täusche sind da zwei Fälle drin, die nicht abgefangen sind.

  1. Die
    Code:
    while [ $messages -ne $new_messages ]; do
    Schleife wird zur Endosschleife, für den Fall, dass tatsächlich einmal ein neuer Wert zwischen dem ersten und zweiten 'get_message_count' ist Ereignislog geschrieben wurde, denn $messages und $new_messages werden dann nie wieder gleich
  2. Für den Fall, dass 1) nicht eintritt, ist $new_messages identisch mit $messages nach dem ersten Schleifendurchlauf (new_messages wurde ja vorher bewusst anders initialisiert). Da $lastmessage aber mit -1 initialisiert wird, dürfte $messages direkt nach der Schleife in der Arithmetik auf einen falschen Wert gesetzt werden

Habe ich das richtig analysiert?
 
@KingTutt:
Du hast natürlich bei 1) vollkommen recht ... da habe ich mich im Optimierungswahn (nur ein eventsdump-Kommando, auch für den initialen Fall) zu sehr hinreißen lassen, ohne noch einmal zurückzudenken.

So sollte es dann doch wieder funktionieren:
Code:
#!/bin/sh
binfile=/var/.ar7events
tmpfile=/var/tmp/eventsdump.log
lastmessage=-1

# saves work to change the call, if the format isn't stable and is more intuitive while reading later
get_message_count() 
{
    local num
    num=$(testvalue $binfile 4 8 2>/dev/null)
    [ $? -ne 0 ] && num=0
    echo $num
}

while true; do
    [ $lastmessage -ge 0 ] && inotifywait -e close_write $binfile 2>/dev/null 1>&2 || last_message=0
    [ $? -ne 0 ] && exit # usally the monitored file has vanished and we want avoid looping in case of an error
    while true; do
        messages=$(get_message_count)
        eventsdump >$tmpfile
        new_messages=$(get_message_count)
        [ $new_messages -eq $messages ] && break
        sleep 1 # let's settle one second, if there're more messages queued
    done
    messages=$(( $messages - $lastmessage ))
    [ $messages -gt 0 ] && sed -n -e "1,${messages}p" $tmpfile | logger
    lastmessage=$new_messages
done
Durch das Setzen von lastmessage auf 0 in der Schleife anstelle des "inotifywait" beim ersten Durchlauf wird auch ein "result code" von 0 erzeugt und gleichzeitig der gewünschte Effekt (-1 zeigt ersten Durchlauf an, wobei dann alle Nachrichten zu verarbeiten sind) erreicht. Auch Deine zweite (richtige) Anmerkung (eine fehlende Nachricht beim ersten Mal) ist damit hoffentlich erledigt.

Ob das "sleep 1" überhaupt sinnvoll ist, kann man auch diskutieren ... selbst wenn sich bei zehn eventdumps hintereinander innerhalb einer Sekunde zweimal dasselbe Ergebnis bei get_message_count ergibt, wird ja erst einmal weiter gearbeitet (also die innere Schleife dann auch mal verlassen). Wenn dann - noch während der restlichen Verarbeitung innerhalb der äußeren Schleife - eine weitere Nachricht geschrieben wird, geht u.U. das "close_write" dafür verloren, weil noch kein inotifywait dafür aktiv ist.

Diese Nachricht würde dann erst mit der nächsten zusammen gesichert (das wären dann also zwei), aber das muß man entweder verschmerzen oder tatsächlich mit einem inotifywait im Monitoring-Modus arbeiten, das auch während der Verarbeitung der aktuellen Schleife weitere "close_write"-Events erzeugen würde, was dann sofort zu einem erneuten Durchlauf der Schleife führen würde.

So wartet die Schleife halt eine Sekunde, bevor das nächste eventsdump gemacht wird ... das sichert aber auch nicht dagegen ab, daß zwischen dem get_message_count für new_messages in der inneren Schleife und dem nächsten inotifywait schon wieder eine neue Nachricht geschrieben wird (halt nach 1,0001 Sekunden).

Das kommt jedenfalls davon, wenn man solche Sachen nicht selbst mal laufen läßt und nur in der Theorie entwickelt und dann zu allem Überfluß noch doppelte Kommandos vermeiden will. :blonk: Das ist aber auch wieder nicht selbst getestet ... nicht daß das jetzt falsch rüberkommt.
 
Holen Sie sich 3CX - völlig kostenlos!
Verbinden Sie Ihr Team und Ihre Kunden Telefonie Livechat Videokonferenzen

Gehostet oder selbst-verwaltet. Für bis zu 10 Nutzer dauerhaft kostenlos. Keine Kreditkartendetails erforderlich. Ohne Risiko testen.

3CX
Für diese E-Mail-Adresse besteht bereits ein 3CX-Konto. Sie werden zum Kundenportal weitergeleitet, wo Sie sich anmelden oder Ihr Passwort zurücksetzen können, falls Sie dieses vergessen haben.