luci-base: increase max size of network dropdowns
[oweals/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 local ntm    = require "luci.model.network"
11
12 local luci  = {}
13 luci.util   = require "luci.util"
14 luci.ip     = require "luci.ip"
15
16 local tonumber, ipairs, pairs, pcall, type, next, setmetatable, require, select, unpack =
17         tonumber, ipairs, pairs, pcall, type, next, setmetatable, require, select, unpack
18
19
20 module "luci.sys"
21
22 function call(...)
23         return os.execute(...) / 256
24 end
25
26 exec = luci.util.exec
27
28 function mounts()
29         local data = {}
30         local k = {"fs", "blocks", "used", "available", "percent", "mountpoint"}
31         local ps = luci.util.execi("df")
32
33         if not ps then
34                 return
35         else
36                 ps()
37         end
38
39         for line in ps do
40                 local row = {}
41
42                 local j = 1
43                 for value in line:gmatch("[^%s]+") do
44                         row[k[j]] = value
45                         j = j + 1
46                 end
47
48                 if row[k[1]] then
49
50                         -- this is a rather ugly workaround to cope with wrapped lines in
51                         -- the df output:
52                         --
53                         --      /dev/scsi/host0/bus0/target0/lun0/part3
54                         --                   114382024  93566472  15005244  86% /mnt/usb
55                         --
56
57                         if not row[k[2]] then
58                                 j = 2
59                                 line = ps()
60                                 for value in line:gmatch("[^%s]+") do
61                                         row[k[j]] = value
62                                         j = j + 1
63                                 end
64                         end
65
66                         table.insert(data, row)
67                 end
68         end
69
70         return data
71 end
72
73 function mtds()
74         local data = {}
75
76         if fs.access("/proc/mtd") then
77                 for l in io.lines("/proc/mtd") do
78                         local d, s, e, n = l:match('^([^%s]+)%s+([^%s]+)%s+([^%s]+)%s+"([^%s]+)"')
79                         if s and n then
80                                 local d = {}
81                                 d.size = tonumber(s, 16)
82                                 d.name = n
83                                 table.insert(data, d)
84                         end
85                 end
86         end
87
88         return data
89 end
90
91 -- containing the whole environment is returned otherwise this function returns
92 -- the corresponding string value for the given name or nil if no such variable
93 -- exists.
94 getenv = nixio.getenv
95
96 function hostname(newname)
97         if type(newname) == "string" and #newname > 0 then
98                 fs.writefile( "/proc/sys/kernel/hostname", newname )
99                 return newname
100         else
101                 return nixio.uname().nodename
102         end
103 end
104
105 function httpget(url, stream, target)
106         if not target then
107                 local source = stream and io.popen or luci.util.exec
108                 return source("wget -qO- %s" % luci.util.shellquote(url))
109         else
110                 return os.execute("wget -qO %s %s" %
111                         {luci.util.shellquote(target), luci.util.shellquote(url)})
112         end
113 end
114
115 function reboot()
116         return os.execute("reboot >/dev/null 2>&1")
117 end
118
119 function syslog()
120         return luci.util.exec("logread")
121 end
122
123 function dmesg()
124         return luci.util.exec("dmesg")
125 end
126
127 function uniqueid(bytes)
128         local rand = fs.readfile("/dev/urandom", bytes)
129         return rand and nixio.bin.hexlify(rand)
130 end
131
132 function uptime()
133         return nixio.sysinfo().uptime
134 end
135
136
137 net = {}
138
139 local function _nethints(what, callback)
140         local _, k, e, mac, ip, name
141         local cur = uci.cursor()
142         local ifn = { }
143         local hosts = { }
144         local lookup = { }
145
146         local function _add(i, ...)
147                 local k = select(i, ...)
148                 if k then
149                         if not hosts[k] then hosts[k] = { } end
150                         hosts[k][1] = select(1, ...) or hosts[k][1]
151                         hosts[k][2] = select(2, ...) or hosts[k][2]
152                         hosts[k][3] = select(3, ...) or hosts[k][3]
153                         hosts[k][4] = select(4, ...) or hosts[k][4]
154                 end
155         end
156
157         luci.ip.neighbors(nil, function(neigh)
158                 if neigh.mac and neigh.family == 4 then
159                         _add(what, neigh.mac:string(), neigh.dest:string(), nil, nil)
160                 elseif neigh.mac and neigh.family == 6 then
161                         _add(what, neigh.mac:string(), nil, neigh.dest:string(), nil)
162                 end
163         end)
164
165         if fs.access("/etc/ethers") then
166                 for e in io.lines("/etc/ethers") do
167                         mac, name = e:match("^([a-fA-F0-9:-]+)%s+(%S+)")
168                         mac = luci.ip.checkmac(mac)
169                         if mac and name then
170                                 if luci.ip.checkip4(name) then
171                                         _add(what, mac, name, nil, nil)
172                                 else
173                                         _add(what, mac, nil, nil, name)
174                                 end
175                         end
176                 end
177         end
178
179         cur:foreach("dhcp", "dnsmasq",
180                 function(s)
181                         if s.leasefile and fs.access(s.leasefile) then
182                                 for e in io.lines(s.leasefile) do
183                                         mac, ip, name = e:match("^%d+ (%S+) (%S+) (%S+)")
184                                         mac = luci.ip.checkmac(mac)
185                                         if mac and ip then
186                                                 _add(what, mac, ip, nil, name ~= "*" and name)
187                                         end
188                                 end
189                         end
190                 end
191         )
192
193         cur:foreach("dhcp", "host",
194                 function(s)
195                         for mac in luci.util.imatch(s.mac) do
196                                 mac = luci.ip.checkmac(mac)
197                                 if mac then
198                                         _add(what, mac, s.ip, nil, s.name)
199                                 end
200                         end
201                 end)
202
203         for _, e in ipairs(nixio.getifaddrs()) do
204                 if e.name ~= "lo" then
205                         ifn[e.name] = ifn[e.name] or { }
206                         if e.family == "packet" and e.addr and #e.addr == 17 then
207                                 ifn[e.name][1] = e.addr:upper()
208                         elseif e.family == "inet" then
209                                 ifn[e.name][2] = e.addr
210                         elseif e.family == "inet6" then
211                                 ifn[e.name][3] = e.addr
212                         end
213                 end
214         end
215
216         for _, e in pairs(ifn) do
217                 if e[what] and (e[2] or e[3]) then
218                         _add(what, e[1], e[2], e[3], e[4])
219                 end
220         end
221
222         for _, e in pairs(hosts) do
223                 lookup[#lookup+1] = (what > 1) and e[what] or (e[2] or e[3])
224         end
225
226         if #lookup > 0 then
227                 lookup = luci.util.ubus("network.rrdns", "lookup", {
228                         addrs   = lookup,
229                         timeout = 250,
230                         limit   = 1000
231                 }) or { }
232         end
233
234         for _, e in luci.util.kspairs(hosts) do
235                 callback(e[1], e[2], e[3], lookup[e[2]] or lookup[e[3]] or e[4])
236         end
237 end
238
239 --          Each entry contains the values in the following order:
240 --          [ "mac", "name" ]
241 function net.mac_hints(callback)
242         if callback then
243                 _nethints(1, function(mac, v4, v6, name)
244                         name = name or v4
245                         if name and name ~= mac then
246                                 callback(mac, name or v4)
247                         end
248                 end)
249         else
250                 local rv = { }
251                 _nethints(1, function(mac, v4, v6, name)
252                         name = name or v4
253                         if name and name ~= mac then
254                                 rv[#rv+1] = { mac, name or v4 }
255                         end
256                 end)
257                 return rv
258         end
259 end
260
261 --          Each entry contains the values in the following order:
262 --          [ "ip", "name" ]
263 function net.ipv4_hints(callback)
264         if callback then
265                 _nethints(2, function(mac, v4, v6, name)
266                         name = name or mac
267                         if name and name ~= v4 then
268                                 callback(v4, name)
269                         end
270                 end)
271         else
272                 local rv = { }
273                 _nethints(2, function(mac, v4, v6, name)
274                         name = name or mac
275                         if name and name ~= v4 then
276                                 rv[#rv+1] = { v4, name }
277                         end
278                 end)
279                 return rv
280         end
281 end
282
283 --          Each entry contains the values in the following order:
284 --          [ "ip", "name" ]
285 function net.ipv6_hints(callback)
286         if callback then
287                 _nethints(3, function(mac, v4, v6, name)
288                         name = name or mac
289                         if name and name ~= v6 then
290                                 callback(v6, name)
291                         end
292                 end)
293         else
294                 local rv = { }
295                 _nethints(3, function(mac, v4, v6, name)
296                         name = name or mac
297                         if name and name ~= v6 then
298                                 rv[#rv+1] = { v6, name }
299                         end
300                 end)
301                 return rv
302         end
303 end
304
305 function net.host_hints(callback)
306         if callback then
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                                 callback(mac, v4, v6, name)
310                         end
311                 end)
312         else
313                 local rv = { }
314                 _nethints(1, function(mac, v4, v6, name)
315                         if mac and mac ~= "00:00:00:00:00:00" and (v4 or v6 or name) then
316                                 local e = { }
317                                 if v4   then e.ipv4 = v4   end
318                                 if v6   then e.ipv6 = v6   end
319                                 if name then e.name = name end
320                                 rv[mac] = e
321                         end
322                 end)
323                 return rv
324         end
325 end
326
327 function net.conntrack(callback)
328         local ok, nfct = pcall(io.lines, "/proc/net/nf_conntrack")
329         if not ok or not nfct then
330                 return nil
331         end
332
333         local line, connt = nil, (not callback) and { }
334         for line in nfct do
335                 local fam, l3, l4, timeout, tuples =
336                         line:match("^(ipv[46]) +(%d+) +%S+ +(%d+) +(%d+) +(.+)$")
337
338                 if fam and l3 and l4 and timeout and not tuples:match("^TIME_WAIT ") then
339                         l4 = nixio.getprotobynumber(l4)
340
341                         local entry = {
342                                 bytes = 0,
343                                 packets = 0,
344                                 layer3 = fam,
345                                 layer4 = l4 and l4.name or "unknown",
346                                 timeout = tonumber(timeout, 10)
347                         }
348
349                         local key, val
350                         for key, val in tuples:gmatch("(%w+)=(%S+)") do
351                                 if key == "bytes" or key == "packets" then
352                                         entry[key] = entry[key] + tonumber(val, 10)
353                                 elseif key == "src" or key == "dst" then
354                                         if entry[key] == nil then
355                                                 entry[key] = luci.ip.new(val):string()
356                                         end
357                                 elseif key == "sport" or key == "dport" then
358                                         if entry[key] == nil then
359                                                 entry[key] = val
360                                         end
361                                 elseif val then
362                                         entry[key] = val
363                                 end
364                         end
365
366                         if callback then
367                                 callback(entry)
368                         else
369                                 connt[#connt+1] = entry
370                         end
371                 end
372         end
373
374         return callback and true or connt
375 end
376
377 function net.devices()
378         local devs = {}
379         local seen = {}
380         for k, v in ipairs(nixio.getifaddrs()) do
381                 if v.name and not seen[v.name] then
382                         seen[v.name] = true
383                         devs[#devs+1] = v.name
384                 end
385         end
386         return devs
387 end
388
389
390 process = {}
391
392 function process.info(key)
393         local s = {uid = nixio.getuid(), gid = nixio.getgid()}
394         return not key and s or s[key]
395 end
396
397 function process.list()
398         local data = {}
399         local k
400         local ps = luci.util.execi("/bin/busybox top -bn1")
401
402         if not ps then
403                 return
404         end
405
406         for line in ps do
407                 local pid, ppid, user, stat, vsz, mem, cpu, cmd = line:match(
408                         "^ *(%d+) +(%d+) +(%S.-%S) +([RSDZTW][W ][<N ]) +(%d+) +(%d+%%) +(%d+%%) +(.+)"
409                 )
410
411                 local idx = tonumber(pid)
412                 if idx then
413                         data[idx] = {
414                                 ['PID']     = pid,
415                                 ['PPID']    = ppid,
416                                 ['USER']    = user,
417                                 ['STAT']    = stat,
418                                 ['VSZ']     = vsz,
419                                 ['%MEM']    = mem,
420                                 ['%CPU']    = cpu,
421                                 ['COMMAND'] = cmd
422                         }
423                 end
424         end
425
426         return data
427 end
428
429 function process.setgroup(gid)
430         return nixio.setgid(gid)
431 end
432
433 function process.setuser(uid)
434         return nixio.setuid(uid)
435 end
436
437 process.signal = nixio.kill
438
439 local function xclose(fd)
440         if fd and fd:fileno() > 2 then
441                 fd:close()
442         end
443 end
444
445 function process.exec(command, stdout, stderr, nowait)
446         local out_r, out_w, err_r, err_w
447         if stdout then out_r, out_w = nixio.pipe() end
448         if stderr then err_r, err_w = nixio.pipe() end
449
450         local pid = nixio.fork()
451         if pid == 0 then
452                 nixio.chdir("/")
453
454                 local null = nixio.open("/dev/null", "w+")
455                 if null then
456                         nixio.dup(out_w or null, nixio.stdout)
457                         nixio.dup(err_w or null, nixio.stderr)
458                         nixio.dup(null, nixio.stdin)
459                         xclose(out_w)
460                         xclose(out_r)
461                         xclose(err_w)
462                         xclose(err_r)
463                         xclose(null)
464                 end
465
466                 nixio.exec(unpack(command))
467                 os.exit(-1)
468         end
469
470         local _, pfds, rv = nil, {}, { code = -1, pid = pid }
471
472         xclose(out_w)
473         xclose(err_w)
474
475         if out_r then
476                 pfds[#pfds+1] = {
477                         fd = out_r,
478                         cb = type(stdout) == "function" and stdout,
479                         name = "stdout",
480                         events = nixio.poll_flags("in", "err", "hup")
481                 }
482         end
483
484         if err_r then
485                 pfds[#pfds+1] = {
486                         fd = err_r,
487                         cb = type(stderr) == "function" and stderr,
488                         name = "stderr",
489                         events = nixio.poll_flags("in", "err", "hup")
490                 }
491         end
492
493         while #pfds > 0 do
494                 local nfds, err = nixio.poll(pfds, -1)
495                 if not nfds and err ~= nixio.const.EINTR then
496                         break
497                 end
498
499                 local i
500                 for i = #pfds, 1, -1 do
501                         local rfd = pfds[i]
502                         if rfd.revents > 0 then
503                                 local chunk, err = rfd.fd:read(4096)
504                                 if chunk and #chunk > 0 then
505                                         if rfd.cb then
506                                                 rfd.cb(chunk)
507                                         else
508                                                 rfd.buf = rfd.buf or {}
509                                                 rfd.buf[#rfd.buf + 1] = chunk
510                                         end
511                                 else
512                                         table.remove(pfds, i)
513                                         if rfd.buf then
514                                                 rv[rfd.name] = table.concat(rfd.buf, "")
515                                         end
516                                         rfd.fd:close()
517                                 end
518                         end
519                 end
520         end
521
522         if not nowait then
523                 _, _, rv.code = nixio.waitpid(pid)
524         end
525
526         return rv
527 end
528
529
530 user = {}
531
532 --                              { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" }
533 user.getuser = nixio.getpw
534
535 function user.getpasswd(username)
536         local pwe = nixio.getsp and nixio.getsp(username) or nixio.getpw(username)
537         local pwh = pwe and (pwe.pwdp or pwe.passwd)
538         if not pwh or #pwh < 1 or pwh == "!" or pwh == "x" then
539                 return nil, pwe
540         else
541                 return pwh, pwe
542         end
543 end
544
545 function user.checkpasswd(username, pass)
546         local pwh, pwe = user.getpasswd(username)
547         if pwe then
548                 return (pwh == nil or nixio.crypt(pass, pwh) == pwh)
549         end
550         return false
551 end
552
553 function user.setpasswd(username, password)
554         return os.execute("(echo %s; sleep 1; echo %s) | passwd %s >/dev/null 2>&1" %{
555                 luci.util.shellquote(password),
556                 luci.util.shellquote(password),
557                 luci.util.shellquote(username)
558         })
559 end
560
561
562 wifi = {}
563
564 function wifi.getiwinfo(ifname)
565         ntm.init()
566
567         local wnet = ntm:get_wifinet(ifname)
568         if wnet and wnet.iwinfo then
569                 return wnet.iwinfo
570         end
571
572         local wdev = ntm:get_wifidev(ifname)
573         if wdev and wdev.iwinfo then
574                 return wdev.iwinfo
575         end
576
577         return { ifname = ifname }
578 end
579
580
581 init = {}
582 init.dir = "/etc/init.d/"
583
584 function init.names()
585         local names = { }
586         for name in fs.glob(init.dir.."*") do
587                 names[#names+1] = fs.basename(name)
588         end
589         return names
590 end
591
592 function init.index(name)
593         if fs.access(init.dir..name) then
594                 return call("env -i sh -c 'source %s%s enabled; exit ${START:-255}' >/dev/null"
595                         %{ init.dir, name })
596         end
597 end
598
599 local function init_action(action, name)
600         if fs.access(init.dir..name) then
601                 return call("env -i %s%s %s >/dev/null" %{ init.dir, name, action })
602         end
603 end
604
605 function init.enabled(name)
606         return (init_action("enabled", name) == 0)
607 end
608
609 function init.enable(name)
610         return (init_action("enable", name) == 1)
611 end
612
613 function init.disable(name)
614         return (init_action("disable", name) == 0)
615 end
616
617 function init.start(name)
618         return (init_action("start", name) == 0)
619 end
620
621 function init.stop(name)
622         return (init_action("stop", name) == 0)
623 end