webmcp

changeset 486:7d53e12f0804

New function execute.command{...}
author jbe
date Sun Jun 11 23:27:17 2017 +0200 (2017-06-11)
parents 9b7a391fd461
children 91d0c8304d74
files framework/env/execute/command.lua
line diff
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/framework/env/execute/command.lua	Sun Jun 11 23:27:17 2017 +0200
     1.3 @@ -0,0 +1,136 @@
     1.4 +--[[--
     1.5 +output,           -- collected data from stdout if process exited successfully
     1.6 +errmsg =          -- error message if execution failed or if process didn't exit successfully
     1.7 +execute.command{
     1.8 +  command               = { filename, arg1, arg2, ... },  -- command and arguments
     1.9 +  stdin_data            = stdin_data,                     -- optional data to be sent to process via stdin
    1.10 +  stdout_result_handler = stdout_result_handler,          -- callback receiving: stdout data, success boolean, optional error message
    1.11 +  stderr_line_handler   = stderr_line_handler,            -- callback for processing stderr line-wise
    1.12 +  exit_handler          = exit_handler,                   -- callback when process exited
    1.13 +  signal_handler        = signal_handler,                 -- callback when process terminated due to signal
    1.14 +  timeout_handler       = timeout_handler,                -- callback when process gets killed due to timeout
    1.15 +  abort_handler         = abort_handler,                  -- callback when process gets killed due to request by poll function
    1.16 +  abortable             = abortable,                      -- set to true if process shall be terminated if poll function requests termination
    1.17 +  poll                  = poll,                           -- alternative poll command with moonbridge_io.poll(...) semantics
    1.18 +  db                    = db,                             -- database handle for waiting for notifies
    1.19 +  db_notify_handler     = db_notify_handler               -- handler for database notifications which may return true to kill process
    1.20 +}
    1.21 +
    1.22 +--]]--
    1.23 +
    1.24 +function execute.command(args)
    1.25 +
    1.26 +  local moonbridge_io = require("moonbridge_io")
    1.27 +  local poll = args.poll or moonbridge_io.poll
    1.28 +
    1.29 +  local start = moonbridge_io.timeref()
    1.30 +  local process, errmsg = moonbridge_io.exec(table.unpack(args.command))
    1.31 +  if not process then return nil, errmsg end
    1.32 +
    1.33 +  local stdout_chunks, stderr_chunks = {}, {}
    1.34 +  local read_fds = {[process.stdout] = true, [process.stderr] = true}
    1.35 +  local write_fds = {[process.stdin] = true}
    1.36 +  if args.db then
    1.37 +    read_fds[args.db.fd] = true
    1.38 +  end
    1.39 +
    1.40 +  local function return_error(errmsg)
    1.41 +    if args.stdout_result_handler then
    1.42 +      args.stdout_result_handler(table.concat(stdout_chunks), false, errmsg)
    1.43 +    end
    1.44 +    return nil, errmsg
    1.45 +  end
    1.46 +
    1.47 +  local function write(...)
    1.48 +    if write_fds[process.stdin] then
    1.49 +      local buffered = process.stdin:flush_nb(...)
    1.50 +      if not buffered or buffered == 0 then
    1.51 +        process.stdin:close()
    1.52 +        write_fds[process.stdin] = nil
    1.53 +      end
    1.54 +    end
    1.55 +  end
    1.56 +  write(args.stdin_data or "")
    1.57 +
    1.58 +  while
    1.59 +    read_fds[process.stdout] or read_fds[process.stderr] or
    1.60 +    write_fds[process.stdin]
    1.61 +  do
    1.62 +    local timeout = args.timeout and args.timeout-moonbridge_io.timeref(start)
    1.63 +    local pollstatus, pollmsg, pollterm =
    1.64 +      poll(read_fds, write_fds, timeout, args.abortable)
    1.65 +    if not pollstatus then
    1.66 +      process:kill():wait()
    1.67 +      if pollterm then
    1.68 +        if args.abort_handler then args.abort_handler() end
    1.69 +      else
    1.70 +        if args.timeout_handler then args.timeout_handler() end
    1.71 +      end
    1.72 +      return return_error(pollmsg)
    1.73 +    end
    1.74 +    if args.db then
    1.75 +      local channel, payload, pid = db:wait(0)
    1.76 +      if channel then
    1.77 +        if args.db_notify_handler(channel, payload, pid) then
    1.78 +          process:kill():wait()
    1.79 +          return return_error("Database event received")
    1.80 +        end
    1.81 +      end
    1.82 +    end
    1.83 +    if read_fds[process.stdout] then
    1.84 +      local chunk, status = process.stdout:read_nb()
    1.85 +      if not chunk or status == "eof" then
    1.86 +        process.stdout:close()
    1.87 +        read_fds[process.stdout] = nil
    1.88 +      end
    1.89 +      if chunk and chunk ~= "" then
    1.90 +        stdout_chunks[#stdout_chunks+1] = chunk
    1.91 +      end
    1.92 +    end
    1.93 +    if read_fds[process.stderr] then
    1.94 +      local chunk, status = process.stderr:read_nb()
    1.95 +      if not chunk or status == "eof" then
    1.96 +        process.stderr:close()
    1.97 +        read_fds[process.stderr] = nil
    1.98 +      end
    1.99 +      if chunk and args.stderr_line_handler then
   1.100 +        while true do
   1.101 +          local chunk1, chunk2 = string.match(chunk, "(.-)\n(.*)")
   1.102 +          if not chunk1 then break end
   1.103 +          stderr_chunks[#stderr_chunks+1] = chunk1
   1.104 +          args.stderr_line_handler(table.concat(stderr_chunks))
   1.105 +          stderr_chunks = {}
   1.106 +          chunk = chunk2
   1.107 +        end
   1.108 +        if chunk ~= "" then
   1.109 +          stderr_chunks[#stderr_chunks+1] = chunk
   1.110 +        end
   1.111 +        if status == "eof" then
   1.112 +          local line = table.concat(stderr_chunks)
   1.113 +          if #line > 0 then args.stderr_line_handler(line) end
   1.114 +        end
   1.115 +      end
   1.116 +    end
   1.117 +    write()
   1.118 +  end
   1.119 +
   1.120 +  local status = process:wait()
   1.121 +
   1.122 +  if status < 0 then
   1.123 +    if args.signal_handler then
   1.124 +      args.signal_handler(-status)
   1.125 +    end
   1.126 +    return return_error("Command terminated by signal " .. -status)
   1.127 +  elseif status > 0 then
   1.128 +    if args.exit_handler then
   1.129 +      args.exit_handler(status)
   1.130 +    end
   1.131 +    return return_error("Command returned exit code " .. status)
   1.132 +  elseif args.stdout_result_handler then
   1.133 +    args.stdout_result_handler(table.concat(stdout_chunks), true)
   1.134 +    return true
   1.135 +  else
   1.136 +    return table.concat(stdout_chunks)
   1.137 +  end
   1.138 +
   1.139 +end

Impressum / About Us