/** @file * * VirtualBox COM base classes implementation */ /* * Copyright (C) 2006 InnoTek Systemberatung GmbH * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file 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 2 as it comes in the "COPYING" file of the VirtualBox OSE * distribution. VirtualBox OSE is distributed in the hope that it will * be useful, but WITHOUT ANY WARRANTY of any kind. * * If you received this file as part of a commercial VirtualBox * distribution, then only the terms of your commercial VirtualBox * license agreement apply instead of the previous paragraph. */ #if defined (__WIN__) #include #include #else // !defined (__WIN__) #include #include #endif #include "VirtualBoxBase.h" #include "VirtualBoxErrorInfoImpl.h" #include "Logging.h" #include // VirtualBoxBaseNEXT_base methods //////////////////////////////////////////////////////////////////////////////// VirtualBoxBaseNEXT_base::VirtualBoxBaseNEXT_base() { mState = NotReady; mStateChangeThread = NIL_RTTHREAD; mCallers = 0; mZeroCallersSem = NIL_RTSEMEVENT; mInitDoneSem = NIL_RTSEMEVENTMULTI; mInitDoneSemUsers = 0; RTCritSectInit (&mStateLock); mObjectLock = NULL; } VirtualBoxBaseNEXT_base::~VirtualBoxBaseNEXT_base() { if (mObjectLock) delete mObjectLock; RTCritSectDelete (&mStateLock); Assert (mInitDoneSemUsers == 0); Assert (mInitDoneSem == NIL_RTSEMEVENTMULTI); if (mZeroCallersSem != NIL_RTSEMEVENT) RTSemEventDestroy (mZeroCallersSem); mCallers = 0; mStateChangeThread = NIL_RTTHREAD; mState = NotReady; } // AutoLock::Lockable interface AutoLock::Handle *VirtualBoxBaseNEXT_base::lockHandle() const { /* lasy initialization */ if (!mObjectLock) mObjectLock = new AutoLock::Handle; return mObjectLock; } /** * Increments the number of calls to this object by one. * * After this method succeeds, it is guaranted that the object will remain in * the Ready (or in the Limited) state at least until #releaseCaller() is * called. * * This method is intended to mark the beginning of sections of code within * methods of COM objects that depend on the readiness (Ready) state. The * Ready state is a primary "ready to serve" state. Usually all code that * works with component's data depends on it. On practice, this means that * almost every public method, setter or getter of the object should add * itself as an object's caller at the very beginning, to protect from an * unexpected uninitialization that may happen on a different thread. * * Besides the Ready state denoting that the object is fully functional, * there is a special Limited state. The Limited state means that the object * is still functional, but its functionality is limited to some degree, so * not all operations are possible. The @a aLimited argument to this method * determines whether the caller represents this limited functionality or not. * * This method succeeeds (and increments the number of callers) only if the * current object's state is Ready. Otherwise, it will return E_UNEXPECTED to * indicate that the object is not operational. There are two exceptions from * this rule: *
    *
  1. If the @a aLimited argument is |true|, then this method will also * succeeed if the object's state is Limited (or Ready, of course).
  2. *
  3. If this method is called from the same thread that placed the object * to InInit or InUninit state (i.e. either from within the AutoInitSpan * or AutoUninitSpan scope), it will succeed as well (but will not * increase the number of callers).
  4. *
