Optimized conntrack (thanks to Joe Burpee)
[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         for _, r in pairs(net.routes6()) do
324                 if r.dest:prefix() == 0 and (not route or route.metric > r.metric) then
325                         route = r
326                 end
327         end
328         return route
329 end
330
331 --- Determine the names of available network interfaces.
332 -- @return      Table containing all current interface names
333 function net.devices()
334         local devices = {}
335         for line in io.lines("/proc/net/dev") do
336                 table.insert(devices, line:match(" *(.-):"))
337         end
338         return devices
339 end
340
341
342 --- Return information about available network interfaces.
343 -- @return      Table containing all current interface names and their information
344 function net.deviceinfo()
345         local devices = {}
346         for line in io.lines("/proc/net/dev") do
347                 local name, data = line:match("^ *(.-): *(.*)$")
348                 if name and data then
349                         devices[name] = luci.util.split(data, " +", nil, true)
350                 end
351         end
352         return devices
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
362         for i, l in ipairs(net.arptable()) do
363                 if l["IP address"] == ip then
364                         mac = l["HW address"]
365                 end
366         end
367
368         return mac
369 end
370
371 --- Returns the current kernel routing table entries.
372 -- @return      Table of tables with properties of the corresponding routes.
373 --                      The following fields are defined for route entry tables:
374 --                      { "dest", "gateway", "metric", "refcount", "usecount", "irtt",
375 --                        "flags", "device" }
376 function net.routes()
377         local routes = { }
378
379         for line in io.lines("/proc/net/route") do
380
381                 local dev, dst_ip, gateway, flags, refcnt, usecnt, metric,
382                           dst_mask, mtu, win, irtt = line:match(
383                         "([^%s]+)\t([A-F0-9]+)\t([A-F0-9]+)\t([A-F0-9]+)\t" ..
384                         "(%d+)\t(%d+)\t(%d+)\t([A-F0-9]+)\t(%d+)\t(%d+)\t(%d+)"
385                 )
386
387                 if dev then
388                         gateway  = luci.ip.Hex( gateway,  32, luci.ip.FAMILY_INET4 )
389                         dst_mask = luci.ip.Hex( dst_mask, 32, luci.ip.FAMILY_INET4 )
390                         dst_ip   = luci.ip.Hex(
391                                 dst_ip, dst_mask:prefix(dst_mask), luci.ip.FAMILY_INET4
392                         )
393
394                         routes[#routes+1] = {
395                                 dest     = dst_ip,
396                                 gateway  = gateway,
397                                 metric   = tonumber(metric),
398                                 refcount = tonumber(refcnt),
399                                 usecount = tonumber(usecnt),
400                                 mtu      = tonumber(mtu),
401                                 window   = tonumber(window),
402                                 irtt     = tonumber(irtt),
403                                 flags    = tonumber(flags, 16),
404                                 device   = dev
405                         }
406                 end
407         end
408
409         return routes
410 end
411
412 --- Returns the current ipv6 kernel routing table entries.
413 -- @return      Table of tables with properties of the corresponding routes.
414 --                      The following fields are defined for route entry tables:
415 --                      { "source", "dest", "nexthop", "metric", "refcount", "usecount",
416 --                        "flags", "device" }
417 function net.routes6()
418         local routes = { }
419
420         for line in io.lines("/proc/net/ipv6_route") do
421
422                 local dst_ip, dst_prefix, src_ip, src_prefix, nexthop,
423                           metric, refcnt, usecnt, flags, dev = line:match(
424                         "([a-f0-9]+) ([a-f0-9]+) " ..
425                         "([a-f0-9]+) ([a-f0-9]+) " ..
426                         "([a-f0-9]+) ([a-f0-9]+) " ..
427                         "([a-f0-9]+) ([a-f0-9]+) " ..
428                         "([a-f0-9]+) +([^%s]+)"
429                 )
430
431                 src_ip = luci.ip.Hex(
432                         src_ip, tonumber(src_prefix, 16), luci.ip.FAMILY_INET6, false
433                 )
434
435                 dst_ip = luci.ip.Hex(
436                         dst_ip, tonumber(dst_prefix, 16), luci.ip.FAMILY_INET6, false
437                 )
438
439                 nexthop = luci.ip.Hex( nexthop, 128, luci.ip.FAMILY_INET6, false )
440
441                 routes[#routes+1] = {
442                         source   = src_ip,
443                         dest     = dst_ip,
444                         nexthop  = nexthop,
445                         metric   = tonumber(metric, 16),
446                         refcount = tonumber(refcnt, 16),
447                         usecount = tonumber(usecnt, 16),
448                         flags    = tonumber(flags, 16),
449                         device   = dev
450                 }
451         end
452
453         return routes
454 end
455
456 --- Tests whether the given host responds to ping probes.
457 -- @param host  String containing a hostname or IPv4 address
458 -- @return              Number containing 0 on success and >= 1 on error
459 function net.pingtest(host)
460         return os.execute("ping -c1 '"..host:gsub("'", '').."' >/dev/null 2>&1")
461 end
462
463
464 --- LuCI system utilities / process related functions.
465 -- @class       module
466 -- @name        luci.sys.process
467 process = {}
468
469 --- Get the current process id.
470 -- @class function
471 -- @name  process.info
472 -- @return      Number containing the current pid
473 process.info = posix.getpid
474
475 --- Retrieve information about currently running processes.
476 -- @return      Table containing process information
477 function process.list()
478         local data = {}
479         local k
480         local ps = luci.util.execi("top -bn1")
481
482         if not ps then
483                 return
484         end
485
486         while true do
487                 local line = ps()
488                 if not line then
489                         return
490                 end
491
492                 k = luci.util.split(luci.util.trim(line), "%s+", nil, true)
493                 if k[1] == "PID" then
494                         break
495                 end
496         end
497
498         for line in ps do
499                 local row = {}
500
501                 line = luci.util.trim(line)
502                 for i, value in ipairs(luci.util.split(line, "%s+", #k-1, true)) do
503                         row[k[i]] = value
504                 end
505
506                 local pid = tonumber(row[k[1]])
507                 if pid then
508                         data[pid] = row
509                 end
510         end
511
512         return data
513 end
514
515 --- Set the gid of a process identified by given pid.
516 -- @param pid   Number containing the process id
517 -- @param gid   Number containing the Unix group id
518 -- @return              Boolean indicating successful operation
519 -- @return              String containing the error message if failed
520 -- @return              Number containing the error code if failed
521 function process.setgroup(pid, gid)
522         return posix.setpid("g", pid, gid)
523 end
524
525 --- Set the uid of a process identified by given pid.
526 -- @param pid   Number containing the process id
527 -- @param uid   Number containing the Unix user id
528 -- @return              Boolean indicating successful operation
529 -- @return              String containing the error message if failed
530 -- @return              Number containing the error code if failed
531 function process.setuser(pid, uid)
532         return posix.setpid("u", pid, uid)
533 end
534
535 --- Send a signal to a process identified by given pid.
536 -- @class function
537 -- @name  process.signal
538 -- @param pid   Number containing the process id
539 -- @param sig   Signal to send (default: 15 [SIGTERM])
540 -- @return              Boolean indicating successful operation
541 -- @return              Number containing the error code if failed
542 process.signal = posix.kill
543
544
545 --- LuCI system utilities / user related functions.
546 -- @class       module
547 -- @name        luci.sys.user
548 user = {}
549
550 --- Retrieve user informations for given uid.
551 -- @class               function
552 -- @name                getuser
553 -- @param uid   Number containing the Unix user id
554 -- @return              Table containing the following fields:
555 --                              { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" }
556 user.getuser = posix.getpasswd
557
558 --- Test whether given string matches the password of a given system user.
559 -- @param username      String containing the Unix user name
560 -- @param password      String containing the password to compare
561 -- @return                      Boolean indicating wheather the passwords are equal
562 function user.checkpasswd(username, password)
563         local account = user.getuser(username)
564
565         if account then
566                 local pwd = account.passwd
567                 local shadowpw
568                 if #pwd == 1 then
569                         if luci.fs.stat("/etc/shadow") then
570                                 if not pcall(function()
571                                         for l in io.lines("/etc/shadow") do
572                                                 shadowpw = l:match("^%s:([^:]+)" % username)
573                                                 if shadowpw then
574                                                         pwd = shadowpw
575                                                         break
576                                                 end
577                                         end
578                                 end) then
579                                         return nil, "Unable to access shadow-file"
580                                 end
581                         end
582
583                         if pwd == "!" then
584                                 return true
585                         end
586                 end
587
588                 if pwd and #pwd > 0 and password and #password > 0 then
589                         return (pwd == posix.crypt(password, pwd))
590                 end
591         end
592
593         return false
594 end
595
596 --- Change the password of given user.
597 -- @param username      String containing the Unix user name
598 -- @param password      String containing the password to compare
599 -- @return                      Number containing 0 on success and >= 1 on error
600 function user.setpasswd(username, password)
601         if password then
602                 password = password:gsub("'", "")
603         end
604
605         if username then
606                 username = username:gsub("'", "")
607         end
608
609         local cmd = "(echo '"..password.."';sleep 1;echo '"..password.."')|"
610         cmd = cmd .. "passwd '"..username.."' >/dev/null 2>&1"
611         return os.execute(cmd)
612 end
613
614
615 --- LuCI system utilities / wifi related functions.
616 -- @class       module
617 -- @name        luci.sys.wifi
618 wifi = {}
619
620 --- Get iwconfig output for all wireless devices.
621 -- @return      Table of tables containing the iwconfing output for each wifi device
622 function wifi.getiwconfig()
623         local cnt = luci.util.exec("/usr/sbin/iwconfig 2>/dev/null")
624         local iwc = {}
625
626         for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
627                 local k = l:match("^(.-) ")
628                 l = l:gsub("^(.-) +", "", 1)
629                 if k then
630                         local entry, flags = _parse_mixed_record(l)
631                         if entry then
632                                 entry.flags = flags
633                         end
634                         iwc[k] = entry
635                 end
636         end
637
638         return iwc
639 end
640
641 --- Get iwlist scan output from all wireless devices.
642 -- @return      Table of tables contaiing all scan results
643 function wifi.iwscan(iface)
644         local siface = iface or ""
645         local cnt = luci.util.exec("iwlist "..siface.." scan 2>/dev/null")
646         local iws = {}
647
648         for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
649                 local k = l:match("^(.-) ")
650                 l = l:gsub("^[^\n]+", "", 1)
651                 l = luci.util.trim(l)
652                 if k then
653                         iws[k] = {}
654                         for j, c in pairs(luci.util.split(l, "\n          Cell")) do
655                                 c = c:gsub("^(.-)- ", "", 1)
656                                 c = luci.util.split(c, "\n", 7)
657                                 c = table.concat(c, "\n", 1)
658                                 local entry, flags = _parse_mixed_record(c)
659                                 if entry then
660                                         entry.flags = flags
661                                 end
662                                 table.insert(iws[k], entry)
663                         end
664                 end
665         end
666
667         return iface and (iws[iface] or {}) or iws
668 end
669
670
671 --- LuCI system utilities / init related functions.
672 -- @class       module
673 -- @name        luci.sys.init
674 init = {}
675 init.dir = "/etc/init.d/"
676
677 --- Get the names of all installed init scripts
678 -- @return      Table containing the names of all inistalled init scripts
679 function init.names()
680         local names = { }
681         for _, name in ipairs(luci.fs.glob(init.dir.."*")) do
682                 names[#names+1] = luci.fs.basename(name)
683         end
684         return names
685 end
686
687 --- Test whether the given init script is enabled
688 -- @param name  Name of the init script
689 -- @return              Boolean indicating whether init is enabled
690 function init.enabled(name)
691         if luci.fs.access(init.dir..name) then
692                 return ( call(init.dir..name.." enabled") == 0 )
693         end
694         return false
695 end
696
697 --- Get the index of he given init script
698 -- @param name  Name of the init script
699 -- @return              Numeric index value
700 function init.index(name)
701         if luci.fs.access(init.dir..name) then
702                 return call("source "..init.dir..name.."; exit $START")
703         end
704 end
705
706 --- Enable the given init script
707 -- @param name  Name of the init script
708 -- @return              Boolean indicating success
709 function init.enable(name)
710         if luci.fs.access(init.dir..name) then
711                 return ( call(init.dir..name.." enable") == 1 )
712         end
713 end
714
715 --- Disable the given init script
716 -- @param name  Name of the init script
717 -- @return              Boolean indicating success
718 function init.disable(name)
719         if luci.fs.access(init.dir..name) then
720                 return ( call(init.dir..name.." disable") == 0 )
721         end
722 end
723
724
725 -- Internal functions
726
727 function _parse_delimited_table(iter, delimiter)
728         delimiter = delimiter or "%s+"
729
730         local data  = {}
731         local trim  = luci.util.trim
732         local split = luci.util.split
733
734         local keys = split(trim(iter()), delimiter, nil, true)
735         for i, j in pairs(keys) do
736                 keys[i] = trim(keys[i])
737         end
738
739         for line in iter do
740                 local row = {}
741                 line = trim(line)
742                 if #line > 0 then
743                         for i, j in pairs(split(line, delimiter, nil, true)) do
744                                 if keys[i] then
745                                         row[keys[i]] = j
746                                 end
747                         end
748                 end
749                 table.insert(data, row)
750         end
751
752         return data
753 end
754
755 function _parse_mixed_record(cnt, delimiter)
756         delimiter = delimiter or "  "
757         local data = {}
758         local flags = {}
759
760         for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n")) do
761                 for j, f in pairs(luci.util.split(luci.util.trim(l), delimiter, nil, true)) do
762                         local k, x, v = f:match('([^%s][^:=]*) *([:=]*) *"*([^\n"]*)"*')
763
764                         if k then
765                                 if x == "" then
766                                         table.insert(flags, k)
767                                 else
768                                         data[k] = v
769                                 end
770                         end
771                 end
772         end
773
774         return data, flags
775 end