4 * Sub-process commuicating with pip I/O channel implementation
6 * Portable Windows Library
8 * Copyright (c) 1993-1998 Equivalence Pty. Ltd.
10 * The contents of this file are subject to the Mozilla Public License
11 * Version 1.0 (the "License"); you may not use this file except in
12 * compliance with the License. You may obtain a copy of the License at
13 * http://www.mozilla.org/MPL/
15 * Software distributed under the License is distributed on an "AS IS"
16 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
17 * the License for the specific language governing rights and limitations
20 * The Original Code is Portable Windows Library.
22 * The Initial Developer of the Original Code is Equivalence Pty. Ltd.
24 * Portions are Copyright (C) 1993 Free Software Foundation, Inc.
25 * All Rights Reserved.
27 * Contributor(s): ______________________________________.
30 * Revision 1.42 2003/04/22 23:43:51 craigs
31 * MacOSX changes as per Hugo Santos
33 * Revision 1.41 2003/01/09 08:21:47 robertj
34 * Fixed possibly handle leak if fork() fails.
35 * Also added belt and braces checks for making sure no handle can leak in
36 * other unknown logic. Plus do handle high water mark logging.
38 * Revision 1.40 2003/01/08 01:33:52 craigs
39 * Fixed problem with not checking errno on return from waitpid
41 * Revision 1.39 2003/01/08 01:29:22 craigs
42 * More changes for return code to waitpid
44 * Revision 1.38 2002/12/18 01:12:09 craigs
45 * Remove erroneous WUNTRACED and added support for EINTR from waitpid
47 * Revision 1.37 2002/12/05 05:11:16 craigs
48 * Fixed IsRunning and WaitForTermination to provide the correct return
49 * codes from subprograms
51 * Revision 1.36 2002/12/02 03:57:18 robertj
52 * More RTEMS support patches, thank you Vladimir Nesic.
54 * Revision 1.35 2002/11/22 10:14:07 robertj
55 * QNX port, thanks Xiaodan Tang
57 * Revision 1.34 2002/10/17 13:44:27 robertj
58 * Port to RTEMS, thanks Vladimir Nesic.
60 * Revision 1.33 2002/10/10 04:43:44 robertj
61 * VxWorks port, thanks Martijn Roest
63 * Revision 1.32 2002/07/31 07:30:11 craigs
64 * WaitForTermination now returns exit code of program, as required
66 * Revision 1.31 2001/10/11 02:20:54 robertj
67 * Added IRIX support (no audio/video), thanks Andre Schulze.
69 * Revision 1.30 2001/08/16 11:58:22 rogerh
70 * Add more Mac OS X changes from John Woods <jfw@jfwhome.funhouse.com>
72 * Revision 1.29 2001/08/12 06:32:04 rogerh
73 * Add Mac OS Carbon changes from John Woods <jfw@jfwhome.funhouse.com>
75 * Revision 1.28 2001/06/30 06:59:07 yurik
76 * Jac Goudsmit from Be submit these changes 6/28. Implemented by Yuri Kiryanov
78 * Revision 1.27 2000/06/21 01:01:22 robertj
79 * AIX port, thanks Wolfgang Platzer (wolfgang.platzer@infonova.at).
81 * Revision 1.26 2000/04/09 18:19:23 rogerh
82 * Add my changes for NetBSD support.
84 * Revision 1.25 2000/04/06 12:11:32 rogerh
85 * MacOS X support submitted by Kevin Packard
87 * Revision 1.24 2000/03/08 12:17:09 rogerh
90 * Revision 1.23 1999/06/28 09:28:02 robertj
91 * Portability issues, especially n BeOS (thanks Yuri!)
93 * Revision 1.22 1999/02/22 13:26:54 robertj
96 * Revision 1.21 1998/11/30 21:51:46 robertj
97 * New directory structure.
99 * Revision 1.20 1998/11/24 10:25:19 robertj
100 * Fixed environment variable on FreeBSD
102 * Revision 1.19 1998/11/24 09:39:11 robertj
105 * Revision 1.18 1998/11/06 01:06:05 robertj
106 * Solaris environment variable name.
108 * Revision 1.17 1998/11/05 09:42:01 robertj
109 * Fixed bug in direct stdout mode opening redirected stdout.
110 * Solaris support, missing environ declaration.
111 * Added assert for unsupported timeout in WaitForTermination() under solaris.
113 * Revision 1.16 1998/11/02 11:11:19 robertj
114 * Added pipe output to stdout/stderr.
116 * Revision 1.15 1998/11/02 10:30:40 robertj
117 * GNU v6 compatibility.
119 * Revision 1.14 1998/11/02 10:07:34 robertj
120 * Added ReadStandardError implementation
122 * Revision 1.13 1998/10/30 13:02:50 robertj
123 * New pipe channel enhancements.
125 * Revision 1.12 1998/10/26 11:09:56 robertj
126 * added separation of stdout and stderr.
128 * Revision 1.11 1998/09/24 04:12:14 robertj
129 * Added open software license.
133 #pragma implementation "pipechan.h"
136 #include <ptlib/pipechan.h>
139 #include <sys/ioctl.h>
140 #include <sys/types.h>
141 #include <sys/wait.h>
145 #if defined(P_LINUX) || defined(P_SOLARIS)
149 #if defined(P_MACOSX)
150 #include <crt_externs.h>
153 #include "../common/pipechan.cxx"
156 int PX_NewHandle(const char *, int);
159 ////////////////////////////////////////////////////////////////
164 PPipeChannel::PPipeChannel()
166 toChildPipe
[0] = toChildPipe
[1] = -1;
167 fromChildPipe
[0] = fromChildPipe
[1] = -1;
168 stderrChildPipe
[0] = stderrChildPipe
[1] = -1;
172 BOOL
PPipeChannel::PlatformOpen(const PString
& subProgram
,
173 const PStringArray
& argumentList
,
177 const PStringToString
* environment
)
179 #if defined(P_VXWORKS) || defined(P_RTEMS)
180 PAssertAlways("PPipeChannel::PlatformOpen");
183 subProgName
= subProgram
;
185 // setup the pipe to the child
186 if (mode
== ReadOnly
)
187 toChildPipe
[0] = toChildPipe
[1] = -1;
189 PAssert(pipe(toChildPipe
) == 0, POperatingSystemError
);
190 PX_NewHandle("PPipeChannel toChildPipe", PMAX(toChildPipe
[0], toChildPipe
[1]));
193 // setup the pipe from the child
194 if (mode
== WriteOnly
|| mode
== ReadWriteStd
)
195 fromChildPipe
[0] = fromChildPipe
[1] = -1;
197 PAssert(pipe(fromChildPipe
) == 0, POperatingSystemError
);
198 PX_NewHandle("PPipeChannel fromChildPipe", PMAX(fromChildPipe
[0], fromChildPipe
[1]));
202 PAssert(pipe(stderrChildPipe
) == 0, POperatingSystemError
);
204 stderrChildPipe
[0] = stderrChildPipe
[1] = -1;
205 PX_NewHandle("PPipeChannel stderrChildPipe", PMAX(stderrChildPipe
[0], stderrChildPipe
[1]));
208 // fork to allow us to execute the child
209 #if defined(__BEOS__) || defined(P_IRIX)
218 // setup the pipe to the child
219 if (toChildPipe
[0] != -1) {
220 ::close(toChildPipe
[0]);
224 if (fromChildPipe
[1] != -1) {
225 ::close(fromChildPipe
[1]);
226 fromChildPipe
[1] = -1;
229 if (stderrChildPipe
[1] != -1) {
230 ::close(stderrChildPipe
[1]);
231 stderrChildPipe
[1] = -1;
238 // the following code is in the child process
240 // if we need to write to the child, make sure the child's stdin
242 if (toChildPipe
[0] != -1) {
243 ::close(STDIN_FILENO
);
244 ::dup(toChildPipe
[0]);
245 ::close(toChildPipe
[0]);
246 ::close(toChildPipe
[1]);
248 int fd
= open("/dev/null", O_RDONLY
);
250 ::close(STDIN_FILENO
);
255 // if we need to read from the child, make sure the child's stdout
256 // and stderr is redirected
257 if (fromChildPipe
[1] != -1) {
258 ::close(STDOUT_FILENO
);
259 ::dup(fromChildPipe
[1]);
260 ::close(STDERR_FILENO
);
262 ::dup(fromChildPipe
[1]);
263 ::close(fromChildPipe
[1]);
264 ::close(fromChildPipe
[0]);
265 } else if (mode
!= ReadWriteStd
) {
266 int fd
= ::open("/dev/null", O_WRONLY
);
268 ::close(STDOUT_FILENO
);
270 ::close(STDERR_FILENO
);
276 if (stderrSeparate
) {
277 ::dup(stderrChildPipe
[1]);
278 ::close(stderrChildPipe
[1]);
279 ::close(stderrChildPipe
[0]);
282 // set the SIGINT and SIGQUIT to ignore so the child process doesn't
283 // inherit them from the parent
284 signal(SIGINT
, SIG_IGN
);
285 signal(SIGQUIT
, SIG_IGN
);
287 // and set ourselves as out own process group so we don't get signals
288 // from our parent's terminal (hopefully!)
291 // setup the arguments, not as we are about to execl or exit, we don't
292 // care about memory leaks, they are not real!
293 char ** args
= (char **)calloc(argumentList
.GetSize()+2, sizeof(char *));
294 args
[0] = strdup(subProgName
.GetTitle());
296 for (i
= 0; i
< argumentList
.GetSize(); i
++)
297 args
[i
+1] = argumentList
[i
].GetPointer();
299 // Set up new environment if one specified.
300 if (environment
!= NULL
) {
301 #if defined(P_SOLARIS) || defined(P_FREEBSD) || defined(P_OPENBSD) || defined (P_NETBSD) || defined(__BEOS__) || defined(P_MACOSX) || defined(P_MACOS) || defined (P_AIX) || defined(P_IRIX) || defined(P_QNX)
302 extern char ** environ
;
303 # if defined(P_MACOSX)
304 # define environ (*_NSGetEnviron())
306 # define __environ environ
308 __environ
= (char **)calloc(environment
->GetSize()+1, sizeof(char*));
309 for (i
= 0; i
< environment
->GetSize(); i
++) {
310 PString str
= environment
->GetKeyAt(i
) + '=' + environment
->GetDataAt(i
);
311 __environ
[i
] = strdup(str
);
315 // execute the child as required
317 execvp(subProgram
, args
);
319 execv(subProgram
, args
);
323 #endif // P_VXWORKS || P_RTEMS
327 BOOL
PPipeChannel::Close()
329 // close pipe from child
330 if (fromChildPipe
[0] != -1) {
331 ::close(fromChildPipe
[0]);
332 fromChildPipe
[0] = -1;
335 if (fromChildPipe
[1] != -1) {
336 ::close(fromChildPipe
[1]);
337 fromChildPipe
[1] = -1;
340 // close pipe to child
341 if (toChildPipe
[0] != -1) {
342 ::close(toChildPipe
[0]);
346 if (toChildPipe
[1] != -1) {
347 ::close(toChildPipe
[1]);
351 // close pipe to child
352 if (stderrChildPipe
[0] != -1) {
353 ::close(stderrChildPipe
[0]);
354 stderrChildPipe
[0] = -1;
357 if (stderrChildPipe
[1] != -1) {
358 ::close(stderrChildPipe
[1]);
359 stderrChildPipe
[1] = -1;
362 // kill the child process
364 kill (childPid
, SIGKILL
);
365 WaitForTermination();
368 // ensure this channel looks like it is closed
375 BOOL
PPipeChannel::Read(void * buffer
, PINDEX len
)
377 PAssert(IsOpen(), "Attempt to read from closed pipe");
378 PAssert(fromChildPipe
[0] != -1, "Attempt to read from write-only pipe");
380 os_handle
= fromChildPipe
[0];
381 BOOL status
= PChannel::Read(buffer
, len
);
386 BOOL
PPipeChannel::Write(const void * buffer
, PINDEX len
)
388 PAssert(IsOpen(), "Attempt to write to closed pipe");
389 PAssert(toChildPipe
[1] != -1, "Attempt to write to read-only pipe");
391 os_handle
= toChildPipe
[1];
392 BOOL status
= PChannel::Write(buffer
, len
);
397 BOOL
PPipeChannel::Execute()
401 if (toChildPipe
[1] != -1) {
402 ::close(toChildPipe
[1]);
409 PPipeChannel::~PPipeChannel()
414 int PPipeChannel::GetReturnCode() const
419 BOOL
PPipeChannel::IsRunning() const
424 #if defined(P_PTHREADS) || defined(P_MAC_MPTHREADS)
428 if ((err
= waitpid(childPid
, &status
, WNOHANG
)) == 0)
434 PPipeChannel
* thisW
= (PPipeChannel
*)this;
437 if (WIFEXITED(status
)) {
438 thisW
->retVal
= WEXITSTATUS(status
);
439 PTRACE(2, "PipeChannel\tChild exited with code " << retVal
);
440 } else if (WIFSIGNALED(status
)) {
441 PTRACE(2, "PipeChannel\tChild was signalled with " << WTERMSIG(status
));
443 } else if (WIFSTOPPED(status
)) {
444 PTRACE(2, "PipeChannel\tChild was stopped with " << WSTOPSIG(status
));
447 PTRACE(2, "PipeChannel\tChild was stopped with unknown status" << status
);
454 return kill(childPid
, 0) == 0;
458 int PPipeChannel::WaitForTermination()
465 #if defined(P_PTHREADS) || defined(P_MAC_MPTHREADS)
468 err
= waitpid(childPid
, &status
, 0);
469 if (err
== childPid
) {
471 if (WIFEXITED(status
)) {
472 retVal
= WEXITSTATUS(status
);
473 PTRACE(2, "PipeChannel\tChild exited with code " << retVal
);
474 } else if (WIFSIGNALED(status
)) {
475 PTRACE(2, "PipeChannel\tChild was signalled with " << WTERMSIG(status
));
477 } else if (WIFSTOPPED(status
)) {
478 PTRACE(2, "PipeChannel\tChild was stopped with " << WSTOPSIG(status
));
481 PTRACE(2, "PipeChannel\tChild was stopped with unknown status" << status
);
486 } while (errno
== EINTR
);
488 if ((err
= kill (childPid
, 0)) == 0)
489 return retVal
= PThread::Current()->PXBlockOnChildTerminate(childPid
, PMaxTimeInterval
);
496 int PPipeChannel::WaitForTermination(const PTimeInterval
& timeout
)
503 #if defined(P_PTHREADS) || defined(P_MAC_MPTHREADS)
504 PAssert(timeout
== PMaxTimeInterval
, PUnimplementedFunction
);
507 err
= waitpid(childPid
, &status
, 0);
508 if (err
== childPid
) {
510 if (WIFEXITED(status
)) {
511 retVal
= WEXITSTATUS(status
);
512 PTRACE(2, "PipeChannel\tChild exited with code " << retVal
);
513 } else if (WIFSIGNALED(status
)) {
514 PTRACE(2, "PipeChannel\tChild was signalled with " << WTERMSIG(status
));
516 } else if (WIFSTOPPED(status
)) {
517 PTRACE(2, "PipeChannel\tChild was stopped with " << WSTOPSIG(status
));
520 PTRACE(2, "PipeChannel\tChild was stopped with unknown status" << status
);
525 } while (errno
== EINTR
);
527 if ((err
= kill (childPid
, 0)) == 0)
528 return retVal
= PThread::Current()->PXBlockOnChildTerminate(childPid
, timeout
);
535 BOOL
PPipeChannel::Kill(int killType
)
537 return ConvertOSError(kill (childPid
, killType
));
540 BOOL
PPipeChannel::CanReadAndWrite()
546 BOOL
PPipeChannel::ReadStandardError(PString
& errors
, BOOL wait
)
548 PAssert(IsOpen(), "Attempt to read from closed pipe");
549 PAssert(stderrChildPipe
[0] != -1, "Attempt to read from write-only pipe");
551 os_handle
= stderrChildPipe
[0];
556 if (ConvertOSError(ioctl(stderrChildPipe
[0], FIONREAD
, &available
))) {
558 status
= PChannel::Read(errors
.GetPointer(available
+1), available
);
561 status
= PChannel::Read(&firstByte
, 1);
564 if (ConvertOSError(ioctl(stderrChildPipe
[0], FIONREAD
, &available
))) {
566 status
= PChannel::Read(errors
.GetPointer(available
+2)+1, available
);