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