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