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"
25 static 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 
58 WvPipe::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 
67 WvPipe::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 
83 WvPipe::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 
98 void 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
221 void WvPipe::kill(int signum)
222 {
223  if (proc.running)
224  proc.kill(signum);
225 }
226 
227 
228 // wait for the child to die
229 int 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.
286 void WvPipe::ignore_read(WvStream& s)
287 {
288  char buf[512];
289  s.read(&buf, sizeof(buf));
290 }
WvPipe::finish
int finish(bool wait_children=true)
wait for child to die.
Definition: wvpipe.cc:229
WvPipe::child_exited
bool child_exited()
returns true if child is dead.
Definition: wvpipe.cc:240
WvFdStream::getrfd
int getrfd() const
Returns the Unix file descriptor for reading from this stream.
Definition: wvfdstream.h:63
WvStream::seterr
virtual void seterr(int _errnum)
Override seterr() from WvError so that it auto-closes the stream.
Definition: wvstream.cc:451
WvString
WvString is an implementation of a simple and efficient printable-string class.
Definition: wvstring.h:329
WvFdStream::setfd
void setfd(int fd)
Sets the file descriptor for both reading and writing.
Definition: wvfdstream.h:36
WvPipe::kill
void kill(int signum)
send the child a signal (signal names are defined in signal.h)
Definition: wvpipe.cc:221
WvPipe::~WvPipe
virtual ~WvPipe()
kill the child process and close the stream.
Definition: wvpipe.cc:276
WvPipe::WvPipe
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
WvStream::read
virtual size_t read(void *buf, size_t count)
read a data block on the stream.
Definition: wvstream.cc:490
WvStream
Unified support for streams, that is, sequences of bytes that may or may not be ready for read/write ...
Definition: wvstream.h:24
WvFdStream::close
virtual void close()
Closes the file descriptors.
Definition: wvfdstream.cc:117
WvFdStream::getwfd
int getwfd() const
Returns the Unix file descriptor for writing to this stream.
Definition: wvfdstream.h:70
WvPipe::child_killed
bool child_killed() const
returns true if child is dead because of a signal.
Definition: wvpipe.cc:251
WvFdStream
Base class for streams built on Unix file descriptors.
Definition: wvfdstream.h:20
WvUnixAddr
A Unix domain socket address is really just a filename.
Definition: wvaddr.h:429
WvStringList
This is a WvList of WvStrings, and is a really handy way to parse strings.
Definition: wvstringlist.h:27
WvPipe::exit_status
int exit_status()
returns the exit status: if child_killed()==true, the signal that killed the child.
Definition: wvpipe.cc:261
WvFdStream::wfd
int wfd
The file descriptor for writing.
Definition: wvfdstream.h:27
WvFdStream::rfd
int rfd
The file descriptor for reading.
Definition: wvfdstream.h:24