]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
replace sandbox table_print with new pretty printer
authorPetr Špaček <petr.spacek@nic.cz>
Mon, 20 Jul 2020 07:46:10 +0000 (09:46 +0200)
committerPetr Špaček <petr.spacek@nic.cz>
Mon, 2 Nov 2020 09:28:54 +0000 (10:28 +0100)
This slightly changes table_print() output format.
table_print() output is not intended for machine consumption,
use krprint.serialize_lua() or JSON for that purpose.

Output from table_print is now a valid Lua expression
if the input contains only serializable data types
(number, string, bool, nil, table), which is nice for copy&pasting.

Functions etc. are also pretty-printed but cannot be deserialized.
Numbers are pretty-printed as well so their precision is reduced (as
compared to krprint.serialize_lua).

daemon/lua/controlsock.test.lua
daemon/lua/sandbox.lua.in
doc/upgrading.rst
tests/config/basic.test.lua

index 53650e0148fdb4b01916834322fb7ef22593a996..e3679dc889bba3bc62ef85c61c33cb3d98913fed 100644 (file)
@@ -51,10 +51,12 @@ local function test_text_prompt()
 end
 
 local function test_text_single_command()
-       local expect = "this is test"
-       ctrl_sock_txt:xwrite(string.format('"%s"\n', expect), nil, timeout)
-       data = ctrl_sock_txt:xread(#expect + 2, nil, timeout)
-       same(data, expect .. '\n\n',
+       local string = "this is test"
+       local input = string.format("'%s'\n", string)
+       local expect = input
+       ctrl_sock_txt:xwrite(input, nil, timeout)
+       data = ctrl_sock_txt:xread(#expect, nil, timeout)
+       same(data, expect,
                'text mode returns output in expected format')
 end
 
@@ -76,24 +78,28 @@ local function test_binary_more_syscalls()
        ctrl_sock_bin:xwrite('id\n', nil, timeout)
        len = binary_xread_len(ctrl_sock_bin)
        data = ctrl_sock_bin:xread(len, nil, timeout)
-       same(data, worker.pid..'\n', 'binary mode returns output in expected format')
+       same(data, tostring(worker.pid),
+               'binary mode returns number in expected format')
 
        ctrl_sock_bin:xwrite('worker.p', nil, timeout)
        worker.sleep(0.01)
        ctrl_sock_bin:xwrite('id\nworker.id\n', nil, timeout)
        len = binary_xread_len(ctrl_sock_bin)
        data = ctrl_sock_bin:xread(len, nil, timeout)
-       same(data, worker.pid..'\n', 'binary mode returns output in expected format')
+       same(data, tostring(worker.pid),
+               'binary mode returns number in expected format')
        len = binary_xread_len(ctrl_sock_bin)
        data = ctrl_sock_bin:xread(len, nil, timeout)
-       same(data, worker.id..'\n', 'binary mode returns output in expected format')
+       same(data, string.format("'%s'", worker.id),
+               'binary mode returns string in expected format')
 
        ctrl_sock_bin:xwrite('worker.pid', nil, timeout)
        worker.sleep(0.01)
        ctrl_sock_bin:xwrite('\n', nil, timeout)
        len = binary_xread_len(ctrl_sock_bin)
        data = ctrl_sock_bin:xread(len, nil, timeout)
-       same(data, worker.pid..'\n', 'binary mode returns output in expected format')
+       same(data, tostring(worker.pid),
+               'binary mode returns output in expected format')
 
        ctrl_sock_bin:xwrite('worker.pid', nil, timeout)
        worker.sleep(0.01)
@@ -102,24 +108,30 @@ local function test_binary_more_syscalls()
        ctrl_sock_bin:xwrite('\n', nil, timeout)
        len = binary_xread_len(ctrl_sock_bin)
        data = ctrl_sock_bin:xread(len, nil, timeout)
-       same(data, worker.pid..'\n', 'binary mode returns output in expected format')
+       same(data, tostring(worker.pid),
+               'binary mode returns number in expected format')
        len = binary_xread_len(ctrl_sock_bin)
        data = ctrl_sock_bin:xread(len, nil, timeout)
-       same(data, worker.id..'\n', 'binary mode returns output in expected format')
+       same(data, string.format("'%s'", worker.id),
+               'binary mode returns string in expected format')
 
        ctrl_sock_bin:xwrite('worker.pid\nworker.pid\nworker.pid\nworker.pid\n', nil, timeout)
        len = binary_xread_len(ctrl_sock_bin)
        data = ctrl_sock_bin:xread(len, nil, timeout)
-       same(data, worker.pid..'\n', 'binary mode returns output in expected format')
+       same(data, tostring(worker.pid),
+               'binary mode returns number in expected format')
        len = binary_xread_len(ctrl_sock_bin)
        data = ctrl_sock_bin:xread(len, nil, timeout)
-       same(data, worker.pid..'\n', 'binary mode returns output in expected format')
+       same(data, tostring(worker.pid),
+               'binary mode returns number in expected format')
        len = binary_xread_len(ctrl_sock_bin)
        data = ctrl_sock_bin:xread(len, nil, timeout)
-       same(data, worker.pid..'\n', 'binary mode returns output in expected format')
+       same(data, tostring(worker.pid),
+               'binary mode returns number in expected format')
        len = binary_xread_len(ctrl_sock_bin)
        data = ctrl_sock_bin:xread(len, nil, timeout)
-       same(data, worker.pid..'\n', 'binary mode returns output in expected format')
+       same(data, tostring(worker.pid),
+               'binary mode returns number in expected format')
 end
 
 local function test_close_incomplete_cmd()
index dfbf815be7cdb174036dea73b2a6ba76fa18d228..87132bb3ff5ccb76f687d82b8b73e114d9679210 100644 (file)
@@ -476,113 +476,7 @@ function eval_cmd(line, raw)
 end
 
 -- Pretty printing
-
-local function funcsign(f)
--- thanks to AnandA777 from StackOverflow! Function funcsign is adapted version of
--- https://stackoverflow.com/questions/51095022/inspect-function-signature-in-lua-5-1
-       assert(type(f) == 'function', "bad argument #1 to 'funcsign' (function expected)")
-       local debuginfo = debug.getinfo(f)
-       if debuginfo.what == 'C' then  -- names N/A
-               return '(?)'
-       end
-
-       local func_args = {}
-       pcall(function()
-               local oldhook
-               local delay = 2
-               local function hook()
-                       delay = delay - 1
-                       if delay == 0 then  -- call this only for the introspected function
-                               -- stack depth 2 is the introspected function
-                               for i = 1, debuginfo.nparams do
-                                       local k = debug.getlocal(2, i)
-                                       table.insert(func_args, k)
-                               end
-                               if debuginfo.isvararg then
-                                       table.insert(func_args, "...")
-                               end
-                               debug.sethook(oldhook)
-                               error('aborting the call to introspected function')
-                       end
-               end
-               oldhook = debug.sethook(hook, "c")  -- invoke hook() on function call
-               f(unpack({}))  -- huh?
-       end)
-       return "(" .. table.concat(func_args, ", ") .. ")"
-end
-
-function table_print(tt, indent, done)
-       done = done or {}
-       indent = indent or 0
-       local result = ""
-       -- Ordered for-iterator for tables with tostring-able keys.
-       local function ordered_iter(unordered_tt)
-               local keys = {}
-               for k in pairs(unordered_tt) do
-                       table.insert(keys, k)
-               end
-               table.sort(keys,
-                       function (a, b)
-                               if type(a) ~= type(b) then
-                                       return type(a) < type(b)
-                               end
-                               if type(a) == 'number' then
-                                       return a < b
-                               else
-                                       return tostring(a) < tostring(b)
-                               end
-                       end)
-               local i = 0
-               return function()
-                       i = i + 1
-                       if keys[i] ~= nil then
-                               return keys[i], unordered_tt[keys[i]]
-                       end
-               end
-       end
-       -- Convert to printable string (escape unprintable)
-       local function printable(value)
-               value = tostring(value)
-               local bytes = {}
-               for i = 1, #value do
-                       local c = string.byte(value, i)
-                       if c >= 0x20 and c < 0x7f then table.insert(bytes, string.char(c))
-                       else                           table.insert(bytes, '\\'..tostring(c))
-                       end
-                       if i > 80 then table.insert(bytes, '...') break end
-               end
-               return table.concat(bytes)
-       end
-       if type(tt) == "table" then
-               for key, value in ordered_iter(tt) do
-                       result = result .. string.rep (" ", indent)
-                       if type (value) == "table" and not done [value] then
-                               done [value] = true
-                               result = result .. string.format("[%s] => {\n", printable (key))
-                               result = result .. table_print (value, indent + 4, done)
-                               result = result .. string.rep (" ", indent)
-                               result = result .. "}\n"
-                       elseif type (value) == "function" then
-                               result = result .. string.format("[%s] => function %s%s: %s\n",
-                                        tostring(key), tostring(key), funcsign(value),
-                                        string.sub(tostring(value), 11))
-                       else
-                               result = result .. string.format("[%s] => %s\n",
-                                        tostring (key), printable(value))
-                       end
-               end
-       else  -- not a table
-               local tt_str
-               if type(tt) == "function" then
-                       tt_str = string.format("function%s: %s\n", funcsign(tt),
-                                               string.sub(tostring(tt), 11))
-               else
-                       tt_str = tostring(tt)
-               end
-               result = result .. tt_str .. "\n"
-       end
-       return result
-end
+table_print = require('krprint').pprint
 
 -- This extends the worker module to allow asynchronous execution of functions and nonblocking I/O.
 -- The current implementation combines cqueues for Lua interface, and event.socket() in order to not
@@ -821,9 +715,20 @@ function map(cmd, format)
                        end
                end
                result_count = result_count + 1
+               -- return value is output from eval_cmd
+               -- i.e. string including "quotes" and Lua escaping in between
+               assert(type(ret) == 'string', 'map() protocol error, '
+                       .. 'string not retured by follower')
+               assert(#ret >= 2 and
+                       string.sub(ret, 1, 1) == "'"
+                       and string.sub(ret, -1, -1) == "'",
+                       'map() protocol error, value returned by follower does '
+                       .. 'not look like a string')
+               -- deserialize string: remove "quotes" and de-escape bytes
+               ret = krprint.deserialize_lua(ret)
                if format == 'luaobj' then
+                       -- ret should be table with xpcall results serialized into string
                        ret = krprint.deserialize_lua(ret)
-                       -- ret is now table with xpcall results
                        assert(type(ret) == 'table', 'map() protocol error, '
                                .. 'table with results not retured by follower')
                        if (ret.n ~= 2) then
@@ -842,9 +747,6 @@ function map(cmd, format)
                        end
                        -- drop wrapper table and return only the actual return value
                        ret = retval
-               else
-                       assert(type(ret) == 'string', 'map() protocol error, '
-                               .. 'string not retured by follower')
                end
                results[result_count] = ret
                ::continue::
index e104317152eece0ac0d7051338a2c7cb1506220b..4f238e085677625f270088b6ec40449bfa5ccebb 100644 (file)
@@ -45,7 +45,13 @@ Users
 * `DNS Flag Day 2020 <https://dnsflagday.net/2020/>`_ is now effective and Knot Resolver uses
   maximum size of UDP answer to 1232 bytes. Please double-check your firewall,
   it has to allow DNS traffic on UDP and **also TCP** port 53.
-
+* Human readable output in interactive mode and from :ref:`control-sockets` was improved and
+  as consequence slightly changed its format. Users who need machine readable output for scripts
+  should use Lua function ``tojson()`` to convert Lua values into standard JSON format instead
+  of attempting to parse the human readable output.
+  For example API call ``tojson(cache.stats())\n`` will return JSON string with ``cache.stats()``
+  results represented as dictionary.
+  Function ``tojson()`` is available in all resolver versions >= 1.0.0.
 
 Configuration file
 ------------------
index 3f5a9b0d03dd589c9f69bd02094a53d9a80dd79b..913b9a4df160a1c89088b1b43ce9cac5b75c9836 100644 (file)
@@ -27,8 +27,8 @@ local function test_globals()
        same(option('REORDER_RR', false), false, 'generic option call')
        boom(option, {'REORDER_RR', 'potato'}, 'generic option call argument check')
        boom(option, {'MARS_VACATION', false}, 'generic option check name')
-       same(table_print('crabdiary'), 'crabdiary\n', 'table print works')
-       same(table_print({fakepizza=1}), '[fakepizza] => 1\n', 'table print works on tables')
+       same(table_print('crabdiary'), "'crabdiary'", 'table print works')
+       same(table_print({fakepizza=1}), "{\n    ['fakepizza'] = 1,\n}", 'table print works on tables')
 end
 
 -- test if dns library functions work