- Add support for hostap driver
[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 posix = require "posix"
31 local table = require "table"
32
33 local luci  = {}
34 luci.util   = require "luci.util"
35 luci.fs     = require "luci.fs"
36 luci.ip     = require "luci.ip"
37
38 local tonumber, ipairs, pairs, pcall, type =
39         tonumber, ipairs, pairs, pcall, type
40
41
42 --- LuCI Linux and POSIX system utilities.
43 module "luci.sys"
44
45 --- Execute a given shell command and return the error code
46 -- @class               function
47 -- @name                call
48 -- @param               ...             Command to call
49 -- @return              Error code of the command
50 function call(...)
51         return os.execute(...) / 256
52 end
53
54 --- Execute a given shell command and capture its standard output
55 -- @class               function
56 -- @name                exec
57 -- @param command       Command to call
58 -- @return                      String containg the return the output of the command
59 exec = luci.util.exec
60
61 --- Invoke the luci-flash executable to write an image to the flash memory.
62 -- @param image         Local path or URL to image file
63 -- @param kpattern      Pattern of files to keep over flash process
64 -- @return                      Return value of os.execute()
65 function flash(image, kpattern)
66         local cmd = "luci-flash "
67         if kpattern then
68                 cmd = cmd .. "-k '" .. kpattern:gsub("'", "") .. "' "
69         end
70         cmd = cmd .. "'" .. image:gsub("'", "") .. "' >/dev/null 2>&1"
71
72         return os.execute(cmd)
73 end
74
75 --- Retrieve information about currently mounted file systems.
76 -- @return      Table containing mount information
77 function mounts()
78         local data = {}
79         local k = {"fs", "blocks", "used", "available", "percent", "mountpoint"}
80         local ps = luci.util.execi("df")
81
82         if not ps then
83                 return
84         else
85                 ps()
86         end
87
88         for line in ps do
89                 local row = {}
90
91                 local j = 1
92                 for value in line:gmatch("[^%s]+") do
93                         row[k[j]] = value
94                         j = j + 1
95                 end
96
97                 if row[k[1]] then
98
99                         -- this is a rather ugly workaround to cope with wrapped lines in
100                         -- the df output:
101                         --
102                         --      /dev/scsi/host0/bus0/target0/lun0/part3
103                         --                   114382024  93566472  15005244  86% /mnt/usb
104                         --
105
106                         if not row[k[2]] then
107                                 j = 2
108                                 line = ps()
109                                 for value in line:gmatch("[^%s]+") do
110                                         row[k[j]] = value
111                                         j = j + 1
112                                 end
113                         end
114
115                         table.insert(data, row)
116                 end
117         end
118
119         return data
120 end
121
122 --- Retrieve environment variables. If no variable is given then a table
123 -- containing the whole environment is returned otherwise this function returns
124 -- the corresponding string value for the given name or nil if no such variable
125 -- exists.
126 -- @class               function
127 -- @name                getenv
128 -- @param var   Name of the environment variable to retrieve (optional)
129 -- @return              String containg the value of the specified variable
130 -- @return              Table containing all variables if no variable name is given
131 getenv = posix.getenv
132
133 --- Get or set the current hostname.
134 -- @param               String containing a new hostname to set (optional)
135 -- @return              String containing the system hostname
136 function hostname(newname)
137         if type(newname) == "string" and #newname > 0 then
138                 luci.fs.writefile( "/proc/sys/kernel/hostname", newname .. "\n" )
139                 return newname
140         else
141                 return posix.uname("%n")
142         end
143 end
144
145 --- Returns the contents of a documented referred by an URL.
146 -- @param url    The URL to retrieve
147 -- @param stream Return a stream instead of a buffer
148 -- @param target Directly write to target file name
149 -- @return              String containing the contents of given the URL
150 function httpget(url, stream, target)
151         if not target then
152                 local source = stream and io.popen or luci.util.exec
153                 return source("wget -qO- '"..url:gsub("'", "").."'")
154         else
155                 return os.execute("wget -qO '%s' '%s'" %
156                         {target:gsub("'", ""), url:gsub("'", "")})
157         end
158 end
159
160 --- Returns the system load average values.
161 -- @return      String containing the average load value 1 minute ago
162 -- @return      String containing the average load value 5 minutes ago
163 -- @return      String containing the average load value 15 minutes ago
164 -- @return      String containing the active and total number of processes
165 -- @return      String containing the last used pid
166 function loadavg()
167         local loadavg = io.lines("/proc/loadavg")()
168         return loadavg:match("^(.-) (.-) (.-) (.-) (.-)$")
169 end
170
171 --- Initiate a system reboot.
172 -- @return      Return value of os.execute()
173 function reboot()
174         return os.execute("reboot >/dev/null 2>&1")
175 end
176
177 --- Returns the system type, cpu name and installed physical memory.
178 -- @return      String containing the system or platform identifier
179 -- @return      String containing hardware model information
180 -- @return      String containing the total memory amount in kB
181 -- @return      String containing the memory used for caching in kB
182 -- @return      String containing the memory used for buffering in kB
183 -- @return      String containing the free memory amount in kB
184 function sysinfo()
185         local cpuinfo = luci.fs.readfile("/proc/cpuinfo")
186         local meminfo = luci.fs.readfile("/proc/meminfo")
187
188         local system = cpuinfo:match("system typ.-:%s*([^\n]+)")
189         local model = ""
190         local memtotal = tonumber(meminfo:match("MemTotal:%s*(%d+)"))
191         local memcached = tonumber(meminfo:match("\nCached:%s*(%d+)"))
192         local memfree = tonumber(meminfo:match("MemFree:%s*(%d+)"))
193         local membuffers = tonumber(meminfo:match("Buffers:%s*(%d+)"))
194
195         if not system then
196                 system = posix.uname("%m")
197                 model = cpuinfo:match("model name.-:%s*([^\n]+)")
198                 if not model then
199                         model = cpuinfo:match("Processor.-:%s*([^\n]+)")
200                 end
201         else
202                 model = cpuinfo:match("cpu model.-:%s*([^\n]+)")
203         end
204
205         return system, model, memtotal, memcached, membuffers, memfree
206 end
207
208 --- Retrieves the output of the "logread" command.
209 -- @return      String containing the current log buffer
210 function syslog()
211         return luci.util.exec("logread")
212 end
213
214 --- Retrieves the output of the "dmesg" command.
215 -- @return      String containing the current log buffer
216 function dmesg()
217         return luci.util.exec("dmesg")
218 end
219
220 --- Generates a random id with specified length.
221 -- @param bytes Number of bytes for the unique id
222 -- @return              String containing hex encoded id
223 function uniqueid(bytes)
224         local fp    = io.open("/dev/urandom")
225         local chunk = { fp:read(bytes):byte(1, bytes) }
226         fp:close()
227
228         local hex = ""
229
230         local pattern = "%02X"
231         for i, byte in ipairs(chunk) do
232                 hex = hex .. pattern:format(byte)
233         end
234
235         return hex
236 end
237
238 --- Returns the current system uptime stats.
239 -- @return      String containing total uptime in seconds
240 -- @return      String containing idle time in seconds
241 function uptime()
242         local loadavg = io.lines("/proc/uptime")()
243         return loadavg:match("^(.-) (.-)$")
244 end
245
246 --- LuCI system utilities / POSIX user group related functions.
247 -- @class       module
248 -- @name        luci.sys.group
249 group = {}
250
251 --- Returns information about a POSIX user group.
252 -- @class function
253 -- @name                getgroup
254 -- @param group Group ID or name of a system user group
255 -- @return      Table with information about the requested group
256 group.getgroup = posix.getgroup
257
258
259 --- LuCI system utilities / network related functions.
260 -- @class       module
261 -- @name        luci.sys.net
262 net = {}
263
264 --- Returns the current arp-table entries as two-dimensional table.
265 -- @return      Table of table containing the current arp entries.
266 --                      The following fields are defined for arp entry objects:
267 --                      { "IP address", "HW address", "HW type", "Flags", "Mask", "Device" }
268 function net.arptable()
269         return _parse_delimited_table(io.lines("/proc/net/arp"), "%s%s+")
270 end
271
272 --- Returns conntrack information
273 -- @return      Table with the currently tracked IP connections
274 function net.conntrack()
275         local connt = {}
276         if luci.fs.access("/proc/net/nf_conntrack", "r") then
277                 for line in io.lines("/proc/net/nf_conntrack") do
278                         line = line:match "^(.-( [^ =]+=).-)%2"
279                         local entry, flags = _parse_mixed_record(line, " +")
280                         entry.layer3 = flags[1]
281                         entry.layer4 = flags[3]
282                         for i=1, #entry do
283                                 entry[i] = nil
284                         end
285
286                         connt[#connt+1] = entry
287                 end
288         elseif luci.fs.access("/proc/net/ip_conntrack", "r") then
289                 for line in io.lines("/proc/net/ip_conntrack") do
290                         line = line:match "^(.-( [^ =]+=).-)%2"
291                         local entry, flags = _parse_mixed_record(line, " +")
292                         entry.layer3 = "ipv4"
293                         entry.layer4 = flags[1]
294                         for i=1, #entry do
295                                 entry[i] = nil
296                         end
297
298                         connt[#connt+1] = entry
299                 end
300         else
301                 return nil
302         end
303         return connt
304 end
305
306 --- Determine the current IPv4 default route. If multiple default routes exist,
307 -- return the one with the lowest metric.
308 -- @return      Table with the properties of the current default route.
309 --                      The following fields are defined:
310 --                      { "dest", "gateway", "metric", "refcount", "usecount", "irtt",
311 --                        "flags", "device" }
312 function net.defaultroute()
313         local route = nil
314         for _, r in pairs(net.routes()) do
315                 if r.dest:prefix() == 0 and (not route or route.metric > r.metric) then
316                         route = r
317                 end
318         end
319         return route
320 end
321
322 --- Determine the current IPv6 default route. If multiple default routes exist,
323 -- return the one with the lowest metric.
324 -- @return      Table with the properties of the current default route.
325 --                      The following fields are defined:
326 --                      { "source", "dest", "nexthop", "metric", "refcount", "usecount",
327 --                        "flags", "device" }
328 function net.defaultroute6()
329         local route   = nil
330         local routes6 = net.routes6()
331         if routes6 then
332                 for _, r in pairs(routes6) do
333                         if r.dest:prefix() == 0 and
334                            (not route or route.metric > r.metric)
335                         then
336                                 route = r
337                         end
338                 end
339         end
340         return route
341 end
342
343 --- Determine the names of available network interfaces.
344 -- @return      Table containing all current interface names
345 function net.devices()
346         local devices = {}
347         for line in io.lines("/proc/net/dev") do
348                 table.insert(devices, line:match(" *(.-):"))
349         end
350         return devices
351 end
352
353
354 --- Return information about available network interfaces.
355 -- @return      Table containing all current interface names and their information
356 function net.deviceinfo()
357         local devices = {}
358         for line in io.lines("/proc/net/dev") do
359                 local name, data = line:match("^ *(.-): *(.*)$")
360                 if name and data then
361                         devices[name] = luci.util.split(data, " +", nil, true)
362                 end
363         end
364         return devices
365 end
366
367
368 -- Determine the MAC address belonging to the given IP address.
369 -- @param ip    IPv4 address
370 -- @return              String containing the MAC address or nil if it cannot be found
371 function net.ip4mac(ip)
372         local mac = nil
373
374         for i, l in ipairs(net.arptable()) do
375                 if l["IP address"] == ip then
376                         mac = l["HW address"]
377                 end
378         end
379
380         return mac
381 end
382
383 --- Returns the current kernel routing table entries.
384 -- @return      Table of tables with properties of the corresponding routes.
385 --                      The following fields are defined for route entry tables:
386 --                      { "dest", "gateway", "metric", "refcount", "usecount", "irtt",
387 --                        "flags", "device" }
388 function net.routes()
389         local routes = { }
390
391         for line in io.lines("/proc/net/route") do
392
393                 local dev, dst_ip, gateway, flags, refcnt, usecnt, metric,
394                           dst_mask, mtu, win, irtt = line:match(
395                         "([^%s]+)\t([A-F0-9]+)\t([A-F0-9]+)\t([A-F0-9]+)\t" ..
396                         "(%d+)\t(%d+)\t(%d+)\t([A-F0-9]+)\t(%d+)\t(%d+)\t(%d+)"
397                 )
398
399                 if dev then
400                         gateway  = luci.ip.Hex( gateway,  32, luci.ip.FAMILY_INET4 )
401                         dst_mask = luci.ip.Hex( dst_mask, 32, luci.ip.FAMILY_INET4 )
402                         dst_ip   = luci.ip.Hex(
403                                 dst_ip, dst_mask:prefix(dst_mask), luci.ip.FAMILY_INET4
404                         )
405
406                         routes[#routes+1] = {
407                                 dest     = dst_ip,
408                                 gateway  = gateway,
409                                 metric   = tonumber(metric),
410                                 refcount = tonumber(refcnt),
411                                 usecount = tonumber(usecnt),
412                                 mtu      = tonumber(mtu),
413                                 window   = tonumber(window),
414                                 irtt     = tonumber(irtt),
415                                 flags    = tonumber(flags, 16),
416                                 device   = dev
417                         }
418                 end
419         end
420
421         return routes
422 end
423
424 --- Returns the current ipv6 kernel routing table entries.
425 -- @return      Table of tables with properties of the corresponding routes.
426 --                      The following fields are defined for route entry tables:
427 --                      { "source", "dest", "nexthop", "metric", "refcount", "usecount",
428 --                        "flags", "device" }
429 function net.routes6()
430         if luci.fs.access("/proc/net/ipv6_route", "r") then
431                 local routes = { }
432
433                 for line in io.lines("/proc/net/ipv6_route") do
434
435                         local dst_ip, dst_prefix, src_ip, src_prefix, nexthop,
436                                   metric, refcnt, usecnt, flags, dev = line:match(
437                                 "([a-f0-9]+) ([a-f0-9]+) " ..
438                                 "([a-f0-9]+) ([a-f0-9]+) " ..
439                                 "([a-f0-9]+) ([a-f0-9]+) " ..
440                                 "([a-f0-9]+) ([a-f0-9]+) " ..
441                                 "([a-f0-9]+) +([^%s]+)"
442                         )
443
444                         src_ip = luci.ip.Hex(
445                                 src_ip, tonumber(src_prefix, 16), luci.ip.FAMILY_INET6, false
446                         )
447
448                         dst_ip = luci.ip.Hex(
449                                 dst_ip, tonumber(dst_prefix, 16), luci.ip.FAMILY_INET6, false
450                         )
451
452                         nexthop = luci.ip.Hex( nexthop, 128, luci.ip.FAMILY_INET6, false )
453
454                         routes[#routes+1] = {
455                                 source   = src_ip,
456                                 dest     = dst_ip,
457                                 nexthop  = nexthop,
458                                 metric   = tonumber(metric, 16),
459                                 refcount = tonumber(refcnt, 16),
460                                 usecount = tonumber(usecnt, 16),
461                                 flags    = tonumber(flags, 16),
462                                 device   = dev
463                         }
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 process.info = posix.getpid
488
489 --- Retrieve information about currently running processes.
490 -- @return      Table containing process information
491 function process.list()
492         local data = {}
493         local k
494         local ps = luci.util.execi("top -bn1")
495
496         if not ps then
497                 return
498         end
499
500         while true do
501                 local line = ps()
502                 if not line then
503                         return
504                 end
505
506                 k = luci.util.split(luci.util.trim(line), "%s+", nil, true)
507                 if k[1] == "PID" then
508                         break
509                 end
510         end
511
512         for line in ps do
513                 local row = {}
514
515                 line = luci.util.trim(line)
516                 for i, value in ipairs(luci.util.split(line, "%s+", #k-1, true)) do
517                         row[k[i]] = value
518                 end
519
520                 local pid = tonumber(row[k[1]])
521                 if pid then
522                         data[pid] = row
523                 end
524         end
525
526         return data
527 end
528
529 --- Set the gid of a process identified by given pid.
530 -- @param pid   Number containing the process id
531 -- @param gid   Number containing the Unix group id
532 -- @return              Boolean indicating successful operation
533 -- @return              String containing the error message if failed
534 -- @return              Number containing the error code if failed
535 function process.setgroup(pid, gid)
536         return posix.setpid("g", pid, gid)
537 end
538
539 --- Set the uid of a process identified by given pid.
540 -- @param pid   Number containing the process id
541 -- @param uid   Number containing the Unix user id
542 -- @return              Boolean indicating successful operation
543 -- @return              String containing the error message if failed
544 -- @return              Number containing the error code if failed
545 function process.setuser(pid, uid)
546         return posix.setpid("u", pid, uid)
547 end
548
549 --- Send a signal to a process identified by given pid.
550 -- @class function
551 -- @name  process.signal
552 -- @param pid   Number containing the process id
553 -- @param sig   Signal to send (default: 15 [SIGTERM])
554 -- @return              Boolean indicating successful operation
555 -- @return              Number containing the error code if failed
556 process.signal = posix.kill
557
558
559 --- LuCI system utilities / user related functions.
560 -- @class       module
561 -- @name        luci.sys.user
562 user = {}
563
564 --- Retrieve user informations for given uid.
565 -- @class               function
566 -- @name                getuser
567 -- @param uid   Number containing the Unix user id
568 -- @return              Table containing the following fields:
569 --                              { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" }
570 user.getuser = posix.getpasswd
571
572 --- Test whether given string matches the password of a given system user.
573 -- @param username      String containing the Unix user name
574 -- @param password      String containing the password to compare
575 -- @return                      Boolean indicating wheather the passwords are equal
576 function user.checkpasswd(username, password)
577         local account = user.getuser(username)
578
579         if account then
580                 local pwd = account.passwd
581                 local shadowpw
582                 if #pwd == 1 then
583                         if luci.fs.stat("/etc/shadow") then
584                                 if not pcall(function()
585                                         for l in io.lines("/etc/shadow") do
586                                                 shadowpw = l:match("^%s:([^:]+)" % username)
587                                                 if shadowpw then
588                                                         pwd = shadowpw
589                                                         break
590                                                 end
591                                         end
592                                 end) then
593                                         return nil, "Unable to access shadow-file"
594                                 end
595                         end
596
597                         if pwd == "!" then
598                                 return true
599                         end
600                 end
601
602                 if pwd and #pwd > 0 and password and #password > 0 then
603                         return (pwd == posix.crypt(password, pwd))
604                 end
605         end
606
607         return false
608 end
609
610 --- Change the password of given user.
611 -- @param username      String containing the Unix user name
612 -- @param password      String containing the password to compare
613 -- @return                      Number containing 0 on success and >= 1 on error
614 function user.setpasswd(username, password)
615         if password then
616                 password = password:gsub("'", "")
617         end
618
619         if username then
620                 username = username:gsub("'", "")
621         end
622
623         local cmd = "(echo '"..password.."';sleep 1;echo '"..password.."')|"
624         cmd = cmd .. "passwd '"..username.."' >/dev/null 2>&1"
625         return os.execute(cmd)
626 end
627
628
629 --- LuCI system utilities / wifi related functions.
630 -- @class       module
631 -- @name        luci.sys.wifi
632 wifi = {}
633
634 --- Get iwconfig output for all wireless devices.
635 -- @return      Table of tables containing the iwconfing output for each wifi device
636 function wifi.getiwconfig()
637         local cnt = luci.util.exec("PATH=/sbin:/usr/sbin iwconfig 2>/dev/null")
638         local iwc = {}
639
640         for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
641                 local k = l:match("^(.-) ")
642                 l = l:gsub("^(.-) +", "", 1)
643                 if k then
644                         local entry, flags = _parse_mixed_record(l)
645                         if entry then
646                                 entry.flags = flags
647                         end
648                         iwc[k] = entry
649                 end
650         end
651
652         return iwc
653 end
654
655 --- Get iwlist scan output from all wireless devices.
656 -- @return      Table of tables contaiing all scan results
657 function wifi.iwscan(iface)
658         local siface = iface or ""
659         local cnt = luci.util.exec("iwlist "..siface.." scan 2>/dev/null")
660         local iws = {}
661
662         for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
663                 local k = l:match("^(.-) ")
664                 l = l:gsub("^[^\n]+", "", 1)
665                 l = luci.util.trim(l)
666                 if k then
667                         iws[k] = {}
668                         for j, c in pairs(luci.util.split(l, "\n          Cell")) do
669                                 c = c:gsub("^(.-)- ", "", 1)
670                                 c = luci.util.split(c, "\n", 7)
671                                 c = table.concat(c, "\n", 1)
672                                 local entry, flags = _parse_mixed_record(c)
673                                 if entry then
674                                         entry.flags = flags
675                                 end
676                                 table.insert(iws[k], entry)
677                         end
678                 end
679         end
680
681         return iface and (iws[iface] or {}) or iws
682 end
683
684 --- Get available channels from given wireless iface.
685 -- @param iface Wireless interface (optional)
686 -- @return              Table of available channels
687 function wifi.channels(iface)
688         local cmd = "iwlist " .. ( iface or "" ) .. " freq 2>/dev/null"
689         local cns = { }
690
691         local fd = io.popen(cmd)
692         if fd then
693                 local ln, c, f
694                 while true do
695                         ln = fd:read("*l")
696                         if not ln then break end
697                         c, f = ln:match("Channel (%d+) : (%d+%.%d+) GHz")
698                         if c and f then
699                                 cns[tonumber(c)] = tonumber(f)
700                         end
701                 end
702                 fd:close()
703         end
704
705         if not ((pairs(cns))(cns)) then
706                 cns = {
707                         2.412, 2.417, 2.422, 2.427, 2.432, 2.437,
708                         2.442, 2.447, 2.452, 2.457, 2.462
709                 }
710         end
711
712         return cns
713 end
714
715
716 --- LuCI system utilities / init related functions.
717 -- @class       module
718 -- @name        luci.sys.init
719 init = {}
720 init.dir = "/etc/init.d/"
721
722 --- Get the names of all installed init scripts
723 -- @return      Table containing the names of all inistalled init scripts
724 function init.names()
725         local names = { }
726         for _, name in ipairs(luci.fs.glob(init.dir.."*")) do
727                 names[#names+1] = luci.fs.basename(name)
728         end
729         return names
730 end
731
732 --- Test whether the given init script is enabled
733 -- @param name  Name of the init script
734 -- @return              Boolean indicating whether init is enabled
735 function init.enabled(name)
736         if luci.fs.access(init.dir..name) then
737                 return ( call(init.dir..name.." enabled") == 0 )
738         end
739         return false
740 end
741
742 --- Get the index of he given init script
743 -- @param name  Name of the init script
744 -- @return              Numeric index value
745 function init.index(name)
746         if luci.fs.access(init.dir..name) then
747                 return call("source "..init.dir..name.."; exit $START")
748         end
749 end
750
751 --- Enable the given init script
752 -- @param name  Name of the init script
753 -- @return              Boolean indicating success
754 function init.enable(name)
755         if luci.fs.access(init.dir..name) then
756                 return ( call(init.dir..name.." enable") == 1 )
757         end
758 end
759
760 --- Disable the given init script
761 -- @param name  Name of the init script
762 -- @return              Boolean indicating success
763 function init.disable(name)
764         if luci.fs.access(init.dir..name) then
765                 return ( call(init.dir..name.." disable") == 0 )
766         end
767 end
768
769
770 -- Internal functions
771
772 function _parse_delimited_table(iter, delimiter)
773         delimiter = delimiter or "%s+"
774
775         local data  = {}
776         local trim  = luci.util.trim
777         local split = luci.util.split
778
779         local keys = split(trim(iter()), delimiter, nil, true)
780         for i, j in pairs(keys) do
781                 keys[i] = trim(keys[i])
782         end
783
784         for line in iter do
785                 local row = {}
786                 line = trim(line)
787                 if #line > 0 then
788                         for i, j in pairs(split(line, delimiter, nil, true)) do
789                                 if keys[i] then
790                                         row[keys[i]] = j
791                                 end
792                         end
793                 end
794                 table.insert(data, row)
795         end
796
797         return data
798 end
799
800 function _parse_mixed_record(cnt, delimiter)
801         delimiter = delimiter or "  "
802         local data = {}
803         local flags = {}
804
805         for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n")) do
806                 for j, f in pairs(luci.util.split(luci.util.trim(l), delimiter, nil, true)) do
807                         local k, x, v = f:match('([^%s][^:=]*) *([:=]*) *"*([^\n"]*)"*')
808
809                         if k then
810                                 if x == "" then
811                                         table.insert(flags, k)
812                                 else
813                                         data[k] = v
814                                 end
815                         end
816                 end
817         end
818
819         return data, flags
820 end