luci-app-upnp: Adding and displaying "Description" to upnp data
[project/luci.git] / modules / luci-base / luasrc / sys.lua
1 -- Copyright 2008 Steven Barth <steven@midlink.org>
2 -- Licensed to the public under the Apache License 2.0.
3
4 local io = require "io"
5 local os = require "os"
6 local table = require "table"
7 local nixio = require "nixio"
8 local fs = require "nixio.fs"
9 local uci = require "luci.model.uci"
10
11 local luci = {}
12 luci.util = require "luci.util"
13 luci.ip = require "luci.ip"
14
15 local tonumber, ipairs, pairs, pcall, type, next, setmetatable, require, select =
16 tonumber, ipairs, pairs, pcall, type, next, setmetatable, require, select
17
18
19 module "luci.sys"
20
21 function call(...)
22 return os.execute(...) / 256
23 end
24
25 exec = luci.util.exec
26
27 function mounts()
28 local data = {}
29 local k = {"fs", "blocks", "used", "available", "percent", "mountpoint"}
30 local ps = luci.util.execi("df")
31
32 if not ps then
33 return
34 else
35 ps()
36 end
37
38 for line in ps do
39 local row = {}
40
41 local j = 1
42 for value in line:gmatch("[^%s]+") do
43 row[k[j]] = value
44 j = j + 1
45 end
46
47 if row[k[1]] then
48
49 -- this is a rather ugly workaround to cope with wrapped lines in
50 -- the df output:
51 --
52 -- /dev/scsi/host0/bus0/target0/lun0/part3
53 -- 114382024 93566472 15005244 86% /mnt/usb
54 --
55
56 if not row[k[2]] then
57 j = 2
58 line = ps()
59 for value in line:gmatch("[^%s]+") do
60 row[k[j]] = value
61 j = j + 1
62 end
63 end
64
65 table.insert(data, row)
66 end
67 end
68
69 return data
70 end
71
72 -- containing the whole environment is returned otherwise this function returns
73 -- the corresponding string value for the given name or nil if no such variable
74 -- exists.
75 getenv = nixio.getenv
76
77 function hostname(newname)
78 if type(newname) == "string" and #newname > 0 then
79 fs.writefile( "/proc/sys/kernel/hostname", newname )
80 return newname
81 else
82 return nixio.uname().nodename
83 end
84 end
85
86 function httpget(url, stream, target)
87 if not target then
88 local source = stream and io.popen or luci.util.exec
89 return source("wget -qO- '"..url:gsub("'", "").."'")
90 else
91 return os.execute("wget -qO '%s' '%s'" %
92 {target:gsub("'", ""), url:gsub("'", "")})
93 end
94 end
95
96 function reboot()
97 return os.execute("reboot >/dev/null 2>&1")
98 end
99
100 function syslog()
101 return luci.util.exec("logread")
102 end
103
104 function dmesg()
105 return luci.util.exec("dmesg")
106 end
107
108 function uniqueid(bytes)
109 local rand = fs.readfile("/dev/urandom", bytes)
110 return rand and nixio.bin.hexlify(rand)
111 end
112
113 function uptime()
114 return nixio.sysinfo().uptime
115 end
116
117
118 net = {}
119
120 -- The following fields are defined for arp entry objects:
121 -- { "IP address", "HW address", "HW type", "Flags", "Mask", "Device" }
122 function net.arptable(callback)
123 local arp = (not callback) and {} or nil
124 local e, r, v
125 if fs.access("/proc/net/arp") then
126 for e in io.lines("/proc/net/arp") do
127 local r = { }, v
128 for v in e:gmatch("%S+") do
129 r[#r+1] = v
130 end
131
132 if r[1] ~= "IP" then
133 local x = {
134 ["IP address"] = r[1],
135 ["HW type"] = r[2],
136 ["Flags"] = r[3],
137 ["HW address"] = r[4],
138 ["Mask"] = r[5],
139 ["Device"] = r[6]
140 }
141
142 if callback then
143 callback(x)
144 else
145 arp = arp or { }
146 arp[#arp+1] = x
147 end
148 end
149 end
150 end
151 return arp
152 end
153
154 local function _nethints(what, callback)
155 local _, k, e, mac, ip, name
156 local cur = uci.cursor()
157 local ifn = { }
158 local hosts = { }
159
160 local function _add(i, ...)
161 local k = select(i, ...)
162 if k then
163 if not hosts[k] then hosts[k] = { } end
164 hosts[k][1] = select(1, ...) or hosts[k][1]
165 hosts[k][2] = select(2, ...) or hosts[k][2]
166 hosts[k][3] = select(3, ...) or hosts[k][3]
167 hosts[k][4] = select(4, ...) or hosts[k][4]
168 end
169 end
170
171 luci.ip.neighbors(nil, function(neigh)
172 if neigh.mac and neigh.family == 4 then
173 _add(what, neigh.mac:upper(), neigh.dest:string(), nil, nil)
174 elseif neigh.mac and neigh.family == 6 then
175 _add(what, neigh.mac:upper(), nil, neigh.dest:string(), nil)
176 end
177 end)
178
179 if fs.access("/etc/ethers") then
180 for e in io.lines("/etc/ethers") do
181 mac, ip = e:match("^([a-f0-9]%S+) (%S+)")
182 if mac and ip then
183 _add(what, mac:upper(), ip, nil, nil)
184 end
185 end
186 end
187
188 cur:foreach("dhcp", "dnsmasq",
189 function(s)
190 if s.leasefile and fs.access(s.leasefile) then
191 for e in io.lines(s.leasefile) do
192 mac, ip, name = e:match("^%d+ (%S+) (%S+) (%S+)")
193 if mac and ip then
194 _add(what, mac:upper(), ip, nil, name ~= "*" and name)
195 end
196 end
197 end
198 end
199 )
200
201 cur:foreach("dhcp", "host",
202 function(s)
203 for mac in luci.util.imatch(s.mac) do
204 _add(what, mac:upper(), s.ip, nil, s.name)
205 end
206 end)
207
208 for _, e in ipairs(nixio.getifaddrs()) do
209 if e.name ~= "lo" then
210 ifn[e.name] = ifn[e.name] or { }
211 if e.family == "packet" and e.addr and #e.addr == 17 then
212 ifn[e.name][1] = e.addr:upper()
213 elseif e.family == "inet" then
214 ifn[e.name][2] = e.addr
215 elseif e.family == "inet6" then
216 ifn[e.name][3] = e.addr
217 end
218 end
219 end
220
221 for _, e in pairs(ifn) do
222 if e[what] and (e[2] or e[3]) then
223 _add(what, e[1], e[2], e[3], e[4])
224 end
225 end
226
227 for _, e in luci.util.kspairs(hosts) do
228 callback(e[1], e[2], e[3], e[4])
229 end
230 end
231
232 -- Each entry contains the values in the following order:
233 -- [ "mac", "name" ]
234 function net.mac_hints(callback)
235 if callback then
236 _nethints(1, function(mac, v4, v6, name)
237 name = name or nixio.getnameinfo(v4 or v6, nil, 100) or v4
238 if name and name ~= mac then
239 callback(mac, name or nixio.getnameinfo(v4 or v6, nil, 100) or v4)
240 end
241 end)
242 else
243 local rv = { }
244 _nethints(1, function(mac, v4, v6, name)
245 name = name or nixio.getnameinfo(v4 or v6, nil, 100) or v4
246 if name and name ~= mac then
247 rv[#rv+1] = { mac, name or nixio.getnameinfo(v4 or v6, nil, 100) or v4 }
248 end
249 end)
250 return rv
251 end
252 end
253
254 -- Each entry contains the values in the following order:
255 -- [ "ip", "name" ]
256 function net.ipv4_hints(callback)
257 if callback then
258 _nethints(2, function(mac, v4, v6, name)
259 name = name or nixio.getnameinfo(v4, nil, 100) or mac
260 if name and name ~= v4 then
261 callback(v4, name)
262 end
263 end)
264 else
265 local rv = { }
266 _nethints(2, function(mac, v4, v6, name)
267 name = name or nixio.getnameinfo(v4, nil, 100) or mac
268 if name and name ~= v4 then
269 rv[#rv+1] = { v4, name }
270 end
271 end)
272 return rv
273 end
274 end
275
276 -- Each entry contains the values in the following order:
277 -- [ "ip", "name" ]
278 function net.ipv6_hints(callback)
279 if callback then
280 _nethints(3, function(mac, v4, v6, name)
281 name = name or nixio.getnameinfo(v6, nil, 100) or mac
282 if name and name ~= v6 then
283 callback(v6, name)
284 end
285 end)
286 else
287 local rv = { }
288 _nethints(3, function(mac, v4, v6, name)
289 name = name or nixio.getnameinfo(v6, nil, 100) or mac
290 if name and name ~= v6 then
291 rv[#rv+1] = { v6, name }
292 end
293 end)
294 return rv
295 end
296 end
297
298 function net.host_hints(callback)
299 if callback then
300 _nethints(1, function(mac, v4, v6, name)
301 if mac and mac ~= "00:00:00:00:00:00" and (v4 or v6 or name) then
302 callback(mac, v4, v6, name)
303 end
304 end)
305 else
306 local rv = { }
307 _nethints(1, function(mac, v4, v6, name)
308 if mac and mac ~= "00:00:00:00:00:00" and (v4 or v6 or name) then
309 local e = { }
310 if v4 then e.ipv4 = v4 end
311 if v6 then e.ipv6 = v6 end
312 if name then e.name = name end
313 rv[mac] = e
314 end
315 end)
316 return rv
317 end
318 end
319
320 function net.conntrack(callback)
321 local ok, nfct = pcall(io.lines, "/proc/net/nf_conntrack")
322 if not ok or not nfct then
323 return nil
324 end
325
326 local line, connt = nil, (not callback) and { }
327 for line in nfct do
328 local fam, l3, l4, timeout, tuples =
329 line:match("^(ipv[46]) +(%d+) +%S+ +(%d+) +(%d+) +(.+)$")
330
331 if fam and l3 and l4 and timeout and not tuples:match("^TIME_WAIT ") then
332 l4 = nixio.getprotobynumber(l4)
333
334 local entry = {
335 bytes = 0,
336 packets = 0,
337 layer3 = fam,
338 layer4 = l4 and l4.name or "unknown",
339 timeout = tonumber(timeout, 10)
340 }
341
342 local key, val
343 for key, val in tuples:gmatch("(%w+)=(%S+)") do
344 if key == "bytes" or key == "packets" then
345 entry[key] = entry[key] + tonumber(val, 10)
346 elseif key == "src" or key == "dst" then
347 if entry[key] == nil then
348 entry[key] = luci.ip.new(val):string()
349 end
350 elseif key == "sport" or key == "dport" then
351 if entry[key] == nil then
352 entry[key] = val
353 end
354 elseif val then
355 entry[key] = val
356 end
357 end
358
359 if callback then
360 callback(entry)
361 else
362 connt[#connt+1] = entry
363 end
364 end
365 end
366
367 return callback and true or connt
368 end
369
370 function net.devices()
371 local devs = {}
372 for k, v in ipairs(nixio.getifaddrs()) do
373 if v.family == "packet" then
374 devs[#devs+1] = v.name
375 end
376 end
377 return devs
378 end
379
380
381 function net.deviceinfo()
382 local devs = {}
383 for k, v in ipairs(nixio.getifaddrs()) do
384 if v.family == "packet" then
385 local d = v.data
386 d[1] = d.rx_bytes
387 d[2] = d.rx_packets
388 d[3] = d.rx_errors
389 d[4] = d.rx_dropped
390 d[5] = 0
391 d[6] = 0
392 d[7] = 0
393 d[8] = d.multicast
394 d[9] = d.tx_bytes
395 d[10] = d.tx_packets
396 d[11] = d.tx_errors
397 d[12] = d.tx_dropped
398 d[13] = 0
399 d[14] = d.collisions
400 d[15] = 0
401 d[16] = 0
402 devs[v.name] = d
403 end
404 end
405 return devs
406 end
407
408
409 -- The following fields are defined for route entry tables:
410 -- { "dest", "gateway", "metric", "refcount", "usecount", "irtt",
411 -- "flags", "device" }
412 function net.routes(callback)
413 local routes = { }
414
415 for line in io.lines("/proc/net/route") do
416
417 local dev, dst_ip, gateway, flags, refcnt, usecnt, metric,
418 dst_mask, mtu, win, irtt = line:match(
419 "([^%s]+)\t([A-F0-9]+)\t([A-F0-9]+)\t([A-F0-9]+)\t" ..
420 "(%d+)\t(%d+)\t(%d+)\t([A-F0-9]+)\t(%d+)\t(%d+)\t(%d+)"
421 )
422
423 if dev then
424 gateway = luci.ip.Hex( gateway, 32, luci.ip.FAMILY_INET4 )
425 dst_mask = luci.ip.Hex( dst_mask, 32, luci.ip.FAMILY_INET4 )
426 dst_ip = luci.ip.Hex(
427 dst_ip, dst_mask:prefix(dst_mask), luci.ip.FAMILY_INET4
428 )
429
430 local rt = {
431 dest = dst_ip,
432 gateway = gateway,
433 metric = tonumber(metric),
434 refcount = tonumber(refcnt),
435 usecount = tonumber(usecnt),
436 mtu = tonumber(mtu),
437 window = tonumber(window),
438 irtt = tonumber(irtt),
439 flags = tonumber(flags, 16),
440 device = dev
441 }
442
443 if callback then
444 callback(rt)
445 else
446 routes[#routes+1] = rt
447 end
448 end
449 end
450
451 return routes
452 end
453
454 -- The following fields are defined for route entry tables:
455 -- { "source", "dest", "nexthop", "metric", "refcount", "usecount",
456 -- "flags", "device" }
457 function net.routes6(callback)
458 if fs.access("/proc/net/ipv6_route", "r") then
459 local routes = { }
460
461 for line in io.lines("/proc/net/ipv6_route") do
462
463 local dst_ip, dst_prefix, src_ip, src_prefix, nexthop,
464 metric, refcnt, usecnt, flags, dev = line:match(
465 "([a-f0-9]+) ([a-f0-9]+) " ..
466 "([a-f0-9]+) ([a-f0-9]+) " ..
467 "([a-f0-9]+) ([a-f0-9]+) " ..
468 "([a-f0-9]+) ([a-f0-9]+) " ..
469 "([a-f0-9]+) +([^%s]+)"
470 )
471
472 if dst_ip and dst_prefix and
473 src_ip and src_prefix and
474 nexthop and metric and
475 refcnt and usecnt and
476 flags and dev
477 then
478 src_ip = luci.ip.Hex(
479 src_ip, tonumber(src_prefix, 16), luci.ip.FAMILY_INET6, false
480 )
481
482 dst_ip = luci.ip.Hex(
483 dst_ip, tonumber(dst_prefix, 16), luci.ip.FAMILY_INET6, false
484 )
485
486 nexthop = luci.ip.Hex( nexthop, 128, luci.ip.FAMILY_INET6, false )
487
488 local rt = {
489 source = src_ip,
490 dest = dst_ip,
491 nexthop = nexthop,
492 metric = tonumber(metric, 16),
493 refcount = tonumber(refcnt, 16),
494 usecount = tonumber(usecnt, 16),
495 flags = tonumber(flags, 16),
496 device = dev,
497
498 -- lua number is too small for storing the metric
499 -- add a metric_raw field with the original content
500 metric_raw = metric
501 }
502
503 if callback then
504 callback(rt)
505 else
506 routes[#routes+1] = rt
507 end
508 end
509 end
510
511 return routes
512 end
513 end
514
515 function net.pingtest(host)
516 return os.execute("ping -c1 '"..host:gsub("'", '').."' >/dev/null 2>&1")
517 end
518
519
520 process = {}
521
522 function process.info(key)
523 local s = {uid = nixio.getuid(), gid = nixio.getgid()}
524 return not key and s or s[key]
525 end
526
527 function process.list()
528 local data = {}
529 local k
530 local ps = luci.util.execi("/bin/busybox top -bn1")
531
532 if not ps then
533 return
534 end
535
536 for line in ps do
537 local pid, ppid, user, stat, vsz, mem, cpu, cmd = line:match(
538 "^ *(%d+) +(%d+) +(%S.-%S) +([RSDZTW][W ][<N ]) +(%d+) +(%d+%%) +(%d+%%) +(.+)"
539 )
540
541 local idx = tonumber(pid)
542 if idx then
543 data[idx] = {
544 ['PID'] = pid,
545 ['PPID'] = ppid,
546 ['USER'] = user,
547 ['STAT'] = stat,
548 ['VSZ'] = vsz,
549 ['%MEM'] = mem,
550 ['%CPU'] = cpu,
551 ['COMMAND'] = cmd
552 }
553 end
554 end
555
556 return data
557 end
558
559 function process.setgroup(gid)
560 return nixio.setgid(gid)
561 end
562
563 function process.setuser(uid)
564 return nixio.setuid(uid)
565 end
566
567 process.signal = nixio.kill
568
569
570 user = {}
571
572 -- { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" }
573 user.getuser = nixio.getpw
574
575 function user.getpasswd(username)
576 local pwe = nixio.getsp and nixio.getsp(username) or nixio.getpw(username)
577 local pwh = pwe and (pwe.pwdp or pwe.passwd)
578 if not pwh or #pwh < 1 or pwh == "!" or pwh == "x" then
579 return nil, pwe
580 else
581 return pwh, pwe
582 end
583 end
584
585 function user.checkpasswd(username, pass)
586 local pwh, pwe = user.getpasswd(username)
587 if pwe then
588 return (pwh == nil or nixio.crypt(pass, pwh) == pwh)
589 end
590 return false
591 end
592
593 function user.setpasswd(username, password)
594 if password then
595 password = password:gsub("'", [['"'"']])
596 end
597
598 if username then
599 username = username:gsub("'", [['"'"']])
600 end
601
602 return os.execute(
603 "(echo '" .. password .. "'; sleep 1; echo '" .. password .. "') | " ..
604 "passwd '" .. username .. "' >/dev/null 2>&1"
605 )
606 end
607
608
609 wifi = {}
610
611 function wifi.getiwinfo(ifname)
612 local stat, iwinfo = pcall(require, "iwinfo")
613
614 if ifname then
615 local d, n = ifname:match("^(%w+)%.network(%d+)")
616 local wstate = luci.util.ubus("network.wireless", "status") or { }
617
618 d = d or ifname
619 n = n and tonumber(n) or 1
620
621 if type(wstate[d]) == "table" and
622 type(wstate[d].interfaces) == "table" and
623 type(wstate[d].interfaces[n]) == "table" and
624 type(wstate[d].interfaces[n].ifname) == "string"
625 then
626 ifname = wstate[d].interfaces[n].ifname
627 else
628 ifname = d
629 end
630
631 local t = stat and iwinfo.type(ifname)
632 local x = t and iwinfo[t] or { }
633 return setmetatable({}, {
634 __index = function(t, k)
635 if k == "ifname" then
636 return ifname
637 elseif x[k] then
638 return x[k](ifname)
639 end
640 end
641 })
642 end
643 end
644
645
646 init = {}
647 init.dir = "/etc/init.d/"
648
649 function init.names()
650 local names = { }
651 for name in fs.glob(init.dir.."*") do
652 names[#names+1] = fs.basename(name)
653 end
654 return names
655 end
656
657 function init.index(name)
658 if fs.access(init.dir..name) then
659 return call("env -i sh -c 'source %s%s enabled; exit ${START:-255}' >/dev/null"
660 %{ init.dir, name })
661 end
662 end
663
664 local function init_action(action, name)
665 if fs.access(init.dir..name) then
666 return call("env -i %s%s %s >/dev/null" %{ init.dir, name, action })
667 end
668 end
669
670 function init.enabled(name)
671 return (init_action("enabled", name) == 0)
672 end
673
674 function init.enable(name)
675 return (init_action("enable", name) == 1)
676 end
677
678 function init.disable(name)
679 return (init_action("disable", name) == 0)
680 end
681
682 function init.start(name)
683 return (init_action("start", name) == 0)
684 end
685
686 function init.stop(name)
687 return (init_action("stop", name) == 0)
688 end