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