// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // // D A V C D A T A . C P P // // HTTP 1.1/DAV 1.0 request handling via ISAPI // // DAVCDATA is the dav process executable for storing handles that should // not be recycled when worker process recycle. It also contains the timing // code for timing out locks, and it establishes the shared memory for // the DAV worker processes. // // This process must run under the same identity as the worker processes. // // Copyright 2000 Microsoft Corporation, All Rights Reserved // ///////////////////////////////////////////////////////////////////////////// #include "_davcdata.h" #include #include // Code borrowed from htpext mem.cpp so we have use of the global heap. #define g_szMemDll L"staxmem.dll" #include #include #include #include // Mapping the exdav non-throwing allocators to something local // LPVOID __fastcall ExAlloc( UINT cb ) { return g_heap.Alloc( cb ); } LPVOID __fastcall ExRealloc( LPVOID pv, UINT cb ) { return g_heap.Realloc( pv, cb ); } VOID __fastcall ExFree( LPVOID pv ) { g_heap.Free( pv ); } VOID IncrementGlobalPerfCounter( UINT iCounter ) { // exceptions in DAVCData are not counted in perf counters (for now) // BUGBUG: Decide if they should be. } #ifdef DBG BOOL g_fDavTrace = FALSE; const CHAR gc_szDbgIni[] = "DAVCData.INI"; #endif // Signature must match that of HTTPEXT's so that the shared memory heap is // shared between the two processes. // EXTERN_C const WCHAR gc_wszSignature[] = L"HTTPEXT"; // Timer constants and globals. // const DWORD WAIT_PERIOD = 60000; // 1 min = 60 sec = 60,000 milliseconds // Event used to notify the existence of davcdata process // HANDLE g_hEventDavCDataUp = NULL; // Array of handles we could wait on // class CDavCDataHandles { enum { MAX_TIMER_HANDLE = 8, MAX_WAIT_HANDLE = 128 }; enum { ih_new_wp, ih_delete_timer, c_events, ih_wp = c_events }; private: HANDLE m_rgHandles[MAX_WAIT_HANDLE]; // Array of handles ULONG m_cHandlesWaiting; ULONG m_uiAddStart; // Start index of handles added ULONG m_uiAdd; // next index to add a handle // Array of timers to be deleted. // Using a fixed array, I don't believe we have more // HANDLE m_rgTimers[MAX_TIMER_HANDLE]; ULONG m_cTimersToDelete; LONG m_lInUse; public: CDavCDataHandles() : m_cHandlesWaiting(0), m_cTimersToDelete(0), m_uiAddStart(0), m_uiAdd(0), m_lInUse(0) {} VOID Lock() { // Simple spinlock // while (1 == InterlockedCompareExchange (&m_lInUse, 1, 0)) { Sleep(1); // Sleep 1 millisecond } } VOID Unlock() { Assert (1 == m_lInUse); InterlockedDecrement(&m_lInUse); } ULONG CHandlesWaiting() { return m_cHandlesWaiting; } HANDLE * PHandlesWaiting() { return m_rgHandles; } SCODE ScInit(); BOOL FAddNewWP(DWORD dwClientProcess); VOID Refresh(); BOOL FDeleteWPHandle (ULONG ulIndex); VOID AddTimerToDelete (HANDLE hTimer); VOID DeleteTimer(); static DWORD __stdcall DwWaitForWPShutdown (PVOID pvThreadData); }; CDavCDataHandles g_handles; // =============================================================== // Supporting class definitions // =============================================================== #include "shlkcache.h" // CWasLockCache holds the global information needed to handle // any requests that the pipeline receives (or that bypass the pipeline) // for setting up and maintaining the Shared Memory Lock Cache. // class CWasLockCache { private: // Private functions for processing // LONG m_lTimerLaunched; HANDLE m_hNewTimer; // Shared Memory objects // SharedPtr m_spCache; SharedPtr m_spStatic; // NOT IMPLEMENTED // CWasLockCache& operator=( const CWasLockCache& ); CWasLockCache( const CWasLockCache& ); public: CWasLockCache() {}; // Initialize sets up all variables that would normally be set in the constructor. // It is done this way to avoid linking issues surronding when the global object // g_wlc's constructor would be called. // VOID Initialize() { m_lTimerLaunched = 0; m_hNewTimer = INVALID_HANDLE_VALUE; }; HRESULT SetupSharedCache(); HANDLE SaveHandle(DWORD OrigProcess, HANDLE SavingHandle); VOID LaunchLockTimer(); VOID DeleteLockTimer(); VOID ExpireLocks(); BOOL FEmpty() { return m_spCache.FIsNull() || m_spCache->FEmpty(); } }; CWasLockCache g_wlc; // Implement a waiting thread listens to WP shutdown event // DWORD __stdcall CDavCDataHandles::DwWaitForWPShutdown(PVOID pvThreadData) { DWORD dwRet; while (1) { dwRet = WaitForMultipleObjects (g_handles.CHandlesWaiting(), // nCount g_handles.PHandlesWaiting(), // lpHandles, FALSE, // fWaitAll, INFINITE); // wait forever switch (dwRet) { case WAIT_OBJECT_0 + ih_new_wp: g_handles.Refresh(); break; case WAIT_OBJECT_0 + ih_delete_timer: g_handles.DeleteTimer(); break; default: if (FALSE == g_handles.FDeleteWPHandle(dwRet - WAIT_OBJECT_0)) { // This means WaitForMultipleObject fails for some unknown reason // DebugTrace ("WaitForMultipleObject returns %d, last error = %d\n", dwRet, GetLastError()); } break; } }; return 0; } // CDavCDataHandles functions // CDavCDataHandles::ScInit // SCODE CDavCDataHandles::ScInit() { SCODE sc = S_OK; HANDLE hWaitingThread; // Create the event that used to notify the arrival of new event // m_rgHandles[ih_new_wp] = CreateEvent (NULL, // lpEventAttributes FALSE, // bManualReset FALSE, // bInitialState NULL); if (m_rgHandles[ih_new_wp] == NULL) { DebugTrace ("CreateEvent failed %d\n", GetLastError()); sc = HRESULT_FROM_WIN32(GetLastError()); goto ret; } // Create the event that listens for timer deletion // m_rgHandles[ih_delete_timer] = CreateEvent (NULL, // lpEventAttributes FALSE, // bManualReset FALSE, // bInitialState NULL); // lpName if (m_rgHandles[ih_delete_timer] == NULL) { DebugTrace ("CreateEvent failed %d\n", GetLastError()); sc = HRESULT_FROM_WIN32(GetLastError()); goto ret; } m_cHandlesWaiting = c_events; // Now create thread that waits on these events and wp handles // hWaitingThread = CreateThread (NULL, // lpThreadAttributes 0, // dwStackSize, ignored CDavCDataHandles::DwWaitForWPShutdown, // lpStartAddress NULL, // lpParam 0, // Start immediately NULL); // lpThreadId if (NULL == hWaitingThread) { DebugTrace ("HandleNewWorkerProcess - Failed to create thread\n"); sc = HRESULT_FROM_WIN32(GetLastError()); goto ret; } // We don't need to thread handle. but we need to close the handle to avoid // having the thread object remains in the system forever. // CloseHandle(hWaitingThread); m_uiAddStart = c_events; m_uiAdd = c_events; ret: return sc; } // CDavCDataHandles::FAddNewWP // // The only thread call this function is ScNamedPipeListener, // BOOL CDavCDataHandles::FAddNewWP (DWORD dwClientProcess) { // Open the worker process handle so that we can synchronize on // HANDLE hWP = OpenProcess(SYNCHRONIZE, false, dwClientProcess); if (NULL == hWP) { DebugTrace ("Failed to open worker process, last error %d\n", GetLastError()); return FALSE; } Lock(); m_rgHandles[m_uiAdd++] = hWP; Unlock(); // Now inform the waiting thread we have one more WP to wait for // SetEvent (m_rgHandles[ih_new_wp]); return TRUE; } // CDavCDataHandles::Refresh // // This method is called from waiting thread // VOID CDavCDataHandles::Refresh () { Lock(); for (ULONG i=m_uiAddStart; i= m_cHandlesWaiting)) { // This must be an error in WaitForMultpleObject. // Don't think we can do anything here // return FALSE; } // Close the process handle // CloseHandle (m_rgHandles[ulIndex]); // No need to lock this operation, because we are the only // thread that could touch m_chandlesWaiting // Move the rest of handles forward // for (ULONG i = ulIndex; i < m_cHandlesWaiting-1; i++) m_rgHandles[i] = m_rgHandles[i+1]; m_cHandlesWaiting--; if ((m_cHandlesWaiting == c_events) && g_wlc.FEmpty()) { //$REVIEW: Is this the right way to stop this process? // //$REVIEW: Another problem is that there may be outstanding // worker processes whose first DO_NEW_WP request is on // its way but we haven't finished processing it. // To cover this problem, we try ScStartDavCData twice // from the worker process side. // ExitProcess (0); } return TRUE; } // CDavCDataHandles::AddTimerToDelete // // This method is called from the timer callback // VOID CDavCDataHandles::AddTimerToDelete(HANDLE h) { Lock(); m_rgTimers[m_cTimersToDelete++] = h; Unlock(); // Notify waiting thread that a timer needs to be deleted // SetEvent (m_rgHandles[ih_delete_timer]); } // CDavCDataHandles::DeleteTimer // // This method is called from the waiting thread // VOID CDavCDataHandles::DeleteTimer() { HANDLE rgTimers[MAX_TIMER_HANDLE]; LONG cTimers; LONG i; // Copy the timer handles locally, so that we can do // the DeleteTimerQueueTimer outside the lock // Otherwise, we may form a deadlock with timer callback // Lock(); cTimers = m_cTimersToDelete; for (i=0; iExpireLocks(); if (m_spCache->FEmpty()) { HANDLE hTimerToDelete = m_hNewTimer; // Allow new timer to be created // InterlockedExchange (&m_lTimerLaunched, 0); if (!m_spCache->FEmpty()) { // Some new locks just added into the cache, and we // don't know for sure if a new timer was started. // Try to launch one anyway // LaunchLockTimer(); } // We must delete the old timer, however, we can't do this // in the time callback // g_handles.AddTimerToDelete (hTimerToDelete); } } } // // Function launches the time if the timer has not been launched yet. // VOID CWasLockCache::LaunchLockTimer() { if (InterlockedCompareExchange(&m_lTimerLaunched, 1, 0) == 0) { if (!CreateTimerQueueTimer(&m_hNewTimer, // timer that we created NULL, // use default timer queue &CheckLocks, // function that will check the locks in the cache // and release any expired locks. NULL, // parameter to the callback function WAIT_PERIOD, // how long to wait before calling the callback function // the first time. WAIT_PERIOD, // how long to wait between calls to the callback function WT_EXECUTEINIOTHREAD)) // where to execute the function call.. { DebugTrace("Failed to CreateTimerQueueTimer last error = %d\r\n", GetLastError()); // Set back the flags so we know that the timer isn't running. // InterlockedExchange(&m_lTimerLaunched, 0); } } } // // Routine initilizes the shared cache and causes the m_spCache // and m_spStatic variables to be linked to their shared memory // objects. // HRESULT CWasLockCache::SetupSharedCache() { return InitalizeSharedCache(m_spCache, m_spStatic, TRUE); } // // Routine will duplicate the handle that is passed in, using the // original process that owns the passed in handle. It will then // return the handle to the caller. Any failure and INVALID_HANDLE_VALUE // will be returned. // // CODEWORK: Could return errors via LastError if neccessary. // HANDLE CWasLockCache::SaveHandle(DWORD OrigProcess, HANDLE SavingHandle) { HANDLE h = INVALID_HANDLE_VALUE; HANDLE hOrigProcess = OpenProcess(PROCESS_DUP_HANDLE, false, OrigProcess); if (hOrigProcess==NULL) { DebugTrace ("Getting Orig Process Failed \r\n"); return h; } if (!DuplicateHandle(hOrigProcess, SavingHandle, GetCurrentProcess(), &h, 0, FALSE, DUPLICATE_SAME_ACCESS)) { DebugTrace("Failed to Dup Handle \r\n"); } DebugTrace("SaveHandle - Handle %x from process %d is duplicated to %x\n", SavingHandle, OrigProcess, h); CloseHandle (hOrigProcess); return h; } // // Function is used to initalize the shared memory. Once it has // created the global heap for the process and has initalized the // shared memory heap, it will then use the InitalizeSharedCache // routine to setup the shared memory as expected for a lock cache. // HRESULT ScInitialize() { HRESULT hr = S_OK; g_wlc.Initialize(); // Setup the global heap for the process. // if (!g_heap.FInit()) { DebugTrace("Initalizing Heap Failed \r\n"); hr = E_OUTOFMEMORY; goto ret; } // Initialize waiting handles // hr = g_handles.ScInit(); if (FAILED(hr)) goto ret; // Now we can setup the shared memory appropriately. // hr = g_wlc.SetupSharedCache(); if (FAILED(hr)) goto ret; // Create the event thread that can be used to inform work processes // g_hEventDavCDataUp = CreateEvent (NULL, // lpEventAttributes TRUE, // bManualReset FALSE, // bInitialState g_szEventDavCData); if (NULL == g_hEventDavCDataUp) { DebugTrace ("Davcdata - Failed to create event\n"); hr = HRESULT_FROM_WIN32(GetLastError()); goto ret; } ret: return hr; } // // Routine will duplicate the handle that is passed in, using the // original process that owns the passed in handle. It will then // save the handle in the appropriate shared memory location. // VOID LockFile(DWORD OrigProcess, HANDLE SavingHandle, LPVOID pshLockData) { if (pshLockData) { HANDLE h = g_wlc.SaveHandle(OrigProcess, SavingHandle); if (h != INVALID_HANDLE_VALUE) { // CODEWORK: Could also set in the davprocess id here, but then we would have issues // with each lock data needing to open it's own davcdata process. Might be nice // to save it anywhere here, and then use it as a way to tell if DAVCData recycled // and a wp kept shared memory open. // Get a shared pointer to the object. // SharedPtr spLockData; // If we succeed in binding to the object then we can go ahead and set the value in. // if (spLockData.FBind(*(SharedHandle*)pshLockData)) { spLockData->SetDAVProcFileHandle(h); g_wlc.LaunchLockTimer(); } else { // If we think that we are saving a file for a lock, but the lock data // is gone, then we best just release our hold on the file. // CloseHandle(h); } } } } // =============================================================== // Named pipe routines. // =============================================================== // // Function handles listening for named pipe communications from the // worker process and sends the requests to there supporting functions. // SCODE ScNamedPipeListener() { SCODE sc = S_OK; DWORD dwAction = 0; DWORD dwClientProcess = 0; HANDLE hClientHandle = 0; DWORD dwErr = 0; DWORD outBufSize =0; LPVOID pVoid = NULL; // Buffer for retrieving data from the caller. // BYTE outBuf[PIPE_MESSAGE_SIZE]; // Create the named pipe communication. // HANDLE hNamedPipe = CreateNamedPipe("\\\\.\\pipe\\SaveHandle" // pipe name , PIPE_ACCESS_DUPLEX // pipe open mode , PIPE_TYPE_MESSAGE | PIPE_WAIT // pipe-specific modes , 1 // maximum number of instances , 0 // output buffer size (bytes) , 32 // input buffer size (bytes) , 3000 // time-out interval (milliseconds = 3 seconds) , NULL); // Security Descriptor if (hNamedPipe == INVALID_HANDLE_VALUE) { sc = HRESULT_FROM_WIN32(GetLastError()); goto cleanup; } // No inform the worker processes that DAVCData is available // Assert (g_hEventDavCDataUp); if (!SetEvent (g_hEventDavCDataUp)) { sc = HRESULT_FROM_WIN32(GetLastError()); goto cleanup; } do { // Wait for the client to signal that data is on the line. // if (!ConnectNamedPipe(hNamedPipe, NULL)) { // The function may still return zero even if client connects in the interval // between the call to CreateNamedPipe() and the call to ConnectNamedPipe(). // In that case the last error will be ERROR_PIPE_CONNECTED that will indicate that // there is already good connection between the client and server processes // if (ERROR_PIPE_CONNECTED != GetLastError()) { DebugTrace("ScNamedPipeListener() - ConnectNamedPipe() failed with error 0x%08lX, retrying.\r\n", GetLastError()); // Loop and try again to wait. // continue; } } // Get the data from the line. // if (!ReadFile(hNamedPipe, (LPVOID) outBuf, PIPE_MESSAGE_SIZE, &outBufSize, NULL)) { // CODEWORK: Do we still need this special error case, now that we are not reading the // data in two passes? // On the first call into read (per instance of the holder object) ERROR_MORE_DATA // is returned from ReadFile, which is expected. However, on subsequent client // sessions it is not returned (which is strange since there is more data in // to be read below. In either case if we continue on, everything works correctly. // dwErr = GetLastError(); if (dwErr != ERROR_MORE_DATA) { sc = HRESULT_FROM_WIN32(dwErr); goto disconnect; } } // Verify we got the size of the data we expected. // if (outBufSize != PIPE_MESSAGE_SIZE) { DebugTrace("Output buffer size did not equal the # of dwords expected \r\n"); goto disconnect; } // Break the data out into the appropriate variables. // dwAction = *((DWORD*)outBuf); dwClientProcess = *((DWORD*) (outBuf+sizeof(DWORD))); hClientHandle = *((HANDLE*) (outBuf+2*sizeof(DWORD))); pVoid = (LPVOID)(outBuf+2*sizeof(DWORD)+sizeof(HANDLE)); DebugTrace("Action = %d, Client Handle = %x, Client Process = %d\n", dwAction, hClientHandle, dwClientProcess); // End of code to be removed (see BUGBUG above) // Launch the appropriate function to process the request. // NOTE: handles are duplicated here and PREFIX finds the path // that we are not releasing them on error (DisconnectNamedPipe() // failure), but the thing is that on error we will terminate the // process and the handles will still be released, so does not // help us if we try to handle that in any way. // switch (dwAction) { case DO_NEW_WP: (VOID)g_handles.FAddNewWP(dwClientProcess); break; case DO_SAVE: g_wlc.SaveHandle(dwClientProcess, hClientHandle); break; case DO_LOCK: LockFile(dwClientProcess, hClientHandle, pVoid); break; case DO_REMOVE: CloseHandle (hClientHandle); break; default: DebugTrace("Invalid action %i sent in \r\n", dwAction); } disconnect: // Release the client from the named pipe so another client can call in. // if (!DisconnectNamedPipe(hNamedPipe)) { sc = HRESULT_FROM_WIN32(GetLastError()); goto cleanup; } } while (TRUE); cleanup: return sc; } // =============================================================== // Main Routine // =============================================================== // // Setting up the shared memory for the lock cache and // establishing the listener who will support the worker processes // storing and releasing file handles. // int _cdecl main () { CSmhInit si; SCODE sc = S_OK; // Initlize shared memory // if (FALSE == si.FInitialize(gc_wszSignature)) { DebugTrace ("Failed to initialize shared memory\n"); sc = E_FAIL; goto ret; } sc = ScInitialize(); if (FAILED(sc)) goto ret; // If we have setup the shared memory then we are ready to take request // to save handles, and start timing out locks. // sc = ScNamedPipeListener(); if (FAILED(sc)) goto ret; ret: return sc; }