/*++ Copyright (c) 1991 Microsoft Corporation Module Name: mprbase.cxx Abstract: Contains implementations of base classes that supply common code for Multi-Provider Router operations. Contains: CMprOperation::Perform CRoutedOperation::ValidateParameters CRoutedOperation::GetResult Author: Anirudh Sahni (anirudhs) 11-Oct-1995 Environment: User Mode -Win32 Notes: Revision History: 11-Oct-1995 AnirudhS Created. 05-May-1999 jschwart Make provider addition/removal dynamic --*/ // // Includes // #include "precomp.hxx" #include // _alloca // // External Globals and Statics // extern DWORD GlobalNumActiveProviders; CRoutedOperation::CPathCache CRoutedOperation::_PathCache; // // Defines // // // Local Function Prototypes // //+------------------------------------------------------------------------- // // Function: CMprOperation::Perform // // Purpose: See header file // // History: 11-Oct-95 AnirudhS Created. // // Notes: // //-------------------------------------------------------------------------- DWORD CMprOperation::Perform() { DWORD status = WN_SUCCESS; __try { // // Ask the derived class to validate the API parameters // status = ValidateParameters(); } __except (EXCEPTION_EXECUTE_HANDLER) { status = GetExceptionCode(); if (status != EXCEPTION_ACCESS_VIOLATION) { MPR_LOG2(ERROR,"CMprOperation(%s): Unexpected exception %#lx\n",_OpName,status); } status = WN_BAD_POINTER; } if (status == WN_SUCCESS) { // // Ask the derived class to perform the operation // status = GetResult(); } if (status != WN_SUCCESS) { SetLastError(status); } return status; } //+------------------------------------------------------------------------- // // Function: CRoutedOperation::Perform // // Purpose: See header file // // History: 27-May-99 jschwart Created. // // Notes: Since the CMprOperation should have no knowledge of // providers, deal with provider-related locking/checking // in the CRoutedOperation class, which is meant for // APIs that use the providers. // //-------------------------------------------------------------------------- DWORD CRoutedOperation::Perform(BOOL fCheckProviders) { // // If an API that uses this class creates another instance of the class // (e.g., CGetConnectionPerformance uses CGetConnection), there needs // to be a way to prevent the second call from trying to acquire the // exclusive lock while the original call holds the shared lock. // if (fCheckProviders) { MprCheckProviders(); } CProviderSharedLock PLock; return CMprOperation::Perform(); } //+------------------------------------------------------------------------- // // Function: CRoutedOperation::ValidateParameters // // Purpose: See header file // // History: 11-Oct-95 AnirudhS Created. // // Notes: // //-------------------------------------------------------------------------- DWORD CRoutedOperation::ValidateParameters() { // // Ask the derived class to validate the API parameters. // Also, if the API caller passed in a specific NP name, the derived // class should pass it back here, to be validated here. The provider // is looked up and stored in _pSpecifiedProvider. // If the API caller passed in a remote name that can be used as a // hint for routing, the derived class should pass it back here. A // pointer to it is stored in _RemoteName and used later, in GetResult(), // to help pick an efficient provider routing order. // LPCWSTR pwszProviderName = NULL; LPCWSTR pwszRemoteName = NULL; LPCWSTR pwszLocalName = NULL; ASSERT(MPRProviderLock.Have()); DWORD status = ValidateRoutedParameters(&pwszProviderName, &pwszRemoteName, &pwszLocalName); if (status == WN_SUCCESS) { // // Optimization: Store away the drive type. In GetResult(), // we need only call the providers if the local name is // a remote drive. _uDriveType is initialized to DRIVE_REMOTE // if (! IS_EMPTY_STRING(pwszLocalName) && pwszLocalName[1] == L':') { WCHAR wszRootPath[] = L" :\\"; wszRootPath[0] = pwszLocalName[0]; _uDriveType = GetDriveType(wszRootPath); } // This probes pwszRemoteName as well as saving its length RtlInitUnicodeString(&_RemoteName, pwszRemoteName); if (! IS_EMPTY_STRING(pwszProviderName)) { // // Level 1 init for MprFindProviderByName // if (!(GlobalInitLevel & FIRST_LEVEL)) { status = MprLevel1Init(); if (status != WN_SUCCESS) { return status; } } _pSpecifiedProvider = MprFindProviderByName(pwszProviderName); if (_pSpecifiedProvider == NULL) { return WN_BAD_PROVIDER; } } } return status; } //+------------------------------------------------------------------------- // // Function: CRoutedOperation::GetResult // // Purpose: See header file // // History: 11-Oct-95 AnirudhS Created. // // Notes: // //-------------------------------------------------------------------------- DWORD CRoutedOperation::GetResult() { DWORD status = WN_SUCCESS; LPPROVIDER *ProviderArray; // // Only call the providers if it's a remote drive // if (_uDriveType != DRIVE_REMOTE) { return WN_NOT_CONNECTED; } INIT_IF_NECESSARY(NETWORK_LEVEL, status); // // If there are no providers, return NO_NETWORK // if (GlobalNumActiveProviders == 0) { return WN_NO_NETWORK; } // Array of pointers into the GlobalProviderInfo array. DWORD numProviders; __try { ProviderArray = (LPPROVIDER *) _alloca(GlobalNumProviders * sizeof(LPPROVIDER)); } __except (EXCEPTION_EXECUTE_HANDLER) { status = WN_OUT_OF_MEMORY; } if (status != WN_SUCCESS) { return status; } // // Find the list of providers to call for this request. // if (_pSpecifiedProvider != NULL) { // // The caller requested a particular Provider // ProviderArray[0] = _pSpecifiedProvider; numProviders = 1; } else { // // A Provider name was not specified. Therefore, we must // create an ordered list and pick the best one. // status = FindCallOrder( &_RemoteName, ProviderArray, &numProviders, NETWORK_TYPE ); if (status != WN_SUCCESS) { return status; } } // // Loop through the list of providers until one answers the request, // or the list is exhausted. // DWORD statusFlag = 0; // Mask of combined error returns DWORD FirstNetPathError = WN_SUCCESS; // First NO_NET or BAD_NAME error DWORD FirstSignificantError = WN_SUCCESS; // First "other" error, used in // aggressive routing only status = WN_NOT_SUPPORTED; // Returned if no providers respond for (DWORD i=0; i*_pProviderFunction == NULL) { // // The provider doesn't supply the required entry point. // status = WN_NOT_SUPPORTED; } else { // // Ask the derived class to try the provider. // __try { MPR_LOG2(ROUTE, "%s: trying %ws ...\n", OpName(), _LastProvider->Resource.lpProvider); status = TestProvider(_LastProvider); MPR_LOG(ROUTE, "... provider returned %lu\n", status); } __except (EXCEPTION_EXECUTE_HANDLER) { status = GetExceptionCode(); MPR_LOG(ROUTE, "... provider threw EXCEPTION %#lx\n", status); if (status != EXCEPTION_ACCESS_VIOLATION) { MPR_LOG3(ERROR, "%s: Unexpected Exception %#lx " "calling %ws provider\n", OpName(), status, _LastProvider->Resource.lpProvider); } status = WN_BAD_POINTER; } } // // Decide whether to stop trying other providers and return the // error immediately, or continue trying other providers. // There are two algorithms for routing to providers, called // "lazy routing" and "aggressive routing". In lazy routing, // we always stop routing, unless the error was an insignificant // one (such as WN_BAD_NETNAME) indicating that the call may be // meant for some other provider. In aggressive routing, we // always continue routing, except on a few special errors (such // as WN_SUCCESS). // switch (status) { ////////////////////////////////////////////////////////////// // Always stop routing on these errors, even if routing // // aggressively // ////////////////////////////////////////////////////////////// case WN_SUCCESS: case WN_MORE_DATA: // // The provider successfully operated on this path, so add it // to the cache... // _PathCache.AddEntry(&_RemoteName, _LastProvider); // // ... and fall through // case WN_BAD_POINTER: case WN_ALREADY_CONNECTED: case WN_CANCEL: goto CleanExit; ////////////////////////////////////////////////////////////// // Always continue routing on these errors // // Classify them so that if we later decide to return one // // of them to the caller, we pick the most sensible one // ////////////////////////////////////////////////////////////// case WN_NOT_SUPPORTED: // // Ignore the error // break; case WN_NO_MORE_DEVICES: statusFlag |= NO_DEVICES; break; case WN_NOT_CONNECTED: statusFlag |= NOT_CONNECTED; break; case WN_NOT_CONTAINER: statusFlag |= NOT_CONTAINER; break; case WN_NO_NETWORK: case WN_FUNCTION_BUSY: case WN_NO_NET_OR_BAD_PATH: case WN_NOT_LOGGED_ON: statusFlag |= NO_NET; if (FirstNetPathError == WN_SUCCESS) { FirstNetPathError = status; } break; case WN_BAD_NETNAME: case ERROR_BAD_NETPATH: case WN_BAD_LOCALNAME: case WN_BAD_VALUE: case WN_BAD_LEVEL: case ERROR_REM_NOT_LIST: statusFlag |= BAD_NAME; if (FirstNetPathError == WN_SUCCESS) { FirstNetPathError = status; } break; ////////////////////////////////////////////////////////////// // On other errors, stop routing if lazy, continue if // // aggressive // ////////////////////////////////////////////////////////////// default: if (_AggressiveRouting) { // Remember the first one of these errors. It will take // precedence over other errors. if (FirstSignificantError == WN_SUCCESS) { FirstSignificantError = status; } break; // Note that if multiple providers return WN_EXTENDED_ERROR, // we'll return the error reported by the last one rather // than the first. } else { // Return this error immediately goto CleanExit; } } // switch } // for all providers // // If a specific provider was tried then return the error from that provider. // Otherwise, concoct the best return code from the errors returned. // if (numProviders > 1) { if (FirstSignificantError != WN_SUCCESS) { status = FirstSignificantError; } else if (statusFlag & NO_DEVICES) { status = WN_NO_MORE_DEVICES; } else if (statusFlag & NOT_CONNECTED) { status = WN_NOT_CONNECTED; } else if (statusFlag & NOT_CONTAINER) { status = WN_NOT_CONTAINER; } else if (statusFlag & (NO_NET | BAD_NAME)) { if ((statusFlag & (NO_NET | BAD_NAME)) == (NO_NET | BAD_NAME)) { // // Mix of special errors occured. // Pass back the combined error message. // status = WN_NO_NET_OR_BAD_PATH; } else { status = FirstNetPathError; } } else { ASSERT(status == WN_NOT_SUPPORTED); } } CleanExit: MPR_LOG2(ROUTE, "CRoutedOperation(%s): returning %lu\n\n", OpName(), status); return status; } //+------------------------------------------------------------------------- // // Function: CRoutedOperation::CPathCache::Construct // // Purpose: Constructor, called explicitly to avoid dependence on CRT // // History: 09-Apr-96 AnirudhS Created. // //-------------------------------------------------------------------------- void CRoutedOperation::CPathCache::Construct() { InitializeCriticalSection(&_Lock); RtlZeroMemory(_RecentPaths, sizeof(_RecentPaths)); InitializeListHead(&_ListHead); _NumFree = PATH_CACHE_SIZE; } //+------------------------------------------------------------------------- // // Function: CRoutedOperation::CPathCache::Destroy // // Purpose: Destructor, called explicitly to avoid dependence on CRT // // History: 09-Apr-96 AnirudhS Created. // //-------------------------------------------------------------------------- void CRoutedOperation::CPathCache::Destroy() { // // This is really needed only if the DLL is being unloaded because of // a FreeLibrary call, not if the process is exiting // for (DWORD i = _NumFree; i < PATH_CACHE_SIZE; i++) { LocalFree(_RecentPaths[i].Path.Buffer); } DeleteCriticalSection(&_Lock); } //+------------------------------------------------------------------------- // // Function: CRoutedOperation::CPathCache::AddEntry // // Purpose: Add an entry to the cache // // History: 09-Apr-96 AnirudhS Created. // //-------------------------------------------------------------------------- void CRoutedOperation::CPathCache::AddEntry( const UNICODE_STRING * Path, LPPROVIDER Provider ) { if (Path->Length == 0 || Path->Length >= (MAX_PATH*sizeof(WCHAR))) { // // Don't add empty or too-long paths to the cache // return; } ASSERT(Path->MaximumLength == Path->Length + sizeof(UNICODE_NULL)); EnterCriticalSection(&_Lock); CacheEntry *pEntry = NULL; // Entry to write // // See if there's a matching path string in the cache already // for (PLIST_ENTRY pLinks = _ListHead.Flink; pLinks != &_ListHead; pLinks = pLinks->Flink) { pEntry = CONTAINING_RECORD(pLinks, CacheEntry, Links); if (RtlEqualUnicodeString(&pEntry->Path, (PUNICODE_STRING) Path, TRUE)) { break; } pEntry = NULL; } if (pEntry == NULL) { // // No matching entry. // If there's a free entry in the array, use it. // Otherwise overwrite the last entry in the list. // if (_NumFree > 0) { _NumFree--; pEntry = &_RecentPaths[_NumFree]; // // Add this new entry to the list. // InsertHeadList(&_ListHead, &pEntry->Links); } else { ASSERT(!IsListEmpty(&_ListHead)); pEntry = CONTAINING_RECORD(_ListHead.Blink, CacheEntry, Links); } // // Copy the path string into the cache. Re-use the string buffer, // unless it's too small. // if (pEntry->Path.MaximumLength < Path->MaximumLength) { // // Re-allocate the string buffer. Allocate twice as much space // as needed, but never more than MAX_PATH Unicode characters. // Note, here we know that MaximumLength <= MAX_PATH characters. // HLOCAL NewBuffer = LocalAlloc( 0, min(Path->MaximumLength * 2, MAX_PATH * sizeof(WCHAR)) ); if (NewBuffer == NULL) { // // Couldn't allocate. Don't add to the cache. // (If it was unused, this cache entry is lost forever. // CODEWORK try to recover it in this case?) // goto CleanExit; } LocalFree(pEntry->Path.Buffer); pEntry->Path.Buffer = (PWSTR) NewBuffer; pEntry->Path.MaximumLength = (USHORT)LocalSize(NewBuffer); } RtlCopyUnicodeString(&pEntry->Path, (PUNICODE_STRING) Path); } // // Remember the provider in the cache. (This overwrites any previously // remembered provider for the path.) // pEntry->Provider = Provider; MPR_LOG2(ROUTE, "cache: cached %ws for %ws\n", Provider->Resource.lpProvider, Path->Buffer); // // Move this entry to the front of the list, if it isn't there already. // if (_ListHead.Flink != &pEntry->Links) { RemoveEntryList(&pEntry->Links); InsertHeadList(&_ListHead, &pEntry->Links); } CleanExit: LeaveCriticalSection(&_Lock); } //+------------------------------------------------------------------------- // // Function: CRoutedOperation::CPathCache::FindEntry // // Purpose: Search for an entry in the cache // // History: 09-Apr-96 AnirudhS Created. // //-------------------------------------------------------------------------- LPPROVIDER CRoutedOperation::CPathCache::FindEntry( const UNICODE_STRING * Path ) { if (Path->Length == 0) { return NULL; } ASSERT(Path->MaximumLength == Path->Length + sizeof(UNICODE_NULL)); EnterCriticalSection(&_Lock); // // Search forward in the list for a matching path string // LPPROVIDER Provider = NULL; for (PLIST_ENTRY pLinks = _ListHead.Flink; pLinks != &_ListHead; pLinks = pLinks->Flink) { CacheEntry *pEntry = CONTAINING_RECORD(pLinks, CacheEntry, Links); if (RtlEqualUnicodeString(&pEntry->Path, (PUNICODE_STRING) Path, TRUE)) { Provider = pEntry->Provider; // // Move this entry to the front of the list, if it isn't there already. // if (_ListHead.Flink != &pEntry->Links) { RemoveEntryList(&pEntry->Links); InsertHeadList(&_ListHead, &pEntry->Links); } break; } } LeaveCriticalSection(&_Lock); MPR_LOG2(ROUTE, "cache: found %ws for %ws\n", (Provider ? Provider->Resource.lpProvider : L"no cached provider"), Path->Buffer); return Provider; }