//========= Copyright Valve Corporation, All rights reserved. ============// // vp4mutex.cpp : Defines the entry point for the console application. // #define Error DbgError #include "tier0/platform.h" #include #include #include // SEE NOTES BELOW ABOUT REVERTING THIS IF REQUIRED #undef Error #undef Verify #include "clientapi.h" #include #include #include #undef SetPort #define RemoveAll clear #define AddToTail push_back #define Count size #define CLIENTSPEC_BUFFER_SIZE (8 * 1024) //----------------------------------------------------------------------------- // internal //----------------------------------------------------------------------------- ClientApi client; ClientUser user; // // NOTE: All of this crap is here since we don't want to have the .exe depend on tier0 or vstdlib.dll. If we change that, this can go away and std::vector can go back // to CUtlVector and CUtlSymbol can be used like it's supposed to be used.... // // // // static void Q_strncpy( char *pDest, char const *pSrc, int maxLen ) { strncpy( pDest, pSrc, maxLen ); if ( maxLen > 0 ) { pDest[maxLen-1] = 0; } } static int Q_strlen( const char *str ) { return strlen( str ); } #if defined( _WIN32 ) || defined( WIN32 ) #define PATHSEPARATOR(c) ((c) == '\\' || (c) == '/') #else //_WIN32 #define PATHSEPARATOR(c) ((c) == '/') #endif //_WIN32 static void Q_FileBase( const char *in, char *out, int maxlen ) { if ( !in || !in[ 0 ] ) { *out = 0; return; } int len, start, end; len = Q_strlen( in ); // scan backward for '.' end = len - 1; while ( end&& in[end] != '.' && !PATHSEPARATOR( in[end] ) ) { end--; } if ( in[end] != '.' ) // no '.', copy to end { end = len-1; } else { end--; // Found ',', copy to left of '.' } // Scan backward for '/' start = len-1; while ( start >= 0 && !PATHSEPARATOR( in[start] ) ) { start--; } if ( start < 0 || !PATHSEPARATOR( in[start] ) ) { start = 0; } else { start++; } // Length of new sting len = end - start + 1; int maxcopy = min( len + 1, maxlen ); // Copy partial string Q_strncpy( out, &in[start], maxcopy ); } #define COPY_ALL_CHARACTERS -1 static char *Q_strncat(char *pDest, const char *pSrc, size_t destBufferSize, int max_chars_to_copy ) { size_t charstocopy = (size_t)0; size_t len = strlen(pDest); size_t srclen = strlen( pSrc ); if ( max_chars_to_copy <= COPY_ALL_CHARACTERS ) { charstocopy = srclen; } else { charstocopy = (size_t)min( max_chars_to_copy, (int)srclen ); } if ( len + charstocopy >= destBufferSize ) { charstocopy = destBufferSize - len - 1; } if ( !charstocopy ) { return pDest; } char *pOut = strncat( pDest, pSrc, charstocopy ); pOut[destBufferSize-1] = 0; return pOut; } //----------------------------------------------------------------------------- // Finds a string in another string with a case insensitive test //----------------------------------------------------------------------------- static char const* Q_stristr( char const* pStr, char const* pSearch ) { if (!pStr || !pSearch) return 0; char const* pLetter = pStr; // Check the entire string while (*pLetter != 0) { // Skip over non-matches if (tolower((unsigned char)*pLetter) == tolower((unsigned char)*pSearch)) { // Check for match char const* pMatch = pLetter + 1; char const* pTest = pSearch + 1; while (*pTest != 0) { // We've run off the end; don't bother. if (*pMatch == 0) return 0; if (tolower((unsigned char)*pMatch) != tolower((unsigned char)*pTest)) break; ++pMatch; ++pTest; } // Found a match! if (*pTest == 0) return pLetter; } ++pLetter; } return 0; } static int Q_stricmp( const char *s1, const char *s2 ) { return stricmp( s1, s2 ); } //----------------------------------------------------------------------------- // Purpose: utility function to split a typical P4 line output into var and value //----------------------------------------------------------------------------- static void SplitP4Output(const_char *data, char *pszCmd, char *pszInfo, int bufLen) { Q_strncpy(pszCmd, data, bufLen); char *mid = (char *)Q_stristr(pszCmd, " "); if (mid) { *mid = 0; Q_strncpy(pszInfo, data + (mid - pszCmd) + 1, bufLen); } else { pszInfo[0] = 0; } } static int Q_atoi (const char *str) { int val; int sign; int c; if (*str == '-') { sign = -1; str++; } else sign = 1; val = 0; // // check for hex // if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X') ) { str += 2; while (1) { c = *str++; if (c >= '0' && c <= '9') val = (val<<4) + c - '0'; else if (c >= 'a' && c <= 'f') val = (val<<4) + c - 'a' + 10; else if (c >= 'A' && c <= 'F') val = (val<<4) + c - 'A' + 10; else return val*sign; } } // // check for character // if (str[0] == '\'') { return sign * str[1]; } // // assume decimal // while (1) { c = *str++; if (c <'0' || c > '9') return val*sign; val = val*10 + c - '0'; } return 0; } static int Q_snprintf( char *pDest, int maxLen, char const *pFormat, ... ) { va_list marker; va_start( marker, pFormat ); #ifdef _WIN32 int len = _vsnprintf( pDest, maxLen, pFormat, marker ); #elif _LINUX int len = vsnprintf( pDest, maxLen, pFormat, marker ); #else #error "define vsnprintf type." #endif va_end( marker ); // Len < 0 represents an overflow if( len < 0 ) { len = maxLen; pDest[maxLen-1] = 0; } return len; } //----------------------------------------------------------------------------- // Purpose: base class for parse input from the P4 server //----------------------------------------------------------------------------- template< class T > class CDataRetrievalUser : public ClientUser { public: std::vector &GetData() { return m_Data; } // call this to start retrieving data void InitRetrievingData() { m_bAwaitingNewRecord = true; m_Data.RemoveAll(); } // implement this to parse out input from the server into the specified object virtual void OutputRecord(T &obj, const char *pszVar, const char *pszInfo) = 0; private: bool m_bAwaitingNewRecord; std::vector m_Data; virtual void OutputInfo(char level, const_char *data) { if (Q_strlen(data) < 1) { // end of a record, await the new one m_bAwaitingNewRecord = true; return; } if (m_bAwaitingNewRecord) { // add in the new record T newRec; m_Data.AddToTail( newRec ); T &record = m_Data[ m_Data.Count() - 1 ]; memset(&record, 0, sizeof(record)); m_bAwaitingNewRecord = false; } // parse char szVar[_MAX_PATH]; char szInfo[_MAX_PATH]; SplitP4Output(data, szVar, szInfo, sizeof(szVar)); // emit T &record = m_Data[m_Data.Count() - 1]; OutputRecord(record, szVar, szInfo); } }; class CP4Counter { public: CP4Counter() : m_nValue( 0 ) { m_szName[ 0 ] = 0; } char const *GetCounterName() const { return m_szName; } int GetValue() const { return m_nValue; } void SetName( char const *name ) { Q_strncpy( m_szName, name, sizeof( m_szName ) ); } void SetValue( int value ) { m_nValue = value; } private: char m_szName[ 128 ]; int m_nValue; }; //----------------------------------------------------------------------------- // Purpose: Retrieves a file list //----------------------------------------------------------------------------- class CCountersUser : public CDataRetrievalUser { public: void RetrieveCounters() { // clear the list InitRetrievingData(); client.Run("counters", this); } private: virtual void OutputRecord(CP4Counter &counter, const char *szCmd, const char *szInfo) { if ( !Q_stricmp( szCmd, "counter" ) ) { counter.SetName( szInfo ); } else if ( !Q_stricmp( szCmd, "value" ) ) { counter.SetValue( Q_atoi( szInfo ) ); } } }; //----------------------------------------------------------------------------- // Purpose: Retrieves a file list //----------------------------------------------------------------------------- class CSetCounterUser : public ClientUser { public: void SetCounter( char *countername, int value ) { char valuestr[ 32 ]; Q_snprintf( valuestr, sizeof( valuestr ), "%d", value ); char *argv[] = { countername, valuestr, NULL }; client.SetArgv( 2, argv ); client.Run("counter", this); } virtual void HandleError( Error *err ) { } virtual void Message( Error *err ) { } virtual void OutputError( const_char *errBuf ) { } virtual void OutputInfo( char level, const_char *data ) { } virtual void OutputBinary( const_char *data, int length ) { } virtual void OutputText( const_char *data, int length ) { } }; static CSetCounterUser g_SetCounterUser; static CCountersUser g_CountersUser; typedef enum { MUTEX_QUERY = 0, MUTEX_LOCK, MUTEX_RELEASE, } MUTEXACTION; static void printusage( char const *basefile ) { printf( "usage: %s \n\ \t< query | release | lock > []\n\ \te.g.:\n\ \t%s query src_main 3 yahn\n\ \t%s query\n\ ", basefile, basefile, basefile ); } struct CUtlSymbol { public: CUtlSymbol() { m_szValue[ 0 ] = 0; } CUtlSymbol& operator =( const char * lhs ) { Q_strncpy( m_szValue, lhs, sizeof( m_szValue ) ); return *this; } char const *String() { return m_szValue; } private: char m_szValue[ 256 ]; }; int FindLockUsers( CCountersUser& counters, char const *branchspec, std::vector< CUtlSymbol >& users, std::vector< int >& locktimes, std::vector< CUtlSymbol >* branchnames = NULL ) { users.RemoveAll(); char lockstr[ 256 ]; if ( branchspec != NULL ) { Q_snprintf( lockstr, sizeof( lockstr ), "%s_lock_", branchspec ); } else { Q_snprintf( lockstr, sizeof( lockstr ), "_lock_", branchspec ); } counters.RetrieveCounters(); std::vector< CP4Counter >& list = counters.GetData(); int count = list.Count(); for ( int i = 0; i < count; ++i ) { char const *name = list[ i ].GetCounterName(); int value = list[ i ].GetValue(); char const *p = Q_stristr( name, lockstr ); if ( !p ) continue; if ( value != 0 ) { CUtlSymbol sym; sym = p + Q_strlen( lockstr ); users.AddToTail( sym ); locktimes.AddToTail( value ); if ( branchnames ) { char branchname[ 512 ]; Q_strncpy( branchname, name, p - name + 1 ); CUtlSymbol sym; sym = branchname; branchnames->AddToTail( sym ); } } } return users.Count(); } static void GetHourMinuteSecondsString( int nInputSeconds, char *pOut, int outLen ) { int nMinutes = nInputSeconds / 60; int nSeconds = nInputSeconds - nMinutes * 60; int nHours = nMinutes / 60; nMinutes -= nHours * 60; 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] ); } static void ComputeHoldTime( int holdtime, char *buf, size_t bufsize ) { buf[ 0 ] = 0; if ( holdtime < 100 ) { Q_snprintf( buf, bufsize, "UNKNOWN" ); } else { // Prepend the time. time_t aclock = (time_t)holdtime; struct tm *newtime = localtime( &aclock ); // Get rid of the \n. Q_strncpy( buf, asctime( newtime ), bufsize ); char *pEnd = (char *)Q_stristr( buf, "\n" ); if ( pEnd ) { *pEnd = 0; } time_t curtime; time( &curtime ); int holdSeconds = curtime - holdtime; if ( holdSeconds > 0 ) { char durstring[ 256 ]; durstring[ 0 ] = 0; GetHourMinuteSecondsString( holdSeconds, durstring, sizeof( durstring ) ); Q_strncat( buf, ", held for ", bufsize, COPY_ALL_CHARACTERS ); Q_strncat( buf, durstring, bufsize, COPY_ALL_CHARACTERS ); } } } int _tmain(int argc, _TCHAR* argv[]) { char basefile[ 256 ]; Q_FileBase( argv[ 0 ], basefile, sizeof( basefile ) ); bool validAction = false; MUTEXACTION action = MUTEX_QUERY; bool validBranch = false; CUtlSymbol branchspec; bool validSleepSeconds = false; int sleepSeconds = 1; bool validClient = false; CUtlSymbol clientname; bool validIP = false; CUtlSymbol ipport; for ( int i = 1; i < argc; ++i ) { switch ( i ) { default: break; case 1: validAction = true; if ( !Q_stricmp( argv[ i ], "query" ) ) { action = MUTEX_QUERY; } else if ( !Q_stricmp( argv[ i ], "release" ) ) { action = MUTEX_RELEASE; } else if ( !Q_stricmp( argv[ i ], "lock" ) ) { action = MUTEX_LOCK; } else { validAction = false; } break; case 2: { validBranch = true; branchspec = argv[ i ]; } break; case 3: { validSleepSeconds = true; sleepSeconds = clamp( Q_atoi( argv[ i ] ), 0, 100 ); } break; case 4: { validClient = true; clientname = argv[ i ]; } break; case 5: { validIP = true; ipport = argv[ i ]; } break; } } bool describeLocksOnly = false; if ( !validBranch || !validSleepSeconds || !validClient || !validIP ) { if ( !validAction || action != MUTEX_QUERY ) { printusage( basefile ); return -1; } describeLocksOnly = true; sleepSeconds = 5; } // set the protocol return all data as key/value pairs client.SetProtocol( "tag", "" ); // connect to the p4 server Error e; if ( ipport.String()[ 0 ] ) { client.SetPort( ipport.String() ); } if ( clientname.String()[ 0 ] ) { client.SetUser( clientname.String() ); } client.Init( &e ); bool connected = ( e.Test() == 0 ) ? true : false; if ( !connected ) { printf( "Unable to connect to perforce server\n" ); return -1; } if ( describeLocksOnly ) { std::vector< CUtlSymbol > users; std::vector< int > locktimes; std::vector< CUtlSymbol > branchnames; int holdCount = FindLockUsers( g_CountersUser, NULL, users, locktimes, &branchnames ); for ( int i = 0; i < holdCount; ++i ) { char timestr[ 128 ]; ComputeHoldTime( locktimes[ i ], timestr, sizeof( timestr ) ); printf( "'%s' HELD by: %s\n\ttime: %s\n", branchnames[ i ].String(), users[ i ].String(), timestr ); } } else { std::vector< CUtlSymbol > users; std::vector< int > locktimes; int holdCount = FindLockUsers( g_CountersUser, branchspec.String(), users, locktimes ); if ( holdCount >= 2 ) { char userlist[ 1024 ]; userlist[ 0 ] = 0; for ( int i = 0; i < (int)users.Count(); ++i ) { Q_strncat( userlist, users[ i ].String(), sizeof( userlist ), COPY_ALL_CHARACTERS ); if ( i != users.Count() - 1 ) { Q_strncat( userlist, ", ", sizeof( userlist ), COPY_ALL_CHARACTERS ); } } printf( "%s: ERROR, multiple users (%s) holding lock on '%s'\n", basefile, userlist, branchspec.String() ); printusage( basefile ); return -1; } char setcountername[ 256 ]; Q_snprintf( setcountername, sizeof( setcountername ), "%s_lock_%s", branchspec.String(), clientname.String() ); switch ( action ) { default: break; case MUTEX_QUERY: { if ( holdCount == 1 ) { bool isHeldByLocal = false; if ( !Q_stricmp( users[ 0 ].String(), clientname.String() ) ) { isHeldByLocal = true; } char timestr[ 128 ]; ComputeHoldTime( locktimes[ 0 ], timestr, sizeof( timestr ) ); printf( "%s: '%s' lock on %s is HELD by: %s\nHOLD INFO: %s\n", basefile, branchspec.String(), ipport.String(), users[ 0 ].String(), timestr ); } else if ( holdCount == 0 ) { printf( "%s: '%s' lock on %s is FREE\n", basefile, branchspec.String(), ipport.String() ); } } break; case MUTEX_LOCK: { printf( "%s: Attempting to lock the '%s' codeline for %s on %s\n\n", basefile, branchspec.String(), clientname.String(), ipport.String() ); // p4mutex: Attempting to lock the 'main_src' codeline for yahn on 207.173.178.12:1666. if ( holdCount == 1 ) { bool isHeldByLocal = false; if ( !Q_stricmp( users[ 0 ].String(), clientname.String() ) ) { isHeldByLocal = true; } char timestr[ 128 ]; ComputeHoldTime( locktimes[ 0 ], timestr, sizeof( timestr ) ); if ( isHeldByLocal ) { // Success: You already have the 'main_src' codeline lock. printf( "Success: You already have the '%s' codeline lock\nInfo: %s\n", branchspec.String(), timestr ); } else { // Failed: 'main_goldsrc' lock currently owned by alfred printf( "Failed: '%s' lock currently owned by %s\nInfo: %s\n", branchspec.String(), users[ 0 ].String(), timestr ); } } else if ( holdCount == 0 ) { // Success: 'main_src' codeline lock granted to yahn. // Set the counter time_t aclock; time( &aclock ); g_SetCounterUser.SetCounter( setcountername, (int)aclock ); printf( "Success: '%s' codeline lock granted to %s\n", branchspec.String(), clientname.String() ); } } break; case MUTEX_RELEASE: { printf( "%s: Attempting to release the '%s' codeline for %s on %s\n\n", basefile, branchspec.String(), clientname.String(), ipport.String() ); // p4mutex: Attempting to release the 'main_src' codeline for yahn on 207.173.178.12:1666. if ( holdCount == 1 ) { bool isHeldByLocal = false; if ( !Q_stricmp( users[ 0 ].String(), clientname.String() ) ) { isHeldByLocal = true; } char timestr[ 128 ]; ComputeHoldTime( locktimes[ 0 ], timestr, sizeof( timestr ) ); if ( isHeldByLocal ) { // Success: 'main_src' codeline lock released. // Set the counter g_SetCounterUser.SetCounter( setcountername, 0 ); printf( "Success: '%s' codeline lock released.\n", branchspec.String() ); } else { // Failed: 'main_goldsrc' lock currently owned by alfred printf( "Failed: '%s' lock currently owned by %s\nInfo: %s\n", branchspec.String(), users[ 0 ].String(), timestr ); } } else if ( holdCount == 0 ) { // Success: The 'main_src' codeline lock is already free. printf( "Success: The '%s' codeline lock is already free\n", branchspec.String() ); } } break; } } if ( sleepSeconds > 0 ) { time_t starttime; time( &starttime ); int elapsed = 0; do { time_t curtime; time( &curtime ); elapsed = curtime - starttime; Sleep( 50 ); } while ( elapsed < sleepSeconds && !kbhit() ); } return 0; }