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.

744 lines
23 KiB

  1. //////////////////////////////////////////////////////////////////////////////
  2. //
  3. // Copyright (c) 1996-2002 Microsoft Corporation
  4. //
  5. // Module Name:
  6. // QuorumUtils.cpp
  7. //
  8. // Description:
  9. // Utility functions for retrieving the root path from a cluster.
  10. //
  11. // Maintained By:
  12. // George Potts (GPotts) 22-OCT-2001
  13. //
  14. //////////////////////////////////////////////////////////////////////////////
  15. #include <windows.h>
  16. #include <StrSafe.h>
  17. #include "QuorumUtils.h"
  18. /////////////////////////////////////////////////////////////////////////////
  19. //++
  20. //
  21. // SplitRootPath
  22. //
  23. // Routine Description:
  24. // Take the current quorum path (from GetClusterQuorumResource) and compare
  25. // it to the device names returned from the resource. From this take the
  26. // additional path from the quorum path and set that as our root path.
  27. //
  28. // It is expected that the IN buffers are at least of size _MAX_PATH.
  29. //
  30. // Arguments:
  31. // hClusterIn Handle to the cluster.
  32. //
  33. // pszPartitionNameOut Partition name buffer to fill.
  34. //
  35. // pcchPartitionIn Max char count of buffer.
  36. //
  37. // pszRootPathOut Root path buffer to fill.
  38. //
  39. // pcchRootPathIn Max char count of buffer.
  40. //
  41. // Return Value:
  42. // ERROR_SUCCESS on success.
  43. //
  44. // ERROR_MORE_DATA
  45. // pcchPartitionInout and pcchRootPathInout will contain the
  46. // minimum sizes needed for the buffers.
  47. //
  48. // Win32 Error code on failure.
  49. //
  50. //--
  51. /////////////////////////////////////////////////////////////////////////////
  52. DWORD SplitRootPath(
  53. HCLUSTER hClusterIn
  54. , WCHAR * pszPartitionNameOut
  55. , DWORD * pcchPartitionInout
  56. , WCHAR * pszRootPathOut
  57. , DWORD * pcchRootPathInout
  58. )
  59. {
  60. HRESOURCE hQuorumResource = NULL;
  61. WCHAR * pszResourceName = NULL;
  62. WCHAR * pszQuorumPath = NULL;
  63. WCHAR * pszDeviceTemp = NULL;
  64. WCHAR * pszTemp = NULL;
  65. CLUSPROP_BUFFER_HELPER buf;
  66. DWORD cbData;
  67. DWORD cchDeviceName;
  68. WCHAR * pszDevice;
  69. DWORD dwVal;
  70. DWORD sc;
  71. PVOID pbDiskInfo = NULL;
  72. DWORD cbDiskInfo = 0;
  73. HRESULT hr;
  74. //
  75. // Validate parameters.
  76. //
  77. if ( hClusterIn == NULL ||
  78. pszPartitionNameOut == NULL || pcchPartitionInout == NULL ||
  79. pszRootPathOut == NULL || pcchRootPathInout == NULL )
  80. {
  81. sc = ERROR_INVALID_PARAMETER;
  82. goto Cleanup;
  83. }
  84. //
  85. // Get the info about the quorum resource.
  86. //
  87. sc = WrapGetClusterQuorumResource( hClusterIn, &pszResourceName, &pszQuorumPath, NULL );
  88. if ( sc != ERROR_SUCCESS )
  89. {
  90. goto Cleanup;
  91. }
  92. //
  93. // Open a handle to the quorum resource to interrogate it.
  94. //
  95. hQuorumResource = OpenClusterResource( hClusterIn, pszResourceName );
  96. if ( sc != ERROR_SUCCESS )
  97. {
  98. goto Cleanup;
  99. }
  100. //
  101. // Get the disk info from the resource.
  102. //
  103. sc = ScWrapClusterResourceControlGet(
  104. hQuorumResource
  105. , NULL
  106. , CLUSCTL_RESOURCE_STORAGE_GET_DISK_INFO
  107. , NULL
  108. , 0
  109. , &pbDiskInfo
  110. , &cbDiskInfo
  111. );
  112. if ( sc != ERROR_SUCCESS )
  113. {
  114. goto Cleanup;
  115. }
  116. //
  117. // Cycle through the buffer looking for the first partition.
  118. //
  119. buf.pb = (BYTE*)pbDiskInfo;
  120. while (buf.pSyntax->dw != CLUSPROP_SYNTAX_ENDMARK)
  121. {
  122. // Calculate the size of the value.
  123. cbData = sizeof(*buf.pValue) + ALIGN_CLUSPROP(buf.pValue->cbLength);
  124. // Parse the value.
  125. if (buf.pSyntax->dw == CLUSPROP_SYNTAX_PARTITION_INFO)
  126. {
  127. //
  128. // A resource may have multiple partitions defined - make sure that ours matches the quorum path.
  129. // For any partition that is an SMB share we have to be careful - the quorum path may differ from the device name
  130. // by the first 8 characters - "\\" vs. "\\?\UNC\". If it's an SMB path do special parsing, otherwise compare
  131. // the beginning of the quorum path against the full device name. The reason for this is
  132. // that SetClusterQuorumResource will convert any given SMB path to a UNC path.
  133. //
  134. // Make it easier to follow.
  135. pszDevice = buf.pPartitionInfoValue->szDeviceName;
  136. if ( (wcslen( pszDevice ) >= 2) && (ClRtlStrNICmp( L"\\\\", pszDevice, 2 ) == 0 ) )
  137. {
  138. //
  139. // We found an SMB/UNC match.
  140. //
  141. //
  142. // SMB and UNC paths always lead off with two leading backslashes - remove these from the
  143. // partition name since a compare of "\\<part>" and "\\?\UNC\<part>" will never match.
  144. // Instead, we'll just search for "<part>" in the quorum path.
  145. //
  146. // Allocate a new buffer to copy the trimmed code to.
  147. // You can use the same buffer for both params of TrimLeft and TrimRight.
  148. pszDeviceTemp = (WCHAR *) LocalAlloc( LMEM_ZEROINIT, ( wcslen( pszDevice ) + 1 ) * sizeof( WCHAR ) );
  149. if ( pszDeviceTemp == NULL )
  150. {
  151. sc = ERROR_OUTOFMEMORY;
  152. goto Cleanup;
  153. }
  154. // This will remove all leading backslashes.
  155. dwVal = TrimLeft( pszDevice, L"\\", pszDeviceTemp );
  156. // It may end with a \ - remove this if present.
  157. dwVal = TrimRight( pszDeviceTemp, L"\\", pszDeviceTemp );
  158. // Find out if pszDeviceTemp is a substring of pszQuorumPath.
  159. pszTemp = wcsstr( pszQuorumPath, pszDeviceTemp );
  160. if ( pszTemp != NULL )
  161. {
  162. // We found a match, now find the offset of the root path.
  163. pszTemp += wcslen( pszDeviceTemp );
  164. // Make sure our buffers are big enough.
  165. if ( wcslen( pszDevice ) >= *pcchPartitionInout )
  166. {
  167. sc = ERROR_MORE_DATA;
  168. }
  169. if ( wcslen( pszTemp ) >= *pcchRootPathInout )
  170. {
  171. sc = ERROR_MORE_DATA;
  172. }
  173. *pcchPartitionInout = static_cast< DWORD >( wcslen( pszDevice ) + 1 );
  174. *pcchRootPathInout = static_cast< DWORD >( wcslen( pszTemp ) + 1 );
  175. if ( sc != ERROR_SUCCESS )
  176. {
  177. goto Cleanup;
  178. }
  179. // Copy the partition and NULL terminate it.
  180. hr = StringCchCopyW( pszPartitionNameOut, *pcchPartitionInout, pszDevice );
  181. if ( FAILED( hr ) )
  182. {
  183. sc = HRESULT_CODE( hr );
  184. break;
  185. }
  186. // Copy the root path and NULL terminate it.
  187. hr = StringCchCopyW( pszRootPathOut, *pcchRootPathInout, pszTemp );
  188. if ( FAILED( hr ) )
  189. {
  190. sc = HRESULT_CODE( hr );
  191. break;
  192. }
  193. break;
  194. } // if: pszDeviceTemp is a substring of pszQuorumPath
  195. } // if: SMB or UNC path
  196. else if ( ClRtlStrNICmp( pszQuorumPath, pszDevice, wcslen( pszDevice )) == 0 )
  197. {
  198. // We found a non-SMB match match - pszDevice is a substring of pszQuorumPath.
  199. cchDeviceName = static_cast< DWORD >( wcslen( pszDevice ) );
  200. if ( cchDeviceName >= *pcchPartitionInout )
  201. {
  202. sc = ERROR_MORE_DATA;
  203. }
  204. if ( wcslen( &(pszQuorumPath[cchDeviceName]) ) >= *pcchRootPathInout )
  205. {
  206. sc = ERROR_MORE_DATA;
  207. }
  208. *pcchPartitionInout = cchDeviceName + 1;
  209. *pcchRootPathInout = static_cast< DWORD >( wcslen( &(pszQuorumPath[cchDeviceName]) ) + 1 );
  210. if ( sc != ERROR_SUCCESS )
  211. {
  212. goto Cleanup;
  213. }
  214. hr = StringCchCopyW( pszPartitionNameOut, *pcchPartitionInout, pszDevice );
  215. if ( FAILED( hr ) )
  216. {
  217. sc = HRESULT_CODE( hr );
  218. break;
  219. }
  220. hr = StringCchCopyW( pszRootPathOut, *pcchRootPathInout, &(pszQuorumPath[cchDeviceName]) );
  221. if ( FAILED( hr ) )
  222. {
  223. sc = HRESULT_CODE( hr );
  224. break;
  225. }
  226. break;
  227. } // if: same partition
  228. } // if: partition info
  229. // Advance the buffer pointer
  230. buf.pb += cbData;
  231. } // while: more values
  232. //
  233. // Something failed - we weren't able to find a partition. Default to the quorum path
  234. // and a single backslash.
  235. //
  236. if ( wcslen( pszPartitionNameOut ) == 0 )
  237. {
  238. hr = StringCchCopyW( pszPartitionNameOut, *pcchPartitionInout, pszQuorumPath );
  239. if ( FAILED( hr ) )
  240. {
  241. sc = HRESULT_CODE( hr );
  242. goto Cleanup;
  243. }
  244. }
  245. if ( wcslen( pszRootPathOut ) == 0 )
  246. {
  247. hr = StringCchCopyW( pszRootPathOut, *pcchRootPathInout, L"\\" );
  248. if ( FAILED( hr ) )
  249. {
  250. sc = HRESULT_CODE( hr );
  251. goto Cleanup;
  252. }
  253. }
  254. Cleanup:
  255. LocalFree( pszResourceName );
  256. LocalFree( pszQuorumPath );
  257. LocalFree( pbDiskInfo );
  258. if ( hQuorumResource != NULL )
  259. {
  260. CloseClusterResource( hQuorumResource );
  261. }
  262. return sc;
  263. } // *** SplitRootPath()
  264. /////////////////////////////////////////////////////////////////////////////
  265. //++
  266. //
  267. // ConstructQuorumPath
  268. //
  269. // Routine Description:
  270. // Construct a quorum path to pass to SetClusterQuorumResource given
  271. // the parsed root path. This function enumerates the resources
  272. // partitions and the first one that it finds it takes the device name
  273. // and appends the rootpath to it.
  274. //
  275. // Arguments:
  276. // hResourceIn Resource that is going to become the quorum.
  277. //
  278. // pszRootPathIn Root path to append to one of the resource's partitions.
  279. //
  280. // pszQuorumPathOut Buffer to receive the constructed quorum path.
  281. //
  282. // pcchQuorumPathInout Count of characters in pszQuorumPathOut.
  283. //
  284. //
  285. // Return Value:
  286. // ERROR_SUCCESS on success.
  287. // The number of characters written (including NULL) is in
  288. // pcchQuorumPathInout.
  289. //
  290. // ERROR_MORE_DATA
  291. // pszQuorumPathOut is too small. The necessary buffer size
  292. // in characters (including NULL) is in pcchQuorumPathInout.
  293. //
  294. // Win32 Error code on failure.
  295. //
  296. //--
  297. /////////////////////////////////////////////////////////////////////////////
  298. DWORD ConstructQuorumPath(
  299. HRESOURCE hResourceIn
  300. , const WCHAR * pszRootPathIn
  301. , WCHAR * pszQuorumPathOut
  302. , DWORD * pcchQuorumPathInout
  303. )
  304. {
  305. DWORD sc = ERROR_SUCCESS;
  306. PVOID pbDiskInfo = NULL;
  307. DWORD cbDiskInfo = 0;
  308. DWORD cbData = 0;
  309. WCHAR * pszDevice = NULL;
  310. size_t cchNeeded = 0;
  311. HRESULT hr;
  312. CLUSPROP_BUFFER_HELPER buf;
  313. //
  314. // Check params.
  315. //
  316. if ( pszRootPathIn == NULL || pszQuorumPathOut == NULL || pcchQuorumPathInout == NULL )
  317. {
  318. sc = ERROR_INVALID_PARAMETER;
  319. goto Cleanup;
  320. }
  321. //
  322. // Get the disk info from the resource.
  323. //
  324. sc = ScWrapClusterResourceControlGet(
  325. hResourceIn
  326. , NULL
  327. , CLUSCTL_RESOURCE_STORAGE_GET_DISK_INFO
  328. , NULL
  329. , 0
  330. , &pbDiskInfo
  331. , &cbDiskInfo
  332. );
  333. if ( sc != ERROR_SUCCESS )
  334. {
  335. goto Cleanup;
  336. }
  337. buf.pb = (BYTE*) pbDiskInfo;
  338. while (buf.pSyntax->dw != CLUSPROP_SYNTAX_ENDMARK)
  339. {
  340. // Calculate the size of the value.
  341. cbData = sizeof(*buf.pValue) + ALIGN_CLUSPROP(buf.pValue->cbLength);
  342. //
  343. // See if this property contains partition info. We grab the first partition.
  344. //
  345. if (buf.pSyntax->dw == CLUSPROP_SYNTAX_PARTITION_INFO)
  346. {
  347. pszDevice = buf.pPartitionInfoValue->szDeviceName;
  348. //
  349. // Calculate the size of the buffer that we need.
  350. //
  351. cchNeeded = wcslen( pszDevice ) + 1;
  352. cchNeeded += wcslen( pszRootPathIn );
  353. if ( pszDevice[ wcslen( pszDevice ) - 1 ] == L'\\' && pszRootPathIn[ 0 ] == L'\\' )
  354. {
  355. //
  356. // We'd have two backslashes if we concatenated them. Prune one of them off.
  357. //
  358. // Decrement one for the removed backslash.
  359. cchNeeded--;
  360. if ( cchNeeded > *pcchQuorumPathInout )
  361. {
  362. sc = ERROR_MORE_DATA;
  363. *pcchQuorumPathInout = static_cast< DWORD >( cchNeeded );
  364. goto Cleanup;
  365. }
  366. //
  367. // Construct the path.
  368. //
  369. hr = StringCchPrintfW( pszQuorumPathOut, *pcchQuorumPathInout, L"%ws%ws", pszDevice, &pszRootPathIn[ 1 ] );
  370. if ( FAILED( hr ) )
  371. {
  372. sc = HRESULT_CODE( hr );
  373. goto Cleanup;
  374. }
  375. } // if: concatenating would introduce double backslashes
  376. else if( pszDevice[ wcslen( pszDevice ) - 1 ] != L'\\' && pszRootPathIn[ 0 ] != L'\\' )
  377. {
  378. //
  379. // We need to insert a backslash between the concatenated strings.
  380. //
  381. // Increment by one for the added backslash.
  382. cchNeeded++;
  383. if ( cchNeeded > *pcchQuorumPathInout )
  384. {
  385. sc = ERROR_MORE_DATA;
  386. *pcchQuorumPathInout = static_cast< DWORD >( cchNeeded );
  387. goto Cleanup;
  388. }
  389. //
  390. // Construct the path.
  391. //
  392. hr = StringCchPrintfW( pszQuorumPathOut, *pcchQuorumPathInout, L"%s\\%s", pszDevice, pszRootPathIn );
  393. if ( FAILED( hr ) )
  394. {
  395. sc = HRESULT_CODE( hr );
  396. goto Cleanup;
  397. }
  398. } // if: we need to introduce a backslash between the concatenation
  399. else
  400. {
  401. //
  402. // We're fine to just construct the path.
  403. //
  404. if ( cchNeeded > *pcchQuorumPathInout )
  405. {
  406. sc = ERROR_MORE_DATA;
  407. *pcchQuorumPathInout = static_cast< DWORD >( cchNeeded );
  408. goto Cleanup;
  409. }
  410. //
  411. // Construct the path.
  412. //
  413. hr = StringCchPrintfW( pszQuorumPathOut, *pcchQuorumPathInout, L"%s%s", pszDevice, pszRootPathIn );
  414. if ( FAILED( hr ) )
  415. {
  416. sc = HRESULT_CODE( hr );
  417. goto Cleanup;
  418. }
  419. } // if: we can just concatenate the strings
  420. //
  421. // Return the number of bytes that we needed in the buffer.
  422. //
  423. *pcchQuorumPathInout = static_cast< DWORD >( cchNeeded );
  424. break;
  425. } // if: partition info
  426. // Advance the buffer pointer
  427. buf.pb += cbData;
  428. } // while: more values
  429. Cleanup:
  430. LocalFree( pbDiskInfo );
  431. return sc;
  432. } //*** ConstructQuorumPath
  433. /////////////////////////////////////////////////////////////////////////////
  434. //++
  435. //
  436. // TrimLeft
  437. //
  438. // Routine Description:
  439. // Trim all leading whitespace as well as any leading characters specified.
  440. //
  441. // Arguments:
  442. // pszTargetIn String to trim characters from.
  443. //
  444. // pszCharsIn List of characters to remove in addition to white space.
  445. //
  446. // pszTrimmedOut Target buffer in which the trimmed string will be
  447. // placed. This may be the same buffer as pszTargetIn.
  448. // This buffer is expected to be at least the size of
  449. // pszTargetIn (in case no characters are removed).
  450. //
  451. // Return Value:
  452. // The count of characters trimmed.
  453. //
  454. // -1. Call GetLastError for more information.
  455. //
  456. //--
  457. /////////////////////////////////////////////////////////////////////////////
  458. DWORD TrimLeft(
  459. const WCHAR * pszTargetIn
  460. , const WCHAR * pszCharsIn
  461. , WCHAR * pszTrimmedOut
  462. )
  463. {
  464. const WCHAR * pszTargetPtr = pszTargetIn;
  465. const WCHAR * pszTemp = NULL;
  466. BOOL fContinue;
  467. DWORD sc = ERROR_SUCCESS;
  468. DWORD cchTrimmed = 0; // Number of characters trimmed.
  469. if ( pszTargetIn == NULL || pszTrimmedOut == NULL )
  470. {
  471. sc = ERROR_INVALID_PARAMETER;
  472. goto Cleanup;
  473. }
  474. //
  475. // Loop until we find non-whitespace or a char not in pszCharsIn or
  476. // we've reached the end of the string.
  477. //
  478. fContinue = TRUE;
  479. while ( *pszTargetPtr != L'\0' && fContinue == TRUE )
  480. {
  481. fContinue = FALSE;
  482. //
  483. // Is the character white space?
  484. //
  485. if ( 0 == iswspace( pszTargetPtr[0] ) )
  486. {
  487. //
  488. // No, it's not. Does it match CharsIn?
  489. //
  490. for( pszTemp = pszCharsIn; pszTemp != NULL && *pszTemp != L'\0'; pszTemp++ )
  491. {
  492. if ( pszTargetPtr[ 0 ] == pszTemp[ 0 ] )
  493. {
  494. //
  495. // We've got a match - trim it and loop on the next character.
  496. //
  497. fContinue = TRUE;
  498. cchTrimmed++;
  499. pszTargetPtr++;
  500. break;
  501. } // if:
  502. } // for:
  503. } // if:
  504. else
  505. {
  506. //
  507. // We've got some whitespace - trim it.
  508. //
  509. fContinue = TRUE;
  510. cchTrimmed++;
  511. pszTargetPtr++;
  512. } // else:
  513. } // while:
  514. //
  515. // Copy the truncated string to the pszTrimmedOut buffer.
  516. // If we truncated everything from the string make sure
  517. // we NULL terminate the string.
  518. //
  519. if ( wcslen( pszTargetPtr ) == 0 )
  520. {
  521. *pszTrimmedOut = L'\0';
  522. }
  523. else
  524. {
  525. // Use memmove because the caller may have passed in the same buffer for both variables.
  526. memmove( pszTrimmedOut, pszTargetPtr, ( wcslen( pszTargetPtr ) + 1 ) * sizeof( WCHAR ) );
  527. }
  528. Cleanup:
  529. if ( sc != ERROR_SUCCESS )
  530. {
  531. cchTrimmed = static_cast< DWORD >( -1 );
  532. }
  533. SetLastError( sc );
  534. return cchTrimmed;
  535. } //*** TrimLeft
  536. /////////////////////////////////////////////////////////////////////////////
  537. //++
  538. //
  539. // TrimRight
  540. //
  541. // Routine Description:
  542. // Trim all trailing whitespace as well as any trailing characters specified.
  543. //
  544. // Arguments:
  545. // pszTargetIn String to trim characters from.
  546. //
  547. // pszCharsIn List of characters to remove in addition to white space.
  548. //
  549. // pszTrimmedOut Target buffer in which the trimmed string will be
  550. // placed. This may be the same buffer as pszTargetIn.
  551. // This buffer is expected to be at least the size of
  552. // pszTargetIn (in case no characters are removed).
  553. //
  554. // Return Value:
  555. // The count of characters trimmed.
  556. //
  557. // -1. Call GetLastError for more information.
  558. //
  559. //--
  560. /////////////////////////////////////////////////////////////////////////////
  561. DWORD TrimRight(
  562. const WCHAR * pszTargetIn
  563. , const WCHAR * pszCharsIn
  564. , WCHAR * pszTrimmedOut
  565. )
  566. {
  567. const WCHAR * pszTargetPtr = pszTargetIn;
  568. const WCHAR * pszTemp = NULL;
  569. BOOL fContinue;
  570. DWORD sc = ERROR_SUCCESS;
  571. DWORD cchTrimmed = 0; // Number of characters trimmed.
  572. size_t cchLen = 0;
  573. if ( pszTargetIn == NULL || pszTrimmedOut == NULL )
  574. {
  575. sc = ERROR_INVALID_PARAMETER;
  576. goto Cleanup;
  577. }
  578. cchLen = wcslen( pszTargetIn );
  579. if ( cchLen == 0 )
  580. {
  581. //
  582. // We've got an empty string.
  583. //
  584. pszTargetPtr = pszTargetIn;
  585. }
  586. else
  587. {
  588. //
  589. // Point to the last character in the string.
  590. //
  591. pszTargetPtr = &(pszTargetIn[ cchLen - 1 ] );
  592. }
  593. //
  594. // Loop until we find non-whitespace or a char not in pszCharsIn or
  595. // we've reached the beginning of the string.
  596. //
  597. fContinue = TRUE;
  598. while ( pszTargetPtr >= pszTargetIn && fContinue == TRUE )
  599. {
  600. fContinue = FALSE;
  601. //
  602. // Is the character white space?
  603. //
  604. if ( 0 == iswspace( pszTargetPtr[0] ) )
  605. {
  606. //
  607. // No, it's not. Does it match CharsIn?
  608. //
  609. for( pszTemp = pszCharsIn; pszTemp != NULL && *pszTemp != L'\0'; pszTemp++ )
  610. {
  611. if ( pszTargetPtr[ 0 ] == pszTemp[ 0 ] )
  612. {
  613. //
  614. // We've got a match - trim it and loop on the next character.
  615. //
  616. fContinue = TRUE;
  617. cchTrimmed++;
  618. pszTargetPtr--;
  619. break;
  620. } // if:
  621. } // for:
  622. } // if:
  623. else
  624. {
  625. //
  626. // We've got some whitespace - trim it.
  627. //
  628. fContinue = TRUE;
  629. cchTrimmed++;
  630. pszTargetPtr--;
  631. } // else:
  632. } // while:
  633. //
  634. // Copy the truncated string to the pszTrimmedOut buffer.
  635. // If we truncated everything from the string make sure
  636. // we NULL terminate the string.
  637. //
  638. if ( wcslen( pszTargetPtr ) == 0 )
  639. {
  640. *pszTrimmedOut = L'\0';
  641. }
  642. else
  643. {
  644. // Use memmove because they may have passed in the same buffer for both variables.
  645. memmove( pszTrimmedOut, pszTargetIn, ( cchLen - cchTrimmed ) * sizeof( WCHAR ) );
  646. pszTrimmedOut[ cchLen - cchTrimmed ] = L'\0';
  647. }
  648. Cleanup:
  649. if ( sc != ERROR_SUCCESS )
  650. {
  651. cchTrimmed = static_cast< DWORD >( -1 );
  652. }
  653. SetLastError( sc );
  654. return cchTrimmed;
  655. } //*** TrimRight