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