You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
845 lines
20 KiB
845 lines
20 KiB
//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======//
|
|
//
|
|
// Purpose:
|
|
//
|
|
// $NoKeywords: $
|
|
//===========================================================================//
|
|
|
|
|
|
#ifdef _LINUX
|
|
|
|
// linux has a multi-processing forked server mode.
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <sys/poll.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/wait.h>
|
|
#include <syscall.h>
|
|
#include <unistd.h>
|
|
#include <arpa/inet.h>
|
|
//#include <linux/tcp.h>
|
|
#include <netdb.h>
|
|
//#include <sys/param.h>
|
|
#include <sys/uio.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include "isys.h"
|
|
#include "dedicated.h"
|
|
#include "engine_hlds_api.h"
|
|
#include "filesystem.h"
|
|
#include "tier0/dbg.h"
|
|
#include "tier1/strtools.h"
|
|
#include "tier0/icommandline.h"
|
|
#include "tier2/socketcreator.h"
|
|
#include "idedicatedexports.h"
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <sys/ioctl.h>
|
|
#include "mathlib/expressioncalculator.h"
|
|
#define closesocket close
|
|
#define WSAGetLastError() errno
|
|
#define ioctlsocket ioctl
|
|
|
|
|
|
|
|
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
static netadr_t net_local_adr;
|
|
unsigned short NET_HostToNetShort( unsigned short us_in )
|
|
{
|
|
return htons( us_in );
|
|
}
|
|
|
|
unsigned short NET_NetToHostShort( unsigned short us_in )
|
|
{
|
|
return ntohs( us_in );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *s -
|
|
// *sadr -
|
|
// Output : bool NET_StringToSockaddr
|
|
//-----------------------------------------------------------------------------
|
|
bool NET_StringToSockaddr( const char *s, struct sockaddr *sadr )
|
|
{
|
|
char *colon;
|
|
char copy[128];
|
|
|
|
Q_memset (sadr, 0, sizeof(*sadr));
|
|
((struct sockaddr_in *)sadr)->sin_family = AF_INET;
|
|
((struct sockaddr_in *)sadr)->sin_port = 0;
|
|
|
|
Q_strncpy (copy, s, sizeof( copy ) );
|
|
// strip off a trailing :port if present
|
|
for (colon = copy ; *colon ; colon++)
|
|
{
|
|
if (*colon == ':')
|
|
{
|
|
*colon = 0;
|
|
((struct sockaddr_in *)sadr)->sin_port = NET_HostToNetShort((short)atoi(colon+1));
|
|
}
|
|
}
|
|
|
|
if (copy[0] >= '0' && copy[0] <= '9' && Q_strstr( copy, "." ) )
|
|
{
|
|
*(int *)&((struct sockaddr_in *)sadr)->sin_addr = inet_addr(copy);
|
|
}
|
|
else
|
|
{
|
|
struct hostent *h;
|
|
if ( (h = gethostbyname(copy)) == NULL )
|
|
return false;
|
|
*(int *)&((struct sockaddr_in *)sadr)->sin_addr = *(int *)h->h_addr_list[0];
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
NET_StringToAdr
|
|
|
|
localhost
|
|
idnewt
|
|
idnewt:28000
|
|
192.246.40.70
|
|
192.246.40.70:28000
|
|
=============
|
|
*/
|
|
bool NET_StringToAdr ( const char *s, netadr_t *a)
|
|
{
|
|
struct sockaddr saddr;
|
|
|
|
char address[128];
|
|
|
|
Q_strncpy( address, s, sizeof(address) );
|
|
|
|
if ( !Q_strncmp( address, "localhost", 10 ) || !Q_strncmp( address, "localhost:", 10 ) )
|
|
{
|
|
// subsitute 'localhost' with '127.0.0.1", both have 9 chars
|
|
// this way we can resolve 'localhost' without DNS and still keep the port
|
|
Q_memcpy( address, "127.0.0.1", 9 );
|
|
}
|
|
|
|
|
|
if ( !NET_StringToSockaddr (address, &saddr) )
|
|
return false;
|
|
|
|
a->SetFromSockadr( &saddr );
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void NET_GetLocalAddress (void)
|
|
{
|
|
net_local_adr.Clear();
|
|
|
|
char buff[512];
|
|
|
|
gethostname( buff, sizeof(buff) ); // get own IP address
|
|
buff[sizeof(buff)-1] = 0; // Ensure that it doesn't overrun the buffer
|
|
NET_StringToAdr (buff, &net_local_adr);
|
|
|
|
}
|
|
|
|
|
|
#define MAX_STATUS_STRING_LENGTH 1024
|
|
#define MAX_INPUT_FROM_CHILD 2048
|
|
|
|
class CConnectedNetConsoleData
|
|
{
|
|
public:
|
|
int m_nCharsInCommandBuffer;
|
|
char m_pszInputCommandBuffer[MAX_INPUT_FROM_CHILD];
|
|
bool m_bAuthorized; // for password protection
|
|
CConnectedNetConsoleData( void )
|
|
{
|
|
m_nCharsInCommandBuffer = 0;
|
|
m_bAuthorized = false;
|
|
}
|
|
};
|
|
|
|
class CParentProcessNetConsoleMgr : public ISocketCreatorListener
|
|
{
|
|
public:
|
|
CSocketCreator m_Socket;
|
|
netadr_t m_Address;
|
|
char m_pPassword[256]; // if set
|
|
bool m_bPasswordProtected;
|
|
bool m_bActive;
|
|
|
|
bool ShouldAcceptSocket( SocketHandle_t hSocket, const netadr_t &netAdr )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void OnSocketAccepted( SocketHandle_t hSocket, const netadr_t &netAdr, void** ppData )
|
|
{
|
|
CConnectedNetConsoleData *pData = new CConnectedNetConsoleData;
|
|
if ( ! m_bPasswordProtected )
|
|
pData->m_bAuthorized = true; // no password, auto-auth
|
|
*ppData = pData;
|
|
}
|
|
|
|
void OnSocketClosed( SocketHandle_t hSocket, const netadr_t &netAdr, void* pData )
|
|
{
|
|
if ( pData )
|
|
free( pData );
|
|
}
|
|
|
|
void RunFrame( void );
|
|
|
|
CParentProcessNetConsoleMgr( void );
|
|
|
|
void HandleInputChars( char const *pIn, int recvLen, CConnectedNetConsoleData *pData, int idx );
|
|
|
|
void SendString( char const *pString, int idx = -1 ); // send a string to all sockets or just one
|
|
|
|
void Execute( CConnectedNetConsoleData *pData, int idx );
|
|
|
|
};
|
|
|
|
void CParentProcessNetConsoleMgr::SendString( char const *pString, int nidx )
|
|
{
|
|
m_Socket.RunFrame();
|
|
int nCount = m_Socket.GetAcceptedSocketCount();
|
|
if ( nCount )
|
|
{
|
|
// lets add the lf to any cr's
|
|
char *pTmp = (char * ) stackalloc( strlen( pString ) * 2 + 1 );
|
|
char *oString = pTmp;
|
|
char const *pIn = pString;
|
|
while ( *pIn )
|
|
{
|
|
if ( *pIn == '\n' )
|
|
*( oString++ ) = '\r';
|
|
*( oString++ ) = *( pIn++ );
|
|
}
|
|
*( oString++ ) = 0;
|
|
|
|
for ( int i = 0; i < nCount; i++ )
|
|
{
|
|
if ( ( nidx == -1 ) || ( i == nidx ) )
|
|
{
|
|
SocketHandle_t hSocket = m_Socket.GetAcceptedSocketHandle( i );
|
|
//const netadr_t& socketAdr = m_Socket.GetAcceptedSocketAddress( i );
|
|
CConnectedNetConsoleData *pData = ( CConnectedNetConsoleData * ) m_Socket.GetAcceptedSocketData( i );
|
|
if ( pData->m_bAuthorized ) // no output to un-authed net consoles
|
|
{
|
|
send( hSocket, pTmp, oString - pTmp - 1, MSG_NOSIGNAL );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
CParentProcessNetConsoleMgr::CParentProcessNetConsoleMgr( void ) : m_Socket( this )
|
|
{
|
|
m_bActive = false;
|
|
m_bPasswordProtected = false;
|
|
int nPassword = CommandLine()->FindParm( "-netconpassword" );
|
|
if ( nPassword )
|
|
{
|
|
char const *pPassword = CommandLine()->GetParm( nPassword + 1 );
|
|
V_strncpy( m_pPassword, pPassword, sizeof( m_pPassword ) );
|
|
m_bPasswordProtected = true;
|
|
}
|
|
int nPort = CommandLine()->FindParm( "-netconport" );
|
|
if ( nPort )
|
|
{
|
|
NET_GetLocalAddress();
|
|
char const *pPortNum = CommandLine()->GetParm( nPort + 1 );
|
|
char newBuf[256];
|
|
V_strncpy( newBuf, pPortNum, sizeof( newBuf ) );
|
|
char *pReplace = V_strstr( newBuf, "##" );
|
|
if ( pReplace )
|
|
{
|
|
pReplace[0] = '0';
|
|
pReplace[1] = '0';
|
|
}
|
|
m_Address = net_local_adr;
|
|
int nPortNumber = EvaluateExpression( newBuf, -1 );
|
|
if ( nPortNumber > 0 )
|
|
{
|
|
m_Address.SetPort( nPortNumber );
|
|
m_bActive = true;
|
|
m_Socket.CreateListenSocket( m_Address, true );
|
|
}
|
|
}
|
|
}
|
|
|
|
void CParentProcessNetConsoleMgr::RunFrame( void )
|
|
{
|
|
// check for incoming data
|
|
if (! m_bActive )
|
|
return;
|
|
m_Socket.RunFrame();
|
|
int nCount = m_Socket.GetAcceptedSocketCount();
|
|
for ( int i = nCount - 1; i >= 0; i-- )
|
|
{
|
|
SocketHandle_t hSocket = m_Socket.GetAcceptedSocketHandle( i );
|
|
// const netadr_t& socketAdr = m_Socket.GetAcceptedSocketAddress( i );
|
|
CConnectedNetConsoleData *pData = ( CConnectedNetConsoleData * ) m_Socket.GetAcceptedSocketData( i );
|
|
char ch;
|
|
int pendingLen = recv( hSocket, &ch, sizeof(ch), MSG_PEEK );
|
|
if ( pendingLen == -1 && SocketWouldBlock() )
|
|
continue;
|
|
|
|
if ( pendingLen <= 0 ) // eof or error
|
|
{
|
|
m_Socket.CloseAcceptedSocket( i );
|
|
continue;
|
|
}
|
|
|
|
// find out how much we have to read
|
|
unsigned long readLen;
|
|
ioctlsocket( hSocket, FIONREAD, &readLen );
|
|
while( readLen > 0 )
|
|
{
|
|
char recvBuf[256];
|
|
int recvLen = recv( hSocket, recvBuf , MIN( sizeof( recvBuf ) , readLen ), 0 );
|
|
if ( recvLen == 0 ) // socket was closed
|
|
{
|
|
m_Socket.CloseAcceptedSocket( i );
|
|
break;
|
|
}
|
|
|
|
if ( recvLen < 0 && !SocketWouldBlock() )
|
|
{
|
|
break;
|
|
}
|
|
|
|
readLen -= recvLen;
|
|
// now, lets write what we've got into the command buffer
|
|
HandleInputChars( recvBuf, recvLen, pData, i );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void CParentProcessNetConsoleMgr::HandleInputChars( char const *pIn, int recvLen, CConnectedNetConsoleData *pData, int idx )
|
|
{
|
|
while( recvLen )
|
|
{
|
|
switch( *pIn )
|
|
{
|
|
case '\r':
|
|
case '\n':
|
|
{
|
|
if ( pData->m_nCharsInCommandBuffer )
|
|
{
|
|
pData->m_pszInputCommandBuffer[pData->m_nCharsInCommandBuffer] = 0;
|
|
Execute( pData, idx );
|
|
}
|
|
pData->m_nCharsInCommandBuffer = 0;
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
if ( pData->m_nCharsInCommandBuffer < MAX_INPUT_FROM_CHILD - 1 )
|
|
pData->m_pszInputCommandBuffer[pData->m_nCharsInCommandBuffer++] = *pIn;
|
|
break;
|
|
}
|
|
}
|
|
pIn++;
|
|
recvLen--;
|
|
}
|
|
}
|
|
|
|
struct CServerInstance
|
|
{
|
|
pid_t m_nPid;
|
|
int m_nSocketToChild; // "our" side of the socket connection
|
|
int m_nNumCharsInInputBuffer;
|
|
int m_nNumPlayers;
|
|
|
|
char m_pszStatus[MAX_STATUS_STRING_LENGTH];
|
|
char m_pszMapName[MAX_PATH];
|
|
char m_pszInputBuffer[MAX_INPUT_FROM_CHILD];
|
|
bool m_bRunning;
|
|
|
|
|
|
void ClearInputBuffer( void )
|
|
{
|
|
m_nNumCharsInInputBuffer = 0;
|
|
}
|
|
|
|
void ResetStatus( void )
|
|
{
|
|
m_pszMapName[0] = 0;
|
|
m_nNumPlayers = 0;
|
|
}
|
|
CServerInstance( void )
|
|
{
|
|
m_pszStatus[0] = 0; // clear status string
|
|
m_bRunning = false;
|
|
m_nSocketToChild = -1;
|
|
ClearInputBuffer();
|
|
ResetStatus();
|
|
}
|
|
|
|
void HandleSocketInput( void );
|
|
|
|
void ProcessInputFromChild( void );
|
|
|
|
};
|
|
|
|
#define MAX_CHILD_PROCESSSES 100
|
|
|
|
CParentProcessNetConsoleMgr *g_pParentProcessNetConsole;
|
|
int g_nNumChildInstances;
|
|
CServerInstance *g_pChildProcesses;
|
|
|
|
static bool s_bQuit = false;
|
|
static bool s_bDelayedQuit = false;
|
|
|
|
typedef void (*CMDFN)( char const *pArgs, int nIdx );
|
|
|
|
struct CommandDescriptor
|
|
{
|
|
char const *m_pCmdName;
|
|
CMDFN m_pCmdFn;
|
|
char const *m_pCmdHelp;
|
|
};
|
|
|
|
static char *va( char *format, ... )
|
|
{
|
|
va_list argptr;
|
|
static char string[8][512];
|
|
static int curstring = 0;
|
|
|
|
curstring = ( curstring + 1 ) % 8;
|
|
|
|
va_start (argptr, format);
|
|
Q_vsnprintf( string[curstring], sizeof( string[curstring] ), format, argptr );
|
|
va_end (argptr);
|
|
|
|
return string[curstring];
|
|
}
|
|
|
|
static void s_DoStatusCmd( char const *pArgs, int nConsoleIdx )
|
|
{
|
|
// print status
|
|
g_pParentProcessNetConsole->SendString( "#status\n", nConsoleIdx );
|
|
for( int i = 0; i < g_nNumChildInstances; i++ )
|
|
{
|
|
CServerInstance *pChild = g_pChildProcesses + i;
|
|
g_pParentProcessNetConsole->SendString( va( "child %d\n", i ), nConsoleIdx );
|
|
if ( pChild && ( pChild->m_nSocketToChild != -1 ) )
|
|
{
|
|
g_pParentProcessNetConsole->SendString( va( " pid : %d\n", i, pChild->m_nPid ), nConsoleIdx );
|
|
g_pParentProcessNetConsole->SendString( va( " map : %s\n", pChild->m_pszMapName ), nConsoleIdx );
|
|
g_pParentProcessNetConsole->SendString( va( " numplayers : %d\n", pChild->m_nNumPlayers ), nConsoleIdx );
|
|
}
|
|
}
|
|
g_pParentProcessNetConsole->SendString( "#end\n", nConsoleIdx );
|
|
}
|
|
|
|
static void s_DoQuit( char const *pArgs, int nConsoleIdx )
|
|
{
|
|
g_pParentProcessNetConsole->SendString( "Killing all children and exiting\n", nConsoleIdx );
|
|
for( int i = 0; i < g_nNumChildInstances; i++ )
|
|
{
|
|
CServerInstance *pChild = g_pChildProcesses + i;
|
|
if ( pChild && ( pChild->m_nSocketToChild != -1 ) )
|
|
{
|
|
g_pParentProcessNetConsole->SendString( va( "killing child %d\n", i ), nConsoleIdx );
|
|
kill( pChild->m_nPid, SIGKILL );
|
|
}
|
|
}
|
|
s_bQuit = true;
|
|
|
|
}
|
|
|
|
static void s_DoBroadCastCmd( char const *pArgs, int nConsoleIdx )
|
|
{
|
|
if ( ! pArgs )
|
|
{
|
|
g_pParentProcessNetConsole->SendString( "Format of command is \"broadcast <concommand>\"\n" );
|
|
}
|
|
else
|
|
{
|
|
for( int i = 0; i < g_nNumChildInstances; i++ )
|
|
{
|
|
CServerInstance *pChild = g_pChildProcesses + i;
|
|
if ( pChild && ( pChild->m_nSocketToChild != -1 ) )
|
|
{
|
|
send( pChild->m_nSocketToChild, pArgs, strlen( pArgs ), MSG_NOSIGNAL );
|
|
send( pChild->m_nSocketToChild, "\n", 1, MSG_NOSIGNAL );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void s_DoShutdown( char const *pArgs, int nConsoleIdx )
|
|
{
|
|
s_bDelayedQuit = ! s_bDelayedQuit;
|
|
if ( s_bDelayedQuit )
|
|
{
|
|
g_pParentProcessNetConsole->SendString( "Server will shutdown when all games are finished and children have exited.\n" );
|
|
}
|
|
else
|
|
{
|
|
g_pParentProcessNetConsole->SendString( "Server shutdown cancelled.\n" );
|
|
}
|
|
for( int i = 0; i < g_nNumChildInstances; i++ )
|
|
{
|
|
CServerInstance *pChild = g_pChildProcesses + i;
|
|
if ( pChild && ( pChild->m_nSocketToChild != -1 ) )
|
|
{
|
|
if ( pChild->m_nNumPlayers == 0 )
|
|
{
|
|
kill( pChild->m_nPid, SIGKILL );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void s_DoFind( char const *pArgs, int nConsoleIdx );
|
|
|
|
|
|
static CommandDescriptor s_CmdTable[]={
|
|
{ "status", s_DoStatusCmd, "List the status of all subprocesses." },
|
|
{ "broadcast", s_DoBroadCastCmd, "Send a command to all subprocesses." },
|
|
{ "find", s_DoFind, "find commands containing a string." },
|
|
{ "shutdown", s_DoShutdown, "Tell the server shutdown once all players have left. This is a toggle." },
|
|
{ "quit", s_DoQuit, "immediately shut down the server and all its child processes." },
|
|
};
|
|
|
|
static void s_DoFind( char const *pArgs, int nConsoleIdx )
|
|
{
|
|
for( int i = 0; i < ARRAYSIZE( s_CmdTable ); i++ )
|
|
{
|
|
if ( ( pArgs[0] == 0 ) || ( V_stristr( s_CmdTable[i].m_pCmdName, pArgs ) ) )
|
|
{
|
|
g_pParentProcessNetConsole->SendString( va( "%s:\t%s\n", s_CmdTable[i].m_pCmdName, s_CmdTable[i].m_pCmdHelp) , nConsoleIdx );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void CParentProcessNetConsoleMgr::Execute( CConnectedNetConsoleData *pData, int idx )
|
|
{
|
|
if ( memcmp( pData->m_pszInputCommandBuffer, "PASS ", 5 ) == 0 )
|
|
{
|
|
if ( V_strcmp( pData->m_pszInputCommandBuffer + 5, m_pPassword ) == 0 )
|
|
{
|
|
pData->m_bAuthorized = true;
|
|
}
|
|
|
|
else
|
|
{
|
|
// bad password
|
|
Warning( "Bad password attempt from net console\n" );
|
|
pData->m_bAuthorized = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( pData->m_bAuthorized )
|
|
{
|
|
char const *pCmd = pData->m_pszInputCommandBuffer;
|
|
pCmd += strspn( pCmd, " \t" );
|
|
char const *pArgs = strchr( pCmd, ' ' );
|
|
int nCmdLen;
|
|
if ( pArgs )
|
|
{
|
|
nCmdLen = pArgs - pCmd;
|
|
pArgs += strspn( pArgs, " \t" ); // skip to first char of first word
|
|
}
|
|
else
|
|
{
|
|
nCmdLen = strlen( pCmd );
|
|
pArgs = pCmd + strlen( pCmd ); // point at trailing 0 bytes
|
|
}
|
|
for( int i = 0; i < ARRAYSIZE( s_CmdTable ); i++ )
|
|
{
|
|
char const *pTblCmd = s_CmdTable[i].m_pCmdName;
|
|
if ( ( strlen( pTblCmd ) == nCmdLen ) &&
|
|
( memcmp( pTblCmd, pCmd, nCmdLen ) == 0 ) )
|
|
{
|
|
// found it
|
|
( *s_CmdTable[i].m_pCmdFn )( pArgs, idx );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SendString( "This server is password protected. Enter PASS <passwd> for access\n", idx );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void HandleDeadChildProcesses( void )
|
|
{
|
|
for(;;)
|
|
{
|
|
int nStatus;
|
|
pid_t nWait = waitpid( -1, &nStatus, WNOHANG );
|
|
if ( nWait > 0 )
|
|
{
|
|
// find the process that exited
|
|
CServerInstance *pFound = NULL;
|
|
int nFound = -1;
|
|
for( int i = 0; i < g_nNumChildInstances; i++ )
|
|
{
|
|
if ( g_pChildProcesses[i].m_nPid == nWait )
|
|
{
|
|
pFound = g_pChildProcesses + i;
|
|
nFound = i;
|
|
break;
|
|
}
|
|
}
|
|
if ( ! pFound )
|
|
{
|
|
Warning( "unknown child process %d exited?\n", nWait );
|
|
}
|
|
else
|
|
{
|
|
if ( WIFEXITED( nStatus ) )
|
|
{
|
|
Msg( "Child %d exited with status %d\n", nFound, WEXITSTATUS( nStatus ) );
|
|
}
|
|
if ( WIFSIGNALED( nStatus ) )
|
|
{
|
|
Msg( "Child %d aborted with signal %d\n", nFound, WTERMSIG( nStatus ) );
|
|
}
|
|
if ( WCOREDUMP( nStatus ) )
|
|
{
|
|
Msg( "Child wrote a core dump\n");
|
|
}
|
|
pFound->m_bRunning = false;
|
|
if ( pFound->m_nSocketToChild != -1 )
|
|
{
|
|
close( pFound->m_nSocketToChild );
|
|
pFound->m_nSocketToChild = -1;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
break; // no dead children
|
|
}
|
|
}
|
|
}
|
|
|
|
#define MAX_ACTIVE_PARENT_NETCONSOLE_SOCKETS 20
|
|
|
|
static void ServiceChildProcesses( void )
|
|
{
|
|
// for any children that aren't running (or not running yet), start them
|
|
pollfd pollFds[MAX_CHILD_PROCESSSES + 1 + MAX_ACTIVE_PARENT_NETCONSOLE_SOCKETS ];
|
|
int nPoll = 0;
|
|
int nNumRunning = 0;
|
|
for( int i = 0; i < g_nNumChildInstances; i++ )
|
|
{
|
|
if ( g_pChildProcesses[i].m_bRunning == false )
|
|
{
|
|
if (! s_bDelayedQuit )
|
|
{
|
|
int nSockets[2];
|
|
int nRslt = socketpair( AF_UNIX, SOCK_STREAM, 0, nSockets );
|
|
if ( nRslt != 0 )
|
|
{
|
|
Error( "socket pair returned error errno = %d\n", errno );
|
|
}
|
|
pid_t nChild = fork();
|
|
if ( nChild == 0 ) // are we the forked child?
|
|
{
|
|
//ResetBaseTime(); // start plat_float time over at 0 for precision
|
|
PerformCommandLineSubstitutions( i + 1 );
|
|
close( nSockets[1] );
|
|
engine->SetSubProcessID( i + 1, nSockets[0] );
|
|
g_nSubProcessId = i + 1;
|
|
RunServer( true );
|
|
syscall( SYS_exit, 0 ); // we are not going to perform a normal c++ exit. We _dont_ want to run destructors, etc.
|
|
}
|
|
else
|
|
{
|
|
g_pChildProcesses[i].m_nPid = nChild;
|
|
g_pChildProcesses[i].m_pszStatus[0] = 0;
|
|
g_pChildProcesses[i].m_bRunning = true;
|
|
close( nSockets[0] );
|
|
g_pChildProcesses[i].m_nSocketToChild = nSockets[1];
|
|
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
nNumRunning++;
|
|
}
|
|
if ( g_pChildProcesses[i].m_nSocketToChild != -1 )
|
|
{
|
|
pollFds[nPoll].fd = g_pChildProcesses[i].m_nSocketToChild;
|
|
pollFds[nPoll].events = POLLIN | POLLERR | POLLHUP;
|
|
pollFds[nPoll].revents = 0;
|
|
nPoll++;
|
|
}
|
|
}
|
|
if ( s_bDelayedQuit && ( nNumRunning == 0 ) )
|
|
{
|
|
_exit( 0 );
|
|
}
|
|
|
|
// now, wait for activity on any of our sockets or stdin
|
|
// pollFds[nPoll].fd = STDIN_FILENO;
|
|
// pollFds[nPoll].events = POLLIN;
|
|
// pollFds[nPoll].revents = 0;
|
|
// nPoll++;
|
|
if ( g_pParentProcessNetConsole && ( g_pParentProcessNetConsole->m_bActive ) )
|
|
{
|
|
pollFds[nPoll].fd = g_pParentProcessNetConsole->m_Socket.m_hListenSocket;
|
|
pollFds[nPoll].events = POLLIN;
|
|
pollFds[nPoll].revents = 0;
|
|
nPoll++;
|
|
|
|
int nCount = g_pParentProcessNetConsole->m_Socket.GetAcceptedSocketCount();
|
|
for( int i = 0; ( i < nCount ) && ( nPoll < ARRAYSIZE( pollFds ) ); i++ )
|
|
{
|
|
SocketHandle_t hSocket = g_pParentProcessNetConsole->m_Socket.GetAcceptedSocketHandle( i );
|
|
pollFds[nPoll].fd = hSocket;
|
|
pollFds[nPoll].events = POLLIN;
|
|
pollFds[nPoll].revents = 0;
|
|
nPoll++;
|
|
}
|
|
}
|
|
|
|
int nPollResult = poll( pollFds, nPoll, 10 * 1000 ); // wait up to 10 seconds. Could wait forever, really
|
|
|
|
// check for activity on the sockets from our children
|
|
int np = 0;
|
|
for( int i = 0; i < g_nNumChildInstances; i++ )
|
|
{
|
|
if ( g_pChildProcesses[i].m_nSocketToChild != -1 )
|
|
{
|
|
if ( pollFds[np].revents & POLLIN ) // data ready to read?
|
|
{
|
|
g_pChildProcesses[i].HandleSocketInput();
|
|
}
|
|
np++;
|
|
}
|
|
|
|
}
|
|
// see if any children have exited
|
|
HandleDeadChildProcesses();
|
|
|
|
g_pParentProcessNetConsole->RunFrame();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RunServerSubProcesses( int nNumChildren )
|
|
{
|
|
g_nNumChildInstances = nNumChildren;
|
|
g_pChildProcesses = new CServerInstance[g_nNumChildInstances];
|
|
g_pParentProcessNetConsole = new CParentProcessNetConsoleMgr;
|
|
|
|
while( ! s_bQuit )
|
|
{
|
|
ServiceChildProcesses();
|
|
}
|
|
_exit( 0 );
|
|
}
|
|
|
|
static bool DecodeParam( char const *pParamName, char const *pInput, char const **pOutPtr )
|
|
{
|
|
// if the left of the string matches pParamName, return the right of the string else return null
|
|
int nPLen = strlen( pParamName );
|
|
if ( memcmp( pParamName, pInput, nPLen ) == 0 )
|
|
{
|
|
*pOutPtr= pInput + nPLen;
|
|
}
|
|
else
|
|
{
|
|
*pOutPtr = NULL;
|
|
}
|
|
return ( *pOutPtr );
|
|
}
|
|
|
|
void CServerInstance::ProcessInputFromChild( void )
|
|
{
|
|
if ( m_pszInputBuffer[0] == '#' ) // spew?
|
|
{
|
|
puts( m_pszInputBuffer );
|
|
}
|
|
else
|
|
{
|
|
char *pSpace = strchr( m_pszInputBuffer, ' ' );
|
|
if ( pSpace )
|
|
{
|
|
*( pSpace++ ) = 0;
|
|
pSpace += strspn( pSpace, " \t" );
|
|
}
|
|
else
|
|
{
|
|
pSpace = m_pszInputBuffer + strlen( m_pszInputBuffer );
|
|
}
|
|
if ( !strcmp( m_pszInputBuffer, "status" ) )
|
|
{
|
|
CUtlStringList statusRecords;
|
|
V_SplitString( pSpace, ";", statusRecords );
|
|
for( int i = 0; i < statusRecords.Count(); i++ )
|
|
{
|
|
char const *pRecord = statusRecords[i];
|
|
char const *pParm;
|
|
if ( DecodeParam( "map=", pRecord, &pParm ) )
|
|
{
|
|
V_strncpy( m_pszMapName, pParm, sizeof( m_pszMapName ) );
|
|
}
|
|
else if ( DecodeParam( "players=", pRecord, &pParm ) )
|
|
{
|
|
m_nNumPlayers = atoi( pParm );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Warning("got unknown cmd %s args %s\n", m_pszInputBuffer, pSpace );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void CServerInstance::HandleSocketInput( void )
|
|
{
|
|
char *pDest = m_pszInputBuffer + m_nNumCharsInInputBuffer;
|
|
int nRead = recv( m_nSocketToChild, pDest, sizeof( m_pszInputBuffer ) - m_nNumCharsInInputBuffer, MSG_DONTWAIT );
|
|
if ( nRead > 0 )
|
|
{
|
|
m_nNumCharsInInputBuffer += nRead;
|
|
if ( m_pszInputBuffer[m_nNumCharsInInputBuffer - 1] == 0 )
|
|
{
|
|
ProcessInputFromChild();
|
|
m_nNumCharsInInputBuffer = 0;
|
|
}
|
|
if ( m_nNumCharsInInputBuffer == MAX_INPUT_FROM_CHILD )
|
|
m_nNumCharsInInputBuffer = 0;
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif //linux
|