SessionImpl.cpp revision 48cd4124bc518862316dd8e67e159e237c7625ad
/** @file
*
* VBox Client Session COM Class implementation
*/
/*
* Copyright (C) 2006-2007 Sun Microsystems, Inc.
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* General Public License (GPL) 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.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
* Clara, CA 95054 USA or visit http://www.sun.com if you need
* additional information or have any questions.
*/
#if defined(RT_OS_WINDOWS)
#elif defined(RT_OS_LINUX)
#endif
# include <errno.h>
#endif
#include "SessionImpl.h"
#include "ConsoleImpl.h"
#include "Logging.h"
#if defined(RT_OS_WINDOWS) || defined (RT_OS_OS2)
/** VM IPC mutex holder thread */
#endif
/**
* Local macro to check whether the session is open and return an error if not.
* @note Don't forget to do |Auto[Reader]Lock alock (this);| before using this
* macro.
*/
#define CHECK_OPEN() \
do { \
if (mState != SessionState_Open) \
return setError (E_UNEXPECTED, \
tr ("The session is not open")); \
} while (0)
// constructor / destructor
/////////////////////////////////////////////////////////////////////////////
{
LogFlowThisFunc(("\n"));
return init();
}
void Session::FinalRelease()
{
LogFlowThisFunc(("\n"));
uninit (true /* aFinalRelease */);
}
// public initializer/uninitializer for internal purposes only
/////////////////////////////////////////////////////////////////////////////
/**
* Initializes the Session object.
*/
{
/* Enclose the state transition NotReady->InInit->Ready */
AutoInitSpan autoInitSpan(this);
#if defined(RT_OS_WINDOWS)
#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
mIPCSem = -1;
#else
# error "Port me!"
#endif
/* Confirm a successful initialization when it's the case */
return S_OK;
}
/**
* Uninitializes the Session object.
*
* @note Locks this object for writing.
*/
{
/* Enclose the state transition Ready->InUninit->NotReady */
AutoUninitSpan autoUninitSpan(this);
if (autoUninitSpan.uninitDone())
{
LogFlowThisFunc(("Already uninitialized.\n"));
return;
}
/* close() needs write lock */
AutoWriteLock alock(this);
if (mState != SessionState_Closed)
{
AssertComRC (rc);
}
}
// ISession properties
/////////////////////////////////////////////////////////////////////////////
{
AutoCaller autoCaller(this);
AutoReadLock alock(this);
return S_OK;
}
{
AutoCaller autoCaller(this);
AutoReadLock alock(this);
CHECK_OPEN();
return S_OK;
}
{
AutoCaller autoCaller(this);
AutoReadLock alock(this);
CHECK_OPEN();
if (mConsole)
else
ComAssertComRC (rc);
return rc;
}
{
AutoCaller autoCaller(this);
AutoReadLock alock(this);
CHECK_OPEN();
if (mConsole)
else
ComAssertComRC (rc);
return rc;
}
// ISession methods
/////////////////////////////////////////////////////////////////////////////
{
AutoCaller autoCaller(this);
/* close() needs write lock */
AutoWriteLock alock(this);
CHECK_OPEN();
}
// IInternalSessionControl methods
/////////////////////////////////////////////////////////////////////////////
{
AutoCaller autoCaller(this);
AutoReadLock alock(this);
return S_OK;
}
{
AutoCaller autoCaller(this);
AutoReadLock alock(this);
("This is not a direct session!\n"), VBOX_E_INVALID_OBJECT_STATE);
/* return a failure if the session already transitioned to Closing
* but the server hasn't processed Machine::OnSessionEnd() yet. */
if (mState != SessionState_Open)
return VBOX_E_INVALID_VM_STATE;
return S_OK;
}
{
AutoCaller autoCaller(this);
AutoWriteLock alock(this);
if (!aMachine)
{
/*
* A special case: the server informs us that this session has been
* passed to IVirtualBox::OpenRemoteSession() so this session will
* become remote (but not existing) when AssignRemoteMachine() is
* called.
*/
return S_OK;
}
/* query IInternalMachineControl interface */
rc = grabIPCSemaphore();
/*
* Reference the VirtualBox object to ensure the server is up
* until the session is closed
*/
{
}
else
{
/* some cleanup */
}
return rc;
}
{
AutoCaller autoCaller(this);
AutoWriteLock alock(this);
/* query IInternalMachineControl interface */
/// @todo (dmik)
// currently, the remote session returns the same machine and
// console objects as the direct session, thus giving the
// (remote) client full control over the direct session. For the
// console, it is the desired behavior (the ability to control
// VM execution is a must for the remote session). What about
// the machine object, we may want to prevent the remote client
// from modifying machine data. In this case, we must:
// 1) assign the Machine object (instead of the SessionMachine
// object that is passed to this method) to mRemoteMachine;
// 2) remove GetMachine() property from the IConsole interface
// because it always returns the SessionMachine object
// (alternatively, we can supply a separate IConsole
// implementation that will return the Machine object in
// response to GetMachine()).
/*
* Reference the VirtualBox object to ensure the server is up
* until the session is closed
*/
{
/*
* RemoteSession type can be already set by AssignMachine() when its
* argument is NULL (a special case)
*/
if (mType != SessionType_Remote)
else
}
else
{
/* some cleanup */
}
return rc;
}
{
AutoCaller autoCaller(this);
{
/*
* We might have already entered Session::uninit() at this point, so
* return silently (not interested in the state change during uninit)
*/
LogFlowThisFunc(("Already uninitialized.\n"));
return S_OK;
}
AutoReadLock alock(this);
if (mState == SessionState_Closing)
{
LogFlowThisFunc(("Already being closed.\n"));
return S_OK;
}
}
{
AutoCaller autoCaller(this);
{
/* close() needs write lock */
AutoWriteLock alock(this);
if (mState == SessionState_Closing)
{
LogFlowThisFunc(("Already being closed.\n"));
return S_OK;
}
/* close ourselves */
}
{
/*
* We might have already entered Session::uninit() at this point,
* return silently
*/
LogFlowThisFunc(("Already uninitialized.\n"));
}
else
{
LogWarningThisFunc(("UNEXPECTED uninitialization!\n"));
}
return rc;
}
{
LogFlowThisFunc(("\n"));
AutoCaller autoCaller(this);
AutoReadLock alock(this);
return mConsole->onDVDDriveChange();
}
{
LogFlowThisFunc(("\n"));
AutoCaller autoCaller(this);
AutoReadLock alock(this);
return mConsole->onFloppyDriveChange();
}
{
LogFlowThisFunc(("\n"));
AutoCaller autoCaller(this);
AutoReadLock alock(this);
}
{
LogFlowThisFunc(("\n"));
AutoCaller autoCaller(this);
AutoReadLock alock(this);
}
{
LogFlowThisFunc(("\n"));
AutoCaller autoCaller(this);
AutoReadLock alock(this);
}
{
LogFlowThisFunc(("\n"));
AutoCaller autoCaller(this);
AutoReadLock alock(this);
return mConsole->onStorageControllerChange();
}
{
LogFlowThisFunc(("\n"));
AutoCaller autoCaller(this);
AutoReadLock alock(this);
return mConsole->onVRDPServerChange();
}
{
LogFlowThisFunc(("\n"));
AutoCaller autoCaller(this);
AutoReadLock alock(this);
return mConsole->onUSBControllerChange();
}
{
LogFlowThisFunc(("\n"));
AutoCaller autoCaller(this);
AutoReadLock alock(this);
}
{
LogFlowThisFunc(("\n"));
AutoCaller autoCaller(this);
AutoReadLock alock(this);
}
{
LogFlowThisFunc(("\n"));
AutoCaller autoCaller(this);
AutoReadLock alock(this);
}
{
AutoCaller autoCaller(this);
AutoReadLock alock(this);
if (mState != SessionState_Open)
{
/* the call from Machine issued when the session is open can arrive
* after the session starts closing or gets closed. Note that when
* aCheck is false, we return E_FAIL to indicate that aWinId we return
* is not valid */
*aWinId = 0;
}
}
{
#ifdef VBOX_WITH_GUEST_PROPS
AutoCaller autoCaller(this);
if (mState != SessionState_Open)
return setError (VBOX_E_INVALID_VM_STATE,
tr ("Machine session is not open (session state: %d)."),
mState);
return E_POINTER;
return E_POINTER;
return E_POINTER;
/* aValue can be NULL for a setter call if the property is to be deleted. */
return E_INVALIDARG;
/* aFlags can be null if it is to be left as is */
return E_INVALIDARG;
if (!aIsSetter)
else
#else /* VBOX_WITH_GUEST_PROPS not defined */
#endif /* VBOX_WITH_GUEST_PROPS not defined */
}
{
#ifdef VBOX_WITH_GUEST_PROPS
AutoCaller autoCaller(this);
if (mState != SessionState_Open)
return setError (VBOX_E_INVALID_VM_STATE,
tr ("Machine session is not open (session state: %d)."),
mState);
return E_POINTER;
if (ComSafeArrayOutIsNull(aNames))
return E_POINTER;
if (ComSafeArrayOutIsNull(aValues))
return E_POINTER;
return E_POINTER;
if (ComSafeArrayOutIsNull(aFlags))
return E_POINTER;
#else /* VBOX_WITH_GUEST_PROPS not defined */
#endif /* VBOX_WITH_GUEST_PROPS not defined */
}
// private methods
///////////////////////////////////////////////////////////////////////////////
/**
* Closes the current session.
*
* @param aFinalRelease called as a result of FinalRelease()
* @param aFromServer called as a result of Uninitialize()
*
* @note To be called only from #uninit(), #Close() or #Uninitialize().
* @note Locks this object for writing.
*/
{
LogFlowThisFunc(("aFinalRelease=%d, isFromServer=%d\n",
AutoCaller autoCaller(this);
AutoWriteLock alock(this);
if (mState != SessionState_Open)
{
/* The session object is going to be uninitialized before it has been
* assigned a direct console of the machine the client requested to open
* a remote session to using IVirtualBox:: openRemoteSession(). It is OK
* only if this close reqiest comes from the server (for example, it
* detected that the VM process it started terminated before opening a
* direct session). Otherwise, it means that the client is too fast and
* trying to close the session before waiting for the progress object it
* got from IVirtualBox:: openRemoteSession() to complete, so assert. */
#if defined(RT_OS_WINDOWS)
#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
#else
# error "Port me!"
#endif
return S_OK;
}
/* go to the closing state */
if (mType == SessionType_Direct)
{
}
else
{
}
if (!aFinalRelease && !aFromServer)
{
/*
* We trigger OnSessionEnd() only when the session closes itself using
* Close(). Note that if isFinalRelease = TRUE here, this means that
* the client process has already initialized the termination procedure
* without issuing Close() and the IPC channel is no more operational --
* so we cannot call the server's method (it will definitely fail). The
* server will instead simply detect the abnormal client death (since
* OnSessionEnd() is not called) and reset the machine state to Aborted.
*/
/*
* while waiting for OnSessionEnd() to complete one of our methods
* can be called by the server (for example, Uninitialize(), if the
* direct session has initiated a closure just a bit before us) so
* we need to release the lock to avoid deadlocks. The state is already
* SessionState_Closing here, so it's safe.
*/
LogFlowThisFunc(("Calling mControl->OnSessionEnd()...\n"));
/*
* If we get E_UNEXPECTED this means that the direct session has already
* been closed, we're just too late with our notification and nothing more
*/
AssertComRC (rc);
}
if (mType == SessionType_Direct)
{
if (!aFinalRelease && !aFromServer)
{
/*
* Wait for the server to grab the semaphore and destroy the session
* machine (allowing us to open a new session with the same machine
* once this method returns)
*/
if (progress)
}
}
/* release the VirtualBox instance as the very last step */
return S_OK;
}
/** @note To be called only from #AssignMachine() */
{
/* open the IPC semaphore based on the sessionId and try to grab it */
#if defined(RT_OS_WINDOWS)
/*
* Since Session is an MTA object, this method can be executed on
* any thread, and this thread will not necessarily match the thread on
* which close() will be called later. Therefore, we need a separate
* thread to hold the IPC mutex and then release it in close().
*/
("Cannot create an event sem, err=%d", ::GetLastError()),
E_FAIL);
void *data [3];
/* create a thread to hold the IPC mutex until signalled to release it */
0, RTTHREADTYPE_MAIN_WORKER, 0, "IPCHolder");
/* wait until thread init is completed */
{
/* memorize the event sem we should signal in close() */
}
else
{
::CloseHandle (mIPCThreadSem);
}
/* We use XPCOM where any message (including close()) can arrive on any
* worker thread (which will not necessarily match this thread that opens
* the mutex). Therefore, we need a separate thread to hold the IPC mutex
* and then release it in close(). */
void *data [3];
/* create a thread to hold the IPC mutex until signalled to release it */
0, RTTHREADTYPE_MAIN_WORKER, 0, "IPCHolder");
/* wait until thread init is completed */
/* the thread must succeed */
#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
# ifdef VBOX_WITH_NEW_SYS_V_KEYGEN
AssertMsgReturn (key != 0,
("Key value of 0 is not valid for IPC semaphore"),
E_FAIL);
# else /* !VBOX_WITH_NEW_SYS_V_KEYGEN */
char *pszSemName = NULL;
# endif /* !VBOX_WITH_NEW_SYS_V_KEYGEN */
AssertMsgReturn (mIPCSem >= 0,
("Cannot open IPC semaphore, errno=%d", errno),
E_FAIL);
/* grab the semaphore */
AssertMsgReturn (rv == 0,
("Cannot grab IPC semaphore, errno=%d", errno),
E_FAIL);
#else
# error "Port me!"
#endif
return rc;
}
/** @note To be called only from #close() */
void Session::releaseIPCSemaphore()
{
/* release the IPC semaphore */
#if defined(RT_OS_WINDOWS)
if (mIPCSem && mIPCThreadSem)
{
/*
* tell the thread holding the IPC mutex to release it;
* it will close mIPCSem handle
*/
/* wait for the thread to finish */
::CloseHandle (mIPCThreadSem);
}
if (mIPCThread != NIL_RTTHREAD)
{
/* tell the thread holding the IPC mutex to release it */
/* wait for the thread to finish */
}
if (mIPCThreadSem != NIL_RTSEMEVENT)
{
}
#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER)
if (mIPCSem >= 0)
{
mIPCSem = -1;
}
#else
# error "Port me!"
#endif
}
#if defined(RT_OS_WINDOWS)
/** VM IPC mutex holder thread */
{
if (ipcMutex)
{
/* grab the mutex */
if (wrc == WAIT_OBJECT_0)
{
if (finishSem)
{
/* signal we're done with init */
::SetEvent (initDoneSem);
/* wait until we're signaled to release the IPC mutex */
/* release the IPC mutex */
LogFlow (("IPCMutexHolderThread(): releasing IPC mutex...\n"));
::CloseHandle (ipcMutex);
::CloseHandle (finishSem);
}
}
}
/* signal we're done */
::SetEvent (initDoneSem);
return 0;
}
#endif
#if defined(RT_OS_OS2)
/** VM IPC mutex holder thread */
{
{
/* grab the mutex */
LogFlowFunc (("grabbing IPC mutex...\n"));
{
/* store the answer */
data [2] = (void *) true;
/* signal we're done */
/* wait until we're signaled to release the IPC mutex */
LogFlowFunc (("waiting for termination signal..\n"));
/* release the IPC mutex */
LogFlowFunc (("releasing IPC mutex...\n"));
}
::DosCloseMutexSem (ipcMutex);
}
/* store the answer */
data [1] = (void *) false;
/* signal we're done */
return 0;
}
#endif
/* vi: set tabstop=4 shiftwidth=4 expandtab: */