summaryrefslogtreecommitdiff
path: root/engine/sv_log.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /engine/sv_log.cpp
downloadarchived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.tar.xz
archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.zip
Diffstat (limited to 'engine/sv_log.cpp')
-rw-r--r--engine/sv_log.cpp897
1 files changed, 897 insertions, 0 deletions
diff --git a/engine/sv_log.cpp b/engine/sv_log.cpp
new file mode 100644
index 0000000..2f7ed27
--- /dev/null
+++ b/engine/sv_log.cpp
@@ -0,0 +1,897 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $Header: $
+// $NoKeywords: $
+//=============================================================================//
+
+#include "server_pch.h"
+#include <time.h>
+#include "server.h"
+#include "sv_log.h"
+#include "filesystem.h"
+#include "filesystem_engine.h"
+#include "tier0/vcrmode.h"
+#include "sv_main.h"
+#include "tier0/icommandline.h"
+#include <proto_oob.h>
+#include "GameEventManager.h"
+#include "netadr.h"
+#include "zlib/zlib.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+static ConVar sv_logsdir( "sv_logsdir", "logs", FCVAR_ARCHIVE, "Folder in the game directory where server logs will be stored." );
+static ConVar sv_logfile( "sv_logfile", "1", FCVAR_ARCHIVE, "Log server information in the log file." );
+static ConVar sv_logflush( "sv_logflush", "0", FCVAR_ARCHIVE, "Flush the log file to disk on each write (slow)." );
+static ConVar sv_logecho( "sv_logecho", "1", FCVAR_ARCHIVE, "Echo log information to the console." );
+static ConVar sv_log_onefile( "sv_log_onefile", "0", FCVAR_ARCHIVE, "Log server information to only one file." );
+static ConVar sv_logbans( "sv_logbans", "0", FCVAR_ARCHIVE, "Log server bans in the server logs." ); // should sv_banid() calls be logged in the server logs?
+static ConVar sv_logsecret( "sv_logsecret", "0", 0, "If set then include this secret when doing UDP logging (will use 0x53 as packet type, not usual 0x52)" );
+
+static ConVar sv_logfilename_format( "sv_logfilename_format", "", FCVAR_ARCHIVE, "Log filename format. See strftime for formatting codes." );
+static ConVar sv_logfilecompress( "sv_logfilecompress", "0", FCVAR_ARCHIVE, "Gzip compress logfile and rename to logfilename.log.gz on close." );
+
+CLog g_Log; // global Log object
+
+CON_COMMAND( log, "Enables logging to file, console, and udp < on | off >." )
+{
+ if ( args.ArgC() != 2 )
+ {
+ ConMsg( "Usage: log < on | off >\n" );
+
+ if ( g_Log.IsActive() )
+ {
+ bool bHaveFirst = false;
+
+ ConMsg( "currently logging to: " );
+
+ if ( sv_logfile.GetInt() )
+ {
+ ConMsg( "file" );
+ bHaveFirst = true;
+ }
+
+ if ( sv_logecho.GetInt() )
+ {
+ if ( bHaveFirst )
+ {
+ ConMsg( ", console" );
+ }
+ else
+ {
+ ConMsg( "console" );
+ bHaveFirst = true;
+ }
+ }
+
+ if ( g_Log.UsingLogAddress() )
+ {
+ if ( bHaveFirst )
+ {
+ ConMsg( ", udp" );
+ }
+ else
+ {
+ ConMsg( "udp" );
+ bHaveFirst = true;
+ }
+ }
+
+ if ( !bHaveFirst )
+ {
+ ConMsg( "no destinations! (file, console, or udp)\n" );
+ ConMsg( "check \"sv_logfile\", \"sv_logecho\", and \"logaddress_list\"" );
+ }
+
+ ConMsg( "\n" );
+ }
+ else
+ {
+ ConMsg( "not currently logging\n" );
+ }
+ return;
+ }
+
+ if ( !Q_stricmp( args[1], "off" ) || !Q_stricmp( args[1], "0" ) )
+ {
+ if ( g_Log.IsActive() )
+ {
+ g_Log.Close();
+ g_Log.SetLoggingState( false );
+ ConMsg( "Server logging disabled.\n" );
+ }
+ }
+ else if ( !Q_stricmp( args[1], "on" ) || !Q_stricmp( args[1], "1" ) )
+ {
+ g_Log.SetLoggingState( true );
+ ConMsg( "Server logging enabled.\n" );
+ g_Log.Open();
+ }
+ else
+ {
+ ConMsg( "log: unknown parameter %s, 'on' and 'off' are valid\n", args[1] );
+ }
+}
+
+// changed log_addaddress back to logaddress_add to be consistent with GoldSrc
+CON_COMMAND( logaddress_add, "Set address and port for remote host <ip:port>." )
+{
+ netadr_t adr;
+ const char *pszIP, *pszPort;
+
+ if ( args.ArgC() != 4 && args.ArgC() != 2 )
+ {
+ ConMsg( "Usage: logaddress_add ip:port\n" );
+ return;
+ }
+
+ pszIP = args[1];
+
+ if ( args.ArgC() == 4 )
+ {
+ pszPort = args[3];
+ }
+ else
+ {
+ pszPort = Q_strstr( pszIP, ":" );
+
+ // if we have "IP:port" as one argument inside quotes
+ if ( pszPort )
+ {
+ // add one to remove the :
+ pszPort++;
+ }
+ else
+ {
+ // default port
+ pszPort = "27015";
+ }
+ }
+
+ if ( !Q_atoi( pszPort ) )
+ {
+ ConMsg( "logaddress_add: must specify a valid port\n" );
+ return;
+ }
+
+ if ( !pszIP || !pszIP[0] )
+ {
+ ConMsg( "logaddress_add: unparseable address\n" );
+ return;
+ }
+
+ char szAdr[32];
+ Q_snprintf( szAdr, sizeof( szAdr ), "%s:%s", pszIP, pszPort );
+
+ if ( NET_StringToAdr( szAdr, &adr ) )
+ {
+ if ( g_Log.AddLogAddress( adr ) )
+ {
+ ConMsg( "logaddress_add: %s\n", adr.ToString() );
+ }
+ else
+ {
+ ConMsg( "logaddress_add: %s is already in the list\n", adr.ToString() );
+ }
+ }
+ else
+ {
+ ConMsg( "logaddress_add: unable to resolve %s\n", szAdr );
+ }
+}
+
+CON_COMMAND( logaddress_delall, "Remove all udp addresses being logged to" )
+{
+ g_Log.DelAllLogAddress();
+}
+
+CON_COMMAND( logaddress_del, "Remove address and port for remote host <ip:port>." )
+{
+ netadr_t adr;
+ const char *pszIP, *pszPort;
+
+ if ( args.ArgC() != 4 && args.ArgC() != 2 )
+ {
+ ConMsg( "Usage: logaddress_del ip:port\n" );
+ return;
+ }
+
+ pszIP = args[1];
+
+ if ( args.ArgC() == 4 )
+ {
+ pszPort = args[3];
+ }
+ else
+ {
+ pszPort = Q_strstr( pszIP, ":" );
+
+ // if we have "IP:port" as one argument inside quotes
+ if ( pszPort )
+ {
+ // add one to remove the :
+ pszPort++;
+ }
+ else
+ {
+ // default port
+ pszPort = "27015";
+ }
+ }
+
+ if ( !Q_atoi( pszPort ) )
+ {
+ ConMsg( "logaddress_del: must specify a valid port\n" );
+ return;
+ }
+
+ if ( !pszIP || !pszIP[0] )
+ {
+ ConMsg( "logaddress_del: unparseable address\n" );
+ return;
+ }
+
+ char szAdr[32];
+ Q_snprintf( szAdr, sizeof( szAdr ), "%s:%s", pszIP, pszPort );
+
+ if ( NET_StringToAdr( szAdr, &adr ) )
+ {
+ if ( g_Log.DelLogAddress( adr ) )
+ {
+ ConMsg( "logaddress_del: %s\n", adr.ToString() );
+ }
+ else
+ {
+ ConMsg( "logaddress_del: address %s not found in the list\n", adr.ToString() );
+ }
+ }
+ else
+ {
+ ConMsg( "logaddress_del: unable to resolve %s\n", szAdr );
+ }
+}
+
+CON_COMMAND( logaddress_list, "List all addresses currently being used by logaddress." )
+{
+ g_Log.ListLogAddress();
+}
+
+CLog::CLog()
+{
+ Reset();
+}
+
+CLog::~CLog()
+{
+
+}
+
+void CLog::Reset( void ) // reset all logging streams
+{
+ m_LogAddresses.RemoveAll();
+
+ m_hLogFile = FILESYSTEM_INVALID_HANDLE;
+ m_LogFilename = NULL;
+
+ m_bActive = false;
+ m_flLastLogFlush = realtime;
+ m_bFlushLog = false;
+#ifndef _XBOX
+ if ( CommandLine()->CheckParm( "-flushlog" ) )
+ {
+ m_bFlushLog = true;
+ }
+#endif
+}
+
+void CLog::Init( void )
+{
+ Reset();
+
+ // listen to these events
+ g_GameEventManager.AddListener( this, "server_spawn", true );
+ g_GameEventManager.AddListener( this, "server_shutdown", true );
+ g_GameEventManager.AddListener( this, "server_cvar", true );
+ g_GameEventManager.AddListener( this, "server_message", true );
+ g_GameEventManager.AddListener( this, "server_addban", true );
+ g_GameEventManager.AddListener( this, "server_removeban", true );
+}
+
+void CLog::Shutdown()
+{
+ Close();
+ Reset();
+ g_GameEventManager.RemoveListener( this );
+}
+
+void CLog::SetLoggingState( bool state )
+{
+ m_bActive = state;
+}
+
+void CLog::RunFrame()
+{
+ if ( m_bFlushLog && m_hLogFile != FILESYSTEM_INVALID_HANDLE && ( realtime - m_flLastLogFlush ) > 1.0f )
+ {
+ m_flLastLogFlush = realtime;
+ g_pFileSystem->Flush( m_hLogFile );
+ }
+}
+
+bool CLog::AddLogAddress(netadr_t addr)
+{
+ int i = 0;
+
+ for ( i = 0; i < m_LogAddresses.Count(); ++i )
+ {
+ if ( m_LogAddresses.Element(i).CompareAdr(addr, false) )
+ {
+ // found!
+ break;
+ }
+ }
+
+ if ( i < m_LogAddresses.Count() )
+ {
+ // already in the list
+ return false;
+ }
+
+ m_LogAddresses.AddToTail( addr );
+ return true;
+}
+
+bool CLog::DelLogAddress(netadr_t addr)
+{
+ int i = 0;
+
+ for ( i = 0; i < m_LogAddresses.Count(); ++i )
+ {
+ if ( m_LogAddresses.Element(i).CompareAdr(addr, false) )
+ {
+ // found!
+ break;
+ }
+ }
+
+ if ( i < m_LogAddresses.Count() )
+ {
+ m_LogAddresses.Remove(i);
+ return true;
+ }
+
+ return false;
+}
+
+void CLog::ListLogAddress( void )
+{
+ netadr_t *pElement;
+ const char *pszAdr;
+ int count = m_LogAddresses.Count();
+
+ if ( count <= 0 )
+ {
+ ConMsg( "logaddress_list: no addresses in the list\n" );
+ }
+ else
+ {
+ if ( count == 1 )
+ {
+ ConMsg( "logaddress_list: %i entry\n", count );
+ }
+ else
+ {
+ ConMsg( "logaddress_list: %i entries\n", count );
+ }
+
+ for ( int i = 0 ; i < count ; ++i )
+ {
+ pElement = &m_LogAddresses.Element(i);
+ pszAdr = pElement->ToString();
+
+ ConMsg( "%s\n", pszAdr );
+ }
+ }
+}
+
+bool CLog::UsingLogAddress( void )
+{
+ return ( m_LogAddresses.Count() > 0 );
+}
+
+void CLog::DelAllLogAddress( void )
+{
+ if ( m_LogAddresses.Count() > 0 )
+ {
+ ConMsg( "logaddress_delall: all addresses cleared\n" );
+ m_LogAddresses.RemoveAll();
+ }
+ else
+ {
+ ConMsg( "logaddress_delall: no addresses in the list\n" );
+ }
+}
+
+/*
+==================
+Log_PrintServerVars
+
+==================
+*/
+void CLog::PrintServerVars( void )
+{
+ const ConCommandBase *var; // Temporary Pointer to cvars
+
+ if ( !IsActive() )
+ {
+ return;
+ }
+
+ Printf( "server cvars start\n" );
+ // Loop through cvars...
+ for ( var= g_pCVar->GetCommands() ; var ; var=var->GetNext() )
+ {
+ if ( var->IsCommand() )
+ continue;
+
+ if ( !( var->IsFlagSet( FCVAR_NOTIFY ) ) )
+ continue;
+
+ Printf( "\"%s\" = \"%s\"\n", var->GetName(), ((ConVar*)var)->GetString() );
+ }
+
+ Printf( "server cvars end\n" );
+}
+
+bool CLog::IsActive( void )
+{
+ return m_bActive;
+}
+
+/*
+==================
+Log_Printf
+
+Prints a log message to the server's log file, console, and possible a UDP address
+==================
+*/
+void CLog::Printf( const char *fmt, ... )
+{
+ va_list argptr;
+ static char string[1024];
+
+ if ( !IsActive() )
+ {
+ return;
+ }
+
+ va_start ( argptr, fmt );
+ Q_vsnprintf ( string, sizeof( string ), fmt, argptr );
+ va_end ( argptr );
+
+ Print( string );
+}
+
+void CLog::Print( const char * text )
+{
+ if ( !IsActive() || !text || !text[0] )
+ {
+ return;
+ }
+
+ tm today;
+ VCRHook_LocalTime( &today );
+
+ if ( Q_strlen( text ) > 1024 )
+ {
+ // Spew a warning, but continue and print the truncated stuff we have.
+ DevMsg( 1, "CLog::Print: string too long (>1024 bytes)." );
+ }
+
+ static char string[1100];
+ V_sprintf_safe( string, "L %02i/%02i/%04i - %02i:%02i:%02i: %s",
+ today.tm_mon+1, today.tm_mday, 1900 + today.tm_year,
+ today.tm_hour, today.tm_min, today.tm_sec, text );
+
+ // Echo to server console
+ if ( sv_logecho.GetInt() )
+ {
+ ConMsg( "%s", string );
+ }
+
+ // Echo to log file
+ if ( sv_logfile.GetInt() && ( m_hLogFile != FILESYSTEM_INVALID_HANDLE ) )
+ {
+ g_pFileSystem->FPrintf( m_hLogFile, "%s", string );
+ if ( sv_logflush.GetBool() )
+ {
+ g_pFileSystem->Flush( m_hLogFile );
+ }
+ }
+
+ // Echo to UDP port
+ if ( m_LogAddresses.Count() > 0 )
+ {
+ // out of band sending
+ for ( int i = 0 ; i < m_LogAddresses.Count() ; i++ )
+ {
+ if ( sv_logsecret.GetInt() != 0 )
+ NET_OutOfBandPrintf(NS_SERVER, m_LogAddresses.Element(i), "%c%s%s", S2A_LOGSTRING2, sv_logsecret.GetString(), string );
+ else
+ NET_OutOfBandPrintf(NS_SERVER, m_LogAddresses.Element(i), "%c%s", S2A_LOGSTRING, string );
+
+ }
+ }
+}
+
+void CLog::FireGameEvent( IGameEvent *event )
+{
+ if ( !IsActive() )
+ return;
+
+ // log server events
+
+ const char * name = event->GetName();
+ if ( !name || !name[0])
+ return;
+
+ if ( Q_strcmp(name, "server_spawn") == 0 )
+ {
+ Printf( "Started map \"%s\" (CRC \"%s\")\n", sv.GetMapName(), MD5_Print( sv.worldmapMD5.bits, MD5_DIGEST_LENGTH ) );
+ }
+
+ else if ( Q_strcmp(name, "server_shutdown") == 0 )
+ {
+ Printf( "server_message: \"%s\"\n", event->GetString("reason") );
+ }
+
+ else if ( Q_strcmp(name, "server_cvar") == 0 )
+ {
+ Printf( "server_cvar: \"%s\" \"%s\"\n", event->GetString("cvarname"), event->GetString("cvarvalue") );
+ }
+
+ else if ( Q_strcmp(name, "server_message") == 0 )
+ {
+ Printf( "server_message: \"%s\"\n", event->GetString("text") );
+ }
+
+ else if ( Q_strcmp(name, "server_addban") == 0 )
+ {
+ if ( sv_logbans.GetInt() > 0 )
+ {
+ const int userid = event->GetInt( "userid" );
+ const char *pszName = event->GetString( "name" );
+ const char *pszNetworkid = event->GetString( "networkid" );
+ const char *pszIP = event->GetString( "ip" );
+ const char *pszDuration = event->GetString( "duration" );
+ const char *pszCmdGiver = event->GetString( "by" );
+ const char *pszResult = NULL;
+
+ if ( Q_strlen( pszIP ) > 0 )
+ {
+ pszResult = event->GetInt( "kicked" ) > 0 ? "was kicked and banned by IP" : "was banned by IP";
+
+ if ( userid > 0 )
+ {
+ Printf( "Addip: \"%s<%i><%s><>\" %s \"%s\" by \"%s\" (IP \"%s\")\n",
+ pszName,
+ userid,
+ pszNetworkid,
+ pszResult,
+ pszDuration,
+ pszCmdGiver,
+ pszIP );
+ }
+ else
+ {
+ Printf( "Addip: \"<><><>\" %s \"%s\" by \"%s\" (IP \"%s\")\n",
+ pszResult,
+ pszDuration,
+ pszCmdGiver,
+ pszIP );
+ }
+ }
+ else
+ {
+ pszResult = event->GetInt( "kicked" ) > 0 ? "was kicked and banned" : "was banned";
+
+ if ( userid > 0 )
+ {
+ Printf( "Banid: \"%s<%i><%s><>\" %s \"%s\" by \"%s\"\n",
+ pszName,
+ userid,
+ pszNetworkid,
+ pszResult,
+ pszDuration,
+ pszCmdGiver );
+ }
+ else
+ {
+ Printf( "Banid: \"<><%s><>\" %s \"%s\" by \"%s\"\n",
+ pszNetworkid,
+ pszResult,
+ pszDuration,
+ pszCmdGiver );
+ }
+ }
+ }
+ }
+
+ else if ( Q_strcmp(name, "server_removeban") == 0 )
+ {
+ if ( sv_logbans.GetInt() > 0 )
+ {
+ const char *pszNetworkid = event->GetString( "networkid" );
+ const char *pszIP = event->GetString( "ip" );
+ const char *pszCmdGiver = event->GetString( "by" );
+
+ if ( Q_strlen( pszIP ) > 0 )
+ {
+ Printf( "Removeip: \"<><><>\" was unbanned by \"%s\" (IP \"%s\")\n",
+ pszCmdGiver,
+ pszIP );
+ }
+ else
+ {
+ Printf( "Removeid: \"<><%s><>\" was unbanned by \"%s\"\n",
+ pszNetworkid,
+ pszCmdGiver );
+ }
+ }
+ }
+}
+
+struct TempFilename_t
+{
+ bool IsGzip;
+ CUtlString Filename;
+ union
+ {
+ FileHandle_t file;
+ gzFile gzfile;
+ } fh;
+};
+
+// Given a base filename and an extension, try to find a file that doesn't exist which we can use. This is
+// accomplished by appending 000, 001, etc. Set IsGzip to use gzopen instead of filesystem open.
+static bool CreateTempFilename( TempFilename_t &info, const char *filenameBase, const char *ext, bool IsGzip )
+{
+ // Check if a logfilename format has been specified - if it has, kick in new behavior.
+ const char *logfilename_format = sv_logfilename_format.GetString();
+ bool bHaveLogfilenameFormat = logfilename_format && logfilename_format[ 0 ];
+
+ info.fh.file = NULL;
+ info.fh.gzfile = 0;
+ info.IsGzip = IsGzip;
+
+ CUtlString fname = CUtlString( filenameBase ).StripExtension();
+
+ for ( int i = 0; i < 1000; i++ )
+ {
+ if ( bHaveLogfilenameFormat )
+ {
+ // For the first pass, let's try not adding the index.
+ if ( i == 0 )
+ info.Filename.Format( "%s.%s", fname.Get(), ext );
+ else
+ info.Filename.Format( "%s_%03i.%s", fname.Get(), i - 1, ext );
+ }
+ else
+ {
+ info.Filename.Format( "%s%03i.%s", fname.Get(), i, ext );
+ }
+
+ // Make sure the path exists.
+ info.Filename.FixSlashes();
+ COM_CreatePath( info.Filename );
+
+ if ( !g_pFileSystem->FileExists( info.Filename, "LOGDIR" ) )
+ {
+ // If the path doesn't exist, try opening the file. If that succeeded, return our filehandle and filename.
+ if ( !IsGzip )
+ {
+ info.fh.file = g_pFileSystem->Open( info.Filename, "wt", "LOGDIR" );
+ if ( info.fh.file )
+ return true;
+ }
+ else
+ {
+ info.fh.gzfile = gzopen( info.Filename, "wb6" );
+ if ( info.fh.gzfile )
+ return true;
+ }
+ }
+ }
+
+ info.Filename = NULL;
+ return false;
+}
+
+// Gzip Filename to Filename.gz.
+static bool gzip_file_compress( const CUtlString &Filename )
+{
+ bool bRet = false;
+
+ // Try to find a unique temp filename.
+ TempFilename_t info;
+ bRet = CreateTempFilename( info, Filename, "log.gz", true );
+ if ( !bRet )
+ return false;
+
+ Msg( "Compressing %s to %s...\n", Filename.Get(), info.Filename.Get() );
+
+ FILE *in = fopen( Filename, "rb" );
+ if ( in )
+ {
+ for (;;)
+ {
+ char buf[ 16384 ];
+ size_t len = fread( buf, 1, sizeof( buf ), in );
+ if ( ferror( in ) )
+ {
+ Msg( "%s: fread failed.\n", __FUNCTION__ );
+ break;
+ }
+ if (len == 0)
+ {
+ bRet = true;
+ break;
+ }
+
+ if ( (size_t)gzwrite( info.fh.gzfile, buf, len ) != len )
+ {
+ Msg( "%s: gzwrite failed.\n", __FUNCTION__ );
+ break;
+ }
+ }
+
+ if ( gzclose( info.fh.gzfile ) != Z_OK )
+ {
+ Msg( "%s: gzclose failed.\n", __FUNCTION__ );
+ bRet = false;
+ }
+
+ fclose( in );
+ }
+
+ return bRet;
+}
+
+static void FixupInvalidPathChars( char *filename )
+{
+ if ( !filename )
+ return;
+
+ for ( ; filename[ 0 ]; filename++ )
+ {
+ switch ( filename[ 0 ] )
+ {
+ case ':':
+ case '\n':
+ case '\r':
+ case '\t':
+ case '.':
+ case '\\':
+ case '/':
+ filename[ 0 ] = '_';
+ break;
+ }
+ }
+}
+
+/*
+====================
+Log_Close
+
+Close logging file
+====================
+*/
+void CLog::Close( void )
+{
+ if ( m_hLogFile != FILESYSTEM_INVALID_HANDLE )
+ {
+ Printf( "Log file closed.\n" );
+ g_pFileSystem->Close( m_hLogFile );
+
+ if ( sv_logfilecompress.GetBool() )
+ {
+ // Try to compress m_LogFilename to m_LogFilename.gz.
+ if ( gzip_file_compress( m_LogFilename ) )
+ {
+ Msg( " Success. Removing %s.\n", m_LogFilename.Get() );
+ g_pFileSystem->RemoveFile( m_LogFilename, "LOGDIR" );
+ }
+ }
+ }
+
+ m_hLogFile = FILESYSTEM_INVALID_HANDLE;
+ m_LogFilename = NULL;
+}
+
+/*
+====================
+Log_Flush
+
+Flushes the log file to disk
+====================
+*/
+void CLog::Flush( void )
+{
+ if ( m_hLogFile != FILESYSTEM_INVALID_HANDLE )
+ {
+ g_pFileSystem->Flush( m_hLogFile );
+ }
+}
+
+/*
+====================
+Log_Open
+
+Open logging file
+====================
+*/
+void CLog::Open( void )
+{
+ if ( !m_bActive || !sv_logfile.GetBool() )
+ return;
+
+ // Do we already have a log file (and we only want one)?
+ if ( m_hLogFile && sv_log_onefile.GetBool() )
+ return;
+
+ Close();
+
+ // Find a new log file slot.
+ tm today;
+ VCRHook_LocalTime( &today );
+
+ // safety check for invalid paths
+ const char *pszLogsDir = sv_logsdir.GetString();
+ if ( !COM_IsValidPath( pszLogsDir ) )
+ pszLogsDir = "logs";
+
+ // Get the logfilename format string.
+ char szLogFilename[ MAX_OSPATH ];
+ szLogFilename[ 0 ] = 0;
+
+ const char *logfilename_format = sv_logfilename_format.GetString();
+ if ( logfilename_format && logfilename_format[ 0 ] )
+ {
+ // Call strftime with the logfilename format.
+ strftime( szLogFilename, sizeof( szLogFilename ), logfilename_format, &today );
+
+ // Make sure it's nil terminated.
+ szLogFilename[ sizeof( szLogFilename ) - 1 ] = 0;
+
+ // Trim any leading and trailing whitespace.
+ Q_AggressiveStripPrecedingAndTrailingWhitespace( szLogFilename );
+ }
+ if ( !szLogFilename[ 0 ] )
+ {
+ // If we got nothing, default to old month / day of month behavior.
+ V_sprintf_safe( szLogFilename, "L%02i%02i", today.tm_mon + 1, today.tm_mday );
+ }
+
+ // Replace any screwy characters with underscores.
+ FixupInvalidPathChars( szLogFilename );
+
+ char szFileBase[ MAX_OSPATH ];
+ V_sprintf_safe( szFileBase, "%s/%s", pszLogsDir, szLogFilename );
+
+ // Try to get a free file.
+ TempFilename_t info;
+ if ( !CreateTempFilename( info, szFileBase, "log", false ) )
+ {
+ ConMsg( "Unable to open logfiles under %s\nLogging disabled\n", szFileBase );
+ return;
+ }
+
+ m_hLogFile = info.fh.file;
+ m_LogFilename = info.Filename;
+
+ ConMsg( "Server logging data to file %s\n", m_LogFilename.Get() );
+ Printf( "Log file started (file \"%s\") (game \"%s\") (version \"%i\")\n", m_LogFilename.Get(), com_gamedir, build_number() );
+}