|
|
//========= 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; }
|