diff options
Diffstat (limited to 'src/VBox/HostDrivers/Support/SUPSvcGrant.cpp')
-rw-r--r-- | src/VBox/HostDrivers/Support/SUPSvcGrant.cpp | 1012 |
1 files changed, 1012 insertions, 0 deletions
diff --git a/src/VBox/HostDrivers/Support/SUPSvcGrant.cpp b/src/VBox/HostDrivers/Support/SUPSvcGrant.cpp new file mode 100644 index 00000000..ba2edb83 --- /dev/null +++ b/src/VBox/HostDrivers/Support/SUPSvcGrant.cpp @@ -0,0 +1,1012 @@ +/* $Id: SUPSvcGrant.cpp $ */ +/** @file + * VirtualBox Support Service - The Grant Service. + */ + +/* + * Copyright (C) 2008-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SUP +#include "SUPSvcInternal.h" + +#include <VBox/log.h> +#include <iprt/asm.h> +#include <iprt/errcore.h> +#include <iprt/assert.h> +#include <iprt/critsect.h> +#include <iprt/mem.h> +#include <iprt/semaphore.h> +#include <iprt/thread.h> +#include <iprt/time.h> +#include <iprt/localipc.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Pointer to a client instance. */ +typedef struct SUPSVCGRANTSESSION *PSUPSVCGRANTSESSION; +/** Pointer to a Grant service instance. */ +typedef struct SUPSVCGRANT *PSUPSVCGRANT; + + +/** + * Grant service session data. + */ +typedef struct SUPSVCGRANTSESSION +{ + /** Pointer to the next client in the list. */ + PSUPSVCGRANTSESSION pNext; + /** Pointer to the previous client in the list. */ + PSUPSVCGRANTSESSION pPrev; + /** Pointer to the parent (the service instance). */ + PSUPSVCGRANT volatile pParent; + /** The local ipc client handle. */ + RTLOCALIPCSESSION volatile hSession; + /** Indicate that the thread should terminate ASAP. */ + bool volatile fTerminate; + /** The thread handle. */ + RTTHREAD hThread; + +} SUPSVCGRANTSESSION; + + +/** + * State grant service machine. + */ +typedef enum SUPSVCGRANTSTATE +{ + /** The invalid zero entry. */ + kSupSvcGrantState_Invalid = 0, + /** Creating - the thread is being started. + * Next: Paused or Butchered. */ + kSupSvcGrantState_Creating, + /** Paused - the thread is blocked on it's user event semaphore. + * Next: Resuming, Terminating or Butchered. + * Prev: Creating, Pausing */ + kSupSvcGrantState_Paused, + /** Resuming - the thread is being unblocked and ushered into RTLocalIpcServiceListen. + * Next: Listen or Butchered. + * Prev: Paused */ + kSupSvcGrantState_Resuming, + /** Listen - the thread is in RTLocalIpcServerListen or setting up an incoming session. + * Next: Pausing or Butchered. + * Prev: Resuming */ + kSupSvcGrantState_Listen, + /** Pausing - Cancelling the listen and dropping any incoming sessions. + * Next: Paused or Butchered. + * Prev: Listen */ + kSupSvcGrantState_Pausing, + /** Butchered - The thread has quit because something when terribly wrong. + * Next: Destroyed + * Prev: Any. */ + kSupSvcGrantState_Butchered, + /** Pausing - Cancelling the listen and dropping any incoming sessions. + * Next: Destroyed + * Prev: Paused */ + kSupSvcGrantState_Terminating, + /** Destroyed - the instance is invalid. + * Prev: Butchered or Terminating */ + kSupSvcGrantState_Destroyed, + /** The end of valid state values. */ + kSupSvcGrantState_End, + /** The usual 32-bit blowup hack. */ + kSupSvcGrantState_32BitHack = 0x7fffffff +} SUPSVCGRANTSTATE; + + +/** + * Grant service instance data. + */ +typedef struct SUPSVCGRANT +{ + /** The local ipc server handle. */ + RTLOCALIPCSERVER hServer; + + /** Critical section serializing access to the session list, the state, + * the response event, the session event, and the thread event. */ + RTCRITSECT CritSect; + /** The service thread will signal this event when it has changed to + * the 'paused' or 'running' state. */ + RTSEMEVENT hResponseEvent; + /** Event that's signaled on session termination. */ + RTSEMEVENT hSessionEvent; + /** The handle to the service thread. */ + RTTHREAD hThread; + /** Head of the session list. */ + PSUPSVCGRANTSESSION volatile pSessionHead; + /** The service state. */ + SUPSVCGRANTSTATE volatile enmState; + + /** Critical section serializing access to the SUPR3HardenedVerify APIs. */ + RTCRITSECT VerifyCritSect; +} SUPSVCGRANT; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static const char *supSvcGrantStateName(SUPSVCGRANTSTATE enmState); + + + + +/** + * Services a client session. + * + * @returns VINF_SUCCESS. + * + * @param hThread The thread handle. + * @param pvSession Pointer to the session instance data. + */ +static DECLCALLBACK(int) supSvcGrantSessionThread(RTTHREAD hThread, void *pvSession) +{ + PSUPSVCGRANTSESSION pThis = (PSUPSVCGRANTSESSION)pvSession; + RTLOCALIPCSESSION hSession = pThis->hSession; + Log(("supSvcGrantSessionThread(%p):\n", pThis)); + + /* + * Process client requests until it quits or we're cancelled on termination. + */ + while (!ASMAtomicUoReadBool(&pThis->fTerminate)) + { + RTThreadSleep(1000); + /** @todo */ + } + + /* + * Clean up the session. + */ + PSUPSVCGRANT pParent = ASMAtomicReadPtrT(&pThis->pParent, PSUPSVCGRANT); + if (pParent) + RTCritSectEnter(&pParent->CritSect); + else + Log(("supSvcGrantSessionThread(%p): No parent\n", pThis)); + + ASMAtomicXchgHandle(&pThis->hSession, NIL_RTLOCALIPCSESSION, &hSession); + if (hSession != NIL_RTLOCALIPCSESSION) + RTLocalIpcSessionClose(hSession); + else + Log(("supSvcGrantSessionThread(%p): No session handle\n", pThis)); + + if (pParent) + { + RTSemEventSignal(pParent->hSessionEvent); + RTCritSectLeave(&pParent->CritSect); + } + Log(("supSvcGrantSessionThread(%p): exits\n")); + return VINF_SUCCESS; +} + + +/** + * Cleans up a session. + * + * This is called while inside the grant service critical section. + * + * @param pThis The session to destroy. + * @param pParent The parent. + */ +static void supSvcGrantSessionDestroy(PSUPSVCGRANTSESSION pThis, PSUPSVCGRANT pParent) +{ + /* + * Unlink it. + */ + if (pThis->pNext) + { + Assert(pThis->pNext->pPrev == pThis); + pThis->pNext->pPrev = pThis->pPrev; + } + + if (pThis->pPrev) + { + Assert(pThis->pPrev->pNext == pThis); + pThis->pPrev->pNext = pThis->pNext; + } + else if (pParent->pSessionHead == pThis) + pParent->pSessionHead = pThis->pNext; + + /* + * Free the resources associated with it. + */ + pThis->hThread = NIL_RTTHREAD; + pThis->pNext = NULL; + pThis->pPrev = NULL; + + RTLOCALIPCSESSION hSession; + ASMAtomicXchgHandle(&pThis->hSession, NIL_RTLOCALIPCSESSION, &hSession); + if (hSession != NIL_RTLOCALIPCSESSION) + RTLocalIpcSessionClose(hSession); + + RTMemFree(pThis); +} + + +/** + * Cleans up zombie sessions, locked. + * + * @param pThis Pointer to the grant service instance data. + */ +static void supSvcGrantCleanUpSessionsLocked(PSUPSVCGRANT pThis) +{ + /* + * Iterate until be make it all the way thru the list. + * + * Only use the thread state as and indicator on whether we can destroy + * the session or not. + */ + PSUPSVCGRANTSESSION pCur; + do + { + for (pCur = pThis->pSessionHead; pCur; pCur = pCur->pNext) + { + int rc = RTThreadWait(pCur->hThread, 0, NULL); + if (RT_SUCCESS(rc)) + { + supSvcGrantSessionDestroy(pCur, pThis); + break; + } + + Assert(rc == VERR_TIMEOUT); + Assert(pCur->hThread != NIL_RTTHREAD); + Assert(pCur->pNext != pThis->pSessionHead); + } + } while (pCur); +} + + +/** + * Cleans up zombie sessions. + * + * @returns VINF_SUCCESS, VBox error code on internal error. + * + * @param pThis Pointer to the grant service instance data. + * @param fOwnCritSect Whether we own the crit sect already. The state is preserved. + */ +static int supSvcGrantCleanUpSessions(PSUPSVCGRANT pThis, bool fOwnCritSect) +{ + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_FAILURE(rc)) + { + supSvcLogError("supSvcGrantCleanUpSessions: RTCritSectEnter returns %Rrc", rc); + return rc; + } + + supSvcGrantCleanUpSessionsLocked(pThis); + + RTCritSectLeave(&pThis->CritSect); + return VINF_SUCCESS; +} + + +/** + * Gets the state name. + * + * @returns The state name string (read only). + * @param enmState The state. + */ +static const char *supSvcGrantStateName(SUPSVCGRANTSTATE enmState) +{ + switch (enmState) + { + case kSupSvcGrantState_Invalid: return "Invalid"; + case kSupSvcGrantState_Creating: return "Creating"; + case kSupSvcGrantState_Paused: return "Paused"; + case kSupSvcGrantState_Resuming: return "Resuming"; + case kSupSvcGrantState_Listen: return "Listen"; + case kSupSvcGrantState_Pausing: return "Pausing"; + case kSupSvcGrantState_Butchered: return "Butchered"; + case kSupSvcGrantState_Terminating: return "Terminating"; + case kSupSvcGrantState_Destroyed: return "Destroyed"; + default: return "?Unknown?"; + } +} + + +/** + * Attempts to flip into the butchered state. + * + * @returns rc. + * @param pThis The instance data. + * @param fOwnCritSect Whether we own the crit sect already. + * @param pszFailed What failed. + * @param rc What to return (lazy bird). + */ +static int supSvcGrantThreadButchered(PSUPSVCGRANT pThis, bool fOwnCritSect, const char *pszFailed, int rc) +{ + int rc2 = VINF_SUCCESS; + if (!fOwnCritSect) + rc2 = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc2)) + { + supSvcLogError("supSvcGrantThread(%s): Butchered; %Rrc: %s", + supSvcGrantStateName(pThis->enmState), rc, pszFailed); + pThis->enmState = kSupSvcGrantState_Butchered; + + RTCritSectLeave(&pThis->CritSect); + } + return rc; +} + + +/** + * Creates a new session. + * + * @returns VINF_SUCCESS on success, VBox error code on internal error. + * + * @param pThis Pointer to the grant service instance data. + * @param hSession The client session handle. + */ +static int supSvcGrantThreadCreateSession(PSUPSVCGRANT pThis, RTLOCALIPCSESSION hSession) +{ + /* + * Allocate and initialize a new session instance before entering the critsect. + */ + PSUPSVCGRANTSESSION pSession = (PSUPSVCGRANTSESSION)RTMemAlloc(sizeof(*pSession)); + if (!pSession) + { + supSvcLogError("supSvcGrantThreadListen: failed to allocate session"); + return VINF_SUCCESS; /* not fatal? */ + } + pSession->pPrev = NULL; + pSession->pNext = NULL; + pSession->pParent = pThis; + pSession->hSession = hSession; + pSession->fTerminate = false; + pSession->hThread = NIL_RTTHREAD; + + /* + * Enter the critsect, check the state, link it and fire off the session thread. + */ + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + /* check the state */ + SUPSVCGRANTSTATE enmState = pThis->enmState; + if (enmState == kSupSvcGrantState_Listen) + { + /* link it */ + pSession->pNext = pThis->pSessionHead; + if (pThis->pSessionHead) + pThis->pSessionHead->pPrev = pSession; + pThis->pSessionHead = pSession; + + /* fire up the thread */ + Log(("supSvcGrantThreadListen: starting session %p\n", pSession)); + rc = RTThreadCreate(&pSession->hThread, supSvcGrantSessionThread, pSession, 0, + RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "SESSION"); + if (RT_SUCCESS(rc)) + { + rc = RTCritSectLeave(&pThis->CritSect); + if (RT_FAILURE(rc)) + return supSvcGrantThreadButchered(pThis, false /* fOwnCritSect */, "RTCritSectLeave", rc); + + /* + * Successfully handled the client. + */ + return VINF_SUCCESS; + } + + /* bail out */ + supSvcLogError("supSvcGrantThreadListen: RTThreadCreate returns %Rrc", rc); + } + else + Log(("supSvcGrantThreadListen: dropping connection, state %s\n", supSvcGrantStateName(enmState))); + + RTCritSectLeave(&pThis->CritSect); + rc = VINF_SUCCESS; + } + else + supSvcGrantThreadButchered(pThis, false /* fOwnCritSect */, "RTCritSectEnter", rc); + RTLocalIpcSessionClose(hSession); + RTMemFree(pSession); + return rc; +} + + +/** + * Listen for a client session and kicks off the service thread for it. + * + * @returns VINF_SUCCESS on normal state change, failure if something gets screwed up. + * + * @param pThis Pointer to the grant service instance data. + */ +static int supSvcGrantThreadListen(PSUPSVCGRANT pThis) +{ + /* + * Wait for a client to connect and create a new session. + */ + RTLOCALIPCSESSION hClientSession = NIL_RTLOCALIPCSESSION; + int rc = RTLocalIpcServerListen(pThis->hServer, &hClientSession); + if (RT_FAILURE(rc)) + { + if (rc == VERR_CANCELLED) + LogFlow(("supSvcGrantThreadListen: cancelled\n")); + else if (rc == VERR_TRY_AGAIN) + /* for testing */; + else + return supSvcGrantThreadButchered(pThis, false /* fOwnCritSect */, "RTLocalIpcServerListen", rc); + return VINF_SUCCESS; + } + + return supSvcGrantThreadCreateSession(pThis, hClientSession); +} + + +/** + * Grant service thread. + * + * This thread is the one listening for clients and kicks off + * the session threads and stuff. + * + * @returns VINF_SUCCESS on normal exit, VBox error status on failure. + * @param hThread The thread handle. + * @param pvThis Pointer to the grant service instance data. + */ +static DECLCALLBACK(int) supSvcGrantThread(RTTHREAD hThread, void *pvThis) +{ + PSUPSVCGRANT pThis = (PSUPSVCGRANT)pvThis; + + /* + * The state loop. + */ + for (;;) + { + /* + * Switch on the current state (requires critsect). + */ + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_FAILURE(rc)) + { + supSvcLogError("supSvcGrantThread - RTCritSectEnter returns %Rrc", rc); + return rc; + } + + SUPSVCGRANTSTATE enmState = pThis->enmState; + LogFlow(("supSvcGrantThread: switching %s\n", supSvcGrantStateName(enmState))); + switch (enmState) + { + case kSupSvcGrantState_Creating: + case kSupSvcGrantState_Pausing: + pThis->enmState = kSupSvcGrantState_Paused; + rc = RTSemEventSignal(pThis->hResponseEvent); + if (RT_FAILURE(rc)) + return supSvcGrantThreadButchered(pThis, true /* fOwnCritSect*/, "RTSemEventSignal", rc); + RT_FALL_THRU(); + + case kSupSvcGrantState_Paused: + RTCritSectLeave(&pThis->CritSect); + + rc = RTThreadUserWait(hThread, 60*1000); /* wake up once in a while (paranoia) */ + if (RT_FAILURE(rc) && rc != VERR_TIMEOUT) + return supSvcGrantThreadButchered(pThis, false /* fOwnCritSect*/, "RTThreadUserWait", rc); + break; + + case kSupSvcGrantState_Resuming: + pThis->enmState = kSupSvcGrantState_Listen; + rc = RTSemEventSignal(pThis->hResponseEvent); + if (RT_FAILURE(rc)) + return supSvcGrantThreadButchered(pThis, true /* fOwnCritSect*/, "RTSemEventSignal", rc); + RT_FALL_THRU(); + + case kSupSvcGrantState_Listen: + RTCritSectLeave(&pThis->CritSect); + rc = supSvcGrantThreadListen(pThis); + if (RT_FAILURE(rc)) + { + Log(("supSvcGrantThread: supSvcGrantDoListening returns %Rrc, exiting\n", rc)); + return rc; + } + break; + + case kSupSvcGrantState_Terminating: + RTCritSectLeave(&pThis->CritSect); + Log(("supSvcGrantThread: Done\n")); + return VINF_SUCCESS; + + case kSupSvcGrantState_Butchered: + default: + return supSvcGrantThreadButchered(pThis, true /* fOwnCritSect*/, "Bad state", VERR_INTERNAL_ERROR); + } + + /* + * Massage the session list between clients and states. + */ + rc = supSvcGrantCleanUpSessions(pThis, false /* fOwnCritSect */); + if (RT_FAILURE(rc)) + return supSvcGrantThreadButchered(pThis, false /* fOwnCritSect */, "supSvcGrantCleanUpSessions", rc); + } +} + + +/** + * Waits for the service thread to respond to a state change. + * + * @returns VINF_SUCCESS on success, VERR_TIMEOUT if it doesn't respond in time, other error code on internal error. + * + * @param pThis Pointer to the grant service instance data. + * @param enmCurState The current state. + * @param enmNewState The new state we're waiting for it to enter. + */ +static int supSvcGrantWait(PSUPSVCGRANT pThis, SUPSVCGRANTSTATE enmCurState, SUPSVCGRANTSTATE enmNewState) +{ + LogFlow(("supSvcGrantWait(,%s,%s): enter\n", + supSvcGrantStateName(enmCurState), supSvcGrantStateName(enmNewState))); + + /* + * Wait a short while for the response event to be set. + */ + RTSemEventWait(pThis->hResponseEvent, 1000); + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + if (pThis->enmState == enmNewState) + { + RTCritSectLeave(&pThis->CritSect); + rc = VINF_SUCCESS; + } + else if (pThis->enmState == enmCurState) + { + /* + * Wait good while longer. + */ + RTCritSectLeave(&pThis->CritSect); + rc = RTSemEventWait(pThis->hResponseEvent, 59*1000); /* 59 sec */ + if (RT_SUCCESS(rc) || rc == VERR_TIMEOUT) + { + rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + /* + * Check the state whether we've succeeded. + */ + SUPSVCGRANTSTATE enmState = pThis->enmState; + if (enmState == enmNewState) + rc = VINF_SUCCESS; + else if (enmState == enmCurState) + { + supSvcLogError("supSvcGrantWait(,%s,%s) - the thread doesn't respond in a timely manner, failing.", + supSvcGrantStateName(enmCurState), supSvcGrantStateName(enmNewState)); + rc = VERR_TIMEOUT; + } + else + { + supSvcLogError("supSvcGrantWait(,%s,%s) - wrong state %s!", supSvcGrantStateName(enmCurState), + supSvcGrantStateName(enmNewState), supSvcGrantStateName(enmState)); + AssertMsgFailed(("%s\n", supSvcGrantStateName(enmState))); + rc = VERR_INTERNAL_ERROR; + } + + RTCritSectLeave(&pThis->CritSect); + } + else + supSvcLogError("supSvcGrantWait(,%s,%s) - RTCritSectEnter returns %Rrc", + supSvcGrantStateName(enmCurState), supSvcGrantStateName(enmNewState)); + } + else + supSvcLogError("supSvcGrantWait(,%s,%s) - RTSemEventWait returns %Rrc", + supSvcGrantStateName(enmCurState), supSvcGrantStateName(enmNewState)); + } + else + { + supSvcLogError("supSvcGrantWait(,%s,%s) - wrong state %s!", supSvcGrantStateName(enmCurState), + supSvcGrantStateName(enmNewState), supSvcGrantStateName(pThis->enmState)); + AssertMsgFailed(("%s\n", supSvcGrantStateName(pThis->enmState))); + RTCritSectLeave(&pThis->CritSect); + rc = VERR_INTERNAL_ERROR; + } + } + else + supSvcLogError("supSvcGrantWait(,%s,%s) - RTCritSectEnter returns %Rrc", + supSvcGrantStateName(enmCurState), supSvcGrantStateName(enmNewState)); + + Log(("supSvcGrantWait(,%s,%s): returns %Rrc\n", + supSvcGrantStateName(enmCurState), supSvcGrantStateName(enmNewState), rc)); + return rc; +} + + +/** @copydoc SUPSVCSERVICE::pfnCreate */ +DECLCALLBACK(int) supSvcGrantCreate(void **ppvInstance) +{ + LogFlowFuncEnter(); + + /* + * Allocate and initialize the session data. + */ + PSUPSVCGRANT pThis = (PSUPSVCGRANT)RTMemAlloc(sizeof(*pThis)); + if (!pThis) + { + supSvcLogError("supSvcGrantCreate - no memory"); + return VERR_NO_MEMORY; + } + bool fFreeIt = true; + pThis->pSessionHead = NULL; + pThis->enmState = kSupSvcGrantState_Creating; + int rc = RTCritSectInit(&pThis->VerifyCritSect); + if (RT_SUCCESS(rc)) + { + rc = RTCritSectInit(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + rc = RTSemEventCreate(&pThis->hResponseEvent); + if (RT_SUCCESS(rc)) + { + rc = RTSemEventCreate(&pThis->hSessionEvent); + if (RT_SUCCESS(rc)) + { + /* + * Create the local IPC instance and then finally fire up the thread. + */ + rc = RTLocalIpcServerCreate(&pThis->hServer, SUPSVC_GRANT_SERVICE_NAME, RTLOCALIPC_FLAGS_MULTI_SESSION); + if (RT_SUCCESS(rc)) + { + rc = RTThreadCreate(&pThis->hThread, supSvcGrantThread, pThis, 0, RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "GRANT"); + if (RT_SUCCESS(rc)) + { + rc = supSvcGrantWait(pThis, kSupSvcGrantState_Creating, kSupSvcGrantState_Paused); + if (RT_SUCCESS(rc)) + { + /* + * Successfully created the grant service! + */ + Log(("supSvcGrantCreate: returns VINF_SUCCESS (pThis=%p)\n", pThis)); + *ppvInstance = pThis; + return VINF_SUCCESS; + } + + /* + * The thread FAILED to start in a timely manner! + */ + RTCritSectEnter(&pThis->CritSect); + pThis->enmState = kSupSvcGrantState_Terminating; + RTCritSectLeave(&pThis->CritSect); + + RTThreadUserSignal(pThis->hThread); + + int cTries = 10; + int rc2 = RTThreadWait(pThis->hThread, 20000, NULL); + if (RT_FAILURE(rc2)) + { + /* poke it a few more times before giving up. */ + while (--cTries > 0) + { + RTThreadUserSignal(pThis->hThread); + RTLocalIpcServerCancel(pThis->hServer); + if (RTThreadWait(pThis->hThread, 1000, NULL) != VERR_TIMEOUT) + break; + } + } + fFreeIt = cTries <= 0; + } + else + supSvcLogError("supSvcGrantCreate - RTThreadCreate returns %Rrc", rc); + RTLocalIpcServerDestroy(pThis->hServer); + pThis->hServer = NIL_RTLOCALIPCSERVER; + } + else + supSvcLogError("supSvcGrantCreate - RTLocalIpcServiceCreate returns %Rrc", rc); + RTSemEventDestroy(pThis->hSessionEvent); + pThis->hSessionEvent = NIL_RTSEMEVENT; + } + else + supSvcLogError("supSvcGrantCreate - RTSemEventCreate returns %Rrc", rc); + RTSemEventDestroy(pThis->hResponseEvent); + pThis->hResponseEvent = NIL_RTSEMEVENT; + } + else + supSvcLogError("supSvcGrantCreate - RTSemEventCreate returns %Rrc", rc); + RTCritSectDelete(&pThis->CritSect); + } + else + supSvcLogError("supSvcGrantCreate - RTCritSectInit returns %Rrc", rc); + RTCritSectDelete(&pThis->VerifyCritSect); + } + else + supSvcLogError("supSvcGrantCreate - RTCritSectInit returns %Rrc", rc); + if (fFreeIt) + RTMemFree(pThis); + Log(("supSvcGrantCreate: returns %Rrc\n", rc)); + return rc; +} + + +/** @copydoc SUPSVCSERVICE::pfnStart */ +DECLCALLBACK(void) supSvcGrantStart(void *pvInstance) +{ + PSUPSVCGRANT pThis = (PSUPSVCGRANT)pvInstance; + + /* + * Change the state and signal the thread. + */ + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + bool fInCritSect = true; + SUPSVCGRANTSTATE enmState = pThis->enmState; + if (enmState == kSupSvcGrantState_Paused) + { + pThis->enmState = kSupSvcGrantState_Resuming; + rc = RTThreadUserSignal(pThis->hThread); + if (RT_SUCCESS(rc)) + { + /* + * Wait for the bugger to respond (no need to bitch here). + */ + RTCritSectLeave(&pThis->CritSect); + supSvcGrantWait(pThis, kSupSvcGrantState_Resuming, kSupSvcGrantState_Listen); + fInCritSect = false; + } + } + else + supSvcLogError("supSvcGrantStart - Incorrect state %s!", supSvcGrantStateName(enmState)); + if (fInCritSect) + RTCritSectLeave(&pThis->CritSect); + } + else + { + supSvcLogError("supSvcGrantStart - RTCritSectEnter returns %Rrc!", rc); + AssertRCReturnVoid(rc); + } +} + + +/** @copydoc SUPSVCSERVICE::pfnTryStop */ +DECLCALLBACK(int) supSvcGrantTryStop(void *pvInstance) +{ + PSUPSVCGRANT pThis = (PSUPSVCGRANT)pvInstance; + + /* + * Don't give up immediately. + */ + uint64_t u64StartTS = RTTimeMilliTS(); + int rc; + for (;;) + { + /* + * First check the state to make sure the thing is actually running. + * If the critsect is butchered, just pretend success. + */ + rc = RTCritSectEnter(&pThis->CritSect); + if (RT_FAILURE(rc)) + { + supSvcLogError("supSvcGrantTryStop - RTCritSectEnter returns %Rrc", rc); + AssertRC(rc); + return VINF_SUCCESS; + } + SUPSVCGRANTSTATE enmState = pThis->enmState; + if (enmState != kSupSvcGrantState_Listen) + { + supSvcLogError("supSvcGrantTryStop - Not running, state: %s", supSvcGrantStateName(enmState)); + RTCritSectLeave(&pThis->CritSect); + return VINF_SUCCESS; + } + + /* + * If there are no clients, usher the thread into the paused state. + */ + supSvcGrantCleanUpSessionsLocked(pThis); + if (!pThis->pSessionHead) + { + rc = RTThreadUserReset(pThis->hThread); + pThis->enmState = kSupSvcGrantState_Pausing; + int rc2 = RTLocalIpcServerCancel(pThis->hServer); + int rc3 = RTCritSectLeave(&pThis->CritSect); + if (RT_SUCCESS(rc) && RT_SUCCESS(rc2) && RT_SUCCESS(rc3)) + supSvcGrantWait(pThis, kSupSvcGrantState_Pausing, kSupSvcGrantState_Paused); + else + { + if (RT_FAILURE(rc)) + supSvcLogError("supSvcGrantTryStop - RTThreadUserReset returns %Rrc", rc); + if (RT_FAILURE(rc2)) + supSvcLogError("supSvcGrantTryStop - RTLocalIpcServerCancel returns %Rrc", rc); + if (RT_FAILURE(rc3)) + supSvcLogError("supSvcGrantTryStop - RTCritSectLeave returns %Rrc", rc); + } + return VINF_SUCCESS; + } + + /* + * Check the time limit, otherwise wait for a client event. + */ + uint64_t u64Elapsed = RTTimeMilliTS() - u64StartTS; + if (u64Elapsed >= 60*1000) /* 1 min */ + { + unsigned cSessions = 0; + for (PSUPSVCGRANTSESSION pCur = pThis->pSessionHead; pCur; pCur = pCur->pNext) + cSessions++; + RTCritSectLeave(&pThis->CritSect); + + supSvcLogError("supSvcGrantTryStop - %u active sessions after waiting %u ms", cSessions, (unsigned)u64Elapsed); + return VERR_TRY_AGAIN; + } + + rc = RTCritSectLeave(&pThis->CritSect); + if (RT_FAILURE(rc)) + { + supSvcLogError("supSvcGrantTryStop - RTCritSectLeave returns %Rrc", rc); + return VINF_SUCCESS; + } + + rc = RTSemEventWait(pThis->hSessionEvent, 60*1000 - u64Elapsed); + if (RT_FAILURE(rc) && rc != VERR_TIMEOUT) + { + supSvcLogError("supSvcGrantTryStop - RTSemEventWait returns %Rrc", rc); + return VINF_SUCCESS; + } + } +} + + +/** @copydoc SUPSVCSERVICE::pfnStopAndDestroy */ +DECLCALLBACK(void) supSvcGrantStopAndDestroy(void *pvInstance, bool fRunning) +{ + PSUPSVCGRANT pThis = (PSUPSVCGRANT)pvInstance; + int rc; + + /* + * Attempt to stop the service, cancelling blocked server and client calls. + */ + RTCritSectEnter(&pThis->CritSect); + + SUPSVCGRANTSTATE enmState = pThis->enmState; + AssertMsg(fRunning == (pThis->enmState == kSupSvcGrantState_Listen), + ("%RTbool %s\n", fRunning, supSvcGrantStateName(enmState))); + + if (enmState == kSupSvcGrantState_Listen) + { + RTThreadUserReset(pThis->hThread); + pThis->enmState = kSupSvcGrantState_Paused; + for (PSUPSVCGRANTSESSION pCur = pThis->pSessionHead; pCur; pCur = pCur->pNext) + ASMAtomicWriteBool(&pCur->fTerminate, true); + + /* try cancel local ipc operations that might be pending */ + RTLocalIpcServerCancel(pThis->hServer); + for (PSUPSVCGRANTSESSION pCur = pThis->pSessionHead; pCur; pCur = pCur->pNext) + { + RTLOCALIPCSESSION hSession; + ASMAtomicReadHandle(&pCur->hSession, &hSession); + if (hSession != NIL_RTLOCALIPCSESSION) + RTLocalIpcSessionCancel(hSession); + } + + /* + * Wait for the thread to respond (outside the crit sect). + */ + RTCritSectLeave(&pThis->CritSect); + supSvcGrantWait(pThis, kSupSvcGrantState_Pausing, kSupSvcGrantState_Paused); + RTCritSectEnter(&pThis->CritSect); + + /* + * Wait for any lingering sessions to exit. + */ + supSvcGrantCleanUpSessionsLocked(pThis); + if (pThis->pSessionHead) + { + uint64_t u64StartTS = RTTimeMilliTS(); + do + { + /* Destroy the sessions since cancelling didn't do the trick. */ + for (PSUPSVCGRANTSESSION pCur = pThis->pSessionHead; pCur; pCur = pCur->pNext) + { + RTLOCALIPCSESSION hSession; + ASMAtomicXchgHandle(&pCur->hSession, NIL_RTLOCALIPCSESSION, &hSession); + if (hSession != NIL_RTLOCALIPCSESSION) + { + rc = RTLocalIpcSessionClose(hSession); + AssertRC(rc); + if (RT_FAILURE(rc)) + supSvcLogError("supSvcGrantStopAndDestroy: RTLocalIpcSessionClose(%p) returns %Rrc", + (uintptr_t)hSession, rc); + } + } + + /* Check the time. */ + uint64_t u64Elapsed = RTTimeMilliTS() - u64StartTS; + if (u64Elapsed >= 60*1000) /* 1 min */ + break; + + /* wait */ + RTCritSectLeave(&pThis->CritSect); + rc = RTSemEventWait(pThis->hSessionEvent, 60*1000 - u64Elapsed); + RTCritSectEnter(&pThis->CritSect); + if (RT_FAILURE(rc) && rc != VERR_TIMEOUT) + break; + + /* cleanup and check again */ + supSvcGrantCleanUpSessionsLocked(pThis); + } while (pThis->pSessionHead); + } + } + + /* + * Tell the service thread to terminate and wait for it to do so. + */ + pThis->enmState = kSupSvcGrantState_Terminating; + RTLOCALIPCSERVER hServer; + ASMAtomicXchgHandle(&pThis->hServer, NIL_RTLOCALIPCSERVER, &hServer); + RTThreadUserSignal(pThis->hThread); + + RTCritSectLeave(&pThis->CritSect); + + rc = RTThreadWait(pThis->hThread, 20*1000, NULL); + if (RT_FAILURE(rc) && rc == VERR_TIMEOUT) + { + RTThreadUserSignal(pThis->hThread); + RTLocalIpcServerDestroy(hServer); + hServer = NIL_RTLOCALIPCSERVER; + + rc = RTThreadWait(pThis->hThread, 40*1000, NULL); + if (RT_FAILURE(rc)) + supSvcLogError("supSvcGrantStopAndDestroy - RTThreadWait(40 sec) returns %Rrc", rc); + } + else if (RT_FAILURE(rc)) + supSvcLogError("supSvcGrantStopAndDestroy - RTThreadWait(20 sec) returns %Rrc", rc); + pThis->hThread = NIL_RTTHREAD; + + /* + * Kill the parent pointers of any lingering sessions. + */ + RTCritSectEnter(&pThis->CritSect); + pThis->enmState = kSupSvcGrantState_Destroyed; + + supSvcGrantCleanUpSessionsLocked(pThis); + unsigned cSessions = 0; + for (PSUPSVCGRANTSESSION pCur = pThis->pSessionHead; pCur; pCur = pCur->pNext) + ASMAtomicWriteNullPtr(&pCur->pParent); + + RTCritSectLeave(&pThis->CritSect); + if (cSessions) + supSvcLogError("supSvcGrantStopAndDestroy: %d session failed to terminate!", cSessions); + + /* + * Free the resource. + */ + RTLocalIpcServerDestroy(hServer); + + RTSemEventDestroy(pThis->hResponseEvent); + pThis->hResponseEvent = NIL_RTSEMEVENT; + + RTSemEventDestroy(pThis->hSessionEvent); + pThis->hSessionEvent = NIL_RTSEMEVENT; + + RTCritSectDelete(&pThis->VerifyCritSect); + RTCritSectDelete(&pThis->CritSect); + + RTMemFree(pThis); + + Log(("supSvcGrantStopAndDestroy: done (rc=%Rrc)\n", rc)); +} + |