luci-app-olsr: handle empty result for non-status tables
[oweals/luci.git] / modules / luci-base / root / usr / libexec / rpcd / luci
1 #!/usr/bin/env lua
2
3 local json = require "luci.jsonc"
4 local fs   = require "nixio.fs"
5
6 local function readfile(path)
7         local s = fs.readfile(path)
8         return s and (s:gsub("^%s+", ""):gsub("%s+$", ""))
9 end
10
11 local methods = {
12         initList = {
13                 args = { name = "name" },
14                 call = function(args)
15                         local sys = require "luci.sys"
16                         local _, name, scripts = nil, nil, {}
17                         for _, name in ipairs(args.name and { args.name } or sys.init.names()) do
18                                 local index = sys.init.index(name)
19                                 if index then
20                                         scripts[name] = { index = index, enabled = sys.init.enabled(name) }
21                                 else
22                                         return { error = "No such init script" }
23                                 end
24                         end
25                         return { result = scripts }
26                 end
27         },
28
29         initCall = {
30                 args = { name = "name", action = "action" },
31                 call = function(args)
32                         local sys = require "luci.sys"
33                         if type(sys.init[args.action]) ~= "function" then
34                                 return { error = "Invalid action" }
35                         end
36                         return { result = sys.init[args.action](args.name) }
37                 end
38         },
39
40         getLocaltime = {
41                 call = function(args)
42                         return { localtime = os.time() }
43                 end
44         },
45
46         setLocaltime = {
47                 args = { localtime = 0 },
48                 call = function(args)
49                         local sys = require "luci.sys"
50                         local date = os.date("*t", args.localtime)
51                         if date then
52                                 sys.call("date -s '%04d-%02d-%02d %02d:%02d:%02d' >/dev/null" %{ date.year, date.month, date.day, date.hour, date.min, date.sec })
53                                 sys.call("/etc/init.d/sysfixtime restart >/dev/null")
54                         end
55                         return { localtime = args.localtime }
56                 end
57         },
58
59         timezone = {
60                 call = function(args)
61                         local util  = require "luci.util"
62                         local zones = require "luci.sys.zoneinfo"
63
64                         local tz = readfile("/etc/TZ")
65                         local res = util.ubus("uci", "get", {
66                                 config = "system",
67                                 section = "@system[0]",
68                                 option = "zonename"
69                         })
70
71                         local result = {}
72                         local _, zone
73                         for _, zone in ipairs(zones.TZ) do
74                                 result[zone[1]] = {
75                                         tzstring = zone[2],
76                                         active = (res and res.value == zone[1]) and true or nil
77                                 }
78                         end
79                         return { result = result }
80                 end
81         },
82
83         leds = {
84                 call = function()
85                         local iter   = fs.dir("/sys/class/leds")
86                         local result = { }
87
88                         if iter then
89                                 local led
90                                 for led in iter do
91                                         local m, s
92
93                                         result[led] = { triggers = {} }
94
95                                         s = readfile("/sys/class/leds/"..led.."/trigger")
96                                         for s in (s or ""):gmatch("%S+") do
97                                                 m = s:match("^%[(.+)%]$")
98                                                 result[led].triggers[#result[led].triggers+1] = m or s
99                                                 result[led].active_trigger = m or result[led].active_trigger
100                                         end
101
102                                         s = readfile("/sys/class/leds/"..led.."/brightness")
103                                         if s then
104                                                 result[led].brightness = tonumber(s)
105                                         end
106
107                                         s = readfile("/sys/class/leds/"..led.."/max_brightness")
108                                         if s then
109                                                 result[led].max_brightness = tonumber(s)
110                                         end
111                                 end
112                         end
113
114                         return result
115                 end
116         },
117
118         usb = {
119                 call = function()
120                         local fs     = require "nixio.fs"
121                         local iter   = fs.glob("/sys/bus/usb/devices/[0-9]*/manufacturer")
122                         local result = { }
123
124                         if iter then
125                                 result.devices = {}
126
127                                 local p
128                                 for p in iter do
129                                         local id = p:match("%d+-%d+")
130
131                                         result.devices[#result.devices+1] = {
132                                                 id      = id,
133                                                 vid     = readfile("/sys/bus/usb/devices/"..id.."/idVendor"),
134                                                 pid     = readfile("/sys/bus/usb/devices/"..id.."/idProduct"),
135                                                 vendor  = readfile("/sys/bus/usb/devices/"..id.."/manufacturer"),
136                                                 product = readfile("/sys/bus/usb/devices/"..id.."/product"),
137                                                 speed   = tonumber((readfile("/sys/bus/usb/devices/"..id.."/product")))
138                                         }
139                                 end
140                         end
141
142                         iter = fs.glob("/sys/bus/usb/devices/*/usb[0-9]*-port[0-9]*")
143
144                         if iter then
145                                 result.ports = {}
146
147                                 local p
148                                 for p in iter do
149                                         local bus, port = p:match("usb(%d+)-port(%d+)")
150
151                                         result.ports[#result.ports+1] = {
152                                                 hub  = tonumber(bus),
153                                                 port = tonumber(port)
154                                         }
155                                 end
156                         end
157
158                         return result
159                 end
160         },
161
162         ifaddrs = {
163                 call = function()
164                         return { result = nixio.getifaddrs() }
165                 end
166         },
167
168         host_hints = {
169                 call = function()
170                         local sys = require "luci.sys"
171                         return sys.net.host_hints()
172                 end
173         },
174
175         duid_hints = {
176                 call = function()
177                         local fp = io.open('/var/hosts/odhcpd')
178                         local result = { }
179                         if fp then
180                                 for line in fp:lines() do
181                                         local dev, duid, name = string.match(line, '# (%S+)%s+(%S+)%s+%d+%s+(%S+)')
182                                         if dev and duid and name then
183                                                 result[duid] = {
184                                                         name = (name ~= "-") and name or nil,
185                                                         device = dev
186                                                 }
187                                         end
188                                 end
189                                 fp:close()
190                         end
191                         return result
192                 end
193         },
194
195         leases = {
196                 args = { family = 0 },
197                 call = function(args)
198                         local s = require "luci.tools.status"
199
200                         if args.family == 4 then
201                                 return { dhcp_leases = s.dhcp_leases() }
202                         elseif args.family == 6 then
203                                 return { dhcp6_leases = s.dhcp6_leases() }
204                         else
205                                 return {
206                                         dhcp_leases = s.dhcp_leases(),
207                                         dhcp6_leases = s.dhcp6_leases()
208                                 }
209                         end
210                 end
211         },
212
213         netdevs = {
214                 call = function(args)
215                         local dir = fs.dir("/sys/class/net")
216                         local result = { }
217                         if dir then
218                                 local dev
219                                 for dev in dir do
220                                         if not result[dev] then
221                                                 result[dev] = { name = dev }
222                                         end
223
224                                         if fs.access("/sys/class/net/"..dev.."/master") then
225                                                 local brname = fs.basename(fs.readlink("/sys/class/net/"..dev.."/master"))
226                                                 if not result[brname] then
227                                                         result[brname] = { name = brname }
228                                                 end
229
230                                                 if not result[brname].ports then
231                                                         result[brname].ports = { }
232                                                 end
233
234                                                 result[brname].ports[#result[brname].ports+1] = dev
235                                         elseif fs.access("/sys/class/net/"..dev.."/bridge") then
236                                                 if not result[dev].ports then
237                                                         result[dev].ports = { }
238                                                 end
239
240                                                 result[dev].id = readfile("/sys/class/net/"..dev.."/bridge/bridge_id")
241                                                 result[dev].stp = (readfile("/sys/class/net/"..dev.."/bridge/stp_state") ~= "0")
242                                                 result[dev].bridge = true
243                                         end
244
245                                         local opr = readfile("/sys/class/net/"..dev.."/operstate")
246
247                                         result[dev].up = (opr == "up" or opr == "unknown")
248                                         result[dev].type = tonumber(readfile("/sys/class/net/"..dev.."/type"))
249                                         result[dev].name = dev
250
251                                         local mtu = tonumber(readfile("/sys/class/net/"..dev.."/mtu"))
252                                         if mtu and mtu > 0 then
253                                                 result[dev].mtu = mtu
254                                         end
255
256                                         local qlen = tonumber(readfile("/sys/class/net/"..dev.."/tx_queue_len"))
257                                         if qlen and qlen > 0 then
258                                                 result[dev].qlen = qlen
259                                         end
260
261                                         local master = fs.readlink("/sys/class/net/"..dev.."/master")
262                                         if master then
263                                                 result[dev].master = fs.basename(master)
264                                         end
265
266                                         local mac = readfile("/sys/class/net/"..dev.."/address")
267                                         if mac and #mac == 17 then
268                                                 result[dev].mac = mac
269                                         end
270                                 end
271                         end
272                         return result
273                 end
274         },
275
276         boardjson = {
277                 call = function(args)
278                         local jsc = require "luci.jsonc"
279                         return jsc.parse(fs.readfile("/etc/board.json") or "")
280                 end
281         },
282
283         offload_support = {
284                 call = function()
285                         local fs = require "nixio.fs"
286                         return { offload_support = not not fs.access("/sys/module/xt_FLOWOFFLOAD/refcnt") }
287                 end
288         },
289
290         conntrack_helpers = {
291                 call = function()
292                         local fd = io.open("/usr/share/fw3/helpers.conf", "r")
293                         local rv = {}
294
295                         local line, entry
296                         while true do
297                                 line = fd:read("*l")
298                                 if not line then
299                                         break
300                                 end
301
302                                 if line:match("^%s*config%s") then
303                                         if entry then
304                                                 rv[#rv+1] = entry
305                                         end
306                                         entry = {}
307                                 else
308                                         local opt, val = line:match("^%s*option%s+(%S+)%s+(%S.*)$")
309                                         if opt and val then
310                                                 opt = opt:gsub("^'(.+)'$", "%1"):gsub('^"(.+)"$', "%1")
311                                                 val = val:gsub("^'(.+)'$", "%1"):gsub('^"(.+)"$', "%1")
312                                                 entry[opt] = val
313                                         end
314                                 end
315                         end
316
317                         if entry then
318                                 rv[#rv+1] = entry
319                         end
320
321                         return { helpers = rv }
322                 end
323         }
324 }
325
326 local function parseInput()
327         local parse = json.new()
328         local done, err
329
330         while true do
331                 local chunk = io.read(4096)
332                 if not chunk then
333                         break
334                 elseif not done and not err then
335                         done, err = parse:parse(chunk)
336                 end
337         end
338
339         if not done then
340                 print(json.stringify({ error = err or "Incomplete input" }))
341                 os.exit(1)
342         end
343
344         return parse:get()
345 end
346
347 local function validateArgs(func, uargs)
348         local method = methods[func]
349         if not method then
350                 print(json.stringify({ error = "Method not found" }))
351                 os.exit(1)
352         end
353
354         if type(uargs) ~= "table" then
355                 print(json.stringify({ error = "Invalid arguments" }))
356                 os.exit(1)
357         end
358
359         uargs.ubus_rpc_session = nil
360
361         local k, v
362         local margs = method.args or {}
363         for k, v in pairs(uargs) do
364                 if margs[k] == nil or
365                    (v ~= nil and type(v) ~= type(margs[k]))
366                 then
367                         print(json.stringify({ error = "Invalid arguments" }))
368                         os.exit(1)
369                 end
370         end
371
372         return method
373 end
374
375 if arg[1] == "list" then
376         local _, method, rv = nil, nil, {}
377         for _, method in pairs(methods) do rv[_] = method.args or {} end
378         print((json.stringify(rv):gsub(":%[%]", ":{}")))
379 elseif arg[1] == "call" then
380         local args = parseInput()
381         local method = validateArgs(arg[2], args)
382         local result, code = method.call(args)
383         print((json.stringify(result):gsub("^%[%]$", "{}")))
384         os.exit(code or 0)
385 end