libs/sys: fix parsing of model and cpu info, consider 2000::/3 as IPv6 default route...
[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("BogoMIPS.-:%s*([^\n]+)"))
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 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 entry.layer3 = flags[1]
244 entry.layer4 = flags[3]
245 for i=1, #entry do
246 entry[i] = nil
247 end
248
249 if callback then
250 callback(entry)
251 else
252 connt[#connt+1] = entry
253 end
254 end
255 elseif fs.access("/proc/net/ip_conntrack", "r") then
256 for line in io.lines("/proc/net/ip_conntrack") do
257 line = line:match "^(.-( [^ =]+=).-)%2"
258 local entry, flags = _parse_mixed_record(line, " +")
259 entry.layer3 = "ipv4"
260 entry.layer4 = flags[1]
261 for i=1, #entry do
262 entry[i] = nil
263 end
264
265 if callback then
266 callback(entry)
267 else
268 connt[#connt+1] = entry
269 end
270 end
271 else
272 return nil
273 end
274 return connt
275 end
276
277 --- Determine the current IPv4 default route. If multiple default routes exist,
278 -- return the one with the lowest metric.
279 -- @return Table with the properties of the current default route.
280 -- The following fields are defined:
281 -- { "dest", "gateway", "metric", "refcount", "usecount", "irtt",
282 -- "flags", "device" }
283 function net.defaultroute()
284 local route
285
286 net.routes(function(rt)
287 if rt.dest:prefix() == 0 and (not route or route.metric > rt.metric) then
288 route = rt
289 end
290 end)
291
292 return route
293 end
294
295 --- Determine the current IPv6 default route. If multiple default routes exist,
296 -- return the one with the lowest metric.
297 -- @return Table with the properties of the current default route.
298 -- The following fields are defined:
299 -- { "source", "dest", "nexthop", "metric", "refcount", "usecount",
300 -- "flags", "device" }
301 function net.defaultroute6()
302 local route
303
304 net.routes6(function(rt)
305 if rt.dest:prefix() == 0 and (not route or route.metric > rt.metric) then
306 route = rt
307 end
308 end)
309
310 if not route then
311 local global_unicast = luci.ip.IPv6("2000::/3")
312 net.routes6(function(rt)
313 if rt.dest:equal(global_unicast) and
314 (not route or route.metric > rt.metric)
315 then
316 route = rt
317 end
318 end)
319 end
320
321 return route
322 end
323
324 --- Determine the names of available network interfaces.
325 -- @return Table containing all current interface names
326 function net.devices()
327 local devs = {}
328 for k, v in ipairs(nixio.getifaddrs()) do
329 if v.family == "packet" then
330 devs[#devs+1] = v.name
331 end
332 end
333 return devs
334 end
335
336
337 --- Return information about available network interfaces.
338 -- @return Table containing all current interface names and their information
339 function net.deviceinfo()
340 local devs = {}
341 for k, v in ipairs(nixio.getifaddrs()) do
342 if v.family == "packet" then
343 local d = v.data
344 d[1] = d.rx_bytes
345 d[2] = d.rx_packets
346 d[3] = d.rx_errors
347 d[4] = d.rx_dropped
348 d[5] = 0
349 d[6] = 0
350 d[7] = 0
351 d[8] = d.multicast
352 d[9] = d.tx_bytes
353 d[10] = d.tx_packets
354 d[11] = d.tx_errors
355 d[12] = d.tx_dropped
356 d[13] = 0
357 d[14] = d.collisions
358 d[15] = 0
359 d[16] = 0
360 devs[v.name] = d
361 end
362 end
363 return devs
364 end
365
366
367 -- Determine the MAC address belonging to the given IP address.
368 -- @param ip IPv4 address
369 -- @return String containing the MAC address or nil if it cannot be found
370 function net.ip4mac(ip)
371 local mac = nil
372 net.arptable(function(e)
373 if e["IP address"] == ip then
374 mac = e["HW address"]
375 end
376 end)
377 return mac
378 end
379
380 --- Returns the current kernel routing table entries.
381 -- @return Table of tables with properties of the corresponding routes.
382 -- The following fields are defined for route entry tables:
383 -- { "dest", "gateway", "metric", "refcount", "usecount", "irtt",
384 -- "flags", "device" }
385 function net.routes(callback)
386 local routes = { }
387
388 for line in io.lines("/proc/net/route") do
389
390 local dev, dst_ip, gateway, flags, refcnt, usecnt, metric,
391 dst_mask, mtu, win, irtt = line:match(
392 "([^%s]+)\t([A-F0-9]+)\t([A-F0-9]+)\t([A-F0-9]+)\t" ..
393 "(%d+)\t(%d+)\t(%d+)\t([A-F0-9]+)\t(%d+)\t(%d+)\t(%d+)"
394 )
395
396 if dev then
397 gateway = luci.ip.Hex( gateway, 32, luci.ip.FAMILY_INET4 )
398 dst_mask = luci.ip.Hex( dst_mask, 32, luci.ip.FAMILY_INET4 )
399 dst_ip = luci.ip.Hex(
400 dst_ip, dst_mask:prefix(dst_mask), luci.ip.FAMILY_INET4
401 )
402
403 local rt = {
404 dest = dst_ip,
405 gateway = gateway,
406 metric = tonumber(metric),
407 refcount = tonumber(refcnt),
408 usecount = tonumber(usecnt),
409 mtu = tonumber(mtu),
410 window = tonumber(window),
411 irtt = tonumber(irtt),
412 flags = tonumber(flags, 16),
413 device = dev
414 }
415
416 if callback then
417 callback(rt)
418 else
419 routes[#routes+1] = rt
420 end
421 end
422 end
423
424 return routes
425 end
426
427 --- Returns the current ipv6 kernel routing table entries.
428 -- @return Table of tables with properties of the corresponding routes.
429 -- The following fields are defined for route entry tables:
430 -- { "source", "dest", "nexthop", "metric", "refcount", "usecount",
431 -- "flags", "device" }
432 function net.routes6(callback)
433 if fs.access("/proc/net/ipv6_route", "r") then
434 local routes = { }
435
436 for line in io.lines("/proc/net/ipv6_route") do
437
438 local dst_ip, dst_prefix, src_ip, src_prefix, nexthop,
439 metric, refcnt, usecnt, flags, dev = line:match(
440 "([a-f0-9]+) ([a-f0-9]+) " ..
441 "([a-f0-9]+) ([a-f0-9]+) " ..
442 "([a-f0-9]+) ([a-f0-9]+) " ..
443 "([a-f0-9]+) ([a-f0-9]+) " ..
444 "([a-f0-9]+) +([^%s]+)"
445 )
446
447 src_ip = luci.ip.Hex(
448 src_ip, tonumber(src_prefix, 16), luci.ip.FAMILY_INET6, false
449 )
450
451 dst_ip = luci.ip.Hex(
452 dst_ip, tonumber(dst_prefix, 16), luci.ip.FAMILY_INET6, false
453 )
454
455 nexthop = luci.ip.Hex( nexthop, 128, luci.ip.FAMILY_INET6, false )
456
457 local rt = {
458 source = src_ip,
459 dest = dst_ip,
460 nexthop = nexthop,
461 metric = tonumber(metric, 16),
462 refcount = tonumber(refcnt, 16),
463 usecount = tonumber(usecnt, 16),
464 flags = tonumber(flags, 16),
465 device = dev,
466
467 -- lua number is too small for storing the metric
468 -- add a metric_raw field with the original content
469 metric_raw = metric
470 }
471
472 if callback then
473 callback(rt)
474 else
475 routes[#routes+1] = rt
476 end
477 end
478
479 return routes
480 end
481 end
482
483 --- Tests whether the given host responds to ping probes.
484 -- @param host String containing a hostname or IPv4 address
485 -- @return Number containing 0 on success and >= 1 on error
486 function net.pingtest(host)
487 return os.execute("ping -c1 '"..host:gsub("'", '').."' >/dev/null 2>&1")
488 end
489
490
491 --- LuCI system utilities / process related functions.
492 -- @class module
493 -- @name luci.sys.process
494 process = {}
495
496 --- Get the current process id.
497 -- @class function
498 -- @name process.info
499 -- @return Number containing the current pid
500 function process.info(key)
501 local s = {uid = nixio.getuid(), gid = nixio.getgid()}
502 return not key and s or s[key]
503 end
504
505 --- Retrieve information about currently running processes.
506 -- @return Table containing process information
507 function process.list()
508 local data = {}
509 local k
510 local ps = luci.util.execi("top -bn1")
511
512 if not ps then
513 return
514 end
515
516 while true do
517 local line = ps()
518 if not line then
519 return
520 end
521
522 k = luci.util.split(luci.util.trim(line), "%s+", nil, true)
523 if k[1] == "PID" then
524 break
525 end
526 end
527
528 for line in ps do
529 local row = {}
530
531 line = luci.util.trim(line)
532 for i, value in ipairs(luci.util.split(line, "%s+", #k-1, true)) do
533 row[k[i]] = value
534 end
535
536 local pid = tonumber(row[k[1]])
537 if pid then
538 data[pid] = row
539 end
540 end
541
542 return data
543 end
544
545 --- Set the gid of a process identified by given pid.
546 -- @param gid Number containing the Unix group id
547 -- @return Boolean indicating successful operation
548 -- @return String containing the error message if failed
549 -- @return Number containing the error code if failed
550 function process.setgroup(gid)
551 return nixio.setgid(gid)
552 end
553
554 --- Set the uid of a process identified by given pid.
555 -- @param uid Number containing the Unix user 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.setuser(uid)
560 return nixio.setuid(uid)
561 end
562
563 --- Send a signal to a process identified by given pid.
564 -- @class function
565 -- @name process.signal
566 -- @param pid Number containing the process id
567 -- @param sig Signal to send (default: 15 [SIGTERM])
568 -- @return Boolean indicating successful operation
569 -- @return Number containing the error code if failed
570 process.signal = nixio.kill
571
572
573 --- LuCI system utilities / user related functions.
574 -- @class module
575 -- @name luci.sys.user
576 user = {}
577
578 --- Retrieve user informations for given uid.
579 -- @class function
580 -- @name getuser
581 -- @param uid Number containing the Unix user id
582 -- @return Table containing the following fields:
583 -- { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" }
584 user.getuser = nixio.getpw
585
586 --- Retrieve the current user password hash.
587 -- @param username String containing the username to retrieve the password for
588 -- @return String containing the hash or nil if no password is set.
589 function user.getpasswd(username)
590 local pwe = nixio.getsp and nixio.getsp(username) or nixio.getpw(username)
591 local pwh = pwe and (pwe.pwdp or pwe.passwd)
592 if not pwh or #pwh < 1 or pwh == "!" or pwh == "x" then
593 return nil
594 else
595 return pwh
596 end
597 end
598
599 --- Test whether given string matches the password of a given system user.
600 -- @param username String containing the Unix user name
601 -- @param pass String containing the password to compare
602 -- @return Boolean indicating wheather the passwords are equal
603 function user.checkpasswd(username, pass)
604 local pwh = user.getpasswd(username)
605 if pwh and nixio.crypt(pass, pwh) ~= pwh then
606 return false
607 else
608 return true
609 end
610 end
611
612 --- Change the password of given user.
613 -- @param username String containing the Unix user name
614 -- @param password String containing the password to compare
615 -- @return Number containing 0 on success and >= 1 on error
616 function user.setpasswd(username, password)
617 if password then
618 password = password:gsub("'", [['"'"']])
619 end
620
621 if username then
622 username = username:gsub("'", [['"'"']])
623 end
624
625 return os.execute(
626 "(echo '" .. password .. "'; sleep 1; echo '" .. password .. "') | " ..
627 "passwd '" .. username .. "' >/dev/null 2>&1"
628 )
629 end
630
631
632 --- LuCI system utilities / wifi related functions.
633 -- @class module
634 -- @name luci.sys.wifi
635 wifi = {}
636
637 --- Get wireless information for given interface.
638 -- @param ifname String containing the interface name
639 -- @return A wrapped iwinfo object instance
640 function wifi.getiwinfo(ifname)
641 local stat, iwinfo = pcall(require, "iwinfo")
642
643 if ifname then
644 local c = 0
645 local u = uci.cursor_state()
646 local d, n = ifname:match("^(%w+)%.network(%d+)")
647 if d and n then
648 n = tonumber(n)
649 u:foreach("wireless", "wifi-iface",
650 function(s)
651 if s.device == d then
652 c = c + 1
653 if c == n then
654 ifname = s.ifname or s.device
655 return false
656 end
657 end
658 end)
659 elseif u:get("wireless", ifname) == "wifi-device" then
660 u:foreach("wireless", "wifi-iface",
661 function(s)
662 if s.device == ifname and s.ifname then
663 ifname = s.ifname
664 return false
665 end
666 end)
667 end
668
669 local t = stat and iwinfo.type(ifname)
670 local x = t and iwinfo[t] or { }
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
683 --- Get iwconfig output for all wireless devices.
684 -- @return Table of tables containing the iwconfing output for each wifi device
685 function wifi.getiwconfig()
686 local cnt = luci.util.exec("PATH=/sbin:/usr/sbin iwconfig 2>/dev/null")
687 local iwc = {}
688
689 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
690 local k = l:match("^(.-) ")
691 l = l:gsub("^(.-) +", "", 1)
692 if k then
693 local entry, flags = _parse_mixed_record(l)
694 if entry then
695 entry.flags = flags
696 end
697 iwc[k] = entry
698 end
699 end
700
701 return iwc
702 end
703
704 --- Get iwlist scan output from all wireless devices.
705 -- @return Table of tables contaiing all scan results
706 function wifi.iwscan(iface)
707 local siface = iface or ""
708 local cnt = luci.util.exec("iwlist "..siface.." scan 2>/dev/null")
709 local iws = {}
710
711 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
712 local k = l:match("^(.-) ")
713 l = l:gsub("^[^\n]+", "", 1)
714 l = luci.util.trim(l)
715 if k then
716 iws[k] = {}
717 for j, c in pairs(luci.util.split(l, "\n Cell")) do
718 c = c:gsub("^(.-)- ", "", 1)
719 c = luci.util.split(c, "\n", 7)
720 c = table.concat(c, "\n", 1)
721 local entry, flags = _parse_mixed_record(c)
722 if entry then
723 entry.flags = flags
724 end
725 table.insert(iws[k], entry)
726 end
727 end
728 end
729
730 return iface and (iws[iface] or {}) or iws
731 end
732
733 --- Get available channels from given wireless iface.
734 -- @param iface Wireless interface (optional)
735 -- @return Table of available channels
736 function wifi.channels(iface)
737 local stat, iwinfo = pcall(require, "iwinfo")
738 local cns
739
740 if stat then
741 local t = iwinfo.type(iface or "")
742 if iface and t and iwinfo[t] then
743 cns = iwinfo[t].freqlist(iface)
744 end
745 end
746
747 if not cns or #cns == 0 then
748 cns = {
749 {channel = 1, mhz = 2412},
750 {channel = 2, mhz = 2417},
751 {channel = 3, mhz = 2422},
752 {channel = 4, mhz = 2427},
753 {channel = 5, mhz = 2432},
754 {channel = 6, mhz = 2437},
755 {channel = 7, mhz = 2442},
756 {channel = 8, mhz = 2447},
757 {channel = 9, mhz = 2452},
758 {channel = 10, mhz = 2457},
759 {channel = 11, mhz = 2462}
760 }
761 end
762
763 return cns
764 end
765
766
767 --- LuCI system utilities / init related functions.
768 -- @class module
769 -- @name luci.sys.init
770 init = {}
771 init.dir = "/etc/init.d/"
772
773 --- Get the names of all installed init scripts
774 -- @return Table containing the names of all inistalled init scripts
775 function init.names()
776 local names = { }
777 for name in fs.glob(init.dir.."*") do
778 names[#names+1] = fs.basename(name)
779 end
780 return names
781 end
782
783 --- Test whether the given init script is enabled
784 -- @param name Name of the init script
785 -- @return Boolean indicating whether init is enabled
786 function init.enabled(name)
787 if fs.access(init.dir..name) then
788 return ( call(init.dir..name.." enabled") == 0 )
789 end
790 return false
791 end
792
793 --- Get the index of he given init script
794 -- @param name Name of the init script
795 -- @return Numeric index value
796 function init.index(name)
797 if fs.access(init.dir..name) then
798 return call("source "..init.dir..name.." enabled; exit $START")
799 end
800 end
801
802 --- Enable the given init script
803 -- @param name Name of the init script
804 -- @return Boolean indicating success
805 function init.enable(name)
806 if fs.access(init.dir..name) then
807 return ( call(init.dir..name.." enable") == 1 )
808 end
809 end
810
811 --- Disable the given init script
812 -- @param name Name of the init script
813 -- @return Boolean indicating success
814 function init.disable(name)
815 if fs.access(init.dir..name) then
816 return ( call(init.dir..name.." disable") == 0 )
817 end
818 end
819
820
821 -- Internal functions
822
823 function _parse_delimited_table(iter, delimiter, callback)
824 delimiter = delimiter or "%s+"
825
826 local data = {}
827 local trim = luci.util.trim
828 local split = luci.util.split
829
830 local keys = split(trim(iter()), delimiter, nil, true)
831 for i, j in pairs(keys) do
832 keys[i] = trim(keys[i])
833 end
834
835 for line in iter do
836 local row = {}
837 line = trim(line)
838 if #line > 0 then
839 for i, j in pairs(split(line, delimiter, nil, true)) do
840 if keys[i] then
841 row[keys[i]] = j
842 end
843 end
844 end
845
846 if callback then
847 callback(row)
848 else
849 data[#data+1] = row
850 end
851 end
852
853 return data
854 end
855
856 function _parse_mixed_record(cnt, delimiter)
857 delimiter = delimiter or " "
858 local data = {}
859 local flags = {}
860
861 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n")) do
862 for j, f in pairs(luci.util.split(luci.util.trim(l), delimiter, nil, true)) do
863 local k, x, v = f:match('([^%s][^:=]*) *([:=]*) *"*([^\n"]*)"*')
864
865 if k then
866 if x == "" then
867 table.insert(flags, k)
868 else
869 data[k] = v
870 end
871 end
872 end
873 end
874
875 return data, flags
876 end