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