Team Fortress 2 Source Code as on 22/4/2020
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.

897 lines
19 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $Header: $
  6. // $NoKeywords: $
  7. //=============================================================================//
  8. #include "server_pch.h"
  9. #include <time.h>
  10. #include "server.h"
  11. #include "sv_log.h"
  12. #include "filesystem.h"
  13. #include "filesystem_engine.h"
  14. #include "tier0/vcrmode.h"
  15. #include "sv_main.h"
  16. #include "tier0/icommandline.h"
  17. #include <proto_oob.h>
  18. #include "GameEventManager.h"
  19. #include "netadr.h"
  20. #include "zlib/zlib.h"
  21. // memdbgon must be the last include file in a .cpp file!!!
  22. #include "tier0/memdbgon.h"
  23. static ConVar sv_logsdir( "sv_logsdir", "logs", FCVAR_ARCHIVE, "Folder in the game directory where server logs will be stored." );
  24. static ConVar sv_logfile( "sv_logfile", "1", FCVAR_ARCHIVE, "Log server information in the log file." );
  25. static ConVar sv_logflush( "sv_logflush", "0", FCVAR_ARCHIVE, "Flush the log file to disk on each write (slow)." );
  26. static ConVar sv_logecho( "sv_logecho", "1", FCVAR_ARCHIVE, "Echo log information to the console." );
  27. static ConVar sv_log_onefile( "sv_log_onefile", "0", FCVAR_ARCHIVE, "Log server information to only one file." );
  28. 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?
  29. 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)" );
  30. static ConVar sv_logfilename_format( "sv_logfilename_format", "", FCVAR_ARCHIVE, "Log filename format. See strftime for formatting codes." );
  31. static ConVar sv_logfilecompress( "sv_logfilecompress", "0", FCVAR_ARCHIVE, "Gzip compress logfile and rename to logfilename.log.gz on close." );
  32. CLog g_Log; // global Log object
  33. CON_COMMAND( log, "Enables logging to file, console, and udp < on | off >." )
  34. {
  35. if ( args.ArgC() != 2 )
  36. {
  37. ConMsg( "Usage: log < on | off >\n" );
  38. if ( g_Log.IsActive() )
  39. {
  40. bool bHaveFirst = false;
  41. ConMsg( "currently logging to: " );
  42. if ( sv_logfile.GetInt() )
  43. {
  44. ConMsg( "file" );
  45. bHaveFirst = true;
  46. }
  47. if ( sv_logecho.GetInt() )
  48. {
  49. if ( bHaveFirst )
  50. {
  51. ConMsg( ", console" );
  52. }
  53. else
  54. {
  55. ConMsg( "console" );
  56. bHaveFirst = true;
  57. }
  58. }
  59. if ( g_Log.UsingLogAddress() )
  60. {
  61. if ( bHaveFirst )
  62. {
  63. ConMsg( ", udp" );
  64. }
  65. else
  66. {
  67. ConMsg( "udp" );
  68. bHaveFirst = true;
  69. }
  70. }
  71. if ( !bHaveFirst )
  72. {
  73. ConMsg( "no destinations! (file, console, or udp)\n" );
  74. ConMsg( "check \"sv_logfile\", \"sv_logecho\", and \"logaddress_list\"" );
  75. }
  76. ConMsg( "\n" );
  77. }
  78. else
  79. {
  80. ConMsg( "not currently logging\n" );
  81. }
  82. return;
  83. }
  84. if ( !Q_stricmp( args[1], "off" ) || !Q_stricmp( args[1], "0" ) )
  85. {
  86. if ( g_Log.IsActive() )
  87. {
  88. g_Log.Close();
  89. g_Log.SetLoggingState( false );
  90. ConMsg( "Server logging disabled.\n" );
  91. }
  92. }
  93. else if ( !Q_stricmp( args[1], "on" ) || !Q_stricmp( args[1], "1" ) )
  94. {
  95. g_Log.SetLoggingState( true );
  96. ConMsg( "Server logging enabled.\n" );
  97. g_Log.Open();
  98. }
  99. else
  100. {
  101. ConMsg( "log: unknown parameter %s, 'on' and 'off' are valid\n", args[1] );
  102. }
  103. }
  104. // changed log_addaddress back to logaddress_add to be consistent with GoldSrc
  105. CON_COMMAND( logaddress_add, "Set address and port for remote host <ip:port>." )
  106. {
  107. netadr_t adr;
  108. const char *pszIP, *pszPort;
  109. if ( args.ArgC() != 4 && args.ArgC() != 2 )
  110. {
  111. ConMsg( "Usage: logaddress_add ip:port\n" );
  112. return;
  113. }
  114. pszIP = args[1];
  115. if ( args.ArgC() == 4 )
  116. {
  117. pszPort = args[3];
  118. }
  119. else
  120. {
  121. pszPort = Q_strstr( pszIP, ":" );
  122. // if we have "IP:port" as one argument inside quotes
  123. if ( pszPort )
  124. {
  125. // add one to remove the :
  126. pszPort++;
  127. }
  128. else
  129. {
  130. // default port
  131. pszPort = "27015";
  132. }
  133. }
  134. if ( !Q_atoi( pszPort ) )
  135. {
  136. ConMsg( "logaddress_add: must specify a valid port\n" );
  137. return;
  138. }
  139. if ( !pszIP || !pszIP[0] )
  140. {
  141. ConMsg( "logaddress_add: unparseable address\n" );
  142. return;
  143. }
  144. char szAdr[32];
  145. Q_snprintf( szAdr, sizeof( szAdr ), "%s:%s", pszIP, pszPort );
  146. if ( NET_StringToAdr( szAdr, &adr ) )
  147. {
  148. if ( g_Log.AddLogAddress( adr ) )
  149. {
  150. ConMsg( "logaddress_add: %s\n", adr.ToString() );
  151. }
  152. else
  153. {
  154. ConMsg( "logaddress_add: %s is already in the list\n", adr.ToString() );
  155. }
  156. }
  157. else
  158. {
  159. ConMsg( "logaddress_add: unable to resolve %s\n", szAdr );
  160. }
  161. }
  162. CON_COMMAND( logaddress_delall, "Remove all udp addresses being logged to" )
  163. {
  164. g_Log.DelAllLogAddress();
  165. }
  166. CON_COMMAND( logaddress_del, "Remove address and port for remote host <ip:port>." )
  167. {
  168. netadr_t adr;
  169. const char *pszIP, *pszPort;
  170. if ( args.ArgC() != 4 && args.ArgC() != 2 )
  171. {
  172. ConMsg( "Usage: logaddress_del ip:port\n" );
  173. return;
  174. }
  175. pszIP = args[1];
  176. if ( args.ArgC() == 4 )
  177. {
  178. pszPort = args[3];
  179. }
  180. else
  181. {
  182. pszPort = Q_strstr( pszIP, ":" );
  183. // if we have "IP:port" as one argument inside quotes
  184. if ( pszPort )
  185. {
  186. // add one to remove the :
  187. pszPort++;
  188. }
  189. else
  190. {
  191. // default port
  192. pszPort = "27015";
  193. }
  194. }
  195. if ( !Q_atoi( pszPort ) )
  196. {
  197. ConMsg( "logaddress_del: must specify a valid port\n" );
  198. return;
  199. }
  200. if ( !pszIP || !pszIP[0] )
  201. {
  202. ConMsg( "logaddress_del: unparseable address\n" );
  203. return;
  204. }
  205. char szAdr[32];
  206. Q_snprintf( szAdr, sizeof( szAdr ), "%s:%s", pszIP, pszPort );
  207. if ( NET_StringToAdr( szAdr, &adr ) )
  208. {
  209. if ( g_Log.DelLogAddress( adr ) )
  210. {
  211. ConMsg( "logaddress_del: %s\n", adr.ToString() );
  212. }
  213. else
  214. {
  215. ConMsg( "logaddress_del: address %s not found in the list\n", adr.ToString() );
  216. }
  217. }
  218. else
  219. {
  220. ConMsg( "logaddress_del: unable to resolve %s\n", szAdr );
  221. }
  222. }
  223. CON_COMMAND( logaddress_list, "List all addresses currently being used by logaddress." )
  224. {
  225. g_Log.ListLogAddress();
  226. }
  227. CLog::CLog()
  228. {
  229. Reset();
  230. }
  231. CLog::~CLog()
  232. {
  233. }
  234. void CLog::Reset( void ) // reset all logging streams
  235. {
  236. m_LogAddresses.RemoveAll();
  237. m_hLogFile = FILESYSTEM_INVALID_HANDLE;
  238. m_LogFilename = NULL;
  239. m_bActive = false;
  240. m_flLastLogFlush = realtime;
  241. m_bFlushLog = false;
  242. #ifndef _XBOX
  243. if ( CommandLine()->CheckParm( "-flushlog" ) )
  244. {
  245. m_bFlushLog = true;
  246. }
  247. #endif
  248. }
  249. void CLog::Init( void )
  250. {
  251. Reset();
  252. // listen to these events
  253. g_GameEventManager.AddListener( this, "server_spawn", true );
  254. g_GameEventManager.AddListener( this, "server_shutdown", true );
  255. g_GameEventManager.AddListener( this, "server_cvar", true );
  256. g_GameEventManager.AddListener( this, "server_message", true );
  257. g_GameEventManager.AddListener( this, "server_addban", true );
  258. g_GameEventManager.AddListener( this, "server_removeban", true );
  259. }
  260. void CLog::Shutdown()
  261. {
  262. Close();
  263. Reset();
  264. g_GameEventManager.RemoveListener( this );
  265. }
  266. void CLog::SetLoggingState( bool state )
  267. {
  268. m_bActive = state;
  269. }
  270. void CLog::RunFrame()
  271. {
  272. if ( m_bFlushLog && m_hLogFile != FILESYSTEM_INVALID_HANDLE && ( realtime - m_flLastLogFlush ) > 1.0f )
  273. {
  274. m_flLastLogFlush = realtime;
  275. g_pFileSystem->Flush( m_hLogFile );
  276. }
  277. }
  278. bool CLog::AddLogAddress(netadr_t addr)
  279. {
  280. int i = 0;
  281. for ( i = 0; i < m_LogAddresses.Count(); ++i )
  282. {
  283. if ( m_LogAddresses.Element(i).CompareAdr(addr, false) )
  284. {
  285. // found!
  286. break;
  287. }
  288. }
  289. if ( i < m_LogAddresses.Count() )
  290. {
  291. // already in the list
  292. return false;
  293. }
  294. m_LogAddresses.AddToTail( addr );
  295. return true;
  296. }
  297. bool CLog::DelLogAddress(netadr_t addr)
  298. {
  299. int i = 0;
  300. for ( i = 0; i < m_LogAddresses.Count(); ++i )
  301. {
  302. if ( m_LogAddresses.Element(i).CompareAdr(addr, false) )
  303. {
  304. // found!
  305. break;
  306. }
  307. }
  308. if ( i < m_LogAddresses.Count() )
  309. {
  310. m_LogAddresses.Remove(i);
  311. return true;
  312. }
  313. return false;
  314. }
  315. void CLog::ListLogAddress( void )
  316. {
  317. netadr_t *pElement;
  318. const char *pszAdr;
  319. int count = m_LogAddresses.Count();
  320. if ( count <= 0 )
  321. {
  322. ConMsg( "logaddress_list: no addresses in the list\n" );
  323. }
  324. else
  325. {
  326. if ( count == 1 )
  327. {
  328. ConMsg( "logaddress_list: %i entry\n", count );
  329. }
  330. else
  331. {
  332. ConMsg( "logaddress_list: %i entries\n", count );
  333. }
  334. for ( int i = 0 ; i < count ; ++i )
  335. {
  336. pElement = &m_LogAddresses.Element(i);
  337. pszAdr = pElement->ToString();
  338. ConMsg( "%s\n", pszAdr );
  339. }
  340. }
  341. }
  342. bool CLog::UsingLogAddress( void )
  343. {
  344. return ( m_LogAddresses.Count() > 0 );
  345. }
  346. void CLog::DelAllLogAddress( void )
  347. {
  348. if ( m_LogAddresses.Count() > 0 )
  349. {
  350. ConMsg( "logaddress_delall: all addresses cleared\n" );
  351. m_LogAddresses.RemoveAll();
  352. }
  353. else
  354. {
  355. ConMsg( "logaddress_delall: no addresses in the list\n" );
  356. }
  357. }
  358. /*
  359. ==================
  360. Log_PrintServerVars
  361. ==================
  362. */
  363. void CLog::PrintServerVars( void )
  364. {
  365. const ConCommandBase *var; // Temporary Pointer to cvars
  366. if ( !IsActive() )
  367. {
  368. return;
  369. }
  370. Printf( "server cvars start\n" );
  371. // Loop through cvars...
  372. for ( var= g_pCVar->GetCommands() ; var ; var=var->GetNext() )
  373. {
  374. if ( var->IsCommand() )
  375. continue;
  376. if ( !( var->IsFlagSet( FCVAR_NOTIFY ) ) )
  377. continue;
  378. Printf( "\"%s\" = \"%s\"\n", var->GetName(), ((ConVar*)var)->GetString() );
  379. }
  380. Printf( "server cvars end\n" );
  381. }
  382. bool CLog::IsActive( void )
  383. {
  384. return m_bActive;
  385. }
  386. /*
  387. ==================
  388. Log_Printf
  389. Prints a log message to the server's log file, console, and possible a UDP address
  390. ==================
  391. */
  392. void CLog::Printf( const char *fmt, ... )
  393. {
  394. va_list argptr;
  395. static char string[1024];
  396. if ( !IsActive() )
  397. {
  398. return;
  399. }
  400. va_start ( argptr, fmt );
  401. Q_vsnprintf ( string, sizeof( string ), fmt, argptr );
  402. va_end ( argptr );
  403. Print( string );
  404. }
  405. void CLog::Print( const char * text )
  406. {
  407. if ( !IsActive() || !text || !text[0] )
  408. {
  409. return;
  410. }
  411. tm today;
  412. VCRHook_LocalTime( &today );
  413. if ( Q_strlen( text ) > 1024 )
  414. {
  415. // Spew a warning, but continue and print the truncated stuff we have.
  416. DevMsg( 1, "CLog::Print: string too long (>1024 bytes)." );
  417. }
  418. static char string[1100];
  419. V_sprintf_safe( string, "L %02i/%02i/%04i - %02i:%02i:%02i: %s",
  420. today.tm_mon+1, today.tm_mday, 1900 + today.tm_year,
  421. today.tm_hour, today.tm_min, today.tm_sec, text );
  422. // Echo to server console
  423. if ( sv_logecho.GetInt() )
  424. {
  425. ConMsg( "%s", string );
  426. }
  427. // Echo to log file
  428. if ( sv_logfile.GetInt() && ( m_hLogFile != FILESYSTEM_INVALID_HANDLE ) )
  429. {
  430. g_pFileSystem->FPrintf( m_hLogFile, "%s", string );
  431. if ( sv_logflush.GetBool() )
  432. {
  433. g_pFileSystem->Flush( m_hLogFile );
  434. }
  435. }
  436. // Echo to UDP port
  437. if ( m_LogAddresses.Count() > 0 )
  438. {
  439. // out of band sending
  440. for ( int i = 0 ; i < m_LogAddresses.Count() ; i++ )
  441. {
  442. if ( sv_logsecret.GetInt() != 0 )
  443. NET_OutOfBandPrintf(NS_SERVER, m_LogAddresses.Element(i), "%c%s%s", S2A_LOGSTRING2, sv_logsecret.GetString(), string );
  444. else
  445. NET_OutOfBandPrintf(NS_SERVER, m_LogAddresses.Element(i), "%c%s", S2A_LOGSTRING, string );
  446. }
  447. }
  448. }
  449. void CLog::FireGameEvent( IGameEvent *event )
  450. {
  451. if ( !IsActive() )
  452. return;
  453. // log server events
  454. const char * name = event->GetName();
  455. if ( !name || !name[0])
  456. return;
  457. if ( Q_strcmp(name, "server_spawn") == 0 )
  458. {
  459. Printf( "Started map \"%s\" (CRC \"%s\")\n", sv.GetMapName(), MD5_Print( sv.worldmapMD5.bits, MD5_DIGEST_LENGTH ) );
  460. }
  461. else if ( Q_strcmp(name, "server_shutdown") == 0 )
  462. {
  463. Printf( "server_message: \"%s\"\n", event->GetString("reason") );
  464. }
  465. else if ( Q_strcmp(name, "server_cvar") == 0 )
  466. {
  467. Printf( "server_cvar: \"%s\" \"%s\"\n", event->GetString("cvarname"), event->GetString("cvarvalue") );
  468. }
  469. else if ( Q_strcmp(name, "server_message") == 0 )
  470. {
  471. Printf( "server_message: \"%s\"\n", event->GetString("text") );
  472. }
  473. else if ( Q_strcmp(name, "server_addban") == 0 )
  474. {
  475. if ( sv_logbans.GetInt() > 0 )
  476. {
  477. const int userid = event->GetInt( "userid" );
  478. const char *pszName = event->GetString( "name" );
  479. const char *pszNetworkid = event->GetString( "networkid" );
  480. const char *pszIP = event->GetString( "ip" );
  481. const char *pszDuration = event->GetString( "duration" );
  482. const char *pszCmdGiver = event->GetString( "by" );
  483. const char *pszResult = NULL;
  484. if ( Q_strlen( pszIP ) > 0 )
  485. {
  486. pszResult = event->GetInt( "kicked" ) > 0 ? "was kicked and banned by IP" : "was banned by IP";
  487. if ( userid > 0 )
  488. {
  489. Printf( "Addip: \"%s<%i><%s><>\" %s \"%s\" by \"%s\" (IP \"%s\")\n",
  490. pszName,
  491. userid,
  492. pszNetworkid,
  493. pszResult,
  494. pszDuration,
  495. pszCmdGiver,
  496. pszIP );
  497. }
  498. else
  499. {
  500. Printf( "Addip: \"<><><>\" %s \"%s\" by \"%s\" (IP \"%s\")\n",
  501. pszResult,
  502. pszDuration,
  503. pszCmdGiver,
  504. pszIP );
  505. }
  506. }
  507. else
  508. {
  509. pszResult = event->GetInt( "kicked" ) > 0 ? "was kicked and banned" : "was banned";
  510. if ( userid > 0 )
  511. {
  512. Printf( "Banid: \"%s<%i><%s><>\" %s \"%s\" by \"%s\"\n",
  513. pszName,
  514. userid,
  515. pszNetworkid,
  516. pszResult,
  517. pszDuration,
  518. pszCmdGiver );
  519. }
  520. else
  521. {
  522. Printf( "Banid: \"<><%s><>\" %s \"%s\" by \"%s\"\n",
  523. pszNetworkid,
  524. pszResult,
  525. pszDuration,
  526. pszCmdGiver );
  527. }
  528. }
  529. }
  530. }
  531. else if ( Q_strcmp(name, "server_removeban") == 0 )
  532. {
  533. if ( sv_logbans.GetInt() > 0 )
  534. {
  535. const char *pszNetworkid = event->GetString( "networkid" );
  536. const char *pszIP = event->GetString( "ip" );
  537. const char *pszCmdGiver = event->GetString( "by" );
  538. if ( Q_strlen( pszIP ) > 0 )
  539. {
  540. Printf( "Removeip: \"<><><>\" was unbanned by \"%s\" (IP \"%s\")\n",
  541. pszCmdGiver,
  542. pszIP );
  543. }
  544. else
  545. {
  546. Printf( "Removeid: \"<><%s><>\" was unbanned by \"%s\"\n",
  547. pszNetworkid,
  548. pszCmdGiver );
  549. }
  550. }
  551. }
  552. }
  553. struct TempFilename_t
  554. {
  555. bool IsGzip;
  556. CUtlString Filename;
  557. union
  558. {
  559. FileHandle_t file;
  560. gzFile gzfile;
  561. } fh;
  562. };
  563. // Given a base filename and an extension, try to find a file that doesn't exist which we can use. This is
  564. // accomplished by appending 000, 001, etc. Set IsGzip to use gzopen instead of filesystem open.
  565. static bool CreateTempFilename( TempFilename_t &info, const char *filenameBase, const char *ext, bool IsGzip )
  566. {
  567. // Check if a logfilename format has been specified - if it has, kick in new behavior.
  568. const char *logfilename_format = sv_logfilename_format.GetString();
  569. bool bHaveLogfilenameFormat = logfilename_format && logfilename_format[ 0 ];
  570. info.fh.file = NULL;
  571. info.fh.gzfile = 0;
  572. info.IsGzip = IsGzip;
  573. CUtlString fname = CUtlString( filenameBase ).StripExtension();
  574. for ( int i = 0; i < 1000; i++ )
  575. {
  576. if ( bHaveLogfilenameFormat )
  577. {
  578. // For the first pass, let's try not adding the index.
  579. if ( i == 0 )
  580. info.Filename.Format( "%s.%s", fname.Get(), ext );
  581. else
  582. info.Filename.Format( "%s_%03i.%s", fname.Get(), i - 1, ext );
  583. }
  584. else
  585. {
  586. info.Filename.Format( "%s%03i.%s", fname.Get(), i, ext );
  587. }
  588. // Make sure the path exists.
  589. info.Filename.FixSlashes();
  590. COM_CreatePath( info.Filename );
  591. if ( !g_pFileSystem->FileExists( info.Filename, "LOGDIR" ) )
  592. {
  593. // If the path doesn't exist, try opening the file. If that succeeded, return our filehandle and filename.
  594. if ( !IsGzip )
  595. {
  596. info.fh.file = g_pFileSystem->Open( info.Filename, "wt", "LOGDIR" );
  597. if ( info.fh.file )
  598. return true;
  599. }
  600. else
  601. {
  602. info.fh.gzfile = gzopen( info.Filename, "wb6" );
  603. if ( info.fh.gzfile )
  604. return true;
  605. }
  606. }
  607. }
  608. info.Filename = NULL;
  609. return false;
  610. }
  611. // Gzip Filename to Filename.gz.
  612. static bool gzip_file_compress( const CUtlString &Filename )
  613. {
  614. bool bRet = false;
  615. // Try to find a unique temp filename.
  616. TempFilename_t info;
  617. bRet = CreateTempFilename( info, Filename, "log.gz", true );
  618. if ( !bRet )
  619. return false;
  620. Msg( "Compressing %s to %s...\n", Filename.Get(), info.Filename.Get() );
  621. FILE *in = fopen( Filename, "rb" );
  622. if ( in )
  623. {
  624. for (;;)
  625. {
  626. char buf[ 16384 ];
  627. size_t len = fread( buf, 1, sizeof( buf ), in );
  628. if ( ferror( in ) )
  629. {
  630. Msg( "%s: fread failed.\n", __FUNCTION__ );
  631. break;
  632. }
  633. if (len == 0)
  634. {
  635. bRet = true;
  636. break;
  637. }
  638. if ( (size_t)gzwrite( info.fh.gzfile, buf, len ) != len )
  639. {
  640. Msg( "%s: gzwrite failed.\n", __FUNCTION__ );
  641. break;
  642. }
  643. }
  644. if ( gzclose( info.fh.gzfile ) != Z_OK )
  645. {
  646. Msg( "%s: gzclose failed.\n", __FUNCTION__ );
  647. bRet = false;
  648. }
  649. fclose( in );
  650. }
  651. return bRet;
  652. }
  653. static void FixupInvalidPathChars( char *filename )
  654. {
  655. if ( !filename )
  656. return;
  657. for ( ; filename[ 0 ]; filename++ )
  658. {
  659. switch ( filename[ 0 ] )
  660. {
  661. case ':':
  662. case '\n':
  663. case '\r':
  664. case '\t':
  665. case '.':
  666. case '\\':
  667. case '/':
  668. filename[ 0 ] = '_';
  669. break;
  670. }
  671. }
  672. }
  673. /*
  674. ====================
  675. Log_Close
  676. Close logging file
  677. ====================
  678. */
  679. void CLog::Close( void )
  680. {
  681. if ( m_hLogFile != FILESYSTEM_INVALID_HANDLE )
  682. {
  683. Printf( "Log file closed.\n" );
  684. g_pFileSystem->Close( m_hLogFile );
  685. if ( sv_logfilecompress.GetBool() )
  686. {
  687. // Try to compress m_LogFilename to m_LogFilename.gz.
  688. if ( gzip_file_compress( m_LogFilename ) )
  689. {
  690. Msg( " Success. Removing %s.\n", m_LogFilename.Get() );
  691. g_pFileSystem->RemoveFile( m_LogFilename, "LOGDIR" );
  692. }
  693. }
  694. }
  695. m_hLogFile = FILESYSTEM_INVALID_HANDLE;
  696. m_LogFilename = NULL;
  697. }
  698. /*
  699. ====================
  700. Log_Flush
  701. Flushes the log file to disk
  702. ====================
  703. */
  704. void CLog::Flush( void )
  705. {
  706. if ( m_hLogFile != FILESYSTEM_INVALID_HANDLE )
  707. {
  708. g_pFileSystem->Flush( m_hLogFile );
  709. }
  710. }
  711. /*
  712. ====================
  713. Log_Open
  714. Open logging file
  715. ====================
  716. */
  717. void CLog::Open( void )
  718. {
  719. if ( !m_bActive || !sv_logfile.GetBool() )
  720. return;
  721. // Do we already have a log file (and we only want one)?
  722. if ( m_hLogFile && sv_log_onefile.GetBool() )
  723. return;
  724. Close();
  725. // Find a new log file slot.
  726. tm today;
  727. VCRHook_LocalTime( &today );
  728. // safety check for invalid paths
  729. const char *pszLogsDir = sv_logsdir.GetString();
  730. if ( !COM_IsValidPath( pszLogsDir ) )
  731. pszLogsDir = "logs";
  732. // Get the logfilename format string.
  733. char szLogFilename[ MAX_OSPATH ];
  734. szLogFilename[ 0 ] = 0;
  735. const char *logfilename_format = sv_logfilename_format.GetString();
  736. if ( logfilename_format && logfilename_format[ 0 ] )
  737. {
  738. // Call strftime with the logfilename format.
  739. strftime( szLogFilename, sizeof( szLogFilename ), logfilename_format, &today );
  740. // Make sure it's nil terminated.
  741. szLogFilename[ sizeof( szLogFilename ) - 1 ] = 0;
  742. // Trim any leading and trailing whitespace.
  743. Q_AggressiveStripPrecedingAndTrailingWhitespace( szLogFilename );
  744. }
  745. if ( !szLogFilename[ 0 ] )
  746. {
  747. // If we got nothing, default to old month / day of month behavior.
  748. V_sprintf_safe( szLogFilename, "L%02i%02i", today.tm_mon + 1, today.tm_mday );
  749. }
  750. // Replace any screwy characters with underscores.
  751. FixupInvalidPathChars( szLogFilename );
  752. char szFileBase[ MAX_OSPATH ];
  753. V_sprintf_safe( szFileBase, "%s/%s", pszLogsDir, szLogFilename );
  754. // Try to get a free file.
  755. TempFilename_t info;
  756. if ( !CreateTempFilename( info, szFileBase, "log", false ) )
  757. {
  758. ConMsg( "Unable to open logfiles under %s\nLogging disabled\n", szFileBase );
  759. return;
  760. }
  761. m_hLogFile = info.fh.file;
  762. m_LogFilename = info.Filename;
  763. ConMsg( "Server logging data to file %s\n", m_LogFilename.Get() );
  764. Printf( "Log file started (file \"%s\") (game \"%s\") (version \"%i\")\n", m_LogFilename.Get(), com_gamedir, build_number() );
  765. }