/*++ Copyright (c) 1991 Microsoft Corporation Module Name: lasterr.c Abstract: Contains the entry point for WNetGetLastErrorW WNetSetLastErrorW MultinetGetErrorTextW Also contains the following support routines: MprAllocErrorRecord MprFreeErrorRecord MprFindErrorRecord Author: Dan Lafferty (danl) 17-Oct-1991 Environment: User Mode - Win32 Revision History: 09-Aug-1996 anirudhs Fixed bug in MprFindErrorRecord. Replaced MprFreeErrorRecord with MprFreeAllErrorRecords (see mprinit.cxx). Moved some definitions from mprdata.h here. 20-Jun-1995 anirudhs Added MultinetGetErrorTextW. Removed CurrentThreadId parameter to MprFindErrorRecord. Some other cleanups. 11-Aug-1993 danl WNetGetLastErrorW: Got rid of TCHAR stuff and committed to UNICODE. Also, consistently use number of characters rather than number of bytes. 08-Jun-1993 danl Now we handle the case where WNetSetLastError is called with a threadId that we don't know about. This happens in the case where a sub-thread does a LoadLibrary on MPR.DLL. We only get notification of the one thread attaching. Yet other threads within that process may call the WinNet functions. 17-Oct-1991 danl Created --*/ // // INCLUDES // #include "precomp.hxx" #include // STRSIZE, STRCPY #include "mprres.h" // IDS_XERR_UNKNOWN //======================= // Data Structures //======================= typedef struct _ERROR_RECORD { struct _ERROR_RECORD *Prev; struct _ERROR_RECORD *Next; DWORD ThreadId; DWORD ErrorCode; LPTSTR ErrorText; // This is an allocated buffer LPTSTR ProviderName; // This is an allocated buffer } ERROR_RECORD, *LPERROR_RECORD; //======================= // MACROS/DEFINES //======================= #define FIND_END_OF_LIST(record) while(record->Next != NULL) { \ record=record->Next; \ } #define REMOVE_FROM_LIST(record) record->Prev->Next = record->Next; \ if (record->Next != NULL) { \ record->Next->Prev = record->Prev; \ } #define ADD_TO_LIST(record, newRec) FIND_END_OF_LIST(record) \ record->Next = newRec; \ newRec->Prev = record; \ newRec->Next = NULL; // // The string (in English) is 44 characters long (+1 for the NULL) // #define UNKNOWN_ERROR_LENGTH 80 //======================= // GLOBALS //======================= extern HMODULE hDLL; // DLL instance handle // // Local Functions // LPERROR_RECORD MprAllocErrorRecord( VOID); LPERROR_RECORD MprFindErrorRecord( VOID); // // Global Data Structures // ERROR_RECORD MprErrorRecList; // Initialized to zeros by loader CRITICAL_SECTION MprErrorRecCritSec; // Initialized in mprinit.cxx DWORD WNetGetLastErrorW( OUT LPDWORD lpError, OUT LPWSTR lpErrorBuf, IN DWORD nErrorBufLen, OUT LPWSTR lpNameBuf, IN DWORD nNameBufLen ) /*++ Routine Description: This function allows users to obtain the error code and accompanying text when they receive a WN_EXTENDED_ERROR in response to a WNet API function call. Arguments: lpError - This is a pointer to the location that will receive the error code. lpErrorBuf - This points to a buffer that will receive the null terminated string describing the error. nErrorBufLen - This value that indicates the size (in characters) of lpErrorBuf. If the buffer is too small to receive an error string, the string will simply be truncated. (it is still guaranteed to be null terminated). A buffer of at least 256 bytes is recommended. lpNameBuf - This points to a buffer that will receive the name of the provider that raised the error. nNameBufLen - This value indicates the size (in characters) of lpNameBuf. If the buffer is too small to receive an error string, the string will simply be truncated. (it is still guaranteed to be null terminated). Return Value: WN_SUCCESS - if the call was successful. WN_BAD_POINTER - One or more of the passed in pointers is bad. WN_DEVICE_ERROR - This indicates that the threadID for the current thread could not be found in that table anywhere. This should never happen. --*/ { LPERROR_RECORD errorRecord; DWORD nameStringLen; DWORD textStringLen; DWORD status = WN_SUCCESS; // // Screen the parameters as best we can. // __try { // // If output buffers are provided, Probe them. // *lpError = WN_SUCCESS; if (IsBadWritePtr(lpErrorBuf, nErrorBufLen * sizeof(WCHAR)) || IsBadWritePtr(lpNameBuf, nNameBufLen * sizeof(WCHAR))) { status = WN_BAD_POINTER; } } __except (EXCEPTION_EXECUTE_HANDLER) { status = GetExceptionCode(); if (status != EXCEPTION_ACCESS_VIOLATION) { MPR_LOG(ERROR,"WNetGetLastError:Unexpected Exception 0x%lx\n",status); } status = WN_BAD_POINTER; } if (status != WN_SUCCESS) { return(status); } // // Get the current thread's error record. // errorRecord = MprFindErrorRecord(); if (errorRecord != NULL) { // // The record was found in the linked list. // See if there is a buffer to put data into. // if (nErrorBufLen > 0) { // // Check to see if there is error text to return. // If not, indicate a 0 length string. // if(errorRecord->ErrorText == NULL) { *lpErrorBuf = L'\0'; } else { // // If the error text won't fit into the user buffer, fill it // as best we can, and NULL terminate it. // textStringLen = wcslen(errorRecord->ErrorText); if(nErrorBufLen < textStringLen + 1) { textStringLen = nErrorBufLen - 1; } // // textStringLen now contains the number of characters we // will copy without the NULL terminator. // wcsncpy(lpErrorBuf, errorRecord->ErrorText, textStringLen); *(lpErrorBuf + textStringLen) = L'\0'; } } // // If there is a Name Buffer to put the provider into, then... // if (nNameBufLen > 0) { // // See if the Provider Name will fit in the user buffer. // nameStringLen = errorRecord->ProviderName ? (wcslen(errorRecord->ProviderName) + 1) : 1 ; // // If the user buffer is smaller than the required size, // set up to copy the smaller of the two. // if(nNameBufLen < nameStringLen + 1) { nameStringLen = nNameBufLen - 1; } if (errorRecord->ProviderName) { wcsncpy(lpNameBuf, errorRecord->ProviderName, nameStringLen); *(lpNameBuf + nameStringLen) = L'\0'; } else { *lpNameBuf = L'\0'; } } *lpError = errorRecord->ErrorCode; return(WN_SUCCESS); } else { // // If we get here, a record for the current thread could not be found. // *lpError = WN_SUCCESS; if (nErrorBufLen > 0) { *lpErrorBuf = L'\0'; } if (nNameBufLen > 0) { *lpNameBuf = L'\0'; } return(WN_SUCCESS); } } VOID WNetSetLastErrorW( IN DWORD err, IN LPWSTR lpError, IN LPWSTR lpProvider ) /*++ Routine Description: This function is used by Network Providers to set extended errors. It saves away the error information in a "per thread" data structure. This function also calls SetLastError with the WN_EXTENDED_ERROR. Arguments: err - The error that occured. This may be a Windows defined error, in which case LpError is ignored. or it may be ERROR_EXTENDED_ERROR to indicate that the provider has a network specific error to report. lpError - String describing a network specific error. lpProvider - String naming the network provider raising the error. Return Value: none --*/ { DWORD status = WN_SUCCESS; LPERROR_RECORD errorRecord; // // Set the extended error status that tells the user they need to // call WNetGetLastError to obtain further information. // SetLastError(WN_EXTENDED_ERROR); // // Get the Error Record for the current thread. // errorRecord = MprFindErrorRecord(); // // if there isn't a record for the current thread, then add it. // if (errorRecord == NULL) { errorRecord = MprAllocErrorRecord(); if (errorRecord == NULL) { MPR_LOG0(ERROR,"WNetSetLastError:Could not allocate Error Record\n"); return; } } // // Update the error code in the error record. At the same time, // free up any old strings since they are now obsolete, and init // the pointer to NULL. Also set the ProviderName pointer in the // ErrorRecord to point to the provider's name. // errorRecord->ErrorCode = err; LocalFree(errorRecord->ProviderName); errorRecord->ProviderName = NULL; LocalFree(errorRecord->ErrorText); errorRecord->ErrorText = NULL; // // Allocate memory for the provider name. // __try { errorRecord->ProviderName = (WCHAR *) LocalAlloc(LPTR, STRSIZE(lpProvider)); } __except (EXCEPTION_EXECUTE_HANDLER) { // // We have a problem with lpProvider. // status = GetExceptionCode(); if (status != EXCEPTION_ACCESS_VIOLATION) { MPR_LOG(ERROR,"WNetSetLastError:Unexpected Exception 0x%lx\n",status); } status = WN_BAD_POINTER; } if (status != WN_SUCCESS) { return; } if (errorRecord->ProviderName == NULL) { // // Unable to allocate memory for the Provider Name for the error // record. // MPR_LOG(ERROR, "WNetSetLastError:Unable to allocate mem for ProviderName\n",0); return; } // // Copy the string to the newly allocated buffer. // wcscpy(errorRecord->ProviderName, lpProvider); // // Allocate memory for the storage of the error text. // __try { errorRecord->ErrorText = (WCHAR *) LocalAlloc(LPTR,STRSIZE(lpError)); } __except (EXCEPTION_EXECUTE_HANDLER) { // // We have a problem with lpError. // status = GetExceptionCode(); if (status != EXCEPTION_ACCESS_VIOLATION) { MPR_LOG(ERROR,"WNetSetLastError:Unexpected Exception 0x%lx\n",status); } status = WN_BAD_POINTER; } if (status != WN_SUCCESS) { errorRecord->ErrorText = NULL; } if (errorRecord->ErrorText == NULL) { // // If we were unsuccessful in allocating for the ErrorText, then // abort. The ErrorText Pointer has already been set to null. // MPR_LOG(ERROR,"WNetSetLastError:Unable to Alloc for ErrorText %d\n", GetLastError()); return; } // // Copy the error text into the newly allocated buffer. // wcscpy(errorRecord->ErrorText, lpError); return; } LPERROR_RECORD MprFindErrorRecord( VOID) /*++ Routine Description: Looks through the linked list of ErrorRecords in search of one for the current thread. Arguments: none Return Value: Returns LPERROR_RECORD if an error record was found. Otherwise, it returns NULL. --*/ { LPERROR_RECORD errorRecord; DWORD CurrentThreadId = GetCurrentThreadId(); EnterCriticalSection(&MprErrorRecCritSec); for (errorRecord = MprErrorRecList.Next; errorRecord != NULL; errorRecord = errorRecord->Next) { if (errorRecord->ThreadId == CurrentThreadId) { break; } } LeaveCriticalSection(&MprErrorRecCritSec); return(errorRecord); } LPERROR_RECORD MprAllocErrorRecord( VOID) /*++ Routine Description: This function allocates and initializes an Error Record for the current thread. Then it places the error record in the global MprErrorRecList. Even if the thread exits, the record is not freed until the DLL unloads. This is OK because this function is called only if a provider calls WNetSetLastError, which is rare. Arguments: none Return Value: TRUE - The operation completed successfully FALSE - An error occured in the allocation. Note: --*/ { LPERROR_RECORD record; LPERROR_RECORD errorRecord; // // Allocate memory for the storage of the error message // and add the record to the linked list. // errorRecord = (LPERROR_RECORD)LocalAlloc(LPTR,sizeof (ERROR_RECORD)); if (errorRecord == NULL) { MPR_LOG1(ERROR,"MprAllocErrorRecord:LocalAlloc Failed %d\n", GetLastError()); return NULL; } // // Initialize the error record // errorRecord->ThreadId = GetCurrentThreadId(); errorRecord->ErrorCode = WN_SUCCESS; errorRecord->ErrorText = NULL; // // Add the record to the linked list. // EnterCriticalSection(&MprErrorRecCritSec); record = &MprErrorRecList; ADD_TO_LIST(record, errorRecord); LeaveCriticalSection(&MprErrorRecCritSec); return errorRecord; } VOID MprFreeAllErrorRecords( VOID) /*++ Routine Description: This function is called when the DLL is unloading due to a FreeLibrary call. It frees all the error records (for all threads) that have been created since the DLL was loaded. If there is a pointer to a text string in a record, the buffer for that string is freed also. Arguments: Return Value: Note: --*/ { LPERROR_RECORD nextRecord; EnterCriticalSection(&MprErrorRecCritSec); for (LPERROR_RECORD record = MprErrorRecList.Next; record != NULL; record = nextRecord) { MPR_LOG1(TRACE,"MprFreeErrorRecord: Freeing Record for thread 0x%x\n", record->ThreadId); LocalFree(record->ErrorText); LocalFree(record->ProviderName); nextRecord = record->Next; LocalFree(record); } MprErrorRecList.Next = NULL; LeaveCriticalSection(&MprErrorRecCritSec); } DWORD MultinetGetErrorTextW( OUT LPWSTR lpErrorTextBuf OPTIONAL, IN OUT LPDWORD lpnErrorBufSize, OUT LPWSTR lpProviderNameBuf OPTIONAL, IN OUT LPDWORD lpnNameBufSize ) /*++ Routine Description: This is an internal interface between the shell and the MPR. It combines the work of calling GetLastError and WNetGetLastError into one call. It returns the text for the last error that occurred in a WNet API in the current thread. The error text could have been customized by the provider calling WNetSetLastError. Arguments: lpErrorTextBuf - Pointer to buffer that will receive a null-terminated string describing the error. May be NULL if not required. lpnErrorBufSize - On entry, indicates the size (in characters) of lpErrorTextBuf. A buffer of at least 256 bytes is recommended. If the buffer is too small for the error string, the string will simply be truncated (it is still guaranteed to be null terminated). May be NULL if lpErrorTextBuf is NULL. lpProviderNameBuf - This points to a buffer that will receive the name of the provider that raised the error, if it is known. May be NULL if not required. lpnNameBufSize - This value indicates the size (in characters) of lpProviderNameBuf. If the buffer is too small for the provider name, the name will simply be truncated (it is still guaranteed to be null terminated). May be NULL if lpProviderNameBuf is NULL. Return Value: WN_SUCCESS - if the call was successful. WN_BAD_POINTER - One or more of the passed in pointers is bad. Other errors from WNetGetLastErrorW or FormatMessageW. Differences from Win 95: The provider name is returned as an empty string except in cases when the error was set by calling WNetSetLastError. Win 95 implements the feature that the caller can call other Win32 APIs before calling this API without losing the last error from the last WNet API. This is not implemented here. The caller must preserve the last error by calling GetLastError after the WNet API and SetLastError before MultinetGetErrorText. Win 95 gives providers the ability to customize error strings for standard Win32 error codes by calling NPSSetCustomText. We do not. --*/ { DWORD status = WN_SUCCESS; DWORD nErrorBufSize = 0; DWORD nNameBufSize = 0; // // Probe output buffers, and initialize to empty strings // if (ARGUMENT_PRESENT(lpErrorTextBuf)) { if (IS_BAD_WCHAR_BUFFER(lpErrorTextBuf, lpnErrorBufSize)) { status = WN_BAD_POINTER; } else { nErrorBufSize = *lpnErrorBufSize; if (nErrorBufSize > 0) { lpErrorTextBuf[0] = L'\0'; } } } if (ARGUMENT_PRESENT(lpProviderNameBuf)) { if (IS_BAD_WCHAR_BUFFER(lpProviderNameBuf, lpnNameBufSize)) { status = WN_BAD_POINTER; } else { nNameBufSize = *lpnNameBufSize; if (nNameBufSize > 0) { lpProviderNameBuf[0] = L'\0'; } } } if (status != WN_SUCCESS) { return status; } // // Get the last error that occurred in this thread // DWORD LastError = GetLastError(); // last error in this thread DWORD ErrorCode = LastError; // message id to retrieve LPWSTR ErrorText = NULL; // error text to return LPWSTR ProviderName = NULL; // provider name to return BOOL bFreeErrorText = FALSE; // whether to call LocalFree // // If it's an extended error, look in this thread's error record for // the actual error code and strings // if (LastError == WN_EXTENDED_ERROR) { LPERROR_RECORD errorRecord = MprFindErrorRecord(); if (errorRecord) { ErrorCode = errorRecord->ErrorCode; ErrorText = errorRecord->ErrorText; ProviderName = errorRecord->ProviderName; } else { // No error record found. // Either someone called SetLastError(WN_EXTENDED_ERROR) without // calling WNetSetErrorText, or an error record couldn't be // allocated because of lack of memory. // We'll return the standard message for WN_EXTENDED_ERROR. MPR_LOG0(ERROR,"MultinetGetErrorTextW:Couldn't retrieve extended error\n"); } } WCHAR Buffer[UNKNOWN_ERROR_LENGTH] = L""; // // Compute the final error text // if (ARGUMENT_PRESENT(lpErrorTextBuf)) { // // If it wasn't an extended error, or we didn't get a custom error // string from WNetGetLastError, load the standard message for the // error code. // if (ErrorText == NULL) { if (FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM | // get msg from system FORMAT_MESSAGE_ALLOCATE_BUFFER | // let system alloc buffer FORMAT_MESSAGE_IGNORE_INSERTS, // no parms in msg NULL, // no source module or buffer ErrorCode, // message id 0, // use default language id (LPWSTR) &ErrorText,// pointer to buffer for message nErrorBufSize, // min num of chars to allocate NULL // message parameters (none) )) { bFreeErrorText = TRUE; // ErrorText was allocated by the system } else { // // Couldn't get a message from the system, so use a catch-all // int cch = LoadString(hDLL, IDS_XERR_UNKNOWN, Buffer, LENGTH(Buffer)); // // If this fails, up the value of UNKNOWN_ERROR_LENGTH // ASSERT(cch > 0 && cch < LENGTH(Buffer)); ErrorText = Buffer; } } ASSERT (ErrorText != NULL); // // Decide whether to copy the error text to the caller's buffer // DWORD ReqLen = wcslen(ErrorText) + 1 ; if (ReqLen > nErrorBufSize) { *lpnErrorBufSize = ReqLen; status = WN_MORE_DATA; } } // // Compute final provider name, decide whether to copy to caller's buffer // if (ARGUMENT_PRESENT(lpProviderNameBuf)) { if (ProviderName == NULL) { ProviderName = L""; } DWORD ReqLen = wcslen(ProviderName) + 1; if (ReqLen > nNameBufSize) { *lpnNameBufSize = ReqLen; status = WN_MORE_DATA; } } // // Copy strings // if (status == WN_SUCCESS) { if (ARGUMENT_PRESENT(lpErrorTextBuf)) { wcscpy(lpErrorTextBuf, ErrorText); } if (ARGUMENT_PRESENT(lpProviderNameBuf)) { wcscpy(lpProviderNameBuf, ProviderName); } } // // Free buffer allocated by FormatMessage // if (bFreeErrorText) { LocalFree(ErrorText); } // // FormatMessage, LocalFree, or other APIs we may have called from this API, // could have changed the last error, so restore it now. // SetLastError(LastError); return status; }