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