diff options
Diffstat (limited to 'utils/vp4mutex')
| -rw-r--r-- | utils/vp4mutex/vp4mutex.cpp | 876 | ||||
| -rw-r--r-- | utils/vp4mutex/vp4mutex.vpc | 39 |
2 files changed, 915 insertions, 0 deletions
diff --git a/utils/vp4mutex/vp4mutex.cpp b/utils/vp4mutex/vp4mutex.cpp new file mode 100644 index 0000000..d0ed24a --- /dev/null +++ b/utils/vp4mutex/vp4mutex.cpp @@ -0,0 +1,876 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// vp4mutex.cpp : Defines the entry point for the console application. +// + +#define Error DbgError + +#include "tier0/platform.h" + +#include <stdio.h> +#include <conio.h> +#include <vector> // SEE NOTES BELOW ABOUT REVERTING THIS IF REQUIRED + +#undef Error +#undef Verify + +#include "clientapi.h" + +#include <time.h> +#include <ctype.h> +#include <windows.h> + +#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<T> &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<T> 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<CP4Counter> +{ +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 > <branchname> <sleepseconds> <clientname> [<ip:port>]\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; +} + diff --git a/utils/vp4mutex/vp4mutex.vpc b/utils/vp4mutex/vp4mutex.vpc new file mode 100644 index 0000000..35f04fa --- /dev/null +++ b/utils/vp4mutex/vp4mutex.vpc @@ -0,0 +1,39 @@ +//----------------------------------------------------------------------------- +// VP4MUTEX.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR "..\.." +$Macro OUTBINDIR "$SRCDIR\devtools\bin" +$Macro OUTBINNAME "p4mutex" + +$Include "$SRCDIR\vpc_scripts\source_exe_con_win32_base.vpc" + +$Configuration +{ + $Compiler + { + $AdditionalIncludeDirectories "$BASE;$SRCDIR\common\p4api" + } + + $Linker + { + $AdditionalDependencies "$BASE ws2_32.lib" + } +} + +$Project "Vp4mutex" +{ + $Folder "Source Files" + { + $File "vp4mutex.cpp" + } + + $Folder "Link Libraries" + { + $File "$SRCDIR\lib\common\p4api\libclient.lib" + $File "$SRCDIR\lib\common\p4api\librpc.lib" + $File "$SRCDIR\lib\common\p4api\libsupp.lib" + } +} |