1 /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
2 file Copyright.txt or https://cmake.org/licensing for details. */
3 #include "cmUVProcessChain.h"
4
5 #include <cassert>
6 #include <istream> // IWYU pragma: keep
7 #include <iterator>
8 #include <utility>
9
10 #include <cm/memory>
11
12 #include <cm3p/uv.h>
13
14 #include "cmGetPipes.h"
15 #include "cmUVHandlePtr.h"
16 #include "cmUVStreambuf.h"
17
18 struct cmUVProcessChain::InternalData
19 {
20 struct BasicStreamData
21 {
22 cmUVStreambuf Streambuf;
23 cm::uv_pipe_ptr BuiltinStream;
24 uv_stdio_container_t Stdio;
25 };
26
27 template <typename IOStream>
28 struct StreamData : public BasicStreamData
29 {
StreamDatacmUVProcessChain::InternalData::StreamData30 StreamData()
31 : BuiltinIOStream(&this->Streambuf)
32 {
33 }
34
35 IOStream BuiltinIOStream;
36
GetBuiltinStreamcmUVProcessChain::InternalData::StreamData37 IOStream* GetBuiltinStream()
38 {
39 if (this->BuiltinStream.get()) {
40 return &this->BuiltinIOStream;
41 }
42 return nullptr;
43 }
44 };
45
46 struct ProcessData
47 {
48 cmUVProcessChain::InternalData* Data;
49 cm::uv_process_ptr Process;
50 cm::uv_pipe_ptr OutputPipe;
51 bool Finished = false;
52 Status ProcessStatus;
53 };
54
55 const cmUVProcessChainBuilder* Builder = nullptr;
56
57 bool Valid = false;
58
59 cm::uv_loop_ptr Loop;
60
61 StreamData<std::istream> OutputStreamData;
62 StreamData<std::istream> ErrorStreamData;
63
64 unsigned int ProcessesCompleted = 0;
65 std::vector<std::unique_ptr<ProcessData>> Processes;
66
67 bool Prepare(const cmUVProcessChainBuilder* builder);
68 bool AddCommand(const cmUVProcessChainBuilder::ProcessConfiguration& config,
69 bool first, bool last);
70 bool Finish();
71
72 static const Status* GetStatus(const ProcessData& data);
73 };
74
cmUVProcessChainBuilder()75 cmUVProcessChainBuilder::cmUVProcessChainBuilder()
76 {
77 this->SetNoStream(Stream_INPUT)
78 .SetNoStream(Stream_OUTPUT)
79 .SetNoStream(Stream_ERROR);
80 }
81
AddCommand(const std::vector<std::string> & arguments)82 cmUVProcessChainBuilder& cmUVProcessChainBuilder::AddCommand(
83 const std::vector<std::string>& arguments)
84 {
85 if (!arguments.empty()) {
86 this->Processes.emplace_back();
87 this->Processes.back().Arguments = arguments;
88 }
89 return *this;
90 }
91
SetNoStream(Stream stdio)92 cmUVProcessChainBuilder& cmUVProcessChainBuilder::SetNoStream(Stream stdio)
93 {
94 switch (stdio) {
95 case Stream_INPUT:
96 case Stream_OUTPUT:
97 case Stream_ERROR: {
98 auto& streamData = this->Stdio[stdio];
99 streamData.Type = None;
100 break;
101 }
102 }
103 return *this;
104 }
105
SetBuiltinStream(Stream stdio)106 cmUVProcessChainBuilder& cmUVProcessChainBuilder::SetBuiltinStream(
107 Stream stdio)
108 {
109 switch (stdio) {
110 case Stream_INPUT:
111 // FIXME
112 break;
113
114 case Stream_OUTPUT:
115 case Stream_ERROR: {
116 auto& streamData = this->Stdio[stdio];
117 streamData.Type = Builtin;
118 break;
119 }
120 }
121 return *this;
122 }
123
SetExternalStream(Stream stdio,int fd)124 cmUVProcessChainBuilder& cmUVProcessChainBuilder::SetExternalStream(
125 Stream stdio, int fd)
126 {
127 switch (stdio) {
128 case Stream_INPUT:
129 // FIXME
130 break;
131
132 case Stream_OUTPUT:
133 case Stream_ERROR: {
134 auto& streamData = this->Stdio[stdio];
135 streamData.Type = External;
136 streamData.FileDescriptor = fd;
137 break;
138 }
139 }
140 return *this;
141 }
142
Start() const143 cmUVProcessChain cmUVProcessChainBuilder::Start() const
144 {
145 cmUVProcessChain chain;
146
147 if (!chain.Data->Prepare(this)) {
148 return chain;
149 }
150
151 for (auto it = this->Processes.begin(); it != this->Processes.end(); ++it) {
152 if (!chain.Data->AddCommand(*it, it == this->Processes.begin(),
153 it == std::prev(this->Processes.end()))) {
154 return chain;
155 }
156 }
157
158 chain.Data->Finish();
159
160 return chain;
161 }
162
GetStatus(const cmUVProcessChain::InternalData::ProcessData & data)163 const cmUVProcessChain::Status* cmUVProcessChain::InternalData::GetStatus(
164 const cmUVProcessChain::InternalData::ProcessData& data)
165 {
166 if (data.Finished) {
167 return &data.ProcessStatus;
168 }
169 return nullptr;
170 }
171
Prepare(const cmUVProcessChainBuilder * builder)172 bool cmUVProcessChain::InternalData::Prepare(
173 const cmUVProcessChainBuilder* builder)
174 {
175 this->Builder = builder;
176
177 auto const& output =
178 this->Builder->Stdio[cmUVProcessChainBuilder::Stream_OUTPUT];
179 auto& outputData = this->OutputStreamData;
180 switch (output.Type) {
181 case cmUVProcessChainBuilder::None:
182 outputData.Stdio.flags = UV_IGNORE;
183 break;
184
185 case cmUVProcessChainBuilder::Builtin:
186 outputData.BuiltinStream.init(*this->Loop, 0);
187 outputData.Stdio.flags =
188 static_cast<uv_stdio_flags>(UV_CREATE_PIPE | UV_WRITABLE_PIPE);
189 outputData.Stdio.data.stream = outputData.BuiltinStream;
190 break;
191
192 case cmUVProcessChainBuilder::External:
193 outputData.Stdio.flags = UV_INHERIT_FD;
194 outputData.Stdio.data.fd = output.FileDescriptor;
195 break;
196 }
197
198 auto const& error =
199 this->Builder->Stdio[cmUVProcessChainBuilder::Stream_ERROR];
200 auto& errorData = this->ErrorStreamData;
201 switch (error.Type) {
202 case cmUVProcessChainBuilder::None:
203 errorData.Stdio.flags = UV_IGNORE;
204 break;
205
206 case cmUVProcessChainBuilder::Builtin: {
207 int pipeFd[2];
208 if (cmGetPipes(pipeFd) < 0) {
209 return false;
210 }
211
212 errorData.BuiltinStream.init(*this->Loop, 0);
213 if (uv_pipe_open(errorData.BuiltinStream, pipeFd[0]) < 0) {
214 return false;
215 }
216 errorData.Stdio.flags = UV_INHERIT_FD;
217 errorData.Stdio.data.fd = pipeFd[1];
218 break;
219 }
220
221 case cmUVProcessChainBuilder::External:
222 errorData.Stdio.flags = UV_INHERIT_FD;
223 errorData.Stdio.data.fd = error.FileDescriptor;
224 break;
225 }
226
227 return true;
228 }
229
AddCommand(const cmUVProcessChainBuilder::ProcessConfiguration & config,bool first,bool last)230 bool cmUVProcessChain::InternalData::AddCommand(
231 const cmUVProcessChainBuilder::ProcessConfiguration& config, bool first,
232 bool last)
233 {
234 this->Processes.emplace_back(cm::make_unique<ProcessData>());
235 auto& process = *this->Processes.back();
236 process.Data = this;
237
238 auto options = uv_process_options_t();
239
240 // Bounds were checked at add time, first element is guaranteed to exist
241 options.file = config.Arguments[0].c_str();
242
243 std::vector<const char*> arguments;
244 for (auto const& arg : config.Arguments) {
245 arguments.push_back(arg.c_str());
246 }
247 arguments.push_back(nullptr);
248 options.args = const_cast<char**>(arguments.data());
249 options.flags = UV_PROCESS_WINDOWS_HIDE;
250
251 std::array<uv_stdio_container_t, 3> stdio;
252 stdio[0] = uv_stdio_container_t();
253 if (first) {
254 stdio[0].flags = UV_IGNORE;
255 } else {
256 assert(this->Processes.size() >= 2);
257 auto& prev = *this->Processes[this->Processes.size() - 2];
258 stdio[0].flags = UV_INHERIT_STREAM;
259 stdio[0].data.stream = prev.OutputPipe;
260 }
261 if (last) {
262 stdio[1] = this->OutputStreamData.Stdio;
263 } else {
264 if (process.OutputPipe.init(*this->Loop, 0) < 0) {
265 return false;
266 }
267 stdio[1] = uv_stdio_container_t();
268 stdio[1].flags =
269 static_cast<uv_stdio_flags>(UV_CREATE_PIPE | UV_WRITABLE_PIPE);
270 stdio[1].data.stream = process.OutputPipe;
271 }
272 stdio[2] = this->ErrorStreamData.Stdio;
273
274 options.stdio = stdio.data();
275 options.stdio_count = 3;
276 options.exit_cb = [](uv_process_t* handle, int64_t exitStatus,
277 int termSignal) {
278 auto* processData = static_cast<ProcessData*>(handle->data);
279 processData->Finished = true;
280 processData->ProcessStatus.ExitStatus = exitStatus;
281 processData->ProcessStatus.TermSignal = termSignal;
282 processData->Data->ProcessesCompleted++;
283 };
284
285 return process.Process.spawn(*this->Loop, options, &process) >= 0;
286 }
287
Finish()288 bool cmUVProcessChain::InternalData::Finish()
289 {
290 if (this->Builder->Stdio[cmUVProcessChainBuilder::Stream_OUTPUT].Type ==
291 cmUVProcessChainBuilder::Builtin) {
292 this->OutputStreamData.Streambuf.open(
293 this->OutputStreamData.BuiltinStream);
294 }
295
296 if (this->Builder->Stdio[cmUVProcessChainBuilder::Stream_ERROR].Type ==
297 cmUVProcessChainBuilder::Builtin) {
298 cm::uv_pipe_ptr tmpPipe;
299 if (tmpPipe.init(*this->Loop, 0) < 0) {
300 return false;
301 }
302 if (uv_pipe_open(tmpPipe, this->ErrorStreamData.Stdio.data.fd) < 0) {
303 return false;
304 }
305 tmpPipe.reset();
306
307 this->ErrorStreamData.Streambuf.open(this->ErrorStreamData.BuiltinStream);
308 }
309
310 this->Valid = true;
311 return true;
312 }
313
cmUVProcessChain()314 cmUVProcessChain::cmUVProcessChain()
315 : Data(cm::make_unique<InternalData>())
316 {
317 this->Data->Loop.init();
318 }
319
cmUVProcessChain(cmUVProcessChain && other)320 cmUVProcessChain::cmUVProcessChain(cmUVProcessChain&& other) noexcept
321 : Data(std::move(other.Data))
322 {
323 }
324
325 cmUVProcessChain::~cmUVProcessChain() = default;
326
operator =(cmUVProcessChain && other)327 cmUVProcessChain& cmUVProcessChain::operator=(
328 cmUVProcessChain&& other) noexcept
329 {
330 this->Data = std::move(other.Data);
331 return *this;
332 }
333
GetLoop()334 uv_loop_t& cmUVProcessChain::GetLoop()
335 {
336 return *this->Data->Loop;
337 }
338
OutputStream()339 std::istream* cmUVProcessChain::OutputStream()
340 {
341 return this->Data->OutputStreamData.GetBuiltinStream();
342 }
343
ErrorStream()344 std::istream* cmUVProcessChain::ErrorStream()
345 {
346 return this->Data->ErrorStreamData.GetBuiltinStream();
347 }
348
Valid() const349 bool cmUVProcessChain::Valid() const
350 {
351 return this->Data->Valid;
352 }
353
Wait(int64_t milliseconds)354 bool cmUVProcessChain::Wait(int64_t milliseconds)
355 {
356 bool timeout = false;
357 cm::uv_timer_ptr timer;
358
359 if (milliseconds >= 0) {
360 timer.init(*this->Data->Loop, &timeout);
361 timer.start(
362 [](uv_timer_t* handle) {
363 auto* timeoutPtr = static_cast<bool*>(handle->data);
364 *timeoutPtr = true;
365 },
366 milliseconds, 0);
367 }
368
369 while (!timeout &&
370 this->Data->ProcessesCompleted < this->Data->Processes.size()) {
371 uv_run(this->Data->Loop, UV_RUN_ONCE);
372 }
373
374 return !timeout;
375 }
376
GetStatus() const377 std::vector<const cmUVProcessChain::Status*> cmUVProcessChain::GetStatus()
378 const
379 {
380 std::vector<const cmUVProcessChain::Status*> statuses(
381 this->Data->Processes.size(), nullptr);
382 for (std::size_t i = 0; i < statuses.size(); i++) {
383 statuses[i] = this->GetStatus(i);
384 }
385 return statuses;
386 }
387
GetStatus(std::size_t index) const388 const cmUVProcessChain::Status* cmUVProcessChain::GetStatus(
389 std::size_t index) const
390 {
391 auto const& process = *this->Data->Processes[index];
392 if (process.Finished) {
393 return &process.ProcessStatus;
394 }
395 return nullptr;
396 }
397