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