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