/****************************************************************************** Copyright (c) 2001 Microsoft Corporation Module Name: erswait.cpp Abstract: Implementation of DLL Exports. Revision History: derekm 02/28/2001 created ******************************************************************************/ #include "stdafx.h" #include "stdio.h" #include "pfrcfg.h" ////////////////////////////////////////////////////////////////////////////// // Globals SECURITY_DESCRIPTOR g_rgsd[ertiCount]; SRequestEventType g_rgEvents[ertiCount]; HANDLE g_hmutUser = NULL; HANDLE g_hmutKrnl = NULL; HANDLE g_hmutShut = NULL; ////////////////////////////////////////////////////////////////////////////// // misc stuff // *************************************************************************** void InitializeSvcDataStructs(void) { ZeroMemory(g_rgsd, ertiCount * sizeof(SECURITY_DESCRIPTOR)); ZeroMemory(g_rgEvents, ertiCount * sizeof(SRequestEventType)); g_rgEvents[ertiHang].pfn = ProcessHangRequest; g_rgEvents[ertiHang].wszPipeName = c_wszHangPipe; g_rgEvents[ertiHang].wszRVPipeCount = c_wszRVNumHangPipe; g_rgEvents[ertiHang].cPipes = c_cMinPipes; g_rgEvents[ertiHang].fAllowNonLS = FALSE; g_rgEvents[ertiFault].pfn = ProcessFaultRequest; g_rgEvents[ertiFault].wszPipeName = c_wszFaultPipe; g_rgEvents[ertiFault].wszRVPipeCount = c_wszRVNumFaultPipe; g_rgEvents[ertiFault].cPipes = c_cMinPipes; g_rgEvents[ertiFault].fAllowNonLS = TRUE; } ////////////////////////////////////////////////////////////////////////////// // pipe manager // *************************************************************************** BOOL ExecServer(SRequest *pReq) { OVERLAPPED ol; HANDLE rghWait[2] = { NULL, NULL }; HANDLE hPipe = INVALID_HANDLE_VALUE; DWORD cbBuf, cb, dw; BOOL fRet, fShutdown = FALSE; BYTE Buf[ERRORREP_PIPE_BUF_SIZE]; rghWait[0] = g_hevSvcStop; hPipe = pReq->hPipe; if (hPipe == INVALID_HANDLE_VALUE || rghWait[0] == NULL || pReq->pret->pfn == NULL) { SetLastError(ERROR_INVALID_PARAMETER); goto done; } // need another event for waiting on the pipe read rghWait[1] = CreateEventW(NULL, TRUE, FALSE, NULL); if (rghWait[1] == NULL) goto done; // setup the overlapped structure ZeroMemory(&ol, sizeof(ol)); ol.hEvent = rghWait[1]; // read the request ResetEvent(ol.hEvent); fRet = ReadFile(hPipe, Buf, sizeof(Buf), &cb, &ol); if (fRet == FALSE && GetLastError() == ERROR_IO_PENDING) { // give the client 60s to write the data to us. // WAIT_OBJECT_0 is the shutdown event // WAIT_OBJECT_0 + 1 is the overlapped event dw = WaitForMultipleObjects(2, rghWait, FALSE, 60000); if (dw == WAIT_OBJECT_0) fShutdown = TRUE; else if (dw != WAIT_OBJECT_0 + 1) goto done; fRet = TRUE; } if (fRet) fRet = GetOverlappedResult(hPipe, &ol, &cbBuf, FALSE); // if we got an error, the client might still be waiting for a // reply, so construct a default one. // ProcessExecRequest() will always construct a reply and store it // in Buf, so no special handling is needed if it fails. if (fShutdown == FALSE && fRet) { cbBuf = sizeof(Buf); fRet = (*(pReq->pret->pfn))(hPipe, Buf, &cbBuf); } else { SPCHExecServGenericReply esrep; ZeroMemory(&esrep, sizeof(esrep)); esrep.cb = sizeof(esrep); esrep.ess = essErr; esrep.dwErr = GetLastError(); RtlCopyMemory(Buf, &esrep, sizeof(esrep)); cbBuf = sizeof(esrep); } // write the reply to the message ResetEvent(ol.hEvent); fRet = WriteFile(hPipe, Buf, cbBuf, &cb, &ol); if (fRet == FALSE && GetLastError() == ERROR_IO_PENDING) { // give ourselves 60s to write the data to the pipe. // WAIT_OBJECT_0 is the shutdown event // WAIT_OBJECT_0 + 1 is the overlapped event dw = WaitForMultipleObjects(2, rghWait, FALSE, 60000); if (dw == WAIT_OBJECT_0) fShutdown = TRUE; else if (dw != WAIT_OBJECT_0 + 1) goto done; fRet = TRUE; } // wait for the client to read the buffer- note that we could use // FlushFileBuffers() to do this, but that is blocking with no // timeout, so we try to do a read on the pipe & wait to get an // error indicating that the client closed it. // Yup, this is a hack, but this is apparently the way to do this // when using async pipe communication. Sigh... if (fShutdown == FALSE && fRet) { ResetEvent(ol.hEvent); fRet = ReadFile(hPipe, Buf, sizeof(Buf), &cb, &ol); if (fRet == FALSE && GetLastError() == ERROR_IO_PENDING) { // give ourselves 60s to read the data from the pipe. // Except for the shutdown notification, don't really // care what this routine returns cuz we're just using // it to wait on the read to finish // WAIT_OBJECT_0 is the shutdown event // WAIT_OBJECT_0 + 1 is the overlapped event dw = WaitForMultipleObjects(2, rghWait, FALSE, 60000); if (dw == WAIT_OBJECT_0) fShutdown = TRUE; } } SetLastError(0); done: dw = GetLastError(); if (hPipe != INVALID_HANDLE_VALUE) DisconnectNamedPipe(hPipe); if (rghWait[1] != NULL) CloseHandle(rghWait[1]); SetLastError(dw); return fShutdown; } // *************************************************************************** DWORD WINAPI threadExecServer(PVOID pvContext) { SRequest *pReq = (SRequest *)pvContext; if (pReq == NULL) { // if (pReq->hModErsvc) FreeLibrary(pReq->hModErsvc); return ERROR_INVALID_PARAMETER; } // this acquires the request CS and holds it until the function exits CAutoUnlockCS aucs(&pReq->csReq, TRUE); // make sure we aren't shutting down if (WaitForSingleObject(g_hevSvcStop, 0) != WAIT_TIMEOUT) { SetLastError( ERROR_SUCCESS ); goto done; } __try { ExecServer(pReq); } __except(SetLastError(GetExceptionCode()), EXCEPTION_EXECUTE_HANDLER) { } done: if (pReq->hModErsvc) FreeLibrary(pReq->hModErsvc); return GetLastError(); } ////////////////////////////////////////////////////////////////////////////// // object manager // *************************************************************************** void NukeRequestObj(SRequest *pReq, BOOL fFreeEvent) { if (pReq == NULL) return; // this acquires the request CS and holds it until the function exits CAutoUnlockCS aucs(&pReq->csReq, TRUE); // free the pipe if (pReq->hPipe != INVALID_HANDLE_VALUE) { DisconnectNamedPipe(pReq->hPipe); CloseHandle(pReq->hPipe); pReq->hPipe = INVALID_HANDLE_VALUE; } if (fFreeEvent && pReq->ol.hEvent != NULL) { CloseHandle(pReq->ol.hEvent); ZeroMemory(&pReq->ol, sizeof(pReq->ol)); } if (pReq->hth != NULL) { CloseHandle(pReq->hth); pReq->hth = NULL; } pReq->ers = ersEmpty; } // *************************************************************************** BOOL BuildRequestObj(SRequest *pReq, SRequestEventType *pret) { SECURITY_ATTRIBUTES sa; HANDLE hev = NULL; HANDLE hPipe = INVALID_HANDLE_VALUE; BOOL fRet = FALSE; if (pReq == NULL || pret == NULL) { SetLastError(ERROR_INVALID_PARAMETER); goto done; } // if this is empty, then we're building a fresh object, so create an // event for waiting on the pipe listen if (pReq->ol.hEvent == NULL) { // need an hev = CreateEventW(NULL, FALSE, FALSE, NULL); if (hev == NULL) goto done; } // otherwise, store away the existing event else { hev = pReq->ol.hEvent; ResetEvent(hev); } // don't want to nuke the critical section! ZeroMemory(((PBYTE)pReq + sizeof(pReq->csReq)), sizeof(SRequest) - sizeof(pReq->csReq)); sa.nLength = sizeof(sa); sa.bInheritHandle = FALSE; sa.lpSecurityDescriptor = pret->psd; // obviously gotta have a pipe hPipe = CreateNamedPipeW(pret->wszPipeName, PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, PIPE_WAIT | PIPE_READMODE_MESSAGE | PIPE_TYPE_MESSAGE, c_cMaxPipes, ERRORREP_PIPE_BUF_SIZE, ERRORREP_PIPE_BUF_SIZE, 0, &sa); if (hPipe == INVALID_HANDLE_VALUE) goto done; // make sure we aren't shutting down if (WaitForSingleObject(g_hevSvcStop, 0) != WAIT_TIMEOUT) goto done; pReq->ol.hEvent = hev; // start waiting on the pipe fRet = ConnectNamedPipe(hPipe, &pReq->ol); if (fRet == FALSE && GetLastError() != ERROR_IO_PENDING) { // if the pipe is already connected, just set the event cuz // ConnectNamedPipe doesn't if (GetLastError() == ERROR_PIPE_CONNECTED) { SetEvent(pReq->ol.hEvent); } else { pReq->ol.hEvent = NULL; goto done; } } // yay! save off everything. pReq->ers = ersWaiting; pReq->pret = pret; pReq->hPipe = hPipe; hev = NULL; hPipe = INVALID_HANDLE_VALUE; fRet = TRUE; done: if (hev != NULL) CloseHandle(hev); if (hPipe != INVALID_HANDLE_VALUE) { DisconnectNamedPipe(hPipe); CloseHandle(hPipe); } return fRet; } // *************************************************************************** BOOL ResetRequestObj(SRequest *pReq) { BOOL fRet = FALSE; if (pReq == NULL || pReq->ers != ersProcessing) { SetLastError(ERROR_INVALID_PARAMETER); goto done; } // clean up the thread handle. if (pReq->hth != NULL) { CloseHandle(pReq->hth); pReq->hth = NULL; } // check and make sure that our object is valid. If it ain't, nuke it // and rebuild it. if (pReq->hPipe != NULL && pReq->ol.hEvent != NULL && pReq->pret != NULL) { // start waiting on the pipe fRet = ConnectNamedPipe(pReq->hPipe, &pReq->ol); if (fRet == FALSE) { switch(GetLastError()) { case ERROR_IO_PENDING: fRet = TRUE; break; case ERROR_PIPE_CONNECTED: SetEvent(pReq->ol.hEvent); fRet = TRUE; break; default: break; } } } if (fRet == FALSE) { NukeRequestObj(pReq, FALSE); fRet = BuildRequestObj(pReq, pReq->pret); if (fRet == FALSE) goto done; } else { pReq->ers = ersWaiting; } done: return fRet; } // *************************************************************************** BOOL ProcessRequestObj(SRequest *pReq) { HANDLE hth = NULL; // should do a LoadLibrary on ersvc.dll before entering the thread. // Then, at the end of the thread, do a FreeLibraryAndExitThread() call. // This eliminates a very very small chance of a race condition (leading to an AV) // when shutting the service down. if (!pReq) { return FALSE; } pReq->hModErsvc = LoadLibraryExW(L"ersvc.dll", NULL, 0); if (pReq->hModErsvc == NULL) { return FALSE; } hth = CreateThread(NULL, 0, threadExecServer, pReq, 0, NULL); if (hth == NULL) { FreeLibrary(pReq->hModErsvc); return FALSE; } pReq->ers = ersProcessing; pReq->hth = hth; hth = NULL; return TRUE; } // *************************************************************************** BOOL ProcessRequests(SRequest *rgReqs, DWORD cReqs) { HANDLE *rghWait = NULL; DWORD iReq, cErrs = 0, dw; BOOL fRet = FALSE; if (rgReqs == NULL || cReqs == NULL || cReqs > MAXIMUM_WAIT_OBJECTS) { SetLastError(ERROR_INVALID_PARAMETER); goto done; } rghWait = (HANDLE *)MyAlloc((cReqs + 1) * sizeof(HANDLE)); if (rghWait == NULL) { SetLastError(ERROR_OUTOFMEMORY); goto done; } // initially, populate all the entries in the wait array with the handles // to the overlapped events rghWait[0] = g_hevSvcStop; for(iReq = 0; iReq < cReqs; iReq++) { if (rgReqs[iReq].ol.hEvent != NULL) rghWait[iReq + 1] = rgReqs[iReq].ol.hEvent; else goto done; } for(;;) { dw = WaitForMultipleObjects(cReqs + 1, rghWait, FALSE, INFINITE); // if it's the first wait handle, then we're shutting down, so just return // TRUE if (dw == WAIT_OBJECT_0) { fRet = TRUE; goto done; } // yippy! It's one of the pipes. else if (dw >= WAIT_OBJECT_0 + 1 && dw <= WAIT_OBJECT_0 + cReqs) { SRequest *pReq; cErrs = 0; iReq = (dw - WAIT_OBJECT_0) - 1; pReq = &rgReqs[iReq]; // check first to make sure we aren't shutting down. If we are, just // bail if (WaitForSingleObject(g_hevSvcStop, 0) != WAIT_TIMEOUT) { fRet = TRUE; goto done; } if (pReq->ers == ersWaiting) { fRet = ProcessRequestObj(pReq); // if we succeeded, then wait for the thread to complete instead // of the named pipe connect event if (fRet) { rghWait[iReq + 1] = pReq->hth; continue; } else { // set this so that we fall thru to the next case & get // everything cleaned up... pReq->ers = ersProcessing; } } if (pReq->ers == ersProcessing) { fRet = ResetRequestObj(pReq); if (fRet == FALSE) { if (iReq < cReqs - 1) { SRequest oReq; HANDLE hWait; CopyMemory(&oReq, pReq, sizeof(oReq)); MoveMemory(&rgReqs[iReq], &rgReqs[iReq + 1], (cReqs - iReq - 1) * sizeof(SRequest)); CopyMemory(&rgReqs[cReqs - 1], &oReq, sizeof(oReq)); // rearrange the rghWait array as well. Otherwise it's out of sync with the object array hWait = rghWait[iReq + 1]; MoveMemory(&rghWait[iReq + 1], &rghWait[iReq + 2], (cReqs - iReq - 1)); rghWait[cReqs] = hWait; } cReqs--; } // ok, time to start waiting on the event to signal that a pipe // has been connected to... else { rghWait[iReq + 1] = pReq->ol.hEvent; } } } // um, this is bad. else { if (cErrs > 8) { ASSERT(FALSE); break; } cErrs++; } } done: if (rghWait != NULL) MyFree(rghWait); return fRet; } ////////////////////////////////////////////////////////////////////////////// // startup & shutdown // *************************************************************************** BOOL StartERSvc(SERVICE_STATUS_HANDLE hss, SERVICE_STATUS &ss, SRequest **prgReqs, DWORD *pcReqs) { SECURITY_DESCRIPTOR sd; SECURITY_ATTRIBUTES sa; SRequest *rgReqs = NULL; HANDLE hth, hmut = NULL; DWORD dw, i, iPipe, dwType, cb, cReqs, iReqs; BOOL fRet = FALSE; HKEY hkey = NULL; ZeroMemory(&sa, sizeof(sa)); if (hss == NULL || prgReqs == NULL || pcReqs == NULL) { SetLastError(ERROR_INVALID_PARAMETER); goto done; } *prgReqs = NULL; *pcReqs = NULL; if (AllocSD(&sd, ER_ACCESS_ALL, ER_ACCESS_ALL, 0) == FALSE) goto done; sa.nLength = sizeof(SECURITY_ATTRIBUTES); sa.lpSecurityDescriptor = &sd; sa.bInheritHandle = FALSE; // these two mutexes will intentionally not be free'd even if we stop the // exec server threads... These need to exist for kernel fault reporting // to work. Don't want to completely bail if we fail since we could just // have restarted the server & we don't actually need these for the // service to work. hmut = CreateMutexW(&sa, FALSE, c_wszMutKrnlName); if (hmut != NULL) { if (GetLastError() != ERROR_ALREADY_EXISTS) g_hmutKrnl = hmut; else CloseHandle(hmut); hmut = NULL; } hmut = CreateMutexW(&sa, FALSE, c_wszMutUserName); if (hmut != NULL) { if (GetLastError() != ERROR_ALREADY_EXISTS) g_hmutKrnl = hmut; else CloseHandle(hmut); hmut = NULL; } hmut = CreateMutexW(&sa, FALSE, c_wszMutShutName); if (hmut != NULL) { if (GetLastError() != ERROR_ALREADY_EXISTS) g_hmutShut = hmut; else CloseHandle(hmut); hmut = NULL; } dw = RegOpenKeyExW(HKEY_LOCAL_MACHINE, c_wszRPCfg, 0, KEY_READ, &hkey); if (dw != ERROR_SUCCESS) hkey = NULL; // find out how many pipes we're gonna create cReqs = 0; for(i = 0; i < ertiCount; i++) { if (hkey != NULL) { cb = sizeof(g_rgEvents[i].cPipes); dw = RegQueryValueExW(hkey, g_rgEvents[i].wszRVPipeCount, 0, &dwType, (LPBYTE)&g_rgEvents[i].cPipes, &cb); if (dwType != REG_DWORD || g_rgEvents[i].cPipes < c_cMinPipes) g_rgEvents[i].cPipes = c_cMinPipes; else if (g_rgEvents[i].cPipes > c_cMaxPipes) g_rgEvents[i].cPipes = c_cMaxPipes; } cReqs += g_rgEvents[i].cPipes; // ss.dwCurrentState = SERVICE_CONTINUE_PENDING; ss.dwCheckPoint++; SetServiceStatus(hss, &ss); } if (cReqs >= MAXIMUM_WAIT_OBJECTS) { SetLastError(ERROR_INVALID_PARAMETER); goto done; } // allocate the array that will hold the request info rgReqs = (SRequest *)MyAlloc(cReqs * sizeof(SRequest)); if (rgReqs == NULL) { SetLastError(ERROR_OUTOFMEMORY); goto done; } // build our array of request objects fRet = TRUE; iReqs = 0; for (i = 0; i < ertiCount; i++) { dw = (g_rgEvents[i].fAllowNonLS) ? ACCESS_RW : 0; fRet = AllocSD(&g_rgsd[i], ER_ACCESS_ALL, dw, dw); if (fRet == FALSE) break; g_rgEvents[i].psd = &g_rgsd[i]; // allocate request objects for (iPipe = 0; iPipe < g_rgEvents[i].cPipes; iPipe++) { rgReqs[iReqs].hPipe = INVALID_HANDLE_VALUE; InitializeCriticalSection(&rgReqs[iReqs].csReq); fRet = BuildRequestObj(&rgReqs[iReqs], &g_rgEvents[i]); if (fRet == FALSE) break; iReqs++; } if (fRet == FALSE) break; // need to update service status // ss.dwCurrentState = SERVICE_CONTINUE_PENDING; ss.dwCheckPoint++; SetServiceStatus(hss, &ss); } if (fRet == FALSE) { ss.dwCheckPoint++; ss.dwCurrentState = SERVICE_STOP_PENDING; SetServiceStatus(hss, &ss); StopERSvc(hss, ss, rgReqs, cReqs); } else { *prgReqs = rgReqs; *pcReqs = cReqs; rgReqs = NULL; cReqs = 0; } done: if (sa.lpSecurityDescriptor != NULL) FreeSD((SECURITY_DESCRIPTOR *)sa.lpSecurityDescriptor); if (rgReqs != NULL) MyFree(rgReqs); if (hkey != NULL) RegCloseKey(hkey); return fRet; } // *************************************************************************** BOOL StopERSvc(SERVICE_STATUS_HANDLE hss, SERVICE_STATUS &ss, SRequest *rgReqs, DWORD cReqs) { DWORD i; if (hss == NULL || rgReqs == NULL || cReqs == 0) { SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } if (g_hevSvcStop == NULL) goto done; SetEvent(g_hevSvcStop); // update service status ss.dwCheckPoint++; // ss.dwCurrentState = SERVICE_CONTINUE_PENDING; SetServiceStatus(hss, &ss); for (i = 0; i < cReqs; i++) { NukeRequestObj(&rgReqs[i], TRUE); DeleteCriticalSection(&rgReqs[i].csReq); } for (i = 0; i < ertiCount; i++) { if (g_rgEvents[i].psd != NULL) { FreeSD(g_rgEvents[i].psd); g_rgEvents[i].psd = NULL; } } // update service status ss.dwCheckPoint++; // ss.dwCurrentState = SERVICE_CONTINUE_PENDING; SetServiceStatus(hss, &ss); done: return TRUE; }