# HG changeset patch # User jbe # Date 1497216437 -7200 # Node ID 7d53e12f0804bb8d5675cc41bde427e10256524c # Parent 9b7a391fd46112e5b7baeca145e3ce6fafd64b41 New function execute.command{...} diff -r 9b7a391fd461 -r 7d53e12f0804 framework/env/execute/command.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/framework/env/execute/command.lua Sun Jun 11 23:27:17 2017 +0200 @@ -0,0 +1,136 @@ +--[[-- +output, -- collected data from stdout if process exited successfully +errmsg = -- error message if execution failed or if process didn't exit successfully +execute.command{ + command = { filename, arg1, arg2, ... }, -- command and arguments + stdin_data = stdin_data, -- optional data to be sent to process via stdin + stdout_result_handler = stdout_result_handler, -- callback receiving: stdout data, success boolean, optional error message + stderr_line_handler = stderr_line_handler, -- callback for processing stderr line-wise + exit_handler = exit_handler, -- callback when process exited + signal_handler = signal_handler, -- callback when process terminated due to signal + timeout_handler = timeout_handler, -- callback when process gets killed due to timeout + abort_handler = abort_handler, -- callback when process gets killed due to request by poll function + abortable = abortable, -- set to true if process shall be terminated if poll function requests termination + poll = poll, -- alternative poll command with moonbridge_io.poll(...) semantics + db = db, -- database handle for waiting for notifies + db_notify_handler = db_notify_handler -- handler for database notifications which may return true to kill process +} + +--]]-- + +function execute.command(args) + + local moonbridge_io = require("moonbridge_io") + local poll = args.poll or moonbridge_io.poll + + local start = moonbridge_io.timeref() + local process, errmsg = moonbridge_io.exec(table.unpack(args.command)) + if not process then return nil, errmsg end + + local stdout_chunks, stderr_chunks = {}, {} + local read_fds = {[process.stdout] = true, [process.stderr] = true} + local write_fds = {[process.stdin] = true} + if args.db then + read_fds[args.db.fd] = true + end + + local function return_error(errmsg) + if args.stdout_result_handler then + args.stdout_result_handler(table.concat(stdout_chunks), false, errmsg) + end + return nil, errmsg + end + + local function write(...) + if write_fds[process.stdin] then + local buffered = process.stdin:flush_nb(...) + if not buffered or buffered == 0 then + process.stdin:close() + write_fds[process.stdin] = nil + end + end + end + write(args.stdin_data or "") + + while + read_fds[process.stdout] or read_fds[process.stderr] or + write_fds[process.stdin] + do + local timeout = args.timeout and args.timeout-moonbridge_io.timeref(start) + local pollstatus, pollmsg, pollterm = + poll(read_fds, write_fds, timeout, args.abortable) + if not pollstatus then + process:kill():wait() + if pollterm then + if args.abort_handler then args.abort_handler() end + else + if args.timeout_handler then args.timeout_handler() end + end + return return_error(pollmsg) + end + if args.db then + local channel, payload, pid = db:wait(0) + if channel then + if args.db_notify_handler(channel, payload, pid) then + process:kill():wait() + return return_error("Database event received") + end + end + end + if read_fds[process.stdout] then + local chunk, status = process.stdout:read_nb() + if not chunk or status == "eof" then + process.stdout:close() + read_fds[process.stdout] = nil + end + if chunk and chunk ~= "" then + stdout_chunks[#stdout_chunks+1] = chunk + end + end + if read_fds[process.stderr] then + local chunk, status = process.stderr:read_nb() + if not chunk or status == "eof" then + process.stderr:close() + read_fds[process.stderr] = nil + end + if chunk and args.stderr_line_handler then + while true do + local chunk1, chunk2 = string.match(chunk, "(.-)\n(.*)") + if not chunk1 then break end + stderr_chunks[#stderr_chunks+1] = chunk1 + args.stderr_line_handler(table.concat(stderr_chunks)) + stderr_chunks = {} + chunk = chunk2 + end + if chunk ~= "" then + stderr_chunks[#stderr_chunks+1] = chunk + end + if status == "eof" then + local line = table.concat(stderr_chunks) + if #line > 0 then args.stderr_line_handler(line) end + end + end + end + write() + end + + local status = process:wait() + + if status < 0 then + if args.signal_handler then + args.signal_handler(-status) + end + return return_error("Command terminated by signal " .. -status) + elseif status > 0 then + if args.exit_handler then + args.exit_handler(status) + end + return return_error("Command returned exit code " .. status) + elseif args.stdout_result_handler then + args.stdout_result_handler(table.concat(stdout_chunks), true) + return true + else + return table.concat(stdout_chunks) + end + +end