2010-03-16 05:54:36 -07:00
/*
This file is part of Warzone 2100.
Copyright ( C ) 1999 - 2004 Eidos Interactive
Copyright ( C ) 2005 - 2010 Warzone Resurrection Project
Warzone 2100 is free software ; you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation ; either version 2 of the License , or
( at your option ) any later version .
Warzone 2100 is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with Warzone 2100 ; if not , write to the Free Software
Foundation , Inc . , 51 Franklin St , Fifth Floor , Boston , MA 02110 - 1301 USA
*/
/**
* @ file netsocket . cpp
*
* Basic raw socket handling code .
*/
# include "lib/framework/frame.h"
2010-05-15 18:36:21 -07:00
# include "lib/framework/wzapp_c.h"
2010-03-16 05:54:36 -07:00
# include "netsocket.h"
# include <vector>
# include <algorithm>
2010-03-16 13:52:57 -07:00
# include <map>
2010-03-16 05:54:36 -07:00
# if defined(WZ_OS_UNIX)
# include <arpa / inet.h>
# include <errno.h>
# include <fcntl.h>
# include <netdb.h>
# include <netinet / in.h>
# include <sys / ioctl.h>
# include <sys / socket.h>
# include <sys / types.h>
# include <sys / select.h>
# include <unistd.h>
typedef int SOCKET ;
static const SOCKET INVALID_SOCKET = - 1 ;
2010-03-20 06:03:47 -07:00
# endif
# ifdef WZ_OS_WIN
2010-03-16 05:54:36 -07:00
# include <winsock2.h>
# include <ws2tcpip.h>
# undef EAGAIN
# undef EBADF
# undef ECONNRESET
# undef EINPROGRESS
# undef EINTR
# undef EISCONN
# undef ETIMEDOUT
# undef EWOULDBLOCK
# define EAGAIN WSAEWOULDBLOCK
# define EBADF WSAEBADF
# define ECONNRESET WSAECONNRESET
# define EINPROGRESS WSAEINPROGRESS
# define EINTR WSAEINTR
# define EISCONN WSAEISCONN
# define ETIMEDOUT WSAETIMEDOUT
# define EWOULDBLOCK WSAEWOULDBLOCK
# ifndef AI_V4MAPPED
# define AI_V4MAPPED 0x0008 /* IPv4 mapped addresses are acceptable. */
# endif
# ifndef AI_ADDRCONFIG
# define AI_ADDRCONFIG 0x0020 /* Use configuration of this host to choose returned address type.. */
# endif
# endif
// Fallback for systems that don't #define this flag
# ifndef MSG_NOSIGNAL
# define MSG_NOSIGNAL 0
# endif
enum
{
SOCK_CONNECTION ,
SOCK_IPV4_LISTEN = SOCK_CONNECTION ,
SOCK_IPV6_LISTEN ,
SOCK_COUNT ,
} ;
struct Socket
{
/* Multiple socket handles only for listening sockets. This allows us
* to listen on multiple protocols and address families ( e . g . IPv4 and
* IPv6 ) .
*
* All non - listening sockets will only use the first socket handle .
*/
2010-03-16 13:52:57 -07:00
Socket ( ) : ready ( false ) , writeError ( false ) { }
2010-03-16 05:54:36 -07:00
SOCKET fd [ SOCK_COUNT ] ;
bool ready ;
2010-03-16 13:52:57 -07:00
bool writeError ;
2010-03-16 05:54:36 -07:00
char textAddress [ 40 ] ;
} ;
struct SocketSet
{
std : : vector < Socket * > fds ;
} ;
2010-03-16 13:52:57 -07:00
static WZ_MUTEX * socketThreadMutex ;
static WZ_SEMAPHORE * socketThreadSemaphore ;
static WZ_THREAD * socketThread = NULL ;
static bool socketThreadQuit ;
typedef std : : map < Socket * , std : : vector < uint8_t > > SocketThreadWriteMap ;
static SocketThreadWriteMap socketThreadWrites ;
2010-03-16 05:54:36 -07:00
bool socketReadReady ( Socket const * sock )
{
return sock - > ready ;
}
int getSockErr ( void )
{
# if defined(WZ_OS_UNIX)
return errno ;
# elif defined(WZ_OS_WIN)
return WSAGetLastError ( ) ;
# endif
}
void setSockErr ( int error )
{
# if defined(WZ_OS_UNIX)
errno = error ;
# elif defined(WZ_OS_WIN)
WSASetLastError ( error ) ;
# endif
}
# if defined(WZ_OS_WIN)
2010-03-20 06:03:47 -07:00
typedef int ( WINAPI * GETADDRINFO_DLL_FUNC ) ( const char * node , const char * service ,
const struct addrinfo * hints ,
struct addrinfo * * res ) ;
typedef int ( WINAPI * FREEADDRINFO_DLL_FUNC ) ( struct addrinfo * res ) ;
2010-03-16 05:54:36 -07:00
static HMODULE winsock2_dll = NULL ;
static unsigned int major_windows_version = 0 ;
2010-03-20 06:03:47 -07:00
static GETADDRINFO_DLL_FUNC getaddrinfo_dll_func = NULL ;
static FREEADDRINFO_DLL_FUNC freeaddrinfo_dll_func = NULL ;
2010-03-16 05:54:36 -07:00
# define getaddrinfo getaddrinfo_dll_dispatcher
# define freeaddrinfo freeaddrinfo_dll_dispatcher
static int getaddrinfo ( const char * node , const char * service ,
const struct addrinfo * hints ,
struct addrinfo * * res )
{
struct addrinfo hint ;
if ( hints )
{
memcpy ( & hint , hints , sizeof ( hint ) ) ;
}
switch ( major_windows_version )
{
case 0 :
case 1 :
case 2 :
case 3 :
// Windows 95, 98 and ME
case 4 :
debug ( LOG_ERROR , " Name resolution isn't supported on this version (%u) of Windows " , major_windows_version ) ;
return EAI_FAIL ;
// Windows 2000, XP and Server 2003
case 5 :
if ( hints )
{
// These flags are only supported from version 6 and onward
hint . ai_flags & = ~ ( AI_V4MAPPED | AI_ADDRCONFIG ) ;
}
// Windows Vista and Server 2008
case 6 :
// Onward (aka: in the future)
default :
if ( ! winsock2_dll )
{
debug ( LOG_ERROR , " Failed to load winsock2 DLL. Required for name resolution. " ) ;
return EAI_FAIL ;
}
if ( ! getaddrinfo_dll_func )
{
debug ( LOG_ERROR , " Failed to retrieve \" getaddrinfo \" function from winsock2 DLL. Required for name resolution. " ) ;
return EAI_FAIL ;
}
return getaddrinfo_dll_func ( node , service , hints ? & hint : NULL , res ) ;
}
}
static void freeaddrinfo ( struct addrinfo * res )
{
switch ( major_windows_version )
{
case 0 :
case 1 :
case 2 :
case 3 :
// Windows 95, 98 and ME
case 4 :
debug ( LOG_ERROR , " Name resolution isn't supported on this version (%u) of Windows " , major_windows_version ) ;
return ;
// Windows 2000, XP and Server 2003
case 5 :
// Windows Vista and Server 2008
case 6 :
// Onward (aka: in the future)
default :
if ( ! winsock2_dll )
{
debug ( LOG_ERROR , " Failed to load winsock2 DLL. Required for name resolution. " ) ;
return ;
}
if ( ! freeaddrinfo_dll_func )
{
debug ( LOG_ERROR , " Failed to retrieve \" freeaddrinfo \" function from winsock2 DLL. Required for name resolution. " ) ;
return ;
}
freeaddrinfo_dll_func ( res ) ;
}
}
# endif
static int addressToText ( const struct sockaddr * addr , char * buf , size_t size )
{
switch ( addr - > sa_family )
{
case AF_INET :
{
unsigned char * address = ( unsigned char * ) & ( ( const struct sockaddr_in * ) addr ) - > sin_addr . s_addr ;
return snprintf ( buf , size ,
" %hhu.%hhu.%hhu.%hhu " ,
address [ 0 ] ,
address [ 1 ] ,
address [ 2 ] ,
address [ 3 ] ) ;
}
case AF_INET6 :
{
uint16_t * address = ( uint16_t * ) & ( ( const struct sockaddr_in6 * ) addr ) - > sin6_addr . s6_addr ;
return snprintf ( buf , size ,
" %hx:%hx:%hx:%hx:%hx:%hx:%hx:%hx " ,
ntohs ( address [ 0 ] ) ,
ntohs ( address [ 1 ] ) ,
ntohs ( address [ 2 ] ) ,
ntohs ( address [ 3 ] ) ,
ntohs ( address [ 4 ] ) ,
ntohs ( address [ 5 ] ) ,
ntohs ( address [ 6 ] ) ,
ntohs ( address [ 7 ] ) ) ;
}
default :
ASSERT ( ! " Unknown address family " , " Got non IPv4 or IPv6 address! " ) ;
return - 1 ;
}
}
const char * strSockError ( int error )
{
# if defined(WZ_OS_WIN)
switch ( error )
{
case 0 : return " No error " ;
case WSAEINTR : return " Interrupted system call " ;
case WSAEBADF : return " Bad file number " ;
case WSAEACCES : return " Permission denied " ;
case WSAEFAULT : return " Bad address " ;
case WSAEINVAL : return " Invalid argument " ;
case WSAEMFILE : return " Too many open sockets " ;
case WSAEWOULDBLOCK : return " Operation would block " ;
case WSAEINPROGRESS : return " Operation now in progress " ;
case WSAEALREADY : return " Operation already in progress " ;
case WSAENOTSOCK : return " Socket operation on non-socket " ;
case WSAEDESTADDRREQ : return " Destination address required " ;
case WSAEMSGSIZE : return " Message too long " ;
case WSAEPROTOTYPE : return " Protocol wrong type for socket " ;
case WSAENOPROTOOPT : return " Bad protocol option " ;
case WSAEPROTONOSUPPORT : return " Protocol not supported " ;
case WSAESOCKTNOSUPPORT : return " Socket type not supported " ;
case WSAEOPNOTSUPP : return " Operation not supported on socket " ;
case WSAEPFNOSUPPORT : return " Protocol family not supported " ;
case WSAEAFNOSUPPORT : return " Address family not supported " ;
case WSAEADDRINUSE : return " Address already in use " ;
case WSAEADDRNOTAVAIL : return " Can't assign requested address " ;
case WSAENETDOWN : return " Network is down " ;
case WSAENETUNREACH : return " Network is unreachable " ;
case WSAENETRESET : return " Net connection reset " ;
case WSAECONNABORTED : return " Software caused connection abort " ;
case WSAECONNRESET : return " Connection reset by peer " ;
case WSAENOBUFS : return " No buffer space available " ;
case WSAEISCONN : return " Socket is already connected " ;
case WSAENOTCONN : return " Socket is not connected " ;
case WSAESHUTDOWN : return " Can't send after socket shutdown " ;
case WSAETOOMANYREFS : return " Too many references, can't splice " ;
case WSAETIMEDOUT : return " Connection timed out " ;
case WSAECONNREFUSED : return " Connection refused " ;
case WSAELOOP : return " Too many levels of symbolic links " ;
case WSAENAMETOOLONG : return " File name too long " ;
case WSAEHOSTDOWN : return " Host is down " ;
case WSAEHOSTUNREACH : return " No route to host " ;
case WSAENOTEMPTY : return " Directory not empty " ;
case WSAEPROCLIM : return " Too many processes " ;
case WSAEUSERS : return " Too many users " ;
case WSAEDQUOT : return " Disc quota exceeded " ;
case WSAESTALE : return " Stale NFS file handle " ;
case WSAEREMOTE : return " Too many levels of remote in path " ;
case WSASYSNOTREADY : return " Network system is unavailable " ;
case WSAVERNOTSUPPORTED : return " Winsock version out of range " ;
case WSANOTINITIALISED : return " WSAStartup not yet called " ;
case WSAEDISCON : return " Graceful shutdown in progress " ;
case WSAHOST_NOT_FOUND : return " Host not found " ;
case WSANO_DATA : return " No host data of that type was found " ;
default : return " Unknown error " ;
}
# elif defined(WZ_OS_UNIX)
return strerror ( error ) ;
# endif
}
/**
* Test whether the given socket still has an open connection .
*
* @ return true when the connection is open , false when it ' s closed or in an
* error state , check getSockErr ( ) to find out which .
*/
static bool connectionIsOpen ( Socket * sock )
{
const SocketSet set = { std : : vector < Socket * > ( 1 , sock ) } ;
ASSERT_OR_RETURN ( ( setSockErr ( EBADF ) , false ) ,
sock & & sock - > fd [ SOCK_CONNECTION ] ! = INVALID_SOCKET , " Invalid socket " ) ;
// Check whether the socket is still connected
int ret = checkSockets ( & set , 0 ) ;
if ( ret = = SOCKET_ERROR )
{
return false ;
}
else if ( ret = = ( int ) set . fds . size ( ) & & sock - > ready )
{
/* The next recv(2) call won't block, but we're writing. So
* check the read queue to see if the connection is closed .
* If there ' s no data in the queue that means the connection
* is closed .
*/
# if defined(WZ_OS_WIN)
unsigned long readQueue ;
ret = ioctlsocket ( sock - > fd [ SOCK_CONNECTION ] , FIONREAD , & readQueue ) ;
# else
int readQueue ;
ret = ioctl ( sock - > fd [ SOCK_CONNECTION ] , FIONREAD , & readQueue ) ;
# endif
if ( ret = = SOCKET_ERROR )
{
debug ( LOG_NET , " socket error " ) ;
return false ;
}
else if ( readQueue = = 0 )
{
// Disconnected
setSockErr ( ECONNRESET ) ;
debug ( LOG_NET , " Read queue empty - failing (ECONNRESET) " ) ;
return false ;
}
}
return true ;
}
2010-03-16 13:52:57 -07:00
static int socketThreadFunction ( void * )
{
wzMutexLock ( socketThreadMutex ) ;
while ( ! socketThreadQuit )
{
# if defined(WZ_OS_UNIX)
SOCKET maxfd = INT_MIN ;
# elif defined(WZ_OS_WIN)
SOCKET maxfd = 0 ;
# endif
fd_set fds ;
FD_ZERO ( & fds ) ;
for ( SocketThreadWriteMap : : iterator i = socketThreadWrites . begin ( ) ; i ! = socketThreadWrites . end ( ) ; + + i )
{
if ( ! i - > second . empty ( ) )
{
SOCKET fd = i - > first - > fd [ SOCK_CONNECTION ] ;
maxfd = std : : max ( maxfd , fd ) ;
FD_SET ( fd , & fds ) ;
}
}
struct timeval tv = { 0 , 50 * 1000 } ;
// Check if we can write to any sockets.
wzMutexUnlock ( socketThreadMutex ) ;
int ret = select ( maxfd + 1 , NULL , & fds , NULL , & tv ) ;
wzMutexLock ( socketThreadMutex ) ;
// We can write to some sockets. (Ignore errors from select, we may have deleted the socket after unlocking the mutex, and before calling select.)
if ( ret > 0 )
{
for ( SocketThreadWriteMap : : iterator i = socketThreadWrites . begin ( ) ; i ! = socketThreadWrites . end ( ) ; )
{
SocketThreadWriteMap : : iterator w = i ;
+ + i ;
Socket * sock = w - > first ;
std : : vector < uint8_t > & writeQueue = socketThreadWrites [ sock ] ;
if ( writeQueue . empty ( ) | | ! FD_ISSET ( sock - > fd [ SOCK_CONNECTION ] , & fds ) )
{
continue ; // This socket is not ready for writing, or we don't have anything to write.
}
// Write data.
ssize_t ret = send ( sock - > fd [ SOCK_CONNECTION ] , reinterpret_cast < char * > ( & writeQueue [ 0 ] ) , writeQueue . size ( ) , MSG_NOSIGNAL ) ;
if ( ret ! = SOCKET_ERROR )
{
// Erase as much data as written.
writeQueue . erase ( writeQueue . begin ( ) , writeQueue . begin ( ) + ret ) ;
if ( writeQueue . empty ( ) )
{
socketThreadWrites . erase ( w ) ; // Nothing left to write, delete from pending list.
}
}
else
{
switch ( getSockErr ( ) )
{
case EAGAIN :
# if defined(EWOULDBLOCK) && EAGAIN != EWOULDBLOCK
case EWOULDBLOCK :
# endif
if ( ! connectionIsOpen ( sock ) )
{
debug ( LOG_NET , " Socket error " ) ;
sock - > writeError = true ;
socketThreadWrites . erase ( w ) ; // Socket broken, don't try writing to it again.
break ;
}
case EINTR :
break ;
# if defined(EPIPE)
case EPIPE :
debug ( LOG_NET , " EPIPE generated " ) ;
// fall through
# endif
default :
sock - > writeError = true ;
socketThreadWrites . erase ( w ) ; // Socket broken, don't try writing to it again.
break ;
}
}
}
}
if ( socketThreadWrites . empty ( ) )
{
// Nothing to do, expect to wait.
wzMutexUnlock ( socketThreadMutex ) ;
wzSemaphoreWait ( socketThreadSemaphore ) ;
wzMutexLock ( socketThreadMutex ) ;
}
}
wzMutexUnlock ( socketThreadMutex ) ;
2010-03-16 14:55:00 -07:00
return 42 ; // Return value arbitrary and unused.
2010-03-16 13:52:57 -07:00
}
2010-03-16 05:54:36 -07:00
/**
* Similar to read ( 2 ) with the exception that this function won ' t be
* interrupted by signals ( EINTR ) .
*/
ssize_t readNoInt ( Socket * sock , void * buf , size_t max_size )
{
ssize_t received ;
if ( sock - > fd [ SOCK_CONNECTION ] = = INVALID_SOCKET )
{
debug ( LOG_ERROR , " Invalid socket " ) ;
setSockErr ( EBADF ) ;
return SOCKET_ERROR ;
}
do
{
2010-03-20 06:03:47 -07:00
received = recv ( sock - > fd [ SOCK_CONNECTION ] , ( char * ) buf , max_size , 0 ) ;
2010-03-16 05:54:36 -07:00
} while ( received = = SOCKET_ERROR & & getSockErr ( ) = = EINTR ) ;
sock - > ready = false ;
return received ;
}
/**
* Similar to write ( 2 ) with the exception that this function will block until
* < em > all < / em > data has been written or an error occurs .
*
* @ return @ c size when succesful or @ c SOCKET_ERROR if an error occurred .
*/
ssize_t writeAll ( Socket * sock , const void * buf , size_t size )
{
if ( ! sock
| | sock - > fd [ SOCK_CONNECTION ] = = INVALID_SOCKET )
{
debug ( LOG_ERROR , " Invalid socket (EBADF) " ) ;
setSockErr ( EBADF ) ;
return SOCKET_ERROR ;
}
2010-03-16 13:52:57 -07:00
if ( sock - > writeError )
2010-03-16 05:54:36 -07:00
{
2010-03-16 13:52:57 -07:00
return SOCKET_ERROR ;
}
2010-03-16 05:54:36 -07:00
2010-03-16 13:52:57 -07:00
if ( size > 0 )
{
wzMutexLock ( socketThreadMutex ) ;
if ( socketThreadWrites . empty ( ) )
2010-03-16 05:54:36 -07:00
{
2010-03-16 13:52:57 -07:00
wzSemaphorePost ( socketThreadSemaphore ) ;
2010-03-16 05:54:36 -07:00
}
2010-03-16 13:52:57 -07:00
std : : vector < uint8_t > & writeQueue = socketThreadWrites [ sock ] ;
writeQueue . insert ( writeQueue . end ( ) , static_cast < char const * > ( buf ) , static_cast < char const * > ( buf ) + size ) ;
wzMutexUnlock ( socketThreadMutex ) ;
2010-03-16 05:54:36 -07:00
}
2010-03-16 13:52:57 -07:00
return size ;
2010-03-16 05:54:36 -07:00
}
SocketSet * allocSocketSet ( )
{
return new SocketSet ;
}
void deleteSocketSet ( SocketSet * set )
{
delete set ;
}
/**
* Add the given socket to the given socket set .
*
* @ return true if @ c socket is succesfully added to @ set .
*/
void SocketSet_AddSocket ( SocketSet * set , Socket * socket )
{
ASSERT_OR_RETURN ( , set ! = NULL , " NULL SocketSet provided " ) ;
ASSERT_OR_RETURN ( , socket ! = NULL , " NULL Socket provided " ) ;
/* Check whether this socket is already present in this set (i.e. it
* shouldn ' t be added again ) .
*/
size_t i = std : : find ( set - > fds . begin ( ) , set - > fds . end ( ) , socket ) - set - > fds . begin ( ) ;
if ( i ! = set - > fds . size ( ) )
{
debug ( LOG_NET , " Already found, socket: (set->fds[%lu]) %p " , ( unsigned long ) i , socket ) ;
return ;
}
set - > fds . push_back ( socket ) ;
debug ( LOG_NET , " Socket added: set->fds[%lu] = %p " , ( unsigned long ) i , socket ) ;
}
/**
* Remove the given socket from the given socket set .
*/
void SocketSet_DelSocket ( SocketSet * set , Socket * socket )
{
ASSERT_OR_RETURN ( , set ! = NULL , " NULL SocketSet provided " ) ;
ASSERT_OR_RETURN ( , socket ! = NULL , " NULL Socket provided " ) ;
size_t i = std : : find ( set - > fds . begin ( ) , set - > fds . end ( ) , socket ) - set - > fds . begin ( ) ;
if ( i ! = set - > fds . size ( ) )
{
debug ( LOG_NET , " Socket %p erased (set->fds[%lu]) " , socket , ( unsigned long ) i ) ;
set - > fds . erase ( set - > fds . begin ( ) + i ) ;
}
}
static bool setSocketBlocking ( const SOCKET fd , bool blocking )
{
# if defined(WZ_OS_UNIX)
int sockopts = fcntl ( fd , F_GETFL ) ;
if ( sockopts = = SOCKET_ERROR )
{
debug ( LOG_NET , " Failed to retrieve current socket options: %s " , strSockError ( getSockErr ( ) ) ) ;
return false ;
}
// Set or clear O_NONBLOCK flag
if ( blocking )
sockopts & = ~ O_NONBLOCK ;
else
sockopts | = O_NONBLOCK ;
if ( fcntl ( fd , F_SETFL , sockopts ) = = SOCKET_ERROR )
# elif defined(WZ_OS_WIN)
unsigned long nonblocking = ! blocking ;
if ( ioctlsocket ( fd , FIONBIO , & nonblocking ) = = SOCKET_ERROR )
# endif
{
debug ( LOG_NET , " Failed to set socket %sblocking: %s " , ( blocking ? " " : " non- " ) , strSockError ( getSockErr ( ) ) ) ;
return false ;
}
debug ( LOG_NET , " Socket is set to %sblocking. " , ( blocking ? " " : " non- " ) ) ;
return true ;
}
static void socketBlockSIGPIPE ( const SOCKET fd , bool block_sigpipe )
{
# if defined(SO_NOSIGPIPE)
const int no_sigpipe = block_sigpipe ? 1 : 0 ;
if ( setsockopt ( fd , SOL_SOCKET , SO_NOSIGPIPE , & no_sigpipe , sizeof ( no_sigpipe ) ) = = SOCKET_ERROR )
{
debug ( LOG_INFO , " Failed to set SO_NOSIGPIPE on socket, SIGPIPE might be raised when connections gets broken. Error: %s " , strSockError ( getSockErr ( ) ) ) ;
}
// this is only for unix, windows don't have SIGPIPE
debug ( LOG_NET , " Socket fd %x sets SIGPIPE to %sblocked. " , fd , ( block_sigpipe ? " " : " non- " ) ) ;
# else
// Prevent warnings
( void ) fd ;
( void ) block_sigpipe ;
# endif
}
int checkSockets ( const SocketSet * set , unsigned int timeout )
{
if ( set - > fds . empty ( ) )
{
return 0 ;
}
int ret ;
fd_set fds ;
# if defined(WZ_OS_UNIX)
SOCKET maxfd = INT_MIN ;
# elif defined(WZ_OS_WIN)
SOCKET maxfd = 0 ;
# endif
for ( size_t i = 0 ; i < set - > fds . size ( ) ; + + i )
{
ASSERT ( set - > fds [ i ] - > fd [ SOCK_CONNECTION ] ! = INVALID_SOCKET , " Invalid file descriptor! " ) ;
maxfd = std : : max ( maxfd , set - > fds [ i ] - > fd [ SOCK_CONNECTION ] ) ;
}
do
{
struct timeval tv = { timeout / 1000 , ( timeout % 1000 ) * 1000 } ;
FD_ZERO ( & fds ) ;
for ( size_t i = 0 ; i < set - > fds . size ( ) ; + + i )
{
const SOCKET fd = set - > fds [ i ] - > fd [ SOCK_CONNECTION ] ;
FD_SET ( fd , & fds ) ;
}
ret = select ( maxfd + 1 , & fds , NULL , NULL , & tv ) ;
} while ( ret = = SOCKET_ERROR & & getSockErr ( ) = = EINTR ) ;
if ( ret = = SOCKET_ERROR )
{
debug ( LOG_ERROR , " select failed: %s " , strSockError ( getSockErr ( ) ) ) ;
return SOCKET_ERROR ;
}
for ( size_t i = 0 ; i < set - > fds . size ( ) ; + + i )
{
set - > fds [ i ] - > ready = FD_ISSET ( set - > fds [ i ] - > fd [ SOCK_CONNECTION ] , & fds ) ;
}
return ret ;
}
/**
* Similar to read ( 2 ) with the exception that this function won ' t be
* interrupted by signals ( EINTR ) and will only return when < em > exactly < / em >
* @ c size bytes have been received . I . e . this function blocks until all data
* has been received or a timeout occurred .
*
* @ param timeout When non - zero this function times out after @ c timeout
* milliseconds . When zero this function blocks until success or
* an error occurs .
*
* @ c return @ c size when succesful , less than @ c size but at least zero ( 0 )
* when the other end disconnected or a timeout occurred . Or @ c SOCKET_ERROR if
* an error occurred .
*/
ssize_t readAll ( Socket * sock , void * buf , size_t size , unsigned int timeout )
{
const SocketSet set = { std : : vector < Socket * > ( 1 , sock ) } ;
size_t received = 0 ;
if ( ! sock
| | sock - > fd [ SOCK_CONNECTION ] = = INVALID_SOCKET )
{
debug ( LOG_ERROR , " Invalid socket (%p), sock->fd[SOCK_CONNECTION]=%x (error: EBADF) " , sock , sock - > fd [ SOCK_CONNECTION ] ) ;
setSockErr ( EBADF ) ;
return SOCKET_ERROR ;
}
while ( received < size )
{
ssize_t ret ;
// If a timeout is set, wait for that amount of time for data to arrive (or abort)
if ( timeout )
{
ret = checkSockets ( & set , timeout ) ;
if ( ret < ( ssize_t ) set . fds . size ( )
| | ! sock - > ready )
{
if ( ret = = 0 )
{
debug ( LOG_NET , " socket (%p) has timed out. " , socket ) ;
setSockErr ( ETIMEDOUT ) ;
}
debug ( LOG_NET , " socket (%p) error. " , socket ) ;
return SOCKET_ERROR ;
}
}
ret = recv ( sock - > fd [ SOCK_CONNECTION ] , & ( ( char * ) buf ) [ received ] , size - received , 0 ) ;
sock - > ready = false ;
if ( ret = = 0 )
{
debug ( LOG_NET , " Socket %x disconnected. " , sock - > fd [ SOCK_CONNECTION ] ) ;
setSockErr ( ECONNRESET ) ;
return received ;
}
if ( ret = = SOCKET_ERROR )
{
switch ( getSockErr ( ) )
{
case EAGAIN :
# if defined(EWOULDBLOCK) && EAGAIN != EWOULDBLOCK
case EWOULDBLOCK :
# endif
case EINTR :
continue ;
default :
return SOCKET_ERROR ;
}
}
received + = ret ;
}
return received ;
}
void socketClose ( Socket * sock )
{
unsigned int i ;
int err = 0 ;
if ( sock )
{
2010-03-16 13:52:57 -07:00
wzMutexLock ( socketThreadMutex ) ;
socketThreadWrites . erase ( sock ) ;
wzMutexUnlock ( socketThreadMutex ) ;
2010-03-16 05:54:36 -07:00
for ( i = 0 ; i < ARRAY_SIZE ( sock - > fd ) ; + + i )
{
if ( sock - > fd [ i ] ! = INVALID_SOCKET )
{
# if defined(WZ_OS_WIN)
err = closesocket ( sock - > fd [ i ] ) ;
# else
err = close ( sock - > fd [ i ] ) ;
# endif
if ( err )
{
debug ( LOG_ERROR , " Failed to close socket %p: %s " , sock , strSockError ( getSockErr ( ) ) ) ;
}
/* Make sure that dangling pointers to this
* structure don ' t think they ' ve got their
* hands on a valid socket .
*/
sock - > fd [ i ] = INVALID_SOCKET ;
}
}
2010-03-16 13:52:57 -07:00
delete sock ;
2010-03-16 05:54:36 -07:00
sock = NULL ;
}
}
Socket * socketAccept ( Socket * sock )
{
unsigned int i ;
ASSERT ( sock ! = NULL , " NULL Socket provided " ) ;
/* Search for a socket that has a pending connection on it and accept
* the first one .
*/
for ( i = 0 ; i < ARRAY_SIZE ( sock - > fd ) ; + + i )
{
if ( sock - > fd [ i ] ! = INVALID_SOCKET )
{
struct sockaddr_storage addr ;
socklen_t addr_len = sizeof ( addr ) ;
Socket * conn ;
unsigned int j ;
const SOCKET newConn = accept ( sock - > fd [ i ] , ( struct sockaddr * ) & addr , & addr_len ) ;
if ( newConn = = INVALID_SOCKET )
{
// Ignore the case where no connection is pending
if ( getSockErr ( ) ! = EAGAIN
& & getSockErr ( ) ! = EWOULDBLOCK )
{
debug ( LOG_ERROR , " accept failed for socket %p: %s " , sock , strSockError ( getSockErr ( ) ) ) ;
}
continue ;
}
socketBlockSIGPIPE ( newConn , true ) ;
2010-03-16 13:52:57 -07:00
conn = new Socket ;
2010-03-16 05:54:36 -07:00
if ( conn = = NULL )
{
debug ( LOG_ERROR , " Out of memory! " ) ;
abort ( ) ;
return NULL ;
}
// Mark all unused socket handles as invalid
for ( j = 0 ; j < ARRAY_SIZE ( conn - > fd ) ; + + j )
{
conn - > fd [ j ] = INVALID_SOCKET ;
}
conn - > fd [ SOCK_CONNECTION ] = newConn ;
sock - > ready = false ;
addressToText ( ( const struct sockaddr * ) & addr , conn - > textAddress , sizeof ( conn - > textAddress ) ) ;
debug ( LOG_NET , " Incoming connection from [%s]:%d " , conn - > textAddress , ( unsigned int ) ntohs ( ( ( const struct sockaddr_in * ) & addr ) - > sin_port ) ) ;
debug ( LOG_NET , " Using socket %p " , conn ) ;
return conn ;
}
}
return NULL ;
}
Socket * socketOpen ( const SocketAddress * addr , unsigned timeout )
{
unsigned int i ;
int ret ;
2010-03-16 13:52:57 -07:00
Socket * const conn = new Socket ;
2010-03-16 05:54:36 -07:00
if ( conn = = NULL )
{
debug ( LOG_ERROR , " Out of memory! " ) ;
abort ( ) ;
return NULL ;
}
ASSERT ( addr ! = NULL , " NULL Socket provided " ) ;
addressToText ( addr - > ai_addr , conn - > textAddress , sizeof ( conn - > textAddress ) ) ;
debug ( LOG_NET , " Connecting to [%s]:%d " , conn - > textAddress , ( int ) ntohs ( ( ( const struct sockaddr_in * ) addr - > ai_addr ) - > sin_port ) ) ;
// Mark all unused socket handles as invalid
for ( i = 0 ; i < ARRAY_SIZE ( conn - > fd ) ; + + i )
{
conn - > fd [ i ] = INVALID_SOCKET ;
}
conn - > fd [ SOCK_CONNECTION ] = socket ( addr - > ai_family , addr - > ai_socktype , addr - > ai_protocol ) ;
if ( conn - > fd [ SOCK_CONNECTION ] = = INVALID_SOCKET )
{
debug ( LOG_ERROR , " Failed to create a socket (%p): %s " , conn , strSockError ( getSockErr ( ) ) ) ;
socketClose ( conn ) ;
return NULL ;
}
debug ( LOG_NET , " setting socket (%p) blocking status (false). " , conn ) ;
if ( ! setSocketBlocking ( conn - > fd [ SOCK_CONNECTION ] , false ) )
{
debug ( LOG_NET , " Couldn't set socket (%p) blocking status (false). Closing. " , conn ) ;
socketClose ( conn ) ;
return NULL ;
}
socketBlockSIGPIPE ( conn - > fd [ SOCK_CONNECTION ] , true ) ;
ret = connect ( conn - > fd [ SOCK_CONNECTION ] , addr - > ai_addr , addr - > ai_addrlen ) ;
if ( ret = = SOCKET_ERROR )
{
fd_set conReady ;
# if defined(WZ_OS_WIN)
fd_set conFailed ;
# endif
if ( ( getSockErr ( ) ! = EINPROGRESS
& & getSockErr ( ) ! = EAGAIN
& & getSockErr ( ) ! = EWOULDBLOCK )
# if defined(WZ_OS_UNIX)
| | conn - > fd [ SOCK_CONNECTION ] > = FD_SETSIZE
# endif
| | timeout = = 0 )
{
debug ( LOG_NET , " Failed to start connecting: %s, using socket %p " , strSockError ( getSockErr ( ) ) , conn ) ;
socketClose ( conn ) ;
return NULL ;
}
do
{
struct timeval tv = { timeout / 1000 , ( timeout % 1000 ) * 1000 } ;
FD_ZERO ( & conReady ) ;
FD_SET ( conn - > fd [ SOCK_CONNECTION ] , & conReady ) ;
# if defined(WZ_OS_WIN)
FD_ZERO ( & conFailed ) ;
FD_SET ( conn - > fd [ SOCK_CONNECTION ] , & conFailed ) ;
# endif
# if defined(WZ_OS_WIN)
ret = select ( conn - > fd [ SOCK_CONNECTION ] + 1 , NULL , & conReady , & conFailed , & tv ) ;
# else
ret = select ( conn - > fd [ SOCK_CONNECTION ] + 1 , NULL , & conReady , NULL , & tv ) ;
# endif
} while ( ret = = SOCKET_ERROR & & getSockErr ( ) = = EINTR ) ;
if ( ret = = SOCKET_ERROR )
{
debug ( LOG_NET , " Failed to wait for connection: %s, socket %p. Closing. " , strSockError ( getSockErr ( ) ) , conn ) ;
socketClose ( conn ) ;
return NULL ;
}
if ( ret = = 0 )
{
setSockErr ( ETIMEDOUT ) ;
debug ( LOG_NET , " Timed out while waiting for connection to be established: %s, using socket %p. Closing. " , strSockError ( getSockErr ( ) ) , conn ) ;
socketClose ( conn ) ;
return NULL ;
}
# if defined(WZ_OS_WIN)
ASSERT ( FD_ISSET ( conn - > fd [ SOCK_CONNECTION ] , & conReady ) | | FD_ISSET ( conn - > fd [ SOCK_CONNECTION ] , & conFailed ) , " \" sock \" is the only file descriptor in set, it should be the one that is set. " ) ;
# else
ASSERT ( FD_ISSET ( conn - > fd [ SOCK_CONNECTION ] , & conReady ) , " \" sock \" is the only file descriptor in set, it should be the one that is set. " ) ;
# endif
# if defined(WZ_OS_WIN)
if ( FD_ISSET ( conn - > fd [ SOCK_CONNECTION ] , & conFailed ) )
# elif defined(WZ_OS_UNIX)
if ( connect ( conn - > fd [ SOCK_CONNECTION ] , addr - > ai_addr , addr - > ai_addrlen ) = = SOCKET_ERROR
& & getSockErr ( ) ! = EISCONN )
# endif
{
debug ( LOG_NET , " Failed to connect: %s, with socket %p. Closing. " , strSockError ( getSockErr ( ) ) , conn ) ;
socketClose ( conn ) ;
return NULL ;
}
}
debug ( LOG_NET , " setting socket (%p) blocking status (true). " , conn ) ;
if ( ! setSocketBlocking ( conn - > fd [ SOCK_CONNECTION ] , true ) )
{
debug ( LOG_NET , " Failed to set socket %p blocking status (true). Closing. " , conn ) ;
socketClose ( conn ) ;
return NULL ;
}
return conn ;
}
Socket * socketListen ( unsigned int port )
{
/* Enable the V4 to V6 mapping, but only when available, because it
* isn ' t available on all platforms .
*/
# if defined(IPV6_V6ONLY)
static const int ipv6_v6only = 0 ;
# endif
static const int so_reuseaddr = 1 ;
struct sockaddr_in addr4 ;
struct sockaddr_in6 addr6 ;
unsigned int i ;
2010-03-16 13:52:57 -07:00
Socket * const conn = new Socket ;
2010-03-16 05:54:36 -07:00
if ( conn = = NULL )
{
debug ( LOG_ERROR , " Out of memory! " ) ;
abort ( ) ;
return NULL ;
}
// Mark all unused socket handles as invalid
for ( i = 0 ; i < ARRAY_SIZE ( conn - > fd ) ; + + i )
{
conn - > fd [ i ] = INVALID_SOCKET ;
}
strncpy ( conn - > textAddress , " LISTENING SOCKET " , sizeof ( conn - > textAddress ) ) ;
// Listen on all local IPv4 and IPv6 addresses for the given port
addr4 . sin_family = AF_INET ;
addr4 . sin_port = htons ( port ) ;
addr4 . sin_addr . s_addr = INADDR_ANY ;
addr6 . sin6_family = AF_INET6 ;
addr6 . sin6_port = htons ( port ) ;
addr6 . sin6_addr = in6addr_any ;
addr6 . sin6_flowinfo = 0 ;
addr6 . sin6_scope_id = 0 ;
conn - > fd [ SOCK_IPV4_LISTEN ] = socket ( addr4 . sin_family , SOCK_STREAM , 0 ) ;
conn - > fd [ SOCK_IPV6_LISTEN ] = socket ( addr6 . sin6_family , SOCK_STREAM , 0 ) ;
if ( conn - > fd [ SOCK_IPV4_LISTEN ] = = INVALID_SOCKET
& & conn - > fd [ SOCK_IPV6_LISTEN ] = = INVALID_SOCKET )
{
debug ( LOG_ERROR , " Failed to create an IPv4 and IPv6 (only supported address families) socket (%p): %s. Closing. " , conn , strSockError ( getSockErr ( ) ) ) ;
socketClose ( conn ) ;
return NULL ;
}
if ( conn - > fd [ SOCK_IPV4_LISTEN ] ! = INVALID_SOCKET )
{
debug ( LOG_NET , " Successfully created an IPv4 socket (%p) " , conn ) ;
}
if ( conn - > fd [ SOCK_IPV6_LISTEN ] ! = INVALID_SOCKET )
{
debug ( LOG_NET , " Successfully created an IPv6 socket (%p) " , conn ) ;
}
# if defined(IPV6_V6ONLY)
if ( conn - > fd [ SOCK_IPV6_LISTEN ] ! = INVALID_SOCKET )
{
2010-03-20 06:03:47 -07:00
if ( setsockopt ( conn - > fd [ SOCK_IPV6_LISTEN ] , IPPROTO_IPV6 , IPV6_V6ONLY , ( char * ) & ipv6_v6only , sizeof ( ipv6_v6only ) ) = = SOCKET_ERROR )
2010-03-16 05:54:36 -07:00
{
debug ( LOG_INFO , " Failed to set IPv6 socket to perform IPv4 to IPv6 mapping. Falling back to using two sockets. Error: %s " , strSockError ( getSockErr ( ) ) ) ;
}
else
{
debug ( LOG_NET , " Successfully enabled IPv4 to IPv6 mapping. Cleaning up IPv4 socket. " ) ;
# if defined(WZ_OS_WIN)
closesocket ( conn - > fd [ SOCK_IPV4_LISTEN ] ) ;
# else
close ( conn - > fd [ SOCK_IPV4_LISTEN ] ) ;
# endif
conn - > fd [ SOCK_IPV4_LISTEN ] = INVALID_SOCKET ;
}
}
# endif
if ( conn - > fd [ SOCK_IPV4_LISTEN ] ! = INVALID_SOCKET )
{
2010-03-20 06:03:47 -07:00
if ( setsockopt ( conn - > fd [ SOCK_IPV4_LISTEN ] , SOL_SOCKET , SO_REUSEADDR , ( char * ) & so_reuseaddr , sizeof ( so_reuseaddr ) ) = = SOCKET_ERROR )
2010-03-16 05:54:36 -07:00
{
debug ( LOG_WARNING , " Failed to set SO_REUSEADDR on IPv4 socket. Error: %s " , strSockError ( getSockErr ( ) ) ) ;
}
debug ( LOG_NET , " setting socket (%p) blocking status (false, IPv4). " , conn ) ;
if ( bind ( conn - > fd [ SOCK_IPV4_LISTEN ] , ( const struct sockaddr * ) & addr4 , sizeof ( addr4 ) ) = = SOCKET_ERROR
| | listen ( conn - > fd [ SOCK_IPV4_LISTEN ] , 5 ) = = SOCKET_ERROR
| | ! setSocketBlocking ( conn - > fd [ SOCK_IPV4_LISTEN ] , false ) )
{
debug ( LOG_ERROR , " Failed to set up IPv4 socket for listening on port %u: %s " , port , strSockError ( getSockErr ( ) ) ) ;
# if defined(WZ_OS_WIN)
closesocket ( conn - > fd [ SOCK_IPV4_LISTEN ] ) ;
# else
close ( conn - > fd [ SOCK_IPV4_LISTEN ] ) ;
# endif
conn - > fd [ SOCK_IPV4_LISTEN ] = INVALID_SOCKET ;
}
}
if ( conn - > fd [ SOCK_IPV6_LISTEN ] ! = INVALID_SOCKET )
{
2010-03-20 06:03:47 -07:00
if ( setsockopt ( conn - > fd [ SOCK_IPV6_LISTEN ] , SOL_SOCKET , SO_REUSEADDR , ( char * ) & so_reuseaddr , sizeof ( so_reuseaddr ) ) = = SOCKET_ERROR )
2010-03-16 05:54:36 -07:00
{
debug ( LOG_INFO , " Failed to set SO_REUSEADDR on IPv6 socket. Error: %s " , strSockError ( getSockErr ( ) ) ) ;
}
debug ( LOG_NET , " setting socket (%p) blocking status (false, IPv6). " , conn ) ;
if ( bind ( conn - > fd [ SOCK_IPV6_LISTEN ] , ( const struct sockaddr * ) & addr6 , sizeof ( addr6 ) ) = = SOCKET_ERROR
| | listen ( conn - > fd [ SOCK_IPV6_LISTEN ] , 5 ) = = SOCKET_ERROR
| | ! setSocketBlocking ( conn - > fd [ SOCK_IPV6_LISTEN ] , false ) )
{
debug ( LOG_ERROR , " Failed to set up IPv6 socket for listening on port %u: %s " , port , strSockError ( getSockErr ( ) ) ) ;
# if defined(WZ_OS_WIN)
closesocket ( conn - > fd [ SOCK_IPV6_LISTEN ] ) ;
# else
close ( conn - > fd [ SOCK_IPV6_LISTEN ] ) ;
# endif
conn - > fd [ SOCK_IPV6_LISTEN ] = INVALID_SOCKET ;
}
}
// Check whether we still have at least a single (operating) socket.
if ( conn - > fd [ SOCK_IPV4_LISTEN ] = = INVALID_SOCKET
& & conn - > fd [ SOCK_IPV6_LISTEN ] = = INVALID_SOCKET )
{
debug ( LOG_NET , " No IPv4 or IPv6 sockets created. " ) ;
socketClose ( conn ) ;
return NULL ;
}
return conn ;
}
Socket * socketOpenAny ( const SocketAddress * addr , unsigned timeout )
{
Socket * ret = NULL ;
while ( addr ! = NULL & & ret = = NULL )
{
ret = socketOpen ( addr , timeout ) ;
addr = addr - > ai_next ;
}
return ret ;
}
size_t socketArrayOpen ( Socket * * sockets , size_t maxSockets , const SocketAddress * addr , unsigned timeout )
{
size_t i = 0 ;
while ( i < maxSockets & & addr ! = NULL )
{
if ( addr - > ai_family = = AF_INET | | addr - > ai_family = = AF_INET6 )
{
sockets [ i ] = socketOpen ( addr , timeout ) ;
i + = sockets [ i ] ! = NULL ;
}
addr = addr - > ai_next ;
}
std : : fill ( sockets + i , sockets + maxSockets , ( Socket * ) NULL ) ;
return i ;
}
void socketArrayClose ( Socket * * sockets , size_t maxSockets )
{
std : : for_each ( sockets , sockets + maxSockets , socketClose ) ; // Close any open sockets.
std : : fill ( sockets , sockets + maxSockets , ( Socket * ) NULL ) ; // Set the pointers to NULL.
}
char const * getSocketTextAddress ( Socket const * sock )
{
return sock - > textAddress ;
}
SocketAddress * resolveHost ( const char * host , unsigned int port )
{
struct addrinfo * results ;
char * service ;
struct addrinfo hint ;
int error , flags = 0 ;
hint . ai_family = AF_UNSPEC ;
hint . ai_socktype = SOCK_STREAM ;
hint . ai_protocol = 0 ;
# ifdef AI_V4MAPPED
flags | = AI_V4MAPPED ;
# endif
# ifdef AI_ADDRCONFIG
flags | = AI_ADDRCONFIG ;
# endif
hint . ai_flags = flags ;
hint . ai_addrlen = 0 ;
hint . ai_addr = NULL ;
hint . ai_canonname = NULL ;
hint . ai_next = NULL ;
sasprintf ( & service , " %u " , port ) ;
error = getaddrinfo ( host , service , & hint , & results ) ;
if ( error ! = 0 )
{
debug ( LOG_NET , " getaddrinfo failed for %s:%s: %s " , host , service , gai_strerror ( error ) ) ;
return NULL ;
}
return results ;
}
void deleteSocketAddress ( SocketAddress * addr )
{
freeaddrinfo ( addr ) ;
}
// ////////////////////////////////////////////////////////////////////////
// setup stuff
void SOCKETinit ( )
{
# if defined(WZ_OS_WIN)
static bool firstCall = true ;
if ( firstCall )
{
firstCall = false ;
static WSADATA stuff ;
WORD ver_required = ( 2 < < 8 ) + 2 ;
if ( WSAStartup ( ver_required , & stuff ) ! = 0 )
{
debug ( LOG_ERROR , " Failed to initialize Winsock: %s " , strSockError ( getSockErr ( ) ) ) ;
2010-03-20 06:03:47 -07:00
return ;
2010-03-16 05:54:36 -07:00
}
winsock2_dll = LoadLibraryA ( " ws2_32.dll " ) ;
if ( winsock2_dll )
{
2010-03-20 06:03:47 -07:00
getaddrinfo_dll_func = ( GETADDRINFO_DLL_FUNC ) GetProcAddress ( winsock2_dll , " getaddrinfo " ) ;
freeaddrinfo_dll_func = ( FREEADDRINFO_DLL_FUNC ) GetProcAddress ( winsock2_dll , " freeaddrinfo " ) ;
2010-03-16 05:54:36 -07:00
}
// Determine major Windows version
major_windows_version = LOBYTE ( LOWORD ( GetVersion ( ) ) ) ;
}
# endif
2010-03-16 13:52:57 -07:00
if ( socketThread = = NULL )
{
socketThreadQuit = false ;
socketThreadMutex = wzMutexCreate ( ) ;
socketThreadSemaphore = wzSemaphoreCreate ( 0 ) ;
socketThread = wzThreadCreate ( socketThreadFunction , NULL ) ;
wzThreadStart ( socketThread ) ;
}
2010-03-16 05:54:36 -07:00
}
void SOCKETshutdown ( )
{
2010-03-16 13:52:57 -07:00
if ( socketThread ! = NULL )
{
wzMutexLock ( socketThreadMutex ) ;
socketThreadQuit = true ;
socketThreadWrites . clear ( ) ;
wzMutexUnlock ( socketThreadMutex ) ;
wzSemaphorePost ( socketThreadSemaphore ) ; // Wake up the thread, so it can quit.
wzThreadJoin ( socketThread ) ;
wzMutexDestroy ( socketThreadMutex ) ;
wzSemaphoreDestroy ( socketThreadSemaphore ) ;
socketThread = NULL ;
}
2010-03-16 05:54:36 -07:00
# if defined(WZ_OS_WIN)
WSACleanup ( ) ;
if ( winsock2_dll )
{
FreeLibrary ( winsock2_dll ) ;
winsock2_dll = NULL ;
getaddrinfo_dll_func = NULL ;
freeaddrinfo_dll_func = NULL ;
}
# endif
}