root/luci/branches/luci-0.10/libs/sys/luasrc/sys.lua @ 6693

Revision 6650, 22.1 KB (checked in by soma, 2 years ago)

modules/freifunk: Make status page update dynamically with javascript and small cosmetic changes.

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