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.

581 lines
14 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================//
  6. #include <windows.h>
  7. #include <stdlib.h>
  8. #include <conio.h>
  9. #include <direct.h>
  10. #include <io.h>
  11. #include "tier1/interface.h"
  12. #include "tier0/dbg.h"
  13. #include "tier1/strtools.h"
  14. #include "filesystem.h"
  15. #include "tier1/KeyValues.h"
  16. #include "time.h"
  17. #include "tier1/utllinkedlist.h"
  18. #include "tier1/checksum_crc.h"
  19. #define SSU_CONFIG_FILENAME "SymbolStoreUpdate.cfg"
  20. enum FileUpdateStatus_t
  21. {
  22. FILEUPDATE_CANTGETLOCALCOPY,
  23. FILEUPDATE_CRC_MATCH, // The CRC matched the last one that was shoved in there so it didn't have to update the symbol store.
  24. FILEUPDATE_UPDATED,
  25. FILEUPDATE_ERROR
  26. };
  27. typedef enum
  28. {
  29. FILETYPE_LOCAL=0,
  30. FILETYPE_VSS,
  31. FILETYPE_PERFORCE
  32. } FileType_t;
  33. class CFileInfo
  34. {
  35. public:
  36. CFileInfo()
  37. {
  38. m_Status = STATUS_OK;
  39. m_P4Client[0] = 0;
  40. m_BinaryName[0] = 0;
  41. m_FileType = FILETYPE_LOCAL;
  42. m_bDidCRC = false;
  43. }
  44. public:
  45. FileType_t m_FileType; // Where does this file come from?
  46. char m_P4Client[256]; // If nonzero length, then this is a perforce binary we need to get.
  47. char m_BinaryName[256]; // Name of the dll or exe file.
  48. bool m_bDidCRC; // Used so we don't keep shoving the same things into the symbol store.
  49. CRC32_t m_LastCRC;
  50. enum
  51. {
  52. STATUS_OK,
  53. STATUS_ON_PROBATION // This means we sent an email warning about this file not being available.
  54. // We can only get back to STATUS_OK when we can update the file successfully.
  55. };
  56. int m_Status;
  57. };
  58. IFileSystem *g_pFileSystem = 0;
  59. CSysModule *g_pFSModule = 0;
  60. char g_SymbolStoreDir[256];
  61. CUtlLinkedList<CFileInfo*,int> g_FileInfos;
  62. unsigned long g_nUpdateIntervalSeconds = 60 * 3; // Every 3 minutes by default.
  63. int g_nSuccessfulSymbolStores;
  64. int g_nSuccessfulP4Updates;
  65. int g_nSuccessfulVSSGets;
  66. int g_nSuccessfulP4Gets;
  67. int g_nUnnecessaryP4Updates;
  68. char* GetTimeString()
  69. {
  70. time_t ltime;
  71. time( &ltime );
  72. char *pStr = ctime( &ltime );
  73. static char tempStr[512];
  74. Q_strncpy( tempStr, pStr, sizeof( tempStr ) );
  75. char *pEnd = strchr( tempStr, '\n' );
  76. if ( pEnd )
  77. *pEnd = 0;
  78. pEnd = strchr( tempStr, '\r' );
  79. if ( pEnd )
  80. *pEnd = 0;
  81. return tempStr;
  82. }
  83. void ParseConfigFile()
  84. {
  85. // Open the KeyValues file.
  86. KeyValues *pKV = new KeyValues( "" );
  87. if ( !pKV->LoadFromFile( g_pFileSystem, SSU_CONFIG_FILENAME ) )
  88. Error( "Error loading config file %s.\n", SSU_CONFIG_FILENAME );
  89. // Set the SSDIR environment variable.
  90. KeyValues *pDir = pKV->FindKey( "ssdir" );
  91. if ( !pDir )
  92. Error( "Config file %s is missing 'ssdir' key", SSU_CONFIG_FILENAME );
  93. char szBuffer[512];
  94. Q_snprintf( szBuffer, sizeof( szBuffer ), "SSDIR=%s", pDir->GetString() );
  95. if ( _putenv( szBuffer ) != 0 )
  96. Error( "_putenv( %s ) failed.\n", szBuffer );
  97. pDir = pKV->FindKey( "SymbolStore" );
  98. if ( !pDir )
  99. Error( "Config file %s is missing 'SymbolStore' key.\n", SSU_CONFIG_FILENAME );
  100. Q_strncpy( g_SymbolStoreDir, pDir->GetString(), sizeof( g_SymbolStoreDir ) );
  101. g_nUpdateIntervalSeconds = pKV->GetInt( "UpdateIntervalSeconds", g_nUpdateIntervalSeconds );
  102. Msg( "Update interval set to %d seconds.\n", g_nUpdateIntervalSeconds );
  103. // Create a tracker for each file in the info.
  104. for ( KeyValues *pKey=pKV->GetFirstSubKey(); pKey; pKey = pKey->GetNextKey() )
  105. {
  106. bool bVSSBinary = (stricmp( pKey->GetName(), "binary" ) == 0);
  107. bool bLocalBinary = (stricmp( pKey->GetName(), "localbinary" ) == 0);
  108. bool bPerforceBinary = (stricmp( pKey->GetName(), "p4binary" ) == 0);
  109. if ( bVSSBinary || bLocalBinary || bPerforceBinary )
  110. {
  111. CFileInfo *pInfo = new CFileInfo;
  112. strncpy( pInfo->m_BinaryName, pKey->GetString(), sizeof( pInfo->m_BinaryName ) );
  113. if ( bLocalBinary )
  114. pInfo->m_FileType = FILETYPE_LOCAL;
  115. else if ( bVSSBinary )
  116. pInfo->m_FileType = FILETYPE_VSS;
  117. else
  118. pInfo->m_FileType = FILETYPE_PERFORCE;
  119. g_FileInfos.AddToTail( pInfo );
  120. }
  121. }
  122. if ( g_FileInfos.Count() == 0 )
  123. Error( "No 'binary' specifications in %s.\n", SSU_CONFIG_FILENAME );
  124. }
  125. void InitFileSystem( const char *pchExeDir )
  126. {
  127. // Init various modules.
  128. if ( !Sys_LoadInterface(
  129. "filesystem_stdio",
  130. FILESYSTEM_INTERFACE_VERSION,
  131. &g_pFSModule,
  132. (void**)&g_pFileSystem ) )
  133. {
  134. Error( "Error loading filesystem_stdio.\n" );
  135. }
  136. g_pFileSystem->AddSearchPath( pchExeDir, "BASE" );
  137. }
  138. bool StripFilenameFromPath( char *pStr )
  139. {
  140. // Now strip off the filename.
  141. char *pLastSlash = pStr;
  142. char *pCur = pStr;
  143. while ( *pCur )
  144. {
  145. if ( *pCur == '/' || *pCur == '\\' )
  146. pLastSlash = pCur;
  147. ++pCur;
  148. }
  149. if ( pLastSlash == pStr )
  150. return false;
  151. *pLastSlash = 0;
  152. return true;
  153. }
  154. int RunCommandGetOutput( const char *cmdLine, CUtlVector<char> &output )
  155. {
  156. const char *pTempOutputFilename = "temp_output.txt";
  157. char fullCmdLine[2048];
  158. Q_snprintf( fullCmdLine, sizeof( fullCmdLine ), "%s > %s", cmdLine, pTempOutputFilename );
  159. int ret = system( fullCmdLine );
  160. if ( ret == 0 )
  161. {
  162. // Read the command output.
  163. FILE *fp = fopen( pTempOutputFilename, "rb" );
  164. if ( fp )
  165. {
  166. fseek( fp, 0, SEEK_END );
  167. output.SetSize( ftell( fp ) );
  168. fseek( fp, 0, SEEK_SET );
  169. fread( output.Base(), output.Count(), 1, fp );
  170. fclose( fp );
  171. }
  172. else
  173. {
  174. output.Purge();
  175. output.AddToTail( 0 );
  176. return ret;
  177. }
  178. // Add a null-terminator.
  179. output.AddToTail( 0 );
  180. }
  181. DeleteFile( pTempOutputFilename );
  182. return ret;
  183. }
  184. void GetFilenameBase( const char *pFilename, const char* &pPrefix, const char* &pFilenameBase )
  185. {
  186. // Get just the base filename.
  187. pFilenameBase = pFilename;
  188. pPrefix = "SymbolStoreUpdateTempFiles\\";
  189. const char *pCur = pFilename;
  190. while ( *pCur )
  191. {
  192. if ( *pCur == '\\' || *pCur == '/' )
  193. pFilenameBase = pCur + 1;
  194. ++pCur;
  195. }
  196. }
  197. bool GetMostRecentP4Revision( CFileInfo *pInfo, char syncCommand[512] )
  198. {
  199. CUtlVector<char> output;
  200. if ( pInfo->m_FileType == FILETYPE_PERFORCE )
  201. {
  202. char cmd[512];
  203. Q_snprintf( cmd, sizeof( cmd ), "p4 changes -m 1 \"%s\"", pInfo->m_BinaryName );
  204. if ( RunCommandGetOutput( cmd, output ) != 0 )
  205. return false;
  206. }
  207. else
  208. {
  209. if ( RunCommandGetOutput( "p4 changes -m 1", output ) != 0 )
  210. return false;
  211. }
  212. if ( Q_stristr( output.Base(), "change " ) != output.Base() )
  213. return false;
  214. int iRevisionNum;
  215. char *pNumber = output.Base() + 7;
  216. if ( sscanf( pNumber, "%d", &iRevisionNum ) == 1 )
  217. {
  218. if ( pInfo->m_FileType == FILETYPE_PERFORCE )
  219. {
  220. const char *pPrefix, *pFilenameBase;
  221. GetFilenameBase( pInfo->m_BinaryName, pPrefix, pFilenameBase );
  222. Q_snprintf( syncCommand, 512, "p4 print -o \"%s\" \"%s\"@%d", pFilenameBase, pInfo->m_BinaryName, iRevisionNum );
  223. }
  224. else
  225. {
  226. Q_snprintf( syncCommand, 512, "p4 sync //valvegames/main/src/...@%d", iRevisionNum );
  227. }
  228. return true;
  229. }
  230. else
  231. {
  232. return false;
  233. }
  234. }
  235. bool GetLocalCopyOfFile( CFileInfo *pInfo, const char* &pPrefix, const char* &pFilenameBase )
  236. {
  237. // Get the file.
  238. pPrefix = "";
  239. char cmdLine[4096];
  240. int ret = 0;
  241. if ( pInfo->m_FileType == FILETYPE_LOCAL )
  242. {
  243. // m_BinaryName points right at a file on the local HD or a UNC path.
  244. pFilenameBase = pInfo->m_BinaryName;
  245. return _access( pInfo->m_BinaryName, 0 ) == 0;
  246. }
  247. else if ( pInfo->m_FileType == FILETYPE_VSS )
  248. {
  249. // This file comes from vss.
  250. Q_snprintf( cmdLine, sizeof( cmdLine ), "ss.exe get %s -I- -GLSymbolStoreUpdateTempFiles >> command_output.txt", pInfo->m_BinaryName );
  251. ret = system( cmdLine );
  252. if ( ret == 0 )
  253. {
  254. ++g_nSuccessfulVSSGets;
  255. GetFilenameBase( pInfo->m_BinaryName, pPrefix, pFilenameBase );
  256. return true;
  257. }
  258. else
  259. {
  260. Warning( "Failed to get '%s' from vss (error %d).\n", pInfo->m_BinaryName, ret );
  261. return false;
  262. }
  263. }
  264. else
  265. {
  266. GetFilenameBase( pInfo->m_BinaryName, pPrefix, pFilenameBase );
  267. // This file comes from Perforce.
  268. Q_snprintf( cmdLine, sizeof( cmdLine ), "p4 print -o \"SymbolStoreUpdateTempFiles\\%s\" \"%s\" >> command_output.txt", pFilenameBase, pInfo->m_BinaryName );
  269. ret = system( cmdLine );
  270. if ( ret == 0 )
  271. {
  272. ++g_nSuccessfulP4Gets;
  273. return true;
  274. }
  275. else
  276. {
  277. Warning( "Failed to get '%s' from Perforce (error %d).\n", pInfo->m_BinaryName, ret );
  278. return false;
  279. }
  280. }
  281. }
  282. void StoreP4Revision( CFileInfo *pInfo, const char *pFilenameBase )
  283. {
  284. if ( pInfo->m_FileType != FILETYPE_VSS && pInfo->m_FileType != FILETYPE_PERFORCE )
  285. return;
  286. char cmdLine[512];
  287. char syncCommand[512];
  288. if ( GetMostRecentP4Revision( pInfo, syncCommand ) )
  289. {
  290. // Now find the directory where it put the file and put the current src_main Perforce revision up there.
  291. CUtlVector<char> output;
  292. Q_snprintf( cmdLine, sizeof( cmdLine ), "symstore query /f SymbolStoreUpdateTempFiles\\%s /s %s", pFilenameBase, g_SymbolStoreDir );
  293. int ret = RunCommandGetOutput( cmdLine, output );
  294. if ( ret == 0 )
  295. {
  296. char *pStr = Q_stristr( output.Base(), g_SymbolStoreDir );
  297. if ( pStr )
  298. {
  299. char *pSpace = pStr;
  300. while ( *pSpace && !isspace( *pSpace ) )
  301. ++pSpace;
  302. *pSpace = 0;
  303. StripFilenameFromPath( pStr );
  304. // Now put the Perforce revision into a file in that directory.
  305. char revisionFilename[512];
  306. Q_snprintf( revisionFilename, sizeof( revisionFilename ), "%s\\p4revision.txt", pStr );
  307. if ( _access( revisionFilename, 00 ) != 0 )
  308. {
  309. FILE *fp = fopen( revisionFilename, "wt" );
  310. if ( fp )
  311. {
  312. fprintf( fp, "%s", syncCommand );
  313. fclose( fp );
  314. ++g_nSuccessfulP4Updates;
  315. }
  316. }
  317. else
  318. {
  319. ++g_nUnnecessaryP4Updates;
  320. }
  321. }
  322. }
  323. }
  324. }
  325. bool GetFileCRC( const char *pFilename, CRC32_t *pCRC )
  326. {
  327. FILE *fp = fopen( pFilename, "rb" );
  328. if ( !fp )
  329. return false;
  330. CUtlVector<char> data;
  331. fseek( fp, 0, SEEK_END );
  332. data.SetSize( ftell( fp ) );
  333. fseek( fp, 0, SEEK_SET );
  334. fread( data.Base(), 1, data.Count(), fp );
  335. fclose( fp );
  336. CRC32_Init( pCRC );
  337. CRC32_ProcessBuffer( pCRC, data.Base(), data.Count() );
  338. CRC32_Final( pCRC );
  339. return true;
  340. }
  341. FileUpdateStatus_t UpdateFileInSymbolStore( CFileInfo *pInfo )
  342. {
  343. const char *pFilenameBase, *pPrefix;
  344. if ( !GetLocalCopyOfFile( pInfo, pPrefix, pFilenameBase ) )
  345. return FILEUPDATE_CANTGETLOCALCOPY;
  346. char fullFilename[512];
  347. Q_snprintf( fullFilename, sizeof( fullFilename ), "%s%s", pPrefix, pFilenameBase );
  348. CRC32_t crc;
  349. if ( !GetFileCRC( fullFilename, &crc ) )
  350. {
  351. Warning( "GetFileCRC( %s ) failed.\n", fullFilename );
  352. return FILEUPDATE_ERROR;
  353. }
  354. // If the file's CRC is the same, then don't bother updating the symbol store again.
  355. if ( pInfo->m_bDidCRC && pInfo->m_LastCRC == crc )
  356. return FILEUPDATE_CRC_MATCH;
  357. pInfo->m_bDidCRC = true;
  358. pInfo->m_LastCRC = crc;
  359. // Now run the symbol store updater.
  360. char cmdLine[512];
  361. Q_snprintf( cmdLine, sizeof( cmdLine ), "symstore add /f \"%s\" /s \"%s\" /o /t SourceEngine >> command_output.txt", fullFilename, g_SymbolStoreDir );
  362. int ret = system( cmdLine );
  363. if ( ret == 0 )
  364. {
  365. ++g_nSuccessfulSymbolStores;
  366. // Ask Perforce what the current revision # is.
  367. StoreP4Revision( pInfo, pFilenameBase );
  368. return FILEUPDATE_UPDATED;
  369. }
  370. else
  371. {
  372. Warning( "%s - symstore.exe failed on '%s'.\n", GetTimeString(), pInfo->m_BinaryName );
  373. return FILEUPDATE_ERROR;
  374. }
  375. }
  376. const char *SetPathToExeDirectory()
  377. {
  378. static char filename[MAX_PATH];
  379. if ( GetModuleFileName( GetModuleHandle( NULL ), filename, sizeof( filename ) ) == 0 )
  380. Error( "GetModuleFileNameEx failed.\n" );
  381. // Now strip off the filename.
  382. if ( !StripFilenameFromPath( filename ) )
  383. Error( "GetModuleFilename returned bad filename (%s).\n", filename );
  384. if ( _chdir( filename ) != 0 )
  385. Error( "_chdir( %s ) failed.\n", filename );
  386. return filename;
  387. }
  388. int main( int argc, char **argv )
  389. {
  390. const char *pchExeDir = SetPathToExeDirectory();
  391. // Make this process idle priority.
  392. SetPriorityClass( GetCurrentProcess(), IDLE_PRIORITY_CLASS );
  393. // Initialize stuff.
  394. InitFileSystem( pchExeDir );
  395. ParseConfigFile();
  396. // Clear out the temp files directory.
  397. if ( _access( "SymbolStoreUpdateTempFiles", 00 ) == 0 )
  398. {
  399. system( "rd /s /q SymbolStoreUpdateTempFiles" );
  400. system( "md SymbolStoreUpdateTempFiles" );
  401. }
  402. unsigned long nextUpdateTime = GetTickCount();
  403. int nPasses = 0;
  404. Msg( "\nUpdating files\n" );
  405. Msg( "- press F to force a refresh\n" );
  406. Msg( "- press ESC or Q to exit\n" );
  407. Msg( "\n" );
  408. while ( 1 )
  409. {
  410. if ( kbhit() )
  411. {
  412. int ch = getch();
  413. if ( toupper( ch ) == 'F' )
  414. {
  415. Msg( "\n\n'F' pressed, forcing an update.\n\n" );
  416. nextUpdateTime = 0;
  417. }
  418. else if ( toupper( ch ) == 'P' )
  419. {
  420. Msg( "\n\nP pressed. Pause for how many minutes? " );
  421. float flMinutes = 0;
  422. scanf( "%f", &flMinutes );
  423. Msg( "\nPausing for %f minutes...\n\n", flMinutes );
  424. nextUpdateTime = GetTickCount() + (int)( flMinutes * 60 * 1000 );
  425. }
  426. else if ( ch == 27 || toupper( ch ) == 'Q' )
  427. {
  428. break;
  429. }
  430. }
  431. if ( GetTickCount() >= nextUpdateTime )
  432. {
  433. g_nSuccessfulSymbolStores = 0;
  434. g_nSuccessfulP4Updates = 0;
  435. g_nSuccessfulVSSGets = 0;
  436. g_nSuccessfulP4Gets = 0;
  437. g_nUnnecessaryP4Updates = 0;
  438. int nCRCMatches = 0;
  439. // For each file, grab its exe and put it in the store, then grab its PDB and do the same.
  440. int iCount = 0;
  441. FOR_EACH_LL( g_FileInfos, j )
  442. {
  443. if ( UpdateFileInSymbolStore( g_FileInfos[j] ) == FILEUPDATE_CRC_MATCH )
  444. ++nCRCMatches;
  445. ++iCount;
  446. Msg( "\rUpdated %d of %d - %d CRC matches... ", iCount, g_FileInfos.Count(), nCRCMatches );
  447. if ( kbhit() )
  448. break;
  449. }
  450. // Wait for 2 minutes.
  451. nextUpdateTime = GetTickCount() + g_nUpdateIntervalSeconds * 1000;
  452. Msg( "\n\n%s\n"
  453. "Pass %d completed.\n", GetTimeString(), ++nPasses );
  454. Msg( "- %d successful vss gets, %d successful p4 gets\n", g_nSuccessfulVSSGets, g_nSuccessfulP4Gets );
  455. Msg( "- %d successful symbol store updates\n", g_nSuccessfulSymbolStores );
  456. Msg( "- %d new p4revision.txt files written\n", g_nSuccessfulP4Updates );
  457. Msg( "\n" );
  458. }
  459. else
  460. {
  461. Sleep( 300 );
  462. }
  463. }
  464. Msg( "\n\nKey pressed, exiting.\n" );
  465. return 0;
  466. }