1 | // tProcess.cpp  |
2 | //  |
3 | // This module contains a class for spawning other processes and receiving their exit-codes as well as some simple  |
4 | // commands for spawning one or many processes at once. Windows platform only.  |
5 | //  |
6 | // Copyright (c) 2005, 2017, 2019, 2020 Tristan Grimmer.  |
7 | // Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby  |
8 | // granted, provided that the above copyright notice and this permission notice appear in all copies.  |
9 | //  |
10 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL  |
11 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,  |
12 | // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN  |
13 | // AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR  |
14 | // PERFORMANCE OF THIS SOFTWARE.  |
15 |   |
16 | #include <System/tThrow.h>  |
17 | #include <System/tPrint.h>  |
18 | #include <System/tTime.h>  |
19 | #include <Foundation/tFundamentals.h>  |
20 | #include "Pipeline/tProcess.h"  |
21 | using namespace tPipeline;  |
22 |   |
23 |   |
24 | // @todo Currently the process stuff, due to the output stream messaging, is windows only. I'm sure with modern C++  |
25 | // this can be abstracted to be multiplatform.  |
26 | #ifdef PLATFORM_WINDOWS  |
27 |   |
28 |   |
29 | tProcess::tProcess(const tString& cmdLine, const tString& workDir, WindowHandle parent, uint32 userData, bool clearEnvironmentVars, int numEnvPairs, ...) :  |
30 | Parent(parent),  |
31 | OutputString(nullptr),  |
32 | PrintCallback(nullptr),  |
33 | PrintCallbackUserPointer(nullptr),  |
34 | ExitCallback(nullptr),  |
35 | ExitCallbackUserPointer(nullptr),  |
36 | ChildProcess(0),  |
37 | ChildThread(0),  |
38 | MonitorProcessExitThread(0),  |
39 | MonitorProcessStdOutThread(0),  |
40 | MonitorProcessStdErrThread(0),  |
41 | StdOutRead(0),  |
42 | StdOutWrite(0),  |
43 | StdErrRead(0),  |
44 | StdErrWrite(0),  |
45 | UserData(userData),  |
46 | ExitCode(nullptr),  |
47 | ClearEnvironment(clearEnvironmentVars),  |
48 | WaitInDestructor(true),  |
49 | Environment(nullptr)  |
50 | {  |
51 | if (numEnvPairs > 0)  |
52 | {  |
53 | va_list args;  |
54 | va_start(args, numEnvPairs);  |
55 | Environment = tProcess::BuildNewEnvironmentData_Ascii( !clearEnvironmentVars, numEnvPairs, args );  |
56 | va_end(args);  |
57 | }  |
58 |   |
59 | CreateChildProcess(cmdLine, workDir);  |
60 | }  |
61 |   |
62 |   |
63 | tProcess::tProcess(const tString& cmdLine, const tString& workDir, WindowHandle parent, uint32 userData, bool clearEnvironmentVars, int numEnvPairs, va_list args) :  |
64 | Parent(parent),  |
65 | OutputString(nullptr),  |
66 | PrintCallback(nullptr),  |
67 | PrintCallbackUserPointer(nullptr),  |
68 | ExitCallback(nullptr),  |
69 | ExitCallbackUserPointer(nullptr),  |
70 | ChildProcess(0),  |
71 | ChildThread(0),  |
72 | MonitorProcessExitThread(0),  |
73 | MonitorProcessStdOutThread(0),  |
74 | MonitorProcessStdErrThread(0),  |
75 | StdOutRead(0),  |
76 | StdOutWrite(0),  |
77 | StdErrRead(0),  |
78 | StdErrWrite(0),  |
79 | UserData(userData),  |
80 | ExitCode(nullptr),  |
81 | ClearEnvironment(clearEnvironmentVars),  |
82 | WaitInDestructor(true),  |
83 | Environment(nullptr)  |
84 | {  |
85 | if (numEnvPairs > 0)  |
86 | Environment = tProcess::BuildNewEnvironmentData_Ascii( !clearEnvironmentVars, numEnvPairs, args );  |
87 |   |
88 | CreateChildProcess(cmdLine, workDir);  |
89 | }  |
90 |   |
91 |   |
92 | tProcess::tProcess(const tString& cmdLine, const tString& workDir, WinHandle& waitHandle, ulong* exitCode, bool clearEnvironmentVars) :  |
93 | Parent(0),  |
94 | OutputString(nullptr),  |
95 | PrintCallback(nullptr),  |
96 | PrintCallbackUserPointer(nullptr),  |
97 | ExitCallback(nullptr),  |
98 | ExitCallbackUserPointer(nullptr),  |
99 | ChildProcess(0),  |
100 | ChildThread(0),  |
101 | MonitorProcessExitThread(0),  |
102 | MonitorProcessStdOutThread(0),  |
103 | MonitorProcessStdErrThread(0),  |
104 | StdOutRead(0),  |
105 | StdOutWrite(0),  |
106 | StdErrRead(0),  |
107 | StdErrWrite(0),  |
108 | UserData(0),  |
109 | ExitCode(exitCode),  |
110 | ClearEnvironment(clearEnvironmentVars),  |
111 |   |
112 | // For this non-blocking constructor, the user of the object is required to wait on the handle!  |
113 | WaitInDestructor(false),  |
114 | Environment(nullptr)  |
115 | {  |
116 | CreateChildProcess(cmdLine, workDir);  |
117 | waitHandle = MonitorProcessExitThread;  |
118 | }  |
119 |   |
120 |   |
121 | tProcess::tProcess(const tString& cmdLine, const tString& workDir, tString& output, ulong* exitCode, bool clearEnvironmentVars, int numEnvPairs, ...) :  |
122 | Parent(0),  |
123 | OutputString(&output),  |
124 | PrintCallback(nullptr),  |
125 | PrintCallbackUserPointer(nullptr),  |
126 | ExitCallback(nullptr),  |
127 | ExitCallbackUserPointer(nullptr),  |
128 | ChildProcess(0),  |
129 | ChildThread(0),  |
130 | MonitorProcessExitThread(0),  |
131 | MonitorProcessStdOutThread(0),  |
132 | MonitorProcessStdErrThread(0),  |
133 | StdOutRead(0),  |
134 | StdOutWrite(0),  |
135 | StdErrRead(0),  |
136 | StdErrWrite(0),  |
137 | UserData(0),  |
138 | ExitCode(exitCode),  |
139 | ClearEnvironment(clearEnvironmentVars),  |
140 | WaitInDestructor(false),  |
141 | Environment(nullptr)  |
142 | {  |
143 | if (numEnvPairs > 0)  |
144 | {  |
145 | va_list args;  |
146 | va_start(args, numEnvPairs);  |
147 | Environment = tProcess::BuildNewEnvironmentData_Ascii( !clearEnvironmentVars, numEnvPairs, args );  |
148 | va_end(args);  |
149 | }  |
150 |   |
151 | CreateChildProcess(cmdLine, workDir);  |
152 |   |
153 | // Wait for exit threads to finish. After this point we can be sure that the exitCode has been  |
154 | // updated if it was non-null. Note: The exit thread waits for the other threads to finish.  |
155 | // Since we are waiting here (blocking), no need to wait in destructor.  |
156 | WaitForSingleObject(MonitorProcessExitThread, INFINITE);  |
157 | }  |
158 |   |
159 |   |
160 | tProcess::tProcess(const tString& cmdLine, const tString& workDir, tString& output, ulong* exitCode, bool clearEnvironmentVars, int numEnvPairs , va_list args) :  |
161 | Parent(0),  |
162 | OutputString(&output),  |
163 | PrintCallback(nullptr),  |
164 | PrintCallbackUserPointer(nullptr),  |
165 | ExitCallback(nullptr),  |
166 | ExitCallbackUserPointer(nullptr),  |
167 | ChildProcess(0),  |
168 | ChildThread(0),  |
169 | MonitorProcessExitThread(0),  |
170 | MonitorProcessStdOutThread(0),  |
171 | MonitorProcessStdErrThread(0),  |
172 | StdOutRead(0),  |
173 | StdOutWrite(0),  |
174 | StdErrRead(0),  |
175 | StdErrWrite(0),  |
176 | UserData(0),  |
177 | ExitCode(exitCode),  |
178 | ClearEnvironment(clearEnvironmentVars),  |
179 | WaitInDestructor(false),  |
180 | Environment(nullptr)  |
181 | {  |
182 | if (numEnvPairs > 0)  |
183 | Environment = tProcess::BuildNewEnvironmentData_Ascii( !clearEnvironmentVars, numEnvPairs, args );  |
184 |   |
185 | CreateChildProcess(cmdLine, workDir);  |
186 |   |
187 | // Wait for exit threads to finish. After this point we can be sure that the exitCode has been  |
188 | // updated if it was non-null. Note: The exit thread waits for the other threads to finish.  |
189 | // Since we are waiting here (blocking), no need to wait in destructor.  |
190 | WaitForSingleObject(MonitorProcessExitThread, INFINITE);  |
191 | }  |
192 |   |
193 |   |
194 | tProcess::tProcess(const tString& cmdLine, const tString& workDir, ulong* exitCode, bool clearEnvironmentVars, int numEnvPairs, ...) :  |
195 | Parent(0),  |
196 | OutputString(nullptr),  |
197 | PrintCallback(nullptr),  |
198 | PrintCallbackUserPointer(nullptr),  |
199 | ExitCallback(nullptr),  |
200 | ExitCallbackUserPointer(nullptr),  |
201 | ChildProcess(0),  |
202 | ChildThread(0),  |
203 | MonitorProcessExitThread(0),  |
204 | MonitorProcessStdOutThread(0),  |
205 | MonitorProcessStdErrThread(0),  |
206 | StdOutRead(0),  |
207 | StdOutWrite(0),  |
208 | StdErrRead(0),  |
209 | StdErrWrite(0),  |
210 | UserData(0),  |
211 | ExitCode(exitCode),  |
212 | ClearEnvironment(clearEnvironmentVars),  |
213 | WaitInDestructor(false),  |
214 | Environment(nullptr)  |
215 | {  |
216 | if (numEnvPairs > 0)  |
217 | {  |
218 | va_list args;  |
219 | va_start(args, numEnvPairs);  |
220 | Environment = tProcess::BuildNewEnvironmentData_Ascii( !clearEnvironmentVars, numEnvPairs, args );  |
221 | va_end(args);  |
222 | }  |
223 |   |
224 | CreateChildProcess(cmdLine, workDir);  |
225 |   |
226 | // Wait for exit threads to finish. After this point we can be sure that the exitCode has been  |
227 | // updated if it was non-null. Note: The exit thread waits for the other threads to finish.  |
228 | // Since we are waiting here (blocking), no need to wait in destructor.  |
229 | WaitForSingleObject(MonitorProcessExitThread, INFINITE);  |
230 | }  |
231 |   |
232 |   |
233 | tProcess::tProcess(const tString& cmdLine, const tString& workDir, ulong* exitCode, bool clearEnvironmentVars, int numEnvPairs, va_list args) :  |
234 | Parent(0),  |
235 | OutputString(nullptr),  |
236 | PrintCallback(nullptr),  |
237 | PrintCallbackUserPointer(nullptr),  |
238 | ExitCallback(nullptr),  |
239 | ExitCallbackUserPointer(nullptr),  |
240 | ChildProcess(0),  |
241 | ChildThread(0),  |
242 | MonitorProcessExitThread(0),  |
243 | MonitorProcessStdOutThread(0),  |
244 | MonitorProcessStdErrThread(0),  |
245 | StdOutRead(0),  |
246 | StdOutWrite(0),  |
247 | StdErrRead(0),  |
248 | StdErrWrite(0),  |
249 | UserData(0),  |
250 | ExitCode(exitCode),  |
251 | ClearEnvironment(clearEnvironmentVars),  |
252 | WaitInDestructor(false),  |
253 | Environment(nullptr)  |
254 | {  |
255 | if (numEnvPairs > 0)  |
256 | Environment = tProcess::BuildNewEnvironmentData_Ascii( !clearEnvironmentVars, numEnvPairs, args );  |
257 |   |
258 | CreateChildProcess(cmdLine, workDir);  |
259 |   |
260 | // Wait for exit threads to finish. After this point we can be sure that the exitCode has been  |
261 | // updated if it was non-null. Note: The exit thread waits for the other threads to finish.  |
262 | // Since we are waiting here (blocking), no need to wait in destructor.  |
263 | WaitForSingleObject(MonitorProcessExitThread, INFINITE);  |
264 | }  |
265 |   |
266 |   |
267 | tProcess::tProcess(const tString& cmdLine, const tString& workDir, ulong* exitCode, tPrintCallback pc, void* user) :  |
268 | Parent(0),  |
269 | OutputString(nullptr),  |
270 | PrintCallback(pc),  |
271 | PrintCallbackUserPointer(user),  |
272 | ExitCallback(nullptr),  |
273 | ExitCallbackUserPointer(nullptr),  |
274 | ChildProcess(0),  |
275 | ChildThread(0),  |
276 | MonitorProcessExitThread(0),  |
277 | MonitorProcessStdOutThread(0),  |
278 | MonitorProcessStdErrThread(0),  |
279 | StdOutRead(0),  |
280 | StdOutWrite(0),  |
281 | StdErrRead(0),  |
282 | StdErrWrite(0),  |
283 | UserData(0),  |
284 | ExitCode(exitCode),  |
285 | ClearEnvironment(true),  |
286 | WaitInDestructor(false),  |
287 | Environment(nullptr)  |
288 | {  |
289 | CreateChildProcess(cmdLine, workDir);  |
290 | WaitForSingleObject(MonitorProcessExitThread, INFINITE);  |
291 | }  |
292 |   |
293 |   |
294 | tProcess::tProcess(const tString& cmd, const tString& wd, tExitCallback ec, void* ecud, tPrintCallback pc, void* pcud) :  |
295 | Parent(0),  |
296 | OutputString(nullptr),  |
297 | PrintCallback(pc),  |
298 | PrintCallbackUserPointer(pcud),  |
299 | ExitCallback(ec),  |
300 | ExitCallbackUserPointer(ecud),  |
301 | ChildProcess(0),  |
302 | ChildThread(0),  |
303 | MonitorProcessExitThread(0),  |
304 | MonitorProcessStdOutThread(0),  |
305 | MonitorProcessStdErrThread(0),  |
306 | StdOutRead(0),  |
307 | StdOutWrite(0),  |
308 | StdErrRead(0),  |
309 | StdErrWrite(0),  |
310 | UserData(0),  |
311 | ExitCode(nullptr),  |
312 | ClearEnvironment(true),  |
313 | WaitInDestructor(true),  |
314 | Environment(nullptr)  |
315 | {  |
316 | CreateChildProcess(cmd, wd);  |
317 | }  |
318 |   |
319 |   |
320 | tProcess::tProcess(const tString& cmd, const tString& wd) :  |
321 | Parent(0),  |
322 | OutputString(nullptr),  |
323 | PrintCallback(nullptr),  |
324 | PrintCallbackUserPointer(nullptr),  |
325 | ExitCallback(nullptr),  |
326 | ExitCallbackUserPointer(nullptr),  |
327 | ChildProcess(0),  |
328 | ChildThread(0),  |
329 | MonitorProcessExitThread(0),  |
330 | MonitorProcessStdOutThread(0),  |
331 | MonitorProcessStdErrThread(0),  |
332 | StdOutRead(0),  |
333 | StdOutWrite(0),  |
334 | StdErrRead(0),  |
335 | StdErrWrite(0),  |
336 | UserData(0),  |
337 | ExitCode(nullptr),  |
338 | ClearEnvironment(true),  |
339 | WaitInDestructor(false),  |
340 | Environment(nullptr)  |
341 | {  |
342 | bool detached = true;  |
343 | CreateChildProcess(cmd, wd, detached);  |
344 | }  |
345 |   |
346 |   |
347 | void tProcess::CreateChildProcess(const tString& cmdLine, const tString& workingDir, bool detached)  |
348 | {  |
349 | if (ExitCode)  |
350 | *ExitCode = 0;  |
351 |   |
352 | // Create the pipes we'll be using.   |
353 | SECURITY_ATTRIBUTES secAttr;  |
354 | secAttr.nLength = sizeof(SECURITY_ATTRIBUTES);   |
355 |   |
356 | // Set the bInheritHandle flag so pipe handles are inherited.  |
357 | secAttr.bInheritHandle = detached ? False : True;  |
358 | secAttr.lpSecurityDescriptor = 0;  |
359 |   |
360 | // By setting the pipe size to be small, we get the process output much sooner  |
361 | // since it can't collect in the pipe.  |
362 | if (!detached)  |
363 | {  |
364 | const int suggestedPipeSizeBytes = 32;  |
365 | WinBool success = CreatePipe(&StdOutRead, &StdOutWrite, &secAttr, suggestedPipeSizeBytes);  |
366 | if (!success)  |
367 | {  |
368 | if (ExitCode)  |
369 | *ExitCode = 1;  |
370 | throw tError("Can not create child pipe." );  |
371 | }  |
372 |   |
373 | success = CreatePipe(&StdErrRead, &StdErrWrite, &secAttr, suggestedPipeSizeBytes);  |
374 | if (!success)  |
375 | {  |
376 | if (ExitCode)  |
377 | *ExitCode = 1;  |
378 | throw tError("Can not create child pipe." );  |
379 | }  |
380 | }  |
381 |   |
382 | // Create the Child process.  |
383 | STARTUPINFO startup;  |
384 | startup.cb = sizeof(STARTUPINFO);  |
385 | startup.lpReserved = 0;  |
386 | startup.lpDesktop = 0;  |
387 | startup.lpTitle = 0;  |
388 | startup.dwX = 0;  |
389 | startup.dwY = 0;  |
390 | startup.dwXSize = 0;  |
391 | startup.dwYSize = 0;  |
392 | startup.dwXCountChars = 0;  |
393 | startup.dwYCountChars = 0;  |
394 | startup.dwFillAttribute = 0;  |
395 | startup.dwFlags = detached ? 0 : STARTF_USESTDHANDLES;  |
396 | startup.wShowWindow = 0;  |
397 | startup.cbReserved2 = 0;  |
398 | startup.lpReserved2 = 0;  |
399 | startup.hStdInput = 0;  |
400 | startup.hStdOutput = detached ? 0 : StdOutWrite;  |
401 | startup.hStdError = detached ? 0 : StdErrWrite;  |
402 |   |
403 | // This struct gets filled out.  |
404 | PROCESS_INFORMATION procInfo;  |
405 |   |
406 | // The environment block sets the environment variables for the command. Here we make sure that there are  |
407 | // effectively none of them if the user requested such. This ensures that the running bahaviour is identical on any  |
408 | // machine no matter what system env variables might be set. A null env block (Environment) means inherit from the  |
409 | // parent process which is what we are trying to avoid in some cases.  |
410 | char* envBlock = Environment;  |
411 |   |
412 | // Assign a default env block in this case. We could probably call it with "\0\0", but this seems safer as the  |
413 | // windows docs to not explicitely mention passing in an empty block.  |
414 | if (ClearEnvironment)  |
415 | envBlock = "PIPELINE=true\0" ;  |
416 |   |
417 | int success = CreateProcess(0, (char*)cmdLine.ConstText(), 0, 0, TRUE, DETACHED_PROCESS, envBlock, workingDir.ConstText(), &startup, &procInfo);  |
418 | if (!success)  |
419 | {  |
420 | ulong lastError = GetLastError();  |
421 | if (ExitCode)  |
422 | *ExitCode = 1;  |
423 |   |
424 | throw tError("CreateProcess failed with %d (0x%08X). Possibly due to windows security settings or invalid working dir." , lastError, lastError);  |
425 | }  |
426 | ChildProcess = procInfo.hProcess;  |
427 | ChildThread = procInfo.hThread;  |
428 |   |
429 | if (detached)  |
430 | {  |
431 | CloseHandle(ChildProcess);  |
432 | ChildProcess = 0;  |
433 | CloseHandle(ChildThread);  |
434 | ChildThread = 0;  |
435 | }  |
436 | else  |
437 | {  |
438 | // Create Monitor threads. These ones send the messages or print the output.  |
439 | ulong threadID;  |
440 | MonitorProcessExitThread = CreateThread(0, 0, tProcess::MonitorExitBridge, this, 0, &threadID);  |
441 | MonitorProcessStdOutThread = CreateThread(0, 0, tProcess::MonitorStdOutBridge, this, 0, &threadID);  |
442 | MonitorProcessStdErrThread = CreateThread(0, 0, tProcess::MonitorStdErrBridge, this, 0, &threadID);  |
443 | }  |
444 | }  |
445 |   |
446 |   |
447 | void tProcess::Terminate()  |
448 | {  |
449 | if (!ChildProcess)  |
450 | return;  |
451 |   |
452 | uint32 exitCode = 23;  |
453 | WinBool success = TerminateProcess(ChildProcess, exitCode);  |
454 |   |
455 | // Note we do NOT set ChildProcess to 0 here. The MonitorExit thread should handle this for us.  |
456 | }  |
457 |   |
458 |   |
459 | void tProcess::TerminateHard()  |
460 | {  |
461 | if (!ChildProcess)  |
462 | return;  |
463 |   |
464 | uint32 exitCode = 23;  |
465 | WinBool success = TerminateProcess(ChildProcess, exitCode);  |
466 |   |
467 | Parent = 0;  |
468 | WaitForSingleObject(MonitorProcessExitThread, INFINITE);  |
469 | WaitInDestructor = false;  |
470 |   |
471 | if (MonitorProcessExitThread)  |
472 | {  |
473 | CloseHandle(MonitorProcessExitThread);  |
474 | MonitorProcessExitThread = 0;  |
475 | }  |
476 |   |
477 | if (MonitorProcessStdOutThread)  |
478 | {  |
479 | CloseHandle(MonitorProcessStdOutThread);  |
480 | MonitorProcessStdOutThread = 0;  |
481 | }  |
482 |   |
483 | if (MonitorProcessStdErrThread)  |
484 | {  |
485 | CloseHandle(MonitorProcessStdErrThread);  |
486 | MonitorProcessStdErrThread = 0;  |
487 | }  |
488 |   |
489 | if (StdOutRead)  |
490 | {  |
491 | CloseHandle(StdOutRead);  |
492 | StdOutRead = 0;  |
493 | }  |
494 |   |
495 | if (StdOutWrite)  |
496 | {  |
497 | CloseHandle(StdOutWrite);  |
498 | StdOutWrite = 0;  |
499 | }  |
500 |   |
501 | if (StdErrRead)  |
502 | {  |
503 | CloseHandle(StdErrRead);  |
504 | StdErrRead = 0;  |
505 | }  |
506 |   |
507 | if (StdErrWrite)  |
508 | {  |
509 | CloseHandle(StdErrWrite);  |
510 | StdErrWrite = 0;  |
511 | }  |
512 | }  |
513 |   |
514 |   |
515 | tProcess::~tProcess()  |
516 | {  |
517 | delete[] Environment;  |
518 |   |
519 | // Wait for exit threads to finish before closing the handles! This destructor will block until  |
520 | // the process has finished what it is doing. Once unblocked, if there is a parent window, we  |
521 | // send a message. Note: The exit thread waits for the other threads to finish.  |
522 | if (WaitInDestructor)  |
523 | WaitForSingleObject(MonitorProcessExitThread, INFINITE);  |
524 |   |
525 | if (MonitorProcessExitThread)  |
526 | CloseHandle(MonitorProcessExitThread);  |
527 |   |
528 | if (MonitorProcessStdOutThread)  |
529 | CloseHandle(MonitorProcessStdOutThread);  |
530 |   |
531 | if (MonitorProcessStdErrThread)  |
532 | CloseHandle(MonitorProcessStdErrThread);  |
533 |   |
534 | if (StdOutRead)  |
535 | CloseHandle(StdOutRead);  |
536 |   |
537 | if (StdOutWrite)  |
538 | CloseHandle(StdOutWrite);  |
539 |   |
540 | if (StdErrRead)  |
541 | CloseHandle(StdErrRead);  |
542 |   |
543 | if (StdErrWrite)  |
544 | CloseHandle(StdErrWrite);  |
545 | }  |
546 |   |
547 |   |
548 | void tProcess::MonitorExit()  |
549 | {  |
550 | // Wait until the child process is finished doing its thing.  |
551 | WaitForSingleObject(ChildProcess, INFINITE);  |
552 |   |
553 | // It's important that we set this to failure by default. It seems that  |
554 | // sometimes the GetExitCode function will not touch it... in particular, if  |
555 | // the working dir did not exist.  |
556 | ulong exitCode = 42;  |
557 | GetExitCodeProcess(ChildProcess, &exitCode);  |
558 |   |
559 | // Now the pipe readers know that everything has stopped.  |
560 | CloseHandle(ChildProcess);  |
561 | ChildProcess = 0;  |
562 | CloseHandle(ChildThread);  |
563 | ChildThread = 0;  |
564 |   |
565 | if (ExitCode)  |
566 | *ExitCode = exitCode;  |
567 |   |
568 | if (ExitCallback)  |
569 | ExitCallback(ExitCallbackUserPointer, exitCode);  |
570 |   |
571 | WinHandle handles[] =  |
572 | {  |
573 | MonitorProcessStdOutThread,  |
574 | MonitorProcessStdErrThread,  |
575 | };  |
576 |   |
577 | // Wait until all output messages have been sent.  |
578 | WaitForMultipleObjects(2, handles, TRUE, INFINITE);  |
579 |   |
580 | if (Parent)  |
581 | PostMessage(Parent, uint(tMessage::ProcessDone), exitCode, UserData);  |
582 |   |
583 | ExitThread(0);  |
584 | }  |
585 |   |
586 |   |
587 | void tProcess::MonitorStdOut()  |
588 | {  |
589 | bool lastWasNewline = false;  |
590 | while (1)  |
591 | {  |
592 | ulong avail;  |
593 | PeekNamedPipe(StdOutRead, 0, 0, 0, &avail, 0);  |
594 |   |
595 | while (avail > 0)  |
596 | {  |
597 | const int bufSize = 64;  |
598 | tString buf(bufSize);  |
599 |   |
600 | int numToRead = tMath::tMin(bufSize - 1, int(avail));  |
601 |   |
602 | ulong numRead;  |
603 | WinBool success = ReadFile(StdOutRead, buf.Text(), numToRead, &numRead, 0);  |
604 | if (success)  |
605 | {  |
606 | buf.Replace('\r', ' ');  |
607 |   |
608 | if (Parent)  |
609 | PostMessage(Parent, uint(tMessage::StdOutString), WinWParam(new tString(buf)), 0);  |
610 |   |
611 | if (OutputString)  |
612 | *OutputString += buf;  |
613 |   |
614 | if (PrintCallback)  |
615 | PrintCallback(PrintCallbackUserPointer, buf.ConstText());  |
616 |   |
617 | // We only go to stdout if all other methods failed.  |
618 | if (!Parent && !OutputString && !PrintCallback)  |
619 | tPrintf("%s" , buf.Pod());  |
620 |   |
621 | avail -= numRead;  |
622 | }  |
623 | }  |
624 |   |
625 | if (!Parent)  |
626 | tFlush(stdout);  |
627 |   |
628 | if (!ChildProcess)  |
629 | ExitThread(0);  |
630 |   |
631 | // Not required... just keeps CPU overhead way down.  |
632 | tSystem::tSleep(10);  |
633 | }  |
634 | }  |
635 |   |
636 |   |
637 | void tProcess::MonitorStdErr()  |
638 | {  |
639 | while (1)  |
640 | {  |
641 | ulong avail;  |
642 | PeekNamedPipe(StdErrRead, 0, 0, 0, &avail, 0);  |
643 |   |
644 | while (avail > 0)  |
645 | {  |
646 | const int bufSize = 64;  |
647 |   |
648 | // Remember, this gets all 0s.  |
649 | tString buf(bufSize);  |
650 |   |
651 | int numToRead = tMath::tMin(bufSize - 1, int(avail));  |
652 |   |
653 | ulong numRead;  |
654 | WinBool success = ReadFile(StdErrRead, buf.Text(), numToRead, &numRead, 0);   |
655 | if (success)  |
656 | {  |
657 | buf.Replace('\r', ' ');  |
658 |   |
659 | if (Parent)  |
660 | PostMessage(Parent, uint(tMessage::StdErrString), WinWParam(new tString(buf)), 0);  |
661 |   |
662 | //if (OutputString)  |
663 | // *OutputString += buf;  |
664 |   |
665 | if (PrintCallback)  |
666 | PrintCallback(PrintCallbackUserPointer, buf.ConstText());  |
667 |   |
668 | // We only go to stdout if all other methods failed.  |
669 | if (!Parent && !OutputString && !PrintCallback)  |
670 | tPrintf("%s" , buf.Pod());  |
671 |   |
672 | avail -= numRead;  |
673 | }  |
674 | }  |
675 |   |
676 | if (!Parent)  |
677 | tFlush(stdout);  |
678 |   |
679 | if (!ChildProcess)  |
680 | ExitThread(0);  |
681 |   |
682 | // Not required... just keeps CPU overhead way down.  |
683 | tSystem::tSleep(10);  |
684 | }  |
685 | }  |
686 |   |
687 |   |
688 | uint32 tProcess::GetEnvironmentDataLength_Ascii(void* enviro)  |
689 | {  |
690 | char* envStr = (char*)enviro;  |
691 | int envIx = 0;  |
692 |   |
693 | bool doneAll = false;  |
694 |   |
695 | while (!doneAll)  |
696 | {  |
697 | while (envStr[envIx++]);  |
698 | if (envStr[envIx] == 0)  |
699 | {  |
700 | envIx++;  |
701 | doneAll = true;  |
702 | }  |
703 | }  |
704 |   |
705 | return envIx;  |
706 | }  |
707 |   |
708 |   |
709 | char* tProcess::BuildNewEnvironmentData_Ascii(bool appendToExisting, int numPairs, va_list args)  |
710 | {  |
711 | char* oldEnviro = 0;  |
712 | if (appendToExisting)  |
713 | oldEnviro = ::GetEnvironmentStrings();  |
714 |   |
715 | const char pairSeparatingCharacter = '\0';  |
716 |   |
717 | tList<tStringItem> names(tListMode::ListOwns);  |
718 | tList<tStringItem> values(tListMode::ListOwns);  |
719 |   |
720 | int oldSize = 0;  |
721 | if (oldEnviro)  |
722 | oldSize = tProcess::GetEnvironmentDataLength_Ascii(oldEnviro);  |
723 |   |
724 | int newSize = 0;  |
725 | for (int p = 0; p < numPairs; ++p)  |
726 | {  |
727 | const char* name = va_arg(args, const char*);  |
728 | const char* value = va_arg(args, const char*);  |
729 |   |
730 | newSize += int(strlen(name)) + 1;  |
731 | newSize += int(strlen(value)) + 1;  |
732 |   |
733 | names.Append( new tStringItem(name) );  |
734 | values.Append( new tStringItem(value) );  |
735 | }  |
736 |   |
737 | int totalSize = oldSize + newSize;  |
738 |   |
739 | char* newenvdata = new char[totalSize];  |
740 | int newDstIdx = 0;  |
741 | if (oldEnviro)  |
742 | {  |
743 | tStd::tMemcpy(newenvdata, oldEnviro, oldSize);  |
744 | newDstIdx = oldSize - 1;  |
745 | }  |
746 |   |
747 | tStringItem* name = names.First();  |
748 | tStringItem* value = values.First();  |
749 | for (; name && value; name = name->Next(), value = value->Next())  |
750 | {  |
751 | int namelen = name->Length();  |
752 | int valuelen = value->Length();  |
753 |   |
754 | tStd::tMemcpy(&newenvdata[newDstIdx], name->ConstText(), namelen);  |
755 | newenvdata[newDstIdx+namelen] = '=';  |
756 | tStd::tMemcpy(&newenvdata[newDstIdx+namelen+1], value->ConstText(), valuelen);  |
757 | newenvdata[newDstIdx+namelen+1+valuelen] = pairSeparatingCharacter;  |
758 | newDstIdx += namelen+1+valuelen+1;  |
759 | }   |
760 | newenvdata[newDstIdx] = '\0';  |
761 |   |
762 | ::FreeEnvironmentStrings(oldEnviro);  |
763 | return newenvdata;  |
764 | }  |
765 |   |
766 |   |
767 | #endif  |
768 | |