| rev | line source | 
| jbe@486 | 1 --[[-- | 
| jbe@486 | 2 output,           -- collected data from stdout if process exited successfully | 
| jbe@486 | 3 errmsg =          -- error message if execution failed or if process didn't exit successfully | 
| jbe@486 | 4 execute.command{ | 
| jbe@486 | 5   command               = { filename, arg1, arg2, ... },  -- command and arguments | 
| jbe@486 | 6   stdin_data            = stdin_data,                     -- optional data to be sent to process via stdin | 
| jbe@486 | 7   stdout_result_handler = stdout_result_handler,          -- callback receiving: stdout data, success boolean, optional error message | 
| jbe@486 | 8   stderr_line_handler   = stderr_line_handler,            -- callback for processing stderr line-wise | 
| jbe@486 | 9   exit_handler          = exit_handler,                   -- callback when process exited | 
| jbe@486 | 10   signal_handler        = signal_handler,                 -- callback when process terminated due to signal | 
| jbe@486 | 11   timeout_handler       = timeout_handler,                -- callback when process gets killed due to timeout | 
| jbe@486 | 12   abort_handler         = abort_handler,                  -- callback when process gets killed due to request by poll function | 
| jbe@486 | 13   abortable             = abortable,                      -- set to true if process shall be terminated if poll function requests termination | 
| jbe@486 | 14   poll                  = poll,                           -- alternative poll command with moonbridge_io.poll(...) semantics | 
| jbe@486 | 15   db                    = db,                             -- database handle for waiting for notifies | 
| jbe@486 | 16   db_notify_handler     = db_notify_handler               -- handler for database notifications which may return true to kill process | 
| jbe@486 | 17 } | 
| jbe@486 | 18 | 
| jbe@486 | 19 --]]-- | 
| jbe@486 | 20 | 
| jbe@486 | 21 function execute.command(args) | 
| jbe@486 | 22 | 
| jbe@486 | 23   local moonbridge_io = require("moonbridge_io") | 
| jbe@486 | 24   local poll = args.poll or moonbridge_io.poll | 
| jbe@486 | 25 | 
| jbe@486 | 26   local start = moonbridge_io.timeref() | 
| jbe@486 | 27   local process, errmsg = moonbridge_io.exec(table.unpack(args.command)) | 
| jbe@486 | 28   if not process then return nil, errmsg end | 
| jbe@486 | 29 | 
| jbe@486 | 30   local stdout_chunks, stderr_chunks = {}, {} | 
| jbe@486 | 31   local read_fds = {[process.stdout] = true, [process.stderr] = true} | 
| jbe@486 | 32   local write_fds = {[process.stdin] = true} | 
| jbe@486 | 33   if args.db then | 
| jbe@486 | 34     read_fds[args.db.fd] = true | 
| jbe@486 | 35   end | 
| jbe@486 | 36 | 
| jbe@486 | 37   local function return_error(errmsg) | 
| jbe@486 | 38     if args.stdout_result_handler then | 
| jbe@486 | 39       args.stdout_result_handler(table.concat(stdout_chunks), false, errmsg) | 
| jbe@486 | 40     end | 
| jbe@486 | 41     return nil, errmsg | 
| jbe@486 | 42   end | 
| jbe@486 | 43 | 
| jbe@486 | 44   local function write(...) | 
| jbe@486 | 45     if write_fds[process.stdin] then | 
| jbe@486 | 46       local buffered = process.stdin:flush_nb(...) | 
| jbe@486 | 47       if not buffered or buffered == 0 then | 
| jbe@486 | 48         process.stdin:close() | 
| jbe@486 | 49         write_fds[process.stdin] = nil | 
| jbe@486 | 50       end | 
| jbe@486 | 51     end | 
| jbe@486 | 52   end | 
| jbe@486 | 53   write(args.stdin_data or "") | 
| jbe@486 | 54 | 
| jbe@486 | 55   while | 
| jbe@486 | 56     read_fds[process.stdout] or read_fds[process.stderr] or | 
| jbe@486 | 57     write_fds[process.stdin] | 
| jbe@486 | 58   do | 
| jbe@486 | 59     local timeout = args.timeout and args.timeout-moonbridge_io.timeref(start) | 
| jbe@486 | 60     local pollstatus, pollmsg, pollterm = | 
| jbe@486 | 61       poll(read_fds, write_fds, timeout, args.abortable) | 
| jbe@486 | 62     if not pollstatus then | 
| jbe@486 | 63       process:kill():wait() | 
| jbe@486 | 64       if pollterm then | 
| jbe@486 | 65         if args.abort_handler then args.abort_handler() end | 
| jbe@486 | 66       else | 
| jbe@486 | 67         if args.timeout_handler then args.timeout_handler() end | 
| jbe@486 | 68       end | 
| jbe@486 | 69       return return_error(pollmsg) | 
| jbe@486 | 70     end | 
| jbe@486 | 71     if args.db then | 
| jbe@486 | 72       local channel, payload, pid = db:wait(0) | 
| jbe@486 | 73       if channel then | 
| jbe@486 | 74         if args.db_notify_handler(channel, payload, pid) then | 
| jbe@486 | 75           process:kill():wait() | 
| jbe@486 | 76           return return_error("Database event received") | 
| jbe@486 | 77         end | 
| jbe@486 | 78       end | 
| jbe@486 | 79     end | 
| jbe@486 | 80     if read_fds[process.stdout] then | 
| jbe@486 | 81       local chunk, status = process.stdout:read_nb() | 
| jbe@486 | 82       if not chunk or status == "eof" then | 
| jbe@486 | 83         process.stdout:close() | 
| jbe@486 | 84         read_fds[process.stdout] = nil | 
| jbe@486 | 85       end | 
| jbe@486 | 86       if chunk and chunk ~= "" then | 
| jbe@486 | 87         stdout_chunks[#stdout_chunks+1] = chunk | 
| jbe@486 | 88       end | 
| jbe@486 | 89     end | 
| jbe@486 | 90     if read_fds[process.stderr] then | 
| jbe@486 | 91       local chunk, status = process.stderr:read_nb() | 
| jbe@486 | 92       if not chunk or status == "eof" then | 
| jbe@486 | 93         process.stderr:close() | 
| jbe@486 | 94         read_fds[process.stderr] = nil | 
| jbe@486 | 95       end | 
| jbe@486 | 96       if chunk and args.stderr_line_handler then | 
| jbe@486 | 97         while true do | 
| jbe@486 | 98           local chunk1, chunk2 = string.match(chunk, "(.-)\n(.*)") | 
| jbe@486 | 99           if not chunk1 then break end | 
| jbe@486 | 100           stderr_chunks[#stderr_chunks+1] = chunk1 | 
| jbe@486 | 101           args.stderr_line_handler(table.concat(stderr_chunks)) | 
| jbe@486 | 102           stderr_chunks = {} | 
| jbe@486 | 103           chunk = chunk2 | 
| jbe@486 | 104         end | 
| jbe@486 | 105         if chunk ~= "" then | 
| jbe@486 | 106           stderr_chunks[#stderr_chunks+1] = chunk | 
| jbe@486 | 107         end | 
| jbe@486 | 108         if status == "eof" then | 
| jbe@486 | 109           local line = table.concat(stderr_chunks) | 
| jbe@486 | 110           if #line > 0 then args.stderr_line_handler(line) end | 
| jbe@486 | 111         end | 
| jbe@486 | 112       end | 
| jbe@486 | 113     end | 
| jbe@486 | 114     write() | 
| jbe@486 | 115   end | 
| jbe@486 | 116 | 
| jbe@486 | 117   local status = process:wait() | 
| jbe@486 | 118 | 
| jbe@486 | 119   if status < 0 then | 
| jbe@486 | 120     if args.signal_handler then | 
| jbe@486 | 121       args.signal_handler(-status) | 
| jbe@486 | 122     end | 
| jbe@486 | 123     return return_error("Command terminated by signal " .. -status) | 
| jbe@486 | 124   elseif status > 0 then | 
| jbe@486 | 125     if args.exit_handler then | 
| jbe@486 | 126       args.exit_handler(status) | 
| jbe@486 | 127     end | 
| jbe@486 | 128     return return_error("Command returned exit code " .. status) | 
| jbe@486 | 129   elseif args.stdout_result_handler then | 
| jbe@486 | 130     args.stdout_result_handler(table.concat(stdout_chunks), true) | 
| jbe@486 | 131     return true | 
| jbe@486 | 132   else | 
| jbe@486 | 133     return table.concat(stdout_chunks) | 
| jbe@486 | 134   end | 
| jbe@486 | 135 | 
| jbe@486 | 136 end |