/* ....[@@@..[@@@..............[@.................. 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(); }