webmcp
changeset 486:7d53e12f0804
New function execute.command{...}
author | jbe |
---|---|
date | Sun Jun 11 23:27:17 2017 +0200 (2017-06-11) |
parents | 9b7a391fd461 |
children | 91d0c8304d74 |
files | framework/env/execute/command.lua |
line diff
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/framework/env/execute/command.lua Sun Jun 11 23:27:17 2017 +0200 1.3 @@ -0,0 +1,136 @@ 1.4 +--[[-- 1.5 +output, -- collected data from stdout if process exited successfully 1.6 +errmsg = -- error message if execution failed or if process didn't exit successfully 1.7 +execute.command{ 1.8 + command = { filename, arg1, arg2, ... }, -- command and arguments 1.9 + stdin_data = stdin_data, -- optional data to be sent to process via stdin 1.10 + stdout_result_handler = stdout_result_handler, -- callback receiving: stdout data, success boolean, optional error message 1.11 + stderr_line_handler = stderr_line_handler, -- callback for processing stderr line-wise 1.12 + exit_handler = exit_handler, -- callback when process exited 1.13 + signal_handler = signal_handler, -- callback when process terminated due to signal 1.14 + timeout_handler = timeout_handler, -- callback when process gets killed due to timeout 1.15 + abort_handler = abort_handler, -- callback when process gets killed due to request by poll function 1.16 + abortable = abortable, -- set to true if process shall be terminated if poll function requests termination 1.17 + poll = poll, -- alternative poll command with moonbridge_io.poll(...) semantics 1.18 + db = db, -- database handle for waiting for notifies 1.19 + db_notify_handler = db_notify_handler -- handler for database notifications which may return true to kill process 1.20 +} 1.21 + 1.22 +--]]-- 1.23 + 1.24 +function execute.command(args) 1.25 + 1.26 + local moonbridge_io = require("moonbridge_io") 1.27 + local poll = args.poll or moonbridge_io.poll 1.28 + 1.29 + local start = moonbridge_io.timeref() 1.30 + local process, errmsg = moonbridge_io.exec(table.unpack(args.command)) 1.31 + if not process then return nil, errmsg end 1.32 + 1.33 + local stdout_chunks, stderr_chunks = {}, {} 1.34 + local read_fds = {[process.stdout] = true, [process.stderr] = true} 1.35 + local write_fds = {[process.stdin] = true} 1.36 + if args.db then 1.37 + read_fds[args.db.fd] = true 1.38 + end 1.39 + 1.40 + local function return_error(errmsg) 1.41 + if args.stdout_result_handler then 1.42 + args.stdout_result_handler(table.concat(stdout_chunks), false, errmsg) 1.43 + end 1.44 + return nil, errmsg 1.45 + end 1.46 + 1.47 + local function write(...) 1.48 + if write_fds[process.stdin] then 1.49 + local buffered = process.stdin:flush_nb(...) 1.50 + if not buffered or buffered == 0 then 1.51 + process.stdin:close() 1.52 + write_fds[process.stdin] = nil 1.53 + end 1.54 + end 1.55 + end 1.56 + write(args.stdin_data or "") 1.57 + 1.58 + while 1.59 + read_fds[process.stdout] or read_fds[process.stderr] or 1.60 + write_fds[process.stdin] 1.61 + do 1.62 + local timeout = args.timeout and args.timeout-moonbridge_io.timeref(start) 1.63 + local pollstatus, pollmsg, pollterm = 1.64 + poll(read_fds, write_fds, timeout, args.abortable) 1.65 + if not pollstatus then 1.66 + process:kill():wait() 1.67 + if pollterm then 1.68 + if args.abort_handler then args.abort_handler() end 1.69 + else 1.70 + if args.timeout_handler then args.timeout_handler() end 1.71 + end 1.72 + return return_error(pollmsg) 1.73 + end 1.74 + if args.db then 1.75 + local channel, payload, pid = db:wait(0) 1.76 + if channel then 1.77 + if args.db_notify_handler(channel, payload, pid) then 1.78 + process:kill():wait() 1.79 + return return_error("Database event received") 1.80 + end 1.81 + end 1.82 + end 1.83 + if read_fds[process.stdout] then 1.84 + local chunk, status = process.stdout:read_nb() 1.85 + if not chunk or status == "eof" then 1.86 + process.stdout:close() 1.87 + read_fds[process.stdout] = nil 1.88 + end 1.89 + if chunk and chunk ~= "" then 1.90 + stdout_chunks[#stdout_chunks+1] = chunk 1.91 + end 1.92 + end 1.93 + if read_fds[process.stderr] then 1.94 + local chunk, status = process.stderr:read_nb() 1.95 + if not chunk or status == "eof" then 1.96 + process.stderr:close() 1.97 + read_fds[process.stderr] = nil 1.98 + end 1.99 + if chunk and args.stderr_line_handler then 1.100 + while true do 1.101 + local chunk1, chunk2 = string.match(chunk, "(.-)\n(.*)") 1.102 + if not chunk1 then break end 1.103 + stderr_chunks[#stderr_chunks+1] = chunk1 1.104 + args.stderr_line_handler(table.concat(stderr_chunks)) 1.105 + stderr_chunks = {} 1.106 + chunk = chunk2 1.107 + end 1.108 + if chunk ~= "" then 1.109 + stderr_chunks[#stderr_chunks+1] = chunk 1.110 + end 1.111 + if status == "eof" then 1.112 + local line = table.concat(stderr_chunks) 1.113 + if #line > 0 then args.stderr_line_handler(line) end 1.114 + end 1.115 + end 1.116 + end 1.117 + write() 1.118 + end 1.119 + 1.120 + local status = process:wait() 1.121 + 1.122 + if status < 0 then 1.123 + if args.signal_handler then 1.124 + args.signal_handler(-status) 1.125 + end 1.126 + return return_error("Command terminated by signal " .. -status) 1.127 + elseif status > 0 then 1.128 + if args.exit_handler then 1.129 + args.exit_handler(status) 1.130 + end 1.131 + return return_error("Command returned exit code " .. status) 1.132 + elseif args.stdout_result_handler then 1.133 + args.stdout_result_handler(table.concat(stdout_chunks), true) 1.134 + return true 1.135 + else 1.136 + return table.concat(stdout_chunks) 1.137 + end 1.138 + 1.139 +end