Don't fail if we do not have v6 support
[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 posix = require "posix"
31 local table = require "table"
32
33 local luci  = {}
34 luci.util   = require "luci.util"
35 luci.fs     = require "luci.fs"
36 luci.ip     = require "luci.ip"
37
38 local tonumber, ipairs, pairs, pcall = tonumber, ipairs, pairs, pcall
39
40
41 --- LuCI Linux and POSIX system utilities.
42 module "luci.sys"
43
44 --- Execute a given shell command and return the error code
45 -- @class               function
46 -- @name                call
47 -- @param               ...             Command to call
48 -- @return              Error code of the command
49 function call(...)
50         return os.execute(...) / 256
51 end
52
53 --- Execute a given shell command and capture its standard output
54 -- @class               function
55 -- @name                exec
56 -- @param command       Command to call
57 -- @return                      String containg the return the output of the command
58 exec = luci.util.exec
59
60 --- Invoke the luci-flash executable to write an image to the flash memory.
61 -- @param image         Local path or URL to image file
62 -- @param kpattern      Pattern of files to keep over flash process
63 -- @return                      Return value of os.execute()
64 function flash(image, kpattern)
65         local cmd = "luci-flash "
66         if kpattern then
67                 cmd = cmd .. "-k '" .. kpattern:gsub("'", "") .. "' "
68         end
69         cmd = cmd .. "'" .. image:gsub("'", "") .. "' >/dev/null 2>&1"
70
71         return os.execute(cmd)
72 end
73
74 --- Retrieve information about currently mounted file systems.
75 -- @return      Table containing mount information
76 function mounts()
77         local data = {}
78         local k = {"fs", "blocks", "used", "available", "percent", "mountpoint"}
79         local ps = luci.util.execi("df")
80
81         if not ps then
82                 return
83         else
84                 ps()
85         end
86
87         for line in ps do
88                 local row = {}
89
90                 local j = 1
91                 for value in line:gmatch("[^%s]+") do
92                         row[k[j]] = value
93                         j = j + 1
94                 end
95
96                 if row[k[1]] then
97
98                         -- this is a rather ugly workaround to cope with wrapped lines in
99                         -- the df output:
100                         --
101                         --      /dev/scsi/host0/bus0/target0/lun0/part3
102                         --                   114382024  93566472  15005244  86% /mnt/usb
103                         --
104
105                         if not row[k[2]] then
106                                 j = 2
107                                 line = ps()
108                                 for value in line:gmatch("[^%s]+") do
109                                         row[k[j]] = value
110                                         j = j + 1
111                                 end
112                         end
113
114                         table.insert(data, row)
115                 end
116         end
117
118         return data
119 end
120
121 --- Retrieve environment variables. If no variable is given then a table
122 -- containing the whole environment is returned otherwise this function returns
123 -- the corresponding string value for the given name or nil if no such variable
124 -- exists.
125 -- @class               function
126 -- @name                getenv
127 -- @param var   Name of the environment variable to retrieve (optional)
128 -- @return              String containg the value of the specified variable
129 -- @return              Table containing all variables if no variable name is given
130 getenv = posix.getenv
131
132 --- Determine the current hostname.
133 -- @return              String containing the system hostname
134 function hostname()
135         return posix.uname("%n")
136 end
137
138 --- Returns the contents of a documented referred by an URL.
139 -- @param url    The URL to retrieve
140 -- @param stream Return a stream instead of a buffer
141 -- @param target Directly write to target file name
142 -- @return              String containing the contents of given the URL
143 function httpget(url, stream, target)
144         if not target then
145                 local source = stream and io.popen or luci.util.exec
146                 return source("wget -qO- '"..url:gsub("'", "").."'")
147         else
148                 return os.execute("wget -qO '%s' '%s'" %
149                         {target:gsub("'", ""), url:gsub("'", "")})
150         end
151 end
152
153 --- Returns the system load average values.
154 -- @return      String containing the average load value 1 minute ago
155 -- @return      String containing the average load value 5 minutes ago
156 -- @return      String containing the average load value 15 minutes ago
157 -- @return      String containing the active and total number of processes
158 -- @return      String containing the last used pid
159 function loadavg()
160         local loadavg = io.lines("/proc/loadavg")()
161         return loadavg:match("^(.-) (.-) (.-) (.-) (.-)$")
162 end
163
164 --- Initiate a system reboot.
165 -- @return      Return value of os.execute()
166 function reboot()
167         return os.execute("reboot >/dev/null 2>&1")
168 end
169
170 --- Returns the system type, cpu name and installed physical memory.
171 -- @return      String containing the system or platform identifier
172 -- @return      String containing hardware model information
173 -- @return      String containing the total memory amount in kB
174 -- @return      String containing the memory used for caching in kB
175 -- @return      String containing the memory used for buffering in kB
176 -- @return      String containing the free memory amount in kB
177 function sysinfo()
178         local cpuinfo = luci.fs.readfile("/proc/cpuinfo")
179         local meminfo = luci.fs.readfile("/proc/meminfo")
180
181         local system = cpuinfo:match("system typ.-:%s*([^\n]+)")
182         local model = ""
183         local memtotal = tonumber(meminfo:match("MemTotal:%s*(%d+)"))
184         local memcached = tonumber(meminfo:match("\nCached:%s*(%d+)"))
185         local memfree = tonumber(meminfo:match("MemFree:%s*(%d+)"))
186         local membuffers = tonumber(meminfo:match("Buffers:%s*(%d+)"))
187
188         if not system then
189                 system = posix.uname("%m")
190                 model = cpuinfo:match("model name.-:%s*([^\n]+)")
191                 if not model then
192                         model = cpuinfo:match("Processor.-:%s*([^\n]+)")
193                 end
194         else
195                 model = cpuinfo:match("cpu model.-:%s*([^\n]+)")
196         end
197
198         return system, model, memtotal, memcached, membuffers, memfree
199 end
200
201 --- Retrieves the output of the "logread" command.
202 -- @return      String containing the current log buffer
203 function syslog()
204         return luci.util.exec("logread")
205 end
206
207 --- Retrieves the output of the "dmesg" command.
208 -- @return      String containing the current log buffer
209 function dmesg()
210         return luci.util.exec("dmesg")
211 end
212
213 --- Generates a random id with specified length.
214 -- @param bytes Number of bytes for the unique id
215 -- @return              String containing hex encoded id
216 function uniqueid(bytes)
217         local fp    = io.open("/dev/urandom")
218         local chunk = { fp:read(bytes):byte(1, bytes) }
219         fp:close()
220
221         local hex = ""
222
223         local pattern = "%02X"
224         for i, byte in ipairs(chunk) do
225                 hex = hex .. pattern:format(byte)
226         end
227
228         return hex
229 end
230
231 --- Returns the current system uptime stats.
232 -- @return      String containing total uptime in seconds
233 -- @return      String containing idle time in seconds
234 function uptime()
235         local loadavg = io.lines("/proc/uptime")()
236         return loadavg:match("^(.-) (.-)$")
237 end
238
239 --- LuCI system utilities / POSIX user group related functions.
240 -- @class       module
241 -- @name        luci.sys.group
242 group = {}
243
244 --- Returns information about a POSIX user group.
245 -- @class function
246 -- @name                getgroup
247 -- @param group Group ID or name of a system user group
248 -- @return      Table with information about the requested group
249 group.getgroup = posix.getgroup
250
251
252 --- LuCI system utilities / network related functions.
253 -- @class       module
254 -- @name        luci.sys.net
255 net = {}
256
257 --- Returns the current arp-table entries as two-dimensional table.
258 -- @return      Table of table containing the current arp entries.
259 --                      The following fields are defined for arp entry objects:
260 --                      { "IP address", "HW address", "HW type", "Flags", "Mask", "Device" }
261 function net.arptable()
262         return _parse_delimited_table(io.lines("/proc/net/arp"), "%s%s+")
263 end
264
265 --- Returns conntrack information
266 -- @return      Table with the currently tracked IP connections
267 function net.conntrack()
268         local connt = {}
269         if luci.fs.access("/proc/net/nf_conntrack", "r") then
270                 for line in io.lines("/proc/net/nf_conntrack") do
271                         line = line:match "^(.-( [^ =]+=).-)%2"
272                         local entry, flags = _parse_mixed_record(line, " +")
273                         entry.layer3 = flags[1]
274                         entry.layer4 = flags[3]
275                         for i=1, #entry do
276                                 entry[i] = nil
277                         end
278
279                         connt[#connt+1] = entry
280                 end
281         elseif luci.fs.access("/proc/net/ip_conntrack", "r") then
282                 for line in io.lines("/proc/net/ip_conntrack") do
283                         line = line:match "^(.-( [^ =]+=).-)%2"
284                         local entry, flags = _parse_mixed_record(line, " +")
285                         entry.layer3 = "ipv4"
286                         entry.layer4 = flags[1]
287                         for i=1, #entry do
288                                 entry[i] = nil
289                         end
290
291                         connt[#connt+1] = entry
292                 end
293         else
294                 return nil
295         end
296         return connt
297 end
298
299 --- Determine the current IPv4 default route. If multiple default routes exist,
300 -- return the one with the lowest metric.
301 -- @return      Table with the properties of the current default route.
302 --                      The following fields are defined:
303 --                      { "dest", "gateway", "metric", "refcount", "usecount", "irtt",
304 --                        "flags", "device" }
305 function net.defaultroute()
306         local route = nil
307         for _, r in pairs(net.routes()) do
308                 if r.dest:prefix() == 0 and (not route or route.metric > r.metric) then
309                         route = r
310                 end
311         end
312         return route
313 end
314
315 --- Determine the current IPv6 default route. If multiple default routes exist,
316 -- return the one with the lowest metric.
317 -- @return      Table with the properties of the current default route.
318 --                      The following fields are defined:
319 --                      { "source", "dest", "nexthop", "metric", "refcount", "usecount",
320 --                        "flags", "device" }
321 function net.defaultroute6()
322         local route = nil
323         local routes6 = net.routes6()
324         if not routes6 then
325                 return nil
326         end
327         for _, r in pairs(routes6) do
328                 if r.dest:prefix() == 0 and (not route or route.metric > r.metric) then
329                         route = r
330                 end
331         end
332         return route
333 end
334
335 --- Determine the names of available network interfaces.
336 -- @return      Table containing all current interface names
337 function net.devices()
338         local devices = {}
339         for line in io.lines("/proc/net/dev") do
340                 table.insert(devices, line:match(" *(.-):"))
341         end
342         return devices
343 end
344
345
346 --- Return information about available network interfaces.
347 -- @return      Table containing all current interface names and their information
348 function net.deviceinfo()
349         local devices = {}
350         for line in io.lines("/proc/net/dev") do
351                 local name, data = line:match("^ *(.-): *(.*)$")
352                 if name and data then
353                         devices[name] = luci.util.split(data, " +", nil, true)
354                 end
355         end
356         return devices
357 end
358
359
360 -- Determine the MAC address belonging to the given IP address.
361 -- @param ip    IPv4 address
362 -- @return              String containing the MAC address or nil if it cannot be found
363 function net.ip4mac(ip)
364         local mac = nil
365
366         for i, l in ipairs(net.arptable()) do
367                 if l["IP address"] == ip then
368                         mac = l["HW address"]
369                 end
370         end
371
372         return mac
373 end
374
375 --- Returns the current kernel routing table entries.
376 -- @return      Table of tables with properties of the corresponding routes.
377 --                      The following fields are defined for route entry tables:
378 --                      { "dest", "gateway", "metric", "refcount", "usecount", "irtt",
379 --                        "flags", "device" }
380 function net.routes()
381         local routes = { }
382
383         for line in io.lines("/proc/net/route") do
384
385                 local dev, dst_ip, gateway, flags, refcnt, usecnt, metric,
386                           dst_mask, mtu, win, irtt = line:match(
387                         "([^%s]+)\t([A-F0-9]+)\t([A-F0-9]+)\t([A-F0-9]+)\t" ..
388                         "(%d+)\t(%d+)\t(%d+)\t([A-F0-9]+)\t(%d+)\t(%d+)\t(%d+)"
389                 )
390
391                 if dev then
392                         gateway  = luci.ip.Hex( gateway,  32, luci.ip.FAMILY_INET4 )
393                         dst_mask = luci.ip.Hex( dst_mask, 32, luci.ip.FAMILY_INET4 )
394                         dst_ip   = luci.ip.Hex(
395                                 dst_ip, dst_mask:prefix(dst_mask), luci.ip.FAMILY_INET4
396                         )
397
398                         routes[#routes+1] = {
399                                 dest     = dst_ip,
400                                 gateway  = gateway,
401                                 metric   = tonumber(metric),
402                                 refcount = tonumber(refcnt),
403                                 usecount = tonumber(usecnt),
404                                 mtu      = tonumber(mtu),
405                                 window   = tonumber(window),
406                                 irtt     = tonumber(irtt),
407                                 flags    = tonumber(flags, 16),
408                                 device   = dev
409                         }
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()
422         local routes = { }
423
424         if not luci.fs.access("/proc/net/ipv6_route", "r") then
425                 return nil
426         end
427
428         for line in io.lines("/proc/net/ipv6_route") do
429
430                 local dst_ip, dst_prefix, src_ip, src_prefix, nexthop,
431                           metric, refcnt, usecnt, flags, dev = line:match(
432                         "([a-f0-9]+) ([a-f0-9]+) " ..
433                         "([a-f0-9]+) ([a-f0-9]+) " ..
434                         "([a-f0-9]+) ([a-f0-9]+) " ..
435                         "([a-f0-9]+) ([a-f0-9]+) " ..
436                         "([a-f0-9]+) +([^%s]+)"
437                 )
438
439                 src_ip = luci.ip.Hex(
440                         src_ip, tonumber(src_prefix, 16), luci.ip.FAMILY_INET6, false
441                 )
442
443                 dst_ip = luci.ip.Hex(
444                         dst_ip, tonumber(dst_prefix, 16), luci.ip.FAMILY_INET6, false
445                 )
446
447                 nexthop = luci.ip.Hex( nexthop, 128, luci.ip.FAMILY_INET6, false )
448
449                 routes[#routes+1] = {
450                         source   = src_ip,
451                         dest     = dst_ip,
452                         nexthop  = nexthop,
453                         metric   = tonumber(metric, 16),
454                         refcount = tonumber(refcnt, 16),
455                         usecount = tonumber(usecnt, 16),
456                         flags    = tonumber(flags, 16),
457                         device   = dev
458                 }
459         end
460
461         return routes
462 end
463
464 --- Tests whether the given host responds to ping probes.
465 -- @param host  String containing a hostname or IPv4 address
466 -- @return              Number containing 0 on success and >= 1 on error
467 function net.pingtest(host)
468         return os.execute("ping -c1 '"..host:gsub("'", '').."' >/dev/null 2>&1")
469 end
470
471
472 --- LuCI system utilities / process related functions.
473 -- @class       module
474 -- @name        luci.sys.process
475 process = {}
476
477 --- Get the current process id.
478 -- @class function
479 -- @name  process.info
480 -- @return      Number containing the current pid
481 process.info = posix.getpid
482
483 --- Retrieve information about currently running processes.
484 -- @return      Table containing process information
485 function process.list()
486         local data = {}
487         local k
488         local ps = luci.util.execi("top -bn1")
489
490         if not ps then
491                 return
492         end
493
494         while true do
495                 local line = ps()
496                 if not line then
497                         return
498                 end
499
500                 k = luci.util.split(luci.util.trim(line), "%s+", nil, true)
501                 if k[1] == "PID" then
502                         break
503                 end
504         end
505
506         for line in ps do
507                 local row = {}
508
509                 line = luci.util.trim(line)
510                 for i, value in ipairs(luci.util.split(line, "%s+", #k-1, true)) do
511                         row[k[i]] = value
512                 end
513
514                 local pid = tonumber(row[k[1]])
515                 if pid then
516                         data[pid] = row
517                 end
518         end
519
520         return data
521 end
522
523 --- Set the gid of a process identified by given pid.
524 -- @param pid   Number containing the process id
525 -- @param gid   Number containing the Unix group id
526 -- @return              Boolean indicating successful operation
527 -- @return              String containing the error message if failed
528 -- @return              Number containing the error code if failed
529 function process.setgroup(pid, gid)
530         return posix.setpid("g", pid, gid)
531 end
532
533 --- Set the uid of a process identified by given pid.
534 -- @param pid   Number containing the process id
535 -- @param uid   Number containing the Unix user 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.setuser(pid, uid)
540         return posix.setpid("u", pid, uid)
541 end
542
543 --- Send a signal to a process identified by given pid.
544 -- @class function
545 -- @name  process.signal
546 -- @param pid   Number containing the process id
547 -- @param sig   Signal to send (default: 15 [SIGTERM])
548 -- @return              Boolean indicating successful operation
549 -- @return              Number containing the error code if failed
550 process.signal = posix.kill
551
552
553 --- LuCI system utilities / user related functions.
554 -- @class       module
555 -- @name        luci.sys.user
556 user = {}
557
558 --- Retrieve user informations for given uid.
559 -- @class               function
560 -- @name                getuser
561 -- @param uid   Number containing the Unix user id
562 -- @return              Table containing the following fields:
563 --                              { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" }
564 user.getuser = posix.getpasswd
565
566 --- Test whether given string matches the password of a given system user.
567 -- @param username      String containing the Unix user name
568 -- @param password      String containing the password to compare
569 -- @return                      Boolean indicating wheather the passwords are equal
570 function user.checkpasswd(username, password)
571         local account = user.getuser(username)
572
573         if account then
574                 local pwd = account.passwd
575                 local shadowpw
576                 if #pwd == 1 then
577                         if luci.fs.stat("/etc/shadow") then
578                                 if not pcall(function()
579                                         for l in io.lines("/etc/shadow") do
580                                                 shadowpw = l:match("^%s:([^:]+)" % username)
581                                                 if shadowpw then
582                                                         pwd = shadowpw
583                                                         break
584                                                 end
585                                         end
586                                 end) then
587                                         return nil, "Unable to access shadow-file"
588                                 end
589                         end
590
591                         if pwd == "!" then
592                                 return true
593                         end
594                 end
595
596                 if pwd and #pwd > 0 and password and #password > 0 then
597                         return (pwd == posix.crypt(password, pwd))
598                 end
599         end
600
601         return false
602 end
603
604 --- Change the password of given user.
605 -- @param username      String containing the Unix user name
606 -- @param password      String containing the password to compare
607 -- @return                      Number containing 0 on success and >= 1 on error
608 function user.setpasswd(username, password)
609         if password then
610                 password = password:gsub("'", "")
611         end
612
613         if username then
614                 username = username:gsub("'", "")
615         end
616
617         local cmd = "(echo '"..password.."';sleep 1;echo '"..password.."')|"
618         cmd = cmd .. "passwd '"..username.."' >/dev/null 2>&1"
619         return os.execute(cmd)
620 end
621
622
623 --- LuCI system utilities / wifi related functions.
624 -- @class       module
625 -- @name        luci.sys.wifi
626 wifi = {}
627
628 --- Get iwconfig output for all wireless devices.
629 -- @return      Table of tables containing the iwconfing output for each wifi device
630 function wifi.getiwconfig()
631         local cnt = luci.util.exec("/usr/sbin/iwconfig 2>/dev/null")
632         local iwc = {}
633
634         for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
635                 local k = l:match("^(.-) ")
636                 l = l:gsub("^(.-) +", "", 1)
637                 if k then
638                         local entry, flags = _parse_mixed_record(l)
639                         if entry then
640                                 entry.flags = flags
641                         end
642                         iwc[k] = entry
643                 end
644         end
645
646         return iwc
647 end
648
649 --- Get iwlist scan output from all wireless devices.
650 -- @return      Table of tables contaiing all scan results
651 function wifi.iwscan(iface)
652         local siface = iface or ""
653         local cnt = luci.util.exec("iwlist "..siface.." scan 2>/dev/null")
654         local iws = {}
655
656         for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
657                 local k = l:match("^(.-) ")
658                 l = l:gsub("^[^\n]+", "", 1)
659                 l = luci.util.trim(l)
660                 if k then
661                         iws[k] = {}
662                         for j, c in pairs(luci.util.split(l, "\n          Cell")) do
663                                 c = c:gsub("^(.-)- ", "", 1)
664                                 c = luci.util.split(c, "\n", 7)
665                                 c = table.concat(c, "\n", 1)
666                                 local entry, flags = _parse_mixed_record(c)
667                                 if entry then
668                                         entry.flags = flags
669                                 end
670                                 table.insert(iws[k], entry)
671                         end
672                 end
673         end
674
675         return iface and (iws[iface] or {}) or iws
676 end
677
678
679 --- LuCI system utilities / init related functions.
680 -- @class       module
681 -- @name        luci.sys.init
682 init = {}
683 init.dir = "/etc/init.d/"
684
685 --- Get the names of all installed init scripts
686 -- @return      Table containing the names of all inistalled init scripts
687 function init.names()
688         local names = { }
689         for _, name in ipairs(luci.fs.glob(init.dir.."*")) do
690                 names[#names+1] = luci.fs.basename(name)
691         end
692         return names
693 end
694
695 --- Test whether the given init script is enabled
696 -- @param name  Name of the init script
697 -- @return              Boolean indicating whether init is enabled
698 function init.enabled(name)
699         if luci.fs.access(init.dir..name) then
700                 return ( call(init.dir..name.." enabled") == 0 )
701         end
702         return false
703 end
704
705 --- Get the index of he given init script
706 -- @param name  Name of the init script
707 -- @return              Numeric index value
708 function init.index(name)
709         if luci.fs.access(init.dir..name) then
710                 return call("source "..init.dir..name.."; exit $START")
711         end
712 end
713
714 --- Enable the given init script
715 -- @param name  Name of the init script
716 -- @return              Boolean indicating success
717 function init.enable(name)
718         if luci.fs.access(init.dir..name) then
719                 return ( call(init.dir..name.." enable") == 1 )
720         end
721 end
722
723 --- Disable the given init script
724 -- @param name  Name of the init script
725 -- @return              Boolean indicating success
726 function init.disable(name)
727         if luci.fs.access(init.dir..name) then
728                 return ( call(init.dir..name.." disable") == 0 )
729         end
730 end
731
732
733 -- Internal functions
734
735 function _parse_delimited_table(iter, delimiter)
736         delimiter = delimiter or "%s+"
737
738         local data  = {}
739         local trim  = luci.util.trim
740         local split = luci.util.split
741
742         local keys = split(trim(iter()), delimiter, nil, true)
743         for i, j in pairs(keys) do
744                 keys[i] = trim(keys[i])
745         end
746
747         for line in iter do
748                 local row = {}
749                 line = trim(line)
750                 if #line > 0 then
751                         for i, j in pairs(split(line, delimiter, nil, true)) do
752                                 if keys[i] then
753                                         row[keys[i]] = j
754                                 end
755                         end
756                 end
757                 table.insert(data, row)
758         end
759
760         return data
761 end
762
763 function _parse_mixed_record(cnt, delimiter)
764         delimiter = delimiter or "  "
765         local data = {}
766         local flags = {}
767
768         for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n")) do
769                 for j, f in pairs(luci.util.split(luci.util.trim(l), delimiter, nil, true)) do
770                         local k, x, v = f:match('([^%s][^:=]*) *([:=]*) *"*([^\n"]*)"*')
771
772                         if k then
773                                 if x == "" then
774                                         table.insert(flags, k)
775                                 else
776                                         data[k] = v
777                                 end
778                         end
779                 end
780         end
781
782         return data, flags
783 end