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.
void setfd(int fd)
Sets the file descriptor for both reading and writing.
int getrfd() const
Returns the Unix file descriptor for reading from this stream.
int getwfd() const
Returns the Unix file descriptor for writing to this stream.
virtual void close()
Closes the file descriptors.
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 ...
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.
WvString is an implementation of a simple and efficient printable-string class.
A Unix domain socket address is really just a filename.