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