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