/* * Grouch.app Copyright (C) 2006 Andy Sveikauskas * ------------------------------------------------------------------------ * This program is free software under the GNU General Public License * -- * Implementation of sockets using Unix/Winsock. */ #import #ifdef USE_UNIX_SOCKETS #import #import #import #import #import #import #ifndef USE_GNUSTEP_EXTENSION #import #endif #include #include #ifdef _WIN32 #include #include /* This is required for IPV6/getaddrinfo(3) because Microsoft likes to create * unnecessary headers. */ #include /* getaddrinfo() is broken on some MinGW -- fall back to gethostbyname() */ #ifndef gai_strerror #undef PF_INET6 #undef AF_INET6 #endif static const char *strerror_win32( int ); #undef strerror #undef hstrerror #define strerror strerror_win32 #define hstrerror strerror_win32 #define errno WSAGetLastError() #define EAGAIN WSAEWOULDBLOCK #define NFDS(x) (((int)x)+1) #else #include #include #include #include #include #include #include #include /* * Make everything like Win32. */ #define INVALID_SOCKET -1 #define SOCKET_ERROR -1 #define closesocket close #define NFDS(x) ((x)+1) #endif union addr { struct sockaddr_in in; #ifdef PF_INET6 struct sockaddr_in6 in6; #endif }; static void find_host( host, port, pf, sockaddr, addr, len ) NSString *host; int port, *pf; union addr *sockaddr; void **addr; size_t *len; { #ifdef PF_INET6 struct addrinfo *addrs, hint; char service[8]; int r; memset( &hint, 0, sizeof(hint) ); hint.ai_family = PF_UNSPEC; hint.ai_socktype = SOCK_STREAM; hint.ai_protocol = IPPROTO_TCP; snprintf( service, sizeof(service)-1, "%i", port ); r = getaddrinfo([host cString], service, &hint, &addrs); if( r ) { NSString *s = [NSString stringWithCString:gai_strerror(r)]; [GrouchException raiseSocketExceptionForHost:host withReason:s]; } *pf = addrs->ai_family; memcpy( (*addr)=sockaddr, addrs->ai_addr, (*len)=addrs->ai_addrlen ); freeaddrinfo( addrs ); #else struct hostent *dns = gethostbyname([host cString]); if( !dns ) { NSString *s = [NSString stringWithCString:hstrerror(h_errno)]; [GrouchException raiseSocketExceptionForHost:host withReason:s]; } *pf = PF_INET; sockaddr->in.sin_family = AF_INET; sockaddr->in.sin_port = htons(port); memcpy( &sockaddr->in.sin_addr.s_addr, dns->h_addr, dns->h_length ); *addr = &sockaddr->in; *len = sizeof(sockaddr->in); #endif } static int socket_set_blocking( int fd, int yes ) { #ifdef _WIN32 u_long setting = !yes; return ioctlsocket( fd, FIONBIO, &setting ); #else return fcntl( fd, F_SETFL, yes ? 0 : O_NONBLOCK ); #endif } #if defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__) || \ defined(__OpenBSD__) || defined(__sun__) #define USE_POLL #endif #if defined(USE_POLL) #ifndef _WIN32 #include #endif static int check_events( SOCKET fd ) { int r = 0; struct pollfd pollfd; memset( &pollfd, 0, sizeof(pollfd) ); pollfd.fd = fd; pollfd.events = POLLIN|POLLOUT; poll( &pollfd, 1, 0 ); if( pollfd.revents&POLLIN ) r |= GrouchSocketEventIn; if( pollfd.revents&POLLOUT ) r |= GrouchSocketEventOut; if( pollfd.revents&POLLERR ) r |= GrouchSocketEventError; return r; } #else #ifndef _WIN32 #include #endif static int try_select( SOCKET fd, int event ) { int r = 0; fd_set fdset; struct timeval tv; FD_ZERO( &fdset ); FD_SET( fd, &fdset ); memset( &tv, 0, sizeof(tv) ); if( event == GrouchSocketEventIn ) r = select( NFDS(fd), &fdset, NULL, NULL, &tv ); else if( event == GrouchSocketEventOut ) r = select( NFDS(fd), NULL, &fdset, NULL, &tv ); else if( event == GrouchSocketEventError ) r = select( NFDS(fd), NULL, NULL, &fdset, &tv ); return (r > 0) ? event : 0; } static int check_events( SOCKET fd ) { return try_select(fd, GrouchSocketEventIn) | try_select(fd, GrouchSocketEventOut) | try_select(fd, GrouchSocketEventError); } #endif @implementation GrouchSocketUnix + socketForHost:(NSString*)host atPort:(int)port withRunLoop:(NSRunLoop*)loopy forSocket:(GrouchSocket*)sock { GrouchSocketUnix *r = [self new]; NS_DURING [r initForHost:host atPort:port withRunLoop:loopy forSocket:sock]; NS_HANDLER [r release]; [localException raise]; NS_ENDHANDLER return r; } - initForHost:(NSString*)str atPort:(int)port withRunLoop:(NSRunLoop*)loopy forSocket:(GrouchSocket*)s { SOCKET sock; int pf; union addr abuf; void *sockaddr; size_t socklen; socketObject = s; memset( &abuf, 0, sizeof(abuf) ); find_host( str, port, &pf, &abuf, &sockaddr, &socklen ); sock = socket( pf, SOCK_STREAM, IPPROTO_TCP ); if( sock == INVALID_SOCKET ) bad: { NSString *s = [NSString stringWithCString:strerror(errno)]; closesocket(sock); [GrouchException raiseSocketExceptionForHost:str withReason:s]; } if( connect(sock, sockaddr, socklen) == SOCKET_ERROR ) goto bad; if( socket_set_blocking(sock, 0) == SOCKET_ERROR ) goto bad; fd = sock; loop = loopy; #ifdef USE_GNUSTEP_EXTENSION { void *vfd = (void*)fd; watcher = [GrouchSocketWatcher watcherForSock:socketObject]; [loop addEvent:vfd type:ET_RDESC watcher:watcher forMode:NSDefaultRunLoopMode]; [loop addEvent:vfd type:ET_WDESC watcher:watcher forMode:NSDefaultRunLoopMode]; [loop addEvent:vfd type:ET_EDESC watcher:watcher forMode:NSDefaultRunLoopMode]; } #else timer = [NSTimer timerWithTimeInterval:0.3 target:socketObject selector:@selector(eventLoop) userInfo:nil repeats:YES]; [loop addTimer:timer forMode:NSDefaultRunLoopMode]; #endif if( loop && [loop isKindOfClass:[GrouchRunLoopHack class]] ) { [(GrouchRunLoopHack*)loop invalidate]; loop = nil; } return self; } - init { [super init]; fd = INVALID_SOCKET; loop = nil; return self; } - (void)dealloc { if( fd != INVALID_SOCKET ) closesocket(fd); if( loop && [loop isKindOfClass:[GrouchRunLoopHack class]] ) [(GrouchRunLoopHack*)loop invalidate]; #ifndef USE_GNUSTEP_EXTENSION if(timer) [timer invalidate]; #endif [super dealloc]; } - (SOCKET)fd { return fd; } - (void)_syscall_prep { error = NO; #ifdef _WIN32 WSASetLastError(0); #else errno = 0; #endif } - (int)_syscall_fin:(int)r { error = (r < 0 && errno != EAGAIN); return r; } - (int)write:(const void*)buf length:(int)len { [self _syscall_prep]; return [self _syscall_fin:send(fd, buf, len, 0)]; } - (int)read:(void *)buf length:(int)len { [self _syscall_prep]; return [self _syscall_fin:recv(fd, buf, len, 0)]; } - (BOOL)lastOperationWasError { return error; } - (void)startWriteThread { #ifdef USE_GNUSTEP_EXTENSION if( fd != INVALID_SOCKET ) [watcher startWriteThread]; #endif } - (void)setBlocking:(BOOL)b { socket_set_blocking(fd, b?1:0); } - (GrouchSocketEvent)pollSocketEvents { if( fd != INVALID_SOCKET ) return check_events(fd); else return 0; } @end #ifdef USE_GNUSTEP_EXTENSION @implementation GrouchSocketWatcher + watcherForSock:(GrouchSocket*)s { return [[self alloc] initForSock:s]; } - initForSock:(GrouchSocket*)s { [sock=s retain]; writeThreadLive = YES; return self; } - (void)dealloc { if( sock ) [sock release]; [super dealloc]; } - (NSDate*)timedOutEvent:(void*)data type:(RunLoopEventType)t forMode:(NSString*)mode { if( sock && [sock fd] ) { [sock eventLoop:0]; // For keep-alive return [NSDate dateWithTimeIntervalSinceNow:[sock keepAlive]]; } else { [[NSRunLoop currentRunLoop] removeEvent:data type:t forMode:mode all:YES]; [self release]; return nil; } } - (void)startWriteThread { if( !writeThreadLive && sock && [sock fd] ) { NSRunLoop *loop = [NSRunLoop currentRunLoop]; void *vfd = (void*)[(GrouchSocketUnix*)[sock fd] fd]; [loop addEvent:vfd type:ET_WDESC watcher:self forMode:NSDefaultRunLoopMode]; [self retain]; } } - (void)receivedEvent:(void*)data type:(RunLoopEventType)t extra:(void*)extra forMode:(NSString*)mode { if( !sock || ![sock fd] ) { [[NSRunLoop currentRunLoop] removeEvent:data type:t forMode:mode all:YES]; [self release]; } else switch( t ) { case ET_RDESC: [sock eventLoop:GrouchSocketEventIn]; break; case ET_WDESC: [sock eventLoop:GrouchSocketEventOut]; if([sock fd] && ![sock outBufferSize]) { writeThreadLive = NO; [[NSRunLoop currentRunLoop] removeEvent:data type:t forMode:mode all:NO]; } break; case ET_EDESC: [sock eventLoop:GrouchSocketEventError]; default:; } } @end #endif #ifdef _WIN32 static const char *strerror_win32( int i ) { static char *buf = NULL; if( buf ) LocalFree(buf); buf = NULL; FormatMessage ( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, i, 0, &buf, 0, NULL ); return buf ? buf : ""; } #endif #endif