* * Normally, calling addCaller() never blocks. However, if this method is * called by a thread created from within the AutoInitSpan scope and this * scope is still active (i.e. the object state is InInit), it will block * until the AutoInitSpan destructor signals that it has finished * initialization. * * When this method returns a failure, the caller must not use the object * and can return the failed result code to his caller. * * @param aState where to store the current object's state * (can be used in overriden methods to determine the * cause of the failure) * @param aLimited |true| to add a limited caller. * @return S_OK on success or E_UNEXPECTED on failure * * @note It is preferrable to use the #addLimitedCaller() rather than calling * this method with @a aLimited = |true|, for better * self-descriptiveness. * * @sa #addLimitedCaller() * @sa #releaseCaller() */ HRESULT VirtualBoxBaseNEXT_base::addCaller (State *aState /* = NULL */, bool aLimited /* = false */) { AutoLock stateLock (mStateLock); HRESULT rc = E_UNEXPECTED; if (mState == Ready || (aLimited && mState == Limited)) { /* if Ready or allows Limited, increase the number of callers */ ++ mCallers; rc = S_OK; } else if ((mState == InInit || mState == InUninit)) { if (mStateChangeThread == RTThreadSelf()) { /* * Called from the same thread that is doing AutoInitSpan or * AutoUninitSpan, just succeed */ rc = S_OK; } else if (mState == InInit) { /* addCaller() is called by a "child" thread while the "parent" * thread is still doing AutoInitSpan/AutoReadySpan. Wait for the * state to become either Ready/Limited or InitFailed/InInit/NotReady * (in case of init failure). Note that we increase the number of * callers anyway to prevent AutoUninitSpan from early completion. */ ++ mCallers; /* lazy creation */ if (mInitDoneSem == NIL_RTSEMEVENTMULTI) RTSemEventMultiCreate (&mInitDoneSem); ++ mInitDoneSemUsers; LogFlowThisFunc (("Waiting for AutoInitSpan/AutoReadySpan to finish...\n")); stateLock.leave(); RTSemEventMultiWait (mInitDoneSem, RT_INDEFINITE_WAIT); stateLock.enter(); if (-- mInitDoneSemUsers == 0) { /* destroy the semaphore since no more necessary */ RTSemEventMultiDestroy (mInitDoneSem); mInitDoneSem = NIL_RTSEMEVENTMULTI; } if (mState == Ready) rc = S_OK; else { AssertMsg (mCallers != 0, ("mCallers is ZERO!")); -- mCallers; if (mCallers == 0 && mState == InUninit) { /* inform AutoUninitSpan ctor there are no more callers */ RTSemEventSignal (mZeroCallersSem); } } } } if (aState) *aState = mState; return rc; } /** * Decrements the number of calls to this object by one. * Must be called after every #addCaller() or #addLimitedCaller() when the * object is no more necessary. */ void VirtualBoxBaseNEXT_base::releaseCaller() { AutoLock stateLock (mStateLock); if (mState == Ready || mState == Limited) { /* if Ready or Limited, decrease the number of callers */ AssertMsgReturn (mCallers != 0, ("mCallers is ZERO!"), (void) 0); -- mCallers; return; } if ((mState == InInit || mState == InUninit)) { if (mStateChangeThread == RTThreadSelf()) { /* * Called from the same thread that is doing AutoInitSpan or * AutoUninitSpan, just succeed */ return; } if (mState == InUninit) { /* the caller is being released after AutoUninitSpan has begun */ AssertMsgReturn (mCallers != 0, ("mCallers is ZERO!"), (void) 0); -- mCallers; if (mCallers == 0) { /* inform the AutoUninitSpan ctor there are no more callers */ RTSemEventSignal (mZeroCallersSem); } return; } } AssertMsgFailed (("mState = %d!", mState)); } // VirtualBoxBaseNEXT_base::AutoInitSpan methods //////////////////////////////////////////////////////////////////////////////// /** * Creates a smart initialization span object and places the object to * InInit state. * * @param aObj |this| pointer of the managed VirtualBoxBase object whose * init() method is being called * @param aStatus initial initialization status for this span */ VirtualBoxBaseNEXT_base::AutoInitSpan:: AutoInitSpan (VirtualBoxBaseNEXT_base *aObj, Status aStatus /* = Failed */) : mObj (aObj), mStatus (aStatus), mOk (false) { Assert (aObj); AutoLock stateLock (mObj->mStateLock); Assert (mObj->mState != InInit && mObj->mState != InUninit && mObj->mState != InitFailed); mOk = mObj->mState == NotReady; if (!mOk) return; mObj->setState (InInit); } /** * Places the managed VirtualBoxBase object to Ready/Limited state if the * initialization succeeded or partly succeeded, or places it to InitFailed * state and calls the object's uninit() method otherwise. */ VirtualBoxBaseNEXT_base::AutoInitSpan::~AutoInitSpan() { /* if the state was other than NotReady, do nothing */ if (!mOk) return; AutoLock stateLock (mObj->mStateLock); Assert (mObj->mState == InInit); if (mObj->mCallers > 0) { Assert (mObj->mInitDoneSemUsers > 0); /* We have some pending addCaller() calls on other threads (created * during InInit), signal that InInit is finished. */ RTSemEventMultiSignal (mObj->mInitDoneSem); } if (mStatus == Succeeded) { mObj->setState (Ready); } else if (mStatus == Limited) { mObj->setState (VirtualBoxBaseNEXT_base::Limited); } else { mObj->setState (InitFailed); /* leave the lock to prevent nesting when uninit() is called */ stateLock.leave(); /* call uninit() to let the object uninit itself after failed init() */ mObj->uninit(); /* Note: the object may no longer exist here (for example, it can call * the destructor in uninit()) */ } } // VirtualBoxBaseNEXT_base::AutoReadySpan methods //////////////////////////////////////////////////////////////////////////////// /** * Creates a smart re-initialization span object and places the object to * InInit state. * * @param aObj |this| pointer of the managed VirtualBoxBase object whose * re-initialization method is being called */ VirtualBoxBaseNEXT_base::AutoReadySpan:: AutoReadySpan (VirtualBoxBaseNEXT_base *aObj) : mObj (aObj), mSucceeded (false), mOk (false) { Assert (aObj); AutoLock stateLock (mObj->mStateLock); Assert (mObj->mState != InInit && mObj->mState != InUninit && mObj->mState != InitFailed); mOk = mObj->mState == Limited; if (!mOk) return; mObj->setState (InInit); } /** * Places the managed VirtualBoxBase object to Ready state if the * re-initialization succeeded (i.e. #setSucceeded() has been called) or * back to Limited state otherwise. */ VirtualBoxBaseNEXT_base::AutoReadySpan::~AutoReadySpan() { /* if the state was other than Limited, do nothing */ if (!mOk) return; AutoLock stateLock (mObj->mStateLock); Assert (mObj->mState == InInit); if (mObj->mCallers > 0 && mObj->mInitDoneSemUsers > 0) { /* We have some pending addCaller() calls on other threads, * signal that InInit is finished. */ RTSemEventMultiSignal (mObj->mInitDoneSem); } if (mSucceeded) { mObj->setState (Ready); } else { mObj->setState (Limited); } } // VirtualBoxBaseNEXT_base::AutoUninitSpan methods //////////////////////////////////////////////////////////////////////////////// /** * Creates a smart uninitialization span object and places this object to * InUninit state. * * @note This method blocks the current thread execution until the number of * callers of the managed VirtualBoxBase object drops to zero! * * @param aObj |this| pointer of the VirtualBoxBase object whose uninit() * method is being called */ VirtualBoxBaseNEXT_base::AutoUninitSpan::AutoUninitSpan (VirtualBoxBaseNEXT_base *aObj) : mObj (aObj), mInitFailed (false), mUninitDone (false) { Assert (aObj); AutoLock stateLock (mObj->mStateLock); Assert (mObj->mState != InInit); /* * Set mUninitDone to |true| if this object is already uninitialized * (NotReady) or if another AutoUninitSpan is currently active on some * other thread (InUninit). */ mUninitDone = mObj->mState == NotReady || mObj->mState == InUninit; if (mObj->mState == InitFailed) { /* we've been called by init() on failure */ mInitFailed = true; } else { /* do nothing if already uninitialized */ if (mUninitDone) return; } /* go to InUninit to prevent from adding new callers */ mObj->setState (InUninit); if (mObj->mCallers > 0) { /* lazy creation */ Assert (mObj->mZeroCallersSem == NIL_RTSEMEVENT); RTSemEventCreate (&mObj->mZeroCallersSem); /* wait until remaining callers release the object */ LogFlowThisFunc (("Waiting for callers (%d) to drop to zero...\n", mObj->mCallers)); stateLock.leave(); RTSemEventWait (mObj->mZeroCallersSem, RT_INDEFINITE_WAIT); } } /** * Places the managed VirtualBoxBase object to the NotReady state. */ VirtualBoxBaseNEXT_base::AutoUninitSpan::~AutoUninitSpan() { /* do nothing if already uninitialized */ if (mUninitDone) return; AutoLock stateLock (mObj->mStateLock); Assert (mObj->mState == InUninit); mObj->setState (NotReady); } // VirtualBoxBase methods //////////////////////////////////////////////////////////////////////////////// /** * Translates the given text string according to the currently installed * translation table and current context. The current context is determined * by the context parameter. Additionally, a comment to the source text * string text can be given. This comment (which is NULL by default) * is helpful in sutuations where it is necessary to distinguish between * two or more semantically different roles of the same source text in the * same context. * * @param context the context of the the translation (can be NULL * to indicate the global context) * @param sourceText the string to translate * @param comment the comment to the string (NULL means no comment) * * @return * the translated version of the source string in UTF-8 encoding, * or the source string itself if the translation is not found * in the given context. */ // static const char *VirtualBoxBase::translate (const char *context, const char *sourceText, const char *comment) { // Log(("VirtualBoxBase::translate:\n" // " context={%s}\n" // " sourceT={%s}\n" // " comment={%s}\n", // context, sourceText, comment)); /// @todo (dmik) incorporate Qt translation file parsing and lookup return sourceText; } /// @todo (dmik) // Using StackWalk() is not necessary here once we have ASMReturnAddress(). // Delete later. #if defined(DEBUG) && 0 //static void VirtualBoxBase::AutoLock::CritSectEnter (RTCRITSECT *aLock) { AssertReturn (aLock, (void) 0); #if defined(__LINUX__) && defined(__GNUC__) RTCritSectEnterDebug (aLock, "AutoLock::lock()/enter() return address >>>", 0, (RTUINTPTR) __builtin_return_address (1)); #elif defined(__WIN__) STACKFRAME sf; memset (&sf, 0, sizeof(sf)); { __asm eip: __asm mov eax, eip __asm lea ebx, sf __asm mov [ebx]sf.AddrPC.Offset, eax __asm mov [ebx]sf.AddrStack.Offset, esp __asm mov [ebx]sf.AddrFrame.Offset, ebp } sf.AddrPC.Mode = AddrModeFlat; sf.AddrStack.Mode = AddrModeFlat; sf.AddrFrame.Mode = AddrModeFlat; HANDLE process = GetCurrentProcess(); HANDLE thread = GetCurrentThread(); // get our stack frame BOOL ok = StackWalk (IMAGE_FILE_MACHINE_I386, process, thread, &sf, NULL, NULL, SymFunctionTableAccess, SymGetModuleBase, NULL); // sanity check of the returned stack frame ok = ok & (sf.AddrFrame.Offset != 0); if (ok) { // get the stack frame of our caller which is either // lock() or enter() ok = StackWalk (IMAGE_FILE_MACHINE_I386, process, thread, &sf, NULL, NULL, SymFunctionTableAccess, SymGetModuleBase, NULL); // sanity check of the returned stack frame ok = ok & (sf.AddrFrame.Offset != 0); } if (ok) { // the return address here should be the code where lock() or enter() // has been called from (to be more precise, where it will return) RTCritSectEnterDebug (aLock, "AutoLock::lock()/enter() return address >>>", 0, (RTUINTPTR) sf.AddrReturn.Offset); } else { RTCritSectEnter (aLock); } #else RTCritSectEnter (aLock); #endif // defined(__LINUX__) } #endif // defined(DEBUG) // VirtualBoxSupportTranslationBase methods //////////////////////////////////////////////////////////////////////////////// /** * Modifies the given argument so that it will contain only a class name * (null-terminated). The argument must point to a non-constant * string containing a valid value, as it is generated by the * __PRETTY_FUNCTION__ built-in macro of the GCC compiler, or by the * __FUNCTION__ macro of any other compiler. * * The function assumes that the macro is used within the member of the * class derived from the VirtualBoxSupportTranslation<> template. * * @param prettyFunctionName string to modify * @return * true on success and false otherwise */ bool VirtualBoxSupportTranslationBase::cutClassNameFrom__PRETTY_FUNCTION__ (char *fn) { Assert (fn); if (!fn) return false; #if defined (__GNUC__) // the format is like: // VirtualBoxSupportTranslation::VirtualBoxSupportTranslation() [with C = VirtualBox] #define START " = " #define END "]" #elif defined (_MSC_VER) // the format is like: // VirtualBoxSupportTranslation::__ctor #define START "::" #endif char *start = strstr (fn, START); Assert (start); if (start) { start += sizeof (START) - 1; char *end = strstr (start, END); Assert (end && (end > start)); if (end && (end > start)) { size_t len = end - start; memmove (fn, start, len); fn [len] = 0; return true; } } #undef END #undef START return false; } // VirtualBoxSupportErrorInfoImplBase methods //////////////////////////////////////////////////////////////////////////////// // static HRESULT VirtualBoxSupportErrorInfoImplBase::setError ( HRESULT resultCode, const GUID &iid, const Bstr &component, const Bstr &text) { LogRel (("ERROR [COM]: rc=%#08x IID={%Vuuid} component={%ls} text={%ls}\n", resultCode, &iid, component.raw(), text.raw())); /* these are mandatory, others -- not */ Assert (FAILED (resultCode)); Assert (!text.isEmpty()); if (SUCCEEDED (resultCode) || text.isEmpty()) return E_FAIL; ComObjPtr info; HRESULT rc = info.createObject(); if (SUCCEEDED (rc)) { info->init (resultCode, iid, component, text); #if defined (__WIN__) ComPtr err; rc = info.queryInterfaceTo (err.asOutParam()); if (SUCCEEDED (rc)) rc = ::SetErrorInfo (0, err); #else // !defined (__WIN__) nsCOMPtr es; es = do_GetService (NS_EXCEPTIONSERVICE_CONTRACTID, &rc); if (NS_SUCCEEDED (rc)) { nsCOMPtr em; rc = es->GetCurrentExceptionManager (getter_AddRefs (em)); if (NS_SUCCEEDED (rc)) { ComPtr ex; rc = info.queryInterfaceTo (ex.asOutParam()); if (SUCCEEDED (rc)) rc = em->SetCurrentException (ex); } } else if (rc == NS_ERROR_UNEXPECTED) { /* * It is possible that setError() is being called by the object * after the XPCOM shutdown sequence has been initiated * (for example, when XPCOM releases all instances it internally * references, which can cause object's FinalConstruct() and then * uninit()). In this case, do_GetService() above will return * NS_ERROR_UNEXPECTED and it doesn't actually make sense to * set the exception (nobody will be able to read it). */ LogWarningFunc (("Will not set an exception because " "nsIExceptionService is not available " "(NS_ERROR_UNEXPECTED). " "XPCOM is being shutdown?\n")); rc = NS_OK; } #endif // !defined (__WIN__) } AssertComRC (rc); return SUCCEEDED (rc) ? resultCode : rc; } // static HRESULT VirtualBoxSupportErrorInfoImplBase::setError ( HRESULT resultCode, const GUID &iid, const Bstr &component, const char *text, va_list args) { return VirtualBoxSupportErrorInfoImplBase::setError ( resultCode, iid, component, Utf8StrFmt (text, args)); } // VirtualBoxBaseWithChildren methods //////////////////////////////////////////////////////////////////////////////// /** * Uninitializes all dependent children registered with #addDependentChild(). * * @note * This method will call uninit() methods of children. If these methods * access the parent object, uninitDependentChildren() must be called * either at the beginning of the parent uninitialization sequence (when * it is still operational) or after setReady(false) is called to * indicate the parent is out of action. */ void VirtualBoxBaseWithChildren::uninitDependentChildren() { /// @todo (r=dmik) see todo in VirtualBoxBase.h, in // template void removeDependentChild (C *child) LogFlowThisFuncEnter(); AutoLock alock (this); AutoLock mapLock (mMapLock); LogFlowThisFunc (("count=%d...\n", mDependentChildren.size())); if (mDependentChildren.size()) { /* We keep the lock until we have enumerated all children. * Those ones that will try to call #removeDependentChild() from * a different thread will have to wait */ Assert (mUninitDoneSem == NIL_RTSEMEVENT); int vrc = RTSemEventCreate (&mUninitDoneSem); AssertRC (vrc); Assert (mChildrenLeft == 0); mChildrenLeft = mDependentChildren.size(); for (DependentChildren::iterator it = mDependentChildren.begin(); it != mDependentChildren.end(); ++ it) { VirtualBoxBase *child = (*it).second; Assert (child); if (child) child->uninit(); } mDependentChildren.clear(); } /* Wait until all children started uninitializing on their own * (and therefore are waiting for some parent's method or for * #removeDependentChild() to return) are finished uninitialization */ if (mUninitDoneSem != NIL_RTSEMEVENT) { /* let stuck children run */ mapLock.leave(); alock.leave(); LogFlowThisFunc (("Waiting for uninitialization of all children...\n")); RTSemEventWait (mUninitDoneSem, RT_INDEFINITE_WAIT); alock.enter(); mapLock.enter(); RTSemEventDestroy (mUninitDoneSem); mUninitDoneSem = NIL_RTSEMEVENT; Assert (mChildrenLeft == 0); } LogFlowThisFuncLeave(); } /** * Returns a pointer to the dependent child corresponding to the given * interface pointer (used as a key in the map) or NULL if the interface * pointer doesn't correspond to any child registered using * #addDependentChild(). * * @param unk * Pointer to map to the dependent child object (it is ComPtr * rather than IUnknown *, to guarantee IUnknown * identity) * @return * Pointer to the dependent child object */ VirtualBoxBase *VirtualBoxBaseWithChildren::getDependentChild ( const ComPtr &unk) { AssertReturn (!!unk, NULL); AutoLock alock (mMapLock); if (mUninitDoneSem != NIL_RTSEMEVENT) return NULL; DependentChildren::const_iterator it = mDependentChildren.find (unk); if (it == mDependentChildren.end()) return NULL; return (*it).second; } /** Helper for addDependentChild() template method */ void VirtualBoxBaseWithChildren::addDependentChild ( const ComPtr &unk, VirtualBoxBase *child) { AssertReturn (!!unk && child, (void) 0); AutoLock alock (mMapLock); if (mUninitDoneSem != NIL_RTSEMEVENT) { // for this very unlikely case, we have to increase the number of // children left, for symmetry with #removeDependentChild() ++ mChildrenLeft; return; } std::pair result = mDependentChildren.insert (DependentChildren::value_type (unk, child)); AssertMsg (result.second, ("Failed to insert a child to the map\n")); } /** Helper for removeDependentChild() template method */ void VirtualBoxBaseWithChildren::removeDependentChild (const ComPtr &unk) { /// @todo (r=dmik) see todo in VirtualBoxBase.h, in // template void removeDependentChild (C *child) AssertReturn (!!unk, (void) 0); AutoLock alock (mMapLock); if (mUninitDoneSem != NIL_RTSEMEVENT) { // uninitDependentChildren() is in action, just increase the number // of children left and signal a semaphore when it reaches zero Assert (mChildrenLeft != 0); -- mChildrenLeft; if (mChildrenLeft == 0) { int vrc = RTSemEventSignal (mUninitDoneSem); AssertRC (vrc); } return; } DependentChildren::size_type result = mDependentChildren.erase (unk); AssertMsg (result == 1, ("Failed to remove a child from the map\n")); NOREF (result); }