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.
903 lines
19 KiB
903 lines
19 KiB
//========= 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; |
|
} |
|
#if HAVE_ZLIB |
|
else |
|
{ |
|
info.fh.gzfile = gzopen( info.Filename, "wb6" ); |
|
if ( info.fh.gzfile ) |
|
return true; |
|
} |
|
#endif |
|
} |
|
} |
|
|
|
info.Filename = NULL; |
|
return false; |
|
} |
|
|
|
// Gzip Filename to Filename.gz. |
|
static bool gzip_file_compress( const CUtlString &Filename ) |
|
{ |
|
#if HAVE_ZLIB |
|
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; |
|
#else |
|
return false; |
|
#endif |
|
} |
|
|
|
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() ); |
|
}
|
|
|