/*
....[@@@..[@@@..............[@.................. MUD++ is a written from
....[@..[@..[@..[@..[@..[@@@@@....[@......[@.... scratch multi-user swords and
....[@..[@..[@..[@..[@..[@..[@..[@@@@@..[@@@@@.. sorcery game written in C++.
....[@......[@..[@..[@..[@..[@....[@......[@.... This server is an ongoing
....[@......[@..[@@@@@..[@@@@@.................. development project. All
................................................ contributions are welcome.
....Copyright(C).1995.Melvin.Smith.............. Enjoy.
------------------------------------------------------------------------------
Melvin Smith (aka Fusion) msmith@hom.net
MUD++ development mailing list mudpp@van.ml.org
------------------------------------------------------------------------------
main.cc
*/
// The game loop is here.
// Also the main objects are created here: server, players, areas loaded
// WARNING: This file is now a nightmare. I dont recommend even trying
// to comprehend it until John Olson and I clean it up. Most
// of the problem is bad organization on my part which didn't
// handle platform specific additions.
#include "config.h"
#include "io.h"
#include "string.h"
#include "server.h"
#include "room.h"
#include "pc.h"
#include "hash.h"
#include "llist.h"
#include "area.h"
#include "edit.h"
#include "help.h"
#include "env.h"
#include "cluster.h"
#include "global.h"
#include "properties.h"
#ifndef WIN32
#include <sys/time.h>
#include <sys/wait.h>
#include <sys/resource.h>
#include <sys/signal.h>
#else
#include <io.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <direct.h>
#include <process.h>
#include <winsock.h>
#ifdef __SC__
#include <sys/unistd.h>
#endif // SC
#endif // WIN32
const char * version = "\n\r\
MUD++ Cyberspace Server version 0.34 (non-commercial)\n\r\
Copyright(C) 1995-1997 Melvin Smith\n\n\r";
extern "C"
{
#ifdef ultrix
int getrlimit( int, struct rlimit * );
int setrlimit( int, struct rlimit * );
#endif
#ifdef sun
int getrlimit( int, struct rlimit * );
int setrlimit( int, const struct rlimit * );
#endif
}
// ****************************************
// Global variables
Server server;
String title = "Title screen failed to load. Check file.\n\r";
#if defined(CLIENT) || defined(SERVER)
Descriptor mmd_fd;
int mmd_messg_count;
#endif
char String::_argbuf[ 1024 ];
char * String::_argptr;
char * String::_argnext;
StringRep * String::repEmpty = new StringRep("");
LList<PC> pcs;
LList<PC> shellpcs;
LList<NPC> npcs;
LList<Area> areas;
LList<Object> objects;
LList<Action> actions;
LList<Help> helps_ll;
HashTable<Help> helps_ht;
bool DOWN = false;
bool REBOOT = false;
bool CONSOLE = false;
int pulse = PULSE_PER_SEC;
int action_pulse = ACTION_PULSE;
long tot_login = 0;
long cur_login = 0;
long max_login = 0;
bool reap_shells = false;
#ifdef WIN32
WSADATA WinsockInfo; // contains available version info, max sockets...
#endif
// ****************************************
void loadAreas();
void loadSocials();
void loadTitle();
void buildWorld();
void loadPersistentData();
void savePersistentData();
void loadGuilds();
bool Nanny( PC *, const String & str = "" );
int startDaemon( const char *, const char * );
void IWannaBeADaemonToo();
void reboot();
void init_vm_type_table();
bool link_vmachine();
void mudppFinalizer();
void initProperties();
#ifndef WIN32
RETSIGTYPE sigHandler( int );
int installSigHandlers();
#endif
// Borland C++ Builder creates a main module with its own style, so call
// real_main() from there. May want to add other Win32 compilers
// which do similar things.
#ifndef __BORLANDC__
int real_main( int, char ** );
int main( int argc, char **argv )
{
return real_main( argc, argv );
}
#endif
// Real main program
int real_main( int argc , char ** argv)
{
#ifndef WIN32
pid_t pid;
#endif
Cout << version;
atexit(mudppFinalizer);
// Parse command line arguments
if( argc > 1 )
{
bool err = false;
argv++;
while( *argv )
{
char * arg = *argv;
if( *arg != '-' )
{
err = true;
Cout << "Invalid switch: " << arg << endl;
}
if( !strcmp( arg+1, "console" ) )
CONSOLE = true;
argv++;
}
if( err )
{
Cout << "Usage: mud++ [-console]\n";
exit(0);
}
}
// We need to do it early, to allow daemon/not daemon property
Cout << "Loading properties ... ";
initProperties();
// WIN32 sockets are BSD 4.3 flavor with certain windows requirements
// such as code to kickoff the winsock.dll and version checking.
#ifdef WIN32
int iWStartError; // if 0 then no error and winsock is ok
// 0x0101 = version 1.1
iWStartError = WSAStartup( 0x0101, &WinsockInfo);
if (iWStartError != 0)
switch (iWStartError)
{
case WSASYSNOTREADY:
{
Cout << "WSAStartup failed:System not ready for network communications.\n\n\r";
return (0);
}
case WSAVERNOTSUPPORTED:
case WSAEINVAL:
{
Cout << "WSAStartup failed:Version requested is not available in this winsock.dll.\n\n\r";
return(0);
}
}
else
/* right now this spits our version info that is difficult to read, but info is correct */
{
Cout << "Starting Winsock with version " << WinsockInfo.wVersion << ".\n\r";
Cout << "Winsock Manuf Data: " << WinsockInfo.szDescription << "\n\r";
server.setmaxdesc(WinsockInfo.iMaxSockets);
}
#endif
#ifndef WIN32
chdir( AREA_DIR );
umask( ~( S_IRUSR | S_IWUSR ) );
#else
_chdir( AREA_DIR );
_umask( ~( _S_IREAD | _S_IWRITE ) );
#endif
// Raise the soft limit on coredump size to the max (hard limit)
// if it isn't already. This isn't POSIX but it is SYSV and 4.3+BSD
// so it should compile. If it doesn't, delete these lines and
// invoke mud++ through a csh or ksh after giving the command
// 'unlimit coredumpsize', otherwise you may not get a core for debugging.
#ifndef WIN32
#if !defined(__CYGWIN32__)
struct rlimit rl;
getrlimit( RLIMIT_CORE, &rl );
rl.rlim_cur = rl.rlim_max;
setrlimit( RLIMIT_CORE, &rl );
static Property * demonize =0;
UseProperty( &demonize, "fork_at_startup", PROP_BOOL, "true", NULL, false);
if ( (!CONSOLE) && demonize->getBool() )
{
// Zap off into daemon land.
if( ( pid = fork() ) < 0 )
{
perror( "main:fork" );
mudpp_exit( 0 );
}
else if( pid != 0 ) // parent exits
mudpp_exit(0);
}
else
Cout << "Running in console mode.\n";
// Get our pid now
if( ( pid = getpid() ) < 0 )
perror( "getpid" );
// Create a new session.
setsid();
if( installSigHandlers() < 0 )
{
perror( "installing signal handlers" );
mudpp_exit(0);
}
#else // CYGWIN32
if ( ( pid = getpid() ) < 0 )
perror( "getpid" );
#endif // CYGWIN32
#endif // WIN32
Cout << "Looking for reboot.tab\n";
InputFile tab( "./reboot.tab" );
// This is important!
// If we are already a daemon we DON'T want to go through the
// process of becoming a daemon again or stuff gets screwed.
// For example, the first time through we close stdin, so it
// is usually recycled to the Server desc port, so we don't
// want to close it again in case of a reboot. This was a bug
// in early versions ( 0.10 and back )
#ifndef WIN32
if( !CONSOLE && !tab )
IWannaBeADaemonToo();
Cout << "Main server pid [" << pid << "]\n";
#else
Cout << "Main server is this application instance.\n";
#endif
#if 0
#if defined(CLIENT) || defined(SERVER)
#if defined(CLIENT)
mmd_fd = startDaemon( "mmdaemon", "-c" );
#else
mmd_fd = startDaemon( "mmdaemon", "-s" );
#endif
if( mmd_fd < 0 )
{
Cout << "Could not start mmdaemon.\n";
mudpp_exit(0);
}
Cout << "mmdaemon started.\n";
#endif
#endif
// Init table type - temporary hack, check vm/types.cc
init_vm_type_table();
// Read ../etc/HOSTS even if we aren't a cluster.
// To change the port for the server edit this file and
// change the 'local' entry.
if( loadHostsFile() < 0 )
{
Cout << "Error loading hosts file.\n";
mudpp_exit(0);
}
// See if we have been spawned by another instance.
// If so, inherit all open descriptors.
PC *pc;
String str;
Cout << "Initialization of Garbage Collector.\n";
GC_init();
Cout << "VMachine data linking...";
if( link_vmachine( ) )
Cout << "linked successfully.\n";
else
Cout << "error occured. VMachine disabled.\n";
loadTitle();
Cout << "Loading help Index.\n";
loadHelps();
Cout << "Loading socials database.\n";
loadSocials();
Cout << "Loading guilds.\n";
loadGuilds();
Cout << "Loading areas.\n";
loadAreas();
Cout << "Linking world.\n";
buildWorld();
Cout << "Parsing hints file ... loaded " << loadHints() << " entries.\n";
Cout << "Loading persistent data.\n";
loadPersistentData();
if( !tab )
{
Cout << "No reboot.tab, this is a fresh boot.\n\r";
server.boot( local.getPlayerPort() );
}
else
{
char name[ 256 ];
char host[ 256 ];
int desc;
int port;
Cout << "Live reboot, loading descriptor data.\n\r";
// For now I assume that first entry is the socket control
// entry so I dont examine it.
tab.getstring( name ); // name
tab.getstring( host ); // hostname ignored for this entry
port = tab.getnum();
desc = tab.getnum();
server.boot( port, desc );
Cout << "Reloading server on TCP port " << port << ':' << desc << endl;
Cout << "Restoring old connections.\n";
for( ; ; )
{
if( tab.eof() )
break;
tab.getstring( name );
if( tab.eof() )
break;
tab.getstring( host );
if( tab.eof() )
break;
port = tab.getnum();
desc = tab.getnum();
Cout << name << ':' << host << ':' << port << ':' << desc << endl;
Socket *newSock = new Socket( host, port, desc );
server.addSock( newSock );
pc = new PC( &server, newSock, name );
pcs.add( pc );
newSock->write( "\n\rSuccessful Reboot.\n\r" );
// Here I got lazy. The potential is here to go ahead
// and load the player -and- the mob he was fighting
// if there was one, along with mobs old hp/mana
// For now this is still better than closing connection.
pc->setState( STATE_GET_NAME );
str = name;
Nanny( pc, str );
}
Cout << "reboot.tab loaded.\n";
tab.close();
remove( "./reboot.tab" );
}
server.useNameServer();
String commd;
#ifdef WIN32
struct _timeb timelast;
struct _timeb timenow;
unsigned short millidiff;
time_t secdiff;
_ftime(&timelast); // get the time for comparisons
#endif // WIN32
// OK here goes the game loop
while( !DOWN )
{
// High precision timer to conserve CPU
#ifndef WIN32
server.sleep( PULSE );
#else
// sleeping is a waste of the multithreading feature of WIN32 OS
_ftime(&timenow); // get the time for this game loop
secdiff = timenow.time - timelast.time; // difference in seconds
millidiff = timenow.millitm - timelast.millitm; // diff in milliseconds
if ( (secdiff != 0) || (millidiff > (1000 * PULSE)) )
{
_ftime(&timelast);
#endif /* WIN32 */
// Pulse
if( !pulse-- )
{
if( reap_shells )
{
Cout << "DEBUG: Reaping shell.\n";
// Careful with the reap_shells var, there is
// some juggling here. If the SIGCHLD handler toggles
// reap_shells but the pipe isn't ready yet, it will
// skip over it. What I'm doing now is while there
// are PCs in the shell list, once 1 has exited I
// I try to reap shells every pulse until all are gone.
// There are other ways around this race condition.
shellpcs.reset();
if( !shellpcs.peek() )
reap_shells = false;
else while( ( pc = shellpcs.peek() ) )
{
if( pc->getPipeIn()->canRead() )
{
pc->closePipeIn();
shellpcs.remove( pc );
pcs.addTop( pc );
shellpcs.reset();
if( !shellpcs.peek() )
reap_shells = false;
pc->getSocket()->write( "\n\rMUD IO resumed.\n\r" );
if( pc->getEditor() )
pc->getEditor()->command( "" );
continue;
}
shellpcs.next();
}
}
heartbeat();
pulse = PULSE_PER_SEC;
}
// Actions need to be updated sooner than game "ticks"
if( !action_pulse-- )
{
action_heartbeat();
action_pulse = ACTION_PULSE;
}
#ifdef WIN32
} // end of code only called once per main PULSE
#endif /* WIN32 */
// Check for new connections
if( server.newConnection() )
{
Socket * sock = server.accept();
if( sock )
{
sock->nonBlock();
pc = new PC( &server, sock, "" );
Cout << "New connection from " << sock->getHostName() << endl;
pc->setState( STATE_INIT );
sock->write( title.chars() );
pcs.addTop( pc );
// If Nanny returns false, pc pointer is no longer valid
if( Nanny( pc ) )
pc->flush();
else
Cout << "Nanny terminated new player.\n";
}
}
// Poll all active descriptors
server.poll();
// Set the current pointer back to head of player list
pcs.reset();
while( ( pc = pcs.peek() ) )
{
// Keep this at top of loop, since continue is used
pcs.next();
// Check for guys to boot.
if( pc->getState() == STATE_BOOT )
{
// Make sure he gets any pending text
// This could be a disconnect, slay, etc. so inform him
if( pc->getSocket() )
{
pc->flush();
server.remove( pc->getSocket() );
pc->getSocket()->close();
Cout << "PLR_BOOT:Closing link to " << pc->getName() << "\n";
}
if( pc->inRoom() )
{
pc->inRoom()->rmCharInv( pc );
cur_login--;
}
pcs.remove( pc );
pc->fordelete();
continue;
}
if( !pc->getSocket() )
continue;
// Boot sockets with exceptional condition
// Need to add handler instead of booting.
if( server.error( pc->getSocket() ) )
{
str.clr();
str << "SOCK_ERR:Closing link to " << pc->getName();
wizLog( str, pc );
server.remove( pc->getSocket() );
pc->getSocket()->close();
pc->setSocket( 0 ); // NULL out the socket pointer
continue;
}
// Read socket if ready
if( server.canRead( pc->getSocket() ) )
{
pc->readInput();
if( pc->getSocket()->eof() )
{
if( (bool)pc->getName() )
{
str.clr();
str << "Closing link to " << pc->getName();
wizLog( str, pc );
}
else pc->setState( STATE_BOOT );
server.remove( pc->getSocket() );
pc->getSocket()->close();
pc->setSocket( 0 );
continue;
}
}
if( !pc->inBuf() && !pc->outBuf() )
continue;
else if( pc->pagePending() )
{
if( pc->outBuf() )
{
pc->flush();
pc->putPrompt();
continue;
}
if( !pc->getNextCommand() )
continue;
pc->page( pc->getCommand() );
pc->putPrompt();
pc->clrIdle();
}
else if( pc->getEditor() )
{
if( !pc->getNextCommand() )
continue;
pc->clrIdle();
if( (pc->getCommand() == "quit") || (pc->getCommand() == "exit") )
{
pc->quitEditor();
}
else
{
// Need to check return status and pass command on
// to regular interpreter if not recognized
pc->getEditor()->command( pc->getCommand() );
}
pc->flush();
pc->putPrompt();
continue;
}
else if( pc->inBuf() )
{
if( pc->getState() >= STATE_INIT
&& pc->getState() < STATE_PLAYING )
{
if( !pc->getNextCommand() )
continue;
pc->clrIdle();
if( !Nanny( pc, pc->getCommand() ) )
continue;
if( pc->pagePending( ) )
pc->page( "" );
}
else
{
if( !pc->getNextCommand() )
continue;
pc->command();
if( pc->outBuf() )
pc->flush( );
if( pc->pagePending( ) )
pc->page( "" );
pc->putPrompt( );
pc->clrIdle();
}
}
// Process some buffered output
if( pc->outBuf() )
{
pc->flush();
pc->putPrompt();
}
}
}
// Bottom of game loop
savePersistentData();
if( REBOOT )
reboot(); // in WIN32 reboot will _exitthread
Cout << "MUD++ daemon shutdown normally.\n";
mudpp_exit(0);
// shouldn't get here
return 0;
} // end of main for UN*X, end of daemonThread for WIN32
void reboot()
{
PC * pc;
OutputFile out( "reboot.tab" );
// Write the control socket descriptor so the Server object can
// pick it up on way back in after execl()
Cout << "Saving server on TCP port " << server.getPort() << ':'
<< server.getDescriptor() << endl;
out << "mudpp.1~" << "localhost~"
<< server.getPort() << " "
<< server.getDescriptor();
// Write each player name and descriptor so mud++ can reload
// the player files after execl()
pcs.reset();
while( ( pc = pcs.peek() ) )
{
pcs.next();
if( !pc->getSocket() )
continue;
out << "\n"
<< pc->getName() << '~'
<< pc->getSocket()->getHostName() << '~'
<< pc->getSocket()->getPort() << " "
<< pc->getSocket()->getDescriptor();
}
out.close();
execl( "../src/mud++", "mud++", (char*)0 );
perror( "execl" );
mudpp_exit(0);
}
void loadAreas()
{
String areaKey;
char filename[256];
char buf[256];
Area *area;
InputFile in( AREA_FILE );
while( !in.eof() )
{
in.getline( filename );
Cout << "Area-[" << filename << "]" << endl;
if( !*filename )
break;
InputFile inArea( filename );
if( inArea )
{
inArea.getword( buf );
if( *buf == 'A' )
{
areaKey = inArea.getword( buf );
if( !isalpha( areaKey[0] ) )
inArea.error( "Invalid or missing Area index scope" );
area = new Area( areaKey );
area->readFrom( inArea );
areas.addTop( area );
}
else
{
Cout << "Area file [" << filename << "] has no area header.\n";
mudpp_exit(0);
}
}
else
{
Cout << "Error opening area file [" << filename << "]\n";
mudpp_exit(0);
}
in.skipwhite();
}
Cout <<"\nDatabase booted sucessfully.\n";
}
void loadTitle()
{
InputFile in( "../etc/title" );
if( !in )
return;
title = in.getBuf();
in.close();
}
// Link rooms, do initial repop
void buildWorld()
{
Area *area;
for_each( areas, area )
{
area->hardLink();
}
// 2 seperate loops in case I decide to do something funky with doors.
for_each( areas, area )
{
area->repop();
}
}
// Close terminal and open the log like a good daemon
void IWannaBeADaemonToo()
{
const char * fail = "Failed to redirect STDOUT/STDERR to log.\n\r";
Cout << "Logging started.\n";
static Property * logToFile =0;
UseProperty( &logToFile, "log_to_file", PROP_BOOL, "true", NULL, false );
if ( !logToFile->getBool() )
return;
int logfd = open( "../log/server.log", O_WRONLY|O_CREAT|O_APPEND, 0777 );
if( logfd < 0 )
{
perror( "startLogging:open" );
mudpp_exit(0);
}
close( STDOUT_FILENO );
close( STDERR_FILENO );
if( dup( logfd ) != STDOUT_FILENO || dup( logfd ) != STDERR_FILENO )
{
write( logfd, fail, strlen( fail ) );
mudpp_exit(0);
}
// Now all Cout << and perror() will go into the log.
close( logfd );
close( STDIN_FILENO ); // We don't need it anymore
}
// MUD++ daemons communicate with UNIX DOMAIN sockets which are generally
// very fast, slightly more than the cost of 2 memcpys which is what we
// want here. Will change to shared memory later.
// TODO WIN32
// if this subroutine is to be used then it could be the daemonThread
// starter function, in any event if it is implemented under WIN32 as
// a thread it does not need pipes.
#if !defined(WIN32) && !defined(__CYGWIN32__)
int startDaemon( const char * name, const char * arg )
{
char path[256];
int pair[2];
pid_t pid;
if( socketpair( AF_UNIX, SOCK_STREAM, 0, pair ) < 0 )
{
perror( "startDaemon:socketpair" );
return -1;
}
pid = fork();
if( pid > 0 )
{
close( pair[1] );
return pair[0];
}
else if( pid == 0 )
{
close( pair[0] );
if( dup2( pair[1], 0 ) < 0 || dup2( pair[1], 1 ) < 0
|| dup2( pair[1], 2 ) < 0 )
mudpp_exit(0);
strcpy( path, "../src/" );
strcat( path, name );
execl( path, name, arg, (char*)0 );
Cout << "ERR: exec daemon failed.\n";
mudpp_exit(0);
}
return -1;
}
#endif /* WIN32 && CYGWIN32 */
#ifndef WIN32
RETSIGTYPE defaultSigHandler( int sig )
{
pid_t child_pid;
if( sig == SIGCHLD )
{
int status;
if( ( child_pid = wait( &status ) ) < 0 )
{
return;
// To get system("gzip -d") working I had to comment this
// is it needed for some serious debugging ??
//perror( "wait" );
//mudpp_exit(0);
}
#ifdef DEBUG
Cout << "Child [" << child_pid << "] exited.\n";
#endif
reap_shells = true;
return;
}
else if( sig == SIGINT )
{
Cout << "Caught SIGINT. Shutting down.\n";
exit(0);
}
else if( sig == SIGHUP )
{
Cout << "Received SIGHUP. Rebooting.\n";
reboot();
}
else abort();
}
int installSigHandlers()
{
#ifndef SA_RESTART
#define SA_RESTART 0
#endif
static struct sigaction sa;
sigemptyset( &sa.sa_mask );
sa.sa_flags = SA_RESTART;
sa.sa_handler = defaultSigHandler;
if( sigaction( SIGINT, &sa, 0 ) < 0 )
return -1;
if( sigaction( SIGTERM, &sa, 0 ) < 0 )
return -1;
#if !defined(CLIENT)
if( sigaction( SIGCHLD, &sa, 0 ) < 0 )
return -1;
#endif
return 0;
}
#endif /* WIN32 */
void mudppFinalizer()
{
Cout.flush();
}