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