53204b10b1ccc1724cf88fdcada9f3b48c77abb3
[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[1] == "PID" then
530 break
531 end
532 end
533
534 for line in ps do
535 local row = {}
536
537 line = luci.util.trim(line)
538 for i, value in ipairs(luci.util.split(line, "%s+", #k-1, true)) do
539 row[k[i]] = value
540 end
541
542 local pid = tonumber(row[k[1]])
543 if pid then
544 data[pid] = row
545 end
546 end
547
548 return data
549 end
550
551 --- Set the gid of a process identified by given pid.
552 -- @param gid Number containing the Unix group id
553 -- @return Boolean indicating successful operation
554 -- @return String containing the error message if failed
555 -- @return Number containing the error code if failed
556 function process.setgroup(gid)
557 return nixio.setgid(gid)
558 end
559
560 --- Set the uid of a process identified by given pid.
561 -- @param uid Number containing the Unix user id
562 -- @return Boolean indicating successful operation
563 -- @return String containing the error message if failed
564 -- @return Number containing the error code if failed
565 function process.setuser(uid)
566 return nixio.setuid(uid)
567 end
568
569 --- Send a signal to a process identified by given pid.
570 -- @class function
571 -- @name process.signal
572 -- @param pid Number containing the process id
573 -- @param sig Signal to send (default: 15 [SIGTERM])
574 -- @return Boolean indicating successful operation
575 -- @return Number containing the error code if failed
576 process.signal = nixio.kill
577
578
579 --- LuCI system utilities / user related functions.
580 -- @class module
581 -- @name luci.sys.user
582 user = {}
583
584 --- Retrieve user informations for given uid.
585 -- @class function
586 -- @name getuser
587 -- @param uid Number containing the Unix user id
588 -- @return Table containing the following fields:
589 -- { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" }
590 user.getuser = nixio.getpw
591
592 --- Retrieve the current user password hash.
593 -- @param username String containing the username to retrieve the password for
594 -- @return String containing the hash or nil if no password is set.
595 function user.getpasswd(username)
596 local pwe = nixio.getsp and nixio.getsp(username) or nixio.getpw(username)
597 local pwh = pwe and (pwe.pwdp or pwe.passwd)
598 if not pwh or #pwh < 1 or pwh == "!" or pwh == "x" then
599 return nil
600 else
601 return pwh
602 end
603 end
604
605 --- Test whether given string matches the password of a given system user.
606 -- @param username String containing the Unix user name
607 -- @param pass String containing the password to compare
608 -- @return Boolean indicating wheather the passwords are equal
609 function user.checkpasswd(username, pass)
610 local pwh = user.getpasswd(username)
611 if pwh and nixio.crypt(pass, pwh) ~= pwh then
612 return false
613 else
614 return true
615 end
616 end
617
618 --- Change the password of given user.
619 -- @param username String containing the Unix user name
620 -- @param password String containing the password to compare
621 -- @return Number containing 0 on success and >= 1 on error
622 function user.setpasswd(username, password)
623 if password then
624 password = password:gsub("'", [['"'"']])
625 end
626
627 if username then
628 username = username:gsub("'", [['"'"']])
629 end
630
631 return os.execute(
632 "(echo '" .. password .. "'; sleep 1; echo '" .. password .. "') | " ..
633 "passwd '" .. username .. "' >/dev/null 2>&1"
634 )
635 end
636
637
638 --- LuCI system utilities / wifi related functions.
639 -- @class module
640 -- @name luci.sys.wifi
641 wifi = {}
642
643 --- Get wireless information for given interface.
644 -- @param ifname String containing the interface name
645 -- @return A wrapped iwinfo object instance
646 function wifi.getiwinfo(ifname)
647 local stat, iwinfo = pcall(require, "iwinfo")
648
649 if ifname then
650 local c = 0
651 local u = uci.cursor_state()
652 local d, n = ifname:match("^(%w+)%.network(%d+)")
653 if d and n then
654 n = tonumber(n)
655 u:foreach("wireless", "wifi-iface",
656 function(s)
657 if s.device == d then
658 c = c + 1
659 if c == n then
660 ifname = s.ifname or s.device
661 return false
662 end
663 end
664 end)
665 elseif u:get("wireless", ifname) == "wifi-device" then
666 u:foreach("wireless", "wifi-iface",
667 function(s)
668 if s.device == ifname and s.ifname then
669 ifname = s.ifname
670 return false
671 end
672 end)
673 end
674
675 local t = stat and iwinfo.type(ifname)
676 local x = t and iwinfo[t] or { }
677 return setmetatable({}, {
678 __index = function(t, k)
679 if k == "ifname" then
680 return ifname
681 elseif x[k] then
682 return x[k](ifname)
683 end
684 end
685 })
686 end
687 end
688
689 --- Get iwconfig output for all wireless devices.
690 -- @return Table of tables containing the iwconfing output for each wifi device
691 function wifi.getiwconfig()
692 local cnt = luci.util.exec("PATH=/sbin:/usr/sbin iwconfig 2>/dev/null")
693 local iwc = {}
694
695 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
696 local k = l:match("^(.-) ")
697 l = l:gsub("^(.-) +", "", 1)
698 if k then
699 local entry, flags = _parse_mixed_record(l)
700 if entry then
701 entry.flags = flags
702 end
703 iwc[k] = entry
704 end
705 end
706
707 return iwc
708 end
709
710 --- Get iwlist scan output from all wireless devices.
711 -- @return Table of tables contaiing all scan results
712 function wifi.iwscan(iface)
713 local siface = iface or ""
714 local cnt = luci.util.exec("iwlist "..siface.." scan 2>/dev/null")
715 local iws = {}
716
717 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
718 local k = l:match("^(.-) ")
719 l = l:gsub("^[^\n]+", "", 1)
720 l = luci.util.trim(l)
721 if k then
722 iws[k] = {}
723 for j, c in pairs(luci.util.split(l, "\n Cell")) do
724 c = c:gsub("^(.-)- ", "", 1)
725 c = luci.util.split(c, "\n", 7)
726 c = table.concat(c, "\n", 1)
727 local entry, flags = _parse_mixed_record(c)
728 if entry then
729 entry.flags = flags
730 end
731 table.insert(iws[k], entry)
732 end
733 end
734 end
735
736 return iface and (iws[iface] or {}) or iws
737 end
738
739 --- Get available channels from given wireless iface.
740 -- @param iface Wireless interface (optional)
741 -- @return Table of available channels
742 function wifi.channels(iface)
743 local stat, iwinfo = pcall(require, "iwinfo")
744 local cns
745
746 if stat then
747 local t = iwinfo.type(iface or "")
748 if iface and t and iwinfo[t] then
749 cns = iwinfo[t].freqlist(iface)
750 end
751 end
752
753 if not cns or #cns == 0 then
754 cns = {
755 {channel = 1, mhz = 2412},
756 {channel = 2, mhz = 2417},
757 {channel = 3, mhz = 2422},
758 {channel = 4, mhz = 2427},
759 {channel = 5, mhz = 2432},
760 {channel = 6, mhz = 2437},
761 {channel = 7, mhz = 2442},
762 {channel = 8, mhz = 2447},
763 {channel = 9, mhz = 2452},
764 {channel = 10, mhz = 2457},
765 {channel = 11, mhz = 2462}
766 }
767 end
768
769 return cns
770 end
771
772
773 --- LuCI system utilities / init related functions.
774 -- @class module
775 -- @name luci.sys.init
776 init = {}
777 init.dir = "/etc/init.d/"
778
779 --- Get the names of all installed init scripts
780 -- @return Table containing the names of all inistalled init scripts
781 function init.names()
782 local names = { }
783 for name in fs.glob(init.dir.."*") do
784 names[#names+1] = fs.basename(name)
785 end
786 return names
787 end
788
789 --- Test whether the given init script is enabled
790 -- @param name Name of the init script
791 -- @return Boolean indicating whether init is enabled
792 function init.enabled(name)
793 if fs.access(init.dir..name) then
794 return ( call(init.dir..name.." enabled") == 0 )
795 end
796 return false
797 end
798
799 --- Get the index of he given init script
800 -- @param name Name of the init script
801 -- @return Numeric index value
802 function init.index(name)
803 if fs.access(init.dir..name) then
804 return call("source "..init.dir..name.." enabled; exit $START")
805 end
806 end
807
808 --- Enable the given init script
809 -- @param name Name of the init script
810 -- @return Boolean indicating success
811 function init.enable(name)
812 if fs.access(init.dir..name) then
813 return ( call(init.dir..name.." enable") == 1 )
814 end
815 end
816
817 --- Disable the given init script
818 -- @param name Name of the init script
819 -- @return Boolean indicating success
820 function init.disable(name)
821 if fs.access(init.dir..name) then
822 return ( call(init.dir..name.." disable") == 0 )
823 end
824 end
825
826
827 -- Internal functions
828
829 function _parse_delimited_table(iter, delimiter, callback)
830 delimiter = delimiter or "%s+"
831
832 local data = {}
833 local trim = luci.util.trim
834 local split = luci.util.split
835
836 local keys = split(trim(iter()), delimiter, nil, true)
837 for i, j in pairs(keys) do
838 keys[i] = trim(keys[i])
839 end
840
841 for line in iter do
842 local row = {}
843 line = trim(line)
844 if #line > 0 then
845 for i, j in pairs(split(line, delimiter, nil, true)) do
846 if keys[i] then
847 row[keys[i]] = j
848 end
849 end
850 end
851
852 if callback then
853 callback(row)
854 else
855 data[#data+1] = row
856 end
857 end
858
859 return data
860 end
861
862 function _parse_mixed_record(cnt, delimiter)
863 delimiter = delimiter or " "
864 local data = {}
865 local flags = {}
866
867 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n")) do
868 for j, f in pairs(luci.util.split(luci.util.trim(l), delimiter, nil, true)) do
869 local k, x, v = f:match('([^%s][^:=]*) *([:=]*) *"*([^\n"]*)"*')
870
871 if k then
872 if x == "" then
873 table.insert(flags, k)
874 else
875 data[k] = v
876 end
877 end
878 end
879 end
880
881 return data, flags
882 end