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