5 Utilities for interaction with the Linux system
11 Copyright 2008 Steven Barth <steven@midlink.org>
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
17 http://www.apache.org/licenses/LICENSE-2.0
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.
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"
36 luci.util = require "luci.util"
37 luci.ip = require "luci.ip"
39 local tonumber, ipairs, pairs, pcall, type, next, setmetatable, require, select =
40 tonumber, ipairs, pairs, pcall, type, next, setmetatable, require, select
43 --- LuCI Linux and POSIX system utilities.
46 --- Execute a given shell command and return the error code
49 -- @param ... Command to call
50 -- @return Error code of the command
52 return os.execute(...) / 256
55 --- Execute a given shell command and capture its standard output
58 -- @param command Command to call
59 -- @return String containg the return the output of the command
62 --- Retrieve information about currently mounted file systems.
63 -- @return Table containing mount information
66 local k = {"fs", "blocks", "used", "available", "percent", "mountpoint"}
67 local ps = luci.util.execi("df")
79 for value in line:gmatch("[^%s]+") do
86 -- this is a rather ugly workaround to cope with wrapped lines in
89 -- /dev/scsi/host0/bus0/target0/lun0/part3
90 -- 114382024 93566472 15005244 86% /mnt/usb
96 for value in line:gmatch("[^%s]+") do
102 table.insert(data, row)
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
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
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 )
128 return nixio.uname().nodename
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)
139 local source = stream and io.popen or luci.util.exec
140 return source("wget -qO- '"..url:gsub("'", "").."'")
142 return os.execute("wget -qO '%s' '%s'" %
143 {target:gsub("'", ""), url:gsub("'", "")})
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
152 local info = nixio.sysinfo()
153 return info.loads[1], info.loads[2], info.loads[3]
156 --- Initiate a system reboot.
157 -- @return Return value of os.execute()
159 return os.execute("reboot >/dev/null 2>&1")
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)
171 local cpuinfo = fs.readfile("/proc/cpuinfo")
172 local meminfo = fs.readfile("/proc/meminfo")
174 local memtotal = tonumber(meminfo:match("MemTotal:%s*(%d+)"))
175 local memcached = tonumber(meminfo:match("\nCached:%s*(%d+)"))
176 local memfree = tonumber(meminfo:match("MemFree:%s*(%d+)"))
177 local membuffers = tonumber(meminfo:match("Buffers:%s*(%d+)"))
178 local bogomips = tonumber(cpuinfo:match("[Bb]ogo[Mm][Ii][Pp][Ss].-: ([^\n]+)")) or 0
181 cpuinfo:match("system type\t+: ([^\n]+)") or
182 cpuinfo:match("Processor\t+: ([^\n]+)") or
183 cpuinfo:match("model name\t+: ([^\n]+)")
186 luci.util.pcdata(fs.readfile("/tmp/sysinfo/model")) or
187 cpuinfo:match("machine\t+: ([^\n]+)") or
188 cpuinfo:match("Hardware\t+: ([^\n]+)") or
189 luci.util.pcdata(fs.readfile("/proc/diag/model")) or
190 nixio.uname().machine or
193 return system, model, memtotal, memcached, membuffers, memfree, bogomips
196 --- Retrieves the output of the "logread" command.
197 -- @return String containing the current log buffer
199 return luci.util.exec("logread")
202 --- Retrieves the output of the "dmesg" command.
203 -- @return String containing the current log buffer
205 return luci.util.exec("dmesg")
208 --- Generates a random id with specified length.
209 -- @param bytes Number of bytes for the unique id
210 -- @return String containing hex encoded id
211 function uniqueid(bytes)
212 local rand = fs.readfile("/dev/urandom", bytes)
213 return rand and nixio.bin.hexlify(rand)
216 --- Returns the current system uptime stats.
217 -- @return String containing total uptime in seconds
219 return nixio.sysinfo().uptime
223 --- LuCI system utilities / network related functions.
225 -- @name luci.sys.net
228 --- Returns the current arp-table entries as two-dimensional table.
229 -- @return Table of table containing the current arp entries.
230 -- The following fields are defined for arp entry objects:
231 -- { "IP address", "HW address", "HW type", "Flags", "Mask", "Device" }
232 function net.arptable(callback)
233 return _parse_delimited_table(io.lines("/proc/net/arp"), "%s%s+", callback)
236 local function _nethints(what, callback)
237 local _, k, e, mac, ip, name
241 local function _add(i, ...)
242 local k = select(i, ...)
244 if not hosts[k] then hosts[k] = { } end
245 hosts[k][1] = select(1, ...) or hosts[k][1]
246 hosts[k][2] = select(2, ...) or hosts[k][2]
247 hosts[k][3] = select(3, ...) or hosts[k][3]
248 hosts[k][4] = select(4, ...) or hosts[k][4]
252 if fs.access("/proc/net/arp") then
253 for e in io.lines("/proc/net/arp") do
254 ip, mac = e:match("^([%d%.]+)%s+%S+%s+%S+%s+([a-fA-F0-9:]+)%s+")
256 _add(what, mac:upper(), ip, nil, nil)
261 if fs.access("/etc/ethers") then
262 for e in io.lines("/etc/ethers") do
263 mac, ip = e:match("^([a-f0-9]%S+) (%S+)")
265 _add(what, mac:upper(), ip, nil, nil)
270 if fs.access("/var/dhcp.leases") then
271 for e in io.lines("/var/dhcp.leases") do
272 mac, ip, name = e:match("^%d+ (%S+) (%S+) (%S+)")
274 _add(what, mac:upper(), ip, nil, name ~= "*" and name)
279 for _, e in ipairs(nixio.getifaddrs()) do
280 if e.name ~= "lo" then
281 ifn[e.name] = ifn[e.name] or { }
282 if e.family == "packet" and e.addr and #e.addr == 17 then
283 ifn[e.name][1] = e.addr:upper()
284 elseif e.family == "inet" then
285 ifn[e.name][2] = e.addr
286 elseif e.family == "inet6" then
287 ifn[e.name][3] = e.addr
292 for _, e in pairs(ifn) do
293 if e[what] and (e[2] or e[3]) then
294 _add(what, e[1], e[2], e[3], e[4])
298 for _, e in luci.util.kspairs(hosts) do
299 callback(e[1], e[2], e[3], e[4])
303 --- Returns a two-dimensional table of mac address hints.
304 -- @return Table of table containing known hosts from various sources.
305 -- Each entry contains the values in the following order:
307 function net.mac_hints(callback)
309 _nethints(1, function(mac, v4, v6, name)
310 name = name or nixio.getnameinfo(v4 or v6) or v4
311 if name and name ~= mac then
312 callback(mac, name or nixio.getnameinfo(v4 or v6) or v4)
317 _nethints(1, function(mac, v4, v6, name)
318 name = name or nixio.getnameinfo(v4 or v6) or v4
319 if name and name ~= mac then
320 rv[#rv+1] = { mac, name or nixio.getnameinfo(v4 or v6) or v4 }
327 --- Returns a two-dimensional table of IPv4 address hints.
328 -- @return Table of table containing known hosts from various sources.
329 -- Each entry contains the values in the following order:
331 function net.ipv4_hints(callback)
333 _nethints(2, function(mac, v4, v6, name)
334 name = name or nixio.getnameinfo(v4) or mac
335 if name and name ~= v4 then
341 _nethints(2, function(mac, v4, v6, name)
342 name = name or nixio.getnameinfo(v4) or mac
343 if name and name ~= v4 then
344 rv[#rv+1] = { v4, name }
351 --- Returns a two-dimensional table of IPv6 address hints.
352 -- @return Table of table containing known hosts from various sources.
353 -- Each entry contains the values in the following order:
355 function net.ipv6_hints(callback)
357 _nethints(3, function(mac, v4, v6, name)
358 name = name or nixio.getnameinfo(v6) or mac
359 if name and name ~= v6 then
365 _nethints(3, function(mac, v4, v6, name)
366 name = name or nixio.getnameinfo(v6) or mac
367 if name and name ~= v6 then
368 rv[#rv+1] = { v6, name }
375 --- Returns conntrack information
376 -- @return Table with the currently tracked IP connections
377 function net.conntrack(callback)
379 if fs.access("/proc/net/nf_conntrack", "r") then
380 for line in io.lines("/proc/net/nf_conntrack") do
381 line = line:match "^(.-( [^ =]+=).-)%2"
382 local entry, flags = _parse_mixed_record(line, " +")
383 if flags[6] ~= "TIME_WAIT" then
384 entry.layer3 = flags[1]
385 entry.layer4 = flags[3]
393 connt[#connt+1] = entry
397 elseif fs.access("/proc/net/ip_conntrack", "r") then
398 for line in io.lines("/proc/net/ip_conntrack") do
399 line = line:match "^(.-( [^ =]+=).-)%2"
400 local entry, flags = _parse_mixed_record(line, " +")
401 if flags[4] ~= "TIME_WAIT" then
402 entry.layer3 = "ipv4"
403 entry.layer4 = flags[1]
411 connt[#connt+1] = entry
421 --- Determine the current IPv4 default route. If multiple default routes exist,
422 -- return the one with the lowest metric.
423 -- @return Table with the properties of the current default route.
424 -- The following fields are defined:
425 -- { "dest", "gateway", "metric", "refcount", "usecount", "irtt",
426 -- "flags", "device" }
427 function net.defaultroute()
430 net.routes(function(rt)
431 if rt.dest:prefix() == 0 and (not route or route.metric > rt.metric) then
439 --- Determine the current IPv6 default route. If multiple default routes exist,
440 -- return the one with the lowest metric.
441 -- @return Table with the properties of the current default route.
442 -- The following fields are defined:
443 -- { "source", "dest", "nexthop", "metric", "refcount", "usecount",
444 -- "flags", "device" }
445 function net.defaultroute6()
448 net.routes6(function(rt)
449 if rt.dest:prefix() == 0 and rt.device ~= "lo" and
450 (not route or route.metric > rt.metric)
457 local global_unicast = luci.ip.IPv6("2000::/3")
458 net.routes6(function(rt)
459 if rt.dest:equal(global_unicast) and
460 (not route or route.metric > rt.metric)
470 --- Determine the names of available network interfaces.
471 -- @return Table containing all current interface names
472 function net.devices()
474 for k, v in ipairs(nixio.getifaddrs()) do
475 if v.family == "packet" then
476 devs[#devs+1] = v.name
483 --- Return information about available network interfaces.
484 -- @return Table containing all current interface names and their information
485 function net.deviceinfo()
487 for k, v in ipairs(nixio.getifaddrs()) do
488 if v.family == "packet" then
513 -- Determine the MAC address belonging to the given IP address.
514 -- @param ip IPv4 address
515 -- @return String containing the MAC address or nil if it cannot be found
516 function net.ip4mac(ip)
518 net.arptable(function(e)
519 if e["IP address"] == ip then
520 mac = e["HW address"]
526 --- Returns the current kernel routing table entries.
527 -- @return Table of tables with properties of the corresponding routes.
528 -- The following fields are defined for route entry tables:
529 -- { "dest", "gateway", "metric", "refcount", "usecount", "irtt",
530 -- "flags", "device" }
531 function net.routes(callback)
534 for line in io.lines("/proc/net/route") do
536 local dev, dst_ip, gateway, flags, refcnt, usecnt, metric,
537 dst_mask, mtu, win, irtt = line:match(
538 "([^%s]+)\t([A-F0-9]+)\t([A-F0-9]+)\t([A-F0-9]+)\t" ..
539 "(%d+)\t(%d+)\t(%d+)\t([A-F0-9]+)\t(%d+)\t(%d+)\t(%d+)"
543 gateway = luci.ip.Hex( gateway, 32, luci.ip.FAMILY_INET4 )
544 dst_mask = luci.ip.Hex( dst_mask, 32, luci.ip.FAMILY_INET4 )
545 dst_ip = luci.ip.Hex(
546 dst_ip, dst_mask:prefix(dst_mask), luci.ip.FAMILY_INET4
552 metric = tonumber(metric),
553 refcount = tonumber(refcnt),
554 usecount = tonumber(usecnt),
556 window = tonumber(window),
557 irtt = tonumber(irtt),
558 flags = tonumber(flags, 16),
565 routes[#routes+1] = rt
573 --- Returns the current ipv6 kernel routing table entries.
574 -- @return Table of tables with properties of the corresponding routes.
575 -- The following fields are defined for route entry tables:
576 -- { "source", "dest", "nexthop", "metric", "refcount", "usecount",
577 -- "flags", "device" }
578 function net.routes6(callback)
579 if fs.access("/proc/net/ipv6_route", "r") then
582 for line in io.lines("/proc/net/ipv6_route") do
584 local dst_ip, dst_prefix, src_ip, src_prefix, nexthop,
585 metric, refcnt, usecnt, flags, dev = line:match(
586 "([a-f0-9]+) ([a-f0-9]+) " ..
587 "([a-f0-9]+) ([a-f0-9]+) " ..
588 "([a-f0-9]+) ([a-f0-9]+) " ..
589 "([a-f0-9]+) ([a-f0-9]+) " ..
590 "([a-f0-9]+) +([^%s]+)"
593 if dst_ip and dst_prefix and
594 src_ip and src_prefix and
595 nexthop and metric and
596 refcnt and usecnt and
599 src_ip = luci.ip.Hex(
600 src_ip, tonumber(src_prefix, 16), luci.ip.FAMILY_INET6, false
603 dst_ip = luci.ip.Hex(
604 dst_ip, tonumber(dst_prefix, 16), luci.ip.FAMILY_INET6, false
607 nexthop = luci.ip.Hex( nexthop, 128, luci.ip.FAMILY_INET6, false )
613 metric = tonumber(metric, 16),
614 refcount = tonumber(refcnt, 16),
615 usecount = tonumber(usecnt, 16),
616 flags = tonumber(flags, 16),
619 -- lua number is too small for storing the metric
620 -- add a metric_raw field with the original content
627 routes[#routes+1] = rt
636 --- Tests whether the given host responds to ping probes.
637 -- @param host String containing a hostname or IPv4 address
638 -- @return Number containing 0 on success and >= 1 on error
639 function net.pingtest(host)
640 return os.execute("ping -c1 '"..host:gsub("'", '').."' >/dev/null 2>&1")
644 --- LuCI system utilities / process related functions.
646 -- @name luci.sys.process
649 --- Get the current process id.
651 -- @name process.info
652 -- @return Number containing the current pid
653 function process.info(key)
654 local s = {uid = nixio.getuid(), gid = nixio.getgid()}
655 return not key and s or s[key]
658 --- Retrieve information about currently running processes.
659 -- @return Table containing process information
660 function process.list()
663 local ps = luci.util.execi("top -bn1")
675 k = luci.util.split(luci.util.trim(line), "%s+", nil, true)
676 if k[6] == "%VSZ" then
679 if k[1] == "PID" then
687 line = luci.util.trim(line)
688 for i, value in ipairs(luci.util.split(line, "%s+", #k-1, true)) do
692 local pid = tonumber(row[k[1]])
701 --- Set the gid of a process identified by given pid.
702 -- @param gid Number containing the Unix group id
703 -- @return Boolean indicating successful operation
704 -- @return String containing the error message if failed
705 -- @return Number containing the error code if failed
706 function process.setgroup(gid)
707 return nixio.setgid(gid)
710 --- Set the uid of a process identified by given pid.
711 -- @param uid Number containing the Unix user id
712 -- @return Boolean indicating successful operation
713 -- @return String containing the error message if failed
714 -- @return Number containing the error code if failed
715 function process.setuser(uid)
716 return nixio.setuid(uid)
719 --- Send a signal to a process identified by given pid.
721 -- @name process.signal
722 -- @param pid Number containing the process id
723 -- @param sig Signal to send (default: 15 [SIGTERM])
724 -- @return Boolean indicating successful operation
725 -- @return Number containing the error code if failed
726 process.signal = nixio.kill
729 --- LuCI system utilities / user related functions.
731 -- @name luci.sys.user
734 --- Retrieve user informations for given uid.
737 -- @param uid Number containing the Unix user id
738 -- @return Table containing the following fields:
739 -- { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" }
740 user.getuser = nixio.getpw
742 --- Retrieve the current user password hash.
743 -- @param username String containing the username to retrieve the password for
744 -- @return String containing the hash or nil if no password is set.
745 -- @return Password database entry
746 function user.getpasswd(username)
747 local pwe = nixio.getsp and nixio.getsp(username) or nixio.getpw(username)
748 local pwh = pwe and (pwe.pwdp or pwe.passwd)
749 if not pwh or #pwh < 1 or pwh == "!" or pwh == "x" then
756 --- Test whether given string matches the password of a given system user.
757 -- @param username String containing the Unix user name
758 -- @param pass String containing the password to compare
759 -- @return Boolean indicating wheather the passwords are equal
760 function user.checkpasswd(username, pass)
761 local pwh, pwe = user.getpasswd(username)
763 return (pwh == nil or nixio.crypt(pass, pwh) == pwh)
768 --- Change the password of given user.
769 -- @param username String containing the Unix user name
770 -- @param password String containing the password to compare
771 -- @return Number containing 0 on success and >= 1 on error
772 function user.setpasswd(username, password)
774 password = password:gsub("'", [['"'"']])
778 username = username:gsub("'", [['"'"']])
782 "(echo '" .. password .. "'; sleep 1; echo '" .. password .. "') | " ..
783 "passwd '" .. username .. "' >/dev/null 2>&1"
788 --- LuCI system utilities / wifi related functions.
790 -- @name luci.sys.wifi
793 --- Get wireless information for given interface.
794 -- @param ifname String containing the interface name
795 -- @return A wrapped iwinfo object instance
796 function wifi.getiwinfo(ifname)
797 local stat, iwinfo = pcall(require, "iwinfo")
801 local u = uci.cursor_state()
802 local d, n = ifname:match("^(%w+)%.network(%d+)")
805 u:foreach("wireless", "wifi-iface",
807 if s.device == d then
810 ifname = s.ifname or s.device
815 elseif u:get("wireless", ifname) == "wifi-device" then
816 u:foreach("wireless", "wifi-iface",
818 if s.device == ifname and s.ifname then
825 local t = stat and iwinfo.type(ifname)
826 local x = t and iwinfo[t] or { }
827 return setmetatable({}, {
828 __index = function(t, k)
829 if k == "ifname" then
839 --- Get iwconfig output for all wireless devices.
840 -- @return Table of tables containing the iwconfing output for each wifi device
841 function wifi.getiwconfig()
842 local cnt = luci.util.exec("PATH=/sbin:/usr/sbin iwconfig 2>/dev/null")
845 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
846 local k = l:match("^(.-) ")
847 l = l:gsub("^(.-) +", "", 1)
849 local entry, flags = _parse_mixed_record(l)
860 --- Get iwlist scan output from all wireless devices.
861 -- @return Table of tables contaiing all scan results
862 function wifi.iwscan(iface)
863 local siface = iface or ""
864 local cnt = luci.util.exec("iwlist "..siface.." scan 2>/dev/null")
867 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do
868 local k = l:match("^(.-) ")
869 l = l:gsub("^[^\n]+", "", 1)
870 l = luci.util.trim(l)
873 for j, c in pairs(luci.util.split(l, "\n Cell")) do
874 c = c:gsub("^(.-)- ", "", 1)
875 c = luci.util.split(c, "\n", 7)
876 c = table.concat(c, "\n", 1)
877 local entry, flags = _parse_mixed_record(c)
881 table.insert(iws[k], entry)
886 return iface and (iws[iface] or {}) or iws
889 --- Get available channels from given wireless iface.
890 -- @param iface Wireless interface (optional)
891 -- @return Table of available channels
892 function wifi.channels(iface)
893 local stat, iwinfo = pcall(require, "iwinfo")
897 local t = iwinfo.type(iface or "")
898 if iface and t and iwinfo[t] then
899 cns = iwinfo[t].freqlist(iface)
903 if not cns or #cns == 0 then
905 {channel = 1, mhz = 2412},
906 {channel = 2, mhz = 2417},
907 {channel = 3, mhz = 2422},
908 {channel = 4, mhz = 2427},
909 {channel = 5, mhz = 2432},
910 {channel = 6, mhz = 2437},
911 {channel = 7, mhz = 2442},
912 {channel = 8, mhz = 2447},
913 {channel = 9, mhz = 2452},
914 {channel = 10, mhz = 2457},
915 {channel = 11, mhz = 2462}
923 --- LuCI system utilities / init related functions.
925 -- @name luci.sys.init
927 init.dir = "/etc/init.d/"
929 --- Get the names of all installed init scripts
930 -- @return Table containing the names of all inistalled init scripts
931 function init.names()
933 for name in fs.glob(init.dir.."*") do
934 names[#names+1] = fs.basename(name)
939 --- Get the index of he given init script
940 -- @param name Name of the init script
941 -- @return Numeric index value
942 function init.index(name)
943 if fs.access(init.dir..name) then
944 return call("env -i sh -c 'source %s%s enabled; exit ${START:-255}' >/dev/null"
949 local function init_action(action, name)
950 if fs.access(init.dir..name) then
951 return call("env -i %s%s %s >/dev/null" %{ init.dir, name, action })
955 --- Test whether the given init script is enabled
956 -- @param name Name of the init script
957 -- @return Boolean indicating whether init is enabled
958 function init.enabled(name)
959 return (init_action("enabled", name) == 0)
962 --- Enable the given init script
963 -- @param name Name of the init script
964 -- @return Boolean indicating success
965 function init.enable(name)
966 return (init_action("enable", name) == 1)
969 --- Disable the given init script
970 -- @param name Name of the init script
971 -- @return Boolean indicating success
972 function init.disable(name)
973 return (init_action("disable", name) == 0)
976 --- Start the given init script
977 -- @param name Name of the init script
978 -- @return Boolean indicating success
979 function init.start(name)
980 return (init_action("start", name) == 0)
983 --- Stop the given init script
984 -- @param name Name of the init script
985 -- @return Boolean indicating success
986 function init.stop(name)
987 return (init_action("stop", name) == 0)
991 -- Internal functions
993 function _parse_delimited_table(iter, delimiter, callback)
994 delimiter = delimiter or "%s+"
997 local trim = luci.util.trim
998 local split = luci.util.split
1000 local keys = split(trim(iter()), delimiter, nil, true)
1001 for i, j in pairs(keys) do
1002 keys[i] = trim(keys[i])
1009 for i, j in pairs(split(line, delimiter, nil, true)) do
1026 function _parse_mixed_record(cnt, delimiter)
1027 delimiter = delimiter or " "
1031 for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n")) do
1032 for j, f in pairs(luci.util.split(luci.util.trim(l), delimiter, nil, true)) do
1033 local k, x, v = f:match('([^%s][^:=]*) *([:=]*) *"*([^\n"]*)"*')
1037 table.insert(flags, k)