cc29fa37bba5d3319c849c2e0b2ecb1a25ecfae2
[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                 cpuinfo:match("machine\t+: ([^\n]+)") or
187                 cpuinfo:match("Hardware\t+: ([^\n]+)") or
188                 luci.util.pcdata(fs.readfile("/proc/diag/model")) or
189                 nixio.uname().machine or
190                 system
191
192         return system, model, memtotal, memcached, membuffers, memfree, bogomips
193 end
194
195 --- Retrieves the output of the "logread" command.
196 -- @return      String containing the current log buffer
197 function syslog()
198         return luci.util.exec("logread")
199 end
200
201 --- Retrieves the output of the "dmesg" command.
202 -- @return      String containing the current log buffer
203 function dmesg()
204         return luci.util.exec("dmesg")
205 end
206
207 --- Generates a random id with specified length.
208 -- @param bytes Number of bytes for the unique id
209 -- @return              String containing hex encoded id
210 function uniqueid(bytes)
211         local rand = fs.readfile("/dev/urandom", bytes)
212         return rand and nixio.bin.hexlify(rand)
213 end
214
215 --- Returns the current system uptime stats.
216 -- @return      String containing total uptime in seconds
217 function uptime()
218         return nixio.sysinfo().uptime
219 end
220
221
222 --- LuCI system utilities / network related functions.
223 -- @class       module
224 -- @name        luci.sys.net
225 net = {}
226
227 --- Returns the current arp-table entries as two-dimensional table.
228 -- @return      Table of table containing the current arp entries.
229 --                      The following fields are defined for arp entry objects:
230 --                      { "IP address", "HW address", "HW type", "Flags", "Mask", "Device" }
231 function net.arptable(callback)
232         return _parse_delimited_table(io.lines("/proc/net/arp"), "%s%s+", callback)
233 end
234
235 --- Returns conntrack information
236 -- @return      Table with the currently tracked IP connections
237 function net.conntrack(callback)
238         local connt = {}
239         if fs.access("/proc/net/nf_conntrack", "r") then
240                 for line in io.lines("/proc/net/nf_conntrack") do
241                         line = line:match "^(.-( [^ =]+=).-)%2"
242                         local entry, flags = _parse_mixed_record(line, " +")
243                         entry.layer3 = flags[1]
244                         entry.layer4 = flags[3]
245                         for i=1, #entry do
246                                 entry[i] = nil
247                         end
248
249                         if callback then
250                                 callback(entry)
251                         else
252                                 connt[#connt+1] = entry
253                         end
254                 end
255         elseif fs.access("/proc/net/ip_conntrack", "r") then
256                 for line in io.lines("/proc/net/ip_conntrack") do
257                         line = line:match "^(.-( [^ =]+=).-)%2"
258                         local entry, flags = _parse_mixed_record(line, " +")
259                         entry.layer3 = "ipv4"
260                         entry.layer4 = flags[1]
261                         for i=1, #entry do
262                                 entry[i] = nil
263                         end
264
265                         if callback then
266                                 callback(entry)
267                         else
268                                 connt[#connt+1] = entry
269                         end
270                 end
271         else
272                 return nil
273         end
274         return connt
275 end
276
277 --- Determine the current IPv4 default route. If multiple default routes exist,
278 -- return the one with the lowest metric.
279 -- @return      Table with the properties of the current default route.
280 --                      The following fields are defined:
281 --                      { "dest", "gateway", "metric", "refcount", "usecount", "irtt",
282 --                        "flags", "device" }
283 function net.defaultroute()
284         local route
285
286         net.routes(function(rt)
287                 if rt.dest:prefix() == 0 and (not route or route.metric > rt.metric) then
288                         route = rt
289                 end
290         end)
291
292         return route
293 end
294
295 --- Determine the current IPv6 default route. If multiple default routes exist,
296 -- return the one with the lowest metric.
297 -- @return      Table with the properties of the current default route.
298 --                      The following fields are defined:
299 --                      { "source", "dest", "nexthop", "metric", "refcount", "usecount",
300 --                        "flags", "device" }
301 function net.defaultroute6()
302         local route
303
304         net.routes6(function(rt)
305                 if rt.dest:prefix() == 0 and rt.device ~= "lo" and 
306                    (not route or route.metric > rt.metric)
307                 then
308                         route = rt
309                 end
310         end)
311
312         if not route then
313                 local global_unicast = luci.ip.IPv6("2000::/3")
314                 net.routes6(function(rt)
315                         if rt.dest:equal(global_unicast) and
316                            (not route or route.metric > rt.metric)
317                         then
318                                 route = rt
319                         end
320                 end)
321         end
322
323         return route
324 end
325
326 --- Determine the names of available network interfaces.
327 -- @return      Table containing all current interface names
328 function net.devices()
329         local devs = {}
330         for k, v in ipairs(nixio.getifaddrs()) do
331                 if v.family == "packet" then
332                         devs[#devs+1] = v.name
333                 end
334         end
335         return devs
336 end
337
338
339 --- Return information about available network interfaces.
340 -- @return      Table containing all current interface names and their information
341 function net.deviceinfo()
342         local devs = {}
343         for k, v in ipairs(nixio.getifaddrs()) do
344                 if v.family == "packet" then
345                         local d = v.data
346                         d[1] = d.rx_bytes
347                         d[2] = d.rx_packets
348                         d[3] = d.rx_errors
349                         d[4] = d.rx_dropped
350                         d[5] = 0
351                         d[6] = 0
352                         d[7] = 0
353                         d[8] = d.multicast
354                         d[9] = d.tx_bytes
355                         d[10] = d.tx_packets
356                         d[11] = d.tx_errors
357                         d[12] = d.tx_dropped
358                         d[13] = 0
359                         d[14] = d.collisions
360                         d[15] = 0
361                         d[16] = 0
362                         devs[v.name] = d
363                 end
364         end
365         return devs
366 end
367
368
369 -- Determine the MAC address belonging to the given IP address.
370 -- @param ip    IPv4 address
371 -- @return              String containing the MAC address or nil if it cannot be found
372 function net.ip4mac(ip)
373         local mac = nil
374         net.arptable(function(e)
375                 if e["IP address"] == ip then
376                         mac = e["HW address"]
377                 end
378         end)
379         return mac
380 end
381
382 --- Returns the current kernel routing table entries.
383 -- @return      Table of tables with properties of the corresponding routes.
384 --                      The following fields are defined for route entry tables:
385 --                      { "dest", "gateway", "metric", "refcount", "usecount", "irtt",
386 --                        "flags", "device" }
387 function net.routes(callback)
388         local routes = { }
389
390         for line in io.lines("/proc/net/route") do
391
392                 local dev, dst_ip, gateway, flags, refcnt, usecnt, metric,
393                           dst_mask, mtu, win, irtt = line:match(
394                         "([^%s]+)\t([A-F0-9]+)\t([A-F0-9]+)\t([A-F0-9]+)\t" ..
395                         "(%d+)\t(%d+)\t(%d+)\t([A-F0-9]+)\t(%d+)\t(%d+)\t(%d+)"
396                 )
397
398                 if dev then
399                         gateway  = luci.ip.Hex( gateway,  32, luci.ip.FAMILY_INET4 )
400                         dst_mask = luci.ip.Hex( dst_mask, 32, luci.ip.FAMILY_INET4 )
401                         dst_ip   = luci.ip.Hex(
402                                 dst_ip, dst_mask:prefix(dst_mask), luci.ip.FAMILY_INET4
403                         )
404
405                         local rt = {
406                                 dest     = dst_ip,
407                                 gateway  = gateway,
408                                 metric   = tonumber(metric),
409                                 refcount = tonumber(refcnt),
410                                 usecount = tonumber(usecnt),
411                                 mtu      = tonumber(mtu),
412                                 window   = tonumber(window),
413                                 irtt     = tonumber(irtt),
414                                 flags    = tonumber(flags, 16),
415                                 device   = dev
416                         }
417
418                         if callback then
419                                 callback(rt)
420                         else
421                                 routes[#routes+1] = rt
422                         end
423                 end
424         end
425
426         return routes
427 end
428
429 --- Returns the current ipv6 kernel routing table entries.
430 -- @return      Table of tables with properties of the corresponding routes.
431 --                      The following fields are defined for route entry tables:
432 --                      { "source", "dest", "nexthop", "metric", "refcount", "usecount",
433 --                        "flags", "device" }
434 function net.routes6(callback)
435         if fs.access("/proc/net/ipv6_route", "r") then
436                 local routes = { }
437
438                 for line in io.lines("/proc/net/ipv6_route") do
439
440                         local dst_ip, dst_prefix, src_ip, src_prefix, nexthop,
441                                   metric, refcnt, usecnt, flags, dev = line:match(
442                                 "([a-f0-9]+) ([a-f0-9]+) " ..
443                                 "([a-f0-9]+) ([a-f0-9]+) " ..
444                                 "([a-f0-9]+) ([a-f0-9]+) " ..
445                                 "([a-f0-9]+) ([a-f0-9]+) " ..
446                                 "([a-f0-9]+) +([^%s]+)"
447                         )
448
449                         src_ip = luci.ip.Hex(
450                                 src_ip, tonumber(src_prefix, 16), luci.ip.FAMILY_INET6, false
451                         )
452
453                         dst_ip = luci.ip.Hex(
454                                 dst_ip, tonumber(dst_prefix, 16), luci.ip.FAMILY_INET6, false
455                         )
456
457                         nexthop = luci.ip.Hex( nexthop, 128, luci.ip.FAMILY_INET6, false )
458
459                         local rt = {
460                                 source   = src_ip,
461                                 dest     = dst_ip,
462                                 nexthop  = nexthop,
463                                 metric   = tonumber(metric, 16),
464                                 refcount = tonumber(refcnt, 16),
465                                 usecount = tonumber(usecnt, 16),
466                                 flags    = tonumber(flags, 16),
467                                 device   = dev,
468
469                                 -- lua number is too small for storing the metric
470                                 -- add a metric_raw field with the original content
471                                 metric_raw = metric
472                         }
473
474                         if callback then
475                                 callback(rt)
476                         else
477                                 routes[#routes+1] = rt
478                         end
479                 end
480
481                 return routes
482         end
483 end
484
485 --- Tests whether the given host responds to ping probes.
486 -- @param host  String containing a hostname or IPv4 address
487 -- @return              Number containing 0 on success and >= 1 on error
488 function net.pingtest(host)
489         return os.execute("ping -c1 '"..host:gsub("'", '').."' >/dev/null 2>&1")
490 end
491
492
493 --- LuCI system utilities / process related functions.
494 -- @class       module
495 -- @name        luci.sys.process
496 process = {}
497
498 --- Get the current process id.
499 -- @class function
500 -- @name  process.info
501 -- @return      Number containing the current pid
502 function process.info(key)
503         local s = {uid = nixio.getuid(), gid = nixio.getgid()}
504         return not key and s or s[key]
505 end
506
507 --- Retrieve information about currently running processes.
508 -- @return      Table containing process information
509 function process.list()
510         local data = {}
511         local k
512         local ps = luci.util.execi("top -bn1")
513
514         if not ps then
515                 return
516         end
517
518         while true do
519                 local line = ps()
520                 if not line then
521                         return
522                 end
523
524                 k = luci.util.split(luci.util.trim(line), "%s+", nil, true)
525                 if k[1] == "PID" then
526                         break
527                 end
528         end
529
530         for line in ps do
531                 local row = {}
532
533                 line = luci.util.trim(line)
534                 for i, value in ipairs(luci.util.split(line, "%s+", #k-1, true)) do
535                         row[k[i]] = value
536                 end
537
538                 local pid = tonumber(row[k[1]])
539                 if pid then
540                         data[pid] = row
541                 end
542         end
543
544         return data
545 end
546
547 --- Set the gid of a process identified by given pid.
548 -- @param gid   Number containing the Unix group id
549 -- @return              Boolean indicating successful operation
550 -- @return              String containing the error message if failed
551 -- @return              Number containing the error code if failed
552 function process.setgroup(gid)
553         return nixio.setgid(gid)
554 end
555
556 --- Set the uid of a process identified by given pid.
557 -- @param uid   Number containing the Unix user id
558 -- @return              Boolean indicating successful operation
559 -- @return              String containing the error message if failed
560 -- @return              Number containing the error code if failed
561 function process.setuser(uid)
562         return nixio.setuid(uid)
563 end
564
565 --- Send a signal to a process identified by given pid.
566 -- @class function
567 -- @name  process.signal
568 -- @param pid   Number containing the process id
569 -- @param sig   Signal to send (default: 15 [SIGTERM])
570 -- @return              Boolean indicating successful operation
571 -- @return              Number containing the error code if failed
572 process.signal = nixio.kill
573
574
575 --- LuCI system utilities / user related functions.
576 -- @class       module
577 -- @name        luci.sys.user
578 user = {}
579
580 --- Retrieve user informations for given uid.
581 -- @class               function
582 -- @name                getuser
583 -- @param uid   Number containing the Unix user id
584 -- @return              Table containing the following fields:
585 --                              { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" }
586 user.getuser = nixio.getpw
587
588 --- Retrieve the current user password hash.
589 -- @param username      String containing the username to retrieve the password for
590 -- @return                      String containing the hash or nil if no password is set.
591 function user.getpasswd(username)
592         local pwe = nixio.getsp and nixio.getsp(username) or nixio.getpw(username)
593         local pwh = pwe and (pwe.pwdp or pwe.passwd)
594         if not pwh or #pwh < 1 or pwh == "!" or pwh == "x" then
595                 return nil
596         else
597                 return pwh
598         end
599 end
600
601 --- Test whether given string matches the password of a given system user.
602 -- @param username      String containing the Unix user name
603 -- @param pass          String containing the password to compare
604 -- @return                      Boolean indicating wheather the passwords are equal
605 function user.checkpasswd(username, pass)
606         local pwh = user.getpasswd(username)
607         if pwh and nixio.crypt(pass, pwh) ~= pwh then
608                 return false
609         else
610                 return true
611         end
612 end
613
614 --- Change the password of given user.
615 -- @param username      String containing the Unix user name
616 -- @param password      String containing the password to compare
617 -- @return                      Number containing 0 on success and >= 1 on error
618 function user.setpasswd(username, password)
619         if password then
620                 password = password:gsub("'", [['"'"']])
621         end
622
623         if username then
624                 username = username:gsub("'", [['"'"']])
625         end
626
627         return os.execute(
628                 "(echo '" .. password .. "'; sleep 1; echo '" .. password .. "') | " ..
629                 "passwd '" .. username .. "' >/dev/null 2>&1"
630         )
631 end
632
633
634 --- LuCI system utilities / wifi related functions.
635 -- @class       module
636 -- @name        luci.sys.wifi
637 wifi = {}
638
639 --- Get wireless information for given interface.
640 -- @param ifname        String containing the interface name
641 -- @return              A wrapped iwinfo object instance
642 function wifi.getiwinfo(ifname)
643         local stat, iwinfo = pcall(require, "iwinfo")
644
645         if ifname then
646                 local c = 0
647                 local u = uci.cursor_state()
648                 local d, n = ifname:match("^(%w+)%.network(%d+)")
649                 if d and n then
650                         n = tonumber(n)
651                         u:foreach("wireless", "wifi-iface",
652                                 function(s)
653                                         if s.device == d then
654                                                 c = c + 1
655                                                 if c == n then
656                                                         ifname = s.ifname or s.device
657                                                         return false
658                                                 end
659                                         end
660                                 end)
661                 elseif u:get("wireless", ifname) == "wifi-device" then
662                         u:foreach("wireless", "wifi-iface",
663                                 function(s)
664                                         if s.device == ifname and s.ifname then
665                                                 ifname = s.ifname
666                                                 return false
667                                         end
668                                 end)
669                 end
670
671                 local t = stat and iwinfo.type(ifname)
672                 local x = t and iwinfo[t] or { }
673                 return setmetatable({}, {
674                         __index = function(t, k)
675                                 if k == "ifname" then
676                                         return ifname
677                                 elseif x[k] then
678                                         return x[k](ifname)
679                                 end
680                         end
681                 })
682         end
683 end
684
685 --- Get iwconfig output for all wireless devices.
686 -- @return      Table of tables containing the iwconfing output for each wifi device
687 function wifi.getiwconfig()
688         local cnt = luci.util.exec("PATH=/sbin:/usr/sbin iwconfig 2>/dev/null")
689         local iwc = {}
690
691         for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
692                 local k = l:match("^(.-) ")
693                 l = l:gsub("^(.-) +", "", 1)
694                 if k then
695                         local entry, flags = _parse_mixed_record(l)
696                         if entry then
697                                 entry.flags = flags
698                         end
699                         iwc[k] = entry
700                 end
701         end
702
703         return iwc
704 end
705
706 --- Get iwlist scan output from all wireless devices.
707 -- @return      Table of tables contaiing all scan results
708 function wifi.iwscan(iface)
709         local siface = iface or ""
710         local cnt = luci.util.exec("iwlist "..siface.." scan 2>/dev/null")
711         local iws = {}
712
713         for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
714                 local k = l:match("^(.-) ")
715                 l = l:gsub("^[^\n]+", "", 1)
716                 l = luci.util.trim(l)
717                 if k then
718                         iws[k] = {}
719                         for j, c in pairs(luci.util.split(l, "\n          Cell")) do
720                                 c = c:gsub("^(.-)- ", "", 1)
721                                 c = luci.util.split(c, "\n", 7)
722                                 c = table.concat(c, "\n", 1)
723                                 local entry, flags = _parse_mixed_record(c)
724                                 if entry then
725                                         entry.flags = flags
726                                 end
727                                 table.insert(iws[k], entry)
728                         end
729                 end
730         end
731
732         return iface and (iws[iface] or {}) or iws
733 end
734
735 --- Get available channels from given wireless iface.
736 -- @param iface Wireless interface (optional)
737 -- @return              Table of available channels
738 function wifi.channels(iface)
739         local stat, iwinfo = pcall(require, "iwinfo")
740         local cns
741
742         if stat then
743                 local t = iwinfo.type(iface or "")
744                 if iface and t and iwinfo[t] then
745                         cns = iwinfo[t].freqlist(iface)
746                 end
747         end
748
749         if not cns or #cns == 0 then
750                 cns = {
751                         {channel =  1, mhz = 2412},
752                         {channel =  2, mhz = 2417},
753                         {channel =  3, mhz = 2422},
754                         {channel =  4, mhz = 2427},
755                         {channel =  5, mhz = 2432},
756                         {channel =  6, mhz = 2437},
757                         {channel =  7, mhz = 2442},
758                         {channel =  8, mhz = 2447},
759                         {channel =  9, mhz = 2452},
760                         {channel = 10, mhz = 2457},
761                         {channel = 11, mhz = 2462}
762                 }
763         end
764
765         return cns
766 end
767
768
769 --- LuCI system utilities / init related functions.
770 -- @class       module
771 -- @name        luci.sys.init
772 init = {}
773 init.dir = "/etc/init.d/"
774
775 --- Get the names of all installed init scripts
776 -- @return      Table containing the names of all inistalled init scripts
777 function init.names()
778         local names = { }
779         for name in fs.glob(init.dir.."*") do
780                 names[#names+1] = fs.basename(name)
781         end
782         return names
783 end
784
785 --- Test whether the given init script is enabled
786 -- @param name  Name of the init script
787 -- @return              Boolean indicating whether init is enabled
788 function init.enabled(name)
789         if fs.access(init.dir..name) then
790                 return ( call(init.dir..name.." enabled") == 0 )
791         end
792         return false
793 end
794
795 --- Get the index of he given init script
796 -- @param name  Name of the init script
797 -- @return              Numeric index value
798 function init.index(name)
799         if fs.access(init.dir..name) then
800                 return call("source "..init.dir..name.." enabled; exit $START")
801         end
802 end
803
804 --- Enable the given init script
805 -- @param name  Name of the init script
806 -- @return              Boolean indicating success
807 function init.enable(name)
808         if fs.access(init.dir..name) then
809                 return ( call(init.dir..name.." enable") == 1 )
810         end
811 end
812
813 --- Disable the given init script
814 -- @param name  Name of the init script
815 -- @return              Boolean indicating success
816 function init.disable(name)
817         if fs.access(init.dir..name) then
818                 return ( call(init.dir..name.." disable") == 0 )
819         end
820 end
821
822
823 -- Internal functions
824
825 function _parse_delimited_table(iter, delimiter, callback)
826         delimiter = delimiter or "%s+"
827
828         local data  = {}
829         local trim  = luci.util.trim
830         local split = luci.util.split
831
832         local keys = split(trim(iter()), delimiter, nil, true)
833         for i, j in pairs(keys) do
834                 keys[i] = trim(keys[i])
835         end
836
837         for line in iter do
838                 local row = {}
839                 line = trim(line)
840                 if #line > 0 then
841                         for i, j in pairs(split(line, delimiter, nil, true)) do
842                                 if keys[i] then
843                                         row[keys[i]] = j
844                                 end
845                         end
846                 end
847
848                 if callback then
849                         callback(row)
850                 else
851                         data[#data+1] = row
852                 end
853         end
854
855         return data
856 end
857
858 function _parse_mixed_record(cnt, delimiter)
859         delimiter = delimiter or "  "
860         local data = {}
861         local flags = {}
862
863         for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n")) do
864                 for j, f in pairs(luci.util.split(luci.util.trim(l), delimiter, nil, true)) do
865                         local k, x, v = f:match('([^%s][^:=]*) *([:=]*) *"*([^\n"]*)"*')
866
867                         if k then
868                                 if x == "" then
869                                         table.insert(flags, k)
870                                 else
871                                         data[k] = v
872                                 end
873                         end
874                 end
875         end
876
877         return data, flags
878 end