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.

876 lines
18 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. // vp4mutex.cpp : Defines the entry point for the console application.
  3. //
  4. #define Error DbgError
  5. #include "tier0/platform.h"
  6. #include <stdio.h>
  7. #include <conio.h>
  8. #include <vector> // SEE NOTES BELOW ABOUT REVERTING THIS IF REQUIRED
  9. #undef Error
  10. #undef Verify
  11. #include "clientapi.h"
  12. #include <time.h>
  13. #include <ctype.h>
  14. #include <windows.h>
  15. #undef SetPort
  16. #define RemoveAll clear
  17. #define AddToTail push_back
  18. #define Count size
  19. #define CLIENTSPEC_BUFFER_SIZE (8 * 1024)
  20. //-----------------------------------------------------------------------------
  21. // internal
  22. //-----------------------------------------------------------------------------
  23. ClientApi client;
  24. ClientUser user;
  25. //
  26. // 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
  27. // to CUtlVector and CUtlSymbol can be used like it's supposed to be used....
  28. //
  29. //
  30. //
  31. //
  32. static void Q_strncpy( char *pDest, char const *pSrc, int maxLen )
  33. {
  34. strncpy( pDest, pSrc, maxLen );
  35. if ( maxLen > 0 )
  36. {
  37. pDest[maxLen-1] = 0;
  38. }
  39. }
  40. static int Q_strlen( const char *str )
  41. {
  42. return strlen( str );
  43. }
  44. #if defined( _WIN32 ) || defined( WIN32 )
  45. #define PATHSEPARATOR(c) ((c) == '\\' || (c) == '/')
  46. #else //_WIN32
  47. #define PATHSEPARATOR(c) ((c) == '/')
  48. #endif //_WIN32
  49. static void Q_FileBase( const char *in, char *out, int maxlen )
  50. {
  51. if ( !in || !in[ 0 ] )
  52. {
  53. *out = 0;
  54. return;
  55. }
  56. int len, start, end;
  57. len = Q_strlen( in );
  58. // scan backward for '.'
  59. end = len - 1;
  60. while ( end&& in[end] != '.' && !PATHSEPARATOR( in[end] ) )
  61. {
  62. end--;
  63. }
  64. if ( in[end] != '.' ) // no '.', copy to end
  65. {
  66. end = len-1;
  67. }
  68. else
  69. {
  70. end--; // Found ',', copy to left of '.'
  71. }
  72. // Scan backward for '/'
  73. start = len-1;
  74. while ( start >= 0 && !PATHSEPARATOR( in[start] ) )
  75. {
  76. start--;
  77. }
  78. if ( start < 0 || !PATHSEPARATOR( in[start] ) )
  79. {
  80. start = 0;
  81. }
  82. else
  83. {
  84. start++;
  85. }
  86. // Length of new sting
  87. len = end - start + 1;
  88. int maxcopy = min( len + 1, maxlen );
  89. // Copy partial string
  90. Q_strncpy( out, &in[start], maxcopy );
  91. }
  92. #define COPY_ALL_CHARACTERS -1
  93. static char *Q_strncat(char *pDest, const char *pSrc, size_t destBufferSize, int max_chars_to_copy )
  94. {
  95. size_t charstocopy = (size_t)0;
  96. size_t len = strlen(pDest);
  97. size_t srclen = strlen( pSrc );
  98. if ( max_chars_to_copy <= COPY_ALL_CHARACTERS )
  99. {
  100. charstocopy = srclen;
  101. }
  102. else
  103. {
  104. charstocopy = (size_t)min( max_chars_to_copy, (int)srclen );
  105. }
  106. if ( len + charstocopy >= destBufferSize )
  107. {
  108. charstocopy = destBufferSize - len - 1;
  109. }
  110. if ( !charstocopy )
  111. {
  112. return pDest;
  113. }
  114. char *pOut = strncat( pDest, pSrc, charstocopy );
  115. pOut[destBufferSize-1] = 0;
  116. return pOut;
  117. }
  118. //-----------------------------------------------------------------------------
  119. // Finds a string in another string with a case insensitive test
  120. //-----------------------------------------------------------------------------
  121. static char const* Q_stristr( char const* pStr, char const* pSearch )
  122. {
  123. if (!pStr || !pSearch)
  124. return 0;
  125. char const* pLetter = pStr;
  126. // Check the entire string
  127. while (*pLetter != 0)
  128. {
  129. // Skip over non-matches
  130. if (tolower((unsigned char)*pLetter) == tolower((unsigned char)*pSearch))
  131. {
  132. // Check for match
  133. char const* pMatch = pLetter + 1;
  134. char const* pTest = pSearch + 1;
  135. while (*pTest != 0)
  136. {
  137. // We've run off the end; don't bother.
  138. if (*pMatch == 0)
  139. return 0;
  140. if (tolower((unsigned char)*pMatch) != tolower((unsigned char)*pTest))
  141. break;
  142. ++pMatch;
  143. ++pTest;
  144. }
  145. // Found a match!
  146. if (*pTest == 0)
  147. return pLetter;
  148. }
  149. ++pLetter;
  150. }
  151. return 0;
  152. }
  153. static int Q_stricmp( const char *s1, const char *s2 )
  154. {
  155. return stricmp( s1, s2 );
  156. }
  157. //-----------------------------------------------------------------------------
  158. // Purpose: utility function to split a typical P4 line output into var and value
  159. //-----------------------------------------------------------------------------
  160. static void SplitP4Output(const_char *data, char *pszCmd, char *pszInfo, int bufLen)
  161. {
  162. Q_strncpy(pszCmd, data, bufLen);
  163. char *mid = (char *)Q_stristr(pszCmd, " ");
  164. if (mid)
  165. {
  166. *mid = 0;
  167. Q_strncpy(pszInfo, data + (mid - pszCmd) + 1, bufLen);
  168. }
  169. else
  170. {
  171. pszInfo[0] = 0;
  172. }
  173. }
  174. static int Q_atoi (const char *str)
  175. {
  176. int val;
  177. int sign;
  178. int c;
  179. if (*str == '-')
  180. {
  181. sign = -1;
  182. str++;
  183. }
  184. else
  185. sign = 1;
  186. val = 0;
  187. //
  188. // check for hex
  189. //
  190. if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X') )
  191. {
  192. str += 2;
  193. while (1)
  194. {
  195. c = *str++;
  196. if (c >= '0' && c <= '9')
  197. val = (val<<4) + c - '0';
  198. else if (c >= 'a' && c <= 'f')
  199. val = (val<<4) + c - 'a' + 10;
  200. else if (c >= 'A' && c <= 'F')
  201. val = (val<<4) + c - 'A' + 10;
  202. else
  203. return val*sign;
  204. }
  205. }
  206. //
  207. // check for character
  208. //
  209. if (str[0] == '\'')
  210. {
  211. return sign * str[1];
  212. }
  213. //
  214. // assume decimal
  215. //
  216. while (1)
  217. {
  218. c = *str++;
  219. if (c <'0' || c > '9')
  220. return val*sign;
  221. val = val*10 + c - '0';
  222. }
  223. return 0;
  224. }
  225. static int Q_snprintf( char *pDest, int maxLen, char const *pFormat, ... )
  226. {
  227. va_list marker;
  228. va_start( marker, pFormat );
  229. #ifdef _WIN32
  230. int len = _vsnprintf( pDest, maxLen, pFormat, marker );
  231. #elif _LINUX
  232. int len = vsnprintf( pDest, maxLen, pFormat, marker );
  233. #else
  234. #error "define vsnprintf type."
  235. #endif
  236. va_end( marker );
  237. // Len < 0 represents an overflow
  238. if( len < 0 )
  239. {
  240. len = maxLen;
  241. pDest[maxLen-1] = 0;
  242. }
  243. return len;
  244. }
  245. //-----------------------------------------------------------------------------
  246. // Purpose: base class for parse input from the P4 server
  247. //-----------------------------------------------------------------------------
  248. template< class T >
  249. class CDataRetrievalUser : public ClientUser
  250. {
  251. public:
  252. std::vector<T> &GetData()
  253. {
  254. return m_Data;
  255. }
  256. // call this to start retrieving data
  257. void InitRetrievingData()
  258. {
  259. m_bAwaitingNewRecord = true;
  260. m_Data.RemoveAll();
  261. }
  262. // implement this to parse out input from the server into the specified object
  263. virtual void OutputRecord(T &obj, const char *pszVar, const char *pszInfo) = 0;
  264. private:
  265. bool m_bAwaitingNewRecord;
  266. std::vector<T> m_Data;
  267. virtual void OutputInfo(char level, const_char *data)
  268. {
  269. if (Q_strlen(data) < 1)
  270. {
  271. // end of a record, await the new one
  272. m_bAwaitingNewRecord = true;
  273. return;
  274. }
  275. if (m_bAwaitingNewRecord)
  276. {
  277. // add in the new record
  278. T newRec;
  279. m_Data.AddToTail( newRec );
  280. T &record = m_Data[ m_Data.Count() - 1 ];
  281. memset(&record, 0, sizeof(record));
  282. m_bAwaitingNewRecord = false;
  283. }
  284. // parse
  285. char szVar[_MAX_PATH];
  286. char szInfo[_MAX_PATH];
  287. SplitP4Output(data, szVar, szInfo, sizeof(szVar));
  288. // emit
  289. T &record = m_Data[m_Data.Count() - 1];
  290. OutputRecord(record, szVar, szInfo);
  291. }
  292. };
  293. class CP4Counter
  294. {
  295. public:
  296. CP4Counter() :
  297. m_nValue( 0 )
  298. {
  299. m_szName[ 0 ] = 0;
  300. }
  301. char const *GetCounterName() const
  302. {
  303. return m_szName;
  304. }
  305. int GetValue() const
  306. {
  307. return m_nValue;
  308. }
  309. void SetName( char const *name )
  310. {
  311. Q_strncpy( m_szName, name, sizeof( m_szName ) );
  312. }
  313. void SetValue( int value )
  314. {
  315. m_nValue = value;
  316. }
  317. private:
  318. char m_szName[ 128 ];
  319. int m_nValue;
  320. };
  321. //-----------------------------------------------------------------------------
  322. // Purpose: Retrieves a file list
  323. //-----------------------------------------------------------------------------
  324. class CCountersUser : public CDataRetrievalUser<CP4Counter>
  325. {
  326. public:
  327. void RetrieveCounters()
  328. {
  329. // clear the list
  330. InitRetrievingData();
  331. client.Run("counters", this);
  332. }
  333. private:
  334. virtual void OutputRecord(CP4Counter &counter, const char *szCmd, const char *szInfo)
  335. {
  336. if ( !Q_stricmp( szCmd, "counter" ) )
  337. {
  338. counter.SetName( szInfo );
  339. }
  340. else if ( !Q_stricmp( szCmd, "value" ) )
  341. {
  342. counter.SetValue( Q_atoi( szInfo ) );
  343. }
  344. }
  345. };
  346. //-----------------------------------------------------------------------------
  347. // Purpose: Retrieves a file list
  348. //-----------------------------------------------------------------------------
  349. class CSetCounterUser : public ClientUser
  350. {
  351. public:
  352. void SetCounter( char *countername, int value )
  353. {
  354. char valuestr[ 32 ];
  355. Q_snprintf( valuestr, sizeof( valuestr ), "%d", value );
  356. char *argv[] = { countername, valuestr, NULL };
  357. client.SetArgv( 2, argv );
  358. client.Run("counter", this);
  359. }
  360. virtual void HandleError( Error *err )
  361. {
  362. }
  363. virtual void Message( Error *err )
  364. {
  365. }
  366. virtual void OutputError( const_char *errBuf )
  367. {
  368. }
  369. virtual void OutputInfo( char level, const_char *data )
  370. {
  371. }
  372. virtual void OutputBinary( const_char *data, int length )
  373. {
  374. }
  375. virtual void OutputText( const_char *data, int length )
  376. {
  377. }
  378. };
  379. static CSetCounterUser g_SetCounterUser;
  380. static CCountersUser g_CountersUser;
  381. typedef enum
  382. {
  383. MUTEX_QUERY = 0,
  384. MUTEX_LOCK,
  385. MUTEX_RELEASE,
  386. } MUTEXACTION;
  387. static void printusage( char const *basefile )
  388. {
  389. printf( "usage: %s \n\
  390. \t< query | release | lock > <branchname> <sleepseconds> <clientname> [<ip:port>]\n\
  391. \te.g.:\n\
  392. \t%s query src_main 3 yahn\n\
  393. \t%s query\n\
  394. ", basefile, basefile, basefile );
  395. }
  396. struct CUtlSymbol
  397. {
  398. public:
  399. CUtlSymbol()
  400. {
  401. m_szValue[ 0 ] = 0;
  402. }
  403. CUtlSymbol& operator =( const char * lhs )
  404. {
  405. Q_strncpy( m_szValue, lhs, sizeof( m_szValue ) );
  406. return *this;
  407. }
  408. char const *String()
  409. {
  410. return m_szValue;
  411. }
  412. private:
  413. char m_szValue[ 256 ];
  414. };
  415. int FindLockUsers( CCountersUser& counters, char const *branchspec, std::vector< CUtlSymbol >& users, std::vector< int >& locktimes, std::vector< CUtlSymbol >* branchnames = NULL )
  416. {
  417. users.RemoveAll();
  418. char lockstr[ 256 ];
  419. if ( branchspec != NULL )
  420. {
  421. Q_snprintf( lockstr, sizeof( lockstr ), "%s_lock_", branchspec );
  422. }
  423. else
  424. {
  425. Q_snprintf( lockstr, sizeof( lockstr ), "_lock_", branchspec );
  426. }
  427. counters.RetrieveCounters();
  428. std::vector< CP4Counter >& list = counters.GetData();
  429. int count = list.Count();
  430. for ( int i = 0; i < count; ++i )
  431. {
  432. char const *name = list[ i ].GetCounterName();
  433. int value = list[ i ].GetValue();
  434. char const *p = Q_stristr( name, lockstr );
  435. if ( !p )
  436. continue;
  437. if ( value != 0 )
  438. {
  439. CUtlSymbol sym;
  440. sym = p + Q_strlen( lockstr );
  441. users.AddToTail( sym );
  442. locktimes.AddToTail( value );
  443. if ( branchnames )
  444. {
  445. char branchname[ 512 ];
  446. Q_strncpy( branchname, name, p - name + 1 );
  447. CUtlSymbol sym;
  448. sym = branchname;
  449. branchnames->AddToTail( sym );
  450. }
  451. }
  452. }
  453. return users.Count();
  454. }
  455. static void GetHourMinuteSecondsString( int nInputSeconds, char *pOut, int outLen )
  456. {
  457. int nMinutes = nInputSeconds / 60;
  458. int nSeconds = nInputSeconds - nMinutes * 60;
  459. int nHours = nMinutes / 60;
  460. nMinutes -= nHours * 60;
  461. char *extra[2] = { "", "s" };
  462. if ( nHours > 0 )
  463. 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] );
  464. else if ( nMinutes > 0 )
  465. Q_snprintf( pOut, outLen, "%d minute%s, %d second%s", nMinutes, extra[nMinutes != 1], nSeconds, extra[nSeconds != 1] );
  466. else
  467. Q_snprintf( pOut, outLen, "%d second%s", nSeconds, extra[nSeconds != 1] );
  468. }
  469. static void ComputeHoldTime( int holdtime, char *buf, size_t bufsize )
  470. {
  471. buf[ 0 ] = 0;
  472. if ( holdtime < 100 )
  473. {
  474. Q_snprintf( buf, bufsize, "UNKNOWN" );
  475. }
  476. else
  477. {
  478. // Prepend the time.
  479. time_t aclock = (time_t)holdtime;
  480. struct tm *newtime = localtime( &aclock );
  481. // Get rid of the \n.
  482. Q_strncpy( buf, asctime( newtime ), bufsize );
  483. char *pEnd = (char *)Q_stristr( buf, "\n" );
  484. if ( pEnd )
  485. {
  486. *pEnd = 0;
  487. }
  488. time_t curtime;
  489. time( &curtime );
  490. int holdSeconds = curtime - holdtime;
  491. if ( holdSeconds > 0 )
  492. {
  493. char durstring[ 256 ];
  494. durstring[ 0 ] = 0;
  495. GetHourMinuteSecondsString( holdSeconds, durstring, sizeof( durstring ) );
  496. Q_strncat( buf, ", held for ", bufsize, COPY_ALL_CHARACTERS );
  497. Q_strncat( buf, durstring, bufsize, COPY_ALL_CHARACTERS );
  498. }
  499. }
  500. }
  501. int _tmain(int argc, _TCHAR* argv[])
  502. {
  503. char basefile[ 256 ];
  504. Q_FileBase( argv[ 0 ], basefile, sizeof( basefile ) );
  505. bool validAction = false;
  506. MUTEXACTION action = MUTEX_QUERY;
  507. bool validBranch = false;
  508. CUtlSymbol branchspec;
  509. bool validSleepSeconds = false;
  510. int sleepSeconds = 1;
  511. bool validClient = false;
  512. CUtlSymbol clientname;
  513. bool validIP = false;
  514. CUtlSymbol ipport;
  515. for ( int i = 1; i < argc; ++i )
  516. {
  517. switch ( i )
  518. {
  519. default:
  520. break;
  521. case 1:
  522. validAction = true;
  523. if ( !Q_stricmp( argv[ i ], "query" ) )
  524. {
  525. action = MUTEX_QUERY;
  526. }
  527. else if ( !Q_stricmp( argv[ i ], "release" ) )
  528. {
  529. action = MUTEX_RELEASE;
  530. }
  531. else if ( !Q_stricmp( argv[ i ], "lock" ) )
  532. {
  533. action = MUTEX_LOCK;
  534. }
  535. else
  536. {
  537. validAction = false;
  538. }
  539. break;
  540. case 2:
  541. {
  542. validBranch = true;
  543. branchspec = argv[ i ];
  544. }
  545. break;
  546. case 3:
  547. {
  548. validSleepSeconds = true;
  549. sleepSeconds = clamp( Q_atoi( argv[ i ] ), 0, 100 );
  550. }
  551. break;
  552. case 4:
  553. {
  554. validClient = true;
  555. clientname = argv[ i ];
  556. }
  557. break;
  558. case 5:
  559. {
  560. validIP = true;
  561. ipport = argv[ i ];
  562. }
  563. break;
  564. }
  565. }
  566. bool describeLocksOnly = false;
  567. if ( !validBranch ||
  568. !validSleepSeconds ||
  569. !validClient ||
  570. !validIP )
  571. {
  572. if ( !validAction || action != MUTEX_QUERY )
  573. {
  574. printusage( basefile );
  575. return -1;
  576. }
  577. describeLocksOnly = true;
  578. sleepSeconds = 5;
  579. }
  580. // set the protocol return all data as key/value pairs
  581. client.SetProtocol( "tag", "" );
  582. // connect to the p4 server
  583. Error e;
  584. if ( ipport.String()[ 0 ] )
  585. {
  586. client.SetPort( ipport.String() );
  587. }
  588. if ( clientname.String()[ 0 ] )
  589. {
  590. client.SetUser( clientname.String() );
  591. }
  592. client.Init( &e );
  593. bool connected = ( e.Test() == 0 ) ? true : false;
  594. if ( !connected )
  595. {
  596. printf( "Unable to connect to perforce server\n" );
  597. return -1;
  598. }
  599. if ( describeLocksOnly )
  600. {
  601. std::vector< CUtlSymbol > users;
  602. std::vector< int > locktimes;
  603. std::vector< CUtlSymbol > branchnames;
  604. int holdCount = FindLockUsers( g_CountersUser, NULL, users, locktimes, &branchnames );
  605. for ( int i = 0; i < holdCount; ++i )
  606. {
  607. char timestr[ 128 ];
  608. ComputeHoldTime( locktimes[ i ], timestr, sizeof( timestr ) );
  609. printf( "'%s' HELD by: %s\n\ttime: %s\n", branchnames[ i ].String(), users[ i ].String(), timestr );
  610. }
  611. }
  612. else
  613. {
  614. std::vector< CUtlSymbol > users;
  615. std::vector< int > locktimes;
  616. int holdCount = FindLockUsers( g_CountersUser, branchspec.String(), users, locktimes );
  617. if ( holdCount >= 2 )
  618. {
  619. char userlist[ 1024 ];
  620. userlist[ 0 ] = 0;
  621. for ( int i = 0; i < (int)users.Count(); ++i )
  622. {
  623. Q_strncat( userlist, users[ i ].String(), sizeof( userlist ), COPY_ALL_CHARACTERS );
  624. if ( i != users.Count() - 1 )
  625. {
  626. Q_strncat( userlist, ", ", sizeof( userlist ), COPY_ALL_CHARACTERS );
  627. }
  628. }
  629. printf( "%s: ERROR, multiple users (%s) holding lock on '%s'\n", basefile, userlist, branchspec.String() );
  630. printusage( basefile );
  631. return -1;
  632. }
  633. char setcountername[ 256 ];
  634. Q_snprintf( setcountername, sizeof( setcountername ), "%s_lock_%s", branchspec.String(), clientname.String() );
  635. switch ( action )
  636. {
  637. default:
  638. break;
  639. case MUTEX_QUERY:
  640. {
  641. if ( holdCount == 1 )
  642. {
  643. bool isHeldByLocal = false;
  644. if ( !Q_stricmp( users[ 0 ].String(), clientname.String() ) )
  645. {
  646. isHeldByLocal = true;
  647. }
  648. char timestr[ 128 ];
  649. ComputeHoldTime( locktimes[ 0 ], timestr, sizeof( timestr ) );
  650. printf( "%s: '%s' lock on %s is HELD by: %s\nHOLD INFO: %s\n", basefile, branchspec.String(), ipport.String(), users[ 0 ].String(), timestr );
  651. }
  652. else if ( holdCount == 0 )
  653. {
  654. printf( "%s: '%s' lock on %s is FREE\n", basefile, branchspec.String(), ipport.String() );
  655. }
  656. }
  657. break;
  658. case MUTEX_LOCK:
  659. {
  660. printf( "%s: Attempting to lock the '%s' codeline for %s on %s\n\n", basefile, branchspec.String(), clientname.String(), ipport.String() );
  661. // p4mutex: Attempting to lock the 'main_src' codeline for yahn on 207.173.178.12:1666.
  662. if ( holdCount == 1 )
  663. {
  664. bool isHeldByLocal = false;
  665. if ( !Q_stricmp( users[ 0 ].String(), clientname.String() ) )
  666. {
  667. isHeldByLocal = true;
  668. }
  669. char timestr[ 128 ];
  670. ComputeHoldTime( locktimes[ 0 ], timestr, sizeof( timestr ) );
  671. if ( isHeldByLocal )
  672. {
  673. // Success: You already have the 'main_src' codeline lock.
  674. printf( "Success: You already have the '%s' codeline lock\nInfo: %s\n", branchspec.String(), timestr );
  675. }
  676. else
  677. {
  678. // Failed: 'main_goldsrc' lock currently owned by alfred
  679. printf( "Failed: '%s' lock currently owned by %s\nInfo: %s\n", branchspec.String(), users[ 0 ].String(), timestr );
  680. }
  681. }
  682. else if ( holdCount == 0 )
  683. {
  684. // Success: 'main_src' codeline lock granted to yahn.
  685. // Set the counter
  686. time_t aclock;
  687. time( &aclock );
  688. g_SetCounterUser.SetCounter( setcountername, (int)aclock );
  689. printf( "Success: '%s' codeline lock granted to %s\n", branchspec.String(), clientname.String() );
  690. }
  691. }
  692. break;
  693. case MUTEX_RELEASE:
  694. {
  695. printf( "%s: Attempting to release the '%s' codeline for %s on %s\n\n", basefile, branchspec.String(), clientname.String(), ipport.String() );
  696. // p4mutex: Attempting to release the 'main_src' codeline for yahn on 207.173.178.12:1666.
  697. if ( holdCount == 1 )
  698. {
  699. bool isHeldByLocal = false;
  700. if ( !Q_stricmp( users[ 0 ].String(), clientname.String() ) )
  701. {
  702. isHeldByLocal = true;
  703. }
  704. char timestr[ 128 ];
  705. ComputeHoldTime( locktimes[ 0 ], timestr, sizeof( timestr ) );
  706. if ( isHeldByLocal )
  707. {
  708. // Success: 'main_src' codeline lock released.
  709. // Set the counter
  710. g_SetCounterUser.SetCounter( setcountername, 0 );
  711. printf( "Success: '%s' codeline lock released.\n", branchspec.String() );
  712. }
  713. else
  714. {
  715. // Failed: 'main_goldsrc' lock currently owned by alfred
  716. printf( "Failed: '%s' lock currently owned by %s\nInfo: %s\n", branchspec.String(), users[ 0 ].String(), timestr );
  717. }
  718. }
  719. else if ( holdCount == 0 )
  720. {
  721. // Success: The 'main_src' codeline lock is already free.
  722. printf( "Success: The '%s' codeline lock is already free\n", branchspec.String() );
  723. }
  724. }
  725. break;
  726. }
  727. }
  728. if ( sleepSeconds > 0 )
  729. {
  730. time_t starttime;
  731. time( &starttime );
  732. int elapsed = 0;
  733. do
  734. {
  735. time_t curtime;
  736. time( &curtime );
  737. elapsed = curtime - starttime;
  738. Sleep( 50 );
  739. } while ( elapsed < sleepSeconds && !kbhit() );
  740. }
  741. return 0;
  742. }