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" 
21using 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 
29tProcess::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 
63tProcess::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 
92tProcess::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 
121tProcess::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 
160tProcess::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 
194tProcess::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 
233tProcess::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 
267tProcess::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 
294tProcess::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 
320tProcess::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 
347void 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 
447void 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 
459void 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 
515tProcess::~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 
548void 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 
587void 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 
637void 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 
688uint32 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 
709char* 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