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