libs/sys: disregard ::/0 routes on "lo" in luci.sys.net.defaultroute6()
[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 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 rt.device ~= "lo" and
306 (not route or route.metric > rt.metric)
307 then
308 route = rt
309 end
310 end)
311
312 if not route then
313 local global_unicast = luci.ip.IPv6("2000::/3")
314 net.routes6(function(rt)
315 if rt.dest:equal(global_unicast) and
316 (not route or route.metric > rt.metric)
317 then
318 route = rt
319 end
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 pwh and 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 return os.execute(
628 "(echo '" .. password .. "'; sleep 1; echo '" .. password .. "') | " ..
629 "passwd '" .. username .. "' >/dev/null 2>&1"
630 )
631 end
632
633
634 --- LuCI system utilities / wifi related functions.
635 -- @class module
636 -- @name luci.sys.wifi
637 wifi = {}
638
639 --- Get wireless information for given interface.
640 -- @param ifname String containing the interface name
641 -- @return A wrapped iwinfo object instance
642 function wifi.getiwinfo(ifname)
643 local stat, iwinfo = pcall(require, "iwinfo")
644
645 if ifname then
646 local c = 0
647 local u = uci.cursor_state()
648 local d, n = ifname:match("^(%w+)%.network(%d+)")
649 if d and n then
650 n = tonumber(n)
651 u:foreach("wireless", "wifi-iface",
652 function(s)
653 if s.device == d then
654 c = c + 1
655 if c == n then
656 ifname = s.ifname or s.device
657 return false
658 end
659 end
660 end)
661 elseif u:get("wireless", ifname) == "wifi-device" then
662 u:foreach("wireless", "wifi-iface",
663 function(s)
664 if s.device == ifname and s.ifname then
665 ifname = s.ifname
666 return false
667 end
668 end)
669 end
670
671 local t = stat and iwinfo.type(ifname)
672 local x = t and iwinfo[t] or { }
673 return setmetatable({}, {
674 __index = function(t, k)
675 if k == "ifname" then
676 return ifname
677 elseif x[k] then
678 return x[k](ifname)
679 end
680 end
681 })
682 end
683 end
684
685 --- Get iwconfig output for all wireless devices.
686 -- @return Table of tables containing the iwconfing output for each wifi device
687 function wifi.getiwconfig()
688 local cnt = luci.util.exec("PATH=/sbin:/usr/sbin iwconfig 2>/dev/null")
689 local iwc = {}
690
691 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
692 local k = l:match("^(.-) ")
693 l = l:gsub("^(.-) +", "", 1)
694 if k then
695 local entry, flags = _parse_mixed_record(l)
696 if entry then
697 entry.flags = flags
698 end
699 iwc[k] = entry
700 end
701 end
702
703 return iwc
704 end
705
706 --- Get iwlist scan output from all wireless devices.
707 -- @return Table of tables contaiing all scan results
708 function wifi.iwscan(iface)
709 local siface = iface or ""
710 local cnt = luci.util.exec("iwlist "..siface.." scan 2>/dev/null")
711 local iws = {}
712
713 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
714 local k = l:match("^(.-) ")
715 l = l:gsub("^[^\n]+", "", 1)
716 l = luci.util.trim(l)
717 if k then
718 iws[k] = {}
719 for j, c in pairs(luci.util.split(l, "\n Cell")) do
720 c = c:gsub("^(.-)- ", "", 1)
721 c = luci.util.split(c, "\n", 7)
722 c = table.concat(c, "\n", 1)
723 local entry, flags = _parse_mixed_record(c)
724 if entry then
725 entry.flags = flags
726 end
727 table.insert(iws[k], entry)
728 end
729 end
730 end
731
732 return iface and (iws[iface] or {}) or iws
733 end
734
735 --- Get available channels from given wireless iface.
736 -- @param iface Wireless interface (optional)
737 -- @return Table of available channels
738 function wifi.channels(iface)
739 local stat, iwinfo = pcall(require, "iwinfo")
740 local cns
741
742 if stat then
743 local t = iwinfo.type(iface or "")
744 if iface and t and iwinfo[t] then
745 cns = iwinfo[t].freqlist(iface)
746 end
747 end
748
749 if not cns or #cns == 0 then
750 cns = {
751 {channel = 1, mhz = 2412},
752 {channel = 2, mhz = 2417},
753 {channel = 3, mhz = 2422},
754 {channel = 4, mhz = 2427},
755 {channel = 5, mhz = 2432},
756 {channel = 6, mhz = 2437},
757 {channel = 7, mhz = 2442},
758 {channel = 8, mhz = 2447},
759 {channel = 9, mhz = 2452},
760 {channel = 10, mhz = 2457},
761 {channel = 11, mhz = 2462}
762 }
763 end
764
765 return cns
766 end
767
768
769 --- LuCI system utilities / init related functions.
770 -- @class module
771 -- @name luci.sys.init
772 init = {}
773 init.dir = "/etc/init.d/"
774
775 --- Get the names of all installed init scripts
776 -- @return Table containing the names of all inistalled init scripts
777 function init.names()
778 local names = { }
779 for name in fs.glob(init.dir.."*") do
780 names[#names+1] = fs.basename(name)
781 end
782 return names
783 end
784
785 --- Test whether the given init script is enabled
786 -- @param name Name of the init script
787 -- @return Boolean indicating whether init is enabled
788 function init.enabled(name)
789 if fs.access(init.dir..name) then
790 return ( call(init.dir..name.." enabled") == 0 )
791 end
792 return false
793 end
794
795 --- Get the index of he given init script
796 -- @param name Name of the init script
797 -- @return Numeric index value
798 function init.index(name)
799 if fs.access(init.dir..name) then
800 return call("source "..init.dir..name.." enabled; exit $START")
801 end
802 end
803
804 --- Enable the given init script
805 -- @param name Name of the init script
806 -- @return Boolean indicating success
807 function init.enable(name)
808 if fs.access(init.dir..name) then
809 return ( call(init.dir..name.." enable") == 1 )
810 end
811 end
812
813 --- Disable the given init script
814 -- @param name Name of the init script
815 -- @return Boolean indicating success
816 function init.disable(name)
817 if fs.access(init.dir..name) then
818 return ( call(init.dir..name.." disable") == 0 )
819 end
820 end
821
822
823 -- Internal functions
824
825 function _parse_delimited_table(iter, delimiter, callback)
826 delimiter = delimiter or "%s+"
827
828 local data = {}
829 local trim = luci.util.trim
830 local split = luci.util.split
831
832 local keys = split(trim(iter()), delimiter, nil, true)
833 for i, j in pairs(keys) do
834 keys[i] = trim(keys[i])
835 end
836
837 for line in iter do
838 local row = {}
839 line = trim(line)
840 if #line > 0 then
841 for i, j in pairs(split(line, delimiter, nil, true)) do
842 if keys[i] then
843 row[keys[i]] = j
844 end
845 end
846 end
847
848 if callback then
849 callback(row)
850 else
851 data[#data+1] = row
852 end
853 end
854
855 return data
856 end
857
858 function _parse_mixed_record(cnt, delimiter)
859 delimiter = delimiter or " "
860 local data = {}
861 local flags = {}
862
863 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n")) do
864 for j, f in pairs(luci.util.split(luci.util.trim(l), delimiter, nil, true)) do
865 local k, x, v = f:match('([^%s][^:=]*) *([:=]*) *"*([^\n"]*)"*')
866
867 if k then
868 if x == "" then
869 table.insert(flags, k)
870 else
871 data[k] = v
872 end
873 end
874 end
875 end
876
877 return data, flags
878 end