Leaked source code of windows server 2003
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.

750 lines
19 KiB

  1. /*++
  2. Copyright (C) 1996-2001 Microsoft Corporation
  3. Module Name:
  4. WBEMUTIL.CPP
  5. Abstract:
  6. General utility functions.
  7. History:
  8. a-raymcc 17-Apr-96 Created.
  9. --*/
  10. #include "precomp.h"
  11. #include <sys/types.h>
  12. #include <sys/stat.h>
  13. #include <time.h>
  14. #include <stdio.h>
  15. #include <wbemutil.h>
  16. #include <corex.h>
  17. #include "reg.h"
  18. #include "sync.h"
  19. #include <statsync.h>
  20. #include <ARRTEMPL.H>
  21. static BOOL g_bMachineDown = FALSE;
  22. BOOL WbemGetMachineShutdown()
  23. {
  24. return g_bMachineDown;
  25. };
  26. BOOL WbemSetMachineShutdown(BOOL bVal)
  27. {
  28. BOOL bTmp = g_bMachineDown;
  29. g_bMachineDown = bVal;
  30. return bTmp;
  31. }
  32. class AutoRevert
  33. {
  34. private:
  35. HANDLE oldToken_;
  36. BOOL SetThrTokResult_;
  37. bool self_;
  38. public:
  39. AutoRevert();
  40. ~AutoRevert();
  41. void dismiss();
  42. bool self(){ return self_;}
  43. };
  44. AutoRevert::AutoRevert():oldToken_(NULL),self_(true),SetThrTokResult_(FALSE)
  45. {
  46. if (OpenThreadToken(GetCurrentThread(),TOKEN_IMPERSONATE,TRUE,&oldToken_))
  47. {
  48. RevertToSelf();
  49. }
  50. else
  51. {
  52. if (GetLastError() != ERROR_NO_TOKEN)
  53. self_ = false;
  54. };
  55. }
  56. AutoRevert::~AutoRevert()
  57. {
  58. dismiss();
  59. }
  60. void AutoRevert::dismiss()
  61. {
  62. if (oldToken_)
  63. {
  64. // if the handle has been opened with TOKEN_IMPERSONATE
  65. // and if nobody has touched the SD for the ETHREAD object, this will work
  66. SetThrTokResult_ = SetThreadToken(NULL,oldToken_);
  67. CloseHandle(oldToken_);
  68. }
  69. }
  70. //***************************************************************************
  71. //
  72. // BOOL isunialpha(wchar_t c)
  73. //
  74. // Used to test if a wide character is a unicode character or underscore.
  75. //
  76. // Parameters:
  77. // c = The character being tested.
  78. // Return value:
  79. // TRUE if OK.
  80. //
  81. //***************************************************************************
  82. BOOL POLARITY isunialpha(wchar_t c)
  83. {
  84. if(c == 0x5f || (0x41 <= c && c <= 0x5a) ||
  85. (0x61 <= c && c <= 0x7a) || (0x80 <= c && c <= 0xfffd))
  86. return TRUE;
  87. else
  88. return FALSE;
  89. }
  90. //***************************************************************************
  91. //
  92. // BOOL isunialphanum(char_t c)
  93. //
  94. // Used to test if a wide character is string suitable for identifiers.
  95. //
  96. // Parameters:
  97. // pwc = The character being tested.
  98. // Return value:
  99. // TRUE if OK.
  100. //
  101. //***************************************************************************
  102. BOOL POLARITY isunialphanum(wchar_t c)
  103. {
  104. if(isunialpha(c))
  105. return TRUE;
  106. else
  107. return wbem_iswdigit(c);
  108. }
  109. BOOL IsValidElementName( LPCWSTR wszName, DWORD MaxAllow )
  110. {
  111. if(wszName[0] == 0)
  112. return FALSE;
  113. if(wszName[0] == '_')
  114. return FALSE;
  115. const WCHAR* pwc = wszName;
  116. LPCWSTR pTail = wszName+MaxAllow+1;
  117. // Check the first letter
  118. // ======================
  119. // this is for compatibility with IWbemPathParser
  120. if (iswspace(pwc[0]))
  121. return FALSE;
  122. if(!isunialpha(*pwc))
  123. return FALSE;
  124. pwc++;
  125. // Check the rest
  126. // ==============
  127. while(*pwc && (pwc < pTail))
  128. {
  129. if(!isunialphanum(*pwc))
  130. return FALSE;
  131. pwc++;
  132. }
  133. if ( pwc == pTail ) return FALSE;
  134. if (iswspace(*(pwc-1)))
  135. return FALSE;
  136. if(pwc[-1] == '_')
  137. return FALSE;
  138. return TRUE;
  139. }
  140. // Can't use overloading and/or default parameters because
  141. // "C" files use these guys. No, I'm not happy about
  142. // this!
  143. BOOL IsValidElementName2( LPCWSTR wszName,DWORD MaxAllow, BOOL bAllowUnderscore )
  144. {
  145. if(wszName[0] == 0)
  146. return FALSE;
  147. if(!bAllowUnderscore && wszName[0] == '_')
  148. return FALSE;
  149. const WCHAR* pwc = wszName;
  150. LPCWSTR pTail = wszName+MaxAllow+1;
  151. // Check the first letter
  152. // ======================
  153. // this is for compatibility with IWbemPathParser
  154. if (iswspace(pwc[0]))
  155. return FALSE;
  156. if(!isunialpha(*pwc))
  157. return FALSE;
  158. pwc++;
  159. // Check the rest
  160. // ==============
  161. while(*pwc && (pwc < pTail))
  162. {
  163. if(!isunialphanum(*pwc))
  164. return FALSE;
  165. pwc++;
  166. }
  167. if ( pwc == pTail ) return FALSE;
  168. if (iswspace(*(pwc-1)))
  169. return FALSE;
  170. if(!bAllowUnderscore && pwc[-1] == '_')
  171. return FALSE;
  172. return TRUE;
  173. }
  174. BLOB POLARITY BlobCopy(const BLOB *pSrc)
  175. {
  176. BLOB Blob;
  177. BYTE *p = new BYTE[pSrc->cbSize];
  178. // Check for allocation failure
  179. if ( NULL == p )
  180. {
  181. throw CX_MemoryException();
  182. }
  183. Blob.cbSize = pSrc->cbSize;
  184. Blob.pBlobData = p;
  185. memcpy(p, pSrc->pBlobData, Blob.cbSize);
  186. return Blob;
  187. }
  188. void POLARITY BlobAssign(BLOB *pBlob, LPVOID pBytes, DWORD dwCount, BOOL bAcquire)
  189. {
  190. BYTE *pSrc = 0;
  191. if (bAcquire)
  192. pSrc = (BYTE *) pBytes;
  193. else {
  194. pSrc = new BYTE[dwCount];
  195. // Check for allocation failure
  196. if ( NULL == pSrc )
  197. {
  198. throw CX_MemoryException();
  199. }
  200. memcpy(pSrc, pBytes, dwCount);
  201. }
  202. pBlob->cbSize = dwCount;
  203. pBlob->pBlobData = pSrc;
  204. }
  205. void POLARITY BlobClear(BLOB *pSrc)
  206. {
  207. if (pSrc->pBlobData)
  208. delete pSrc->pBlobData;
  209. pSrc->pBlobData = 0;
  210. pSrc->cbSize = 0;
  211. }
  212. class __Trace
  213. {
  214. struct ARMutex
  215. {
  216. HANDLE h_;
  217. ARMutex(HANDLE h):h_(h){}
  218. ~ARMutex(){ ReleaseMutex(h_);}
  219. };
  220. public:
  221. enum { REG_CHECK_INTERVAL =1000 * 60 };
  222. DWORD m_dwLogging;
  223. DWORD m_dwMaxLogSize;
  224. DWORD m_dwTimeLastRegCheck;
  225. wchar_t m_szLoggingDirectory[MAX_PATH+1];
  226. char m_szTraceBuffer[2048];
  227. char m_szTraceBuffer2[4096];
  228. wchar_t m_szBackupFileName[MAX_PATH+1];
  229. wchar_t m_szLogFileName[MAX_PATH+1];
  230. static const wchar_t *m_szLogFileNames[];
  231. BOOL LoggingLevelEnabled(DWORD dwLevel);
  232. DWORD GetLoggingLevel();
  233. int Trace(char caller, const char *fmt, va_list &argptr);
  234. __Trace();
  235. ~__Trace();
  236. HANDLE get_logfile(const wchar_t * name );
  237. private:
  238. void ReadLogDirectory();
  239. void ReReadRegistry();
  240. HANDLE buffers_lock_;
  241. };
  242. const wchar_t * __Trace::m_szLogFileNames[] =
  243. { FILENAME_PREFIX_CORE TEXT(".log"),
  244. FILENAME_PREFIX_EXE TEXT(".log"),
  245. FILENAME_PREFIX_ESS TEXT(".log"),
  246. FILENAME_PREFIX_CLI_MARSH TEXT(".log"),
  247. FILENAME_PREFIX_SERV_MARSH TEXT(".log"),
  248. FILENAME_PREFIX_QUERY TEXT(".log"),
  249. FILENAME_PROFIX_MOFCOMP TEXT(".log"),
  250. FILENAME_PROFIX_EVENTLOG TEXT(".log"),
  251. FILENAME_PROFIX_WBEMDISP TEXT(".log"),
  252. FILENAME_PROFIX_STDPROV TEXT(".log"),
  253. FILENAME_PROFIX_WMIPROV TEXT(".log"),
  254. FILENAME_PROFIX_WMIOLEDB TEXT(".log"),
  255. FILENAME_PREFIX_WMIADAP TEXT(".log"),
  256. FILENAME_PREFIX_REPDRV TEXT(".log"),
  257. FILENAME_PREFIX_PROVSS TEXT(".log"),
  258. FILENAME_PREFIX_EVTPROV TEXT(".log"),
  259. FILENAME_PREFIX_VIEWPROV TEXT(".log"),
  260. FILENAME_PREFIX_DSPROV TEXT(".log"),
  261. FILENAME_PREFIX_SNMPPROV TEXT(".log"),
  262. FILENAME_PREFIX_PROVTHRD TEXT(".log")
  263. };
  264. __Trace __g_traceInfo;
  265. __Trace::__Trace()
  266. : m_dwLogging(1),
  267. m_dwMaxLogSize(65536),
  268. m_dwTimeLastRegCheck(GetTickCount())
  269. {
  270. buffers_lock_ = CreateMutexA(0,0,0);
  271. if (NULL == buffers_lock_)
  272. {
  273. CStaticCritSec::SetFailure();
  274. return;
  275. }
  276. ReadLogDirectory();
  277. ReReadRegistry();
  278. }
  279. __Trace::~__Trace()
  280. {
  281. if (buffers_lock_) CloseHandle(buffers_lock_);
  282. };
  283. void __Trace::ReReadRegistry()
  284. {
  285. Registry r(WBEM_REG_WINMGMT, KEY_READ);
  286. //Get the logging level
  287. if (r.GetDWORDStr(TEXT("Logging"), &m_dwLogging) != Registry::no_error)
  288. {
  289. m_dwLogging = 1;
  290. r.SetDWORDStr(TEXT("Logging"), m_dwLogging);
  291. }
  292. //Get the maximum log file size
  293. if (r.GetDWORDStr(TEXT("Log File Max Size"), &m_dwMaxLogSize) != Registry::no_error)
  294. {
  295. m_dwMaxLogSize = 65536;
  296. r.SetDWORDStr(TEXT("Log File Max Size"), m_dwMaxLogSize);
  297. }
  298. }
  299. void __Trace::ReadLogDirectory()
  300. {
  301. Registry r(WBEM_REG_WINMGMT);
  302. //Retrieve the logging directory
  303. TCHAR *tmpStr = 0;
  304. if ((r.GetStr(TEXT("Logging Directory"), &tmpStr) == Registry::failed) ||
  305. (lstrlen(tmpStr) > (MAX_PATH)))
  306. {
  307. delete [] tmpStr; //Just in case someone was trying for a buffer overrun with a long path in the registry...
  308. if (GetSystemDirectory(m_szLoggingDirectory, MAX_PATH+1) == 0)
  309. {
  310. StringCchCopy(m_szLoggingDirectory, MAX_PATH+1, TEXT("c:\\"));
  311. }
  312. else
  313. {
  314. StringCchCat(m_szLoggingDirectory, MAX_PATH+1, TEXT("\\WBEM\\Logs\\"));
  315. r.SetStr(TEXT("Logging Directory"), m_szLoggingDirectory);
  316. }
  317. }
  318. else
  319. {
  320. StringCchCopy(m_szLoggingDirectory, MAX_PATH+1, tmpStr);
  321. //make sure there is a '\' on the end of the path...
  322. if (m_szLoggingDirectory[lstrlen(m_szLoggingDirectory) - 1] != '\\')
  323. {
  324. StringCchCat(m_szLoggingDirectory, MAX_PATH+1,TEXT("\\"));
  325. r.SetStr(TEXT("Logging Directory"), m_szLoggingDirectory);
  326. }
  327. delete [] tmpStr;
  328. }
  329. //Make sure directory exists
  330. WbemCreateDirectory(m_szLoggingDirectory);
  331. }
  332. HANDLE __Trace::get_logfile(const wchar_t * file_name )
  333. {
  334. AutoRevert revert;
  335. if (revert.self()==false)
  336. return INVALID_HANDLE_VALUE;
  337. HANDLE hTraceFile = INVALID_HANDLE_VALUE;
  338. bool bDoneWrite = false;
  339. //Keep trying to open the file
  340. while (!bDoneWrite)
  341. {
  342. while (hTraceFile == INVALID_HANDLE_VALUE)
  343. {
  344. if (WaitForSingleObject(buffers_lock_,-1)==WAIT_FAILED)
  345. return INVALID_HANDLE_VALUE;
  346. StringCchCopy(m_szLogFileName, MAX_PATH+1, m_szLoggingDirectory);;
  347. StringCchCat(m_szLogFileName, MAX_PATH+1, file_name);
  348. hTraceFile = ::CreateFileW( m_szLogFileName,
  349. GENERIC_WRITE,
  350. FILE_SHARE_READ | FILE_SHARE_DELETE,
  351. NULL,
  352. OPEN_ALWAYS,
  353. FILE_ATTRIBUTE_NORMAL|FILE_ATTRIBUTE_NOT_CONTENT_INDEXED,
  354. NULL );
  355. if ( hTraceFile == INVALID_HANDLE_VALUE )
  356. {
  357. ReleaseMutex(buffers_lock_);
  358. if (GetLastError() == ERROR_SHARING_VIOLATION)
  359. {
  360. Sleep(20);
  361. }
  362. else
  363. {
  364. return INVALID_HANDLE_VALUE;
  365. }
  366. }
  367. }
  368. ARMutex arm(buffers_lock_);
  369. //
  370. // Now move the file pointer to the end of the file
  371. //
  372. LARGE_INTEGER liSize;
  373. liSize.QuadPart = 0;
  374. if ( !::SetFilePointerEx( hTraceFile,
  375. liSize,
  376. NULL,
  377. FILE_END ) )
  378. {
  379. CloseHandle( hTraceFile );
  380. return INVALID_HANDLE_VALUE;
  381. }
  382. bDoneWrite = true;
  383. //Rename file if file length is exceeded
  384. LARGE_INTEGER liMaxSize;
  385. liMaxSize.QuadPart = m_dwMaxLogSize;
  386. if (GetFileSizeEx(hTraceFile, &liSize))
  387. {
  388. if (liSize.QuadPart > liMaxSize.QuadPart)
  389. {
  390. StringCchCopy(m_szBackupFileName, MAX_PATH+1, m_szLogFileName);
  391. StringCchCopy(m_szBackupFileName + lstrlen(m_szBackupFileName) - 3, MAX_PATH+1, TEXT("lo_"));
  392. DeleteFile(m_szBackupFileName);
  393. if (MoveFile(m_szLogFileName, m_szBackupFileName) == 0)
  394. {
  395. if ( liSize.QuadPart < liMaxSize.QuadPart*2)
  396. return hTraceFile;
  397. else
  398. {
  399. CloseHandle(hTraceFile);
  400. return INVALID_HANDLE_VALUE;
  401. };
  402. }
  403. //Need to re-open the file!
  404. bDoneWrite = false;
  405. CloseHandle(hTraceFile);
  406. hTraceFile = INVALID_HANDLE_VALUE;
  407. }
  408. }
  409. }
  410. return hTraceFile;
  411. };
  412. int __Trace::Trace(char caller, const char *fmt, va_list &argptr)
  413. {
  414. HANDLE hTraceFile = INVALID_HANDLE_VALUE;
  415. try
  416. {
  417. if (caller >= (sizeof(m_szLogFileNames) / sizeof(char)))
  418. caller = 0;
  419. hTraceFile = get_logfile(m_szLogFileNames[caller]);
  420. if (hTraceFile == INVALID_HANDLE_VALUE)
  421. return 0;
  422. CCloseMe ch(hTraceFile);
  423. if (WaitForSingleObject(buffers_lock_,-1)==WAIT_FAILED)
  424. return 0;
  425. ARMutex arm(buffers_lock_);
  426. // Get time.
  427. // =========
  428. char timebuf[64];
  429. time_t now = time(0);
  430. struct tm *local = localtime(&now);
  431. if(local)
  432. {
  433. StringCchCopyA(timebuf, 64, asctime(local));
  434. timebuf[strlen(timebuf) - 1] = 0; // O
  435. }
  436. else
  437. {
  438. StringCchCopyA(timebuf, 64, "??");
  439. }
  440. //Put time in start of log
  441. StringCchPrintfA(m_szTraceBuffer, 2048, "(%s.%d) : ", timebuf, GetTickCount());
  442. //Format the user string
  443. int nLen = strlen(m_szTraceBuffer);
  444. StringCchVPrintfA(m_szTraceBuffer + nLen, 2048 - nLen, fmt, argptr);
  445. //Unfortunately, lots of people only put \n in the string, so we need to convert the string...
  446. int nLen2 = 0;
  447. char *p = m_szTraceBuffer;
  448. char *p2 = m_szTraceBuffer2;
  449. for (; *p; p++,p2++,nLen2++)
  450. {
  451. if (*p == '\n')
  452. {
  453. *p2 = '\r';
  454. p2++;
  455. nLen2++;
  456. *p2 = '\n';
  457. }
  458. else
  459. {
  460. *p2 = *p;
  461. }
  462. }
  463. *p2 = '\0';
  464. //
  465. // Write to file :
  466. //
  467. DWORD dwWritten;
  468. ::WriteFile( hTraceFile, m_szTraceBuffer2, nLen2, &dwWritten, NULL);
  469. return 1;
  470. }
  471. catch(...)
  472. {
  473. return 0;
  474. }
  475. }
  476. BOOL __Trace::LoggingLevelEnabled(DWORD dwLevel)
  477. {
  478. if (!WbemGetMachineShutdown()) // prevent touching registry during machine shutdown
  479. {
  480. DWORD dwCurTicks = GetTickCount();
  481. if (dwCurTicks - m_dwTimeLastRegCheck > REG_CHECK_INTERVAL)
  482. {
  483. ReReadRegistry();
  484. m_dwTimeLastRegCheck = dwCurTicks;
  485. }
  486. }
  487. if ((dwLevel > m_dwLogging))
  488. return FALSE;
  489. else
  490. return TRUE;
  491. }
  492. DWORD __Trace::GetLoggingLevel()
  493. {
  494. if (!WbemGetMachineShutdown()) // prevent touching registry during machine shutdown
  495. {
  496. DWORD dwCurTicks = GetTickCount();
  497. if (dwCurTicks - m_dwTimeLastRegCheck > REG_CHECK_INTERVAL)
  498. {
  499. ReReadRegistry();
  500. m_dwTimeLastRegCheck = dwCurTicks;
  501. }
  502. }
  503. return m_dwLogging;
  504. };
  505. DWORD GetLoggingLevelEnabled()
  506. {
  507. return __g_traceInfo.GetLoggingLevel();
  508. }
  509. BOOL LoggingLevelEnabled(DWORD dwLevel)
  510. {
  511. return __g_traceInfo.LoggingLevelEnabled(dwLevel);
  512. }
  513. int ErrorTrace(char caller, const char *fmt, ...)
  514. {
  515. if (__g_traceInfo.LoggingLevelEnabled(1))
  516. {
  517. va_list argptr;
  518. va_start(argptr, fmt);
  519. __g_traceInfo.Trace(caller, fmt, argptr);
  520. va_end(argptr);
  521. return 1;
  522. }
  523. else
  524. return 0;
  525. }
  526. int DebugTrace(char caller, const char *fmt, ...)
  527. {
  528. if (__g_traceInfo.LoggingLevelEnabled(2))
  529. {
  530. va_list argptr;
  531. va_start(argptr, fmt);
  532. __g_traceInfo.Trace(caller, fmt, argptr);
  533. va_end(argptr);
  534. return 1;
  535. }
  536. else
  537. return 0;
  538. }
  539. int CriticalFailADAPTrace(const char *string)
  540. //
  541. // The intention of this trace function is to be used in situations where catastrophic events
  542. // may have occured where the state of the heap may be in question. The function uses only
  543. // stack variables. Note that if a heap corruption has occured there is a small chance that
  544. // the global object __g_traceInfo may have been damaged.
  545. {
  546. return ErrorTrace(LOG_WMIADAP, "**CRITICAL FAILURE** %s", string);
  547. }
  548. // Helper for quick wchar to multibyte conversions. Caller muts
  549. // free the returned pointer
  550. BOOL POLARITY AllocWCHARToMBS( WCHAR* pWstr, char** ppStr )
  551. {
  552. if ( NULL == pWstr )
  553. {
  554. return FALSE;
  555. }
  556. // Get the length allocate space and copy the string
  557. long lLen = wcstombs(NULL, pWstr, 0);
  558. *ppStr = new char[lLen + 1];
  559. if (*ppStr == 0)
  560. return FALSE;
  561. wcstombs( *ppStr, pWstr, lLen + 1 );
  562. return TRUE;
  563. }
  564. LPTSTR GetWbemWorkDir( void )
  565. {
  566. LPTSTR pWorkDir = NULL;
  567. Registry r1(WBEM_REG_WINMGMT);
  568. if (r1.GetStr(TEXT("Working Directory"), &pWorkDir))
  569. {
  570. size_t bufferLength = MAX_PATH + 1 + lstrlen(TEXT("\\WBEM"));
  571. wmilib::auto_buffer<TCHAR> p(new TCHAR[bufferLength]);
  572. if (NULL == p.get()) return NULL;
  573. DWORD dwRet = GetSystemDirectory(p.get(), MAX_PATH + 1);
  574. if (0 == dwRet) return NULL;
  575. if (dwRet > MAX_PATH)
  576. {
  577. bufferLength = 4 + 1 + dwRet;
  578. p.reset(new TCHAR[bufferLength]);
  579. if (NULL == p.get()) return NULL;
  580. if (0 == GetSystemDirectory(p.get(), dwRet + 1)) return NULL;
  581. }
  582. StringCchCat(p.get(), bufferLength, TEXT("\\WBEM"));
  583. pWorkDir = p.release();
  584. }
  585. return pWorkDir;
  586. }
  587. LPTSTR GetWMIADAPCmdLine( int nExtra )
  588. {
  589. LPTSTR pWorkDir = GetWbemWorkDir();
  590. CVectorDeleteMe<TCHAR> vdm( pWorkDir );
  591. if ( NULL == pWorkDir )
  592. {
  593. return NULL;
  594. }
  595. // Buffer should be big enough for two quotes, WMIADAP.EXE and cmdline switches
  596. size_t bufferLength = lstrlen( pWorkDir ) + lstrlen(TEXT("\\\\?\\\\WMIADAP.EXE")) + nExtra + 1;
  597. LPTSTR pCmdLine = new TCHAR[bufferLength];
  598. if ( NULL == pCmdLine )
  599. {
  600. return NULL;
  601. }
  602. StringCchPrintf( pCmdLine,bufferLength, TEXT("\\\\?\\%s\\WMIADAP.EXE"), pWorkDir );
  603. return pCmdLine;
  604. }
  605. BOOL IsNtSetupRunning()
  606. {
  607. HKEY hKey;
  608. long lRes = RegOpenKeyExW(HKEY_LOCAL_MACHINE,L"system\\Setup",0, KEY_READ, &hKey);
  609. if(ERROR_SUCCESS != lRes) return FALSE;
  610. DWORD dwSetupRunning;
  611. DWORD dwLen = sizeof(DWORD);
  612. lRes = RegQueryValueExW(hKey, L"SystemSetupInProgress", NULL, NULL,(LPBYTE)&dwSetupRunning, &dwLen);
  613. RegCloseKey(hKey);
  614. if(lRes == ERROR_SUCCESS && (dwSetupRunning == 1))
  615. {
  616. return TRUE;
  617. }
  618. return FALSE;
  619. }