libs/sys: ignore TIME_WAIT connections in luci.sys.net.conntrack()
[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[1] == "PID" then
530                         break
531                 end
532         end
533
534         for line in ps do
535                 local row = {}
536
537                 line = luci.util.trim(line)
538                 for i, value in ipairs(luci.util.split(line, "%s+", #k-1, true)) do
539                         row[k[i]] = value
540                 end
541
542                 local pid = tonumber(row[k[1]])
543                 if pid then
544                         data[pid] = row
545                 end
546         end
547
548         return data
549 end
550
551 --- Set the gid of a process identified by given pid.
552 -- @param gid   Number containing the Unix group id
553 -- @return              Boolean indicating successful operation
554 -- @return              String containing the error message if failed
555 -- @return              Number containing the error code if failed
556 function process.setgroup(gid)
557         return nixio.setgid(gid)
558 end
559
560 --- Set the uid of a process identified by given pid.
561 -- @param uid   Number containing the Unix user id
562 -- @return              Boolean indicating successful operation
563 -- @return              String containing the error message if failed
564 -- @return              Number containing the error code if failed
565 function process.setuser(uid)
566         return nixio.setuid(uid)
567 end
568
569 --- Send a signal to a process identified by given pid.
570 -- @class function
571 -- @name  process.signal
572 -- @param pid   Number containing the process id
573 -- @param sig   Signal to send (default: 15 [SIGTERM])
574 -- @return              Boolean indicating successful operation
575 -- @return              Number containing the error code if failed
576 process.signal = nixio.kill
577
578
579 --- LuCI system utilities / user related functions.
580 -- @class       module
581 -- @name        luci.sys.user
582 user = {}
583
584 --- Retrieve user informations for given uid.
585 -- @class               function
586 -- @name                getuser
587 -- @param uid   Number containing the Unix user id
588 -- @return              Table containing the following fields:
589 --                              { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" }
590 user.getuser = nixio.getpw
591
592 --- Retrieve the current user password hash.
593 -- @param username      String containing the username to retrieve the password for
594 -- @return                      String containing the hash or nil if no password is set.
595 function user.getpasswd(username)
596         local pwe = nixio.getsp and nixio.getsp(username) or nixio.getpw(username)
597         local pwh = pwe and (pwe.pwdp or pwe.passwd)
598         if not pwh or #pwh < 1 or pwh == "!" or pwh == "x" then
599                 return nil
600         else
601                 return pwh
602         end
603 end
604
605 --- Test whether given string matches the password of a given system user.
606 -- @param username      String containing the Unix user name
607 -- @param pass          String containing the password to compare
608 -- @return                      Boolean indicating wheather the passwords are equal
609 function user.checkpasswd(username, pass)
610         local pwh = user.getpasswd(username)
611         if pwh and nixio.crypt(pass, pwh) ~= pwh then
612                 return false
613         else
614                 return true
615         end
616 end
617
618 --- Change the password of given user.
619 -- @param username      String containing the Unix user name
620 -- @param password      String containing the password to compare
621 -- @return                      Number containing 0 on success and >= 1 on error
622 function user.setpasswd(username, password)
623         if password then
624                 password = password:gsub("'", [['"'"']])
625         end
626
627         if username then
628                 username = username:gsub("'", [['"'"']])
629         end
630
631         return os.execute(
632                 "(echo '" .. password .. "'; sleep 1; echo '" .. password .. "') | " ..
633                 "passwd '" .. username .. "' >/dev/null 2>&1"
634         )
635 end
636
637
638 --- LuCI system utilities / wifi related functions.
639 -- @class       module
640 -- @name        luci.sys.wifi
641 wifi = {}
642
643 --- Get wireless information for given interface.
644 -- @param ifname        String containing the interface name
645 -- @return              A wrapped iwinfo object instance
646 function wifi.getiwinfo(ifname)
647         local stat, iwinfo = pcall(require, "iwinfo")
648
649         if ifname then
650                 local c = 0
651                 local u = uci.cursor_state()
652                 local d, n = ifname:match("^(%w+)%.network(%d+)")
653                 if d and n then
654                         n = tonumber(n)
655                         u:foreach("wireless", "wifi-iface",
656                                 function(s)
657                                         if s.device == d then
658                                                 c = c + 1
659                                                 if c == n then
660                                                         ifname = s.ifname or s.device
661                                                         return false
662                                                 end
663                                         end
664                                 end)
665                 elseif u:get("wireless", ifname) == "wifi-device" then
666                         u:foreach("wireless", "wifi-iface",
667                                 function(s)
668                                         if s.device == ifname and s.ifname then
669                                                 ifname = s.ifname
670                                                 return false
671                                         end
672                                 end)
673                 end
674
675                 local t = stat and iwinfo.type(ifname)
676                 local x = t and iwinfo[t] or { }
677                 return setmetatable({}, {
678                         __index = function(t, k)
679                                 if k == "ifname" then
680                                         return ifname
681                                 elseif x[k] then
682                                         return x[k](ifname)
683                                 end
684                         end
685                 })
686         end
687 end
688
689 --- Get iwconfig output for all wireless devices.
690 -- @return      Table of tables containing the iwconfing output for each wifi device
691 function wifi.getiwconfig()
692         local cnt = luci.util.exec("PATH=/sbin:/usr/sbin iwconfig 2>/dev/null")
693         local iwc = {}
694
695         for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
696                 local k = l:match("^(.-) ")
697                 l = l:gsub("^(.-) +", "", 1)
698                 if k then
699                         local entry, flags = _parse_mixed_record(l)
700                         if entry then
701                                 entry.flags = flags
702                         end
703                         iwc[k] = entry
704                 end
705         end
706
707         return iwc
708 end
709
710 --- Get iwlist scan output from all wireless devices.
711 -- @return      Table of tables contaiing all scan results
712 function wifi.iwscan(iface)
713         local siface = iface or ""
714         local cnt = luci.util.exec("iwlist "..siface.." scan 2>/dev/null")
715         local iws = {}
716
717         for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
718                 local k = l:match("^(.-) ")
719                 l = l:gsub("^[^\n]+", "", 1)
720                 l = luci.util.trim(l)
721                 if k then
722                         iws[k] = {}
723                         for j, c in pairs(luci.util.split(l, "\n          Cell")) do
724                                 c = c:gsub("^(.-)- ", "", 1)
725                                 c = luci.util.split(c, "\n", 7)
726                                 c = table.concat(c, "\n", 1)
727                                 local entry, flags = _parse_mixed_record(c)
728                                 if entry then
729                                         entry.flags = flags
730                                 end
731                                 table.insert(iws[k], entry)
732                         end
733                 end
734         end
735
736         return iface and (iws[iface] or {}) or iws
737 end
738
739 --- Get available channels from given wireless iface.
740 -- @param iface Wireless interface (optional)
741 -- @return              Table of available channels
742 function wifi.channels(iface)
743         local stat, iwinfo = pcall(require, "iwinfo")
744         local cns
745
746         if stat then
747                 local t = iwinfo.type(iface or "")
748                 if iface and t and iwinfo[t] then
749                         cns = iwinfo[t].freqlist(iface)
750                 end
751         end
752
753         if not cns or #cns == 0 then
754                 cns = {
755                         {channel =  1, mhz = 2412},
756                         {channel =  2, mhz = 2417},
757                         {channel =  3, mhz = 2422},
758                         {channel =  4, mhz = 2427},
759                         {channel =  5, mhz = 2432},
760                         {channel =  6, mhz = 2437},
761                         {channel =  7, mhz = 2442},
762                         {channel =  8, mhz = 2447},
763                         {channel =  9, mhz = 2452},
764                         {channel = 10, mhz = 2457},
765                         {channel = 11, mhz = 2462}
766                 }
767         end
768
769         return cns
770 end
771
772
773 --- LuCI system utilities / init related functions.
774 -- @class       module
775 -- @name        luci.sys.init
776 init = {}
777 init.dir = "/etc/init.d/"
778
779 --- Get the names of all installed init scripts
780 -- @return      Table containing the names of all inistalled init scripts
781 function init.names()
782         local names = { }
783         for name in fs.glob(init.dir.."*") do
784                 names[#names+1] = fs.basename(name)
785         end
786         return names
787 end
788
789 --- Test whether the given init script is enabled
790 -- @param name  Name of the init script
791 -- @return              Boolean indicating whether init is enabled
792 function init.enabled(name)
793         if fs.access(init.dir..name) then
794                 return ( call(init.dir..name.." enabled") == 0 )
795         end
796         return false
797 end
798
799 --- Get the index of he given init script
800 -- @param name  Name of the init script
801 -- @return              Numeric index value
802 function init.index(name)
803         if fs.access(init.dir..name) then
804                 return call("source "..init.dir..name.." enabled; exit $START")
805         end
806 end
807
808 --- Enable the given init script
809 -- @param name  Name of the init script
810 -- @return              Boolean indicating success
811 function init.enable(name)
812         if fs.access(init.dir..name) then
813                 return ( call(init.dir..name.." enable") == 1 )
814         end
815 end
816
817 --- Disable the given init script
818 -- @param name  Name of the init script
819 -- @return              Boolean indicating success
820 function init.disable(name)
821         if fs.access(init.dir..name) then
822                 return ( call(init.dir..name.." disable") == 0 )
823         end
824 end
825
826
827 -- Internal functions
828
829 function _parse_delimited_table(iter, delimiter, callback)
830         delimiter = delimiter or "%s+"
831
832         local data  = {}
833         local trim  = luci.util.trim
834         local split = luci.util.split
835
836         local keys = split(trim(iter()), delimiter, nil, true)
837         for i, j in pairs(keys) do
838                 keys[i] = trim(keys[i])
839         end
840
841         for line in iter do
842                 local row = {}
843                 line = trim(line)
844                 if #line > 0 then
845                         for i, j in pairs(split(line, delimiter, nil, true)) do
846                                 if keys[i] then
847                                         row[keys[i]] = j
848                                 end
849                         end
850                 end
851
852                 if callback then
853                         callback(row)
854                 else
855                         data[#data+1] = row
856                 end
857         end
858
859         return data
860 end
861
862 function _parse_mixed_record(cnt, delimiter)
863         delimiter = delimiter or "  "
864         local data = {}
865         local flags = {}
866
867         for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n")) do
868                 for j, f in pairs(luci.util.split(luci.util.trim(l), delimiter, nil, true)) do
869                         local k, x, v = f:match('([^%s][^:=]*) *([:=]*) *"*([^\n"]*)"*')
870
871                         if k then
872                                 if x == "" then
873                                         table.insert(flags, k)
874                                 else
875                                         data[k] = v
876                                 end
877                         end
878                 end
879         end
880
881         return data, flags
882 end