root/luci/trunk/libs/sys/luasrc/sys.lua @ 8673

Revision 8673, 23.1 KB (checked in by jow, 13 months ago)

libs/sys: fix luci.sys.init.index() (#399)

  • Property svn:keywords set to Id
Line 
1--[[
2LuCI - System library
3
4Description:
5Utilities for interaction with the Linux system
6
7FileId:
8$Id$
9
10License:
11Copyright 2008 Steven Barth <steven@midlink.org>
12
13Licensed under the Apache License, Version 2.0 (the "License");
14you may not use this file except in compliance with the License.
15You may obtain a copy of the License at
16
17    http://www.apache.org/licenses/LICENSE-2.0
18
19Unless required by applicable law or agreed to in writing, software
20distributed under the License is distributed on an "AS IS" BASIS,
21WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22See the License for the specific language governing permissions and
23limitations under the License.
24
25]]--
26
27
28local io     = require "io"
29local os     = require "os"
30local table  = require "table"
31local nixio  = require "nixio"
32local fs     = require "nixio.fs"
33local uci    = require "luci.model.uci"
34
35local luci  = {}
36luci.util   = require "luci.util"
37luci.ip     = require "luci.ip"
38
39local 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.
44module "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
51function call(...)
52    return os.execute(...) / 256
53end
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
60exec = luci.util.exec
61
62--- Retrieve information about currently mounted file systems.
63-- @return  Table containing mount information
64function 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
107end
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
118getenv = 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
123function 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
130end
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
137function 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
145end
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
151function loadavg()
152    local info = nixio.sysinfo()
153    return info.loads[1], info.loads[2], info.loads[3]
154end
155
156--- Initiate a system reboot.
157-- @return  Return value of os.execute()
158function reboot()
159    return os.execute("reboot >/dev/null 2>&1")
160end
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)
170function 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
194end
195
196--- Retrieves the output of the "logread" command.
197-- @return  String containing the current log buffer
198function syslog()
199    return luci.util.exec("logread")
200end
201
202--- Retrieves the output of the "dmesg" command.
203-- @return  String containing the current log buffer
204function dmesg()
205    return luci.util.exec("dmesg")
206end
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
211function uniqueid(bytes)
212    local rand = fs.readfile("/dev/urandom", bytes)
213    return rand and nixio.bin.hexlify(rand)
214end
215
216--- Returns the current system uptime stats.
217-- @return  String containing total uptime in seconds
218function uptime()
219    return nixio.sysinfo().uptime
220end
221
222
223--- LuCI system utilities / network related functions.
224-- @class   module
225-- @name    luci.sys.net
226net = {}
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" }
232function net.arptable(callback)
233    return _parse_delimited_table(io.lines("/proc/net/arp"), "%s%s+", callback)
234end
235
236--- Returns conntrack information
237-- @return  Table with the currently tracked IP connections
238function 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
280end
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" }
288function 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
298end
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" }
306function 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
329end
330
331--- Determine the names of available network interfaces.
332-- @return  Table containing all current interface names
333function 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
341end
342
343
344--- Return information about available network interfaces.
345-- @return  Table containing all current interface names and their information
346function 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
371end
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
377function 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
385end
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" }
392function 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
432end
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" }
439function 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
488end
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
493function net.pingtest(host)
494    return os.execute("ping -c1 '"..host:gsub("'", '').."' >/dev/null 2>&1")
495end
496
497
498--- LuCI system utilities / process related functions.
499-- @class   module
500-- @name    luci.sys.process
501process = {}
502
503--- Get the current process id.
504-- @class function
505-- @name  process.info
506-- @return  Number containing the current pid
507function process.info(key)
508    local s = {uid = nixio.getuid(), gid = nixio.getgid()}
509    return not key and s or s[key]
510end
511
512--- Retrieve information about currently running processes.
513-- @return  Table containing process information
514function 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
553end
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
560function process.setgroup(gid)
561    return nixio.setgid(gid)
562end
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
569function process.setuser(uid)
570    return nixio.setuid(uid)
571end
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
580process.signal = nixio.kill
581
582
583--- LuCI system utilities / user related functions.
584-- @class   module
585-- @name    luci.sys.user
586user = {}
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" }
594user.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.
599function user.getpasswd(username)
600    local pwe = nixio.getsp and nixio.getsp(username) or nixio.getpw(username)
601    local pwh = pwe and (pwe.pwdp or pwe.passwd)
602    if not pwh or #pwh < 1 or pwh == "!" or pwh == "x" then
603        return nil
604    else
605        return pwh
606    end
607end
608
609--- Test whether given string matches the password of a given system user.
610-- @param username  String containing the Unix user name
611-- @param pass      String containing the password to compare
612-- @return          Boolean indicating wheather the passwords are equal
613function user.checkpasswd(username, pass)
614    local pwh = user.getpasswd(username)
615    if pwh and nixio.crypt(pass, pwh) ~= pwh then
616        return false
617    else
618        return true
619    end
620end
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
626function 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    )
639end
640
641
642--- LuCI system utilities / wifi related functions.
643-- @class   module
644-- @name    luci.sys.wifi
645wifi = {}
646
647--- Get wireless information for given interface.
648-- @param ifname        String containing the interface name
649-- @return              A wrapped iwinfo object instance
650function 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
691end
692
693--- Get iwconfig output for all wireless devices.
694-- @return  Table of tables containing the iwconfing output for each wifi device
695function 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
712end
713
714--- Get iwlist scan output from all wireless devices.
715-- @return  Table of tables contaiing all scan results
716function 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
741end
742
743--- Get available channels from given wireless iface.
744-- @param iface Wireless interface (optional)
745-- @return      Table of available channels
746function 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
774end
775
776
777--- LuCI system utilities / init related functions.
778-- @class   module
779-- @name    luci.sys.init
780init = {}
781init.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
785function 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
791end
792
793--- Get the index of he given init script
794-- @param name  Name of the init script
795-- @return      Numeric index value
796function 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
801end
802
803local 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
807end
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
812function init.enabled(name)
813    return (init_action("enabled", name) == 0)
814end
815
816--- Enable the given init script
817-- @param name  Name of the init script
818-- @return      Boolean indicating success
819function init.enable(name)
820    return (init_action("enable", name) == 1)
821end
822
823--- Disable the given init script
824-- @param name  Name of the init script
825-- @return      Boolean indicating success
826function init.disable(name)
827    return (init_action("disable", name) == 0)
828end
829
830--- Start the given init script
831-- @param name  Name of the init script
832-- @return      Boolean indicating success
833function init.start(name)
834    return (init_action("start", name) == 0)
835end
836
837--- Stop the given init script
838-- @param name  Name of the init script
839-- @return      Boolean indicating success
840function init.stop(name)
841    return (init_action("stop", name) == 0)
842end
843
844
845-- Internal functions
846
847function _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
878end
879
880function _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
900end
Note: See TracBrowser for help on using the browser.