/* * external.c * * by Gary Wong , 2001, 2002. * * This program is free software; you can redistribute it and/or modify * it under the terms of version 2 of the GNU General Public License as * published by the Free Software Foundation. * * This program 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 this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * $Id: external.c,v 1.39 2004/03/18 23:29:14 oysteijo Exp $ */ #include "config.h" #if HAVE_SOCKETS #include #if HAVE_UNISTD_H #include #endif /* #if HAVE_UNISTD_H */ #ifndef WIN32 #include #include #include #include #include #if HAVE_SYS_SOCKET_H #include #include #include #include #include #include #endif /* #if HAVE_SYS_SOCKET_H */ #include #else /* #ifndef WIN32 */ #include #include #define EWOULDBLOCK WSAEWOULDBLOCK #define EINPROGRESS WSAEINPROGRESS #define EALREADY WSAEALREADY #define ENOTSOCK WSAENOTSOCK #define EDESTADDRREQ WSAEDESTADDRREQ #define EMSGSIZE WSAEMSGSIZE #define EPROTOTYPE WSAEPROTOTYPE #define ENOPROTOOPT WSAENOPROTOOPT #define EPROTONOSUPPORT WSAEPROTONOSUPPORT #define ESOCKTNOSUPPORT WSAESOCKTNOSUPPORT #define EOPNOTSUPP WSAEOPNOTSUPP #define EPFNOSUPPORT WSAEPFNOSUPPORT #define EAFNOSUPPORT WSAEAFNOSUPPORT #define EADDRINUSE WSAEADDRINUSE #define EADDRNOTAVAIL WSAEADDRNOTAVAIL #define ENETDOWN WSAENETDOWN #define ENETUNREACH WSAENETUNREACH #define ENETRESET WSAENETRESET #define ECONNABORTED WSAECONNABORTED #define ECONNRESET WSAECONNRESET #define ENOBUFS WSAENOBUFS #define EISCONN WSAEISCONN #define ENOTCONN WSAENOTCONN #define ESHUTDOWN WSAESHUTDOWN #define ETOOMANYREFS WSAETOOMANYREFS #define ETIMEDOUT WSAETIMEDOUT #define ECONNREFUSED WSAECONNREFUSED #define ELOOP WSAELOOP #define ENAMETOOLONG WSAENAMETOOLONG #define EHOSTDOWN WSAEHOSTDOWN #define EHOSTUNREACH WSAEHOSTUNREACH #define ENOTEMPTY WSAENOTEMPTY #define EPROCLIM WSAEPROCLIM #define EUSERS WSAEUSERS #define EDQUOT WSAEDQUOT #define ESTALE WSAESTALE #define EREMOTE WSAEREMOTE #define EINVAL WSAEINVAL #define EINTR WSAEINTR #define inet_aton(ip,addr) (addr)->s_addr = inet_addr(ip), 1 #define inet_pton(fam,ip,addr) (addr)->s_addr = inet_addr(ip), 1 #endif /* #ifndef WIN32 */ #endif /* HAVE_SOCKETS */ #include "backgammon.h" #include "drawboard.h" #include "external.h" #include "rollout.h" #include "i18n.h" #if HAVE_SOCKETS /* Stuff for the yacc/lex parser */ extern extcmd ec; static int fError = FALSE; static char *szError = NULL; extern void ext_scan_string( const char *s ); extern void extparse(); #if defined(AF_UNIX) && !defined(AF_LOCAL) #define AF_LOCAL AF_UNIX #define PF_LOCAL PF_UNIX #endif extern int ExternalSocket( struct sockaddr **ppsa, int *pcb, char *sz ) { int sock, f; struct sockaddr_un *psun; struct sockaddr_in *psin; struct hostent *phe; char *pch; if( ( pch = strchr( sz, ':' ) ) && !strchr( sz, '/' ) ) { /* Internet domain socket. */ if( ( sock = socket( PF_INET, SOCK_STREAM, 0 ) ) < 0 ) return -1; f = TRUE; #ifdef WIN32 if( setsockopt( (SOCKET) sock, SOL_SOCKET, SO_REUSEADDR, (const char*) &f, sizeof f ) ) #else if( setsockopt( sock, SOL_SOCKET, SO_REUSEADDR, &f, sizeof f ) ) #endif /* WIN32 */ return -1; psin = malloc( *pcb = sizeof (struct sockaddr_in) ); memset( psin, 0, sizeof (*psin)); psin->sin_family = AF_INET; /* split hostname and port */ *pch = 0; if( !*sz ) /* no host specified */ psin->sin_addr.s_addr = htonl( INADDR_ANY ); #ifdef WIN32 else { #else else if( !inet_aton( sz, &psin->sin_addr ) ) { #endif /* WIN32 */ if( !( phe = gethostbyname( sz ) ) ) { *pch = ':'; errno = EINVAL; free( psin ); return -1; } #ifdef WIN32 memcpy( &(psin->sin_addr), (struct in_addr *) (phe->h_addr), phe->h_length); #else psin->sin_addr = *(struct in_addr *) phe->h_addr; #endif /* WIN32 */ } *pch++ = ':'; psin->sin_port = htons( atoi( pch ) ); *ppsa = (struct sockaddr *) psin; } else { /* Local domain socket. */ if( ( sock = socket( PF_LOCAL, SOCK_STREAM, 0 ) ) < 0 ) return -1; /* yuck... there's no portable way to obtain the necessary sockaddr_un size, but this is a conservative estimate */ psun = malloc( *pcb = 16 + strlen( sz ) ); #ifndef WIN32 psun->sun_family = AF_LOCAL; strcpy( psun->sun_path, sz ); #else /* #ifndef WIN32 */ /* FIXME: what will we do on Windows? */ return -1; #endif /* #ifndef WIN32 */ *ppsa = (struct sockaddr *) psun; } return sock; #if 0 assert( FALSE ); #endif /* 0 */ } #endif /* HAVE_SOCKETS */ #if HAVE_SOCKETS static void ExternalUnbind( char *sz ) { if( strchr( sz, ':' ) && !strchr( sz, '/' ) ) /* it was a TCP socket; no cleanup necessary */ return; unlink( sz ); } #endif /* HAVE_SOCKETS */ #if HAVE_SOCKETS extern int ExternalRead( int h, char *pch, int cch ) { char *p = pch, *pEnd; int n; #ifndef WIN32 psighandler sh; #endif while( cch ) { if( fAction ) fnAction(); if( fInterrupt ) return -1; #ifndef WIN32 PortableSignal( SIGPIPE, SIG_IGN, &sh, FALSE ); #endif #ifdef WIN32 /* reading from sockets doesn't work on Windows use recv instead */ n = recv( (SOCKET) h, p, cch, 0); #else n = read( h, p, cch ); #endif #ifndef WIN32 PortableSignalRestore( SIGPIPE, &sh ); #endif if( !n ) { outputl( _("External connection closed.") ); return -1; } else if( n < 0 ) { if( errno == EINTR ) continue; outputerr( _("reading from external connection") ); return -1; } if( ( pEnd = memchr( p, '\n', n ) ) ) { *pEnd = 0; return 0; } cch -= n; p += n; } p[ cch - 1 ] = 0; return 0; #if 0 assert( FALSE ); #endif } #endif /* HAVE_SOCKETS */ #if HAVE_SOCKETS extern int ExternalWrite( int h, char *pch, int cch ) { char *p = pch; int n; #ifndef WIN32 psighandler sh; #endif while( cch ) { if( fAction ) fnAction(); if( fInterrupt ) return -1; #ifndef WIN32 PortableSignal( SIGPIPE, SIG_IGN, &sh, FALSE ); #endif #ifdef WIN32 /* writing to sockets doesn't work on Windows use send instead */ n = send( (SOCKET) h, (const char *) p, cch, 0); #else n = write( h, p, cch ); #endif #ifndef WIN32 PortableSignalRestore( SIGPIPE, &sh ); #endif if( !n ) return 0; else if( n < 0 ) { if( errno == EINTR ) continue; outputerr( _("writing to external connection") ); return -1; } cch -= n; p += n; } return 0; #if 0 assert( FALSE ); #endif } #endif /* HAVE_SOCKETS */ #if HAVE_SOCKETS static void ErrorHandler( const char *szMessage, const char *szNear, const int fParseError ) { fError = TRUE; g_free( szError ); szError = g_strdup_printf( "Error (%s) near '%s'\n", szMessage, szNear ); } static extcmd * ExtParse( const char *szCommand ) { ExtErrorHandler = ErrorHandler; fError = FALSE; szError = NULL; ext_scan_string( szCommand ); extparse(); return fError ? NULL : &ec; } static char * ExtEvaluation( extcmd *pec ) { char szName[ MAX_NAME_LEN ], szOpp[ MAX_NAME_LEN ]; int anBoard[ 2 ][ 25 ], nMatchTo, anScore[ 2 ], anDice[ 2 ], nCube, fCubeOwner, fDoubled, fTurn, fCrawford; float arOutput[ NUM_ROLLOUT_OUTPUTS ]; cubeinfo ci; int nScore, nScoreOpponent; char *szResponse; float r; evalcontext ec; if( ParseFIBSBoard( pec->szFIBSBoard, anBoard, szName, szOpp, &nMatchTo, &nScore, &nScoreOpponent, anDice, &nCube, &fCubeOwner, &fDoubled, &fTurn, &fCrawford ) ) { outputl( _("Warning: badly formed board from external controller.") ); szResponse = g_strdup_printf( "Error: badly formed board ('%s')\n", pec->szFIBSBoard ); } else { anScore[ 0 ] = fTurn ? nScoreOpponent : nScore; anScore[ 1 ] = fTurn ? nScore : nScoreOpponent; SetCubeInfo ( &ci, nCube, fCubeOwner, fTurn, nMatchTo, anScore, fCrawford, fJacoby, nBeavers, bgvDefault ); ec.fCubeful = pec->fCubeful; ec.nPlies = pec->nPlies; ec.nReduced = pec->nReduced; ec.fDeterministic = pec->fDeterministic; ec.rNoise = pec->rNoise; if ( !fTurn ) SwapSides( anBoard ); if ( GeneralEvaluationE( arOutput, anBoard, &ci, &ec ) ) return NULL; if ( nMatchTo ) { if ( ec.fCubeful ) r = arOutput[ OUTPUT_CUBEFUL_EQUITY ]; else r = eq2mwc( arOutput[ OUTPUT_EQUITY ], &ci ); } else r = ec.fCubeful ? arOutput[ 6 ] : arOutput[ 5 ]; szResponse = g_strdup_printf( "%f %f %f %f %f %f\n", arOutput[ 0 ], arOutput[ 1 ], arOutput[ 2 ], arOutput[ 3 ], arOutput[ 4 ], r ); } return szResponse; } static char * ExtFIBSBoard( extcmd *pec ) { char szName[ MAX_NAME_LEN ], szOpp[ MAX_NAME_LEN ]; int anBoard[ 2 ][ 25 ], anBoardOrig[ 2 ][ 25 ], nMatchTo, anScore[ 2 ], anDice[ 2 ], nCube, fCubeOwner, fDoubled, fTurn, fCrawford, anMove[ 8 ]; float arDouble[ NUM_CUBEFUL_OUTPUTS ], aarOutput[ 2 ][ NUM_ROLLOUT_OUTPUTS ], aarStdDev[ 2 ][ NUM_ROLLOUT_OUTPUTS ]; rolloutstat aarsStatistics[ 2 ][ 2 ]; cubeinfo ci; int nScore, nScoreOpponent; char *szResponse; if( ParseFIBSBoard( pec->szFIBSBoard, anBoard, szName, szOpp, &nMatchTo, &nScore, &nScoreOpponent, anDice, &nCube, &fCubeOwner, &fDoubled, &fTurn, &fCrawford ) ) { outputl( _("Warning: badly formed board from external controller.") ); szResponse = g_strdup_printf( "Error: badly formed board ('%s')\n", pec->szFIBSBoard ); } else { anScore[ 0 ] = fTurn ? nScoreOpponent : nScore; anScore[ 1 ] = fTurn ? nScore : nScoreOpponent; SetCubeInfo ( &ci, nCube, fCubeOwner, fTurn, nMatchTo, anScore, fCrawford, fJacoby, nBeavers, bgvDefault ); if ( !fTurn ) SwapSides( anBoard ); #if 0 { char *asz[ 7 ] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL }; cha szBoard[ 10000 ]; outputl( DrawBoard( szBoard, anBoard, fTurn, asz, "no matchid", 15 ) ); printf( "score %d-%d to %d ", anScore[ 0 ], anScore[ 1 ], nMatchTo ); printf( "dice %d %d ", anDice[ 0 ], anDice[ 1 ] ); printf( "cubeowner %d cube %d turn %d crawford %d doubled %d\n", fCubeOwner, nCube, fTurn, fCrawford, fDoubled ); } #endif memcpy( anBoardOrig, anBoard, sizeof( anBoard ) ); if ( fDoubled ) { /* take decision */ if( GeneralCubeDecision( aarOutput, aarStdDev, aarsStatistics, anBoard, &ci, &esEvalCube, NULL, NULL ) < 0 ) return NULL; switch( FindCubeDecision( arDouble, GCCCONSTAHACK aarOutput, &ci )) { case DOUBLE_PASS: case TOOGOOD_PASS: case REDOUBLE_PASS: case TOOGOODRE_PASS: szResponse = g_strdup( "drop\n" ); break; case NODOUBLE_BEAVER: case DOUBLE_BEAVER: case NO_REDOUBLE_BEAVER: szResponse = g_strdup( "beaver\n" ); break; default: szResponse = g_strdup( "take\n" ); } #if 0 /* this code is broken as the sign of fDoubled indicates who doubled */ } else if ( fDoubled < 0 ) { /* if opp wants to resign (extension to FIBS board) */ float arOutput[ NUM_ROLLOUT_OUTPUTS ]; float rEqBefore, rEqAfter; const float epsilon = 1.0e-6; getResignation( arOutput, anBoard, &ci, &esEvalCube ); getResignEquities ( arOutput, &ci, -fDoubled, &rEqBefore, &rEqAfter ); /* if opponent gives up equity by resigning */ if( ( rEqAfter - epsilon ) < rEqBefore ) szResponse = g_strdup( "accept\n" ); else szResponse = g_strdup( "reject\n" ); #endif /* broken */ } else if( anDice[ 0 ] ) { /* move */ char szMove[ 64 ]; if( FindBestMove( anMove, anDice[ 0 ], anDice[ 1 ], anBoard, &ci, &esEvalChequer.ec, aamfEval ) < 0 ) return NULL; FormatMovePlain( szMove, anBoardOrig, anMove ); szResponse = g_strconcat( szMove, "\n", NULL ); } else { /* double decision */ if( GeneralCubeDecision( aarOutput, aarStdDev, aarsStatistics, anBoard, &ci, &esEvalCube, NULL, NULL ) < 0 ) return NULL; switch( FindCubeDecision( arDouble, GCCCONSTAHACK aarOutput, &ci )) { case DOUBLE_TAKE: case DOUBLE_PASS: case DOUBLE_BEAVER: case REDOUBLE_TAKE: case REDOUBLE_PASS: szResponse = g_strdup( "double\n" ); break; default: szResponse = g_strdup( "roll\n" ); } } } return szResponse; } #endif extern void CommandExternal( char *sz ) { #if !HAVE_SOCKETS outputl( _("This installation of GNU Backgammon was compiled without\n" "socket support, and does not implement external controllers.") ); #else int h, hPeer, cb; struct sockaddr *psa; char szCommand[ 256 ]; char *szResponse; struct sockaddr_in saRemote; socklen_t saLen; extcmd *pec; sz = NextToken( &sz ); if( !sz || !*sz ) { outputl( _("You must specify the name of the socket to the external\n" "controller -- try `help external'.") ); return; } do { if( ( h = ExternalSocket( &psa, &cb, sz ) ) < 0 ) { outputerr( sz ); return; } if( bind( h, psa, cb ) < 0 ) { outputerr( sz ); close( h ); free( psa ); return; } free( psa ); if( listen( h, 1 ) < 0 ) { outputerr( _("listen") ); close( h ); ExternalUnbind( sz ); return; } while( ( hPeer = accept( h, (struct sockaddr*)&saRemote, &saLen ) ) < 0 ) { if( errno == EINTR ) { if( fAction ) fnAction(); if( fInterrupt ) { close( h ); ExternalUnbind( sz ); return; } continue; } outputerr( _("accept") ); close( h ); ExternalUnbind( sz ); return; } close( h ); ExternalUnbind( sz ); /* print info about remove client */ outputf( _("Accepted connection from %s.\n"), inet_ntoa( saRemote.sin_addr ) ); PushLocale( "C" ); while( !ExternalRead( hPeer, szCommand, sizeof( szCommand ) ) ) { if ( ! ( pec = ExtParse( szCommand ) ) ) { /* parse error */ szResponse = szError; } else { switch ( pec->ct ) { case COMMAND_NONE: szResponse = g_strdup( "Error: no command given\n" ); break; case COMMAND_FIBSBOARD: szResponse = ExtFIBSBoard( pec ); break; case COMMAND_EVALUATION: szResponse = ExtEvaluation( pec ); break; } } if ( szResponse ) { if ( ExternalWrite( hPeer, szResponse, strlen( szResponse ) ) ) break; g_free( szResponse ); } } PopLocale(); close( hPeer ); } while ( 1 ); #endif }