817217b900978e8bb4a7de28a6df69b37a0b0cc5
[project/luci.git] / libs / sys / luasrc / sys.lua
1 --[[
2 LuCI - System library
3
4 Description:
5 Utilities for interaction with the Linux system
6
7 FileId:
8 $Id$
9
10 License:
11 Copyright 2008 Steven Barth <steven@midlink.org>
12
13 Licensed under the Apache License, Version 2.0 (the "License");
14 you may not use this file except in compliance with the License.
15 You may obtain a copy of the License at
16
17         http://www.apache.org/licenses/LICENSE-2.0
18
19 Unless required by applicable law or agreed to in writing, software
20 distributed under the License is distributed on an "AS IS" BASIS,
21 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22 See the License for the specific language governing permissions and
23 limitations under the License.
24
25 ]]--
26
27
28 local io     = require "io"
29 local os     = require "os"
30 local table  = require "table"
31 local nixio  = require "nixio"
32 local fs     = require "nixio.fs"
33 local uci    = require "luci.model.uci"
34
35 local luci  = {}
36 luci.util   = require "luci.util"
37 luci.ip     = require "luci.ip"
38
39 local tonumber, ipairs, pairs, pcall, type, next, setmetatable, require =
40         tonumber, ipairs, pairs, pcall, type, next, setmetatable, require
41
42
43 --- LuCI Linux and POSIX system utilities.
44 module "luci.sys"
45
46 --- Execute a given shell command and return the error code
47 -- @class               function
48 -- @name                call
49 -- @param               ...             Command to call
50 -- @return              Error code of the command
51 function call(...)
52         return os.execute(...) / 256
53 end
54
55 --- Execute a given shell command and capture its standard output
56 -- @class               function
57 -- @name                exec
58 -- @param command       Command to call
59 -- @return                      String containg the return the output of the command
60 exec = luci.util.exec
61
62 --- Retrieve information about currently mounted file systems.
63 -- @return      Table containing mount information
64 function mounts()
65         local data = {}
66         local k = {"fs", "blocks", "used", "available", "percent", "mountpoint"}
67         local ps = luci.util.execi("df")
68
69         if not ps then
70                 return
71         else
72                 ps()
73         end
74
75         for line in ps do
76                 local row = {}
77
78                 local j = 1
79                 for value in line:gmatch("[^%s]+") do
80                         row[k[j]] = value
81                         j = j + 1
82                 end
83
84                 if row[k[1]] then
85
86                         -- this is a rather ugly workaround to cope with wrapped lines in
87                         -- the df output:
88                         --
89                         --      /dev/scsi/host0/bus0/target0/lun0/part3
90                         --                   114382024  93566472  15005244  86% /mnt/usb
91                         --
92
93                         if not row[k[2]] then
94                                 j = 2
95                                 line = ps()
96                                 for value in line:gmatch("[^%s]+") do
97                                         row[k[j]] = value
98                                         j = j + 1
99                                 end
100                         end
101
102                         table.insert(data, row)
103                 end
104         end
105
106         return data
107 end
108
109 --- Retrieve environment variables. If no variable is given then a table
110 -- containing the whole environment is returned otherwise this function returns
111 -- the corresponding string value for the given name or nil if no such variable
112 -- exists.
113 -- @class               function
114 -- @name                getenv
115 -- @param var   Name of the environment variable to retrieve (optional)
116 -- @return              String containg the value of the specified variable
117 -- @return              Table containing all variables if no variable name is given
118 getenv = nixio.getenv
119
120 --- Get or set the current hostname.
121 -- @param               String containing a new hostname to set (optional)
122 -- @return              String containing the system hostname
123 function hostname(newname)
124         if type(newname) == "string" and #newname > 0 then
125                 fs.writefile( "/proc/sys/kernel/hostname", newname )
126                 return newname
127         else
128                 return nixio.uname().nodename
129         end
130 end
131
132 --- Returns the contents of a documented referred by an URL.
133 -- @param url    The URL to retrieve
134 -- @param stream Return a stream instead of a buffer
135 -- @param target Directly write to target file name
136 -- @return              String containing the contents of given the URL
137 function httpget(url, stream, target)
138         if not target then
139                 local source = stream and io.popen or luci.util.exec
140                 return source("wget -qO- '"..url:gsub("'", "").."'")
141         else
142                 return os.execute("wget -qO '%s' '%s'" %
143                         {target:gsub("'", ""), url:gsub("'", "")})
144         end
145 end
146
147 --- Returns the system load average values.
148 -- @return      String containing the average load value 1 minute ago
149 -- @return      String containing the average load value 5 minutes ago
150 -- @return      String containing the average load value 15 minutes ago
151 function loadavg()
152         local info = nixio.sysinfo()
153         return info.loads[1], info.loads[2], info.loads[3]
154 end
155
156 --- Initiate a system reboot.
157 -- @return      Return value of os.execute()
158 function reboot()
159         return os.execute("reboot >/dev/null 2>&1")
160 end
161
162 --- Returns the system type, cpu name and installed physical memory.
163 -- @return      String containing the system or platform identifier
164 -- @return      String containing hardware model information
165 -- @return      String containing the total memory amount in kB
166 -- @return      String containing the memory used for caching in kB
167 -- @return      String containing the memory used for buffering in kB
168 -- @return      String containing the free memory amount in kB
169 -- @return      String containing the cpu bogomips (number)
170 function sysinfo()
171         local cpuinfo = fs.readfile("/proc/cpuinfo")
172         local meminfo = fs.readfile("/proc/meminfo")
173
174         local system = cpuinfo:match("system typ.-:%s*([^\n]+)")
175         local model = ""
176         local memtotal = tonumber(meminfo:match("MemTotal:%s*(%d+)"))
177         local memcached = tonumber(meminfo:match("\nCached:%s*(%d+)"))
178         local memfree = tonumber(meminfo:match("MemFree:%s*(%d+)"))
179         local membuffers = tonumber(meminfo:match("Buffers:%s*(%d+)"))
180         local bogomips = tonumber(cpuinfo:match("BogoMIPS.-:%s*([^\n]+)"))
181
182         if not system then
183                 system = nixio.uname().machine
184                 model = cpuinfo:match("model name.-:%s*([^\n]+)")
185                 if not model then
186                         model = cpuinfo:match("Processor.-:%s*([^\n]+)")
187                 end
188         else
189                 model = cpuinfo:match("cpu model.-:%s*([^\n]+)")
190         end
191
192         return system, model, memtotal, memcached, membuffers, memfree, bogomips
193 end
194
195 --- Retrieves the output of the "logread" command.
196 -- @return      String containing the current log buffer
197 function syslog()
198         return luci.util.exec("logread")
199 end
200
201 --- Retrieves the output of the "dmesg" command.
202 -- @return      String containing the current log buffer
203 function dmesg()
204         return luci.util.exec("dmesg")
205 end
206
207 --- Generates a random id with specified length.
208 -- @param bytes Number of bytes for the unique id
209 -- @return              String containing hex encoded id
210 function uniqueid(bytes)
211         local rand = fs.readfile("/dev/urandom", bytes)
212         return rand and nixio.bin.hexlify(rand)
213 end
214
215 --- Returns the current system uptime stats.
216 -- @return      String containing total uptime in seconds
217 function uptime()
218         return nixio.sysinfo().uptime
219 end
220
221
222 --- LuCI system utilities / network related functions.
223 -- @class       module
224 -- @name        luci.sys.net
225 net = {}
226
227 --- Returns the current arp-table entries as two-dimensional table.
228 -- @return      Table of table containing the current arp entries.
229 --                      The following fields are defined for arp entry objects:
230 --                      { "IP address", "HW address", "HW type", "Flags", "Mask", "Device" }
231 function net.arptable(callback)
232         return _parse_delimited_table(io.lines("/proc/net/arp"), "%s%s+", callback)
233 end
234
235 --- Returns conntrack information
236 -- @return      Table with the currently tracked IP connections
237 function net.conntrack(callback)
238         local connt = {}
239         if fs.access("/proc/net/nf_conntrack", "r") then
240                 for line in io.lines("/proc/net/nf_conntrack") do
241                         line = line:match "^(.-( [^ =]+=).-)%2"
242                         local entry, flags = _parse_mixed_record(line, " +")
243                         entry.layer3 = flags[1]
244                         entry.layer4 = flags[3]
245                         for i=1, #entry do
246                                 entry[i] = nil
247                         end
248
249                         if callback then
250                                 callback(entry)
251                         else
252                                 connt[#connt+1] = entry
253                         end
254                 end
255         elseif fs.access("/proc/net/ip_conntrack", "r") then
256                 for line in io.lines("/proc/net/ip_conntrack") do
257                         line = line:match "^(.-( [^ =]+=).-)%2"
258                         local entry, flags = _parse_mixed_record(line, " +")
259                         entry.layer3 = "ipv4"
260                         entry.layer4 = flags[1]
261                         for i=1, #entry do
262                                 entry[i] = nil
263                         end
264
265                         if callback then
266                                 callback(entry)
267                         else
268                                 connt[#connt+1] = entry
269                         end
270                 end
271         else
272                 return nil
273         end
274         return connt
275 end
276
277 --- Determine the current IPv4 default route. If multiple default routes exist,
278 -- return the one with the lowest metric.
279 -- @return      Table with the properties of the current default route.
280 --                      The following fields are defined:
281 --                      { "dest", "gateway", "metric", "refcount", "usecount", "irtt",
282 --                        "flags", "device" }
283 function net.defaultroute()
284         local route
285
286         net.routes(function(rt)
287                 if rt.dest:prefix() == 0 and (not route or route.metric > rt.metric) then
288                         route = rt
289                 end
290         end)
291
292         return route
293 end
294
295 --- Determine the current IPv6 default route. If multiple default routes exist,
296 -- return the one with the lowest metric.
297 -- @return      Table with the properties of the current default route.
298 --                      The following fields are defined:
299 --                      { "source", "dest", "nexthop", "metric", "refcount", "usecount",
300 --                        "flags", "device" }
301 function net.defaultroute6()
302         local route
303
304         net.routes6(function(rt)
305                 if rt.dest:prefix() == 0 and (not route or route.metric > rt.metric) then
306                         route = rt
307                 end
308         end)
309
310         return route
311 end
312
313 --- Determine the names of available network interfaces.
314 -- @return      Table containing all current interface names
315 function net.devices()
316         local devs = {}
317         for k, v in ipairs(nixio.getifaddrs()) do
318                 if v.family == "packet" then
319                         devs[#devs+1] = v.name
320                 end
321         end
322         return devs
323 end
324
325
326 --- Return information about available network interfaces.
327 -- @return      Table containing all current interface names and their information
328 function net.deviceinfo()
329         local devs = {}
330         for k, v in ipairs(nixio.getifaddrs()) do
331                 if v.family == "packet" then
332                         local d = v.data
333                         d[1] = d.rx_bytes
334                         d[2] = d.rx_packets
335                         d[3] = d.rx_errors
336                         d[4] = d.rx_dropped
337                         d[5] = 0
338                         d[6] = 0
339                         d[7] = 0
340                         d[8] = d.multicast
341                         d[9] = d.tx_bytes
342                         d[10] = d.tx_packets
343                         d[11] = d.tx_errors
344                         d[12] = d.tx_dropped
345                         d[13] = 0
346                         d[14] = d.collisions
347                         d[15] = 0
348                         d[16] = 0
349                         devs[v.name] = d
350                 end
351         end
352         return devs
353 end
354
355
356 -- Determine the MAC address belonging to the given IP address.
357 -- @param ip    IPv4 address
358 -- @return              String containing the MAC address or nil if it cannot be found
359 function net.ip4mac(ip)
360         local mac = nil
361         net.arptable(function(e)
362                 if e["IP address"] == ip then
363                         mac = e["HW address"]
364                 end
365         end)
366         return mac
367 end
368
369 --- Returns the current kernel routing table entries.
370 -- @return      Table of tables with properties of the corresponding routes.
371 --                      The following fields are defined for route entry tables:
372 --                      { "dest", "gateway", "metric", "refcount", "usecount", "irtt",
373 --                        "flags", "device" }
374 function net.routes(callback)
375         local routes = { }
376
377         for line in io.lines("/proc/net/route") do
378
379                 local dev, dst_ip, gateway, flags, refcnt, usecnt, metric,
380                           dst_mask, mtu, win, irtt = line:match(
381                         "([^%s]+)\t([A-F0-9]+)\t([A-F0-9]+)\t([A-F0-9]+)\t" ..
382                         "(%d+)\t(%d+)\t(%d+)\t([A-F0-9]+)\t(%d+)\t(%d+)\t(%d+)"
383                 )
384
385                 if dev then
386                         gateway  = luci.ip.Hex( gateway,  32, luci.ip.FAMILY_INET4 )
387                         dst_mask = luci.ip.Hex( dst_mask, 32, luci.ip.FAMILY_INET4 )
388                         dst_ip   = luci.ip.Hex(
389                                 dst_ip, dst_mask:prefix(dst_mask), luci.ip.FAMILY_INET4
390                         )
391
392                         local rt = {
393                                 dest     = dst_ip,
394                                 gateway  = gateway,
395                                 metric   = tonumber(metric),
396                                 refcount = tonumber(refcnt),
397                                 usecount = tonumber(usecnt),
398                                 mtu      = tonumber(mtu),
399                                 window   = tonumber(window),
400                                 irtt     = tonumber(irtt),
401                                 flags    = tonumber(flags, 16),
402                                 device   = dev
403                         }
404
405                         if callback then
406                                 callback(rt)
407                         else
408                                 routes[#routes+1] = rt
409                         end
410                 end
411         end
412
413         return routes
414 end
415
416 --- Returns the current ipv6 kernel routing table entries.
417 -- @return      Table of tables with properties of the corresponding routes.
418 --                      The following fields are defined for route entry tables:
419 --                      { "source", "dest", "nexthop", "metric", "refcount", "usecount",
420 --                        "flags", "device" }
421 function net.routes6(callback)
422         if fs.access("/proc/net/ipv6_route", "r") then
423                 local routes = { }
424
425                 for line in io.lines("/proc/net/ipv6_route") do
426
427                         local dst_ip, dst_prefix, src_ip, src_prefix, nexthop,
428                                   metric, refcnt, usecnt, flags, dev = line:match(
429                                 "([a-f0-9]+) ([a-f0-9]+) " ..
430                                 "([a-f0-9]+) ([a-f0-9]+) " ..
431                                 "([a-f0-9]+) ([a-f0-9]+) " ..
432                                 "([a-f0-9]+) ([a-f0-9]+) " ..
433                                 "([a-f0-9]+) +([^%s]+)"
434                         )
435
436                         src_ip = luci.ip.Hex(
437                                 src_ip, tonumber(src_prefix, 16), luci.ip.FAMILY_INET6, false
438                         )
439
440                         dst_ip = luci.ip.Hex(
441                                 dst_ip, tonumber(dst_prefix, 16), luci.ip.FAMILY_INET6, false
442                         )
443
444                         nexthop = luci.ip.Hex( nexthop, 128, luci.ip.FAMILY_INET6, false )
445
446                         local rt = {
447                                 source   = src_ip,
448                                 dest     = dst_ip,
449                                 nexthop  = nexthop,
450                                 metric   = tonumber(metric, 16),
451                                 refcount = tonumber(refcnt, 16),
452                                 usecount = tonumber(usecnt, 16),
453                                 flags    = tonumber(flags, 16),
454                                 device   = dev,
455
456                                 -- lua number is too small for storing the metric
457                                 -- add a metric_raw field with the original content
458                                 metric_raw = metric
459                         }
460
461                         if callback then
462                                 callback(rt)
463                         else
464                                 routes[#routes+1] = rt
465                         end
466                 end
467
468                 return routes
469         end
470 end
471
472 --- Tests whether the given host responds to ping probes.
473 -- @param host  String containing a hostname or IPv4 address
474 -- @return              Number containing 0 on success and >= 1 on error
475 function net.pingtest(host)
476         return os.execute("ping -c1 '"..host:gsub("'", '').."' >/dev/null 2>&1")
477 end
478
479
480 --- LuCI system utilities / process related functions.
481 -- @class       module
482 -- @name        luci.sys.process
483 process = {}
484
485 --- Get the current process id.
486 -- @class function
487 -- @name  process.info
488 -- @return      Number containing the current pid
489 function process.info(key)
490         local s = {uid = nixio.getuid(), gid = nixio.getgid()}
491         return not key and s or s[key]
492 end
493
494 --- Retrieve information about currently running processes.
495 -- @return      Table containing process information
496 function process.list()
497         local data = {}
498         local k
499         local ps = luci.util.execi("top -bn1")
500
501         if not ps then
502                 return
503         end
504
505         while true do
506                 local line = ps()
507                 if not line then
508                         return
509                 end
510
511                 k = luci.util.split(luci.util.trim(line), "%s+", nil, true)
512                 if k[1] == "PID" then
513                         break
514                 end
515         end
516
517         for line in ps do
518                 local row = {}
519
520                 line = luci.util.trim(line)
521                 for i, value in ipairs(luci.util.split(line, "%s+", #k-1, true)) do
522                         row[k[i]] = value
523                 end
524
525                 local pid = tonumber(row[k[1]])
526                 if pid then
527                         data[pid] = row
528                 end
529         end
530
531         return data
532 end
533
534 --- Set the gid of a process identified by given pid.
535 -- @param gid   Number containing the Unix group id
536 -- @return              Boolean indicating successful operation
537 -- @return              String containing the error message if failed
538 -- @return              Number containing the error code if failed
539 function process.setgroup(gid)
540         return nixio.setgid(gid)
541 end
542
543 --- Set the uid of a process identified by given pid.
544 -- @param uid   Number containing the Unix user id
545 -- @return              Boolean indicating successful operation
546 -- @return              String containing the error message if failed
547 -- @return              Number containing the error code if failed
548 function process.setuser(uid)
549         return nixio.setuid(uid)
550 end
551
552 --- Send a signal to a process identified by given pid.
553 -- @class function
554 -- @name  process.signal
555 -- @param pid   Number containing the process id
556 -- @param sig   Signal to send (default: 15 [SIGTERM])
557 -- @return              Boolean indicating successful operation
558 -- @return              Number containing the error code if failed
559 process.signal = nixio.kill
560
561
562 --- LuCI system utilities / user related functions.
563 -- @class       module
564 -- @name        luci.sys.user
565 user = {}
566
567 --- Retrieve user informations for given uid.
568 -- @class               function
569 -- @name                getuser
570 -- @param uid   Number containing the Unix user id
571 -- @return              Table containing the following fields:
572 --                              { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" }
573 user.getuser = nixio.getpw
574
575 --- Retrieve the current user password hash.
576 -- @param username      String containing the username to retrieve the password for
577 -- @return                      String containing the hash or nil if no password is set.
578 function user.getpasswd(username)
579         local pwe = nixio.getsp and nixio.getsp(username) or nixio.getpw(username)
580         local pwh = pwe and (pwe.pwdp or pwe.passwd)
581         if not pwh or #pwh < 1 or pwh == "!" or pwh == "x" then
582                 return nil
583         else
584                 return pwh
585         end
586 end
587
588 --- Test whether given string matches the password of a given system user.
589 -- @param username      String containing the Unix user name
590 -- @param pass          String containing the password to compare
591 -- @return                      Boolean indicating wheather the passwords are equal
592 function user.checkpasswd(username, pass)
593         local pwh = user.getpasswd(username)
594         if pwh and nixio.crypt(pass, pwh) ~= pwh then
595                 return false
596         else
597                 return true
598         end
599 end
600
601 --- Change the password of given user.
602 -- @param username      String containing the Unix user name
603 -- @param password      String containing the password to compare
604 -- @return                      Number containing 0 on success and >= 1 on error
605 function user.setpasswd(username, password)
606         if password then
607                 password = password:gsub("'", "")
608         end
609
610         if username then
611                 username = username:gsub("'", "")
612         end
613
614         local cmd = "(echo '"..password.."';sleep 1;echo '"..password.."')|"
615         cmd = cmd .. "passwd '"..username.."' >/dev/null 2>&1"
616         return os.execute(cmd)
617 end
618
619
620 --- LuCI system utilities / wifi related functions.
621 -- @class       module
622 -- @name        luci.sys.wifi
623 wifi = {}
624
625 --- Get wireless information for given interface.
626 -- @param ifname        String containing the interface name
627 -- @return              A wrapped iwinfo object instance
628 function wifi.getiwinfo(ifname)
629         local stat, iwinfo = pcall(require, "iwinfo")
630
631         if ifname then
632                 local c = 0
633                 local u = uci.cursor_state()
634                 local d, n = ifname:match("^(%w+)%.network(%d+)")
635                 if d and n then
636                         n = tonumber(n)
637                         u:foreach("wireless", "wifi-iface",
638                                 function(s)
639                                         if s.device == d then
640                                                 c = c + 1
641                                                 if c == n then
642                                                         ifname = s.ifname or s.device
643                                                         return false
644                                                 end
645                                         end
646                                 end)
647                 elseif u:get("wireless", ifname) == "wifi-device" then
648                         u:foreach("wireless", "wifi-iface",
649                                 function(s)
650                                         if s.device == ifname and s.ifname then
651                                                 ifname = s.ifname
652                                                 return false
653                                         end
654                                 end)
655                 end
656
657                 local t = stat and iwinfo.type(ifname)
658                 local x = t and iwinfo[t] or { }
659                 return setmetatable({}, {
660                         __index = function(t, k)
661                                 if k == "ifname" then
662                                         return ifname
663                                 elseif x[k] then
664                                         return x[k](ifname)
665                                 end
666                         end
667                 })
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