Globally reduce copyright headers
[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 --- LuCI Linux and POSIX system utilities.
20 module "luci.sys"
21
22 --- Execute a given shell command and return the error code
23 -- @class               function
24 -- @name                call
25 -- @param               ...             Command to call
26 -- @return              Error code of the command
27 function call(...)
28         return os.execute(...) / 256
29 end
30
31 --- Execute a given shell command and capture its standard output
32 -- @class               function
33 -- @name                exec
34 -- @param command       Command to call
35 -- @return                      String containg the return the output of the command
36 exec = luci.util.exec
37
38 --- Retrieve information about currently mounted file systems.
39 -- @return      Table containing mount information
40 function mounts()
41         local data = {}
42         local k = {"fs", "blocks", "used", "available", "percent", "mountpoint"}
43         local ps = luci.util.execi("df")
44
45         if not ps then
46                 return
47         else
48                 ps()
49         end
50
51         for line in ps do
52                 local row = {}
53
54                 local j = 1
55                 for value in line:gmatch("[^%s]+") do
56                         row[k[j]] = value
57                         j = j + 1
58                 end
59
60                 if row[k[1]] then
61
62                         -- this is a rather ugly workaround to cope with wrapped lines in
63                         -- the df output:
64                         --
65                         --      /dev/scsi/host0/bus0/target0/lun0/part3
66                         --                   114382024  93566472  15005244  86% /mnt/usb
67                         --
68
69                         if not row[k[2]] then
70                                 j = 2
71                                 line = ps()
72                                 for value in line:gmatch("[^%s]+") do
73                                         row[k[j]] = value
74                                         j = j + 1
75                                 end
76                         end
77
78                         table.insert(data, row)
79                 end
80         end
81
82         return data
83 end
84
85 --- Retrieve environment variables. If no variable is given then a table
86 -- containing the whole environment is returned otherwise this function returns
87 -- the corresponding string value for the given name or nil if no such variable
88 -- exists.
89 -- @class               function
90 -- @name                getenv
91 -- @param var   Name of the environment variable to retrieve (optional)
92 -- @return              String containg the value of the specified variable
93 -- @return              Table containing all variables if no variable name is given
94 getenv = nixio.getenv
95
96 --- Get or set the current hostname.
97 -- @param               String containing a new hostname to set (optional)
98 -- @return              String containing the system hostname
99 function hostname(newname)
100         if type(newname) == "string" and #newname > 0 then
101                 fs.writefile( "/proc/sys/kernel/hostname", newname )
102                 return newname
103         else
104                 return nixio.uname().nodename
105         end
106 end
107
108 --- Returns the contents of a documented referred by an URL.
109 -- @param url    The URL to retrieve
110 -- @param stream Return a stream instead of a buffer
111 -- @param target Directly write to target file name
112 -- @return              String containing the contents of given the URL
113 function httpget(url, stream, target)
114         if not target then
115                 local source = stream and io.popen or luci.util.exec
116                 return source("wget -qO- '"..url:gsub("'", "").."'")
117         else
118                 return os.execute("wget -qO '%s' '%s'" %
119                         {target:gsub("'", ""), url:gsub("'", "")})
120         end
121 end
122
123 --- Initiate a system reboot.
124 -- @return      Return value of os.execute()
125 function reboot()
126         return os.execute("reboot >/dev/null 2>&1")
127 end
128
129 --- Retrieves the output of the "logread" command.
130 -- @return      String containing the current log buffer
131 function syslog()
132         return luci.util.exec("logread")
133 end
134
135 --- Retrieves the output of the "dmesg" command.
136 -- @return      String containing the current log buffer
137 function dmesg()
138         return luci.util.exec("dmesg")
139 end
140
141 --- Generates a random id with specified length.
142 -- @param bytes Number of bytes for the unique id
143 -- @return              String containing hex encoded id
144 function uniqueid(bytes)
145         local rand = fs.readfile("/dev/urandom", bytes)
146         return rand and nixio.bin.hexlify(rand)
147 end
148
149 --- Returns the current system uptime stats.
150 -- @return      String containing total uptime in seconds
151 function uptime()
152         return nixio.sysinfo().uptime
153 end
154
155
156 --- LuCI system utilities / network related functions.
157 -- @class       module
158 -- @name        luci.sys.net
159 net = {}
160
161 --- Returns the current arp-table entries as two-dimensional table.
162 -- @return      Table of table containing the current arp entries.
163 --                      The following fields are defined for arp entry objects:
164 --                      { "IP address", "HW address", "HW type", "Flags", "Mask", "Device" }
165 function net.arptable(callback)
166         local arp = (not callback) and {} or nil
167         local e, r, v
168         if fs.access("/proc/net/arp") then
169                 for e in io.lines("/proc/net/arp") do
170                         local r = { }, v
171                         for v in e:gmatch("%S+") do
172                                 r[#r+1] = v
173                         end
174
175                         if r[1] ~= "IP" then
176                                 local x = {
177                                         ["IP address"] = r[1],
178                                         ["HW type"]    = r[2],
179                                         ["Flags"]      = r[3],
180                                         ["HW address"] = r[4],
181                                         ["Mask"]       = r[5],
182                                         ["Device"]     = r[6]
183                                 }
184
185                                 if callback then
186                                         callback(x)
187                                 else
188                                         arp = arp or { }
189                                         arp[#arp+1] = x
190                                 end
191                         end
192                 end
193         end
194         return arp
195 end
196
197 local function _nethints(what, callback)
198         local _, k, e, mac, ip, name
199         local cur = uci.cursor()
200         local ifn = { }
201         local hosts = { }
202
203         local function _add(i, ...)
204                 local k = select(i, ...)
205                 if k then
206                         if not hosts[k] then hosts[k] = { } end
207                         hosts[k][1] = select(1, ...) or hosts[k][1]
208                         hosts[k][2] = select(2, ...) or hosts[k][2]
209                         hosts[k][3] = select(3, ...) or hosts[k][3]
210                         hosts[k][4] = select(4, ...) or hosts[k][4]
211                 end
212         end
213
214         if fs.access("/proc/net/arp") then
215                 for e in io.lines("/proc/net/arp") do
216                         ip, mac = e:match("^([%d%.]+)%s+%S+%s+%S+%s+([a-fA-F0-9:]+)%s+")
217                         if ip and mac then
218                                 _add(what, mac:upper(), ip, nil, nil)
219                         end
220                 end
221         end
222
223         if fs.access("/etc/ethers") then
224                 for e in io.lines("/etc/ethers") do
225                         mac, ip = e:match("^([a-f0-9]%S+) (%S+)")
226                         if mac and ip then
227                                 _add(what, mac:upper(), ip, nil, nil)
228                         end
229                 end
230         end
231
232         if fs.access("/var/dhcp.leases") then
233                 for e in io.lines("/var/dhcp.leases") do
234                         mac, ip, name = e:match("^%d+ (%S+) (%S+) (%S+)")
235                         if mac and ip then
236                                 _add(what, mac:upper(), ip, nil, name ~= "*" and name)
237                         end
238                 end
239         end
240
241         cur:foreach("dhcp", "host",
242                 function(s)
243                         for mac in luci.util.imatch(s.mac) do
244                                 _add(what, mac:upper(), s.ip, nil, s.name)
245                         end
246                 end)
247
248         for _, e in ipairs(nixio.getifaddrs()) do
249                 if e.name ~= "lo" then
250                         ifn[e.name] = ifn[e.name] or { }
251                         if e.family == "packet" and e.addr and #e.addr == 17 then
252                                 ifn[e.name][1] = e.addr:upper()
253                         elseif e.family == "inet" then
254                                 ifn[e.name][2] = e.addr
255                         elseif e.family == "inet6" then
256                                 ifn[e.name][3] = e.addr
257                         end
258                 end
259         end
260
261         for _, e in pairs(ifn) do
262                 if e[what] and (e[2] or e[3]) then
263                         _add(what, e[1], e[2], e[3], e[4])
264                 end
265         end
266
267         for _, e in luci.util.kspairs(hosts) do
268                 callback(e[1], e[2], e[3], e[4])
269         end
270 end
271
272 --- Returns a two-dimensional table of mac address hints.
273 -- @return  Table of table containing known hosts from various sources.
274 --          Each entry contains the values in the following order:
275 --          [ "mac", "name" ]
276 function net.mac_hints(callback)
277         if callback then
278                 _nethints(1, function(mac, v4, v6, name)
279                         name = name or nixio.getnameinfo(v4 or v6, nil, 100) or v4
280                         if name and name ~= mac then
281                                 callback(mac, name or nixio.getnameinfo(v4 or v6, nil, 100) or v4)
282                         end
283                 end)
284         else
285                 local rv = { }
286                 _nethints(1, function(mac, v4, v6, name)
287                         name = name or nixio.getnameinfo(v4 or v6, nil, 100) or v4
288                         if name and name ~= mac then
289                                 rv[#rv+1] = { mac, name or nixio.getnameinfo(v4 or v6, nil, 100) or v4 }
290                         end
291                 end)
292                 return rv
293         end
294 end
295
296 --- Returns a two-dimensional table of IPv4 address hints.
297 -- @return  Table of table containing known hosts from various sources.
298 --          Each entry contains the values in the following order:
299 --          [ "ip", "name" ]
300 function net.ipv4_hints(callback)
301         if callback then
302                 _nethints(2, function(mac, v4, v6, name)
303                         name = name or nixio.getnameinfo(v4, nil, 100) or mac
304                         if name and name ~= v4 then
305                                 callback(v4, name)
306                         end
307                 end)
308         else
309                 local rv = { }
310                 _nethints(2, function(mac, v4, v6, name)
311                         name = name or nixio.getnameinfo(v4, nil, 100) or mac
312                         if name and name ~= v4 then
313                                 rv[#rv+1] = { v4, name }
314                         end
315                 end)
316                 return rv
317         end
318 end
319
320 --- Returns a two-dimensional table of IPv6 address hints.
321 -- @return  Table of table containing known hosts from various sources.
322 --          Each entry contains the values in the following order:
323 --          [ "ip", "name" ]
324 function net.ipv6_hints(callback)
325         if callback then
326                 _nethints(3, function(mac, v4, v6, name)
327                         name = name or nixio.getnameinfo(v6, nil, 100) or mac
328                         if name and name ~= v6 then
329                                 callback(v6, name)
330                         end
331                 end)
332         else
333                 local rv = { }
334                 _nethints(3, function(mac, v4, v6, name)
335                         name = name or nixio.getnameinfo(v6, nil, 100) or mac
336                         if name and name ~= v6 then
337                                 rv[#rv+1] = { v6, name }
338                         end
339                 end)
340                 return rv
341         end
342 end
343
344 --- Returns conntrack information
345 -- @return      Table with the currently tracked IP connections
346 function net.conntrack(callback)
347         local connt = {}
348         if fs.access("/proc/net/nf_conntrack", "r") then
349                 for line in io.lines("/proc/net/nf_conntrack") do
350                         line = line:match "^(.-( [^ =]+=).-)%2"
351                         local entry, flags = _parse_mixed_record(line, " +")
352                         if flags[6] ~= "TIME_WAIT" then
353                                 entry.layer3 = flags[1]
354                                 entry.layer4 = flags[3]
355                                 for i=1, #entry do
356                                         entry[i] = nil
357                                 end
358
359                                 if callback then
360                                         callback(entry)
361                                 else
362                                         connt[#connt+1] = entry
363                                 end
364                         end
365                 end
366         elseif fs.access("/proc/net/ip_conntrack", "r") then
367                 for line in io.lines("/proc/net/ip_conntrack") do
368                         line = line:match "^(.-( [^ =]+=).-)%2"
369                         local entry, flags = _parse_mixed_record(line, " +")
370                         if flags[4] ~= "TIME_WAIT" then
371                                 entry.layer3 = "ipv4"
372                                 entry.layer4 = flags[1]
373                                 for i=1, #entry do
374                                         entry[i] = nil
375                                 end
376
377                                 if callback then
378                                         callback(entry)
379                                 else
380                                         connt[#connt+1] = entry
381                                 end
382                         end
383                 end
384         else
385                 return nil
386         end
387         return connt
388 end
389
390 --- Determine the current IPv4 default route. If multiple default routes exist,
391 -- return the one with the lowest metric.
392 -- @return      Table with the properties of the current default route.
393 --                      The following fields are defined:
394 --                      { "dest", "gateway", "metric", "refcount", "usecount", "irtt",
395 --                        "flags", "device" }
396 function net.defaultroute()
397         local route
398
399         net.routes(function(rt)
400                 if rt.dest:prefix() == 0 and (not route or route.metric > rt.metric) then
401                         route = rt
402                 end
403         end)
404
405         return route
406 end
407
408 --- Determine the current IPv6 default route. If multiple default routes exist,
409 -- return the one with the lowest metric.
410 -- @return      Table with the properties of the current default route.
411 --                      The following fields are defined:
412 --                      { "source", "dest", "nexthop", "metric", "refcount", "usecount",
413 --                        "flags", "device" }
414 function net.defaultroute6()
415         local route
416
417         net.routes6(function(rt)
418                 if rt.dest:prefix() == 0 and rt.device ~= "lo" and
419                    (not route or route.metric > rt.metric)
420                 then
421                         route = rt
422                 end
423         end)
424
425         if not route then
426                 local global_unicast = luci.ip.IPv6("2000::/3")
427                 net.routes6(function(rt)
428                         if rt.dest:equal(global_unicast) and
429                            (not route or route.metric > rt.metric)
430                         then
431                                 route = rt
432                         end
433                 end)
434         end
435
436         return route
437 end
438
439 --- Determine the names of available network interfaces.
440 -- @return      Table containing all current interface names
441 function net.devices()
442         local devs = {}
443         for k, v in ipairs(nixio.getifaddrs()) do
444                 if v.family == "packet" then
445                         devs[#devs+1] = v.name
446                 end
447         end
448         return devs
449 end
450
451
452 --- Return information about available network interfaces.
453 -- @return      Table containing all current interface names and their information
454 function net.deviceinfo()
455         local devs = {}
456         for k, v in ipairs(nixio.getifaddrs()) do
457                 if v.family == "packet" then
458                         local d = v.data
459                         d[1] = d.rx_bytes
460                         d[2] = d.rx_packets
461                         d[3] = d.rx_errors
462                         d[4] = d.rx_dropped
463                         d[5] = 0
464                         d[6] = 0
465                         d[7] = 0
466                         d[8] = d.multicast
467                         d[9] = d.tx_bytes
468                         d[10] = d.tx_packets
469                         d[11] = d.tx_errors
470                         d[12] = d.tx_dropped
471                         d[13] = 0
472                         d[14] = d.collisions
473                         d[15] = 0
474                         d[16] = 0
475                         devs[v.name] = d
476                 end
477         end
478         return devs
479 end
480
481
482 -- Determine the MAC address belonging to the given IP address.
483 -- @param ip    IPv4 address
484 -- @return              String containing the MAC address or nil if it cannot be found
485 function net.ip4mac(ip)
486         local mac = nil
487         net.arptable(function(e)
488                 if e["IP address"] == ip then
489                         mac = e["HW address"]
490                 end
491         end)
492         return mac
493 end
494
495 --- Returns the current kernel routing table entries.
496 -- @return      Table of tables with properties of the corresponding routes.
497 --                      The following fields are defined for route entry tables:
498 --                      { "dest", "gateway", "metric", "refcount", "usecount", "irtt",
499 --                        "flags", "device" }
500 function net.routes(callback)
501         local routes = { }
502
503         for line in io.lines("/proc/net/route") do
504
505                 local dev, dst_ip, gateway, flags, refcnt, usecnt, metric,
506                           dst_mask, mtu, win, irtt = line:match(
507                         "([^%s]+)\t([A-F0-9]+)\t([A-F0-9]+)\t([A-F0-9]+)\t" ..
508                         "(%d+)\t(%d+)\t(%d+)\t([A-F0-9]+)\t(%d+)\t(%d+)\t(%d+)"
509                 )
510
511                 if dev then
512                         gateway  = luci.ip.Hex( gateway,  32, luci.ip.FAMILY_INET4 )
513                         dst_mask = luci.ip.Hex( dst_mask, 32, luci.ip.FAMILY_INET4 )
514                         dst_ip   = luci.ip.Hex(
515                                 dst_ip, dst_mask:prefix(dst_mask), luci.ip.FAMILY_INET4
516                         )
517
518                         local rt = {
519                                 dest     = dst_ip,
520                                 gateway  = gateway,
521                                 metric   = tonumber(metric),
522                                 refcount = tonumber(refcnt),
523                                 usecount = tonumber(usecnt),
524                                 mtu      = tonumber(mtu),
525                                 window   = tonumber(window),
526                                 irtt     = tonumber(irtt),
527                                 flags    = tonumber(flags, 16),
528                                 device   = dev
529                         }
530
531                         if callback then
532                                 callback(rt)
533                         else
534                                 routes[#routes+1] = rt
535                         end
536                 end
537         end
538
539         return routes
540 end
541
542 --- Returns the current ipv6 kernel routing table entries.
543 -- @return      Table of tables with properties of the corresponding routes.
544 --                      The following fields are defined for route entry tables:
545 --                      { "source", "dest", "nexthop", "metric", "refcount", "usecount",
546 --                        "flags", "device" }
547 function net.routes6(callback)
548         if fs.access("/proc/net/ipv6_route", "r") then
549                 local routes = { }
550
551                 for line in io.lines("/proc/net/ipv6_route") do
552
553                         local dst_ip, dst_prefix, src_ip, src_prefix, nexthop,
554                                   metric, refcnt, usecnt, flags, dev = line:match(
555                                 "([a-f0-9]+) ([a-f0-9]+) " ..
556                                 "([a-f0-9]+) ([a-f0-9]+) " ..
557                                 "([a-f0-9]+) ([a-f0-9]+) " ..
558                                 "([a-f0-9]+) ([a-f0-9]+) " ..
559                                 "([a-f0-9]+) +([^%s]+)"
560                         )
561
562                         if dst_ip and dst_prefix and
563                            src_ip and src_prefix and
564                            nexthop and metric and
565                            refcnt and usecnt and
566                            flags and dev
567                         then
568                                 src_ip = luci.ip.Hex(
569                                         src_ip, tonumber(src_prefix, 16), luci.ip.FAMILY_INET6, false
570                                 )
571
572                                 dst_ip = luci.ip.Hex(
573                                         dst_ip, tonumber(dst_prefix, 16), luci.ip.FAMILY_INET6, false
574                                 )
575
576                                 nexthop = luci.ip.Hex( nexthop, 128, luci.ip.FAMILY_INET6, false )
577
578                                 local rt = {
579                                         source   = src_ip,
580                                         dest     = dst_ip,
581                                         nexthop  = nexthop,
582                                         metric   = tonumber(metric, 16),
583                                         refcount = tonumber(refcnt, 16),
584                                         usecount = tonumber(usecnt, 16),
585                                         flags    = tonumber(flags, 16),
586                                         device   = dev,
587
588                                         -- lua number is too small for storing the metric
589                                         -- add a metric_raw field with the original content
590                                         metric_raw = metric
591                                 }
592
593                                 if callback then
594                                         callback(rt)
595                                 else
596                                         routes[#routes+1] = rt
597                                 end
598                         end
599                 end
600
601                 return routes
602         end
603 end
604
605 --- Tests whether the given host responds to ping probes.
606 -- @param host  String containing a hostname or IPv4 address
607 -- @return              Number containing 0 on success and >= 1 on error
608 function net.pingtest(host)
609         return os.execute("ping -c1 '"..host:gsub("'", '').."' >/dev/null 2>&1")
610 end
611
612
613 --- LuCI system utilities / process related functions.
614 -- @class       module
615 -- @name        luci.sys.process
616 process = {}
617
618 --- Get the current process id.
619 -- @class function
620 -- @name  process.info
621 -- @return      Number containing the current pid
622 function process.info(key)
623         local s = {uid = nixio.getuid(), gid = nixio.getgid()}
624         return not key and s or s[key]
625 end
626
627 --- Retrieve information about currently running processes.
628 -- @return      Table containing process information
629 function process.list()
630         local data = {}
631         local k
632         local ps = luci.util.execi("/bin/busybox top -bn1")
633
634         if not ps then
635                 return
636         end
637
638         for line in ps do
639                 local pid, ppid, user, stat, vsz, mem, cpu, cmd = line:match(
640                         "^ *(%d+) +(%d+) +(%S.-%S) +([RSDZTW][W ][<N ]) +(%d+) +(%d+%%) +(%d+%%) +(.+)"
641                 )
642
643                 local idx = tonumber(pid)
644                 if idx then
645                         data[idx] = {
646                                 ['PID']     = pid,
647                                 ['PPID']    = ppid,
648                                 ['USER']    = user,
649                                 ['STAT']    = stat,
650                                 ['VSZ']     = vsz,
651                                 ['%MEM']    = mem,
652                                 ['%CPU']    = cpu,
653                                 ['COMMAND'] = cmd
654                         }
655                 end
656         end
657
658         return data
659 end
660
661 --- Set the gid of a process identified by given pid.
662 -- @param gid   Number containing the Unix group id
663 -- @return              Boolean indicating successful operation
664 -- @return              String containing the error message if failed
665 -- @return              Number containing the error code if failed
666 function process.setgroup(gid)
667         return nixio.setgid(gid)
668 end
669
670 --- Set the uid of a process identified by given pid.
671 -- @param uid   Number containing the Unix user id
672 -- @return              Boolean indicating successful operation
673 -- @return              String containing the error message if failed
674 -- @return              Number containing the error code if failed
675 function process.setuser(uid)
676         return nixio.setuid(uid)
677 end
678
679 --- Send a signal to a process identified by given pid.
680 -- @class function
681 -- @name  process.signal
682 -- @param pid   Number containing the process id
683 -- @param sig   Signal to send (default: 15 [SIGTERM])
684 -- @return              Boolean indicating successful operation
685 -- @return              Number containing the error code if failed
686 process.signal = nixio.kill
687
688
689 --- LuCI system utilities / user related functions.
690 -- @class       module
691 -- @name        luci.sys.user
692 user = {}
693
694 --- Retrieve user informations for given uid.
695 -- @class               function
696 -- @name                getuser
697 -- @param uid   Number containing the Unix user id
698 -- @return              Table containing the following fields:
699 --                              { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" }
700 user.getuser = nixio.getpw
701
702 --- Retrieve the current user password hash.
703 -- @param username      String containing the username to retrieve the password for
704 -- @return                      String containing the hash or nil if no password is set.
705 -- @return                      Password database entry
706 function user.getpasswd(username)
707         local pwe = nixio.getsp and nixio.getsp(username) or nixio.getpw(username)
708         local pwh = pwe and (pwe.pwdp or pwe.passwd)
709         if not pwh or #pwh < 1 or pwh == "!" or pwh == "x" then
710                 return nil, pwe
711         else
712                 return pwh, pwe
713         end
714 end
715
716 --- Test whether given string matches the password of a given system user.
717 -- @param username      String containing the Unix user name
718 -- @param pass          String containing the password to compare
719 -- @return                      Boolean indicating wheather the passwords are equal
720 function user.checkpasswd(username, pass)
721         local pwh, pwe = user.getpasswd(username)
722         if pwe then
723                 return (pwh == nil or nixio.crypt(pass, pwh) == pwh)
724         end
725         return false
726 end
727
728 --- Change the password of given user.
729 -- @param username      String containing the Unix user name
730 -- @param password      String containing the password to compare
731 -- @return                      Number containing 0 on success and >= 1 on error
732 function user.setpasswd(username, password)
733         if password then
734                 password = password:gsub("'", [['"'"']])
735         end
736
737         if username then
738                 username = username:gsub("'", [['"'"']])
739         end
740
741         return os.execute(
742                 "(echo '" .. password .. "'; sleep 1; echo '" .. password .. "') | " ..
743                 "passwd '" .. username .. "' >/dev/null 2>&1"
744         )
745 end
746
747
748 --- LuCI system utilities / wifi related functions.
749 -- @class       module
750 -- @name        luci.sys.wifi
751 wifi = {}
752
753 --- Get wireless information for given interface.
754 -- @param ifname        String containing the interface name
755 -- @return              A wrapped iwinfo object instance
756 function wifi.getiwinfo(ifname)
757         local stat, iwinfo = pcall(require, "iwinfo")
758
759         if ifname then
760                 local c = 0
761                 local u = uci.cursor_state()
762                 local d, n = ifname:match("^(%w+)%.network(%d+)")
763                 if d and n then
764                         ifname = d
765                         n = tonumber(n)
766                         u:foreach("wireless", "wifi-iface",
767                                 function(s)
768                                         if s.device == d then
769                                                 c = c + 1
770                                                 if c == n then
771                                                         ifname = s.ifname or s.device
772                                                         return false
773                                                 end
774                                         end
775                                 end)
776                 elseif u:get("wireless", ifname) == "wifi-device" then
777                         u:foreach("wireless", "wifi-iface",
778                                 function(s)
779                                         if s.device == ifname and s.ifname then
780                                                 ifname = s.ifname
781                                                 return false
782                                         end
783                                 end)
784                 end
785
786                 local t = stat and iwinfo.type(ifname)
787                 local x = t and iwinfo[t] or { }
788                 return setmetatable({}, {
789                         __index = function(t, k)
790                                 if k == "ifname" then
791                                         return ifname
792                                 elseif x[k] then
793                                         return x[k](ifname)
794                                 end
795                         end
796                 })
797         end
798 end
799
800
801 --- LuCI system utilities / init related functions.
802 -- @class       module
803 -- @name        luci.sys.init
804 init = {}
805 init.dir = "/etc/init.d/"
806
807 --- Get the names of all installed init scripts
808 -- @return      Table containing the names of all inistalled init scripts
809 function init.names()
810         local names = { }
811         for name in fs.glob(init.dir.."*") do
812                 names[#names+1] = fs.basename(name)
813         end
814         return names
815 end
816
817 --- Get the index of he given init script
818 -- @param name  Name of the init script
819 -- @return              Numeric index value
820 function init.index(name)
821         if fs.access(init.dir..name) then
822                 return call("env -i sh -c 'source %s%s enabled; exit ${START:-255}' >/dev/null"
823                         %{ init.dir, name })
824         end
825 end
826
827 local function init_action(action, name)
828         if fs.access(init.dir..name) then
829                 return call("env -i %s%s %s >/dev/null" %{ init.dir, name, action })
830         end
831 end
832
833 --- Test whether the given init script is enabled
834 -- @param name  Name of the init script
835 -- @return              Boolean indicating whether init is enabled
836 function init.enabled(name)
837         return (init_action("enabled", name) == 0)
838 end
839
840 --- Enable the given init script
841 -- @param name  Name of the init script
842 -- @return              Boolean indicating success
843 function init.enable(name)
844         return (init_action("enable", name) == 1)
845 end
846
847 --- Disable the given init script
848 -- @param name  Name of the init script
849 -- @return              Boolean indicating success
850 function init.disable(name)
851         return (init_action("disable", name) == 0)
852 end
853
854 --- Start the given init script
855 -- @param name  Name of the init script
856 -- @return              Boolean indicating success
857 function init.start(name)
858         return (init_action("start", name) == 0)
859 end
860
861 --- Stop the given init script
862 -- @param name  Name of the init script
863 -- @return              Boolean indicating success
864 function init.stop(name)
865         return (init_action("stop", name) == 0)
866 end
867
868
869 -- Internal functions
870
871 function _parse_mixed_record(cnt, delimiter)
872         delimiter = delimiter or "  "
873         local data = {}
874         local flags = {}
875
876         for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n")) do
877                 for j, f in pairs(luci.util.split(luci.util.trim(l), delimiter, nil, true)) do
878                         local k, x, v = f:match('([^%s][^:=]*) *([:=]*) *"*([^\n"]*)"*')
879
880                         if k then
881                                 if x == "" then
882                                         table.insert(flags, k)
883                                 else
884                                         data[k] = v
885                                 end
886                         end
887                 end
888         end
889
890         return data, flags
891 end