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