WvStreams
wvpipe.cc
1/*
2 * Worldvisions Weaver Software:
3 * Copyright (C) 1997-2002 Net Integration Technologies, Inc.
4 *
5 * Implementation of a WvPipe stream. WvPipes allow you to create a new
6 * process, attaching its stdin/stdout to a WvStream.
7 *
8 * See wvpipe.h for more information.
9 */
10#include <fcntl.h>
11#include <sys/types.h>
12#include <sys/socket.h>
13#include <signal.h>
14#include <sys/wait.h>
15#include <errno.h>
16#include <sys/ioctl.h>
17#include <assert.h>
18#include "wvpipe.h"
19
20// this code is pretty handy for debugging, since 'netstat -nap' can't tell
21// you the endpoints of a socketpair(), but it can tell you the name of a
22// "real" Unix domain socket.
23#if 0
24#include "wvaddr.h"
25static int socketpair(int d, int type, int protocol, int sv[2])
26{
27 static int counter = 10;
28
29 int f1 = socket(PF_UNIX, SOCK_STREAM, protocol);
30 int f2 = socket(PF_UNIX, SOCK_STREAM, protocol);
31
32 WvString s("/tmp/sock%s", ++counter);
33 WvString s2("/tmp/sock%sb", counter);
34 WvUnixAddr a(s), a2(s2);
35
36 unlink(s);
37 unlink(s2);
38
39 bind(f1, a.sockaddr(), a.sockaddr_len());
40 bind(f2, a2.sockaddr(), a2.sockaddr_len());
41 listen(f1, 10);
42 connect(f2, a.sockaddr(), a.sockaddr_len());
43
44 socklen_t ll = a.sockaddr_len();
45 int f3 = accept(f1, a.sockaddr(), &ll);
46 close(f1);
47
48 sv[0] = f3;
49 sv[1] = f2;
50
51 return 0;
52}
53#endif
54
55
56// The assorted WvPipe::WvPipe() constructors are described in wvpipe.h
57
58WvPipe::WvPipe(const char *program, const char * const *argv,
59 bool writable, bool readable, bool catch_stderr,
60 int stdin_fd, int stdout_fd, int stderr_fd, WvStringList *env)
61{
62 setup(program, argv, writable, readable, catch_stderr,
63 stdin_fd, stdout_fd, stderr_fd, env);
64}
65
66
67WvPipe::WvPipe(const char *program, const char * const *argv,
68 bool writable, bool readable, bool catch_stderr,
69 WvFDStream *stdin_str, WvFDStream *stdout_str,
70 WvFDStream *stderr_str, WvStringList *env)
71{
72 int fd0 = 0, fd1 = 1, fd2 = 2;
73 if (stdin_str)
74 fd0 = stdin_str->getrfd();
75 if (stdout_str)
76 fd1 = stdout_str->getwfd();
77 if (stderr_str)
78 fd2 = stderr_str->getwfd();
79 setup(program, argv, writable, readable, catch_stderr, fd0, fd1, fd2, env);
80}
81
82
83WvPipe::WvPipe(const char *program, const char **argv,
84 bool writable, bool readable, bool catch_stderr,
85 WvFDStream *stdio_str, WvStringList *env)
86{
87 if (stdio_str)
88 {
89 int rfd = stdio_str->getrfd(), wfd = stdio_str->getwfd();
90 setup(program, argv, writable, readable, catch_stderr,
91 rfd, wfd, wfd, env);
92 }
93 else
94 setup(program, argv, writable, readable, catch_stderr, 0, 1, 2, env);
95}
96
97
98void WvPipe::setup(const char *program, const char * const *argv,
99 bool writable, bool readable, bool catch_stderr,
100 int stdin_fd, int stdout_fd, int stderr_fd,
101 WvStringList *env)
102{
103 int socks[2];
104 int flags;
105 int waitfd;
106 int pid;
107
108 if (!program || !argv)
109 {
110 seterr(EINVAL);
111 return;
112 }
113
114 if (socketpair(AF_UNIX, SOCK_STREAM, 0, socks))
115 {
116 seterr(errno);
117 return;
118 }
119
120 fcntl(socks[0], F_SETFL, O_RDWR|O_NONBLOCK);
121 setfd(socks[0]);
122
123 if (env)
124 {
125 WvStringList::Iter it(*env);
126 for (it.rewind(); it.next(); )
127 {
128 proc.env.append(*it);
129 }
130 }
131 pid = proc.fork(&waitfd);
132
133 if (!pid)
134 {
135 // child process
136 ::close(socks[0]);
137
138 if (writable)
139 dup2(socks[1], 0); // writable means redirect child stdin
140 else if (stdin_fd == -1)
141 ::close(0);
142 else
143 dup2(stdin_fd, 0);
144 if (readable)
145 dup2(socks[1], 1); // readable means we redirect child stdout
146 else if (stdout_fd == -1)
147 ::close(1);
148 else
149 dup2(stdout_fd, 1);
150 if (catch_stderr)
151 dup2(socks[1], 2); // but catch_stderr does what you think
152 else if (stderr_fd == -1)
153 ::close(2);
154 else
155 dup2(stderr_fd, 2);
156
157 /* never close stdin/stdout/stderr */
158 fcntl(0, F_SETFD, 0);
159 fcntl(1, F_SETFD, 0);
160 fcntl(2, F_SETFD, 0);
161
162 /* drop the O_NONBLOCK from stdin/stdout/stderr, it confuses
163 * some programs */
164 flags = fcntl(0, F_GETFL);
165 fcntl(0, F_SETFL, flags & ~O_NONBLOCK);
166 flags = fcntl(1, F_GETFL);
167 fcntl(1, F_SETFL, flags & ~O_NONBLOCK);
168 flags = fcntl(2, F_GETFL);
169 fcntl(2, F_SETFL, flags & ~O_NONBLOCK);
170
171 /* If we're not capturing any of these through the socket, it
172 * means that the child end of the socket will be closed right
173 * at the execvp, which is bad. If we set the close-on-exec to
174 * false, the child end of the socket will be closed when the
175 * child (or sub-) process exits. */
176 if (!writable && !readable && !catch_stderr)
177 fcntl(socks[1], F_SETFD, 0); // never close the socketpair
178 else
179 ::close(socks[1]); // has already been duplicated
180
181 // this will often fail, but when it does work it is probably
182 // the Right Thing To Do (tm)
183 if (!readable && stdout_fd != 1)
184 {
185 setsid();
186// Only on some OSes will we find TIOCSCTTY to set the controlling tty.
187// On others, we need to use TCSETCTTY, but we are too lazy to implement that.
188#ifdef TIOCSCTTY
189 ioctl(1, TIOCSCTTY, 1);
190#else
191# ifdef TCSETCTTY
192# warning You should implement TCSETCTTY here. Thanks!
193# endif
194#endif
195 }
196
197 ::close(waitfd);
198
199 // now run the program. If it fails, use _exit() so no destructors
200 // get called and make a mess.
201 execvp(program, (char * const *)argv);
202 _exit(242);
203 }
204 else if (pid > 0)
205 {
206 // parent process.
207 // now that we've forked, it's okay to close this fd if we fork again.
208 fcntl(socks[0], F_SETFD, 1);
209 ::close(socks[1]);
210 }
211 else
212 {
213 ::close(socks[0]);
214 ::close(socks[1]);
215 return;
216 }
217}
218
219
220// send the child process a signal
221void WvPipe::kill(int signum)
222{
223 if (proc.running)
224 proc.kill(signum);
225}
226
227
228// wait for the child to die
229int WvPipe::finish(bool wait_children)
230{
231 shutdown(getwfd(), SHUT_WR);
232 close();
233 while (proc.running)
234 proc.wait(1000, wait_children);
235
236 return proc.estatus;
237}
238
239
241{
242 /* FIXME: bug in WvSubProc? */
243 proc.wait(0);
244 proc.wait(0);
245 return !proc.running;
246}
247
248
249// if child_exited(), return true if it died because of a signal, or
250// false if it died due to a call to exit().
252{
253 int st = proc.estatus;
254 assert (WIFEXITED(st) || WIFSIGNALED(st));
255 return WIFSIGNALED(st);
256}
257
258
259// return the numeric exit status of the child (if it exited) or the
260// signal that killed the child (if it was killed).
262{
263 /* FIXME: bug in WvSubProc? */
264 proc.wait(0);
265 proc.wait(0);
266
267 int st = proc.estatus;
268 assert (WIFEXITED(st) || WIFSIGNALED(st));
269 if (child_killed())
270 return WTERMSIG(st);
271 else
272 return WEXITSTATUS(st);
273}
274
275
277{
278 close();
279}
280
281
282// this is necessary when putting, say, sendmail through a WvPipe on the
283// globallist so we can forget about it. We call nowrite() so that it'll
284// get the EOF and then go away when it's done, but we need to read from it
285// for it the WvPipe stop selecting true and get deleted.
286void WvPipe::ignore_read(WvStream& s)
287{
288 char buf[512];
289 s.read(&buf, sizeof(buf));
290}
Base class for streams built on Unix file descriptors.
Definition: wvfdstream.h:21
void setfd(int fd)
Sets the file descriptor for both reading and writing.
Definition: wvfdstream.h:36
int getrfd() const
Returns the Unix file descriptor for reading from this stream.
Definition: wvfdstream.h:63
int getwfd() const
Returns the Unix file descriptor for writing to this stream.
Definition: wvfdstream.h:70
virtual void close()
Closes the file descriptors.
Definition: wvfdstream.cc:117
int rfd
The file descriptor for reading.
Definition: wvfdstream.h:24
int wfd
The file descriptor for writing.
Definition: wvfdstream.h:27
WvPipe(const char *program, const char *const *argv, bool writable, bool readable, bool catch_stderr, int stdin_fd=0, int stdout_fd=1, int stderr_fd=2, WvStringList *env=NULL)
default pipe constructor; if you just want to use a pipe, use this.
Definition: wvpipe.cc:58
bool child_exited()
returns true if child is dead.
Definition: wvpipe.cc:240
int finish(bool wait_children=true)
wait for child to die.
Definition: wvpipe.cc:229
int exit_status()
returns the exit status: if child_killed()==true, the signal that killed the child.
Definition: wvpipe.cc:261
void kill(int signum)
send the child a signal (signal names are defined in signal.h)
Definition: wvpipe.cc:221
virtual ~WvPipe()
kill the child process and close the stream.
Definition: wvpipe.cc:276
bool child_killed() const
returns true if child is dead because of a signal.
Definition: wvpipe.cc:251
Unified support for streams, that is, sequences of bytes that may or may not be ready for read/write ...
Definition: wvstream.h:25
virtual size_t read(void *buf, size_t count)
read a data block on the stream.
Definition: wvstream.cc:490
virtual void seterr(int _errnum)
Override seterr() from WvError so that it auto-closes the stream.
Definition: wvstream.cc:451
This is a WvList of WvStrings, and is a really handy way to parse strings.
Definition: wvstringlist.h:28
WvString is an implementation of a simple and efficient printable-string class.
Definition: wvstring.h:330
A Unix domain socket address is really just a filename.
Definition: wvaddr.h:430