Panda3D
bioStreamBuf.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 bioStreamBuf.cxx
10  * @author drose
11  * @date 2002-09-25
12  */
13 
14 #include "bioStreamBuf.h"
15 #include "config_downloader.h"
16 #include "openSSLWrapper.h"
17 #include <errno.h>
18 
19 #ifdef HAVE_OPENSSL
20 
21 #if defined(WIN32_VC) || defined(WIN64_VC)
22  #include <WinSock2.h>
23  #include <windows.h> // for WSAGetLastError()
24  #undef X509_NAME
25 #endif // WIN32_VC
26 
27 /**
28  *
29  */
30 BioStreamBuf::
31 BioStreamBuf() {
32  _read_open = false;
33  _write_open = false;
34 
35 #ifdef PHAVE_IOSTREAM
36  _buffer = (char *)PANDA_MALLOC_ARRAY(8192);
37  char *ebuf = _buffer + 8192;
38  char *mbuf = _buffer + 4096;
39  setg(_buffer, mbuf, mbuf);
40  setp(mbuf, ebuf);
41 
42 #else
43  allocate();
44  // Chop the buffer in half. The bottom half goes to the get buffer; the top
45  // half goes to the put buffer.
46  char *b = base();
47  char *t = ebuf();
48  char *m = b + (t - b) / 2;
49  setg(b, m, m);
50  setp(b, m);
51 #endif
52 }
53 
54 /**
55  *
56  */
57 BioStreamBuf::
58 ~BioStreamBuf() {
59  close();
60 #ifdef PHAVE_IOSTREAM
61  PANDA_FREE_ARRAY(_buffer);
62 #endif
63 }
64 
65 /**
66  *
67  */
68 void BioStreamBuf::
69 open(BioPtr *source) {
70  _source = source;
71  _read_open = true;
72  _write_open = true;
73 }
74 
75 /**
76  *
77  */
78 void BioStreamBuf::
79 close() {
80  sync();
81  _source.clear();
82  _read_open = false;
83  _write_open = false;
84 }
85 
86 /**
87  * Called by the system ostream implementation when its internal buffer is
88  * filled, plus one character.
89  */
90 int BioStreamBuf::
91 overflow(int ch) {
92  size_t n = pptr() - pbase();
93  if (downloader_cat.is_spam()) {
94  downloader_cat.spam()
95  << "BioStreamBuf::overflow, " << n << " bytes\n";
96  }
97  if (n != 0) {
98  size_t num_wrote = write_chars(pbase(), n);
99  pbump(-(int)n);
100  if (num_wrote != n) {
101  return EOF;
102  }
103  }
104 
105  if (ch != EOF) {
106  // Store the next character back in the buffer.
107  *pptr() = ch;
108  pbump(1);
109  }
110 
111  return 0;
112 }
113 
114 /**
115  * Called by the system iostream implementation to implement a flush
116  * operation.
117  */
118 int BioStreamBuf::
119 sync() {
120  /*
121  size_t n = egptr() - gptr();
122  gbump(n);
123  */
124 
125  size_t n = pptr() - pbase();
126 
127  if (downloader_cat.is_spam() && n != 0) {
128  downloader_cat.spam()
129  << "BioStreamBuf::sync, " << n << " bytes\n";
130  }
131 
132  size_t num_wrote = write_chars(pbase(), n);
133  pbump(-(int)n);
134 
135  if (num_wrote != n) {
136  return EOF;
137  }
138 
139  return 0;
140 }
141 
142 /**
143  * Called by the system istream implementation when its internal buffer needs
144  * more characters.
145  */
146 int BioStreamBuf::
147 underflow() {
148  // Sometimes underflow() is called even if the buffer is not empty.
149  if (gptr() >= egptr()) {
150  size_t buffer_size = egptr() - eback();
151  gbump(-(int)buffer_size);
152 
153  size_t num_bytes = buffer_size;
154 
155  // BIO_read might return -1 or -2 on eof or error, so we have to allow for
156  // negative numbers.
157  int read_count = BIO_read(*_source, gptr(), buffer_size);
158  thread_consider_yield();
159 
160  if (read_count != (int)num_bytes) {
161  // Oops, we didn't read what we thought we would.
162  if (read_count <= 0) {
163  // Immediately save the os error in case we screw up and do something
164  // that will change its value before we can output it.
165 #if defined(WIN32_VC) || defined(WIN64_VC)
166  int os_error = WSAGetLastError();
167 #else
168  int os_error = errno;
169 #endif // WIN32_VC
170 
171  // Though BIO_eof() is tempting, it appears there are cases in which
172  // that never returns true, if the socket is closed by the server.
173  // But BIO_should_retry() *appears* to be reliable.
174  _read_open = (BIO_should_retry(*_source) != 0);
175 
176 #ifdef IS_OSX
177  // occassionally we get -1 on read_open on the mac the os_error is 35
178  // which means "Resource temporarily unavailable".
179  if (!_read_open && os_error == 35) {
180  downloader_cat.warning() << "forcing retry to true again and _read_open to true\n";
181  BIO_set_retry_read(*_source);
182  _read_open = true;
183  }
184 #endif
185  if (!_read_open) {
186  downloader_cat.info()
187  << "Lost connection to "
188  << _source->get_server_name() << ":"
189  << _source->get_port() << " (" << read_count << ").\n";
190  OpenSSLWrapper::get_global_ptr()->notify_ssl_errors();
191 
192  SSL *ssl = nullptr;
193  BIO_get_ssl(*_source, &ssl);
194  if (ssl != nullptr) {
195  downloader_cat.warning()
196  << "OpenSSL error code: " << SSL_get_error(ssl, read_count)
197  << "\n";
198  }
199 
200 #if defined(WIN32_VC) || defined(WIN64_VC)
201  downloader_cat.warning()
202  << "Windows error code: " << os_error << "\n";
203 #else
204  downloader_cat.warning()
205  << "Unix error code: " << os_error << "\n";
206 #endif // WIN32_VC
207  }
208  gbump(num_bytes);
209  return EOF;
210  }
211 
212  // Slide what we did read to the top of the buffer.
213  nassertr(read_count < (int)num_bytes, EOF);
214  size_t delta = (int)num_bytes - read_count;
215  memmove(gptr() + delta, gptr(), read_count);
216  gbump(delta);
217  }
218 
219  if (downloader_cat.is_spam()) {
220  downloader_cat.spam()
221  << "read " << read_count << " bytes from " << _source << "\n";
222  }
223  }
224 
225  return (unsigned char)*gptr();
226 }
227 
228 /**
229  * Sends some characters to the dest stream. Does not return until all
230  * characters are sent or the socket is closed, even if the underlying BIO is
231  * non-blocking.
232  */
233 size_t BioStreamBuf::
234 write_chars(const char *start, size_t length) {
235  if (length != 0) {
236  size_t wrote_so_far = 0;
237 
238  int write_count = BIO_write(*_source, start, length);
239  thread_consider_yield();
240  while (write_count != (int)(length - wrote_so_far)) {
241  if (write_count <= 0) {
242 /*
243  * http:www.openssl.orgdocscryptoBIO_s_bio.html "Calls to BIO_write() will
244  * place data in the buffer or request a retry if the buffer is full." when
245  * the server is terminated, this seems to be the best way of detecting that
246  * case on the client: a BIO write error without a retry request _write_open =
247  * BIO_should_retry(*_source); _write_open = !BIO_eof(*_source);
248  */
249  _write_open = (BIO_should_write(*_source) != 0 || BIO_should_retry(*_source) != 0);
250  if (!_write_open) {
251  return wrote_so_far;
252  }
253 
254  // Block on the underlying socket before we try to write some more.
255  int fd = -1;
256  BIO_get_fd(*_source, &fd);
257  if (fd < 0) {
258  downloader_cat.warning()
259  << "socket BIO has no file descriptor.\n";
260  } else {
261  if (downloader_cat.is_spam()) {
262  downloader_cat.spam()
263  << "waiting to write to BIO.\n";
264  }
265 #if defined(HAVE_THREADS) && defined(SIMPLE_THREADS)
266  // In SIMPLE_THREADS mode, instead of blocking, simply yield the
267  // thread.
268  thread_yield();
269 #else
270  // In any other threading mode, we actually want to block.
271  fd_set wset;
272  FD_ZERO(&wset);
273  FD_SET(fd, &wset);
274  select(fd + 1, nullptr, &wset, nullptr, nullptr);
275 #endif // SIMPLE_THREADS
276  }
277 
278  } else {
279  // wrote some characters.
280  wrote_so_far += write_count;
281  if (downloader_cat.is_spam()) {
282  downloader_cat.spam()
283  << "wrote " << write_count << " bytes to " << _source << "\n";
284  }
285  }
286 
287  // Try to write some more.
288  write_count = BIO_write(*_source, start + wrote_so_far, length - wrote_so_far);
289  if (downloader_cat.is_spam()) {
290  downloader_cat.spam()
291  << "continued, wrote " << write_count << " bytes.\n";
292  }
293  thread_consider_yield();
294  }
295  if (downloader_cat.is_spam()) {
296  downloader_cat.spam()
297  << "wrote " << write_count << " bytes to " << _source << "\n";
298  }
299  }
300 
301  return length;
302 }
303 
304 #endif // HAVE_OPENSSL
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.