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