/*++ Copyright (c) 2001 Microsoft Corporation Module Name: w3wplist.cxx Abstract: implementation for w3wplist utility Author: Hamid Mahmood (t-hamidm) 06-08-2001 Revision History: --*/ #include #include "w3wplist.hxx" #include DECLARE_DEBUG_PRINTS_OBJECT(); DECLARE_DEBUG_VARIABLE(); w3wpList::w3wpList() /*++ Routine Description: Constructor. Arguments: none Return Value: none --*/ { m_pdwProcessId = NULL; m_pszTargetAppPoolId = NULL; m_dwCurrSizeOfProcessIdArray = 0; m_chVerbosity = 0; m_dwNumProcessId = 0; m_startingIndex = 0; m_dwTargetPID = 0; InitializeListHead(&m_listHead); // // initialize global data // CONSOLE_HANDLER_VAR::g_HAS_CONSOLE_EVENT_OCCURED = FALSE; CONSOLE_HANDLER_VAR::g_THREAD_EXIT_CODE = 1; CONSOLE_HANDLER_VAR::g_PROCESS_EXIT_CODE = 1; } w3wpList::~w3wpList() /*++ Routine Description: Destructor. Arguments: none Return Value: none --*/ { DBG_ASSERT (m_dwCurrSizeOfProcessIdArray == 0); DBG_ASSERT (m_chVerbosity == 0); DBG_ASSERT (m_dwNumProcessId == 0); DBG_ASSERT (m_startingIndex == 0); DBG_ASSERT (m_dwTargetPID == 0); DBG_ASSERT (m_pdwProcessId == NULL); DBG_ASSERT (m_pszTargetAppPoolId == NULL); DBG_ASSERT (IsListEmpty (&m_listHead) == TRUE ); } HRESULT w3wpList::Init( IN UCHAR chVerbosity, IN BOOL fIsListMode, IN WCHAR* pszInputAppPoolId, IN DWORD dwPID ) /*++ Routine Description: Initializes all the class members. Arguments: chVerbosity -- verbosity level Return Value: none --*/ { HRESULT hr = S_OK; SYSTEM_INFO systemInfo; m_dwCurrSizeOfProcessIdArray = MIN_SIZE; m_chVerbosity = chVerbosity; m_fIsListMode = fIsListMode; m_dwTargetPID = dwPID; m_startingIndex = 0; InitializeListHead(&m_listHead); InitializeCriticalSection(&m_CriticalSection); m_pdwProcessId = new DWORD[m_dwCurrSizeOfProcessIdArray]; if ( m_pdwProcessId == NULL) { hr = HRESULT_FROM_WIN32(GetLastError()); goto end; } if ( pszInputAppPoolId != NULL ) { m_pszTargetAppPoolId = new WCHAR[wcslen(pszInputAppPoolId) + 1]; if ( m_pszTargetAppPoolId == NULL ) { hr = HRESULT_FROM_WIN32(GetLastError()); goto cleanup; } wcscpy ( m_pszTargetAppPoolId, pszInputAppPoolId ); } // figure out how to read the num of processors GetSystemInfo( &systemInfo ); m_dwNumThreads = systemInfo.dwNumberOfProcessors; goto end; cleanup: delete [] m_pdwProcessId; end: return hr; } VOID w3wpList::DestroyObject() /*++ Routine Description: deallocates memory. Arguments: none Return Value: none --*/ { W3WPLIST_NODE* pW3wpListNode = NULL; PLIST_ENTRY pListEntry = NULL; m_fIsListMode = FALSE; m_dwCurrSizeOfProcessIdArray = 0; m_chVerbosity = 0; m_dwNumThreads = 0; m_dwNumProcessId = 0; m_startingIndex = 0; m_dwTargetPID = 0; delete [] m_pdwProcessId; m_pdwProcessId = NULL; delete [] m_pszTargetAppPoolId; m_pszTargetAppPoolId = NULL; DeleteCriticalSection(&m_CriticalSection); // // deleting the link list // while ( ! IsListEmpty (&m_listHead) ) { pListEntry = RemoveHeadList(&m_listHead); pW3wpListNode = CONTAINING_RECORD ( pListEntry, W3WPLIST_NODE, m_listEntry ); // // terminate the ProcessDetails obj // before deleting // DBG_ASSERT(pW3wpListNode->CheckSignature()); pW3wpListNode->m_wpDetails.Terminate(); delete pW3wpListNode; } pW3wpListNode = NULL; } HRESULT w3wpList::GetProcesses() /*++ Routine Description: Gets all the PIDs and starts up threads to go through them. Arguments: None Return Value: HRESULT hr -- S_OK if successful, else failed with error code --*/ { BOOL fIsFull = FALSE; BOOL fCreateThreadFailed = FALSE; DWORD dwBytesReceived; DWORD dwBufferSize; HRESULT hr; DWORD dwIndex; DWORD dwCount; HANDLE* pHandles = NULL; // // Enumerate all the processes. Memory is // re-allocated if the original size // was not enough. // do { dwBufferSize = sizeof (DWORD) * m_dwCurrSizeOfProcessIdArray; if ( EnumProcesses ( m_pdwProcessId, dwBufferSize, &dwBytesReceived ) == FALSE ) { hr = HRESULT_FROM_WIN32(GetLastError()); goto end; } m_dwNumProcessId = dwBytesReceived/sizeof(DWORD); // // Check for overflow, if yes increase size of // m_pdwProcessId array // if ( m_dwNumProcessId == m_dwCurrSizeOfProcessIdArray ) { DWORD * pdwTemp = m_pdwProcessId; m_dwCurrSizeOfProcessIdArray *= 2; m_pdwProcessId = new DWORD[m_dwCurrSizeOfProcessIdArray]; delete [] pdwTemp; pdwTemp = NULL; if ( m_pdwProcessId == NULL) { hr = HRESULT_FROM_WIN32(GetLastError()); goto end; } fIsFull = TRUE; } else { fIsFull = FALSE; } } while (fIsFull == TRUE); // // enable debug privilege for the current process // hr = EnableDebugPrivilege(); if ( hr != S_OK ) { goto end; } // // Create array for thread handles // pHandles = new HANDLE[m_dwNumThreads]; if ( pHandles == NULL ) { hr = HRESULT_FROM_WIN32(GetLastError()); goto end; } // // spin off threads in suspended mode // for ( dwIndex = 0; dwIndex < m_dwNumThreads; dwIndex++ ) { pHandles[dwIndex] = CreateThread( NULL, /* no security descriptor */ 0, /* default stack size */ SpinNewThread, this, /* thread parameters */ CREATE_SUSPENDED , /* suspended mode*/ NULL /* not getting thread id */ ); if ( pHandles[dwIndex] == NULL ) // thread creation failed { hr = HRESULT_FROM_WIN32(GetLastError()); fCreateThreadFailed = TRUE; break; } } // // we need dwCount b/c CreateThread may fail, // then we just need to loop through dwCount // threads // dwCount = dwIndex; if ( fCreateThreadFailed == TRUE ) { for ( dwIndex = 0; dwIndex < dwCount; dwIndex++) { TerminateThread( pHandles[dwIndex], GetLastError() ); CloseHandle( pHandles[dwIndex]); } goto end; } // // Set our own event handler for CTRL_C_EVENT, // CTRL_BREAK_EVENT, CTRL_LOGOFF_EVENT, // CTRL_SHUTDOWN_EVENT if verbosity > 0 // else we don't debug // if ( m_chVerbosity > 0 ) { SetConsoleCtrlHandler( ConsoleCtrlHandler, TRUE ); } // // resume thread // for ( dwIndex = 0; dwIndex < dwCount; dwIndex++) { ResumeThread( pHandles[dwIndex]); } // // wait for all threads to exit // WaitForMultipleObjects( dwCount, // number of handles in array pHandles, // object-handle array TRUE, // wait option INFINITE // time-out interval ); // // check for Ctrl+C, Ctrl+Break, etc // if ( CONSOLE_HANDLER_VAR::g_HAS_CONSOLE_EVENT_OCCURED == TRUE ) { ExitProcess(CONSOLE_HANDLER_VAR::g_PROCESS_EXIT_CODE); } // // Turn back the default handler for CTRL_C_EVENT, // CTRL_BREAK_EVENT, CTRL_LOGOFF_EVENT, // CTRL_SHUTDOWN_EVENT // if ( m_chVerbosity > 0 ) { SetConsoleCtrlHandler( ConsoleCtrlHandler, FALSE ); } for ( dwIndex = 0; dwIndex < dwCount; dwIndex++) { CloseHandle(pHandles[dwIndex]); } end: delete [] pHandles; return hr; } DWORD WINAPI w3wpList::SpinNewThread( LPVOID lpParam ) /*++ Routine Description: static Thread function Arguments: None Return Value: DWORD --*/ { w3wpList* pw3wpList = (w3wpList*) lpParam; pw3wpList->EnumAllWPThread(); return TRUE; } VOID w3wpList::EnumAllWPThread() /*++ Routine Description: Each thread goes through the PID array and processess specific process. So, thread n will process the first available PID and then PIDs at an increment of NumOfThreads positions in the array Arguments: None Return Value: None --*/ { // // get the first available index to start with // DWORD dwStartIndex = InterlockedIncrement(&m_startingIndex); // // since m_startingIndex is initialized to zero and we want to start // from index 0 // dwStartIndex--; // // walk throught the array of PIDs and // call DoWork on each of them. DoWork // creates ProcessDetails obj for // each PID and gets the requests // if it were a worker process // for ( DWORD i = dwStartIndex; i < m_dwNumProcessId; i += m_dwNumThreads) { // // continue until we find the PID // of our interest. Only interesting // if lInputPID != -1, i.e were looking // at the app pool id // if ( (m_dwTargetPID != -1 ) && (m_dwTargetPID != m_pdwProcessId[i]) ) { continue; } DoWork( m_pdwProcessId[i] ); if (m_dwTargetPID == m_pdwProcessId[i]) { break; } }// end for } VOID w3wpList::DoWork( IN DWORD dwPID ) /*++ Routine Description: Ccreates ProcessDetails obj for each PID and gets the requests if it were a worker process. It then adds that obj to the list Arguments: dwPID -- PID of the process Return Value: None --*/ { HRESULT hr; W3WPLIST_NODE* pW3wpListNode = NULL; // // create new Node for the list // pW3wpListNode = new W3WPLIST_NODE(); if ( pW3wpListNode == NULL ) { goto end; } // // Initialize the ProcessDetails obj // in the node // pW3wpListNode->m_wpDetails.Init( dwPID, m_chVerbosity, m_fIsListMode ); // // return value is S_OK if this was a worker process, // else it is S_FALSE // hr = pW3wpListNode->m_wpDetails.GetProcessDetails(m_pszTargetAppPoolId); if ( hr == S_FALSE ) { goto cleanup; } // // It was a worker process, add the node to // the list // EnterCriticalSection( &m_CriticalSection ); InsertTailList( &m_listHead, &(pW3wpListNode->m_listEntry) ); LeaveCriticalSection( &m_CriticalSection ); goto end; cleanup: pW3wpListNode->m_wpDetails.Terminate(); delete pW3wpListNode; end: return; } HRESULT w3wpList::EnableDebugPrivilege() /*++ Routine Description: Changes the privilege of the current process so that it can debug other processes. Arguments: None Return Value: HRESULT hr -- S_FALSE, if exe name does not match S_OK if name matched and other funcitons called from within also passed Error code: if anything else failed --*/ { HRESULT Status = S_OK; HANDLE Token; PTOKEN_PRIVILEGES NewPrivileges; BYTE OldPriv[1024]; ULONG cbNeeded; LUID LuidPrivilege; // // Make sure we have access to adjust and to get the // old token privileges // if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &Token) ) { Status = GetLastError(); goto EH_Exit; } cbNeeded = 0; // // Initialize the privilege adjustment structure // LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &LuidPrivilege); NewPrivileges = (PTOKEN_PRIVILEGES) calloc(1, sizeof(TOKEN_PRIVILEGES) + (1 - ANYSIZE_ARRAY) * sizeof(LUID_AND_ATTRIBUTES)); if (NewPrivileges == NULL) { Status = E_OUTOFMEMORY; goto EH_Token; } // // set new privilege // NewPrivileges->PrivilegeCount = 1; NewPrivileges->Privileges[0].Luid = LuidPrivilege; NewPrivileges->Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; // // Enable the privilege // if (!AdjustTokenPrivileges( Token, FALSE, NewPrivileges, sizeof(OldPriv), (PTOKEN_PRIVILEGES)OldPriv, &cbNeeded ) ) { // // If the stack was too small to hold the privileges // then allocate off the heap // if ( GetLastError() == ERROR_INSUFFICIENT_BUFFER ) { PBYTE pbOldPriv; BOOL Adjusted; pbOldPriv = (PUCHAR)calloc(1, cbNeeded); if ( pbOldPriv == NULL ) { Status = E_OUTOFMEMORY; goto EH_NewPriv; } Adjusted = AdjustTokenPrivileges( Token, FALSE, NewPrivileges, cbNeeded, (PTOKEN_PRIVILEGES)pbOldPriv, &cbNeeded ); free(pbOldPriv); if ( !Adjusted ) { Status = GetLastError(); } } } EH_NewPriv: free(NewPrivileges); EH_Token: CloseHandle(Token); EH_Exit: return Status; } VOID w3wpList::OutputRequests() /*++ Routine Description: Outputs the information for each of the ProcessDetails obj Arguments: None Return Value: None --*/ { PLIST_ENTRY pListEntry; W3WPLIST_NODE* pListNode; for ( pListEntry = m_listHead.Flink; pListEntry != &m_listHead; pListEntry = pListEntry->Flink ) { pListNode = CONTAINING_RECORD( pListEntry, W3WPLIST_NODE, m_listEntry ); DBG_ASSERT (pListNode->CheckSignature()); pListNode->m_wpDetails.DumpRequests(); } } BOOL w3wpList::ListEmpty() /*++ Routine Description: Returns TRUE if the list of ProcessDetails obj is empty Arguments: None Return Value: BOOL --*/ { return IsListEmpty(&m_listHead); } BOOL WINAPI w3wpList::ConsoleCtrlHandler( DWORD dwCtrlType ) { BOOL fReturnValue = TRUE; switch (dwCtrlType) { // Handle the CTRL+C signal. case CTRL_C_EVENT: case CTRL_BREAK_EVENT: case CTRL_LOGOFF_EVENT: case CTRL_SHUTDOWN_EVENT: { CONSOLE_HANDLER_VAR::g_HAS_CONSOLE_EVENT_OCCURED = TRUE; break; } default: { CONSOLE_HANDLER_VAR::g_HAS_CONSOLE_EVENT_OCCURED = FALSE; fReturnValue = FALSE; } } return fReturnValue; } int __cdecl wmain(DWORD argc, WCHAR ** argv) { #define COL_WIDTH 30 WCHAR pszIndent[] = { L" " }; w3wpList w3wplstMyList; BOOL fIsListMode = FALSE; WCHAR* pszInputAppPoolId = NULL; UCHAR chInputVerbosity = 0; LONG lInputPID = -1; HRESULT hr; if ( argc <= 1 ) // no input args; list mode { fIsListMode = TRUE; } else if ( argc == 2 ) // better be /?, else it's error { if ( _wcsicmp (argv[1], L"/?") == 0 ) // help { wprintf (L"Copyright (c) Microsoft Corporation. All rights reserved.\n\n\n"); wprintf (L"%s\n%s\n%s\n\n", L"Description: Use this tool to obtain worker process information, determine which", L"worker process is assigned to a given application pool and show the requests", L"that are queued in the worker process."); wprintf (L"Syntax: w3wplist [{/p | /a} []]\n\n"); wprintf (L"Parameters:\n"); wprintf ( L"%-*s%s\n\n", COL_WIDTH, L"Value", L"Description"); wprintf ( L"%-*s%s\n%-*s%s\n", COL_WIDTH, L"/p", L"Indicates that the is a worker", COL_WIDTH, pszIndent, L"process PID number." ); wprintf ( L"%-*s%s\n%-*s%s\n\n", COL_WIDTH, L"/a", L"Indicates that the is an application", COL_WIDTH, pszIndent, L"pool identifier." ); wprintf ( L"%-*s\n", COL_WIDTH, L"" ); wprintf ( L"%-*s%s\n%-*s%s\n%-*s%s\n", COL_WIDTH, L"application pool identifier", L"Displays information associated with all active ", COL_WIDTH, pszIndent, L"worker processes for the application pool", COL_WIDTH, pszIndent, L"specified." ); wprintf ( L"%-*s%s\n%-*s%s\n\n", COL_WIDTH, L"worker process PID", L"Displays information associated with the worker", COL_WIDTH, pszIndent, L"process specified." ); wprintf ( L"%-*s%s\n", COL_WIDTH, L"", L"Verbosity levels are cumulative" ); wprintf ( L"%-*s%s\n%-*s%s\n%-*s%s\n%-*s%s\n", COL_WIDTH, L"/v or /v0", L"Displays the worker process PID, the application", COL_WIDTH, pszIndent, L"pool identifier, the application pool friendly-", COL_WIDTH, pszIndent, L"name, and the total number of requests that have", COL_WIDTH, pszIndent, L"been processed." ); wprintf ( L"%-*s%s\n%-*s%s\n", COL_WIDTH, L"/v1", L"Adds the Universal Resource Identifier, host name,", COL_WIDTH, pszIndent, L"and the HTTP verb." ); wprintf ( L"%-*s%s\n", COL_WIDTH, L"/v2", L"Adds the URI query and the protocol version." ); wprintf ( L"%-*s%s\n%-*s%s\n", COL_WIDTH, L"/v3", L"Adds the time, date, client-ip, and the referer,", COL_WIDTH, pszIndent, L"user-agent, and cookie headers if available." ); wprintf ( L"%-*s%s\n%-*s%s\n\n", COL_WIDTH, L"/v4", L"Adds the remaining available headers for the ", COL_WIDTH, pszIndent, L"current request." ); wprintf ( L"%s\n%s\n%s\n%s\n%s\n%s\n", L"Examples:", L" C:\\>w3wplist", L" C:\\>w3wplist /a app_pool_014", L" C:\\>w3wplist /p 4852", L" C:\\>w3wplist /a app_pool_014 /v", L" C:\\>w3wplist /p 4852 /v1" ); // MORE STUFF GOES HERE goto end; } else if ( _wcsicmp (argv[1], L"/p") == 0 ) { wprintf (L"%s\n%s\n", L"No worker process PID (W3wp.exe) was specified. Use Windows Task Manager to", L"obtain a valid W3wp.exe PID number."); goto end; } else if ( _wcsicmp (argv[1], L"/a") == 0 ) { wprintf (L"%s\n%s\n", L"No application pool identifier was specified. Search in Metabase.xml to obtain", L"a valid application pool identifier."); goto end; } else // invalid syntax { wprintf (L"Invalid syntax. Type w3wplist /? for help.\n"); goto end; } } else if (argc == 4) { if ( (_wcsicmp(argv[3], L"/v") == 0) || (_wcsicmp(argv[3], L"/v0") == 0 ) ) { chInputVerbosity = 0; } else if (_wcsicmp(argv[3], L"/v1") == 0) { chInputVerbosity = 1; } else if (_wcsicmp(argv[3], L"/v2") == 0) { chInputVerbosity = 2; } else if (_wcsicmp(argv[3], L"/v3") == 0) { chInputVerbosity = 3; } else if (_wcsicmp(argv[3], L"/v4") == 0) { chInputVerbosity = 4; } else if ( argc != 3 ) { wprintf (L"Invalid syntax. Type w3wplist /? for help.\n"); goto end; } } if ( (argc == 3 ) || (argc == 4) ) { // default verbosity of 0 if ( _wcsicmp (argv[1], L"/p") == 0 ) { lInputPID = wcstol(argv[2], L'\0', 10); } else if ( _wcsicmp (argv[1], L"/a") == 0 ) { pszInputAppPoolId = argv[2]; } } // // initialize the object // hr = w3wplstMyList.Init( chInputVerbosity, fIsListMode, pszInputAppPoolId, lInputPID ); if ( FAILED (hr) ) { goto error; } hr = w3wplstMyList.GetProcesses(); if ( FAILED (hr) ) { goto error; } // // if list is empty, it means that either the input // PID number was not a worker process or was invalid, // or the input app pool id was invalid // if ( w3wplstMyList.ListEmpty() ) { // some message of not found if ( lInputPID != -1 ) // /p switch was input { wprintf( L"%s\n%s\n%s\n", L"The worker process PID number (W3wp.exe) is not valid or is not associated with", L"a W3wp.exe process. Use Windows Task Manager to obtain a valid W3wp.exe PID", L"number." ); } else if ( pszInputAppPoolId != NULL ) // /a switch was input { wprintf( L"%s\n%s\n", L"The application pool identifier is not valid or does not exist. Search in", L"Metabase.xml to obtain a valid application pool identifier." ); } } // print out requests w3wplstMyList.OutputRequests(); // deallocate memory w3wplstMyList.DestroyObject(); goto end; error: // message of error wprintf ( L"Error occured. Error code : %d\n", HRESULT_CODE(hr) ); end: return 0; }