"Fossies" - the Fresh Open Source Software Archive 
Member "muscle/dataio/ChildProcessDataIO.cpp" (21 Nov 2020, 35083 Bytes) of package /linux/privat/muscle7.62.zip:
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style:
standard) with prefixed line numbers and
code folding option.
Alternatively you can here
view or
download the uninterpreted source code file.
For more information about "ChildProcessDataIO.cpp" see the
Fossies "Dox" file reference documentation and the latest
Fossies "Diffs" side-by-side code changes report:
7.61_vs_7.62.
1 /* This file is Copyright 2000-2013 Meyer Sound Laboratories Inc. See the included LICENSE.txt file for details. */
2
3 #include <limits.h> // for PATH_MAX
4 #include "dataio/ChildProcessDataIO.h"
5 #include "util/MiscUtilityFunctions.h" // for ExitWithoutCleanup()
6 #include "util/NetworkUtilityFunctions.h" // SendData() and ReceiveData()
7 #include "util/SocketMultiplexer.h"
8
9 #if defined(WIN32) || defined(__CYGWIN__)
10 # include <process.h> // for _beginthreadex()
11 # if defined(_UNICODE) || defined(UNICODE)
12 # undef GetEnvironmentStrings // here because Windows headers are FUBAR ( https://devblogs.microsoft.com/oldnewthing/20130117-00/?p=5533 )
13 # endif
14 # define USE_WINDOWS_CHILDPROCESSDATAIO_IMPLEMENTATION
15 #else
16 # if defined(__linux__)
17 # include <pty.h> // for forkpty() on Linux
18 # elif !defined(MUSCLE_AVOID_FORKPTY)
19 # include <util.h> // for forkpty() on MacOS/X
20 # endif
21 # include <termios.h>
22 # include <signal.h> // for SIGHUP, etc
23 # include <sys/wait.h> // for waitpid()
24 #endif
25
26 #if defined(__APPLE__) && defined(MUSCLE_ENABLE_AUTHORIZATION_EXECUTE_WITH_PRIVILEGES)
27 # include <fcntl.h> // for F_GETOWN
28 # include <Security/Authorization.h>
29 # include <Security/AuthorizationTags.h>
30 #endif
31
32 #include "util/NetworkUtilityFunctions.h"
33 #include "util/String.h"
34 #include "util/StringTokenizer.h"
35
36 namespace muscle {
37
38 ChildProcessDataIO :: ChildProcessDataIO(bool blocking)
39 : _blocking(blocking)
40 , _killChildOkay(true)
41 , _maxChildWaitTime(0)
42 , _signalNumber(-1)
43 , _childProcessCrashed(false)
44 , _childProcessIsIndependent(false)
45 #ifdef USE_WINDOWS_CHILDPROCESSDATAIO_IMPLEMENTATION
46 , _readFromStdout(INVALID_HANDLE_VALUE), _writeToStdin(INVALID_HANDLE_VALUE), _ioThread(INVALID_HANDLE_VALUE), _wakeupSignal(INVALID_HANDLE_VALUE), _childProcess(INVALID_HANDLE_VALUE), _childThread(INVALID_HANDLE_VALUE), _requestThreadExit(false)
47 #else
48 , _childPID(-1)
49 #endif
50 #if defined(__APPLE__) && defined(MUSCLE_ENABLE_AUTHORIZATION_EXECUTE_WITH_PRIVILEGES)
51 , _authRef(NULL)
52 #endif
53 {
54 // empty
55 }
56
57 #ifdef USE_WINDOWS_CHILDPROCESSDATAIO_IMPLEMENTATION
58 static void SafeCloseHandle(::HANDLE & h)
59 {
60 if (h != INVALID_HANDLE_VALUE)
61 {
62 CloseHandle(h);
63 h = INVALID_HANDLE_VALUE;
64 }
65 }
66 #endif
67
68 status_t ChildProcessDataIO :: LaunchChildProcess(const Queue<String> & argq, ChildProcessLaunchFlags launchFlags, const char * optDirectory, const Hashtable<String, String> * optEnvironmentVariables)
69 {
70 const uint32 numItems = argq.GetNumItems();
71 if (numItems == 0) return B_BAD_ARGUMENT;
72
73 const char ** argv = newnothrow_array(const char *, numItems+1);
74 if (argv == NULL) RETURN_OUT_OF_MEMORY;
75 for (uint32 i=0; i<numItems; i++) argv[i] = argq[i]();
76 argv[numItems] = NULL;
77 status_t ret = LaunchChildProcess(numItems, argv, launchFlags, optDirectory, optEnvironmentVariables);
78 delete [] argv;
79 return ret;
80 }
81
82 void ChildProcessDataIO :: SetChildProcessShutdownBehavior(bool okayToKillChild, int sendSignalNumber, uint64 maxChildWaitTime)
83 {
84 _killChildOkay = okayToKillChild;
85 _signalNumber = sendSignalNumber;
86 _maxChildWaitTime = maxChildWaitTime;
87 }
88
89 status_t ChildProcessDataIO :: LaunchChildProcessAux(int argc, const void * args, ChildProcessLaunchFlags launchFlags, const char * optDirectory, const Hashtable<String, String> * optEnvironmentVariables)
90 {
91 TCHECKPOINT;
92
93 Close(); // paranoia
94 _childProcessCrashed = false; // we don't care about the crashed-flag of a previous process anymore!
95
96 #ifdef MUSCLE_AVOID_FORKPTY
97 launchFlags.ClearBit(CHILD_PROCESS_LAUNCH_FLAG_USE_FORKPTY); // no sense trying to use pseudo-terminals if they were forbidden at compile time
98 #endif
99
100 #ifdef USE_WINDOWS_CHILDPROCESSDATAIO_IMPLEMENTATION
101 SECURITY_ATTRIBUTES saAttr;
102 {
103 memset(&saAttr, 0, sizeof(saAttr));
104 saAttr.nLength = sizeof(saAttr);
105 saAttr.bInheritHandle = true;
106 }
107
108 status_t ret;
109
110 ::HANDLE childStdoutRead, childStdoutWrite;
111 if (CreatePipe(&childStdoutRead, &childStdoutWrite, &saAttr, 0))
112 {
113 if (DuplicateHandle(GetCurrentProcess(), childStdoutRead, GetCurrentProcess(), &_readFromStdout, 0, false, DUPLICATE_SAME_ACCESS))
114 {
115 SafeCloseHandle(childStdoutRead); // we'll use the dup from now on
116
117 ::HANDLE childStdinRead, childStdinWrite;
118 if (CreatePipe(&childStdinRead, &childStdinWrite, &saAttr, 0))
119 {
120 if (DuplicateHandle(GetCurrentProcess(), childStdinWrite, GetCurrentProcess(), &_writeToStdin, 0, false, DUPLICATE_SAME_ACCESS))
121 {
122 SafeCloseHandle(childStdinWrite); // we'll use the dup from now on
123
124 PROCESS_INFORMATION piProcInfo;
125 memset(&piProcInfo, 0, sizeof(piProcInfo));
126
127 STARTUPINFOA siStartInfo;
128 {
129 const bool hideChildGUI = launchFlags.IsBitSet(CHILD_PROCESS_LAUNCH_FLAG_WIN32_HIDE_GUI);
130
131 memset(&siStartInfo, 0, sizeof(siStartInfo));
132 siStartInfo.cb = sizeof(siStartInfo);
133 siStartInfo.hStdError = childStdoutWrite;
134 siStartInfo.hStdOutput = childStdoutWrite;
135 siStartInfo.hStdInput = childStdinRead;
136 siStartInfo.dwFlags = STARTF_USESTDHANDLES | (hideChildGUI ? STARTF_USESHOWWINDOW : 0);
137 siStartInfo.wShowWindow = hideChildGUI ? SW_HIDE : 0;
138 }
139
140 String cmd;
141 if (argc < 0) cmd = (const char *) args;
142 else
143 {
144 const char ** argv = (const char **) args;
145 Queue<String> tmpQ; (void) tmpQ.EnsureSize(argc);
146 for (int i=0; i<argc; i++) (void) tmpQ.AddTail(argv[i]);
147 cmd = UnparseArgs(tmpQ);
148 }
149
150 // If environment-vars are specified, we need to create a new environment-variable-block
151 // for the child process to use. It will be the same as our own, but with the specified
152 // env-vars added/updated in it.
153 void * envVars = NULL;
154 char * newBlock = NULL;
155 if ((optEnvironmentVariables)&&(optEnvironmentVariables->HasItems()))
156 {
157 Hashtable<String, String> curEnvVars;
158
159 char * oldEnvs = GetEnvironmentStrings();
160 if (oldEnvs)
161 {
162 const char * s = oldEnvs;
163 while(s)
164 {
165 if (*s)
166 {
167 const char * equals = strchr(s, '=');
168 if ((equals ? curEnvVars.Put(String(s, (uint32)(equals-s)), equals+1) : curEnvVars.Put(s, GetEmptyString())).IsOK(ret)) s = strchr(s, '\0')+1;
169 else break;
170 }
171 else break;
172 }
173
174 FreeEnvironmentStringsA(oldEnvs);
175 }
176
177 (void) curEnvVars.Put(*optEnvironmentVariables); // update our existing vars with the specified ones
178
179 // Now we can make a new environment-variables-block out of (curEnvVars)
180 uint32 newBlockSize = 1; // this represents the final NUL terminator (after the last string)
181 for (HashtableIterator<String, String> iter(curEnvVars); iter.HasData(); iter++) newBlockSize += iter.GetKey().FlattenedSize()+iter.GetValue().FlattenedSize(); // includes NUL terminators
182
183 uint8 * newBlock = newnothrow uint8[newBlockSize];
184 if (newBlock)
185 {
186 uint8 * s = newBlock;
187 for (HashtableIterator<String, String> iter(curEnvVars); iter.HasData(); iter++)
188 {
189 iter.GetKey().Flatten(s); s += iter.GetKey().FlattenedSize();
190 *(s-1) = '='; // replace key's trailing-NUL with an '=' sign
191 iter.GetValue().Flatten(s); s += iter.GetValue().FlattenedSize();
192 }
193 *s++ = '\0';
194
195 envVars = newBlock;
196 }
197 else
198 {
199 WARN_OUT_OF_MEMORY;
200 ret = B_OUT_OF_MEMORY;
201 }
202 }
203
204 if (ret == B_NO_ERROR)
205 {
206 if (CreateProcessA((argc>=0)?(((const char **)args)[0]):NULL, (char *)cmd(), NULL, NULL, TRUE, 0, envVars, optDirectory, &siStartInfo, &piProcInfo))
207 {
208 delete [] newBlock;
209 newBlock = NULL; // void possible double-delete below
210
211 _childProcess = piProcInfo.hProcess;
212 _childThread = piProcInfo.hThread;
213
214 if (_blocking) return B_NO_ERROR; // done!
215 else
216 {
217 // For non-blocking, we must have a separate proxy thread do the I/O for us :^P
218 _wakeupSignal = CreateEvent(0, false, false, 0);
219 if (_wakeupSignal == INVALID_HANDLE_VALUE) ret = B_ERRNO;
220 else if (CreateConnectedSocketPair(_masterNotifySocket, _slaveNotifySocket, false).IsOK(ret))
221 {
222 DWORD junkThreadID;
223 typedef unsigned (__stdcall *PTHREAD_START) (void *);
224 if ((_ioThread = (::HANDLE) _beginthreadex(NULL, 0, (PTHREAD_START)IOThreadEntryFunc, this, 0, (unsigned *) &junkThreadID)) != INVALID_HANDLE_VALUE) return B_NO_ERROR;
225 else ret = B_ERRNO;
226 }
227 }
228 }
229 else ret = B_ERRNO;
230 }
231
232 delete [] newBlock;
233 }
234 else ret = B_ERRNO;
235
236 SafeCloseHandle(childStdinRead); // cleanup
237 SafeCloseHandle(childStdinWrite); // cleanup
238 }
239 else ret = B_ERRNO;
240 }
241 else ret = B_ERRNO;
242
243 SafeCloseHandle(childStdoutRead); // cleanup
244 SafeCloseHandle(childStdoutWrite); // cleanup
245 }
246 else ret = B_ERRNO;
247
248 Close(); // free all allocated object state we may have
249 return ret | B_ERROR;
250 #else
251 status_t ret;
252
253 // First, set up our arguments array here in the parent process, since the child process won't be able to do any dynamic allocations.
254 Queue<String> scratchChildArgQ; // holds the strings that the pointers in the argv buffer will point to
255 const bool isParsed = (argc<0);
256 if (argc < 0)
257 {
258 if (ParseArgs(String((const char *)args), scratchChildArgQ).IsError(ret)) return ret;
259 argc = scratchChildArgQ.GetNumItems();
260 }
261
262 Queue<const char *> scratchChildArgv; // the child process's argv array, NULL terminated
263 if (scratchChildArgv.EnsureSize(argc+1, true).IsError(ret)) return ret;
264
265 // Populate the argv array for our child process to use
266 const char ** argv = scratchChildArgv.HeadPointer();
267 if (isParsed) for (int i=0; i<argc; i++) argv[i] = scratchChildArgQ[i]();
268 else memcpy(argv, args, argc*sizeof(char *));
269 argv[argc] = NULL; // argv array must be NULL terminated!
270
271 #if defined(__APPLE__) && defined(MUSCLE_ENABLE_AUTHORIZATION_EXECUTE_WITH_PRIVILEGES)
272 if (_dialogPrompt.HasChars()) return LaunchPrivilegedChildProcess(argv);
273 #endif
274
275 pid_t pid = (pid_t) -1;
276 if (launchFlags.IsBitSet(CHILD_PROCESS_LAUNCH_FLAG_USE_FORKPTY))
277 {
278 # ifdef MUSCLE_AVOID_FORKPTY
279 return B_UNIMPLEMENTED; // this branch should never be taken, due to the ifdef at the top of this function... but just in case
280 # else
281 // New-fangled forkpty() implementation
282 int masterFD = -1;
283 pid = forkpty(&masterFD, NULL, NULL, NULL);
284 if (pid > 0) _handle = GetConstSocketRefFromPool(masterFD);
285 else if (pid == 0)
286 {
287 // Turn off the echo, we don't want to see that back on stdout
288 struct termios tios;
289 if (tcgetattr(STDIN_FILENO, &tios) >= 0)
290 {
291 tios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
292 tios.c_oflag &= ~(ONLCR); /* also turn off NL to CR/NL mapping on output */
293 (void) tcsetattr(STDIN_FILENO, TCSANOW, &tios);
294 }
295 }
296 #endif
297 }
298 else
299 {
300 // Old-fashioned fork() implementation
301 ConstSocketRef masterSock, slaveSock;
302 if (CreateConnectedSocketPair(masterSock, slaveSock, true).IsError(ret)) return ret;
303 pid = fork();
304 if (pid > 0) _handle = masterSock;
305 else if (pid == 0)
306 {
307 int fd = slaveSock()->GetFileDescriptor();
308 if ((launchFlags.IsBitSet(CHILD_PROCESS_LAUNCH_FLAG_EXCLUDE_STDIN) == false)&&(dup2(fd, STDIN_FILENO) < 0)) ExitWithoutCleanup(20);
309 if ((launchFlags.IsBitSet(CHILD_PROCESS_LAUNCH_FLAG_EXCLUDE_STDOUT) == false)&&(dup2(fd, STDOUT_FILENO) < 0)) ExitWithoutCleanup(20);
310 if ((launchFlags.IsBitSet(CHILD_PROCESS_LAUNCH_FLAG_EXCLUDE_STDERR) == false)&&(dup2(fd, STDERR_FILENO) < 0)) ExitWithoutCleanup(20);
311 }
312 }
313
314 if (pid < 0) return B_ERRNO; // fork failure!
315 else if (pid == 0)
316 {
317 // we are the child process
318 (void) signal(SIGHUP, SIG_DFL); // FogBugz #2918
319
320 // Close any file descriptors leftover from the parent process
321 if (launchFlags.IsBitSet(CHILD_PROCESS_LAUNCH_FLAG_INHERIT_FDS) == false)
322 {
323 const int fdlimit = (int)sysconf(_SC_OPEN_MAX);
324 for (int i=STDERR_FILENO+1; i<fdlimit; i++) close(i);
325 }
326
327 if (_childProcessIsIndependent) (void) BecomeDaemonProcess(); // used by the LaunchIndependentChildProcess() static methods only
328
329 char absArgv0[PATH_MAX];
330 const char ** zargv = &scratchChildArgv[0];
331 const char * zargv0 = scratchChildArgv[0];
332 if (optDirectory)
333 {
334 // If we are going to change to a different directory, then we need to
335 // generate an absolute-filepath for zargv[0] first, otherwise we won't
336 // be able to find the executable to run!
337 if (realpath(zargv[0], absArgv0) != NULL) zargv[0] = zargv0 = absArgv0;
338 if (chdir(optDirectory) < 0) perror("ChildProcessDataIO::chdir"); // FogBugz #10023
339 }
340
341 if (optEnvironmentVariables)
342 {
343 for (HashtableIterator<String,String> iter(*optEnvironmentVariables); iter.HasData(); iter++) (void) setenv(iter.GetKey()(), iter.GetValue()(), 1);
344 }
345
346 if (ChildProcessReadyToRun().IsOK(ret))
347 {
348 if (execvp(zargv0, const_cast<char **>(zargv)) < 0) perror("ChildProcessDataIO::execvp"); // execvp() should never return
349 }
350 else LogTime(MUSCLE_LOG_ERROR, "ChildProcessDataIO: ChildProcessReadyToRun() returned [%s], not running child process!\n", ret());
351
352 ExitWithoutCleanup(20);
353 }
354 else if (_handle()) // if we got this far, we are the parent process
355 {
356 _childPID = pid;
357 if (SetSocketBlockingEnabled(_handle, _blocking).IsOK(ret)) return B_NO_ERROR;
358 }
359
360 Close(); // roll back!
361 return ret | B_ERROR;
362 #endif
363 }
364
365 #if defined(__APPLE__) && defined(MUSCLE_ENABLE_AUTHORIZATION_EXECUTE_WITH_PRIVILEGES)
366 status_t ChildProcessDataIO :: LaunchPrivilegedChildProcess(const char ** argv)
367 {
368 // Code adapted from Performant Design's cocoasudo program
369 // at git://github.com/performantdesign/cocoasudo.git
370 OSStatus status;
371 AuthorizationRef authRef = NULL;
372
373 AuthorizationItem right = { kAuthorizationRightExecute, strlen(argv[0]), &argv[0], 0 };
374 AuthorizationRights rightSet = { 1, &right };
375
376 AuthorizationEnvironment myAuthorizationEnvironment; memset(&myAuthorizationEnvironment, 0, sizeof(myAuthorizationEnvironment));
377 AuthorizationItem kAuthEnv; memset(&kAuthEnv, 0, sizeof(kAuthEnv));
378 myAuthorizationEnvironment.items = &kAuthEnv;
379
380 kAuthEnv.name = kAuthorizationEnvironmentPrompt;
381 kAuthEnv.valueLength = _dialogPrompt.Length();
382 kAuthEnv.value = (void *) _dialogPrompt();
383 kAuthEnv.flags = 0;
384 myAuthorizationEnvironment.count = 1;
385
386 if (AuthorizationCreate(NULL, &myAuthorizationEnvironment, kAuthorizationFlagDefaults, &authRef) != errAuthorizationSuccess)
387 {
388 LogTime(MUSCLE_LOG_ERROR, "ChildProcessDataIO::LaunchPrivilegedChildProcess(): Could not create authorization reference object.\n");
389 return B_ERROR("AuthorizationCreate() failed");
390 }
391 else status = AuthorizationCopyRights(authRef, &rightSet, &myAuthorizationEnvironment, kAuthorizationFlagDefaults|kAuthorizationFlagPreAuthorize|kAuthorizationFlagInteractionAllowed|kAuthorizationFlagExtendRights, NULL);
392
393 if (status == errAuthorizationSuccess)
394 {
395 FILE *ioPipe;
396 status = AuthorizationExecuteWithPrivileges(authRef, argv[0], kAuthorizationFlagDefaults, (char **)(&argv[1]), &ioPipe); // +1 because it doesn't take the traditional argv[0]
397 if (status == errAuthorizationSuccess)
398 {
399 _ioPipe.SetFile(ioPipe);
400 _handle = _ioPipe.GetReadSelectSocket();
401 _authRef = authRef;
402 return _handle() ? SetSocketBlockingEnabled(_handle, false) : B_ERROR;
403 }
404 else
405 {
406 AuthorizationFree(authRef, kAuthorizationFlagDestroyRights);
407 return (status == errAuthorizationCanceled) ? B_ACCESS_DENIED : B_ERROR("AuthorizationExecuteWithPrivileges() failed");
408 }
409 }
410 else
411 {
412 AuthorizationFree(authRef, kAuthorizationFlagDestroyRights);
413 return (status == errAuthorizationCanceled) ? B_ACCESS_DENIED : B_ERROR("AuthorizationCopyRights() pre-authorization failed");
414 }
415 }
416 #endif
417
418 ChildProcessDataIO :: ~ChildProcessDataIO()
419 {
420 TCHECKPOINT;
421 Close();
422 }
423
424 bool ChildProcessDataIO :: IsChildProcessAvailable() const
425 {
426 #ifdef USE_WINDOWS_CHILDPROCESSDATAIO_IMPLEMENTATION
427 return (_readFromStdout != INVALID_HANDLE_VALUE);
428 #else
429 return (_handle.GetFileDescriptor() >= 0);
430 #endif
431 }
432
433 status_t ChildProcessDataIO :: KillChildProcess()
434 {
435 TCHECKPOINT;
436
437 #ifdef USE_WINDOWS_CHILDPROCESSDATAIO_IMPLEMENTATION
438 if (_childProcess == INVALID_HANDLE_VALUE) return B_BAD_OBJECT;
439 return TerminateProcess(_childProcess, 0) ? B_NO_ERROR : B_ERRNO;
440 #else
441 # if defined(__APPLE__) && defined(MUSCLE_ENABLE_AUTHORIZATION_EXECUTE_WITH_PRIVILEGES)
442 if (_ioPipe.GetFile()) return B_UNIMPLEMENTED; // no can do
443 # endif
444 if (_childPID < 0) return B_BAD_OBJECT;
445 if (kill(_childPID, SIGKILL) == 0)
446 {
447 (void) waitpid(_childPID, NULL, 0); // avoid creating a zombie process
448 _childPID = -1;
449 return B_NO_ERROR;
450 }
451 else return B_ERRNO;
452 #endif
453 }
454
455 status_t ChildProcessDataIO :: SignalChildProcess(int sigNum)
456 {
457 TCHECKPOINT;
458
459 #ifdef USE_WINDOWS_CHILDPROCESSDATAIO_IMPLEMENTATION
460 (void) sigNum; // to shut the compiler up
461 return B_UNIMPLEMENTED; // Not implemented under Windows!
462 #else
463 # if defined(__APPLE__) && defined(MUSCLE_ENABLE_AUTHORIZATION_EXECUTE_WITH_PRIVILEGES)
464 if (_ioPipe.GetFile()) return B_UNIMPLEMENTED; // no can do
465 # endif
466 if (_childPID < 0) return B_BAD_OBJECT;
467 return (kill(_childPID, sigNum) == 0) ? B_NO_ERROR : B_ERRNO; // Yes, kill() is a misnomer. Silly Unix people!
468 #endif
469 }
470
471 void ChildProcessDataIO :: Close()
472 {
473 TCHECKPOINT;
474
475 #ifdef USE_WINDOWS_CHILDPROCESSDATAIO_IMPLEMENTATION
476 if (_ioThread != INVALID_HANDLE_VALUE) // if this is valid, _wakeupSignal is guaranteed valid too
477 {
478 _requestThreadExit = true; // set the "Please go away" flag
479 SetEvent(_wakeupSignal); // wake the thread up so he'll check the flag
480 WaitForSingleObject(_ioThread, INFINITE); // then wait for him to go away
481 ::CloseHandle(_ioThread); // fix handle leak
482 _ioThread = INVALID_HANDLE_VALUE;
483 }
484 _masterNotifySocket.Reset();
485 _slaveNotifySocket.Reset();
486 SafeCloseHandle(_wakeupSignal);
487 SafeCloseHandle(_readFromStdout);
488 SafeCloseHandle(_writeToStdin);
489 if ((_childProcess != INVALID_HANDLE_VALUE)&&(_childProcessIsIndependent == false)) DoGracefulChildShutdown(); // Windows can't double-fork, so in the independent case we just won't wait for him
490 SafeCloseHandle(_childProcess);
491 SafeCloseHandle(_childThread);
492 #else
493
494 _handle.Reset();
495
496 if (_childPID >= 0) DoGracefulChildShutdown();
497 _childPID = -1;
498
499 # if defined(__APPLE__) && defined(MUSCLE_ENABLE_AUTHORIZATION_EXECUTE_WITH_PRIVILEGES)
500 _ioPipe.Shutdown();
501 if (_authRef)
502 {
503 AuthorizationFree((AuthorizationRef)_authRef, kAuthorizationFlagDestroyRights);
504 _authRef = NULL;
505 }
506 # endif
507 #endif
508 }
509
510 void ChildProcessDataIO :: DoGracefulChildShutdown()
511 {
512 if (_signalNumber >= 0) (void) SignalChildProcess(_signalNumber);
513 if ((WaitForChildProcessToExit(_maxChildWaitTime) == false)&&(_killChildOkay)) (void) KillChildProcess();
514 }
515
516 bool ChildProcessDataIO :: WaitForChildProcessToExit(uint64 maxWaitTimeMicros)
517 {
518 #ifdef USE_WINDOWS_CHILDPROCESSDATAIO_IMPLEMENTATION
519 if (_childProcess == INVALID_HANDLE_VALUE) return true; // a non-existent child process is an exited child process, if you ask me.
520 _childProcessCrashed = false; // reset the flag only when there is an actual child process to wait for
521
522 if (WaitForSingleObject(_childProcess, (maxWaitTimeMicros==MUSCLE_TIME_NEVER)?INFINITE:((DWORD)(maxWaitTimeMicros/1000))) == WAIT_OBJECT_0)
523 {
524 DWORD exitCode;
525 if (GetExitCodeProcess(_childProcess, &exitCode))
526 {
527 // Note that this is only a heuristic since a sufficiently
528 // demented child process could return a code that matches
529 // this criterion as part of its normal exit(), and a crashed
530 // program could (conceivably) have an exit code that doesn't
531 // meet this criterion. But in general this will work. --jaf
532 _childProcessCrashed = ((exitCode & (0xC0000000)) != 0);
533 }
534 return true;
535 }
536 #else
537 # if defined(__APPLE__) && defined(MUSCLE_ENABLE_AUTHORIZATION_EXECUTE_WITH_PRIVILEGES)
538 if (_ioPipe.GetFile())
539 {
540 const int fd = fileno(_ioPipe.GetFile());
541
542 // Since AuthorizationExecuteWithPrivileges() doesn't give us a _childPID to wait on, all we can do is
543 // block-and-read until either we read EOF from the _ioPipe or we reach the timeout-time.
544 bool sawEOF = false;
545 SocketMultiplexer sm;
546 const uint64 endTime = (maxWaitTimeMicros == MUSCLE_TIME_NEVER) ? MUSCLE_TIME_NEVER : (GetRunTime64()+maxWaitTimeMicros);
547 while(GetRunTime64() < endTime)
548 {
549 if ((sm.RegisterSocketForReadReady(fd) != B_NO_ERROR)||(sm.WaitForEvents(endTime) < 0)) break;
550 else
551 {
552 char junk[1024] = "";
553 if ((fread(junk, sizeof(junk), 1, _ioPipe.GetFile()) < 0)||(feof(_ioPipe.GetFile())))
554 {
555 sawEOF = true;
556 break;
557 }
558 }
559 }
560 return sawEOF; // if we saw EOF on the _ioPipe, we'll take that to mean the child process exited -- best we can do
561 }
562 # endif
563
564 if (_childPID < 0) return true; // a non-existent child process is an exited child process, if you ask me.
565 _childProcessCrashed = false; // reset the flag only when there is an actual child process to wait for
566
567 if (maxWaitTimeMicros == MUSCLE_TIME_NEVER)
568 {
569 int status = 0;
570 const int pid = waitpid(_childPID, &status, 0);
571 if (pid == _childPID)
572 {
573 _childProcessCrashed = WIFSIGNALED(status);
574 return true;
575 }
576 }
577 else
578 {
579 // The tricky case... waiting for the child process to exit, with a timeout.
580 // I'm implementing it via a polling loop, which is a sucky way to implement
581 // it but the only alternative would involve mucking about with signal handlers,
582 // and doing it that way would be unreliable in multithreaded environments.
583 const uint64 endTime = GetRunTime64()+maxWaitTimeMicros;
584 uint64 pollInterval = 0; // we'll start quickly, and start polling more slowly only if the child process doesn't exit soon
585 while(1)
586 {
587 int status = 0;
588 const int r = waitpid(_childPID, &status, WNOHANG); // WNOHANG should guarantee that this call will not block
589 if (r == _childPID)
590 {
591 _childProcessCrashed = WIFSIGNALED(status);
592 return true; // yay, he exited!
593 }
594 else if (r == -1) break; // fail on error
595
596 const int64 microsLeft = endTime-GetRunTime64();
597 if (microsLeft <= 0) break; // we're out of time!
598
599 // At this point, r was probably zero because the child wasn't ready to exit
600 if ((int64)pollInterval < MillisToMicros(200)) pollInterval += MillisToMicros(10);
601 Snooze64(muscleMin(pollInterval, (uint64)microsLeft));
602 }
603 }
604 #endif
605
606 return false;
607 }
608
609 int32 ChildProcessDataIO :: Read(void *buf, uint32 len)
610 {
611 TCHECKPOINT;
612
613 if (IsChildProcessAvailable())
614 {
615 #ifdef USE_WINDOWS_CHILDPROCESSDATAIO_IMPLEMENTATION
616 if (_blocking)
617 {
618 DWORD actual_read;
619 if (ReadFile(_readFromStdout, buf, len, &actual_read, NULL)) return actual_read;
620 }
621 else
622 {
623 const int32 ret = ReceiveData(_masterNotifySocket, buf, len, _blocking);
624 if (ret >= 0) SetEvent(_wakeupSignal); // wake up the thread in case he has more data to give us
625 return ret;
626 }
627 #else
628 const long r = read_ignore_eintr(_handle.GetFileDescriptor(), buf, len);
629 return _blocking ? (int32)r : ConvertReturnValueToMuscleSemantics(r, len, _blocking);
630 #endif
631 }
632 return -1;
633 }
634
635 int32 ChildProcessDataIO :: Write(const void *buf, uint32 len)
636 {
637 TCHECKPOINT;
638
639 if (IsChildProcessAvailable())
640 {
641 #ifdef USE_WINDOWS_CHILDPROCESSDATAIO_IMPLEMENTATION
642 if (_blocking)
643 {
644 DWORD actual_write;
645 if (WriteFile(_writeToStdin, buf, len, &actual_write, 0)) return actual_write;
646 }
647 else
648 {
649 const int32 ret = SendData(_masterNotifySocket, buf, len, _blocking);
650 if (ret > 0) SetEvent(_wakeupSignal); // wake up the thread so he'll check his socket for our new data
651 return ret;
652 }
653 #else
654 return ConvertReturnValueToMuscleSemantics(write_ignore_eintr(_handle.GetFileDescriptor(), buf, len), len, _blocking);
655 #endif
656 }
657 return -1;
658 }
659
660 void ChildProcessDataIO :: FlushOutput()
661 {
662 // not implemented
663 }
664
665 status_t ChildProcessDataIO :: ChildProcessReadyToRun()
666 {
667 return B_NO_ERROR;
668 }
669
670 const ConstSocketRef & ChildProcessDataIO :: GetChildSelectSocket() const
671 {
672 #ifdef USE_WINDOWS_CHILDPROCESSDATAIO_IMPLEMENTATION
673 return _blocking ? GetNullSocket() : _masterNotifySocket;
674 #else
675 return _handle;
676 #endif
677 }
678
679 void ChildProcessDataIO :: Shutdown()
680 {
681 Close();
682 }
683
684 #ifdef USE_WINDOWS_CHILDPROCESSDATAIO_IMPLEMENTATION
685
686 const uint32 CHILD_BUFFER_SIZE = 1024;
687
688 // Used as a temporary holding area for data in transit
689 class ChildProcessBuffer
690 {
691 public:
692 ChildProcessBuffer()
693 : _length(0)
694 , _index(0)
695 {
696 // empty
697 }
698
699 char _buf[CHILD_BUFFER_SIZE];
700 uint32 _length; // how many bytes in _buf are actually valid
701 uint32 _index; // Index of the next byte to process
702 };
703
704 // to be called from within the I/O thread only!
705 void ChildProcessDataIO :: IOThreadAbort()
706 {
707 // If we read zero bytes, that means EOF! Child process has gone away!
708 _slaveNotifySocket.Reset();
709 _requestThreadExit = true; // this will cause the I/O thread to go away now
710 }
711
712 void ChildProcessDataIO :: IOThreadEntry()
713 {
714 bool childProcessExited = false;
715
716 ChildProcessBuffer inBuf; // bytes from the child process's stdout, waiting to go to the _slaveNotifySocket
717 ChildProcessBuffer outBuf; // bytes from the _slaveNotifySocket, waiting to go to the child process's stdin
718
719 ::HANDLE events[] = {_wakeupSignal, _childProcess};
720 while(_requestThreadExit == false)
721 {
722 // IOThread <-> UserThread i/o handling here
723 {
724 // While we have any data in inBuf, send as much of it as possible back to the user thread. This won't block.
725 while(inBuf._index < inBuf._length)
726 {
727 const int32 bytesToWrite = inBuf._length-inBuf._index;
728 const int32 bytesWritten = (bytesToWrite > 0) ? SendData(_slaveNotifySocket, &inBuf._buf[inBuf._index], bytesToWrite, false) : 0;
729 if (bytesWritten > 0)
730 {
731 inBuf._index += bytesWritten;
732 if (inBuf._index == inBuf._length) inBuf._index = inBuf._length = 0;
733 }
734 else
735 {
736 if (bytesWritten < 0) IOThreadAbort(); // use thread connection closed!?
737 break; // can't write any more, for now
738 }
739 }
740
741 // While we have room in our outBuf, try to read some more data into it from the slave socket. This won't block.
742 while(outBuf._length < sizeof(outBuf._buf))
743 {
744 const int32 maxLen = sizeof(outBuf._buf)-outBuf._length;
745 const int32 ret = ReceiveData(_slaveNotifySocket, &outBuf._buf[outBuf._length], maxLen, false);
746 if (ret > 0) outBuf._length += ret;
747 else
748 {
749 if (ret < 0) IOThreadAbort(); // user thread connection closed!?
750 break; // no more to read, for now
751 }
752 }
753 }
754
755 // IOThread <-> ChildProcess i/o handling (and blocking) here
756 {
757 if (childProcessExited)
758 {
759 if (inBuf._index == inBuf._length) IOThreadAbort();
760 break;
761 }
762
763 // block here until an event happens... gotta poll because
764 // the Window anonymous pipes system doesn't allow me to
765 // to check for events on the pipe using WaitForMultipleObjects().
766 // It may be worth it to use named pipes some day to get around this...
767 const int evt = WaitForMultipleObjects(ARRAYITEMS(events)-(childProcessExited?1:0), events, false, 250)-WAIT_OBJECT_0;
768 if (evt == 1) childProcessExited = true;
769
770 int32 numBytesToRead;
771 while((numBytesToRead = sizeof(inBuf._buf)-inBuf._length) > 0)
772 {
773 // See if there is actually any data available for reading first
774 DWORD pipeSize;
775 if (PeekNamedPipe(_readFromStdout, NULL, 0, NULL, &pipeSize, NULL))
776 {
777 if (pipeSize > 0)
778 {
779 DWORD numBytesRead;
780 if (ReadFile(_readFromStdout, &inBuf._buf[inBuf._length], numBytesToRead, &numBytesRead, NULL))
781 {
782 inBuf._length += numBytesRead;
783 }
784 else
785 {
786 IOThreadAbort(); // child process exited?
787 break;
788 }
789 }
790 else break;
791 }
792 else
793 {
794 IOThreadAbort(); // child process exited?
795 break;
796 }
797 }
798
799 int32 numBytesToWrite;
800 while((numBytesToWrite = outBuf._length-outBuf._index) > 0)
801 {
802 DWORD bytesWritten;
803 if (WriteFile(_writeToStdin, &outBuf._buf[outBuf._index], numBytesToWrite, &bytesWritten, 0))
804 {
805 if (bytesWritten > 0)
806 {
807 outBuf._index += bytesWritten;
808 if (outBuf._index == outBuf._length) outBuf._index = outBuf._length = 0;
809 }
810 else break; // no more space to write to, for now
811 }
812 else IOThreadAbort(); // wtf?
813 }
814 }
815 }
816 }
817 #endif
818
819 status_t ChildProcessDataIO :: System(int argc, const char * argv[], ChildProcessLaunchFlags launchFlags, uint64 maxWaitTimeMicros, const char * optDirectory, const Hashtable<String, String> * optEnvironmentVariables)
820 {
821 status_t ret;
822 ChildProcessDataIO cpdio(false);
823 if (cpdio.LaunchChildProcess(argc, argv, launchFlags, optDirectory, optEnvironmentVariables).IsOK(ret))
824 {
825 (void) cpdio.WaitForChildProcessToExit(maxWaitTimeMicros);
826 return B_NO_ERROR;
827 }
828 else return ret;
829 }
830
831 status_t ChildProcessDataIO :: System(const Queue<String> & argq, ChildProcessLaunchFlags launchFlags, uint64 maxWaitTimeMicros, const char * optDirectory, const Hashtable<String, String> * optEnvironmentVariables)
832 {
833 const uint32 numItems = argq.GetNumItems();
834 if (numItems == 0) return B_BAD_ARGUMENT;
835
836 const char ** argv = newnothrow_array(const char *, numItems+1);
837 if (argv == NULL) RETURN_OUT_OF_MEMORY;
838 for (uint32 i=0; i<numItems; i++) argv[i] = argq[i]();
839 argv[numItems] = NULL;
840 const status_t ret = System(numItems, argv, launchFlags, maxWaitTimeMicros, optDirectory, optEnvironmentVariables);
841 delete [] argv;
842 return ret;
843 }
844
845 status_t ChildProcessDataIO :: System(const char * cmdLine, ChildProcessLaunchFlags launchFlags, uint64 maxWaitTimeMicros, const char * optDirectory, const Hashtable<String, String> * optEnvironmentVariables)
846 {
847 ChildProcessDataIO cpdio(false);
848 status_t ret;
849 if (cpdio.LaunchChildProcess(cmdLine, launchFlags, optDirectory, optEnvironmentVariables).IsOK(ret))
850 {
851 cpdio.WaitForChildProcessToExit(maxWaitTimeMicros);
852 return B_NO_ERROR;
853 }
854 else return ret;
855 }
856
857 status_t ChildProcessDataIO :: LaunchIndependentChildProcess(int argc, const char * argv[], const char * optDirectory, ChildProcessLaunchFlags launchFlags, const Hashtable<String, String> * optEnvironmentVariables)
858 {
859 ChildProcessDataIO cpdio(true);
860 cpdio._childProcessIsIndependent = true; // so the cpdio dtor won't block waiting for the child to exit
861 cpdio.SetChildProcessShutdownBehavior(false);
862 return cpdio.LaunchChildProcess(argc, argv, launchFlags, optDirectory, optEnvironmentVariables);
863 }
864
865 status_t ChildProcessDataIO :: LaunchIndependentChildProcess(const char * cmdLine, const char * optDirectory, ChildProcessLaunchFlags launchFlags, const Hashtable<String, String> * optEnvironmentVariables)
866 {
867 ChildProcessDataIO cpdio(true);
868 cpdio._childProcessIsIndependent = true; // so the cpdio dtor won't block waiting for the child to exit
869 cpdio.SetChildProcessShutdownBehavior(false);
870 return cpdio.LaunchChildProcess(cmdLine, launchFlags, optDirectory, optEnvironmentVariables);
871 }
872
873 status_t ChildProcessDataIO :: LaunchIndependentChildProcess(const Queue<String> & argv, const char * optDirectory, ChildProcessLaunchFlags launchFlags, const Hashtable<String, String> * optEnvironmentVariables)
874 {
875 ChildProcessDataIO cpdio(true);
876 cpdio._childProcessIsIndependent = true; // so the cpdio dtor won't block waiting for the child to exit
877 cpdio.SetChildProcessShutdownBehavior(false);
878 return cpdio.LaunchChildProcess(argv, launchFlags, optDirectory, optEnvironmentVariables);
879 }
880
881 } // end namespace muscle