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