From: Petr Špaček Date: Mon, 20 Jul 2020 07:46:10 +0000 (+0200) Subject: replace sandbox table_print with new pretty printer X-Git-Tag: v5.2.0~4^2~6 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=649602c2941061a62c9cf0cc7e4125e63c314f13;p=thirdparty%2Fknot-resolver.git replace sandbox table_print with new pretty printer 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). --- diff --git a/daemon/lua/controlsock.test.lua b/daemon/lua/controlsock.test.lua index 53650e014..e3679dc88 100644 --- a/daemon/lua/controlsock.test.lua +++ b/daemon/lua/controlsock.test.lua @@ -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() diff --git a/daemon/lua/sandbox.lua.in b/daemon/lua/sandbox.lua.in index dfbf815be..87132bb3f 100644 --- a/daemon/lua/sandbox.lua.in +++ b/daemon/lua/sandbox.lua.in @@ -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:: diff --git a/doc/upgrading.rst b/doc/upgrading.rst index e10431715..4f238e085 100644 --- a/doc/upgrading.rst +++ b/doc/upgrading.rst @@ -45,7 +45,13 @@ Users * `DNS Flag Day 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 ------------------ diff --git a/tests/config/basic.test.lua b/tests/config/basic.test.lua index 3f5a9b0d0..913b9a4df 100644 --- a/tests/config/basic.test.lua +++ b/tests/config/basic.test.lua @@ -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