/*++ Copyright (c) 1998 Microsoft Corporation Module Name : WpConfig.cxx Abstract: Module implementing the Worker Process Configuration Data structure. WP_CONFIG object encapsulates configuration supplied from the commandline as well as remotely supplied from the admin process. Author: Murali R. Krishnan ( MuraliK ) 21-Oct-1998 Environment: Win32 - User Mode Project: IIS Worker Process --*/ /************************************************************ * Include Headers ************************************************************/ # include "precomp.hxx" #include "CatMeta.h" /* Usage: \bin\iisrearc\inetsrv\iiswp [options] APN APN -- AppPool Name -d -- Indicates that the process should register the given namespace itself. This mode is for running worker process in stand alone mode (for debugging) -l -- Log errors that stop the worker process into the eventlog -ld -- Disables logging the errors of worker process to eventlog (default is not write to eventlog) -a -- Look for web admin service and register with the same (default is look for web admin service) -ad -- Do not look for web admin service and do registration -r -- Restart the worker process after n requests. -t -- Shutdown the worker process if idle for n milliseconds. -h -- Assume the default site is rooted at this path. uses the syntax: {http[s]://IP:port/URL | http[s]://hostname:port/URL }+ with space as separator eg: -d http://localhost:80/ => listen for all HTTP requests on port 80 eg: -d http://localhost:80/ http://localhost:81/ => listen on port 80 & 81 */ const CHAR g_rgchUsage[] = "Usage: %ws [options] APN\n" "\tAPN -- AppPool Name\n" "\t-d -- Indicates that the process should register the given \n" "\t\tnamespace itself. This mode is for running worker process in\n" "\t\tstand alone mode (for debugging)\n" "\t-l -- Log errors that stop the worker process into the eventlog\n" "\t-ld -- Disables logging the errors of worker process to eventlog\n" "\t\t(default is not write to eventlog)\n" "\t-a -- Look for web admin service and register with the same\n" "\t\t(default is look for web admin service)\n" "\t-ad -- Do not look for web admin service and do registration\n" "\t-r -- Restart the worker process after n requests\n" "\t-t -- Shutdown the worker process if idle for n milliseconds\n" "\t-h -- Assume the default site is rooted at this path\n" "\t-p -- Tell COR to add IceCAP instrumentation\n" "\t uses the syntax: {http[s]://IP:port/URL | http[s]://hostname:port/URL }+\n" "\t\twith space as separator\n" "\t\t eg: -d http://*:80/ => listen for all HTTP requests on port 80\n" "\t\t eg: -d http://localhost:80/ http://localhost:81/ => listen on port 80 & 81\n" ; /************************************************************ * Convert a DWORD to a hex string. The incoming buffer must * be big enough to hold "12345678" (including the null * teriminator). ************************************************************/ static const WCHAR hexDigits[] = L"0123456789ABCDEF"; // Convert a 32-bit DWORD to a hex string. The incoming buffer must // be big enough to hold "12345678" (including the null terminator). void DwordToHexString(DWORD dword, WCHAR wszHexDword[]) { const int DWORDHEXDIGITS = sizeof(DWORD) * 2; WCHAR* p = wszHexDword; unsigned shift = (sizeof(DWORD) * 8) - 4; DWORD mask = 0xFu << shift; int i; for (i = 0; i < DWORDHEXDIGITS; ++i, mask >>= 4, shift -= 4) { unsigned digit = (dword & mask) >> shift; p[i] = hexDigits[digit]; } p[i] = L'\0'; } HMODULE LoadXSP() { // Load xspisapi.dll. We find this DLL by inspecting // HKLM/Software/Microsoft/XSP and it's InstallDir value. HKEY hkeyXSP = NULL; HMODULE hMod = NULL; __try { long rc = RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\XSP", 0, // reserved KEY_READ, &hkeyXSP); if (rc != ERROR_SUCCESS) { IF_DEBUG( TRACE) { DBGPRINTF((DBG_CONTEXT, "XSP is not installed\n")); } __leave; } WCHAR path[MAX_PATH + 1]; DWORD valueType = 0; DWORD len = MAX_PATH; rc = RegQueryValueEx(hkeyXSP, L"InstallDir", NULL, // reserved &valueType, reinterpret_cast(path), &len); if (rc != ERROR_SUCCESS || (valueType != REG_SZ && valueType != REG_EXPAND_SZ)) { IF_DEBUG( TRACE) { DBGPRINTF((DBG_CONTEXT, "XSP is not installed\n")); } __leave; } len /= sizeof path[0]; // Convert to characters // I now have a path something like "d:\winnt\xspdt", so just // append the DLL name. wcscat(path + len - 1, L"\\xspisapi.dll"); hMod = LoadLibraryEx(path, NULL, LOAD_WITH_ALTERED_SEARCH_PATH); } __finally { if (hkeyXSP != NULL) RegCloseKey(hkeyXSP); } return hMod; } /************************************************************ * Stupid pointer class. ************************************************************/ template class StupidComPointer { public: StupidComPointer() : _ptr(NULL) { } ~StupidComPointer() { if (_ptr != NULL) _ptr->Release(); } operator T*() { return (T*)_ptr; } T* operator->() { return (T*)_ptr; } T** operator&() { return (T**)&_ptr; } T& operator*() { return *(T*)_ptr; } private: IUnknown* _ptr; }; /************************************************************ * Member functions of WP_CONFIG ************************************************************/ WP_CONFIG::WP_CONFIG(void) : m_pwszAppPoolName (AP_NAME), m_fSetupControlChannel(false), m_fLogErrorsToEventLog(false), m_fRegisterWithWAS (true), m_RestartCount (0), m_NamedPipeId (0), m_IdleTime (0), m_homeDirectory (NULL), m_istDispenser (NULL), m_catFile (NULL), m_xspisapiDLL (NULL), _AppDomainGetObject(NULL), _AppDomainUninitFactory(NULL) { lstrcpy( m_pwszProgram, L"WP"); InitializeCriticalSection(&m_workerTableGuard); } WP_CONFIG::~WP_CONFIG() { DeleteCriticalSection(&m_workerTableGuard); m_ulcc.Cleanup(); free(m_homeDirectory); free(m_catFile); if (m_istDispenser != NULL) m_istDispenser->Release(); } void WP_CONFIG::PrintUsage() const { DBGPRINTF((DBG_CONTEXT, g_rgchUsage, m_pwszProgram)); } /********************************************************************++ Routine Description: Parses the command line to read in all configuration supplied. This function updates the state variables inside WP_CONFIG for use in starting up the Worker process. See g_rgchUsage[] for details on the arguments that can be supplied Arguments: argc - count of arguments supplied argv - pointer to strings containing the arguments. Returns: Boolean --********************************************************************/ bool WP_CONFIG::ParseCommandLine(int argc, PWSTR argv[]) { bool fRet = true; int iArg; lstrcpyn( m_pwszProgram, argv[0], sizeof m_pwszProgram / sizeof m_pwszProgram[0]); if ( argc < 2) { DBGPRINTF((DBG_CONTEXT, "Invalid number of parameters (%d)\n", argc)); PrintUsage(); return (false); } for( iArg = 1; iArg < argc; iArg++) { if ( (argv[iArg][0] == L'-') || (argv[iArg][0] == L'/')) { switch (argv[iArg][1]) { case L'c': case L'C': DBGPRINTF((DBG_CONTEXT, "-C: obsolete\n")); break; case L'd': case L'D': m_fSetupControlChannel = true; iArg++; while ( (iArg < argc-1) && (argv[iArg][0] != L'-') && (argv[iArg][0] != L'/')) { if ( !InsertURLIntoList(argv[iArg]) ) { DBGPRINTF((DBG_CONTEXT, "Invalid URL: %ws\n", argv[iArg])); } iArg++; } iArg--; break; case L'a': case L'A': if ( (L'd' == argv[iArg][2]) || (L'D' == argv[iArg][2])) { m_fRegisterWithWAS = false; } else { // -a NamedPipeId if (L'\0' == argv[iArg][2]) { iArg++; } m_NamedPipeId = wcstoul(argv[iArg], NULL, 0); DBGPRINTF((DBG_CONTEXT, "NamedPipe Id, %lu\n", m_NamedPipeId)); if (0 == m_NamedPipeId) { DBGPRINTF((DBG_CONTEXT, "Invalid NamedPipe Id, %ws\n", argv[iArg])); fRet = false; } } break; case L'l': case L'L': DBGPRINTF((DBG_CONTEXT, "Warning: This option is not supported presently\n")); if (L' ' == argv[iArg][0]) { m_fLogErrorsToEventLog = true; } break; case L'r': case L'R': m_RestartCount = wcstoul(argv[++iArg], NULL, 0); if (m_RestartCount == 0) { DBGPRINTF((DBG_CONTEXT, "Invalid maximum requests %ws\n", argv[iArg])); return false; } else { DBGPRINTF((DBG_CONTEXT, "Maximum requests is %lu\n", m_RestartCount)); } break; case L't': case L'T': m_IdleTime = wcstoul(argv[++iArg], NULL, 0); if (m_IdleTime == 0) { DBGPRINTF((DBG_CONTEXT, "Invalid idle time %ws\n", argv[iArg])); return false; } else { DBGPRINTF((DBG_CONTEXT, "The idle time value is %lu\n", m_IdleTime)); } break; case L'h': case L'H': m_homeDirectory = _wcsdup(argv[++iArg]); if (m_homeDirectory == NULL) return false; break; case L'p': case L'P': SetEnvironmentVariable(L"CORDBG_ENABLE", L"0x20"); SetEnvironmentVariable(L"COR_PROFILER", L"\"ComPlusIcecapProfile.CorIcecapProfiler\""); SetEnvironmentVariable(L"PROF_CONFIG", L"/callcap"); break; default: case L'?': fRet = false; break; } // switch } else { // // Take the next item as the NSG name and bail out here // m_pwszAppPoolName = argv[iArg]; if ( iArg != (argc - 1)) { // // this is not the last argument => unwanted parameters exist // give warning and ignore // DBGPRINTF((DBG_CONTEXT, "Warning: Too many arguments supplied\n")); } break; // get out of here. } } if (!m_fRegisterWithWAS) { m_RestartCount = 0; m_IdleTime = 0; } if (!fRet) { PrintUsage(); } return ( fRet); } // WP_CONFIG::ParseCommandLine() /********************************************************************++ Routine Description: Sets up the control channel for processing requests. It uses the configuration parameters supplied for initializing the UL_CONTROL_CHANNEL. See g_rgchUsage[] for details on the arguments that can be supplied Arguments: Returns: Win32 error --********************************************************************/ ULONG WP_CONFIG::SetupControlChannel(void) { // // Setup a control channel for our local use now. Used mainly for // the purpose of debugging. // In general control channel work is done by the AdminProces. // return m_ulcc.Initialize( m_mszURLList, m_pwszAppPoolName); } // WP_CONFIG::SetupControlChannel() /********************************************************************++ --********************************************************************/ bool WP_CONFIG::InsertURLIntoList( LPCWSTR pwszURL ) { LPCWSTR pwszOriginalURL = pwszURL; // // Minimum length: 11 (http://*:1/). Begins with http // if ( ( wcslen(pwszURL) < 11 ) || ( 0 != _wcsnicmp(pwszURL, L"http", 4)) ) { return false; } pwszURL += 4; // // https // if ((L's' == *pwszURL) || (L'S' == *pwszURL)) { pwszURL++; } // // :// // if ( (L':' != *pwszURL) || (L'/' != *(pwszURL+1)) || (L'/' != *(pwszURL+2)) ) { return false; } pwszURL += 3; // // Skip host name or Ip Address // while ( (0 != *pwszURL) && ( L':' != *pwszURL)) { pwszURL++; } // // Check port # exists // if (0 == *pwszURL) { return false; } // // Check port number is numeric // pwszURL++; while ( (0 != *pwszURL) && ( L'/' != *pwszURL) ) { if (( L'0' > *pwszURL) || ( L'9' < *pwszURL)) { return false; } pwszURL++; } // // Check / after port number exists // if (0 == *pwszURL) { return false; } // // URL is good. // IF_DEBUG( TRACE) { DBGPRINTF(( DBG_CONTEXT, "Inserting URL '%ws' into Config Group List\n", pwszOriginalURL )); } return ( TRUE == m_mszURLList.Append( pwszOriginalURL)); } // WP_CONFIG::InsertURLIntoList() /********************************************************************++ Routine Description: Determine the path of the duct-tape root directory. This is where the global config file and the System.IIS.dll must reside. We find this by inspecting the registry key: HKLM/Software/Microsoft/Catalog42/URT under the value "Dll". Arguments: Returns: S_OK if successful, failure code if not. --********************************************************************/ HRESULT WP_CONFIG::GetDuctTapeRoot(WCHAR path[MAX_PATH+1], size_t* pathLength) { long rc; HKEY hkeyCat42 = NULL; HRESULT hr = S_OK; __try { rc = RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Catalog42\\URT", 0, // reserved KEY_READ, &hkeyCat42); if (rc != ERROR_SUCCESS) { IF_DEBUG( TRACE) { DBGPRINTF(( DBG_CONTEXT, "Catalog42 is not installed\n" )); } hr = HRESULT_FROM_WIN32(rc); __leave; } DWORD valueType = 0; DWORD len = MAX_PATH; rc = RegQueryValueEx(hkeyCat42, L"Dll", NULL, // reserved &valueType, reinterpret_cast(path), &len); if (rc != ERROR_SUCCESS || (valueType != REG_SZ && valueType != REG_EXPAND_SZ)) { IF_DEBUG( TRACE) { DBGPRINTF(( DBG_CONTEXT, "Catalog42 is not installed\n" )); } hr = HRESULT_FROM_WIN32(rc); __leave; } *pathLength = len / sizeof path[0]; // Convert to characters // I now have a path something like "d:\winnt\xspdt\catalog42.dll". // The cooked-down catalog is in the same directory // ("d:\winnt\xspdt") in a file called "WASMB.CLB". WCHAR* lastWhack = wcsrchr(path, L'\\'); if (lastWhack == NULL) { // This should never happen, but if it does, just let the // string be empty and let the world fall to pieces if // it's not found in the current directory. *pathLength = 0; path[0] = L'\0'; } else { // Chop off the dll name, but leave the trailing slash. *pathLength -= *pathLength - (lastWhack - path) - 1; path[*pathLength] = L'\0'; } } __finally { if (hkeyCat42 != NULL) RegCloseKey(hkeyCat42); } return hr; } /********************************************************************++ Routine Description: Prepares the catalog, and also prepares the app domain factory. Arguments: Returns: S_OK if successful, failure code if not. --********************************************************************/ HRESULT WP_CONFIG::InitConfiguration() { static const WCHAR cookedCatalogName[] = L"WASMB.CLB"; HRESULT hr = S_OK; // Get the duct-tape root directory. WCHAR path[MAX_PATH + 1]; size_t pathLength = MAX_PATH; hr = GetDuctTapeRoot(path, &pathLength); if (FAILED(hr)) return hr; // Construct the catalog name by appending to the duct-tape root. wcscat(path + pathLength, cookedCatalogName); m_catFile = _wcsdup(path); if (m_catFile == NULL) return E_OUTOFMEMORY; // Construct the query cells used in subsequent queries. m_queryCell[0].pData = (void*)m_catFile; m_queryCell[0].eOperator = eST_OP_EQUAL; m_queryCell[0].iCell = iST_CELL_FILE; m_queryCell[0].dbType = DBTYPE_WSTR; m_queryCell[0].cbSize = (pathLength * sizeof m_catFile[0]) + sizeof cookedCatalogName; // Get the SimpleTable dispenser hr = GetSimpleTableDispenser(WSZ_PRODUCT_URT, 0, &m_istDispenser); if (m_istDispenser == NULL) { IF_DEBUG( TRACE) { DBGPRINTF(( DBG_CONTEXT, "Could not get simple table dispenser for %S, hr=0x%x\n", WSZ_PRODUCT_URT, hr )); } return hr; } // Initialize our xspisapi imports. m_xspisapiDLL = LoadXSP(); if (m_xspisapiDLL == NULL) return HRESULT_FROM_WIN32(GetLastError()); APP_DOMAIN_INIT_FACTORY* AppDomainInitFactory; AppDomainInitFactory = (APP_DOMAIN_INIT_FACTORY*)GetProcAddress(m_xspisapiDLL, "AppDomainInitFactory"); _AppDomainUninitFactory = (APP_DOMAIN_UNINIT_FACTORY*)GetProcAddress(m_xspisapiDLL, "AppDomainUninitFactory"); _AppDomainGetObject = (APP_DOMAIN_GET_OBJECT*)GetProcAddress(m_xspisapiDLL, "AppDomainGetObject"); // Initialize XSP's app domain factory. path[pathLength] = L'\0'; wcscat(path + pathLength, L"System.IIS.dll"); hr = AppDomainInitFactory(path, L"System.IIS.Hosting.ULManagedWorker"); if (FAILED(hr)) { IF_DEBUG( TRACE) { DBGPRINTF(( DBG_CONTEXT, "Could not initialize the app domain factory, hr=0x%x\n", hr )); } } return hr; } // WP_CONFIG::InitConfiguration() HRESULT WP_CONFIG::UnInitConfiguration() { HRESULT hr = _AppDomainUninitFactory(); FreeLibrary(m_xspisapiDLL); return hr; } xspmrt::_ULManagedWorker* WP_CONFIG::ChooseWorker(void* pvRequest) { UL_HTTP_REQUEST* request = (UL_HTTP_REQUEST*)pvRequest; // Get the UrlContext, which is actually the AppID. UL_URL_CONTEXT urlContext = request->UrlContext; xspmrt::_ULManagedWorker* worker = NULL; WCHAR* appPath = NULL; WCHAR* rootDirectory = NULL; DWORD siteID = 0; HRESULT hr = S_OK; FILETIME ftCreateTime; LARGE_INTEGER liCreateTime; WCHAR appID[sizeof "12345678"]; DwordToHexString((DWORD)urlContext, appID); __try { EnterCriticalSection(&m_workerTableGuard); // Look for an existing worker in an existing app domain. hr = _AppDomainGetObject(appID, NULL, (IUnknown**)&worker); if (FAILED(hr)) __leave; if (hr == S_FALSE) { // The worker is new, so we need to initialize it. if (m_mszURLList.First() != NULL) { // We're launching this from the command line. The // root directory is given in m_homeDirectory, and // we're going to hard-code the application as "/". if (m_homeDirectory == NULL) { // Kludge WCHAR defaultHome[] = L"%SystemDrive%\\InetPub\\DtRoot"; WCHAR buf[MAX_PATH + 1]; size_t len = ExpandEnvironmentStrings(defaultHome, buf, MAX_PATH); if (len == 0) { hr = HRESULT_FROM_WIN32(GetLastError()); __leave; } m_homeDirectory = _wcsdup(buf); if (m_homeDirectory == NULL) { hr = E_OUTOFMEMORY; __leave; } } rootDirectory = _wcsdup(m_homeDirectory); appPath = _wcsdup(L"/"); if (rootDirectory == NULL || appPath == NULL) { hr = E_OUTOFMEMORY; __leave; } } else { // Find the Application info we need: the corresponding site ID, // and the application path. hr = GetAppInfo(urlContext, &appPath, &siteID); if (FAILED(hr)) __leave; // Get the useful information about the corresponding site: it's // home directory. hr = GetSiteInfo(siteID, &rootDirectory); if (FAILED(hr)) __leave; } // BUGBUG! For now, form the physical app path by concatenating // the WCHAR appPhysPath[MAX_PATH+1]; wcscpy(appPhysPath, rootDirectory); wcscat(appPhysPath, appPath); // Find any "/" and turn them to "\". for (WCHAR* p = appPhysPath; *p != L'\0'; ++p) { if (*p == L'/') *p = L'\\'; } hr = _AppDomainGetObject(appID, appPhysPath, (IUnknown**)&worker); if (FAILED(hr)) __leave; // // Get the current filetime. This timestamp for appdomain // creation will be used in generating lame ETags for now // GetSystemTimeAsFileTime( &ftCreateTime ); liCreateTime.LowPart = ftCreateTime.dwLowDateTime; liCreateTime.HighPart = ftCreateTime.dwHighDateTime; hr = worker->Initialize(m_pwszAppPoolName, rootDirectory, appPath, liCreateTime.QuadPart); if (FAILED(hr)) { worker->Release(); worker = NULL; __leave; } } } __finally { LeaveCriticalSection(&m_workerTableGuard); free(appPath); free(rootDirectory); } return worker; } HRESULT WP_CONFIG::GetTable(const WCHAR* tableID, ISimpleTableRead2** pIst) { *pIst = NULL; unsigned long numCells = sizeof m_queryCell / sizeof m_queryCell[0]; HRESULT hr = m_istDispenser->GetTable(wszDATABASE_URTGLOBAL, tableID, &m_queryCell, &numCells, eST_QUERYFORMAT_CELLS, 0, // fServiceRequests reinterpret_cast(pIst)); if (FAILED(hr) || pIst == NULL) { IF_DEBUG( TRACE) { DBGPRINTF((DBG_CONTEXT, "Could not get %S table from dispenser\n", tableID)); } *pIst = NULL; } return hr; } HRESULT WP_CONFIG::GetAppInfo(UL_URL_CONTEXT urlContext, WCHAR** pAppPath, DWORD* pSiteID) { HRESULT hr = S_OK; StupidComPointer istApplications; *pAppPath = NULL; *pSiteID = 0; DWORD appID = urlContext; m_queryCell[1].eOperator = eST_OP_EQUAL; m_queryCell[1].iCell = iAPPS_AppID; m_queryCell[1].dbType = DBTYPE_I4; m_queryCell[1].cbSize = sizeof appID; m_queryCell[1].pData = &appID; hr = GetTable(wszTABLE_APPS, &istApplications); if (FAILED(hr)) return hr; static ULONG appColumns[] = { iAPPS_AppURL, iAPPS_SiteID }; static const size_t numAppColumns = sizeof appColumns / sizeof appColumns[0]; void* appValues[cAPPS_NumberOfColumns]; hr = istApplications->GetColumnValues(0, numAppColumns, appColumns, NULL, appValues); if (FAILED(hr)) { IF_DEBUG( TRACE) { DBGPRINTF((DBG_CONTEXT, "Could not get Application row (AppID=%UI64) from dispenser\n", urlContext)); } return hr; } *pSiteID = *(DWORD*)appValues[iAPPS_SiteID]; *pAppPath = _wcsdup((WCHAR*)appValues[iAPPS_AppURL]); if (*pAppPath == NULL) return E_OUTOFMEMORY; return S_OK; } HRESULT WP_CONFIG::GetSiteInfo(DWORD siteID, WCHAR** pHomeDirectory) { HRESULT hr = S_OK; StupidComPointer istSites; *pHomeDirectory = NULL; m_queryCell[1].eOperator = eST_OP_EQUAL; m_queryCell[1].iCell = iSITES_SiteID; m_queryCell[1].dbType = DBTYPE_I4; m_queryCell[1].cbSize = sizeof siteID; m_queryCell[1].pData = &siteID; hr = GetTable(wszTABLE_SITES, &istSites); if (FAILED(hr)) return hr; static ULONG siteColumns[] = { iSITES_HomeDirectory, iSITES_Bindings }; static const size_t numSiteColumns = sizeof siteColumns / sizeof siteColumns[0]; void* siteValues[cSITES_NumberOfColumns]; hr = istSites->GetColumnValues(0, numSiteColumns, siteColumns, NULL, siteValues); if (FAILED(hr)) { IF_DEBUG( TRACE) { DBGPRINTF((DBG_CONTEXT, "Error reading Sites row.\n")); } return hr; } // Expand any environment variables in the root directory. WCHAR rootDirectory[MAX_PATH+1]; size_t rootDirLen = ExpandEnvironmentStrings((WCHAR*)siteValues[iSITES_HomeDirectory], rootDirectory, MAX_PATH); if (rootDirLen == 0) { IF_DEBUG( TRACE) { DBGPRINTF((DBG_CONTEXT, "Error while expanding environment strings.\n")); } return HRESULT_FROM_WIN32(GetLastError()); } *pHomeDirectory = _wcsdup(rootDirectory); return S_OK; }