| 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 stdout_chunks, stderr_chunks = {}, {} | 
| jbe@486 | 27 | 
| jbe@486 | 28   local function return_error(errmsg) | 
| jbe@486 | 29     if args.stdout_result_handler then | 
| jbe@486 | 30       args.stdout_result_handler(table.concat(stdout_chunks), false, errmsg) | 
| jbe@486 | 31     end | 
| jbe@486 | 32     return nil, errmsg | 
| jbe@486 | 33   end | 
| jbe@486 | 34 | 
| jbe@487 | 35   if args.abortable then | 
| jbe@487 | 36     local pollready, pollmsg, pollterm = poll(nil, nil, 0, true) | 
| jbe@487 | 37     if pollterm then | 
| jbe@488 | 38       if args.abort_handler then args.abort_handler(pollmsg) end | 
| jbe@487 | 39       return_error(pollmsg) | 
| jbe@487 | 40     end | 
| jbe@487 | 41   end | 
| jbe@487 | 42 | 
| jbe@487 | 43   local start = moonbridge_io.timeref() | 
| jbe@487 | 44   local process, errmsg = moonbridge_io.exec(table.unpack(args.command)) | 
| jbe@487 | 45   if not process then return nil, errmsg end | 
| jbe@487 | 46 | 
| jbe@489 | 47   local read_fds = { | 
| jbe@489 | 48     [process] = true, | 
| jbe@489 | 49     [process.stdout] = true, | 
| jbe@489 | 50     [process.stderr] = true | 
| jbe@489 | 51   } | 
| jbe@489 | 52   local write_fds = { | 
| jbe@489 | 53     [process.stdin] = true | 
| jbe@489 | 54   } | 
| jbe@487 | 55   if args.db then | 
| jbe@487 | 56     read_fds[args.db.fd] = true | 
| jbe@487 | 57   end | 
| jbe@487 | 58 | 
| jbe@486 | 59   local function write(...) | 
| jbe@486 | 60     if write_fds[process.stdin] then | 
| jbe@486 | 61       local buffered = process.stdin:flush_nb(...) | 
| jbe@486 | 62       if not buffered or buffered == 0 then | 
| jbe@486 | 63         process.stdin:close() | 
| jbe@486 | 64         write_fds[process.stdin] = nil | 
| jbe@486 | 65       end | 
| jbe@486 | 66     end | 
| jbe@486 | 67   end | 
| jbe@486 | 68   write(args.stdin_data or "") | 
| jbe@486 | 69 | 
| jbe@489 | 70   local status | 
| jbe@489 | 71 | 
| jbe@486 | 72   while | 
| jbe@489 | 73     not status or | 
| jbe@486 | 74     read_fds[process.stdout] or read_fds[process.stderr] or | 
| jbe@486 | 75     write_fds[process.stdin] | 
| jbe@486 | 76   do | 
| jbe@486 | 77     local timeout = args.timeout and args.timeout-moonbridge_io.timeref(start) | 
| jbe@487 | 78     local pollready, pollmsg, pollterm = | 
| jbe@486 | 79       poll(read_fds, write_fds, timeout, args.abortable) | 
| jbe@487 | 80     if not pollready then | 
| jbe@489 | 81       if not status then | 
| jbe@489 | 82         process:kill():wait() | 
| jbe@489 | 83       end | 
| jbe@486 | 84       if pollterm then | 
| jbe@488 | 85         if args.abort_handler then args.abort_handler(pollmsg) end | 
| jbe@486 | 86       else | 
| jbe@486 | 87         if args.timeout_handler then args.timeout_handler() end | 
| jbe@486 | 88       end | 
| jbe@486 | 89       return return_error(pollmsg) | 
| jbe@486 | 90     end | 
| jbe@489 | 91     if not status then | 
| jbe@489 | 92       status = process:wait_nb() | 
| jbe@489 | 93       if status then | 
| jbe@489 | 94         read_fds[process] = nil | 
| jbe@489 | 95       end | 
| jbe@489 | 96     end | 
| jbe@486 | 97     if args.db then | 
| jbe@486 | 98       local channel, payload, pid = db:wait(0) | 
| jbe@486 | 99       if channel then | 
| jbe@486 | 100         if args.db_notify_handler(channel, payload, pid) then | 
| jbe@486 | 101           process:kill():wait() | 
| jbe@486 | 102           return return_error("Database event received") | 
| jbe@486 | 103         end | 
| jbe@486 | 104       end | 
| jbe@486 | 105     end | 
| jbe@486 | 106     if read_fds[process.stdout] then | 
| jbe@486 | 107       local chunk, status = process.stdout:read_nb() | 
| jbe@486 | 108       if not chunk or status == "eof" then | 
| jbe@486 | 109         process.stdout:close() | 
| jbe@486 | 110         read_fds[process.stdout] = nil | 
| jbe@486 | 111       end | 
| jbe@486 | 112       if chunk and chunk ~= "" then | 
| jbe@486 | 113         stdout_chunks[#stdout_chunks+1] = chunk | 
| jbe@486 | 114       end | 
| jbe@486 | 115     end | 
| jbe@486 | 116     if read_fds[process.stderr] then | 
| jbe@486 | 117       local chunk, status = process.stderr:read_nb() | 
| jbe@486 | 118       if not chunk or status == "eof" then | 
| jbe@486 | 119         process.stderr:close() | 
| jbe@486 | 120         read_fds[process.stderr] = nil | 
| jbe@486 | 121       end | 
| jbe@486 | 122       if chunk and args.stderr_line_handler then | 
| jbe@486 | 123         while true do | 
| jbe@486 | 124           local chunk1, chunk2 = string.match(chunk, "(.-)\n(.*)") | 
| jbe@486 | 125           if not chunk1 then break end | 
| jbe@486 | 126           stderr_chunks[#stderr_chunks+1] = chunk1 | 
| jbe@486 | 127           args.stderr_line_handler(table.concat(stderr_chunks)) | 
| jbe@486 | 128           stderr_chunks = {} | 
| jbe@486 | 129           chunk = chunk2 | 
| jbe@486 | 130         end | 
| jbe@486 | 131         if chunk ~= "" then | 
| jbe@486 | 132           stderr_chunks[#stderr_chunks+1] = chunk | 
| jbe@486 | 133         end | 
| jbe@486 | 134         if status == "eof" then | 
| jbe@486 | 135           local line = table.concat(stderr_chunks) | 
| jbe@486 | 136           if #line > 0 then args.stderr_line_handler(line) end | 
| jbe@486 | 137         end | 
| jbe@486 | 138       end | 
| jbe@486 | 139     end | 
| jbe@486 | 140     write() | 
| jbe@486 | 141   end | 
| jbe@486 | 142 | 
| jbe@486 | 143   if status < 0 then | 
| jbe@486 | 144     if args.signal_handler then | 
| jbe@486 | 145       args.signal_handler(-status) | 
| jbe@486 | 146     end | 
| jbe@486 | 147     return return_error("Command terminated by signal " .. -status) | 
| jbe@486 | 148   elseif status > 0 then | 
| jbe@486 | 149     if args.exit_handler then | 
| jbe@486 | 150       args.exit_handler(status) | 
| jbe@486 | 151     end | 
| jbe@486 | 152     return return_error("Command returned exit code " .. status) | 
| jbe@486 | 153   elseif args.stdout_result_handler then | 
| jbe@486 | 154     args.stdout_result_handler(table.concat(stdout_chunks), true) | 
| jbe@486 | 155     return true | 
| jbe@486 | 156   else | 
| jbe@486 | 157     return table.concat(stdout_chunks) | 
| jbe@486 | 158   end | 
| jbe@486 | 159 | 
| jbe@486 | 160 end |