updates from orpheus for windoze build
[feisty_meow.git] / octopi / library / sockets / raw_socket.cpp
1 /*****************************************************************************\
2 *                                                                             *
3 *  Name   : raw_socket                                                        *
4 *  Author : Chris Koeritz                                                     *
5 *                                                                             *
6 *******************************************************************************
7 * Copyright (c) 1991-$now By Author.  This program is free software; you can  *
8 * redistribute it and/or modify it under the terms of the GNU General Public  *
9 * License as published by the Free Software Foundation; either version 2 of   *
10 * the License or (at your option) any later version.  This is online at:      *
11 *     http://www.fsf.org/copyleft/gpl.html                                    *
12 * Please send any updates to: fred@gruntose.com                               *
13 \*****************************************************************************/
14
15 #include "raw_socket.h"
16 #include "tcpip_stack.h"
17
18 #include <basis/functions.h>
19 #include <loggers/critical_events.h>
20 #include <loggers/program_wide_logger.h>
21 #include <timely/time_stamp.h>
22
23 #include <stdlib.h>
24 #ifdef __APPLE__
25   #include <fcntl.h>
26 #endif
27 //#ifdef __UNIX__
28   #include <arpa/inet.h>
29   #include <errno.h>
30   #include <netinet/tcp.h>
31   #include <sys/ioctl.h>
32   #include <sys/socket.h>
33   #include <unistd.h>
34   #define OPTYPE (void *)
35 //#endif
36 //#ifdef __WIN32__
37 //  #define OPTYPE (char *)
38 //#endif
39
40 using namespace basis;
41 using namespace loggers;
42 using namespace timely;
43
44 namespace sockets {
45
46 //#define DEBUG_RAW_SOCKET
47   // uncomment for noisy diagnostics.
48
49 #undef LOG
50 #define LOG(to_print) CLASS_EMERGENCY_LOG(program_wide_logger::get(), to_print)
51
52 const int MULTIPLE_DISCONNECT_CHECKS = 28;
53   // we will make certain that select really says there's data and ioctl
54   // really says there's no data waiting before we believe there's been
55   // a disconnect.  we'll check that state the number of times specified.
56
57 class fd_set_wrapper : public fd_set {};
58
59 //////////////
60
61 const basis::un_int NON_BLOCKING = FIONBIO;
62 const basis::un_int IOCTL_READ = FIONREAD;
63
64 /*
65 #ifdef __WIN32__
66 // defined by winsock header but not present in the winsock dll.
67 int PASCAL FAR __WSAFDIsSet(SOCKET fd, fd_set FAR *the_set)
68 {
69   int i = the_set->fd_count;
70   while (i--)
71     if (the_set->fd_array[i] == fd)
72       return true;
73   return false;
74 }
75 #endif
76 */
77
78 //////////////
79
80 raw_socket::raw_socket()
81 : _stack(new tcpip_stack())
82 {}
83
84 raw_socket::~raw_socket()
85 {
86   WHACK(_stack);
87 }
88
89 int raw_socket::close(basis::un_int &socket)
90 {
91   int to_return = 0;
92 //#ifdef __WIN32__
93 //  to_return = closesocket(socket);
94 //#endif
95 //#ifdef __UNIX__
96   to_return = ::close(socket);
97 //#endif
98   socket = 0;
99   return to_return;
100 }
101
102 //move this into parser bits as a OR combiner or something.
103 void combine(astring &existing, const astring &addition)
104 {
105   if (addition.t()) {
106     if (existing.t()) existing += " | ";
107     existing += addition;
108   }
109 }
110
111 astring raw_socket::interest_name(int interest)
112 {
113   astring to_return;
114   if (interest & SI_CONNECTED) combine(to_return, "CONNECTED");
115   if (interest & SI_DISCONNECTED) combine(to_return, "DISCONNECTED");
116   if (interest & SI_WRITABLE) combine(to_return, "WRITABLE");
117   if (interest & SI_READABLE) combine(to_return, "READABLE");
118   if (interest & SI_ERRONEOUS) combine(to_return, "ERRONEOUS");
119   if (!interest) combine(to_return, "NORMAL");
120   return to_return;
121 }
122
123 int raw_socket::ioctl(basis::un_int socket, int request, void *argp) const
124 {
125 //#ifdef __UNIX__
126   return ::ioctl(socket, request, argp);
127   /*
128 #endif
129 #ifdef __WIN32__
130   #ifdef _MSC_VER
131     return ioctlsocket(socket, request, (un_long *)argp);
132   #else
133     return ioctlsocket(socket, request, (un_int *)argp);
134   #endif
135 #endif
136 */
137 }
138
139 bool raw_socket::set_non_blocking(basis::un_int socket, bool non_blocking)
140 {
141   FUNCDEF("set_non_blocking");
142 #ifdef __APPLE__
143   int curr_flags = fcntl(socket, F_GETFL, 0);
144   if (fcntl(socket, F_SETFL, curr_flags | O_NONBLOCK) < 0) return false;
145 #else
146   int arg = int(non_blocking);
147   if (negative(ioctl(socket, NON_BLOCKING, &arg))) {
148     LOG(a_sprintf("Could not set non-blocking (FIONBIO) option on raw_socket %u.", socket));
149     return false;
150   }
151 #endif
152   return true;
153 }
154
155 bool raw_socket::set_nagle_algorithm(basis::un_int socket, bool use_nagle)
156 {
157   FUNCDEF("set_nagle_algorithm");
158   int arg = int(!use_nagle);  // opposite of the flag, since we set no-delay.
159   if (negative(setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, OPTYPE &arg,
160       sizeof(arg)))) {
161     LOG(a_sprintf("Could not change nagle coalescing mode on %u.", socket));
162     return false;
163   }
164   return true;
165 }
166
167 bool raw_socket::set_broadcast(basis::un_int socket, bool broadcasting)
168 {
169   FUNCDEF("set_broadcast");
170   int arg = int(broadcasting);
171   if (negative(setsockopt(socket, SOL_SOCKET, SO_BROADCAST, OPTYPE &arg,
172       sizeof(arg)))) {
173     LOG(a_sprintf("Could not change broadcast mode on %u.", socket));
174     return false;
175   }
176   return true;
177 }
178
179 bool raw_socket::set_reuse_address(basis::un_int socket, bool reuse)
180 {
181   FUNCDEF("set_reuse_address");
182   int arg = int(reuse);
183   if (negative(setsockopt(socket, SOL_SOCKET, SO_REUSEADDR, OPTYPE &arg,
184       sizeof(arg)))) {
185     LOG(a_sprintf("Could not set reuse address mode on %u.", socket));
186     return false;
187   }
188   return true;
189 }
190
191 bool raw_socket::set_keep_alive(basis::un_int socket, bool keep_alive)
192 {
193   FUNCDEF("set_keep_alive");
194   int arg = int(keep_alive);
195   if (negative(setsockopt(socket, SOL_SOCKET, SO_KEEPALIVE, OPTYPE &arg,
196       sizeof(arg)))) {
197     LOG(a_sprintf("Could not set keep alive mode on %u.", socket));
198     return false;
199   }
200   return true;
201 }
202
203 int raw_socket::select(basis::un_int socket, int mode, int timeout) const
204 {
205   FUNCDEF("select [single]");
206   if (!socket) return SI_ERRONEOUS;
207   fd_set_wrapper read_list, write_list, exceps;
208   int ret = inner_select(socket, mode, timeout, read_list, write_list, exceps);
209   if (!ret) return 0;  // nothing is happening.
210   if (ret == SI_ERRONEOUS) return SI_ERRONEOUS;  // something bad happened.
211   // otherwise we should be at base-line status.
212   return analyze_select_result(socket, mode, read_list, write_list, exceps);
213 }
214
215 int raw_socket::inner_select(basis::un_int socket, int mode, int timeout,
216     fd_set_wrapper &read_list, fd_set_wrapper &write_list,
217     fd_set_wrapper &exceptions) const
218 {
219   FUNCDEF("inner_select");
220   // setup the file descriptor sets for the select.  we check readability,
221   // writability and exception status.
222   FD_ZERO(&read_list); FD_SET(socket, &read_list);
223   FD_ZERO(&write_list); FD_SET(socket, &write_list);
224   FD_ZERO(&exceptions); FD_SET(socket, &exceptions);
225
226   timeval base_time_out;
227   time_stamp::fill_timeval_ms(base_time_out, timeout);
228     // timeval has tv_sec=seconds, tv_usec=microseconds.
229 //#if !defined(__GNU_WINDOWS__)
230   timeval *time_out = &base_time_out;
231   /*
232 #elif defined(__GNU_WINDOWS__)
233   __ms_timeval win_time_out;
234   win_time_out.tv_sec = base_time_out.tv_sec;
235   win_time_out.tv_usec = base_time_out.tv_usec;
236   __ms_timeval *time_out = &win_time_out;
237 #endif
238 */
239
240   // select will tell us about the socket.
241   int ret = ::select(socket + 1,
242       (mode & SELECTING_JUST_WRITE)? NULL_POINTER : &read_list,
243       (mode & SELECTING_JUST_READ)? NULL_POINTER : &write_list,
244       &exceptions, time_out);
245   int error = critical_events::system_error();
246   if (!ret) return 0;  // nothing to report.
247
248   if (ret == SOCKET_ERROR) {
249     switch (error) {
250       // all real errors fall out to the error handling stuff.
251       case SOCK_EFAULT:  // intentional fall-through.
252       case SOCK_ENETDOWN:  // intentional fall-through.
253       case SOCK_EINVAL:  // intentional fall-through.
254       case SOCK_EINTR:  // intentional fall-through.
255 /* #ifdef __WIN32__
256       case SOCK_NOTINITIALISED:  // intentional fall-through.
257 #endif */
258       case SOCK_ENOTSOCK:
259         break;
260
261       // hopefully all these others are bogus errors...
262       case SOCK_EINPROGRESS:  // intentional fall-through.
263       case 0:  // intentional fall-through.
264       default:
265 #ifdef DEBUG_RAW_SOCKET
266         LOG("got to weird case, in progress or zero.");
267 #endif
268         return 0;  // not really an error.
269     }
270 #ifdef DEBUG_RAW_SOCKET
271     LOG(a_sprintf("socket %u had error %d in select: %s.",
272         socket, error, _stack->tcpip_error_name(error).s()));
273 #endif
274     return SI_ERRONEOUS;
275   }
276
277   // if we got to here, then there are some things to report...
278   return SI_BASELINE;
279 }
280
281 int raw_socket::test_readability(basis::un_int socket) const
282 {
283   FUNCDEF("test_readability");
284   basis::un_int len;
285   if (negative(ioctl(socket, IOCTL_READ, &len))) {
286     LOG(astring(astring::SPRINTF, "socket %u had ioctl error: %s.",
287         socket, _stack->tcpip_error_name(critical_events::system_error()).s()));
288     return SI_ERRONEOUS;
289   } else {
290     if (positive(len)) return SI_READABLE;
291     else return SI_DISCONNECTED;
292   }
293 }
294
295 int raw_socket::analyze_select_result(basis::un_int socket, int mode,
296     fd_set_wrapper &read_list, fd_set_wrapper &write_list,
297     fd_set_wrapper &exceptions) const
298 {
299 #ifdef DEBUG_RAW_SOCKET
300   FUNCDEF("analyze_select_result");
301 #endif
302   int to_return = 0;
303
304   // in case of an exception, we return an error.
305   if (FD_ISSET(socket, &exceptions)) {
306 #ifdef DEBUG_RAW_SOCKET
307     LOG(astring(astring::SPRINTF, "exception seen for socket %u!", socket));
308 #endif
309   }
310
311   // check to see if there are bytes to read.
312   if ( ! (mode & SELECTING_JUST_WRITE) && FD_ISSET(socket, &read_list)) {
313     // make sure we have data.  if no data is available, it means a
314     // disconnect occurred.
315
316     int readable = test_readability(socket);
317     if (readable == SI_ERRONEOUS)
318       to_return |= SI_ERRONEOUS;
319     else if (readable == SI_READABLE)
320       to_return |= SI_READABLE;
321     else if (readable == SI_DISCONNECTED) {
322       // we need to check multiple times to be sure the OS really means this.
323       // either windoze seems to report an erroneous disconnect every few
324       // days or there's a bad synchronization issue as yet uncovered.
325       bool really_disconnected = true;
326       for (int i = 0; i < MULTIPLE_DISCONNECT_CHECKS; i++) {
327         fd_set_wrapper read_list, write_list, exceps;
328         int temp_ret = inner_select(socket, SELECTING_JUST_READ, 0, read_list,
329             write_list, exceps);
330         // check the return value first...
331         if (!temp_ret) {
332           // nothing happening (a zero return) means the socket's no longer
333           // claiming to have a readable state; our disconnect condition is
334           // thus violated and we can leave.
335           really_disconnected = false;
336           break;
337         }
338         if (temp_ret == SI_ERRONEOUS) {
339           // this, on the other hand, sounds really bad.  the socket doesn't
340           // seem to exist any more or something else horrid happened.
341           really_disconnected = true;
342           break;
343         }
344         // if the select worked, we can check the fd_set now for readability.
345         if (!FD_ISSET(socket, &read_list)) {
346           // we are not in a disconnected state without being told we're
347           // readable.  supposedly.
348           really_disconnected = false;
349           break;
350         }
351         // now we really test the socket for readability by making sure there
352         // really is data pending on the socket.  if it's readable but there's
353         // no data, then either a disconnection has occurred or is in progress.
354         readable = test_readability(socket);
355         if (readable != SI_DISCONNECTED) {
356           // we are not disconnected if there's really data waiting.
357           really_disconnected = false;
358           break;
359         }
360       }
361       if (really_disconnected) {
362 #ifdef DEBUG_RAW_SOCKET
363         LOG(a_sprintf("connection closed on socket %u.", socket));
364 #endif
365         to_return |= SI_DISCONNECTED;
366       }
367     }
368   }
369
370   // check writability state.
371   if (! (mode & SELECTING_JUST_READ) && FD_ISSET(socket, &write_list)) {
372     to_return |= SI_WRITABLE;
373   }
374
375   return to_return;
376 }
377
378 int raw_socket::select(int_array &read_sox, int_array &write_sox,
379     int timeout) const
380 {
381 #ifdef DEBUG_RAW_SOCKET
382   FUNCDEF("select [multiple]");
383 #endif
384   if (!read_sox.length() && !write_sox.length())
385    return 0;  // nothing happened to nothing.
386
387   int to_return = 0;  // will get bits slammed into it to report results.
388
389   // setup the file descriptor sets for the select.  we check readability,
390   // writability and exception status.
391   fd_set_wrapper read_list; FD_ZERO(&read_list);
392   fd_set_wrapper write_list; FD_ZERO(&write_list);
393   fd_set_wrapper exceptions; FD_ZERO(&exceptions);
394   // set up the lists with the sets we were handed.
395   basis::un_int highest = 0;
396   int i = 0;
397   for (i = 0; i < read_sox.length(); i++) {
398     basis::un_int sock = (basis::un_int)read_sox[i];
399     if (sock > highest) highest = sock;
400     FD_SET(sock, &read_list);
401   }
402   for (i = 0; i < write_sox.length(); i++) {
403     basis::un_int sock = (basis::un_int)write_sox[i];
404     if (sock > highest) highest = sock;
405     FD_SET(sock, &write_list);
406   }
407
408   timeval base_time_out;
409   time_stamp::fill_timeval_ms(base_time_out, timeout);
410     // timeval has tv_sec=seconds, tv_usec=microseconds.
411 //#if !defined(__GNU_WINDOWS__)
412   timeval *time_out = &base_time_out;
413 /*#elif defined(__GNU_WINDOWS__)
414   __ms_timeval win_time_out;
415   win_time_out.tv_sec = base_time_out.tv_sec;
416   win_time_out.tv_usec = base_time_out.tv_usec;
417   __ms_timeval *time_out = &win_time_out;
418 #endif
419 */
420
421   // select will tell us about the socket.
422   int ret = ::select(highest + 1,
423       (read_sox.length())? &read_list : NULL_POINTER,
424       (write_sox.length())? &write_list : NULL_POINTER,
425       &exceptions, time_out);
426   int error = critical_events::system_error();
427
428   if (ret == SOCKET_ERROR) {
429     switch (error) {
430       // all real errors fall out to the error handling stuff.
431       case SOCK_EFAULT:  // intentional fall-through.
432       case SOCK_ENETDOWN:  // intentional fall-through.
433       case SOCK_EINVAL:  // intentional fall-through.
434       case SOCK_EINTR:  // intentional fall-through.
435 /*#ifdef __WIN32__
436       case SOCK_NOTINITIALISED:  // intentional fall-through.
437 #endif*/
438       case SOCK_ENOTSOCK:
439         break;
440
441       // hopefully all these others are bogus errors...
442       case SOCK_EINPROGRESS:  // intentional fall-through.
443       case 0:  // intentional fall-through.
444       default:
445 #ifdef DEBUG_RAW_SOCKET
446         LOG("got to weird case, in progress or zero.");
447 #endif
448
449 //hmmm: fix retd sox?  what's this outcome mean for the list?
450
451         return 0;  // not really an error.
452     }
453 #ifdef DEBUG_RAW_SOCKET
454     LOG(a_sprintf("sockets had error %d in select: %s.",
455         error, _stack->tcpip_error_name(error).s()));
456 #endif
457
458 //hmmm: fix retd sox?  what's this outcome mean for the list?
459
460     return SI_ERRONEOUS;
461   } else if (!ret) {
462     // we know of nothing exciting for any of these.
463     read_sox.reset();
464     write_sox.reset();
465     return 0;  // nothing to report.
466   }
467
468   // if we got to here, then there are some things to report...
469   // iterate through the lists and check results.
470   for (int k = 0; k < read_sox.length(); k++) {
471     basis::un_int socket = read_sox[k];
472     int interim = analyze_select_result(socket, SELECTING_JUST_READ, read_list,
473         write_list, exceptions);
474     if (!interim) {
475       // nothing happened on that guy.
476       read_sox.zap(k, k);
477       k--;  // skip back to before the whack.
478       continue;
479     }
480     to_return |= interim;  // join the results with overall result.
481   }
482   for (int p = 0; p < write_sox.length(); p++) {
483     basis::un_int socket = write_sox[p];
484     int interim = analyze_select_result(socket, SELECTING_JUST_WRITE, read_list,
485         write_list, exceptions);
486     if (!interim) {
487       // nothing happened on that guy.
488       write_sox.zap(p, p);
489       p--;  // skip back to before the whack.
490       continue;
491     }
492     to_return |= interim;  // join the results with overall result.
493   }
494
495   return to_return;
496 }
497
498 } //namespace.
499