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.

858 lines
23 KiB

  1. /*++
  2. Copyright (c) 1991 Microsoft Corporation
  3. Module Name:
  4. lasterr.c
  5. Abstract:
  6. Contains the entry point for
  7. WNetGetLastErrorW
  8. WNetSetLastErrorW
  9. MultinetGetErrorTextW
  10. Also contains the following support routines:
  11. MprAllocErrorRecord
  12. MprFreeErrorRecord
  13. MprFindErrorRecord
  14. Author:
  15. Dan Lafferty (danl) 17-Oct-1991
  16. Environment:
  17. User Mode - Win32
  18. Revision History:
  19. 09-Aug-1996 anirudhs
  20. Fixed bug in MprFindErrorRecord. Replaced MprFreeErrorRecord with
  21. MprFreeAllErrorRecords (see mprinit.cxx). Moved some definitions
  22. from mprdata.h here.
  23. 20-Jun-1995 anirudhs
  24. Added MultinetGetErrorTextW. Removed CurrentThreadId parameter to
  25. MprFindErrorRecord. Some other cleanups.
  26. 11-Aug-1993 danl
  27. WNetGetLastErrorW: Got rid of TCHAR stuff and committed to UNICODE.
  28. Also, consistently use number of characters rather than number
  29. of bytes.
  30. 08-Jun-1993 danl
  31. Now we handle the case where WNetSetLastError is called with a
  32. threadId that we don't know about. This happens in the case where
  33. a sub-thread does a LoadLibrary on MPR.DLL. We only get
  34. notification of the one thread attaching. Yet other threads within
  35. that process may call the WinNet functions.
  36. 17-Oct-1991 danl
  37. Created
  38. --*/
  39. //
  40. // INCLUDES
  41. //
  42. #include "precomp.hxx"
  43. #include "mprres.h" // IDS_XERR_UNKNOWN
  44. //=======================
  45. // Data Structures
  46. //=======================
  47. typedef struct _ERROR_RECORD {
  48. struct _ERROR_RECORD *Prev;
  49. struct _ERROR_RECORD *Next;
  50. DWORD ThreadId;
  51. DWORD ErrorCode;
  52. LPTSTR ErrorText; // This is an allocated buffer
  53. LPTSTR ProviderName; // This is an allocated buffer
  54. } ERROR_RECORD, *LPERROR_RECORD;
  55. //=======================
  56. // MACROS/DEFINES
  57. //=======================
  58. #define FIND_END_OF_LIST(record) while(record->Next != NULL) { \
  59. record=record->Next; \
  60. }
  61. #define REMOVE_FROM_LIST(record) record->Prev->Next = record->Next; \
  62. if (record->Next != NULL) { \
  63. record->Next->Prev = record->Prev; \
  64. }
  65. #define ADD_TO_LIST(record, newRec) FIND_END_OF_LIST(record) \
  66. record->Next = newRec; \
  67. newRec->Prev = record; \
  68. newRec->Next = NULL;
  69. //
  70. // The string (in English) is 44 characters long (+1 for the NULL)
  71. //
  72. #define UNKNOWN_ERROR_LENGTH 80
  73. //=======================
  74. // GLOBALS
  75. //=======================
  76. extern HMODULE hDLL; // DLL instance handle
  77. //
  78. // Local Functions
  79. //
  80. LPERROR_RECORD
  81. MprAllocErrorRecord(
  82. VOID);
  83. LPERROR_RECORD
  84. MprFindErrorRecord(
  85. VOID);
  86. //
  87. // Global Data Structures
  88. //
  89. ERROR_RECORD MprErrorRecList; // Initialized to zeros by loader
  90. CRITICAL_SECTION MprErrorRecCritSec; // Initialized in mprinit.cxx
  91. DWORD
  92. WNetGetLastErrorW(
  93. OUT LPDWORD lpError,
  94. OUT LPWSTR lpErrorBuf,
  95. IN DWORD nErrorBufLen,
  96. OUT LPWSTR lpNameBuf,
  97. IN DWORD nNameBufLen
  98. )
  99. /*++
  100. Routine Description:
  101. This function allows users to obtain the error code and accompanying
  102. text when they receive a WN_EXTENDED_ERROR in response to a WNet API
  103. function call.
  104. Arguments:
  105. lpError - This is a pointer to the location that will receive the
  106. error code.
  107. lpErrorBuf - This points to a buffer that will receive the null
  108. terminated string describing the error.
  109. nErrorBufLen - This value that indicates the size (in characters) of
  110. lpErrorBuf.
  111. If the buffer is too small to receive an error string, the
  112. string will simply be truncated. (it is still guaranteed to be
  113. null terminated). A buffer of at least 256 bytes is recommended.
  114. lpNameBuf - This points to a buffer that will receive the name of
  115. the provider that raised the error.
  116. nNameBufLen - This value indicates the size (in characters) of lpNameBuf.
  117. If the buffer is too small to receive an error string, the
  118. string will simply be truncated. (it is still guaranteed to be
  119. null terminated).
  120. Return Value:
  121. WN_SUCCESS - if the call was successful.
  122. WN_BAD_POINTER - One or more of the passed in pointers is bad.
  123. WN_DEVICE_ERROR - This indicates that the threadID for the current
  124. thread could not be found in that table anywhere. This should
  125. never happen.
  126. --*/
  127. {
  128. LPERROR_RECORD errorRecord;
  129. DWORD nameStringLen;
  130. DWORD textStringLen;
  131. DWORD status = WN_SUCCESS;
  132. //
  133. // Screen the parameters as best we can.
  134. //
  135. __try {
  136. //
  137. // If output buffers are provided, Probe them.
  138. //
  139. *lpError = WN_SUCCESS;
  140. if (IsBadWritePtr(lpErrorBuf, nErrorBufLen * sizeof(WCHAR)) ||
  141. IsBadWritePtr(lpNameBuf, nNameBufLen * sizeof(WCHAR)))
  142. {
  143. status = WN_BAD_POINTER;
  144. }
  145. }
  146. __except (EXCEPTION_EXECUTE_HANDLER) {
  147. status = GetExceptionCode();
  148. if (status != EXCEPTION_ACCESS_VIOLATION) {
  149. MPR_LOG(ERROR,"WNetGetLastError:Unexpected Exception 0x%lx\n",status);
  150. }
  151. status = WN_BAD_POINTER;
  152. }
  153. if (status != WN_SUCCESS) {
  154. return(status);
  155. }
  156. //
  157. // Get the current thread's error record.
  158. //
  159. errorRecord = MprFindErrorRecord();
  160. if (errorRecord != NULL) {
  161. //
  162. // The record was found in the linked list.
  163. // See if there is a buffer to put data into.
  164. //
  165. if (nErrorBufLen > 0) {
  166. //
  167. // Check to see if there is error text to return.
  168. // If not, indicate a 0 length string.
  169. //
  170. if(errorRecord->ErrorText == NULL) {
  171. *lpErrorBuf = L'\0';
  172. }
  173. else {
  174. //
  175. // If the error text won't fit into the user buffer, fill it
  176. // as best we can, and NULL terminate it.
  177. //
  178. textStringLen = wcslen(errorRecord->ErrorText);
  179. if(nErrorBufLen < textStringLen + 1) {
  180. textStringLen = nErrorBufLen - 1;
  181. }
  182. //
  183. // textStringLen now contains the number of characters we
  184. // will copy without the NULL terminator.
  185. //
  186. wcsncpy(lpErrorBuf, errorRecord->ErrorText, textStringLen);
  187. *(lpErrorBuf + textStringLen) = L'\0';
  188. }
  189. }
  190. //
  191. // If there is a Name Buffer to put the provider into, then...
  192. //
  193. if (nNameBufLen > 0) {
  194. //
  195. // See if the Provider Name will fit in the user buffer.
  196. //
  197. nameStringLen = errorRecord->ProviderName ?
  198. (wcslen(errorRecord->ProviderName) + 1) :
  199. 1 ;
  200. //
  201. // If the user buffer is smaller than the required size,
  202. // set up to copy the smaller of the two.
  203. //
  204. if(nNameBufLen < nameStringLen + 1) {
  205. nameStringLen = nNameBufLen - 1;
  206. }
  207. if (errorRecord->ProviderName) {
  208. wcsncpy(lpNameBuf, errorRecord->ProviderName, nameStringLen);
  209. *(lpNameBuf + nameStringLen) = L'\0';
  210. }
  211. else {
  212. *lpNameBuf = L'\0';
  213. }
  214. }
  215. *lpError = errorRecord->ErrorCode;
  216. return(WN_SUCCESS);
  217. }
  218. else {
  219. //
  220. // If we get here, a record for the current thread could not be found.
  221. //
  222. *lpError = WN_SUCCESS;
  223. if (nErrorBufLen > 0) {
  224. *lpErrorBuf = L'\0';
  225. }
  226. if (nNameBufLen > 0) {
  227. *lpNameBuf = L'\0';
  228. }
  229. return(WN_SUCCESS);
  230. }
  231. }
  232. VOID
  233. WNetSetLastErrorW(
  234. IN DWORD err,
  235. IN LPWSTR lpError,
  236. IN LPWSTR lpProvider
  237. )
  238. /*++
  239. Routine Description:
  240. This function is used by Network Providers to set extended errors.
  241. It saves away the error information in a "per thread" data structure.
  242. This function also calls SetLastError with the WN_EXTENDED_ERROR.
  243. Arguments:
  244. err - The error that occured. This may be a Windows defined error,
  245. in which case LpError is ignored. or it may be ERROR_EXTENDED_ERROR
  246. to indicate that the provider has a network specific error to report.
  247. lpError - String describing a network specific error.
  248. lpProvider - String naming the network provider raising the error.
  249. Return Value:
  250. none
  251. --*/
  252. {
  253. DWORD status = WN_SUCCESS;
  254. LPERROR_RECORD errorRecord;
  255. //
  256. // Set the extended error status that tells the user they need to
  257. // call WNetGetLastError to obtain further information.
  258. //
  259. SetLastError(WN_EXTENDED_ERROR);
  260. //
  261. // Get the Error Record for the current thread.
  262. //
  263. errorRecord = MprFindErrorRecord();
  264. //
  265. // if there isn't a record for the current thread, then add it.
  266. //
  267. if (errorRecord == NULL)
  268. {
  269. errorRecord = MprAllocErrorRecord();
  270. if (errorRecord == NULL)
  271. {
  272. MPR_LOG0(ERROR,"WNetSetLastError:Could not allocate Error Record\n");
  273. return;
  274. }
  275. }
  276. //
  277. // Update the error code in the error record. At the same time,
  278. // free up any old strings since they are now obsolete, and init
  279. // the pointer to NULL. Also set the ProviderName pointer in the
  280. // ErrorRecord to point to the provider's name.
  281. //
  282. errorRecord->ErrorCode = err;
  283. LocalFree(errorRecord->ProviderName);
  284. errorRecord->ProviderName = NULL;
  285. LocalFree(errorRecord->ErrorText);
  286. errorRecord->ErrorText = NULL;
  287. //
  288. // Allocate memory for the provider name.
  289. //
  290. __try {
  291. errorRecord->ProviderName = (WCHAR *) LocalAlloc(LPTR,
  292. (wcslen(lpProvider) + 1) * sizeof(WCHAR));
  293. }
  294. __except (MPR_EXCEPTION_FILTER) {
  295. //
  296. // We have a problem with lpProvider.
  297. //
  298. status = GetExceptionCode();
  299. if (status != EXCEPTION_ACCESS_VIOLATION) {
  300. MPR_LOG(ERROR,"WNetSetLastError:Unexpected Exception 0x%lx\n",status);
  301. }
  302. status = WN_BAD_POINTER;
  303. }
  304. if (status != WN_SUCCESS) {
  305. return;
  306. }
  307. if (errorRecord->ProviderName == NULL) {
  308. //
  309. // Unable to allocate memory for the Provider Name for the error
  310. // record.
  311. //
  312. MPR_LOG(ERROR,
  313. "WNetSetLastError:Unable to allocate mem for ProviderName\n",0);
  314. return;
  315. }
  316. //
  317. // Copy the string to the newly allocated buffer.
  318. //
  319. wcscpy(errorRecord->ProviderName, lpProvider);
  320. //
  321. // Allocate memory for the storage of the error text.
  322. //
  323. __try {
  324. errorRecord->ErrorText = (WCHAR *) LocalAlloc(LPTR,
  325. (wcslen(lpError) + 1) * sizeof(WCHAR));
  326. }
  327. __except (MPR_EXCEPTION_FILTER) {
  328. //
  329. // We have a problem with lpError.
  330. //
  331. status = GetExceptionCode();
  332. if (status != EXCEPTION_ACCESS_VIOLATION) {
  333. MPR_LOG(ERROR,"WNetSetLastError:Unexpected Exception 0x%lx\n",status);
  334. }
  335. status = WN_BAD_POINTER;
  336. }
  337. if (status != WN_SUCCESS) {
  338. errorRecord->ErrorText = NULL;
  339. }
  340. if (errorRecord->ErrorText == NULL) {
  341. //
  342. // If we were unsuccessful in allocating for the ErrorText, then
  343. // abort. The ErrorText Pointer has already been set to null.
  344. //
  345. MPR_LOG(ERROR,"WNetSetLastError:Unable to Alloc for ErrorText %d\n",
  346. GetLastError());
  347. return;
  348. }
  349. //
  350. // Copy the error text into the newly allocated buffer.
  351. //
  352. wcscpy(errorRecord->ErrorText, lpError);
  353. return;
  354. }
  355. LPERROR_RECORD
  356. MprFindErrorRecord(
  357. VOID)
  358. /*++
  359. Routine Description:
  360. Looks through the linked list of ErrorRecords in search of one for
  361. the current thread.
  362. Arguments:
  363. none
  364. Return Value:
  365. Returns LPERROR_RECORD if an error record was found. Otherwise, it
  366. returns NULL.
  367. --*/
  368. {
  369. LPERROR_RECORD errorRecord;
  370. DWORD CurrentThreadId = GetCurrentThreadId();
  371. EnterCriticalSection(&MprErrorRecCritSec);
  372. for (errorRecord = MprErrorRecList.Next;
  373. errorRecord != NULL;
  374. errorRecord = errorRecord->Next)
  375. {
  376. if (errorRecord->ThreadId == CurrentThreadId)
  377. {
  378. break;
  379. }
  380. }
  381. LeaveCriticalSection(&MprErrorRecCritSec);
  382. return(errorRecord);
  383. }
  384. LPERROR_RECORD
  385. MprAllocErrorRecord(
  386. VOID)
  387. /*++
  388. Routine Description:
  389. This function allocates and initializes an Error Record for the
  390. current thread. Then it places the error record in the global
  391. MprErrorRecList.
  392. Even if the thread exits, the record is not freed until the DLL unloads.
  393. This is OK because this function is called only if a provider calls
  394. WNetSetLastError, which is rare.
  395. Arguments:
  396. none
  397. Return Value:
  398. TRUE - The operation completed successfully
  399. FALSE - An error occured in the allocation.
  400. Note:
  401. --*/
  402. {
  403. LPERROR_RECORD record;
  404. LPERROR_RECORD errorRecord;
  405. //
  406. // Allocate memory for the storage of the error message
  407. // and add the record to the linked list.
  408. //
  409. errorRecord = (LPERROR_RECORD)LocalAlloc(LPTR,sizeof (ERROR_RECORD));
  410. if (errorRecord == NULL) {
  411. MPR_LOG1(ERROR,"MprAllocErrorRecord:LocalAlloc Failed %d\n",
  412. GetLastError());
  413. return NULL;
  414. }
  415. //
  416. // Initialize the error record
  417. //
  418. errorRecord->ThreadId = GetCurrentThreadId();
  419. errorRecord->ErrorCode = WN_SUCCESS;
  420. errorRecord->ErrorText = NULL;
  421. //
  422. // Add the record to the linked list.
  423. //
  424. EnterCriticalSection(&MprErrorRecCritSec);
  425. record = &MprErrorRecList;
  426. ADD_TO_LIST(record, errorRecord);
  427. LeaveCriticalSection(&MprErrorRecCritSec);
  428. return errorRecord;
  429. }
  430. VOID
  431. MprFreeAllErrorRecords(
  432. VOID)
  433. /*++
  434. Routine Description:
  435. This function is called when the DLL is unloading due to a FreeLibrary
  436. call. It frees all the error records (for all threads) that have been
  437. created since the DLL was loaded. If there is a pointer to a text string
  438. in a record, the buffer for that string is freed also.
  439. Arguments:
  440. Return Value:
  441. Note:
  442. --*/
  443. {
  444. LPERROR_RECORD nextRecord;
  445. EnterCriticalSection(&MprErrorRecCritSec);
  446. for (LPERROR_RECORD record = MprErrorRecList.Next;
  447. record != NULL;
  448. record = nextRecord)
  449. {
  450. MPR_LOG1(TRACE,"MprFreeErrorRecord: Freeing Record for thread 0x%x\n",
  451. record->ThreadId);
  452. LocalFree(record->ErrorText);
  453. LocalFree(record->ProviderName);
  454. nextRecord = record->Next;
  455. LocalFree(record);
  456. }
  457. MprErrorRecList.Next = NULL;
  458. LeaveCriticalSection(&MprErrorRecCritSec);
  459. }
  460. DWORD
  461. MultinetGetErrorTextW(
  462. OUT LPWSTR lpErrorTextBuf OPTIONAL,
  463. IN OUT LPDWORD lpnErrorBufSize,
  464. OUT LPWSTR lpProviderNameBuf OPTIONAL,
  465. IN OUT LPDWORD lpnNameBufSize
  466. )
  467. /*++
  468. Routine Description:
  469. This is an internal interface between the shell and the MPR. It
  470. combines the work of calling GetLastError and WNetGetLastError into
  471. one call. It returns the text for the last error that occurred in
  472. a WNet API in the current thread. The error text could have been
  473. customized by the provider calling WNetSetLastError.
  474. Arguments:
  475. lpErrorTextBuf - Pointer to buffer that will receive a null-terminated
  476. string describing the error. May be NULL if not required.
  477. lpnErrorBufSize - On entry, indicates the size (in characters) of
  478. lpErrorTextBuf. A buffer of at least 256 bytes is recommended.
  479. If the buffer is too small for the error string, the string will
  480. simply be truncated (it is still guaranteed to be null terminated).
  481. May be NULL if lpErrorTextBuf is NULL.
  482. lpProviderNameBuf - This points to a buffer that will receive the name
  483. of the provider that raised the error, if it is known. May be NULL
  484. if not required.
  485. lpnNameBufSize - This value indicates the size (in characters) of
  486. lpProviderNameBuf. If the buffer is too small for the provider
  487. name, the name will simply be truncated (it is still guaranteed to
  488. be null terminated). May be NULL if lpProviderNameBuf is NULL.
  489. Return Value:
  490. WN_SUCCESS - if the call was successful.
  491. WN_BAD_POINTER - One or more of the passed in pointers is bad.
  492. Other errors from WNetGetLastErrorW or FormatMessageW.
  493. Differences from Win 95:
  494. The provider name is returned as an empty string except in cases when
  495. the error was set by calling WNetSetLastError.
  496. Win 95 implements the feature that the caller can call other Win32 APIs
  497. before calling this API without losing the last error from the last
  498. WNet API. This is not implemented here. The caller must preserve the
  499. last error by calling GetLastError after the WNet API and SetLastError
  500. before MultinetGetErrorText.
  501. Win 95 gives providers the ability to customize error strings for
  502. standard Win32 error codes by calling NPSSetCustomText. We do not.
  503. --*/
  504. {
  505. DWORD status = WN_SUCCESS;
  506. DWORD nErrorBufSize = 0;
  507. DWORD nNameBufSize = 0;
  508. //
  509. // Probe output buffers, and initialize to empty strings
  510. //
  511. if (ARGUMENT_PRESENT(lpErrorTextBuf))
  512. {
  513. if (IS_BAD_WCHAR_BUFFER(lpErrorTextBuf, lpnErrorBufSize))
  514. {
  515. status = WN_BAD_POINTER;
  516. }
  517. else
  518. {
  519. nErrorBufSize = *lpnErrorBufSize;
  520. if (nErrorBufSize > 0)
  521. {
  522. lpErrorTextBuf[0] = L'\0';
  523. }
  524. }
  525. }
  526. if (ARGUMENT_PRESENT(lpProviderNameBuf))
  527. {
  528. if (IS_BAD_WCHAR_BUFFER(lpProviderNameBuf, lpnNameBufSize))
  529. {
  530. status = WN_BAD_POINTER;
  531. }
  532. else
  533. {
  534. nNameBufSize = *lpnNameBufSize;
  535. if (nNameBufSize > 0)
  536. {
  537. lpProviderNameBuf[0] = L'\0';
  538. }
  539. }
  540. }
  541. if (status != WN_SUCCESS)
  542. {
  543. return status;
  544. }
  545. //
  546. // Get the last error that occurred in this thread
  547. //
  548. DWORD LastError = GetLastError(); // last error in this thread
  549. DWORD ErrorCode = LastError; // message id to retrieve
  550. LPWSTR ErrorText = NULL; // error text to return
  551. LPWSTR ProviderName = NULL; // provider name to return
  552. BOOL bFreeErrorText = FALSE; // whether to call LocalFree
  553. //
  554. // If it's an extended error, look in this thread's error record for
  555. // the actual error code and strings
  556. //
  557. if (LastError == WN_EXTENDED_ERROR)
  558. {
  559. LPERROR_RECORD errorRecord = MprFindErrorRecord();
  560. if (errorRecord)
  561. {
  562. ErrorCode = errorRecord->ErrorCode;
  563. ErrorText = errorRecord->ErrorText;
  564. ProviderName = errorRecord->ProviderName;
  565. }
  566. else
  567. {
  568. // No error record found.
  569. // Either someone called SetLastError(WN_EXTENDED_ERROR) without
  570. // calling WNetSetErrorText, or an error record couldn't be
  571. // allocated because of lack of memory.
  572. // We'll return the standard message for WN_EXTENDED_ERROR.
  573. MPR_LOG0(ERROR,"MultinetGetErrorTextW:Couldn't retrieve extended error\n");
  574. }
  575. }
  576. WCHAR Buffer[UNKNOWN_ERROR_LENGTH] = L"";
  577. //
  578. // Compute the final error text
  579. //
  580. if (ARGUMENT_PRESENT(lpErrorTextBuf))
  581. {
  582. //
  583. // If it wasn't an extended error, or we didn't get a custom error
  584. // string from WNetGetLastError, load the standard message for the
  585. // error code.
  586. //
  587. if (ErrorText == NULL)
  588. {
  589. if (FormatMessage(
  590. FORMAT_MESSAGE_FROM_SYSTEM | // get msg from system
  591. FORMAT_MESSAGE_ALLOCATE_BUFFER | // let system alloc buffer
  592. FORMAT_MESSAGE_IGNORE_INSERTS, // no parms in msg
  593. NULL, // no source module or buffer
  594. ErrorCode, // message id
  595. 0, // use default language id
  596. (LPWSTR) &ErrorText,// pointer to buffer for message
  597. nErrorBufSize, // min num of chars to allocate
  598. NULL // message parameters (none)
  599. ))
  600. {
  601. bFreeErrorText = TRUE; // ErrorText was allocated by the system
  602. }
  603. else
  604. {
  605. //
  606. // Couldn't get a message from the system, so use a catch-all
  607. //
  608. int cch = LoadString(hDLL,
  609. IDS_XERR_UNKNOWN,
  610. Buffer,
  611. LENGTH(Buffer));
  612. //
  613. // If this fails, up the value of UNKNOWN_ERROR_LENGTH
  614. //
  615. ASSERT(cch > 0 && cch < LENGTH(Buffer));
  616. ErrorText = Buffer;
  617. }
  618. }
  619. ASSERT (ErrorText != NULL);
  620. //
  621. // Decide whether to copy the error text to the caller's buffer
  622. //
  623. DWORD ReqLen = wcslen(ErrorText) + 1 ;
  624. if (ReqLen > nErrorBufSize)
  625. {
  626. *lpnErrorBufSize = ReqLen;
  627. status = WN_MORE_DATA;
  628. }
  629. }
  630. //
  631. // Compute final provider name, decide whether to copy to caller's buffer
  632. //
  633. if (ARGUMENT_PRESENT(lpProviderNameBuf))
  634. {
  635. if (ProviderName == NULL)
  636. {
  637. ProviderName = L"";
  638. }
  639. DWORD ReqLen = wcslen(ProviderName) + 1;
  640. if (ReqLen > nNameBufSize)
  641. {
  642. *lpnNameBufSize = ReqLen;
  643. status = WN_MORE_DATA;
  644. }
  645. }
  646. //
  647. // Copy strings
  648. //
  649. if (status == WN_SUCCESS)
  650. {
  651. if (ARGUMENT_PRESENT(lpErrorTextBuf))
  652. {
  653. wcscpy(lpErrorTextBuf, ErrorText);
  654. }
  655. if (ARGUMENT_PRESENT(lpProviderNameBuf))
  656. {
  657. wcscpy(lpProviderNameBuf, ProviderName);
  658. }
  659. }
  660. //
  661. // Free buffer allocated by FormatMessage
  662. //
  663. if (bFreeErrorText)
  664. {
  665. LocalFree(ErrorText);
  666. }
  667. //
  668. // FormatMessage, LocalFree, or other APIs we may have called from this API,
  669. // could have changed the last error, so restore it now.
  670. //
  671. SetLastError(LastError);
  672. return status;
  673. }