//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ // //=============================================================================// // ----------------------- // cmdlib.c // ----------------------- #include "tier0/platform.h" #ifdef IS_WINDOWS_PC #include #endif #include "cmdlib.h" #include #include #include "tier1/strtools.h" #ifdef _WIN32 #include #endif #include "utlvector.h" #include "filesystem_helpers.h" #include "utllinkedlist.h" #include "tier0/icommandline.h" #include "KeyValues.h" #include "filesystem_tools.h" #if defined( MPI ) #include "vmpi.h" #include "vmpi_tools_shared.h" #endif #if defined( _WIN32 ) || defined( WIN32 ) #include #endif #if defined( _X360 ) #include "xbox/xbox_win32stubs.h" #endif #include "tier0/memdbgon.h" // set these before calling CheckParm int myargc; char **myargv; char com_token[1024]; qboolean archive; char archivedir[1024]; FileHandle_t g_pLogFile = 0; CUtlLinkedList g_CleanupFunctions; CUtlLinkedList g_ExtraSpewHooks; bool g_bStopOnExit = false; void (*g_ExtraSpewHook)(const char*) = NULL; #if defined( _WIN32 ) || defined( WIN32 ) void CmdLib_FPrintf( FileHandle_t hFile, const char *pFormat, ... ) { static CUtlVector buf; if ( buf.Count() == 0 ) buf.SetCount( 1024 ); va_list marker; va_start( marker, pFormat ); while ( 1 ) { int ret = Q_vsnprintf( buf.Base(), buf.Count(), pFormat, marker ); if ( ret >= 0 ) { // Write the string. g_pFileSystem->Write( buf.Base(), ret, hFile ); break; } else { // Make the buffer larger. int newSize = buf.Count() * 2; buf.SetCount( newSize ); if ( buf.Count() != newSize ) { Error( "CmdLib_FPrintf: can't allocate space for text." ); } } } va_end( marker ); } char* CmdLib_FGets( char *pOut, int outSize, FileHandle_t hFile ) { int iCur=0; for ( ; iCur < (outSize-1); iCur++ ) { char c; if ( !g_pFileSystem->Read( &c, 1, hFile ) ) { if ( iCur == 0 ) return NULL; else break; } pOut[iCur] = c; if ( c == '\n' ) break; if ( c == EOF ) { if ( iCur == 0 ) return NULL; else break; } } pOut[iCur] = 0; return pOut; } #if !defined( _X360 ) #include #endif // This pauses before exiting if they use -StopOnExit. Useful for debugging. class CExitStopper { public: ~CExitStopper() { if ( g_bStopOnExit ) { Warning( "\nPress any key to quit.\n" ); getch(); } } } g_ExitStopper; static unsigned short g_InitialColor = 0xFFFF; static unsigned short g_LastColor = 0xFFFF; static unsigned short g_BadColor = 0xFFFF; static WORD g_BackgroundFlags = 0xFFFF; static void GetInitialColors( ) { #if !defined( _X360 ) // Get the old background attributes. CONSOLE_SCREEN_BUFFER_INFO oldInfo; GetConsoleScreenBufferInfo( GetStdHandle( STD_OUTPUT_HANDLE ), &oldInfo ); g_InitialColor = g_LastColor = oldInfo.wAttributes & (FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE|FOREGROUND_INTENSITY); g_BackgroundFlags = oldInfo.wAttributes & (BACKGROUND_RED|BACKGROUND_GREEN|BACKGROUND_BLUE|BACKGROUND_INTENSITY); g_BadColor = 0; if (g_BackgroundFlags & BACKGROUND_RED) g_BadColor |= FOREGROUND_RED; if (g_BackgroundFlags & BACKGROUND_GREEN) g_BadColor |= FOREGROUND_GREEN; if (g_BackgroundFlags & BACKGROUND_BLUE) g_BadColor |= FOREGROUND_BLUE; if (g_BackgroundFlags & BACKGROUND_INTENSITY) g_BadColor |= FOREGROUND_INTENSITY; #endif } WORD SetConsoleTextColor( int red, int green, int blue, int intensity ) { WORD ret = g_LastColor; #if !defined( _X360 ) g_LastColor = 0; if( red ) g_LastColor |= FOREGROUND_RED; if( green ) g_LastColor |= FOREGROUND_GREEN; if( blue ) g_LastColor |= FOREGROUND_BLUE; if( intensity ) g_LastColor |= FOREGROUND_INTENSITY; // Just use the initial color if there's a match... if (g_LastColor == g_BadColor) g_LastColor = g_InitialColor; SetConsoleTextAttribute( GetStdHandle( STD_OUTPUT_HANDLE ), g_LastColor | g_BackgroundFlags ); #endif return ret; } void RestoreConsoleTextColor( WORD color ) { #if !defined( _X360 ) SetConsoleTextAttribute( GetStdHandle( STD_OUTPUT_HANDLE ), color | g_BackgroundFlags ); g_LastColor = color; #endif } #if defined( CMDLIB_NODBGLIB ) // This can go away when everything is in bin. void Error( char const *pMsg, ... ) { va_list marker; va_start( marker, pMsg ); vprintf( pMsg, marker ); va_end( marker ); exit( -1 ); } #else CRITICAL_SECTION g_SpewCS; bool g_bSpewCSInitted = false; bool g_bSuppressPrintfOutput = false; SpewRetval_t CmdLib_SpewOutputFunc( SpewType_t type, char const *pMsg ) { // Hopefully two threads won't call this simultaneously right at the start! if ( !g_bSpewCSInitted ) { InitializeCriticalSection( &g_SpewCS ); g_bSpewCSInitted = true; } WORD old; SpewRetval_t retVal; EnterCriticalSection( &g_SpewCS ); { if (( type == SPEW_MESSAGE ) || (type == SPEW_LOG )) { Color c = *GetSpewOutputColor(); if ( c.r() != 255 || c.g() != 255 || c.b() != 255 ) { // custom color old = SetConsoleTextColor( c.r(), c.g(), c.b(), c.a() ); } else { old = SetConsoleTextColor( 1, 1, 1, 0 ); } retVal = SPEW_CONTINUE; } else if( type == SPEW_WARNING ) { old = SetConsoleTextColor( 1, 1, 0, 1 ); retVal = SPEW_CONTINUE; } else if( type == SPEW_ASSERT ) { old = SetConsoleTextColor( 1, 0, 0, 1 ); retVal = SPEW_DEBUGGER; #ifdef MPI // VMPI workers don't want to bring up dialogs and suchlike. // They need to have a special function installed to handle // the exceptions and write the minidumps. // Install the function after VMPI_Init with a call: // SetupToolsMinidumpHandler( VMPI_ExceptionFilter ); if ( g_bUseMPI && !g_bMPIMaster && !Plat_IsInDebugSession() ) { // Generating an exception and letting the // installed handler handle it ::RaiseException ( 0, // dwExceptionCode EXCEPTION_NONCONTINUABLE, // dwExceptionFlags 0, // nNumberOfArguments, NULL // const ULONG_PTR* lpArguments ); // Never get here (non-continuable exception) VMPI_HandleCrash( pMsg, NULL, true ); exit( 0 ); } #endif } else if( type == SPEW_ERROR ) { old = SetConsoleTextColor( 1, 0, 0, 1 ); retVal = SPEW_ABORT; // doesn't matter.. we exit below so we can return an errorlevel (which dbg.dll doesn't do). } else { old = SetConsoleTextColor( 1, 1, 1, 1 ); retVal = SPEW_CONTINUE; } if ( !g_bSuppressPrintfOutput || type == SPEW_ERROR ) printf( "%s", pMsg ); OutputDebugString( pMsg ); if ( type == SPEW_ERROR ) { printf( "\n" ); OutputDebugString( "\n" ); } if( g_pLogFile ) { CmdLib_FPrintf( g_pLogFile, "%s", pMsg ); g_pFileSystem->Flush( g_pLogFile ); } // Dispatch to other spew hooks. FOR_EACH_LL( g_ExtraSpewHooks, i ) g_ExtraSpewHooks[i]( pMsg ); RestoreConsoleTextColor( old ); } LeaveCriticalSection( &g_SpewCS ); if ( type == SPEW_ERROR ) { CmdLib_Exit( 1 ); } return retVal; } void InstallSpewFunction() { setvbuf( stdout, NULL, _IONBF, 0 ); setvbuf( stderr, NULL, _IONBF, 0 ); SpewOutputFunc( CmdLib_SpewOutputFunc ); GetInitialColors(); } void InstallExtraSpewHook( SpewHookFn pFn ) { g_ExtraSpewHooks.AddToTail( pFn ); } #if 0 void CmdLib_AllocError( unsigned long size ) { Error( "Error trying to allocate %d bytes.\n", size ); } int CmdLib_NewHandler( size_t size ) { CmdLib_AllocError( size ); return 0; } #endif void InstallAllocationFunctions() { // _set_new_mode( 1 ); // so if malloc() fails, we exit. // _set_new_handler( CmdLib_NewHandler ); } void SetSpewFunctionLogFile( char const *pFilename ) { Assert( (!g_pLogFile) ); g_pLogFile = g_pFileSystem->Open( pFilename, "a" ); Assert( g_pLogFile ); if (!g_pLogFile) Error("Can't create LogFile:\"%s\"\n", pFilename ); CmdLib_FPrintf( g_pLogFile, "\n\n\n" ); } void CloseSpewFunctionLogFile() { if ( g_pFileSystem && g_pLogFile ) { g_pFileSystem->Close( g_pLogFile ); g_pLogFile = FILESYSTEM_INVALID_HANDLE; } } void CmdLib_AtCleanup( CleanupFn pFn ) { g_CleanupFunctions.AddToTail( pFn ); } void CmdLib_Cleanup() { CloseSpewFunctionLogFile(); CmdLib_TermFileSystem(); FOR_EACH_LL( g_CleanupFunctions, i ) g_CleanupFunctions[i](); #if defined( MPI ) // Unfortunately, when you call exit(), even if you have things registered with atexit(), // threads go into a seemingly undefined state where GetExitCodeThread gives STILL_ACTIVE // and WaitForSingleObject will stall forever on the thread. Because of this, we must cleanup // everything that uses threads before exiting. VMPI_Finalize(); #endif } void CmdLib_Exit( int exitCode ) { TerminateProcess( GetCurrentProcess(), 1 ); } #endif #endif /* =================== ExpandWildcards Mimic unix command line expansion =================== */ #define MAX_EX_ARGC 1024 int ex_argc; char *ex_argv[MAX_EX_ARGC]; #if defined( _WIN32 ) && !defined( _X360 ) #include "io.h" void ExpandWildcards (int *argc, char ***argv) { struct _finddata_t fileinfo; int handle; int i; char filename[1024]; char filebase[1024]; char *path; ex_argc = 0; for (i=0 ; i<*argc ; i++) { path = (*argv)[i]; if ( path[0] == '-' || ( !strstr(path, "*") && !strstr(path, "?") ) ) { ex_argv[ex_argc++] = path; continue; } handle = _findfirst (path, &fileinfo); if (handle == -1) return; Q_ExtractFilePath (path, filebase, sizeof( filebase )); do { sprintf (filename, "%s%s", filebase, fileinfo.name); ex_argv[ex_argc++] = copystring (filename); } while (_findnext( handle, &fileinfo ) != -1); _findclose (handle); } *argc = ex_argc; *argv = ex_argv; } #else void ExpandWildcards (int *argc, char ***argv) { } #endif // only printf if in verbose mode qboolean verbose = false; void qprintf (const char *format, ...) { if (!verbose) return; va_list argptr; va_start (argptr,format); char str[2048]; Q_vsnprintf( str, sizeof(str), format, argptr ); #if defined( CMDLIB_NODBGLIB ) printf( "%s", str ); #else Msg( "%s", str ); #endif va_end (argptr); } // ---------------------------------------------------------------------------------------------------- // // Helpers. // ---------------------------------------------------------------------------------------------------- // static void CmdLib_getwd( char *out, int outSize ) { #if defined( _WIN32 ) || defined( WIN32 ) _getcwd( out, outSize ); Q_strncat( out, "\\", outSize, COPY_ALL_CHARACTERS ); #else getcwd(out, outSize); strcat(out, "/"); #endif Q_FixSlashes( out ); } char *ExpandArg (char *path) { static char full[1024]; if (path[0] != '/' && path[0] != '\\' && path[1] != ':') { CmdLib_getwd (full, sizeof( full )); Q_strncat (full, path, sizeof( full ), COPY_ALL_CHARACTERS); } else Q_strncpy (full, path, sizeof( full )); return full; } char *ExpandPath (char *path) { static char full[1024]; if (path[0] == '/' || path[0] == '\\' || path[1] == ':') return path; sprintf (full, "%s%s", qdir, path); return full; } char *copystring(const char *s) { char *b; b = (char *)malloc(strlen(s)+1); strcpy (b, s); return b; } void GetHourMinuteSeconds( int nInputSeconds, int &nHours, int &nMinutes, int &nSeconds ) { } void GetHourMinuteSecondsString( int nInputSeconds, char *pOut, int outLen ) { int nMinutes = nInputSeconds / 60; int nSeconds = nInputSeconds - nMinutes * 60; int nHours = nMinutes / 60; nMinutes -= nHours * 60; const char *extra[2] = { "", "s" }; if ( nHours > 0 ) Q_snprintf( pOut, outLen, "%d hour%s, %d minute%s, %d second%s", nHours, extra[nHours != 1], nMinutes, extra[nMinutes != 1], nSeconds, extra[nSeconds != 1] ); else if ( nMinutes > 0 ) Q_snprintf( pOut, outLen, "%d minute%s, %d second%s", nMinutes, extra[nMinutes != 1], nSeconds, extra[nSeconds != 1] ); else Q_snprintf( pOut, outLen, "%d second%s", nSeconds, extra[nSeconds != 1] ); } void Q_mkdir (char *path) { #if defined( _WIN32 ) || defined( WIN32 ) if (_mkdir (path) != -1) return; #else if (mkdir (path, 0777) != -1) return; #endif // if (errno != EEXIST) Error ("mkdir failed %s\n", path ); } void CmdLib_InitFileSystem( const char *pFilename, int maxMemoryUsage ) { FileSystem_Init( pFilename, maxMemoryUsage ); if ( !g_pFileSystem ) Error( "CmdLib_InitFileSystem failed." ); } void CmdLib_TermFileSystem() { FileSystem_Term(); } CreateInterfaceFn CmdLib_GetFileSystemFactory() { return FileSystem_GetFactory(); } /* ============ FileTime returns -1 if not present ============ */ int FileTime (char *path) { struct stat buf; if (stat (path,&buf) == -1) return -1; return buf.st_mtime; } /* ============== COM_Parse Parse a token out of a string ============== */ char *COM_Parse (char *data) { return (char*)ParseFile( data, com_token, NULL ); } /* ============================================================================= MISC FUNCTIONS ============================================================================= */ /* ================= CheckParm Checks for the given parameter in the program's command line arguments Returns the argument number (1 to argc-1) or 0 if not present ================= */ int CheckParm (char *check) { int i; for (i = 1;iSize( f ); } FileHandle_t SafeOpenWrite ( const char *filename ) { FileHandle_t f = g_pFileSystem->Open(filename, "wb"); if (!f) { //Error ("Error opening %s: %s",filename,strerror(errno)); // BUGBUG: No way to get equivalent of errno from IFileSystem! Error ("Error opening %s! (Check for write enable)\n",filename); } return f; } #define MAX_CMDLIB_BASE_PATHS 10 static char g_pBasePaths[MAX_CMDLIB_BASE_PATHS][MAX_PATH]; static int g_NumBasePaths = 0; void CmdLib_AddBasePath( const char *pPath ) { // printf( "CmdLib_AddBasePath( \"%s\" )\n", pPath ); if( g_NumBasePaths < MAX_CMDLIB_BASE_PATHS ) { Q_strncpy( g_pBasePaths[g_NumBasePaths], pPath, MAX_PATH ); Q_FixSlashes( g_pBasePaths[g_NumBasePaths] ); g_NumBasePaths++; } else { Assert( 0 ); } } bool CmdLib_HasBasePath( const char *pFileName_, int &pathLength ) { char *pFileName = ( char * )_alloca( strlen( pFileName_ ) + 1 ); strcpy( pFileName, pFileName_ ); Q_FixSlashes( pFileName ); pathLength = 0; int i; for( i = 0; i < g_NumBasePaths; i++ ) { // see if we can rip the base off of the filename. if( Q_strncasecmp( g_pBasePaths[i], pFileName, strlen( g_pBasePaths[i] ) ) == 0 ) { pathLength = strlen( g_pBasePaths[i] ); return true; } } return false; } int CmdLib_GetNumBasePaths( void ) { return g_NumBasePaths; } const char *CmdLib_GetBasePath( int i ) { Assert( i >= 0 && i < g_NumBasePaths ); return g_pBasePaths[i]; } //----------------------------------------------------------------------------- // Like ExpandPath but expands the path for each base path like SafeOpenRead //----------------------------------------------------------------------------- int CmdLib_ExpandWithBasePaths( CUtlVector< CUtlString > &expandedPathList, const char *pszPath ) { int nPathLength = 0; pszPath = ExpandPath( const_cast< char * >( pszPath ) ); // Kind of redundant but it's how CmdLib_HasBasePath needs things if ( CmdLib_HasBasePath( pszPath, nPathLength ) ) { pszPath = pszPath + nPathLength; for ( int i = 0; i < CmdLib_GetNumBasePaths(); ++i ) { CUtlString &expandedPath = expandedPathList[ expandedPathList.AddToTail( CmdLib_GetBasePath( i ) ) ]; expandedPath += pszPath; } } else { expandedPathList.AddToTail( pszPath ); } return expandedPathList.Count(); } FileHandle_t SafeOpenRead( const char *filename ) { int pathLength; FileHandle_t f = 0; if( CmdLib_HasBasePath( filename, pathLength ) ) { filename = filename + pathLength; int i; for( i = 0; i < g_NumBasePaths; i++ ) { char tmp[MAX_PATH]; strcpy( tmp, g_pBasePaths[i] ); strcat( tmp, filename ); f = g_pFileSystem->Open( tmp, "rb" ); if( f ) { return f; } } Error ("Error opening %s\n",filename ); return f; } else { f = g_pFileSystem->Open( filename, "rb" ); if ( !f ) Error ("Error opening %s",filename ); return f; } } void SafeRead( FileHandle_t f, void *buffer, int count) { if ( g_pFileSystem->Read (buffer, count, f) != (size_t)count) Error ("File read failure"); } void SafeWrite ( FileHandle_t f, void *buffer, int count) { if (g_pFileSystem->Write (buffer, count, f) != (size_t)count) Error ("File write failure"); } /* ============== FileExists ============== */ qboolean FileExists ( const char *filename ) { FileHandle_t hFile = g_pFileSystem->Open( filename, "rb" ); if ( hFile == FILESYSTEM_INVALID_HANDLE ) { return false; } else { g_pFileSystem->Close( hFile ); return true; } } /* ============== LoadFile ============== */ int LoadFile ( const char *filename, void **bufferptr ) { int length = 0; void *buffer; FileHandle_t f = SafeOpenRead (filename); if ( FILESYSTEM_INVALID_HANDLE != f ) { length = Q_filelength (f); buffer = malloc (length+1); ((char *)buffer)[length] = 0; SafeRead (f, buffer, length); g_pFileSystem->Close (f); *bufferptr = buffer; } else { *bufferptr = NULL; } return length; } /* ============== SaveFile ============== */ void SaveFile ( const char *filename, void *buffer, int count ) { FileHandle_t f = SafeOpenWrite (filename); SafeWrite (f, buffer, count); g_pFileSystem->Close (f); } /* ==================== Extract file parts ==================== */ // FIXME: should include the slash, otherwise // backing to an empty path will be wrong when appending a slash /* ============== ParseNum / ParseHex ============== */ int ParseHex (char *hex) { char *str; int num; num = 0; str = hex; while (*str) { num <<= 4; if (*str >= '0' && *str <= '9') num += *str-'0'; else if (*str >= 'a' && *str <= 'f') num += 10 + *str-'a'; else if (*str >= 'A' && *str <= 'F') num += 10 + *str-'A'; else Error ("Bad hex number: %s",hex); str++; } return num; } int ParseNum (char *str) { if (str[0] == '$') return ParseHex (str+1); if (str[0] == '0' && str[1] == 'x') return ParseHex (str+2); return atol (str); } /* ============ CreatePath ============ */ void CreatePath (char *path) { char *ofs, c; // strip the drive if (path[1] == ':') path += 2; for (ofs = path+1 ; *ofs ; ofs++) { c = *ofs; if (c == '/' || c == '\\') { // create the directory *ofs = 0; Q_mkdir (path); *ofs = c; } } } //----------------------------------------------------------------------------- // Creates a path, path may already exist //----------------------------------------------------------------------------- #if defined( _WIN32 ) || defined( WIN32 ) void SafeCreatePath( char *path ) { char *ptr; // skip past the drive path, but don't strip if ( path[1] == ':' ) { ptr = strchr( path, '\\' ); } else { ptr = path; } while ( ptr ) { ptr = strchr( ptr+1, '\\' ); if ( ptr ) { *ptr = '\0'; _mkdir( path ); *ptr = '\\'; } } } #endif /* ============ QCopyFile Used to archive source files ============ */ void QCopyFile (char *from, char *to) { void *buffer; int length; length = LoadFile (from, &buffer); CreatePath (to); SaveFile (to, buffer, length); free (buffer); }