From b7fa894b0dc2859c32dddf77655b57086fe561fb Mon Sep 17 00:00:00 2001 From: Eike Baran <eike.baran@uni-oldenburg.de> Date: Sat, 15 Aug 2015 23:25:05 +0200 Subject: [PATCH] completely rewrite of nodewatcher2 due to the unbearable an unmaintainable pseudo-array code for emulating bash-arrays. nodewatcher2 is now written in lua and can be configured by uci. It outputs json- and xml-files containing the nodedata. Optionally those files can additionally be gzipped to save some traffic. Test showed gzipping costs nearly no time and reduces the size of the json- and xml-files by about 60%) The nodewatcher2 divides its operation into two parts: 1) At first the lua-script collects all data it needs from the the system and puts it in a lua-table 2) The script now uses this lua-table to generate a abitrary set of output files by using it's defined and enabled output generators (xml, json,...) The generated files then are put in /tmp/nodedata, which is symlinked into the /nodedata-url of the http-server of the gluon status-page TODO: - make nodewatcher-compat generator - remove files whose generator was disabled via uci in the mean time --- nodewatcher2/Makefile | 5 +- .../files/etc/config/nodewatcher2.config | 5 + .../lib/ffnw/nodewatcher2/nodewatcher.lua | 287 ++++++++++++++ .../lib/ffnw/nodewatcher2/nodewatcher.sh | 254 ------------ .../files/lib/gluon/cron/nodewatcher2 | 2 +- nodewatcher2/files/usr/lib/lua/dkjson.lua | 369 ++++++++++++++++++ 6 files changed, 666 insertions(+), 256 deletions(-) create mode 100644 nodewatcher2/files/etc/config/nodewatcher2.config create mode 100755 nodewatcher2/files/lib/ffnw/nodewatcher2/nodewatcher.lua delete mode 100755 nodewatcher2/files/lib/ffnw/nodewatcher2/nodewatcher.sh create mode 100644 nodewatcher2/files/usr/lib/lua/dkjson.lua diff --git a/nodewatcher2/Makefile b/nodewatcher2/Makefile index dffe4d7..e4ebf87 100644 --- a/nodewatcher2/Makefile +++ b/nodewatcher2/Makefile @@ -17,7 +17,7 @@ endef define Package/ffnw-nodewatcher2/description Provides an xml-File containing the most important informations about the router. Nodewatcher2 is meant to - be more expansable for other export-formats in the future + be more expandable for other export-formats in the future by internally seperating retrieving and outputting the router-data endef @@ -36,6 +36,9 @@ define Package/ffnw-nodewatcher/install $(INSTALL_DATA) files/lib/gluon/cron/nodewatcher2 $(1)/lib/gluon/cron/nodewatcher2 $(INSTALL_DIR) $(1)/lib/ffnw/nodewatcher2/ $(INSTALL_BIN) files/lib/ffnw/nodewatcher2/nodewatcher.sh $(1)/lib/ffnw/nodewatcher2/ + $(INSTALL_DIR) $(1)/usr/lib/lua/ + $(INSTALL_DATA) files/usr/lib/lua/dkjson.lua $(1)/usr/lib/lua/ + $(INSTALL_DATA) files/etc/config/nodewatcher2 $(1)/etc/config/nodewatcher2 endef $(eval $(call BuildPackage,ffnw-nodewatcher2)) diff --git a/nodewatcher2/files/etc/config/nodewatcher2.config b/nodewatcher2/files/etc/config/nodewatcher2.config new file mode 100644 index 0000000..f60f95f --- /dev/null +++ b/nodewatcher2/files/etc/config/nodewatcher2.config @@ -0,0 +1,5 @@ +config 'prefs' 'prefs' + option 'generate_xml' '1' + option 'generate_json' '1' + option 'xml_data_file' '/tmp/node2data.xml' + option 'json_data_file' '/tmp/node2data.json' diff --git a/nodewatcher2/files/lib/ffnw/nodewatcher2/nodewatcher.lua b/nodewatcher2/files/lib/ffnw/nodewatcher2/nodewatcher.lua new file mode 100755 index 0000000..8a1a44c --- /dev/null +++ b/nodewatcher2/files/lib/ffnw/nodewatcher2/nodewatcher.lua @@ -0,0 +1,287 @@ +#!/usr/bin/env lua + + +local json = require ("dkjson") +--require("uci") + +local uci = require("uci").cursor() + + +data ={} + +function file_exists(name) + local f=io.open(name,"r") + if f~=nil then io.close(f) return true else return false end +end + +local function isArray(t) + local i = 0 + for _ in pairs(t) do + i = i + 1 + if t[i] == nil then return false end + end + return true +end + +function xmlEncode(tbl,name,layer) + local out="" + if layer==1 then out=out.."<?xml version='1.0' ?>\n" end + for i=1,layer do out=out..'\t' end + out=out.."<"..name..">" + if type(tbl)=="table" then + + out=out..'\n' + local akey=nil + if isArray(tbl) then + akey=name:sub(0,-2) + end + for k,v in pairs(tbl) do + out=out..(xmlEncode(v,akey or k,layer+1)) + end + for i=1,layer do out=out..'\t' end + else + out=out..(tostring(tbl)) + end + out=out.."</"..name..">\n" + return out +end + +function linesToTable(lines) + if lines==nil then + return {} + end + local tab = {} + for line in lines:lines() do + table.insert (tab, line); + end + -- if next(tab) == nil then + -- return nil + -- end + return tab +end + +function readFile(filepath) + local file = io.open(filepath, "r"); + return linesToTable(file) +end + +function readOutput(command) + + local file = io.popen(command) + return linesToTable(file) +end + +function readFirstRow(tbl) + if next(tbl)~=nil then --and table.getn(tbl)>0 then + return tbl[1] + else + return nil + end +end + + +function fetchMemory() + local tmp=readOutput("cat /proc/meminfo | grep -E '^(MemFree|MemTotal|Cached|Buffers)'") + local memLookup={MemT="total",MemF="free",Buff="buffer",Cach="cache"} + data.memory={} + for k,v in pairs(tmp) do + local t={string.match(v,"(.*) (%d+) .*")} + --print(t[1]) + key=memLookup[string.sub(v,1,4)] + if key~=nil then + data.memory[key]=tonumber(t[2]) + end + end +end + +function fetchTimes() + data.times={} + data.times.up,data.times.idle=string.match(readFile("/proc/uptime")[1],"(.+) (.+)") + for k,v in pairs(data.times) do + data.times[k]=math.floor(tonumber(v)*1000) + end +end + +function fetchPositions() + data.position={} + data.position.lon=readFirstRow(readOutput("uci get gluon-node-info.@location[0].longitude 2>/dev/null")) + data.position.lat=readFirstRow(readOutput("uci get gluon-node-info.@location[0].latitude 2>/dev/null")) + if next(data.position)==nil then data.position=nil end +end + +function fetchSoftware() + data.software={} + data.software.firmware=readFile("/lib/gluon/release")[1] + data.software.kernel=readOutput("uname -r")[1] + data.software.mesh="B.A.T.M.A.N. "..(readFile("/sys/module/batman_adv/version")[1]) + data.software.vpn=readOutput("fastd -v")[1] +-- if readFirstRow(getOutput("uci get autoupdater.settings.enabled 2>/dev/null")) == "1" then +-- data.software.autoupdate=readFirstRow(getOutput("uci get autoupdater.settings.branch 2>/dev/null")) +-- end +end + +function fetchOriginators() + data.originators={} + local tmp=readOutput("batctl o|sed -r 's/([0-9a-f:]+)[[:space:]]+([0-9.]+)s[[:space:]]+\\([[:space:]]*([0-9]{1,3})\\)[[:space:]]+([0-9a-f:]+)[[:space:]]+\\[[[:space:]]*(.+)\\]:.*/\\1 \\2 \\3 \\4 \\5/;tx;d;:x'") + for k,v in pairs(tmp) do + local o={} + local m={} + for v1 in string.gmatch(v,"[^ ]+") do + table.insert(m,v1) + end + o.mac=m[1] + o.nexthop=m[4] + o.linkquality=tonumber(m[3]) + o.lastseen=math.floor(tonumber(m[2])*1000) + -- print(o.mac.."->"..o.nexthop) + if o.mac==o.nexthop then + table.insert(data.originators,o); + end + + + end +end + +function fetchInterfaces() + data.interfaces={} + for _,iface in pairs(readOutput("grep -E 'up|unknown' /sys/class/net/*/operstate")) do + -- print(iface) + i={} + ipath,i.name=string.match(iface,"(.+/(.-))/operstate.*") + -- print(ipath.." jjjjj "..i.name) + if i.name~="lo" then + -- print(ipath.." jjjjj "..i.name) + i.ipv6={} + i.traffic={} + i.traffic.rx=readFirstRow(readFile(ipath.."/statistics/rx_bytes")) + i.traffic.tx=readFirstRow(readFile(ipath.."/statistics/tx_bytes")) + -- general interface info + for _,ipl in pairs(readOutput("ip addr show "..i.name)) do + local match=ipl:match("%s*inet6 ([0-9a-f:]+)(/%d+) .*") + --ugly cascading if-else-if-... because of lua missing continue-command + if match~=nil then + table.insert(i.ipv6,match) + --i.mac=match + else + match=ipl:match("%s*link/ether (.-) .*") + if match ~=nil then + i.mac=match + else + match=ipl:match("%s*inet ([0-9.]+)(/%d+) .*") + if match~=nil then + i.ipv4=match + else + match=ipl:match(".* mtu (%d+) .*") + if match~=nil then + i.mtu=tonumber(match) + end + end + end + end + end + if next(i.ipv6)==nil then i.ipv6=nil end + -- wifi info + i.radio={} + for _,ipl in pairs(readOutput("iwinfo "..i.name.." info 2>/dev/null")) do + -- print(ipl) + local match=ipl:match('ESSID:%s+"(.*)".*') + if match~=nil then + i.radio.essid=match + else + match=ipl:match('Access Point:%s+([A-Za-z0-9:]+).*') + if match~=nil then + i.radio.bssid=match:lower() + else + match={ipl:match('Tx%-Power: ([0-9]+) dBm Link Quality: ([a-z0-9]+/[0-9]+)')} + + if next(match)~=nil then + i.radio.txpower=tonumber(match[1]) + i.radio.linkquality=match[2] + end + end + end + end + if next(i.radio)==nil then i.radio=nil end + --batman? + if i.name~="bat0" then + local bat=readFirstRow(readFile("/sys/class/net/"..i.name.."/batman_adv/iface_status")) + i.meshstatus=(bat~=nil and bat~="not in use") + else + i.meshstatus=false + end + table.insert(data.interfaces,i) + end + end + +end + +function fetchGateways() + data.gateways={} + for _,v in pairs(readOutput("batctl gwl|sed -r 's/^[[:space:]]+([a-f0-9:].*)/false \\1/ ; s/^=>(.*)/true \\1/ ; s/(true|false)[[:space:]]+([0-9a-f:]+)[[:space:]]+\\([[:space:]]*([0-9]+)\\)[[:space:]]+[a-f0-9:]+[[:space:]]+\\[[[:space:]]*(.+)\\]:[[:space:]]+([0-9.\\/]+).*$/\\1 \\2 \\3 \\4 \\5/;tx;d;:x'")) do + local g={} + local m={} + for v1 in string.gmatch(v,"[^ ]+") do + table.insert(m,v1) + end + g.active=(m[1] == "true") + g.mac=m[2] + g.linkquality=tonumber(m[3]) + g.interface=m[4] + g.class=m[5] + table.insert(data.gateways,g) + end +end + + +-- do the fetching +data.hostname=readFile("/etc/hostname")[1] +data.client_count=tonumber(readOutput("echo '5'")[1]); +fetchTimes() +fetchMemory() +fetchPositions() +fetchSoftware() +fetchOriginators() +fetchInterfaces() +fetchGateways() + +local jsonString=json.encode (data, { indent = true }) + + +function writeToFile(string,filename) + local path=uci:get("nodewatcher2","prefs","destination_folder") + if path == nil then + return + end + os.execute('if [[ ! -d "'..path..'" ]] ; then mkdir -p "'..path..'" ; fi') + os.execute('if [[ ! -h /lib/gluon/status-page/www/nodedata ]] ; then ln -s '..path..' /lib/gluon/status-page/www/nodedata ; fi') + path=path.."/"..filename + local file = io.open(path, "w") + file:write(string) + file:close() + if uci:get("nodewatcher2","prefs","enable_gzip") == "1" then + os.execute("echo '"..string.."' |gzip > "..path..".gz") + end +end + +function generateXml() + if uci:get("nodewatcher2","prefs","generate_xml") == "1" then + local string=xmlEncode(data,"data",1) + writeToFile(string,"nodedata.xml") + end +end + +function generateJson() + if uci:get("nodewatcher2","prefs","generate_json") == "1" then + local string=json.encode (data, { indent = true }) + writeToFile(string,"nodedata.json") + + end +end + +generateXml() + +generateJson() + + + + diff --git a/nodewatcher2/files/lib/ffnw/nodewatcher2/nodewatcher.sh b/nodewatcher2/files/lib/ffnw/nodewatcher2/nodewatcher.sh deleted file mode 100755 index a1510bf..0000000 --- a/nodewatcher2/files/lib/ffnw/nodewatcher2/nodewatcher.sh +++ /dev/null @@ -1,254 +0,0 @@ -#!/bin/sh -#OUTPUT_DATA_FILE="$(uci get nodewatcher2.@script[0].data_file)" - -hostname="$(cat /proc/sys/kernel/hostname)" -times_up="$(sed -r 's/([0-9.]+) ([0-9.]+)/\1/' /proc/uptime)" -times_idle="$(sed -r 's/([0-9.]+) ([0-9.]+)/\2/' /proc/uptime)" - -position_lon="$(uci get gluon-node-info.@location[0].longitude)" -position_lat="$(uci get gluon-node-info.@location[0].latitude)" -clientcount="$(($(batctl tl|wc -l) - 3))" -model="$(grep -E '^machine' /proc/cpuinfo|sed -r 's/machine[[:space:]]*:[[:space:]]*(.*)/\1/')" -mac="$(ifconfig |grep bat0|sed -r 's/.*HWaddr ([0-9A-F:]*)[[:space:]]*/\1/'|tr '[ABCDEF]' '[abcdef]')" - -memory_total="$(grep -E "^MemTotal:" /proc/meminfo|sed -r 's/.*:[[:space:]]+([0-9]+) kB/\1/')" -memory_free="$(grep -E "^MemFree:" /proc/meminfo|sed -r 's/.*:[[:space:]]+([0-9]+) kB/\1/')" -memory_cache="$(grep -E "^Cached:" /proc/meminfo|sed -r 's/.*:[[:space:]]+([0-9]+) kB/\1/')" -memory_buffer="$(grep -E "^Buffers:" /proc/meminfo|sed -r 's/.*:[[:space:]]+([0-9]+) kB/\1/')" - -set -- $(sed -r "s/[0-9.]+ [0-9.]+ ([0-9.]+) ([0-9]+)\/([0-9]+) [0-9]+/\1 \2 \3/" /proc/loadavg) -processes_total=$3 -processes_runnable=$2 -processes_loadavg=$1 - -software_firmware="$(cat /lib/gluon/release)" -software_kernel="$(uname -r)" -software_mesh="B.A.T.M.A.N. $(cat /sys/module/batman_adv/version)" -software_vpn="$(fastd -v)" - -software_autoupdate_enabled="$(uci get autoupdater.settings.enabled 2>/dev/null)" -software_autoupdate_branch="$(uci get autoupdater.settings.branch 2>/dev/null)" - - - -ifc=0 -for filename in `grep 'up\|unknown' /sys/class/net/*/operstate`; do - ifpath=${filename%/operstate*} - iface=${ifpath#/sys/class/net/} - if [ "$iface" = "lo" ]; then - continue - fi - eval interface${ifc}_name="\"$iface\"" - - local addrs="$(ip addr show dev ${iface})" - eval interface${ifc}_mtu=$(echo \"$addrs\" | grep -E "mtu"|sed -r "s/.* mtu ([0-9]+) .*/\1/") - eval interface${ifc}_mac=$(echo \"$addrs\" | grep -E "link/ether"|sed -r "s/.* link\/ether ([0-9a-f:]+) .*/\1/") - - eval "interface${ifc}_traffic_rx=\"$(cat $ifpath/statistics/rx_bytes)\"" - eval "interface${ifc}_traffic_tx=\"$(cat $ifpath/statistics/tx_bytes)\"" -# echo -e $addrs | grep -E 'inet '|sed -r 's/.* inet ([0-9.]+)(\/[0-9]+)? .*/\1/' - eval "interface${ifc}_ipv4=\"$(echo -e \"$addrs\" | grep -E 'inet '|sed -r 's/.* inet ([0-9.]+)(\/[0-9]+)? .*/\1/')\"" -# eval "echo \"ip: \$interface${ifc}_ipv4\"" - local ipv6_adresses=$(echo "$addrs" | grep -E 'inet6 ' |sed -r 's/[[:space:]]*inet6 (([0-9a-f:]+)(\/[0-9]*)?) .*/\2/') - local ipc=0 -# echo $ipv6_adresses - for ip in $ipv6_adresses ;do - echo "--"$ip - eval "interface${ifc}_ipv6_${ipc}=\"$ip\"" - ipc=$(($ipc+1)) - done - eval "interface${ifc}_ipv6count=$ipc" - - - if [ "$iface" != "bat0" ] ; then - local tmp=$(cat "/sys/class/net/$iface/batman_adv/iface_status") - if [ "$tmp" != "not in use" ] ; then - eval interface${ifc}_meshstatus=\"$tmp\" - fi - fi - - - local iwi="$(iwinfo ${iface} info 2>/dev/null)" - if [ "$iwi" != "" ] ; then - eval "interface${ifc}_txpower=\"\$(echo \"${iwi}\"|grep 'Tx-Power'|sed -r 's/[[:space:]]+Tx-Power:[[:space:]]+([0-9]+).*/\1/')\"" - eval "interface${ifc}_channel=\"\$(echo \"${iwi}\"|grep 'Channel: '|sed -r 's/.*Channel:[[:space:]]+([0-9]+).*/\1/')\"" - eval "interface${ifc}_linkquality=\"\$(echo \"${iwi}\"|grep 'Link Quality: '|sed -r 's/.*Link Quality:[[:space:]]+(([0-9]+|unknown)\/[0-9]+).*/\1/')\"" - fi - ifc=$(($ifc+1)) -done - -orc=0 -origs="$(batctl o|sed -r 's/([0-9a-f:]+)[[:space:]]+([0-9.]+)s[[:space:]]+\([[:space:]]*([0-9]{1,3})\)[[:space:]]+([0-9a-f:]+)[[:space:]]+\[[[:space:]]*(.+)\]:.*/\1 \2 \3 \4 \5/;tx;d;:x')" - -OIFS="$IFS" -NIFS=$'\n' -IFS="${NIFS}" -#echo "$origs" -for orig in $origs ; do - IFS="${OIFS}" - set -- $orig - eval "originator${orc}_mac=\"$1\"" - eval "originator${orc}_linkquality=\"$3\"" - eval "originator${orc}_lastseen=\"$2\"" - eval "originator${orc}_nexthop=\"$4\"" - eval "originator${orc}_interface=\"$5\"" - - if eval "[ \"\${originator${orc}_mac}\" != \"\${originator${orc}_nexthop}\" -o \"\${originator${orc}_interface}\" == \"mesh-vpn\" ]" - then - # eval "echo \"\$originator${orc}_mac -- \$originator${orc}_nexthop\"" - # echo "skipped" - continue - fi - orc=$(($orc+1)) - IFS="${NIFS}" -done -IFS="${OIFS}" - -gwc=0 -gws="$(batctl gwl|sed -r 's/^[[:space:]]+([a-f0-9:].*)/false \1/ ; s/^=>(.*)/true \1/ ; s/(true|false)[[:space:]]+([0-9a-f:]+)[[:space:]]+\([[:space:]]*([0-9]+)\)[[:space:]]+[a-f0-9:]+[[:space:]]+\[[[:space:]]*(.+)\]:[[:space:]]+([0-9.\/]+).*$/\1 \2 \3 \4 \5/;tx;d;:x')" -#echo "$gws" -IFS="${NIFS}" -for gw in $gws ; do - IFS=${OIFS} -# gw="$(echo "$gw"|sed -r 's/^[[:space:]]+(.*)/false \1/ ; s/^=>(.*)/true \1/ ; s/(true|false)[[:space:]]+([0-9a-f:]+)[[:space:]]+\([[:space:]]*([0-9]+)\)[[:space:]]+[a-f0-9:]+[[:space:]]+\[[[:space:]]*(.+)\]:[[:space:]]+([0-9.\/]+).*$/\1 \2 \3 \4 \5/;tx;d;:x')" -# echo "$gw" - set -- $gw -# echo "sel: $1" - eval "gateway${gwc}_mac=\"$2\"" - eval "gateway${gwc}_selected=\"$1\"" - eval "gateway${gwc}_linkquality=\"$3\"" - eval "gateway${gwc}_class=\"$5\"" - eval "gateway${gwc}_interface=\"$4\"" - - gwc=$(($gwc+1)) - IFS=${NIFS} -done -IFS=${OIFS} - -#################################output to xml -out="<?xml version='1.0' ?>\n" -out=$out"<data>\n" -out=$out"\t<hostname>"$hostname"</hostname>\n" -out=$out"\t<times>\n\t\t<up>$times_up</up>\n\t\t<idle>"$times_idle"</idle>\n\t</times>\n" -out=$out"\t<model>"$model"</model>\n" -out=$out"\t<mac>"$mac"</mac>\n" -if [ $(uci get gluon-node-info.@location[0].share_location) = "1" ] ; then - out=$out"\t<position>\n" - out=$out"\t\t<lon>"$position_lon"</lon>\n" - out=$out"\t\t<lat>"$position_lon"</lat>\n" - out=$out"\t</position>\n" -fi -out=$out"\t<memory>\n" -out=$out"\t\t<total>"$memory_total"</total>\n" -out=$out"\t\t<free>"$memory_free"</free>\n" -out=$out"\t\t<buffer>"$memory_buffer"</buffer>\n" -out=$out"\t\t<cache>"$memory_cache"</cache>\n" -out=$out"\t</memory>\n" -out=$out"\t<processes>\n" -out=$out"\t\t<runnable>$processes_runnable</runnable>\n" -out=$out"\t\t<total>$processes_total</total>\n" -out=$out"\t\t<loadavg>$processes_loadavg</loadavg>\n" -out=$out"\t</processes>\n" -out=$out"\t<software>\n" -out=$out"\t\t<firmware>"$software_firmware"</firmware>\n" -out=$out"\t\t<kernel>"$software_kernel"</kernel>\n" -out=$out"\t\t<mesh>"$software_mesh"</mesh>\n" -out=$out"\t\t<vpn>"$software_vpn"</vpn>\n" -if [ "$software_autoupdate_enabled" = "1" ] ; then - out="$out\t\t<autoupdate_branch>$software_autoupdate_branch</autoupdate_branch>\n" -# out="$out\t\t\t<enabled>$software_autoupdate_enabled</enabled>\n" -# out="$out\t\t\t<branch>$software_autoupdate_branch</branch>\n" -# out="$out\t\t</autoupdate>\n" -fi -out=$out"\t</software>\n" -out=$out"\t<interfaces>\n" - -for i in $(seq 0 $(($ifc-1))) ; do - out=$out"\t\t<interface>\n" - eval "out=\"\${out}\t\t\t<name>\${interface${i}_name}</name>\n\"" - - eval "out=\"\${out}\t\t\t<mtu>\${interface${i}_mtu}</mtu>\n\"" - eval "out=\"\${out}\t\t\t<mac>\${interface${i}_mac}</mac>\n\"" - out="$out\t\t\t<traffic>\n" - eval "out=\"\${out}\t\t\t\t<rx>\${interface${i}_traffic_rx}</rx>\n\"" - eval "out=\"\${out}\t\t\t\t<tx>\${interface${i}_traffic_tx}</tx>\n\"" - out=$out"\t\t\t</traffic>\n" - - eval "local meshstatus=\${interface${i}_meshstatus}" - if [ "$meshstatus" != "" ] ; then - - - - - eval "out=\"\${out}\t\t\t<meshstatus>\${interface${i}_meshstatus}</meshstatus>\n\"" - fi - - eval "local ipv4=\"\${interface${i}_ipv4}\"" -# echo "ip4: $ipv4" - if [ "$ipv4" != "" ]; then - out=$out"\t\t\t<ipv4>$ipv4</ipv4>\n" - fi - - - eval "ic=\${interface${i}_ipv6count}" - - if [ "$ic" != "0" ] ; then - for ip in $(seq 0 $(($ic-1))) ; do - eval "out=\"$out\t\t\t<ipv6>\${interface${i}_ipv6_${ip}}</ipv6>\n\"" -# echo "hi$ip" - #eval interface${ifc}_ipv6_$ip=$ip - done - fi - - eval "local txp=\"\${interface${i}_txpower}\"" - eval "local ch=\"\${interface${i}_channel}\"" -# if [ "$txp" != "" ] - if eval "[ \"\${interface${i}_txpower}\" != \"\" ]" - then - out="$out\t\t\t<txpower>$txp</txpower>\n" - out="$out\t\t\t<channel>$ch</channel>\n" - eval "out=\"$out\t\t\t<linkquality>\${interface${i}_linkquality}</linkquality>\n\"" - fi - - out=$out"\t\t</interface>\n" -done -out="$out\t</interfaces>\n" - -if [ "$orc" != "0" ] ; then - out="$out\t<originators>\n" - for i in $(seq 0 $(($orc-1))) ; do - out="$out\t\t<originator>\n" - eval "out=\"$out\t\t\t<mac>\${originator${i}_mac}</mac>\n\"" - eval "out=\"$out\t\t\t<linkquality>\${originator${i}_linkquality}</linkquality>\n\"" - eval "out=\"$out\t\t\t<lastseen>\${originator${i}_lastseen}</lastseen>\n\"" - eval "out=\"$out\t\t\t<interface>\${originator${i}_interface}</interface>\n\"" - - out="$out\t\t</originator>\n" - done - out="$out\t</originators>\n" -fi - -if [ "$gwc" != "0" ] ; then - out="$out\t<gateways>\n" - for g in $(seq 0 $(($gwc-1))) ; do - out="$out\t\t<gateway>\n" - eval "out=\"$out\t\t\t<mac>\${gateway${g}_mac}</mac>\n\"" - - - eval "out=\"$out\t\t\t<selected>\${gateway${g}_selected}</selected>\n\"" - eval "out=\"$out\t\t\t<linkquality>\${gateway${g}_linkquality}</linkquality>\n\"" -eval "out=\"$out\t\t\t<interface>\${gateway${g}_interface}</interface>\n\"" - eval "out=\"$out\t\t\t<class>\${gateway${g}_class}</class>\n\"" - out="$out\t\t</gateway>\n" - - done - out="$out\t</gateways>\n" -fi - -out="$out</data>" - - -echo -e "$out" > "/tmp/node2data.xml" -if [ ! -h /lib/gluon/status-page/www/node2data.xml ]; then - ln -s /tmp/node2data.xml /lib/gluon/status-page/www/node2data.xml -fi diff --git a/nodewatcher2/files/lib/gluon/cron/nodewatcher2 b/nodewatcher2/files/lib/gluon/cron/nodewatcher2 index 1268c7e..4c421b8 100644 --- a/nodewatcher2/files/lib/gluon/cron/nodewatcher2 +++ b/nodewatcher2/files/lib/gluon/cron/nodewatcher2 @@ -1 +1 @@ -*/5 * * * * sh /lib/ffnw/nodewatcher2/nodewatcher.sh; +*/5 * * * * sh /lib/ffnw/nodewatcher2/nodewatcher.lua; diff --git a/nodewatcher2/files/usr/lib/lua/dkjson.lua b/nodewatcher2/files/usr/lib/lua/dkjson.lua new file mode 100644 index 0000000..fc5350f --- /dev/null +++ b/nodewatcher2/files/usr/lib/lua/dkjson.lua @@ -0,0 +1,369 @@ +-- Module options: +local always_try_using_lpeg = true +local register_global_module_table = false +local global_module_name = 'json' + +--[==[ + +David Kolf's JSON module for Lua 5.1/5.2 + +Version 2.5 + + +For the documentation see the corresponding readme.txt or visit +<http://dkolf.de/src/dkjson-lua.fsl/>. + +You can contact the author by sending an e-mail to 'david' at the +domain 'dkolf.de'. + + +Copyright (C) 2010-2013 David Heiko Kolf + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +--]==] + +-- global dependencies: +local pairs, type, tostring, tonumber, getmetatable, setmetatable, rawset = + pairs, type, tostring, tonumber, getmetatable, setmetatable, rawset +local error, require, pcall, select = error, require, pcall, select +local floor, huge = math.floor, math.huge +local strrep, gsub, strsub, strbyte, strchar, strfind, strlen, strformat = + string.rep, string.gsub, string.sub, string.byte, string.char, + string.find, string.len, string.format +local strmatch = string.match +local concat = table.concat + +local json = { version = "dkjson 2.5" } + +if register_global_module_table then + _G[global_module_name] = json +end + +local _ENV = nil -- blocking globals in Lua 5.2 + +pcall (function() + -- Enable access to blocked metatables. + -- Don't worry, this module doesn't change anything in them. + local debmeta = require "debug".getmetatable + if debmeta then getmetatable = debmeta end +end) + +json.null = setmetatable ({}, { + __tojson = function () return "null" end +}) + +local function isarray (tbl) + local max, n, arraylen = 0, 0, 0 + for k,v in pairs (tbl) do + if k == 'n' and type(v) == 'number' then + arraylen = v + if v > max then + max = v + end + else + if type(k) ~= 'number' or k < 1 or floor(k) ~= k then + return false + end + if k > max then + max = k + end + n = n + 1 + end + end + if max > 10 and max > arraylen and max > n * 2 then + return false -- don't create an array with too many holes + end + return true, max +end + +local escapecodes = { + ["\""] = "\\\"", ["\\"] = "\\\\", ["\b"] = "\\b", ["\f"] = "\\f", + ["\n"] = "\\n", ["\r"] = "\\r", ["\t"] = "\\t" +} + +local function escapeutf8 (uchar) + local value = escapecodes[uchar] + if value then + return value + end + local a, b, c, d = strbyte (uchar, 1, 4) + a, b, c, d = a or 0, b or 0, c or 0, d or 0 + if a <= 0x7f then + value = a + elseif 0xc0 <= a and a <= 0xdf and b >= 0x80 then + value = (a - 0xc0) * 0x40 + b - 0x80 + elseif 0xe0 <= a and a <= 0xef and b >= 0x80 and c >= 0x80 then + value = ((a - 0xe0) * 0x40 + b - 0x80) * 0x40 + c - 0x80 + elseif 0xf0 <= a and a <= 0xf7 and b >= 0x80 and c >= 0x80 and d >= 0x80 then + value = (((a - 0xf0) * 0x40 + b - 0x80) * 0x40 + c - 0x80) * 0x40 + d - 0x80 + else + return "" + end + if value <= 0xffff then + return strformat ("\\u%.4x", value) + elseif value <= 0x10ffff then + -- encode as UTF-16 surrogate pair + value = value - 0x10000 + local highsur, lowsur = 0xD800 + floor (value/0x400), 0xDC00 + (value % 0x400) + return strformat ("\\u%.4x\\u%.4x", highsur, lowsur) + else + return "" + end +end + +local function fsub (str, pattern, repl) + -- gsub always builds a new string in a buffer, even when no match + -- exists. First using find should be more efficient when most strings + -- don't contain the pattern. + if strfind (str, pattern) then + return gsub (str, pattern, repl) + else + return str + end +end + +local function quotestring (value) + -- based on the regexp "escapable" in https://github.com/douglascrockford/JSON-js + value = fsub (value, "[%z\1-\31\"\\\127]", escapeutf8) + if strfind (value, "[\194\216\220\225\226\239]") then + value = fsub (value, "\194[\128-\159\173]", escapeutf8) + value = fsub (value, "\216[\128-\132]", escapeutf8) + value = fsub (value, "\220\143", escapeutf8) + value = fsub (value, "\225\158[\180\181]", escapeutf8) + value = fsub (value, "\226\128[\140-\143\168-\175]", escapeutf8) + value = fsub (value, "\226\129[\160-\175]", escapeutf8) + value = fsub (value, "\239\187\191", escapeutf8) + value = fsub (value, "\239\191[\176-\191]", escapeutf8) + end + return "\"" .. value .. "\"" +end +json.quotestring = quotestring + +local function replace(str, o, n) + local i, j = strfind (str, o, 1, true) + if i then + return strsub(str, 1, i-1) .. n .. strsub(str, j+1, -1) + else + return str + end +end + +-- locale independent num2str and str2num functions +local decpoint, numfilter + +local function updatedecpoint () + decpoint = strmatch(tostring(0.5), "([^05+])") + -- build a filter that can be used to remove group separators + numfilter = "[^0-9%-%+eE" .. gsub(decpoint, "[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0") .. "]+" +end + +updatedecpoint() + +local function num2str (num) + return replace(fsub(tostring(num), numfilter, ""), decpoint, ".") +end + +local function addnewline2 (level, buffer, buflen) + buffer[buflen+1] = "\n" + buffer[buflen+2] = strrep (" ", level) + buflen = buflen + 2 + return buflen +end + +function json.addnewline (state) + if state.indent then + state.bufferlen = addnewline2 (state.level or 0, + state.buffer, state.bufferlen or #(state.buffer)) + end +end + +local encode2 -- forward declaration + +local function addpair (key, value, prev, indent, level, buffer, buflen, tables, globalorder, state) + local kt = type (key) + if kt ~= 'string' and kt ~= 'number' then + return nil, "type '" .. kt .. "' is not supported as a key by JSON." + end + if prev then + buflen = buflen + 1 + buffer[buflen] = "," + end + if indent then + buflen = addnewline2 (level, buffer, buflen) + end + buffer[buflen+1] = quotestring (key) + buffer[buflen+2] = ":" + return encode2 (value, indent, level, buffer, buflen + 2, tables, globalorder, state) +end + +local function appendcustom(res, buffer, state) + local buflen = state.bufferlen + if type (res) == 'string' then + buflen = buflen + 1 + buffer[buflen] = res + end + return buflen +end + +local function exception(reason, value, state, buffer, buflen, defaultmessage) + defaultmessage = defaultmessage or reason + local handler = state.exception + if not handler then + return nil, defaultmessage + else + state.bufferlen = buflen + local ret, msg = handler (reason, value, state, defaultmessage) + if not ret then return nil, msg or defaultmessage end + return appendcustom(ret, buffer, state) + end +end + +function json.encodeexception(reason, value, state, defaultmessage) + return quotestring("<" .. defaultmessage .. ">") +end + +encode2 = function (value, indent, level, buffer, buflen, tables, globalorder, state) + local valtype = type (value) + local valmeta = getmetatable (value) + valmeta = type (valmeta) == 'table' and valmeta -- only tables + local valtojson = valmeta and valmeta.__tojson + if valtojson then + if tables[value] then + return exception('reference cycle', value, state, buffer, buflen) + end + tables[value] = true + state.bufferlen = buflen + local ret, msg = valtojson (value, state) + if not ret then return exception('custom encoder failed', value, state, buffer, buflen, msg) end + tables[value] = nil + buflen = appendcustom(ret, buffer, state) + elseif value == nil then + buflen = buflen + 1 + buffer[buflen] = "null" + elseif valtype == 'number' then + local s + if value ~= value or value >= huge or -value >= huge then + -- This is the behaviour of the original JSON implementation. + s = "null" + else + s = num2str (value) + end + buflen = buflen + 1 + buffer[buflen] = s + elseif valtype == 'boolean' then + buflen = buflen + 1 + buffer[buflen] = value and "true" or "false" + elseif valtype == 'string' then + buflen = buflen + 1 + buffer[buflen] = quotestring (value) + elseif valtype == 'table' then + if tables[value] then + return exception('reference cycle', value, state, buffer, buflen) + end + tables[value] = true + level = level + 1 + local isa, n = isarray (value) + if n == 0 and valmeta and valmeta.__jsontype == 'object' then + isa = false + end + local msg + if isa then -- JSON array + buflen = buflen + 1 + buffer[buflen] = "[" + for i = 1, n do + buflen, msg = encode2 (value[i], indent, level, buffer, buflen, tables, globalorder, state) + if not buflen then return nil, msg end + if i < n then + buflen = buflen + 1 + buffer[buflen] = "," + end + end + buflen = buflen + 1 + buffer[buflen] = "]" + else -- JSON object + local prev = false + buflen = buflen + 1 + buffer[buflen] = "{" + local order = valmeta and valmeta.__jsonorder or globalorder + if order then + local used = {} + n = #order + for i = 1, n do + local k = order[i] + local v = value[k] + if v then + used[k] = true + buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state) + prev = true -- add a seperator before the next element + end + end + for k,v in pairs (value) do + if not used[k] then + buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state) + if not buflen then return nil, msg end + prev = true -- add a seperator before the next element + end + end + else -- unordered + for k,v in pairs (value) do + buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state) + if not buflen then return nil, msg end + prev = true -- add a seperator before the next element + end + end + if indent then + buflen = addnewline2 (level - 1, buffer, buflen) + end + buflen = buflen + 1 + buffer[buflen] = "}" + end + tables[value] = nil + else + return exception ('unsupported type', value, state, buffer, buflen, + "type '" .. valtype .. "' is not supported by JSON.") + end + return buflen +end + +function json.encode (value, state) + state = state or {} + local oldbuffer = state.buffer + local buffer = oldbuffer or {} + state.buffer = buffer + updatedecpoint() + local ret, msg = encode2 (value, state.indent, state.level or 0, + buffer, state.bufferlen or 0, state.tables or {}, state.keyorder, state) + if not ret then + error (msg, 2) + elseif oldbuffer == buffer then + state.bufferlen = ret + return true + else + state.bufferlen = nil + state.buffer = nil + return concat (buffer) + end +end + + +return json + -- GitLab