libs/sys: protect iwinfo loading and return stub if module is not present
[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 stat, iwinfo = pcall(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 = stat and iwinfo.type(ifname)
656                 local x = t and iwinfo[t] or { }
657                 return setmetatable({}, {
658                         __index = function(t, k)
659                                 if k == "ifname" then
660                                         return ifname
661                                 elseif x[k] then
662                                         return x[k](ifname)
663                                 end
664                         end
665                 })
666         end
667 end
668
669 --- Get iwconfig output for all wireless devices.
670 -- @return      Table of tables containing the iwconfing output for each wifi device
671 function wifi.getiwconfig()
672         local cnt = luci.util.exec("PATH=/sbin:/usr/sbin iwconfig 2>/dev/null")
673         local iwc = {}
674
675         for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
676                 local k = l:match("^(.-) ")
677                 l = l:gsub("^(.-) +", "", 1)
678                 if k then
679                         local entry, flags = _parse_mixed_record(l)
680                         if entry then
681                                 entry.flags = flags
682                         end
683                         iwc[k] = entry
684                 end
685         end
686
687         return iwc
688 end
689
690 --- Get iwlist scan output from all wireless devices.
691 -- @return      Table of tables contaiing all scan results
692 function wifi.iwscan(iface)
693         local siface = iface or ""
694         local cnt = luci.util.exec("iwlist "..siface.." scan 2>/dev/null")
695         local iws = {}
696
697         for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
698                 local k = l:match("^(.-) ")
699                 l = l:gsub("^[^\n]+", "", 1)
700                 l = luci.util.trim(l)
701                 if k then
702                         iws[k] = {}
703                         for j, c in pairs(luci.util.split(l, "\n          Cell")) do
704                                 c = c:gsub("^(.-)- ", "", 1)
705                                 c = luci.util.split(c, "\n", 7)
706                                 c = table.concat(c, "\n", 1)
707                                 local entry, flags = _parse_mixed_record(c)
708                                 if entry then
709                                         entry.flags = flags
710                                 end
711                                 table.insert(iws[k], entry)
712                         end
713                 end
714         end
715
716         return iface and (iws[iface] or {}) or iws
717 end
718
719 --- Get available channels from given wireless iface.
720 -- @param iface Wireless interface (optional)
721 -- @return              Table of available channels
722 function wifi.channels(iface)
723         local t = iwinfo.type(iface or "")
724         local cns
725         if iface and t and iwinfo[t] then
726                 cns = iwinfo[t].freqlist(iface)
727         end
728
729         if not cns or #cns == 0 then
730                 cns = {
731                         {channel =  1, mhz = 2412},
732                         {channel =  2, mhz = 2417},
733                         {channel =  3, mhz = 2422},
734                         {channel =  4, mhz = 2427},
735                         {channel =  5, mhz = 2432},
736                         {channel =  6, mhz = 2437},
737                         {channel =  7, mhz = 2442},
738                         {channel =  8, mhz = 2447},
739                         {channel =  9, mhz = 2452},
740                         {channel = 10, mhz = 2457},
741                         {channel = 11, mhz = 2462}
742                 }
743         end
744
745         return cns
746 end
747
748
749 --- LuCI system utilities / init related functions.
750 -- @class       module
751 -- @name        luci.sys.init
752 init = {}
753 init.dir = "/etc/init.d/"
754
755 --- Get the names of all installed init scripts
756 -- @return      Table containing the names of all inistalled init scripts
757 function init.names()
758         local names = { }
759         for name in fs.glob(init.dir.."*") do
760                 names[#names+1] = fs.basename(name)
761         end
762         return names
763 end
764
765 --- Test whether the given init script is enabled
766 -- @param name  Name of the init script
767 -- @return              Boolean indicating whether init is enabled
768 function init.enabled(name)
769         if fs.access(init.dir..name) then
770                 return ( call(init.dir..name.." enabled") == 0 )
771         end
772         return false
773 end
774
775 --- Get the index of he given init script
776 -- @param name  Name of the init script
777 -- @return              Numeric index value
778 function init.index(name)
779         if fs.access(init.dir..name) then
780                 return call("source "..init.dir..name.." enabled; exit $START")
781         end
782 end
783
784 --- Enable the given init script
785 -- @param name  Name of the init script
786 -- @return              Boolean indicating success
787 function init.enable(name)
788         if fs.access(init.dir..name) then
789                 return ( call(init.dir..name.." enable") == 1 )
790         end
791 end
792
793 --- Disable the given init script
794 -- @param name  Name of the init script
795 -- @return              Boolean indicating success
796 function init.disable(name)
797         if fs.access(init.dir..name) then
798                 return ( call(init.dir..name.." disable") == 0 )
799         end
800 end
801
802
803 -- Internal functions
804
805 function _parse_delimited_table(iter, delimiter, callback)
806         delimiter = delimiter or "%s+"
807
808         local data  = {}
809         local trim  = luci.util.trim
810         local split = luci.util.split
811
812         local keys = split(trim(iter()), delimiter, nil, true)
813         for i, j in pairs(keys) do
814                 keys[i] = trim(keys[i])
815         end
816
817         for line in iter do
818                 local row = {}
819                 line = trim(line)
820                 if #line > 0 then
821                         for i, j in pairs(split(line, delimiter, nil, true)) do
822                                 if keys[i] then
823                                         row[keys[i]] = j
824                                 end
825                         end
826                 end
827
828                 if callback then
829                         callback(row)
830                 else
831                         data[#data+1] = row
832                 end
833         end
834
835         return data
836 end
837
838 function _parse_mixed_record(cnt, delimiter)
839         delimiter = delimiter or "  "
840         local data = {}
841         local flags = {}
842
843         for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n")) do
844                 for j, f in pairs(luci.util.split(luci.util.trim(l), delimiter, nil, true)) do
845                         local k, x, v = f:match('([^%s][^:=]*) *([:=]*) *"*([^\n"]*)"*')
846
847                         if k then
848                                 if x == "" then
849                                         table.insert(flags, k)
850                                 else
851                                         data[k] = v
852                                 end
853                         end
854                 end
855         end
856
857         return data, flags
858 end