modules/freifunk: Make status page update dynamically with javascript and small cosme...
[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 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
193 end
194
195 --- Retrieves the output of the "logread" command.
196 -- @return String containing the current log buffer
197 function syslog()
198 return luci.util.exec("logread")
199 end
200
201 --- Retrieves the output of the "dmesg" command.
202 -- @return String containing the current log buffer
203 function dmesg()
204 return luci.util.exec("dmesg")
205 end
206
207 --- Generates a random id with specified length.
208 -- @param bytes Number of bytes for the unique id
209 -- @return String containing hex encoded id
210 function uniqueid(bytes)
211 local rand = fs.readfile("/dev/urandom", bytes)
212 return rand and nixio.bin.hexlify(rand)
213 end
214
215 --- Returns the current system uptime stats.
216 -- @return String containing total uptime in seconds
217 function uptime()
218 return nixio.sysinfo().uptime
219 end
220
221
222 --- LuCI system utilities / network related functions.
223 -- @class module
224 -- @name luci.sys.net
225 net = {}
226
227 --- Returns the current arp-table entries as two-dimensional table.
228 -- @return Table of table containing the current arp entries.
229 -- The following fields are defined for arp entry objects:
230 -- { "IP address", "HW address", "HW type", "Flags", "Mask", "Device" }
231 function net.arptable(callback)
232 return _parse_delimited_table(io.lines("/proc/net/arp"), "%s%s+", callback)
233 end
234
235 --- Returns conntrack information
236 -- @return Table with the currently tracked IP connections
237 function net.conntrack(callback)
238 local connt = {}
239 if fs.access("/proc/net/nf_conntrack", "r") then
240 for line in io.lines("/proc/net/nf_conntrack") do
241 line = line:match "^(.-( [^ =]+=).-)%2"
242 local entry, flags = _parse_mixed_record(line, " +")
243 entry.layer3 = flags[1]
244 entry.layer4 = flags[3]
245 for i=1, #entry do
246 entry[i] = nil
247 end
248
249 if callback then
250 callback(entry)
251 else
252 connt[#connt+1] = entry
253 end
254 end
255 elseif fs.access("/proc/net/ip_conntrack", "r") then
256 for line in io.lines("/proc/net/ip_conntrack") do
257 line = line:match "^(.-( [^ =]+=).-)%2"
258 local entry, flags = _parse_mixed_record(line, " +")
259 entry.layer3 = "ipv4"
260 entry.layer4 = flags[1]
261 for i=1, #entry do
262 entry[i] = nil
263 end
264
265 if callback then
266 callback(entry)
267 else
268 connt[#connt+1] = entry
269 end
270 end
271 else
272 return nil
273 end
274 return connt
275 end
276
277 --- Determine the current IPv4 default route. If multiple default routes exist,
278 -- return the one with the lowest metric.
279 -- @return Table with the properties of the current default route.
280 -- The following fields are defined:
281 -- { "dest", "gateway", "metric", "refcount", "usecount", "irtt",
282 -- "flags", "device" }
283 function net.defaultroute()
284 local route
285
286 net.routes(function(rt)
287 if rt.dest:prefix() == 0 and (not route or route.metric > rt.metric) then
288 route = rt
289 end
290 end)
291
292 return route
293 end
294
295 --- Determine the current IPv6 default route. If multiple default routes exist,
296 -- return the one with the lowest metric.
297 -- @return Table with the properties of the current default route.
298 -- The following fields are defined:
299 -- { "source", "dest", "nexthop", "metric", "refcount", "usecount",
300 -- "flags", "device" }
301 function net.defaultroute6()
302 local route
303
304 net.routes6(function(rt)
305 if rt.dest:prefix() == 0 and (not route or route.metric > rt.metric) then
306 route = rt
307 end
308 end)
309
310 return route
311 end
312
313 --- Determine the names of available network interfaces.
314 -- @return Table containing all current interface names
315 function 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
323 end
324
325
326 --- Return information about available network interfaces.
327 -- @return Table containing all current interface names and their information
328 function 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
353 end
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
359 function 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
367 end
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" }
374 function 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
414 end
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" }
421 function 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
470 end
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
475 function net.pingtest(host)
476 return os.execute("ping -c1 '"..host:gsub("'", '').."' >/dev/null 2>&1")
477 end
478
479
480 --- LuCI system utilities / process related functions.
481 -- @class module
482 -- @name luci.sys.process
483 process = {}
484
485 --- Get the current process id.
486 -- @class function
487 -- @name process.info
488 -- @return Number containing the current pid
489 function process.info(key)
490 local s = {uid = nixio.getuid(), gid = nixio.getgid()}
491 return not key and s or s[key]
492 end
493
494 --- Retrieve information about currently running processes.
495 -- @return Table containing process information
496 function 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
532 end
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
539 function process.setgroup(gid)
540 return nixio.setgid(gid)
541 end
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
548 function process.setuser(uid)
549 return nixio.setuid(uid)
550 end
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
559 process.signal = nixio.kill
560
561
562 --- LuCI system utilities / user related functions.
563 -- @class module
564 -- @name luci.sys.user
565 user = {}
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" }
573 user.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.
578 function 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
586 end
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
592 function 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
599 end
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
605 function 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)
617 end
618
619
620 --- LuCI system utilities / wifi related functions.
621 -- @class module
622 -- @name luci.sys.wifi
623 wifi = {}
624
625 --- Get wireless information for given interface.
626 -- @param ifname String containing the interface name
627 -- @return A wrapped iwinfo object instance
628 function 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
669 end
670
671 --- Get iwconfig output for all wireless devices.
672 -- @return Table of tables containing the iwconfing output for each wifi device
673 function 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
690 end
691
692 --- Get iwlist scan output from all wireless devices.
693 -- @return Table of tables contaiing all scan results
694 function 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
719 end
720
721 --- Get available channels from given wireless iface.
722 -- @param iface Wireless interface (optional)
723 -- @return Table of available channels
724 function 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
748 end
749
750
751 --- LuCI system utilities / init related functions.
752 -- @class module
753 -- @name luci.sys.init
754 init = {}
755 init.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
759 function 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
765 end
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
770 function init.enabled(name)
771 if fs.access(init.dir..name) then
772 return ( call(init.dir..name.." enabled") == 0 )
773 end
774 return false
775 end
776
777 --- Get the index of he given init script
778 -- @param name Name of the init script
779 -- @return Numeric index value
780 function init.index(name)
781 if fs.access(init.dir..name) then
782 return call("source "..init.dir..name.." enabled; exit $START")
783 end
784 end
785
786 --- Enable the given init script
787 -- @param name Name of the init script
788 -- @return Boolean indicating success
789 function init.enable(name)
790 if fs.access(init.dir..name) then
791 return ( call(init.dir..name.." enable") == 1 )
792 end
793 end
794
795 --- Disable the given init script
796 -- @param name Name of the init script
797 -- @return Boolean indicating success
798 function init.disable(name)
799 if fs.access(init.dir..name) then
800 return ( call(init.dir..name.." disable") == 0 )
801 end
802 end
803
804
805 -- Internal functions
806
807 function _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
838 end
839
840 function _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
860 end