luci-base: rework wireless state handling (#1179)
[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 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 =
17         tonumber, ipairs, pairs, pcall, type, next, setmetatable, require, select
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 -- containing the whole environment is returned otherwise this function returns
74 -- the corresponding string value for the given name or nil if no such variable
75 -- exists.
76 getenv = nixio.getenv
77
78 function hostname(newname)
79         if type(newname) == "string" and #newname > 0 then
80                 fs.writefile( "/proc/sys/kernel/hostname", newname )
81                 return newname
82         else
83                 return nixio.uname().nodename
84         end
85 end
86
87 function httpget(url, stream, target)
88         if not target then
89                 local source = stream and io.popen or luci.util.exec
90                 return source("wget -qO- '"..url:gsub("'", "").."'")
91         else
92                 return os.execute("wget -qO '%s' '%s'" %
93                         {target:gsub("'", ""), url:gsub("'", "")})
94         end
95 end
96
97 function reboot()
98         return os.execute("reboot >/dev/null 2>&1")
99 end
100
101 function syslog()
102         return luci.util.exec("logread")
103 end
104
105 function dmesg()
106         return luci.util.exec("dmesg")
107 end
108
109 function uniqueid(bytes)
110         local rand = fs.readfile("/dev/urandom", bytes)
111         return rand and nixio.bin.hexlify(rand)
112 end
113
114 function uptime()
115         return nixio.sysinfo().uptime
116 end
117
118
119 net = {}
120
121 local function _nethints(what, callback)
122         local _, k, e, mac, ip, name
123         local cur = uci.cursor()
124         local ifn = { }
125         local hosts = { }
126         local lookup = { }
127
128         local function _add(i, ...)
129                 local k = select(i, ...)
130                 if k then
131                         if not hosts[k] then hosts[k] = { } end
132                         hosts[k][1] = select(1, ...) or hosts[k][1]
133                         hosts[k][2] = select(2, ...) or hosts[k][2]
134                         hosts[k][3] = select(3, ...) or hosts[k][3]
135                         hosts[k][4] = select(4, ...) or hosts[k][4]
136                 end
137         end
138
139         luci.ip.neighbors(nil, function(neigh)
140                 if neigh.mac and neigh.family == 4 then
141                         _add(what, neigh.mac:upper(), neigh.dest:string(), nil, nil)
142                 elseif neigh.mac and neigh.family == 6 then
143                         _add(what, neigh.mac:upper(), nil, neigh.dest:string(), nil)
144                 end
145         end)
146
147         if fs.access("/etc/ethers") then
148                 for e in io.lines("/etc/ethers") do
149                         mac, ip = e:match("^([a-f0-9]%S+) (%S+)")
150                         if mac and ip then
151                                 _add(what, mac:upper(), ip, nil, nil)
152                         end
153                 end
154         end
155
156         cur:foreach("dhcp", "dnsmasq",
157                 function(s)
158                         if s.leasefile and fs.access(s.leasefile) then
159                                 for e in io.lines(s.leasefile) do
160                                         mac, ip, name = e:match("^%d+ (%S+) (%S+) (%S+)")
161                                         if mac and ip then
162                                                 _add(what, mac:upper(), ip, nil, name ~= "*" and name)
163                                         end
164                                 end
165                         end
166                 end
167         )
168
169         cur:foreach("dhcp", "host",
170                 function(s)
171                         for mac in luci.util.imatch(s.mac) do
172                                 _add(what, mac:upper(), s.ip, nil, s.name)
173                         end
174                 end)
175
176         for _, e in ipairs(nixio.getifaddrs()) do
177                 if e.name ~= "lo" then
178                         ifn[e.name] = ifn[e.name] or { }
179                         if e.family == "packet" and e.addr and #e.addr == 17 then
180                                 ifn[e.name][1] = e.addr:upper()
181                         elseif e.family == "inet" then
182                                 ifn[e.name][2] = e.addr
183                         elseif e.family == "inet6" then
184                                 ifn[e.name][3] = e.addr
185                         end
186                 end
187         end
188
189         for _, e in pairs(ifn) do
190                 if e[what] and (e[2] or e[3]) then
191                         _add(what, e[1], e[2], e[3], e[4])
192                 end
193         end
194
195         for _, e in pairs(hosts) do
196                 lookup[#lookup+1] = (what > 1) and e[what] or (e[2] or e[3])
197         end
198
199         if #lookup > 0 then
200                 lookup = luci.util.ubus("network.rrdns", "lookup", {
201                         addrs   = lookup,
202                         timeout = 250,
203                         limit   = 1000
204                 }) or { }
205         end
206
207         for _, e in luci.util.kspairs(hosts) do
208                 callback(e[1], e[2], e[3], lookup[e[2]] or lookup[e[3]] or e[4])
209         end
210 end
211
212 --          Each entry contains the values in the following order:
213 --          [ "mac", "name" ]
214 function net.mac_hints(callback)
215         if callback then
216                 _nethints(1, function(mac, v4, v6, name)
217                         name = name or v4
218                         if name and name ~= mac then
219                                 callback(mac, name or v4)
220                         end
221                 end)
222         else
223                 local rv = { }
224                 _nethints(1, function(mac, v4, v6, name)
225                         name = name or v4
226                         if name and name ~= mac then
227                                 rv[#rv+1] = { mac, name or v4 }
228                         end
229                 end)
230                 return rv
231         end
232 end
233
234 --          Each entry contains the values in the following order:
235 --          [ "ip", "name" ]
236 function net.ipv4_hints(callback)
237         if callback then
238                 _nethints(2, function(mac, v4, v6, name)
239                         name = name or mac
240                         if name and name ~= v4 then
241                                 callback(v4, name)
242                         end
243                 end)
244         else
245                 local rv = { }
246                 _nethints(2, function(mac, v4, v6, name)
247                         name = name or mac
248                         if name and name ~= v4 then
249                                 rv[#rv+1] = { v4, name }
250                         end
251                 end)
252                 return rv
253         end
254 end
255
256 --          Each entry contains the values in the following order:
257 --          [ "ip", "name" ]
258 function net.ipv6_hints(callback)
259         if callback then
260                 _nethints(3, function(mac, v4, v6, name)
261                         name = name or mac
262                         if name and name ~= v6 then
263                                 callback(v6, name)
264                         end
265                 end)
266         else
267                 local rv = { }
268                 _nethints(3, function(mac, v4, v6, name)
269                         name = name or mac
270                         if name and name ~= v6 then
271                                 rv[#rv+1] = { v6, name }
272                         end
273                 end)
274                 return rv
275         end
276 end
277
278 function net.host_hints(callback)
279         if callback then
280                 _nethints(1, function(mac, v4, v6, name)
281                         if mac and mac ~= "00:00:00:00:00:00" and (v4 or v6 or name) then
282                                 callback(mac, v4, v6, name)
283                         end
284                 end)
285         else
286                 local rv = { }
287                 _nethints(1, function(mac, v4, v6, name)
288                         if mac and mac ~= "00:00:00:00:00:00" and (v4 or v6 or name) then
289                                 local e = { }
290                                 if v4   then e.ipv4 = v4   end
291                                 if v6   then e.ipv6 = v6   end
292                                 if name then e.name = name end
293                                 rv[mac] = e
294                         end
295                 end)
296                 return rv
297         end
298 end
299
300 function net.conntrack(callback)
301         local ok, nfct = pcall(io.lines, "/proc/net/nf_conntrack")
302         if not ok or not nfct then
303                 return nil
304         end
305
306         local line, connt = nil, (not callback) and { }
307         for line in nfct do
308                 local fam, l3, l4, timeout, tuples =
309                         line:match("^(ipv[46]) +(%d+) +%S+ +(%d+) +(%d+) +(.+)$")
310
311                 if fam and l3 and l4 and timeout and not tuples:match("^TIME_WAIT ") then
312                         l4 = nixio.getprotobynumber(l4)
313
314                         local entry = {
315                                 bytes = 0,
316                                 packets = 0,
317                                 layer3 = fam,
318                                 layer4 = l4 and l4.name or "unknown",
319                                 timeout = tonumber(timeout, 10)
320                         }
321
322                         local key, val
323                         for key, val in tuples:gmatch("(%w+)=(%S+)") do
324                                 if key == "bytes" or key == "packets" then
325                                         entry[key] = entry[key] + tonumber(val, 10)
326                                 elseif key == "src" or key == "dst" then
327                                         if entry[key] == nil then
328                                                 entry[key] = luci.ip.new(val):string()
329                                         end
330                                 elseif key == "sport" or key == "dport" then
331                                         if entry[key] == nil then
332                                                 entry[key] = val
333                                         end
334                                 elseif val then
335                                         entry[key] = val
336                                 end
337                         end
338
339                         if callback then
340                                 callback(entry)
341                         else
342                                 connt[#connt+1] = entry
343                         end
344                 end
345         end
346
347         return callback and true or connt
348 end
349
350 function net.devices()
351         local devs = {}
352         local seen = {}
353         for k, v in ipairs(nixio.getifaddrs()) do
354                 if v.name and not seen[v.name] then
355                         seen[v.name] = true
356                         devs[#devs+1] = v.name
357                 end
358         end
359         return devs
360 end
361
362
363 process = {}
364
365 function process.info(key)
366         local s = {uid = nixio.getuid(), gid = nixio.getgid()}
367         return not key and s or s[key]
368 end
369
370 function process.list()
371         local data = {}
372         local k
373         local ps = luci.util.execi("/bin/busybox top -bn1")
374
375         if not ps then
376                 return
377         end
378
379         for line in ps do
380                 local pid, ppid, user, stat, vsz, mem, cpu, cmd = line:match(
381                         "^ *(%d+) +(%d+) +(%S.-%S) +([RSDZTW][W ][<N ]) +(%d+) +(%d+%%) +(%d+%%) +(.+)"
382                 )
383
384                 local idx = tonumber(pid)
385                 if idx then
386                         data[idx] = {
387                                 ['PID']     = pid,
388                                 ['PPID']    = ppid,
389                                 ['USER']    = user,
390                                 ['STAT']    = stat,
391                                 ['VSZ']     = vsz,
392                                 ['%MEM']    = mem,
393                                 ['%CPU']    = cpu,
394                                 ['COMMAND'] = cmd
395                         }
396                 end
397         end
398
399         return data
400 end
401
402 function process.setgroup(gid)
403         return nixio.setgid(gid)
404 end
405
406 function process.setuser(uid)
407         return nixio.setuid(uid)
408 end
409
410 process.signal = nixio.kill
411
412
413 user = {}
414
415 --                              { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" }
416 user.getuser = nixio.getpw
417
418 function user.getpasswd(username)
419         local pwe = nixio.getsp and nixio.getsp(username) or nixio.getpw(username)
420         local pwh = pwe and (pwe.pwdp or pwe.passwd)
421         if not pwh or #pwh < 1 or pwh == "!" or pwh == "x" then
422                 return nil, pwe
423         else
424                 return pwh, pwe
425         end
426 end
427
428 function user.checkpasswd(username, pass)
429         local pwh, pwe = user.getpasswd(username)
430         if pwe then
431                 return (pwh == nil or nixio.crypt(pass, pwh) == pwh)
432         end
433         return false
434 end
435
436 function user.setpasswd(username, password)
437         if password then
438                 password = password:gsub("'", [['"'"']])
439         end
440
441         if username then
442                 username = username:gsub("'", [['"'"']])
443         end
444
445         return os.execute(
446                 "(echo '" .. password .. "'; sleep 1; echo '" .. password .. "') | " ..
447                 "passwd '" .. username .. "' >/dev/null 2>&1"
448         )
449 end
450
451
452 wifi = {}
453
454 function wifi.getiwinfo(ifname)
455         ntm.init()
456         local wnet = ntm.wifinet(ifname)
457         return wnet.iwinfo or { ifname = ifname }
458 end
459
460
461 init = {}
462 init.dir = "/etc/init.d/"
463
464 function init.names()
465         local names = { }
466         for name in fs.glob(init.dir.."*") do
467                 names[#names+1] = fs.basename(name)
468         end
469         return names
470 end
471
472 function init.index(name)
473         if fs.access(init.dir..name) then
474                 return call("env -i sh -c 'source %s%s enabled; exit ${START:-255}' >/dev/null"
475                         %{ init.dir, name })
476         end
477 end
478
479 local function init_action(action, name)
480         if fs.access(init.dir..name) then
481                 return call("env -i %s%s %s >/dev/null" %{ init.dir, name, action })
482         end
483 end
484
485 function init.enabled(name)
486         return (init_action("enabled", name) == 0)
487 end
488
489 function init.enable(name)
490         return (init_action("enable", name) == 1)
491 end
492
493 function init.disable(name)
494         return (init_action("disable", name) == 0)
495 end
496
497 function init.start(name)
498         return (init_action("start", name) == 0)
499 end
500
501 function init.stop(name)
502         return (init_action("stop", name) == 0)
503 end