WvStreams
streams.cc
1#include "streams.h"
2#include "wvstring.h"
3#include <assert.h>
4#include <errno.h>
5#include <conio.h>
6#include <stdio.h>
7#include <io.h>
8
9#if _MSC_VER
10// MS Visual C++ doesn't support varags preproc macros
11# define DPRINTF
12#else
13#if 0
14# define DPRINTF(x, args...) do { \
15 printf(x, ## args); fflush(stdout); \
16 } while (0)
17#else
18# define DPRINTF(x, args...) do { } while(0)
19#endif
20#endif
21
22
23// this class changes the default libc stdout buffering to "line buffered"
24// and stderr to "unbuffered", like they should be in any sane system.
25// Apparently they start off as "fully buffered" in most Windows systems.
27{
28public:
30 {
31 setvbuf(stdout, NULL, _IOLBF, 0);
32 setvbuf(stderr, NULL, _IONBF, 0);
33 }
34};
35static FixLibcIoBuffers fixbufs;
36
37
38// these versions of close/read/write try to work with both sockets and
39// msvcrt file descriptors! (I hope we never get a socket with the same
40// VALUE as a file descriptor!)
41
42
43static void errcode(int err)
44{
45 if (err == EIO)
46 err = EBADF; // sometimes we get EIO when Unix would be EBADF
47 if (err == WSAENOTSOCK)
48 err = EBADF; // if it's not a socket, it's also not a fd
49 SetLastError(err);
50 errno = err;
51}
52
53
54static bool is_socket(int fd)
55{
56 // if _get_osfhandle doesn't work, it must not be a fd, so assume it's
57 // a socket.
58 return (HANDLE)_get_osfhandle(fd) == INVALID_HANDLE_VALUE;
59}
60
61
62int close(int fd)
63{
64 int ret;
65 if (is_socket(fd))
66 {
67 ret = closesocket(fd);
68 errcode(GetLastError());
69 }
70 else
71 {
72 ret = _close(fd);
73 errcode(errno);
74 }
75 return ret;
76}
77
78
79int read(int fd, void *buf, size_t count)
80{
81 int ret;
82 if (is_socket(fd))
83 {
84 ret = recv(fd, (char *)buf, count, 0);
85 errcode(GetLastError());
86 }
87 else
88 {
89 ret = _read(fd, buf, count);
90 errcode(errno);
91 }
92 return ret;
93}
94
95
96int write(int fd, const void *buf, size_t count)
97{
98 int ret;
99 if (is_socket(fd))
100 {
101 ret = send(fd, (char *)buf, count, 0);
102 errcode(GetLastError());
103 }
104 else
105 {
106 ret = _write(fd, buf, count);
107 errcode(errno);
108 }
109 return ret;
110}
111
112
113int socketpair(int family, int type, int protocol, int *sb)
114{
115 SOCKET insock, outsock, newsock;
116 struct sockaddr_in sock_in;
117
118 if (type != SOCK_STREAM)
119 return -1;
120
121 newsock = socket(AF_INET, type, 0);
122 if (newsock == INVALID_SOCKET)
123 return -1;
124
125 sock_in.sin_family = AF_INET;
126 sock_in.sin_port = 0;
127 sock_in.sin_addr.s_addr = INADDR_ANY;
128 if (bind(newsock, (struct sockaddr *) &sock_in, sizeof (sock_in)) < 0)
129 return -1;
130
131 int len = sizeof (sock_in);
132 if (getsockname(newsock, (struct sockaddr *)&sock_in, &len) < 0)
133 return -1;
134
135 if (listen(newsock, 2) < 0)
136 return -1;
137
138 outsock = socket(AF_INET, type, 0);
139 if (outsock == INVALID_SOCKET)
140 return -1;
141
142 sock_in.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
143 if (connect(outsock, (struct sockaddr *)&sock_in, sizeof(sock_in)) < 0)
144 return -1;
145
146 /* For stream sockets, accept the connection and close the listener */
147 len = sizeof(sock_in);
148 insock = accept(newsock, (struct sockaddr *)&sock_in, &len);
149 if (insock == INVALID_SOCKET)
150 return -1;
151
152 if (closesocket(newsock) < 0)
153 return -1;
154
155 sb[0] = insock;
156 sb[1] = outsock;
157 return 0;
158}
159
160
161static void CALLBACK completion(DWORD error, DWORD nread, LPOVERLAPPED ov)
162{
163}
164
165
166static size_t fake_read(int fd, void *buf, size_t len)
167{
168 HANDLE h = (HANDLE)_get_osfhandle(fd);
169 INPUT_RECORD p;
170 DPRINTF("fake_read(%d/%d,%p,%d) = ", fd, (int)h, buf, (int)len);
171
172 DWORD ret = 0;
173 OVERLAPPED ov;
174 memset(&ov, 0, sizeof(ov));
175 ov.Offset = SetFilePointer(h, 0, NULL, FILE_CURRENT);
176
177 if (PeekNamedPipe(h, NULL, 0, NULL, &ret, NULL))
178 {
179 // cygwin sshd/telnetd uses named pipes for stdin. We have to
180 // support these separately. Getting stuck in ReadFile on a named
181 // pipe appears to freeze up gethostbyname() for some reason on win2k!
182 DPRINTF("(stdin is a pipe)\n");
183 while (PeekNamedPipe(h, NULL, 0, NULL, &ret, NULL) && !ret)
184 {
185 DPRINTF(".");
186 Sleep(100);
187 }
188 ReadFile(h, buf, len, &ret, NULL);
189 }
190 else if (PeekConsoleInput(h, &p, 1, &ret))
191 {
192 // a typical stdin/out pair refers to a console. Unfortunately,
193 // console I/O is stupid: you can poll it to see if it's ready, but
194 // if you have it in line mode, then it's not *really* ready.
195 // ReadConsole/ReadFile will only return after the user hits enter.
196 // Unfortunately, it seems the only way around this is to disable
197 // line/echo mode and fake it ourselves. Hopefully this isn't too
198 // ugly...
199 DPRINTF("(stdin is a console)\n");
200
201 size_t used = 0;
202 char *xbuf = (char *)buf;
203 HANDLE hout = CreateFile("CONOUT$", GENERIC_READ | GENERIC_WRITE,
204 FILE_SHARE_READ | FILE_SHARE_WRITE,
205 NULL, OPEN_EXISTING, 0, 0);
206
207 DWORD conmode = 0;
208 GetConsoleMode(h, &conmode);
209 SetConsoleMode(h, conmode &
210 ~(ENABLE_LINE_INPUT | ENABLE_MOUSE_INPUT | ENABLE_ECHO_INPUT));
211
212 while (PeekConsoleInput(h, &p, 1, &ret))
213 {
214 DWORD tmp;
215 if (ret)
216 {
217 ReadConsoleInput(h, &p, 1, &ret);
218 assert(ret);
219 if (p.EventType == KEY_EVENT && p.Event.KeyEvent.bKeyDown)
220 {
221 int key = p.Event.KeyEvent.uChar.AsciiChar;
222 if (key == '\r') // end of line
223 {
224 xbuf[used++] = '\n';
225 WriteConsole(hout, "\r\n", 2, &tmp, NULL);
226 ret = used;
227 break;
228 }
229 else if (key == '\b' && used > 0)
230 {
231 used--;
232 WriteConsole(hout, "\b \b", 3, &tmp, NULL);
233 }
234 else if (key && used < len-1)
235 {
236 xbuf[used++] = key;
237 WriteConsole(hout, xbuf+used-1, 1, &tmp, NULL);
238 }
239 }
240 }
241 else
242 {
243 DPRINTF(".");
244 WaitForSingleObjectEx(h, 1000, true);
245 }
246 }
247
248 CloseHandle(hout);
249 }
250 else
251 {
252 // stdin might be redirected from a file, in which case we can
253 // probably safely (heh) assume it'll never block. Still, try
254 // ReadFileEx with a timeout first and see if that works.
255 DPRINTF("(stdin is a file)\n");
256 while (!ret)
257 {
258 DPRINTF(".");
259 int rv = 0;
260 if (ReadFileEx(h, buf, 0, &ov, &completion))
261 {
262 rv = SleepEx(1000, true);
263 CancelIo(h);
264 DPRINTF("(rv is %d)\n", rv);
265 if (rv == WAIT_IO_COMPLETION)
266 {
267 ReadFile(h, buf, len, &ret, NULL);
268 break;
269 }
270 else if (!rv) // timed out
271 Sleep(1); // ensure lock is released for nonzero time (1ms)
272 else
273 return 0; // unknown problem: assume EOF
274 }
275 else
276 {
277 // can't do ReadFileEx: probably stupid Win9x.
278 ReadFile(h, buf, len, &ret, NULL);
279 break;
280 }
281 }
282 }
283
284 DPRINTF("[%d]\n", ret);
285 return ret;
286}
287
288
289DWORD WINAPI fd2socket_fwd(LPVOID lpThreadParameter)
290{
291// return 0;
292 DWORD retval = 0;
293 const int BUFSIZE = 512;
294 socket_fd_pair *pair = (socket_fd_pair *)lpThreadParameter;
295
296 // fprintf(stderr, "forwarding %d -> %d\n",
297 // pair->fd, pair->socket); fflush(stderr);
298
299 char buf[BUFSIZE];
300 while (true)
301 {
302 char *ptr = buf;
303
304 size_t bytes = fake_read(pair->fd, ptr, BUFSIZE);
305 if (bytes <= 0) { retval = bytes; break; }
306 while (bytes > 0)
307 {
308 int written = send(pair->socket, ptr, bytes, 0);
309 if (written < 0) { retval = written; break; }
310
311 bytes -= written;
312 ptr += written;
313 }
314 }
315
316 shutdown(pair->socket, SD_BOTH);
317 closesocket(pair->socket);
318 // fprintf(stderr, "TERMINATING-%d\n", pair->fd); fflush(stderr);
319 return retval;
320}
321
322
323DWORD WINAPI socket2fd_fwd(LPVOID lpThreadParameter)
324{
325 DWORD retval = 0;
326 const int BUFSIZE = 512;
327 socket_fd_pair *pair = (socket_fd_pair *)lpThreadParameter;
328
329 char buf[BUFSIZE];
330 while (true)
331 {
332 char *ptr = buf;
333 int bytes = recv(pair->socket, ptr, BUFSIZE, 0);
334 if (bytes <= 0) { retval = bytes; break; }
335 while (bytes > 0)
336 {
337 int written = _write(pair->fd, ptr, bytes);
338 if (written < 0) { retval = written; break; }
339 bytes -= written;
340 ptr += written;
341 }
342 }
343 shutdown(pair->socket, SD_BOTH);
344 closesocket(pair->socket);
345 // fprintf(stderr, "TERMINATING-%d\n", pair->fd); fflush(stderr);
346 return retval;
347}
348
349
350SocketFromFDMaker::SocketFromFDMaker(int fd,
351 LPTHREAD_START_ROUTINE lpStartAddress, bool wait)
352 : m_hThread(0), m_socket(INVALID_SOCKET), m_wait(wait)
353{
354 // might do this twice
355 WSAData wsaData;
356 WSAStartup(MAKEWORD(2,0), &wsaData);
357
358 int s[2], result;
359 result = socketpair(AF_INET, SOCK_STREAM, 0, s);
360 assert(result == 0);
361
362 m_pair.fd = fd;
363 m_pair.socket = s[0];
364 m_socket = s[1];
365
366 DWORD threadid;
367 m_hThread = CreateThread(
368 NULL,
369 0,
370 lpStartAddress,
371 &m_pair,
372 0,
373 &threadid
374 );
375 assert(m_hThread);
376}
377
378
379SocketFromFDMaker::~SocketFromFDMaker()
380{
381 int result;
382 // fprintf(stderr, "shutting down #%d\n", m_socket);
383 if (m_socket != INVALID_SOCKET)
384 {
385 result = shutdown(m_socket, SD_BOTH);
386
387 // this assertion will fail if someone has already closed the
388 // socket; eg. if you give the socket to a WvFDStream and then let
389 // him close it. But you shouldn't do that, because nobody is
390 // supposed to close stdin/stdout/stderr!
391 if (result != 0)
392 {
393 int e = GetLastError();
394 if (e == WSASYSNOTREADY || e == WSANOTINITIALISED)
395 {
396 fprintf(stderr, "Abnormal termination. Skipping cleanup.\n");
397 _exit(42);
398 }
399 else
400 {
401 fprintf(stderr,
402 "ERROR! Socket #%d was already shut down! (%d)\n",
403 m_socket, e);
404 assert(result == 0);
405 }
406 }
407
408 if (m_wait) // wait for socket->fd copier
409 {
410 // wait for thread to terminate. Since it's reading from a
411 // socket (m_wait==true), this will be safe, because we know
412 // it'll die politely when it should.
413 WaitForSingleObject(m_hThread, INFINITE);
414 }
415 else
416 {
417 // FIXME: fd->socket copier will never die politely. It gets
418 // stuck in _read(), which enters a critical section and
419 // then blocks until input is available. Unfortunately, it's
420 // impossible to make input *not* available.
421 //
422 // TerminateThread() is generally evil, and doesn't help here
423 // anyway: it just leaves that critical section locked forever, so
424 // no operation on that fd will *ever* finish.
425 //
426 // Answer: just do nothing. Someone will clean up the broken
427 // thread eventually, I guess. (ExitProcess() claims to do
428 // this, but I hope it doesn't get stuck in a critical section...)
429 }
430
431 close(m_socket);
432 }
433 CloseHandle(m_hThread);
434}