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
|