Panda3D
pfstreamBuf.cxx
Go to the documentation of this file.
1 /**
2  * PANDA 3D SOFTWARE
3  * Copyright (c) Carnegie Mellon University. All rights reserved.
4  *
5  * All use of this software is subject to the terms of the revised BSD
6  * license. You should have received a copy of this license along
7  * with this source code in a file named "LICENSE."
8  *
9  * @file pfstreamBuf.cxx
10  * @author cary
11  * @date 2000-12-12
12  */
13 
14 #include "pfstreamBuf.h"
15 #include <assert.h>
16 
17 using std::cerr;
18 using std::endl;
19 using std::string;
20 
21 PipeStreamBuf::PipeStreamBuf(PipeStreamBuf::Direction dir) :
22  _dir(dir)
23 {
24  init_pipe();
25 
26 #ifndef PHAVE_IOSTREAM
27  // These lines, which are essential on older implementations of the iostream
28  // library, are not understood by more recent versions.
29  allocate();
30  assert((dir == Input) || (dir == Output));
31  if (dir == Input) {
32  setg(base(), ebuf(), ebuf());
33  } else {
34  setp(base(), ebuf());
35  }
36 #endif /* PHAVE_IOSTREAM */
37 }
38 
39 PipeStreamBuf::
40 ~PipeStreamBuf(void) {
41  if (is_open()) {
42  sync();
43  flush();
44  close_pipe();
45  }
46 }
47 
48 void PipeStreamBuf::flush(void) {
49  assert(is_open());
50  if (_dir == Output) {
51  write_chars("", 0, true);
52  }
53 }
54 
55 void PipeStreamBuf::command(const string cmd) {
56  assert(!is_open());
57  open_pipe(cmd);
58 }
59 
60 int PipeStreamBuf::overflow(int c) {
61  assert(is_open());
62  assert(_dir == Output);
63  std::streamsize n = pptr() - pbase();
64  if (n != 0) {
65  write_chars(pbase(), n, false);
66  pbump(-n); // reset pptr()
67  }
68  if (c != EOF) {
69  // write one more character
70  char ch = c;
71  write_chars(&ch, 1, false);
72  }
73  return 0;
74 }
75 
76 int PipeStreamBuf::sync(void) {
77  assert(is_open());
78  if (_dir == Output) {
79  std::streamsize n = pptr() - pbase();
80  write_chars(pbase(), n, false);
81  pbump(-n);
82  } else {
83  std::streamsize n = egptr() - gptr();
84  if (n != 0) {
85  gbump(n); // flush all our stored input away
86 #ifndef NDEBUG
87  cerr << "pfstream tossed out " << n << " bytes" << endl;
88 #endif
89  }
90  }
91  return 0;
92 }
93 
94 int PipeStreamBuf::underflow(void) {
95  assert(_dir == Input);
96  if ((eback() == nullptr) || (gptr() == nullptr) ||
97  (egptr() == nullptr)) {
98  // must be new-style iostream library
99  char* buf = new char[4096];
100  char* ebuf = &(buf[4096]);
101  setg(buf, ebuf, ebuf);
102  }
103  if (gptr() < egptr()) {
104  char c = *(gptr());
105  return c;
106  }
107  if (eof_pipe()) {
108  return EOF;
109  }
110 #ifdef PHAVE_IOSTREAM
111  size_t len = 4096;
112 #else /* PHAVE_IOSTREAM */
113  size_t len = ebuf() - base();
114 #endif /* PHAVE_IOSTREAM */
115  char* buf = new char[len];
116  size_t n = read_pipe(buf, len);
117  int ret = buf[0];
118  if (n == 0)
119  ret = EOF;
120  else {
121 #ifdef PHAVE_IOSTREAM
122  memcpy(eback()+(len-n), buf, n);
123 #else /* PHAVE_IOSTREAM */
124  memcpy(base()+(len-n), buf, n);
125 #endif /* PHAVE_IOSTREAM */
126  gbump(-((int)n));
127  }
128  delete[] buf;
129  return ret;
130 }
131 
132 void PipeStreamBuf::write_chars(const char* start, int length, bool flush) {
133  assert(_dir == Output);
134  size_t orig = _line_buffer.length();
135  string latest(start, length);
136  string line;
137 
138  if (flush) {
139  // if we're going to flush the stream now, we dump the whole thing
140  // reguardless of whether we have reached end-of-line.
141  line = _line_buffer + latest;
142  _line_buffer = "";
143  } else {
144  // Otherwise, we check for the end-of-line character.
145  _line_buffer += latest;
146  size_t eol = _line_buffer.rfind('\n', orig);
147  if (eol != string::npos) {
148  line = _line_buffer.substr(0, eol+1);
149  _line_buffer = _line_buffer.substr(eol+1);
150  }
151  }
152  // now output 'line'
153  size_t wrote = write_pipe(line.c_str(), line.length());
154 #ifndef NDEBUG
155  if (wrote != line.length())
156  cerr << "wrote only " << wrote << " of " << line.length()
157  << " bytes to pipe" << endl;
158 #endif
159 }
160 
161 #ifndef WIN_PIPE_CALLS
162 
163 /**
164  * Initializes whatever data structures store the child process information.
165  * This function is only called once at startup, by the constructor.
166  */
167 void PipeStreamBuf::
168 init_pipe() {
169  _pipe = nullptr;
170 }
171 
172 /**
173  * Returns true if the pipe has been opened, false otherwise.
174  */
175 bool PipeStreamBuf::
176 is_open() const {
177  return _pipe != nullptr;
178 }
179 
180 /**
181  * Returns true if there is an end-of-file condition on the input, or if the
182  * pipe was never opened.
183  */
184 bool PipeStreamBuf::
185 eof_pipe() const {
186  return (_pipe == nullptr) && feof(_pipe);
187 }
188 
189 /**
190  * Forks a child to run the indicated command, and according to the setting of
191  * _dir, binds either its input or output to this process for writing or
192  * reading.
193  *
194  * Returns true on success, false on failure.
195  */
196 bool PipeStreamBuf::
197 open_pipe(const string &cmd) {
198  const char *typ = (_dir == Output)?"w":"r";
199  _pipe = popen(cmd.c_str(), typ);
200  return (_pipe != nullptr);
201 }
202 
203 /**
204  * Closes the pipe opened previously.
205  */
206 void PipeStreamBuf::
207 close_pipe() {
208  if (_pipe != nullptr) {
209  fclose(_pipe);
210  _pipe = nullptr;
211  }
212 }
213 
214 /**
215  * Writes the indicated data out to the child process opened previously.
216  * Returns the number of bytes read.
217  */
218 size_t PipeStreamBuf::
219 write_pipe(const char *data, size_t len) {
220  size_t wrote_count = fwrite(data, 1, len, _pipe);
221  fflush(_pipe);
222  return wrote_count;
223 }
224 
225 /**
226  * Reads the indicated amount of data from the child process opened
227  * previously. Returns the number of bytes read.
228  */
229 size_t PipeStreamBuf::
230 read_pipe(char *data, size_t len) {
231  return fread(data, 1, len, _pipe);
232 }
233 
234 #else // WIN_PIPE_CALLS
235 
236 // The official Windows way of reading from a child process, without using a
237 // Unix-style convenience function like popen(), is similar in principle to
238 // the Unix pipe() method. We have to first redirect our own stdout to an
239 // anonymous pipe, then we spawn a child, who inherits this new stdout. Then
240 // we can restore our own stdout, and read from the other end of the pipe.
241 
242 /**
243  * Initializes whatever data structures store the child process information.
244  * This function is only called once at startup, by the constructor.
245  */
246 void PipeStreamBuf::
247 init_pipe() {
248  _child_out = 0;
249 }
250 
251 /**
252  * Returns true if the pipe has been opened, false otherwise.
253  */
254 bool PipeStreamBuf::
255 is_open() const {
256  return (_child_out != 0);
257 }
258 
259 /**
260  * Returns true if there is an end-of-file condition on the input, or if the
261  * pipe was never opened.
262  */
263 bool PipeStreamBuf::
264 eof_pipe() const {
265  return (_child_out == 0);
266 }
267 
268 /**
269  * Forks a child to run the indicated command, and according to the setting of
270  * _dir, binds either its input or output to this process for writing or
271  * reading.
272  *
273  * Returns true on success, false on failure.
274  */
275 bool PipeStreamBuf::
276 open_pipe(const string &cmd) {
277  close_pipe();
278 
279  // At the present, this only works for input pipes. We can add code to
280  // support output pipes later if anyone cares.
281  if (_dir == Output) {
282  return false;
283  }
284 
285  // First, save our current stdout, so we can restore it after all of this
286  // nonsense.
287  HANDLE hSaveStdout = GetStdHandle(STD_OUTPUT_HANDLE);
288 
289  // Now create a pipe to accept the child processes' output.
290  HANDLE hChildStdoutRd, hChildStdoutWr;
291 
292  // Set the bInheritHandle flag so pipe handles are inherited.
293  SECURITY_ATTRIBUTES saAttr;
294  saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
295  saAttr.bInheritHandle = TRUE;
296  saAttr.lpSecurityDescriptor = nullptr;
297  if (!CreatePipe(&hChildStdoutRd, &hChildStdoutWr, &saAttr, 0)) {
298 #ifndef NDEBUG
299  cerr << "Unable to create output pipe\n";
300 #endif
301  return false;
302  }
303 
304  // Remap stdout to the "write" end of this pipe.
305  if (!SetStdHandle(STD_OUTPUT_HANDLE, hChildStdoutWr)) {
306 #ifndef NDEBUG
307  cerr << "Unable to redirect stdout\n";
308 #endif
309  CloseHandle(hChildStdoutRd);
310  CloseHandle(hChildStdoutWr);
311  return false;
312  }
313 
314  // Create noninheritable read handle and close the inheritable read handle.
315 
316  BOOL fSuccess = DuplicateHandle(GetCurrentProcess(), hChildStdoutRd,
317  GetCurrentProcess(), &_child_out,
318  0, FALSE, DUPLICATE_SAME_ACCESS);
319 
320  if (!fSuccess) {
321 #ifndef NDEBUG
322  cerr << "DuplicateHandle failed\n";
323 #endif
324  CloseHandle(hChildStdoutRd);
325  CloseHandle(hChildStdoutWr);
326  return false;
327  }
328  CloseHandle(hChildStdoutRd);
329 
330  // Now spawn the child process.
331 
332  // Both WinExec() and CreateProcess() want a non-const char pointer. Maybe
333  // they change it, and maybe they don't. I'm not taking chances.
334  char *cmdline = new char[cmd.length() + 1];
335  strcpy(cmdline, cmd.c_str());
336 
337  // We should be using CreateProcess() instead of WinExec(), but that seems
338  // to be likely to crash Win98. WinExec() seems better behaved, and it's
339  // all we need anyway.
340  if (!WinExec(cmdline, 0)) {
341 #ifndef NDEBUG
342  cerr << "Unable to spawn process.\n";
343 #endif
344  close_pipe();
345  // Don't return yet, since we still need to clean up.
346  }
347 
348  delete[] cmdline;
349 
350  // Now restore our own stdout, up here in the parent process.
351  if (!SetStdHandle(STD_OUTPUT_HANDLE, hSaveStdout)) {
352 #ifndef NDEBUG
353  cerr << "Unable to restore stdout\n";
354 #endif
355  }
356 
357  // Close the write end of the pipe before reading from the read end of the
358  // pipe.
359  if (!CloseHandle(hChildStdoutWr)) {
360 #ifndef NDEBUG
361  cerr << "Unable to close write end of pipe\n";
362 #endif
363  }
364 
365  return (_child_out != 0);
366 }
367 
368 /**
369  * Closes the pipe opened previously.
370  */
371 void PipeStreamBuf::
372 close_pipe() {
373  if (_child_out != 0) {
374  CloseHandle(_child_out);
375  _child_out = 0;
376  }
377 }
378 
379 /**
380  * Writes the indicated data out to the child process opened previously.
381  * Returns the number of bytes read.
382  */
383 size_t PipeStreamBuf::
384 write_pipe(const char *data, size_t len) {
385  return 0;
386 }
387 
388 /**
389  * Reads the indicated amount of data from the child process opened
390  * previously. Returns the number of bytes read.
391  */
392 size_t PipeStreamBuf::
393 read_pipe(char *data, size_t len) {
394  if (_child_out == 0) {
395  return 0;
396  }
397  DWORD dwRead;
398  if (!ReadFile(_child_out, data, len, &dwRead, nullptr)) {
399  close_pipe();
400  return 0;
401  }
402 
403  return dwRead;
404 }
405 
406 
407 #endif // WIN_PIPE_CALLS
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.