vboxweb.cpp revision df03c5ed15c9b5bf6d75fedcdf5057d3ffce8577
/**
* vboxweb.cpp:
* hand-coded parts of the webservice server. This is linked with the
* generated code in out/.../src/VBox/Main/webservice/methodmaps.cpp
* (plus static gSOAP server code) to implement the actual webservice
* server, to which clients can connect.
*
* Copyright (C) 2007-2012 Oracle Corporation
*
* 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.
*/
// shared webservice header
#include "vboxweb.h"
// vbox headers
#include <VBox/VBoxAuth.h>
#include <iprt/buildconfig.h>
#include <iprt/initterm.h>
#include <iprt/semaphore.h>
#include <iprt/critsect.h>
// workaround for compile problems on gcc 4.1
#ifdef __GNUC__
#endif
// gSOAP headers (must come after vbox includes because it checks for conflicting defs)
#include "soapH.h"
// standard headers
#include <map>
#include <list>
#ifdef __GNUC__
#endif
// include generated namespaces table
#include "vboxwebsrv.nsmap"
// declarations for the generated WSDL text
extern DECLIMPORT(const unsigned char) g_abVBoxWebWSDL[];
extern DECLIMPORT(const unsigned) g_cbVBoxWebWSDL;
/****************************************************************************
*
* private typedefs
*
****************************************************************************/
/****************************************************************************
*
* Read-only global variables
*
****************************************************************************/
// generated strings in methodmaps.cpp
extern const char *g_pcszISession,
// globals for vboxweb command-line arguments
#define DEFAULT_TIMEOUT_SECS 300
#define DEFAULT_TIMEOUT_SECS_STRING "300"
int g_iWatchdogCheckInterval = 5;
#ifdef WITH_OPENSSL
bool g_fSSL = false; // if SSL is enabled
#endif /* WITH_OPENSSL */
bool g_fVerbose = false; // be verbose
#if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) || defined (RT_OS_SOLARIS) || defined(RT_OS_FREEBSD)
bool g_fDaemonize = false; // run in background.
#endif
/****************************************************************************
*
* Writeable global variables
*
****************************************************************************/
// The one global SOAP queue created by main().
class SoapQ;
// this mutex protects the auth lib and authentication
// this mutex protects the global VirtualBox reference below
// this mutex protects all of the below
ULONG64 g_cManagedObjects = 0;
// this mutex protects g_mapThreads
// this mutex synchronizes logging
// Threads map, so we can quickly map an RTTHREAD struct to a logger prefix
/****************************************************************************
*
* Command line help
*
****************************************************************************/
static const RTGETOPTDEF g_aOptions[]
= {
#if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) || defined (RT_OS_SOLARIS) || defined(RT_OS_FREEBSD)
#endif
#ifdef WITH_OPENSSL
#endif /* WITH_OPENSSL */
};
void DisplayHelp()
{
RTStrmPrintf(g_pStdErr, "\nUsage: vboxwebsrv [options]\n\nSupported options (default values in brackets):\n");
for (unsigned i = 0;
i < RT_ELEMENTS(g_aOptions);
++i)
{
str += ", -";
str += ":";
const char *pcszDescr = "";
switch (g_aOptions[i].iShort)
{
case 'h':
pcszDescr = "Print this help message and exit.";
break;
#if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) || defined (RT_OS_SOLARIS) || defined(RT_OS_FREEBSD)
case 'b':
pcszDescr = "Run in background (daemon mode).";
break;
#endif
case 'H':
pcszDescr = "The host to bind to (localhost).";
break;
case 'p':
pcszDescr = "The port to bind to (18083).";
break;
#ifdef WITH_OPENSSL
case 's':
break;
case 'K':
pcszDescr = "Server key and certificate file, PEM format (\"\").";
break;
case 'a':
pcszDescr = "File name for password to server key (\"\").";
break;
case 'c':
pcszDescr = "CA certificate file, PEM format (\"\").";
break;
case 'C':
pcszDescr = "CA certificate path (\"\").";
break;
case 'D':
pcszDescr = "DH file name or DH key length in bits (\"\").";
break;
case 'r':
pcszDescr = "File containing seed for random number generator (\"\").";
break;
#endif /* WITH_OPENSSL */
case 't':
break;
case 'T':
pcszDescr = "Maximum number of worker threads to run in parallel (100).";
break;
case 'k':
pcszDescr = "Maximum number of requests before a socket will be closed (100).";
break;
case 'A':
pcszDescr = "Authentication method for the webservice (\"\").";
break;
case 'i':
pcszDescr = "Frequency of timeout checks in seconds (5).";
break;
case 'v':
pcszDescr = "Be verbose.";
break;
case 'P':
pcszDescr = "Name of the PID file which is created when the daemon was started.";
break;
case 'F':
pcszDescr = "Name of file to write log to (no file).";
break;
case 'R':
pcszDescr = "Number of log files (0 disables log rotation).";
break;
case 'S':
pcszDescr = "Maximum size of a log file to trigger rotation (bytes).";
break;
case 'I':
pcszDescr = "Maximum time interval to trigger log rotation (seconds).";
break;
}
}
}
/****************************************************************************
*
* SoapQ, SoapThread (multithreading)
*
****************************************************************************/
class SoapQ;
class SoapThread
{
public:
/**
* Constructor. Creates the new thread and makes it call process() for processing the queue.
* @param u Thread number. (So we can count from 1 and be readable.)
* @param q SoapQ instance which has the queue to process.
* @param soap struct soap instance from main() which we copy here.
*/
SoapThread(size_t u,
SoapQ &q,
: m_u(u),
m_pQ(&q)
{
// make a copy of the soap struct for the new thread
/* The soap.max_keep_alive value can be set to the maximum keep-alive calls allowed,
* which is important to avoid a client from holding a thread indefinitely.
* http://www.cs.fsu.edu/~engelen/soapdoc2.html#sec:keepalive
*
* Strings with 8-bit content can hold ASCII (default) or UTF8. The latter is
* possible by enabling the SOAP_C_UTFSTRING flag.
*/
this, // pvUser
0, // cbStack,
0,
m_strThread.c_str());
if (RT_FAILURE(rc))
{
exit(1);
}
}
void process();
{
if (!s || strcmp(s, "?wsdl"))
return SOAP_GET_METHOD;
return SOAP_OK;
}
/**
* Static function that can be passed to RTThreadCreate and that calls
* process() on the SoapThread instance passed as the thread parameter.
* @param pThread
* @param pvThread
* @return
*/
{
return 0;
}
};
/**
* SOAP queue encapsulation. There is only one instance of this, to
* which add() adds a queue item (called on the main thread),
* and from which get() fetch items, called from each queue thread.
*/
class SoapQ
{
public:
/**
* Constructor. Creates the soap queue.
* @param pSoap
*/
{
}
~SoapQ()
{
}
/**
* Adds the given socket to the SOAP queue and posts the
* member event sem to wake up the workers. Called on the main thread
* whenever a socket has work to do. Creates a new SOAP thread on the
* first call or when all existing threads are busy.
* @param s Socket from soap_accept() which has work to do.
*/
{
// if no threads have yet been created, or if all threads are busy,
// create a new SOAP thread
if ( !m_cIdleThreads
// but only if we're not exceeding the global maximum (default is 100)
)
{
*this,
m_soap);
}
// enqueue the socket of this connection and post eventsem so that
// one of the threads (possibly the one just created) can pick it up
// unblock one of the worker threads
return cItems;
}
/**
* Blocks the current thread until work comes in; then returns
* the SOAP socket which has work to do. This reduces m_cIdleThreads
* by one, and the caller MUST call done() when it's done processing.
* Called from the worker threads.
* @param cIdleThreads out: no. of threads which are currently idle (not counting the caller)
* @param cThreads out: total no. of SOAP threads running
* @return
*/
{
while (1)
{
// wait for something to happen
if (m_llSocketsQ.size())
{
// reset the multi event only if the queue is now empty; otherwise
// another thread will also wake up when we release the mutex and
// process another one
if (m_llSocketsQ.size() == 0)
return socket;
}
// nothing to do: keep looping
}
}
/**
* To be called by a worker thread after fetching an item from the
* queue via get() and having finished its lengthy processing.
*/
void done()
{
}
// A std::list abused as a queue; this contains the actual jobs to do,
// each int being a socket from soap_accept()
};
/**
* Thread function for each of the SOAP queue worker threads. This keeps
* running, blocks on the event semaphore in SoapThread.SoapQ and picks
* up a socket from the queue therein, which has been put there by
* beginProcessing().
*/
void SoapThread::process()
{
WebLog("New SOAP thread started\n");
while (1)
{
// wait for a socket to arrive on the queue
WebLog("Processing connection from IP=%lu.%lu.%lu.%lu socket=%d (%d out of %d threads idle)\n",
cThreads);
// Ensure that we don't get stuck indefinitely for connections using
// keepalive, otherwise stale connections tie up worker threads.
// process the request; this goes into the COM code in methodmaps.cpp
do {
#ifdef WITH_OPENSSL
{
break;
}
#endif /* WITH_OPENSSL */
} while (0);
// tell the queue we're idle again
}
}
/****************************************************************************
*
* VirtualBoxClient event listener
*
****************************************************************************/
{
public:
{
}
virtual ~VirtualBoxClientEventListener()
{
}
{
return S_OK;
}
void uninit()
{
}
{
switch (aType)
{
{
if (!fAvailable)
{
WebLog("VBoxSVC became unavailable\n");
{
}
{
// we're messing with sessions, so lock them
{
delete pSession;
}
}
}
else
{
WebLog("VBoxSVC became available\n");
}
break;
}
default:
AssertFailed();
}
return S_OK;
}
private:
};
/**
* Implementation for WEBLOG macro defined in vboxweb.h; this prints a message
* to the console and optionally to the file that may have been given to the
* vboxwebsrv command line.
* @param pszFormat
*/
{
}
/**
* Helper for printing SOAP error messages.
* @param soap
*/
/*static*/
{
if (soap_check_state(soap))
{
WebLog("Error: soap struct not initialized\n");
return;
}
WebLog("#### SOAP FAULT: %s [%s]\n",
}
#ifdef WITH_OPENSSL
/****************************************************************************
*
* OpenSSL convenience functions for multithread support
*
****************************************************************************/
struct CRYPTO_dynlock_value
{
};
static unsigned long CRYPTO_id_function()
{
return RTThreadNativeSelf();
}
{
if (mode & CRYPTO_LOCK)
RTCritSectEnter(&g_pSSLMutexes[n]);
else
RTCritSectLeave(&g_pSSLMutexes[n]);
}
{
static uint32_t s_iCritSectDynlock = 0;
struct CRYPTO_dynlock_value *value = (struct CRYPTO_dynlock_value *)RTMemAlloc(sizeof(struct CRYPTO_dynlock_value));
if (value)
return value;
}
static void CRYPTO_dyn_lock_function(int mode, struct CRYPTO_dynlock_value *value, const char * /*file*/, int /*line*/)
{
if (mode & CRYPTO_LOCK)
else
}
static void CRYPTO_dyn_destroy_function(struct CRYPTO_dynlock_value *value, const char * /*file*/, int /*line*/)
{
if (value)
{
}
}
static int CRYPTO_thread_setup()
{
int num_locks = CRYPTO_num_locks();
if (!g_pSSLMutexes)
return SOAP_EOM;
for (int i = 0; i < num_locks; i++)
{
"openssl-%d", i);
if (RT_FAILURE(rc))
{
for ( ; i >= 0; i--)
return SOAP_EOM;
}
}
return SOAP_OK;
}
static void CRYPTO_thread_cleanup()
{
if (!g_pSSLMutexes)
return;
int num_locks = CRYPTO_num_locks();
for (int i = 0; i < num_locks; i++)
}
#endif /* WITH_OPENSSL */
/****************************************************************************
*
* SOAP queue pumper thread
*
****************************************************************************/
void doQueuesLoop()
{
#ifdef WITH_OPENSSL
if (g_fSSL && CRYPTO_thread_setup())
{
WebLog("Failed to set up OpenSSL thread mutex!");
}
#endif /* WITH_OPENSSL */
// set up gSOAP
#ifdef WITH_OPENSSL
{
}
#endif /* WITH_OPENSSL */
// avoid EADDRINUSE on bind()
int m, s; // master and slave sockets
g_uBindToPort, // port
g_uBacklog); // backlog = max queue size for requests
if (m < 0)
else
{
WebLog("Socket connection successful: host = %s, port = %u, %smaster socket = %d\n",
#ifdef WITH_OPENSSL
#else /* !WITH_OPENSSL */
"",
#endif /*!WITH_OPENSSL */
m);
// initialize thread queue, mutex and eventsem
for (uint64_t i = 1;
;
i++)
{
// call gSOAP to handle incoming SOAP connection
s = soap_accept(&soap);
if (s < 0)
{
continue;
}
// add the socket to the queue and tell worker threads to
// pick up the job
}
}
#ifdef WITH_OPENSSL
if (g_fSSL)
#endif /* WITH_OPENSSL */
}
/**
* Thread function for the "queue pumper" thread started from main(). This implements
* the loop that takes SOAP calls from HTTP and serves them by handing sockets to the
* SOAP queue worker threads.
*/
{
// store a log prefix for this thread
doQueuesLoop();
return 0;
}
#ifdef RT_OS_WINDOWS
// Required for ATL
static CComModule _Module;
#endif
/**
* Start up the webservice server. This keeps running and waits
* for incoming SOAP connections; for each request that comes in,
* it calls method implementation code, most of it in the generated
* code in methodmaps.cpp.
*
* @param argc
* @param argv[]
* @return
*/
{
// initialize runtime
if (RT_FAILURE(rc))
return RTMsgInitFailure(rc);
// store a log prefix for this thread
"All rights reserved.\n");
int c;
const char *pszLogFile = NULL;
const char *pszPidFile = NULL;
{
switch (c)
{
case 'H':
{
* interpreted as "localhost" below. */
}
else
break;
case 'p':
break;
#ifdef WITH_OPENSSL
case 's':
g_fSSL = true;
break;
case 'K':
break;
case 'a':
else
{
else
{
if (RT_FAILURE(vrc))
return RTMsgErrorExit(RTEXITCODE_FAILURE, "failed to open password file (%s, %Rrc)", ValueUnion.psz, vrc);
}
char szPasswd[512];
if (RT_FAILURE(vrc))
return RTMsgErrorExit(RTEXITCODE_FAILURE, "failed to read password (%s, %Rrc)", ValueUnion.psz, vrc);
}
break;
case 'c':
break;
case 'C':
break;
case 'D':
break;
case 'r':
break;
#endif /* WITH_OPENSSL */
case 't':
break;
case 'i':
break;
case 'F':
break;
case 'R':
break;
case 'S':
break;
case 'I':
break;
case 'P':
break;
case 'T':
break;
case 'k':
break;
case 'A':
break;
case 'h':
DisplayHelp();
return 0;
case 'v':
g_fVerbose = true;
break;
#if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) || defined (RT_OS_SOLARIS) || defined(RT_OS_FREEBSD)
case 'b':
g_fDaemonize = true;
break;
#endif
case 'V':
return 0;
default:
return rc;
}
}
/* create release logger, to stdout */
"all", "VBOXWEBSRV_RELEASE_LOG",
if (RT_FAILURE(rc))
#if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) || defined (RT_OS_SOLARIS) || defined(RT_OS_FREEBSD)
if (g_fDaemonize)
{
/* prepare release logging */
char szLogFile[RTPATH_MAX];
if (!pszLogFile || !*pszLogFile)
{
if (RT_FAILURE(rc))
if (RT_FAILURE(rc))
}
if (RT_FAILURE(rc))
/* create release logger, to file */
"all", "VBOXWEBSRV_RELEASE_LOG",
if (RT_FAILURE(rc))
}
#endif
// initialize SOAP SSL support if enabled
#ifdef WITH_OPENSSL
if (g_fSSL)
#endif /* WITH_OPENSSL */
{
RTMsgError("failed to create the VirtualBoxClient object!");
{
RTMsgError("Most likely, the VirtualBox COM server is not running or failed to start.");
}
else
return RTEXITCODE_FAILURE;
}
{
return RTEXITCODE_FAILURE;
}
// set the authentication method if requested
{
if (pSystemProperties)
}
/* VirtualBoxClient events registration. */
{
}
// create the global mutexes
// SOAP queue pumper thread
NULL, // pvUser
0, // cbStack (default)
0, // flags
"SQPmp");
if (RT_FAILURE(rc))
// watchdog thread
if (g_iWatchdogTimeoutSecs > 0)
{
// start our watchdog thread
NULL,
0,
0,
"Watchdog");
if (RT_FAILURE(rc))
}
for (;;)
{
// we have to process main event queue
WEBDEBUG(("Pumping COM event queue\n"));
if (RT_FAILURE(rc))
}
/* VirtualBoxClient events unregistration. */
if (vboxClientListener)
{
}
return 0;
}
/****************************************************************************
*
* Watchdog thread
*
****************************************************************************/
/**
* Watchdog thread, runs in the background while the webservice is alive.
*
* This gets started by main() and runs in the background to check all sessions
* for whether they have been no requests in a configurable timeout period. In
* that case, the session is automatically logged off.
*/
{
// store a log prefix for this thread
WEBDEBUG(("Watchdog thread started\n"));
while (1)
{
// we're messing with sessions, so lock them
{
if ( tNow
)
{
delete pSession;
}
else
++it;
}
// re-set the authentication method in case it has been changed
{
if (pSystemProperties)
}
}
WEBDEBUG(("Watchdog thread ending\n"));
return 0;
}
/****************************************************************************
*
* SOAP exceptions
*
****************************************************************************/
/**
* Helper function to raise a SOAP fault. Called by the other helper
* functions, which raise specific SOAP faults.
*
* @param soap
* @param str
* @param extype
* @param ex
*/
const char *pcsz,
int extype,
void *ex)
{
// raise the fault
struct SOAP_ENV__Detail *pDetail = (struct SOAP_ENV__Detail*)soap_malloc(soap, sizeof(struct SOAP_ENV__Detail));
// without the following, gSOAP crashes miserably when sending out the
// data because it will try to serialize all fields (stupid documentation)
// fill extended info depending on SOAP version
{
}
else
{
}
}
/**
* Raises a SOAP fault that signals that an invalid object was passed.
*
* @param soap
* @param obj
*/
{
ex);
}
/**
* Return a safe C++ string from the given COM string,
* without crashing if the COM string is empty.
* @param bstr
* @return
*/
{
return ustr.c_str(); // @todo r=dj since the length is known, we can probably use a better std::string allocator
}
/**
* Return a safe C++ string from the given COM UUID,
* without crashing if the UUID is empty.
* @param bstr
* @return
*/
{
return ustr.c_str(); // @todo r=dj since the length is known, we can probably use a better std::string allocator
}
/** Code to handle string <-> byte arrays base64 conversion. */
{
if (cbData == 0)
return "";
NULL);
}
{
}
/**
* Raises a SOAP runtime fault. Implementation for the RaiseSoapRuntimeFault template
* function in vboxweb.h.
*
* @param pObj
*/
{
WEBDEBUG((" error, raising SOAP exception\n"));
// allocated our own soap fault struct
// some old vbox methods return errors without setting an error in the error info,
// so use the error info code if it's set and the HRESULT from the method otherwise
// compose descriptive message
ex);
}
/****************************************************************************
*
* splitting and merging of object IDs
*
****************************************************************************/
{
uint64_t u = 0;
return u;
}
/**
* Splits a managed object reference (in string form, as
* passed in from a SOAP method call) into two integers for
* session and object IDs, respectively.
*
* @param id
* @param sessid
* @param objid
* @return
*/
{
// 64-bit numbers in hex have 16 digits; hence
// the object-ref string must have 16 + "-" + 16 characters
)
{
char psz[34];
if (pSessid)
if (pObjid)
return true;
}
return false;
}
/**
* Creates a managed object reference (in string form) from
* two integers representing a session and object ID, respectively.
*
* @param sz Buffer with at least 34 bytes space to receive MOR string.
* @param sessid
* @param objid
* @return
*/
void MakeManagedObjectRef(char *sz,
{
}
/****************************************************************************
*
* class WebServiceSession
*
****************************************************************************/
class WebServiceSessionPrivate
{
public:
};
/**
* Constructor for the session object.
*
* Preconditions: Caller must have locked g_pSessionsLockHandle.
*
* @param username
* @param password
*/
: _fDestructing(false),
{
_pp = new WebServiceSessionPrivate;
_uSessionID = RTRandU64();
// register this session globally
g_mapSessions[_uSessionID] = this;
}
/**
* Destructor. Cleans up and destroys all contained managed object references on the way.
*
* Preconditions: Caller must have locked g_pSessionsLockHandle.
*/
{
// delete us from global map first so we can't be found
// any more while we're cleaning up
// notify ManagedObjectRef destructor so it won't
// remove itself from the maps; this avoids rebalancing
// the map's tree on every delete as well
_fDestructing = true;
// if (_pISession)
// {
// delete _pISession;
// _pISession = NULL;
// }
++it)
{
delete pRef; // this frees the contained ComPtr as well
}
delete _pp;
}
/**
* Authenticate the username and password against an authentication authority.
*
* @return 0 if the user was successfully authenticated, or an error code
* otherwise.
*/
const char *pcszPassword,
{
int rc = VERR_WEB_NOT_AUTHENTICATED;
{
}
if (pVirtualBox.isNull())
return rc;
static bool fAuthLibLoaded = false;
if (!fAuthLibLoaded)
{
// retrieve authentication library from system properties
if (filename == "null")
// authentication disabled, let everyone in:
fAuthLibLoaded = true;
else
{
do
{
if (RT_FAILURE(rc))
{
WEBDEBUG(("%s() Failed to load external authentication library. Error code: %Rrc\n", __FUNCTION__, rc));
break;
}
{
WEBDEBUG(("%s(): Could not resolve import '%s'. Error code: %Rrc\n", __FUNCTION__, AUTHENTRY3_NAME, rc));
{
WEBDEBUG(("%s(): Could not resolve import '%s'. Error code: %Rrc\n", __FUNCTION__, AUTHENTRY2_NAME, rc));
WEBDEBUG(("%s(): Could not resolve import '%s'. Error code: %Rrc\n", __FUNCTION__, AUTHENTRY_NAME, rc));
}
}
fAuthLibLoaded = true;
} while (0);
}
}
if (pfnAuthEntry3)
{
result = pfnAuthEntry3("webservice", NULL, AuthGuestNotAsked, pcszUsername, pcszPassword, NULL, true, 0);
if (result == AuthResultAccessGranted)
rc = 0;
}
else if (pfnAuthEntry2)
{
if (result == AuthResultAccessGranted)
rc = 0;
}
else if (pfnAuthEntry)
{
WEBDEBUG(("%s(): result of VRDPAuth(%s, [%d]): %d\n", __FUNCTION__, pcszUsername, strlen(pcszPassword), result));
if (result == AuthResultAccessGranted)
rc = 0;
}
else if (fAuthLibLoaded)
// fAuthLibLoaded = true but both pointers are NULL:
// then the authlib was "null" and auth was disabled
rc = 0;
else
{
WEBDEBUG(("Could not resolve AuthEntry, VRDPAuth2 or VRDPAuth entry point"));
}
if (!rc)
{
do
{
// now create the ISession object that this webservice session can use
// (and of which IWebsessionManager::getSessionObject returns a managed object reference)
{
WEBDEBUG(("ERROR: cannot create session object!"));
break;
}
_pISession = new ManagedObjectRef(*this,
p2, // IUnknown *pobjUnknown
session, // void *pobjInterface
if (g_fVerbose)
{
WEBDEBUG((" * %s: created session object with comptr 0x%lX, MOR = %s\n", __FUNCTION__, p, _pISession->getWSDLID().c_str()));
}
} while (0);
}
return rc;
}
/**
* Look up, in this session, whether a ManagedObjectRef has already been
* created for the given COM pointer.
*
* Note how we require that a ComPtr<IUnknown> is passed, which causes a
* queryInterface call when the caller passes in a different type, since
* a ComPtr<IUnknown> will point to something different than a
* ComPtr<IVirtualBox>, for example. As we store the ComPtr<IUnknown> in
* our private hash table, we must search for one too.
*
* Preconditions: Caller must have locked g_pSessionsLockHandle.
*
* @param pcu pointer to a COM object.
* @return The existing ManagedObjectRef that represents the COM object, or NULL if there's none yet.
*/
{
// WEBDEBUG((" %s: looking up 0x%lX\n", __FUNCTION__, ulp));
{
WEBDEBUG((" %s: found existing ref %s (%s) for COM obj 0x%lX\n", __FUNCTION__, pRef->getWSDLID().c_str(), pRef->getInterfaceName(), ulp));
return pRef;
}
return NULL;
}
/**
* Static method which attempts to find the session for which the given managed
* object reference was created, by splitting the reference into the session and
* object IDs and then looking up the session object for that session ID.
*
* Preconditions: Caller must have locked g_pSessionsLockHandle in read mode.
*
* @param id Managed object reference (with combined session and object IDs).
* @return
*/
{
if (SplitManagedObjectRef(id,
&sessid,
NULL))
{
}
return pSession;
}
/**
*
*/
{
return _pISession->getWSDLID();
}
/**
* Touches the webservice session to prevent it from timing out.
*
* Each webservice session has an internal timestamp that records
* the last request made to it from the client that started it.
* If no request was made within a configurable timeframe, then
* the client is logged off automatically,
* by calling IWebsessionManager::logoff()
*/
void WebServiceSession::touch()
{
}
/****************************************************************************
*
* class ManagedObjectRef
*
****************************************************************************/
/**
* Constructor, which assigns a unique ID to this managed object
* reference and stores it two global hashes:
*
* a) G_mapManagedObjectsById, which maps ManagedObjectID's to
* instances of this class; this hash is then used by the
* findObjectFromRef() template function in vboxweb.h
* to quickly retrieve the COM object from its managed
* object ID (mostly in the context of the method mappers
* in methodmaps.cpp, when a web service client passes in
* a managed object ID);
*
* b) G_mapManagedObjectsByComPtr, which maps COM pointers to
* instances of this class; this hash is used by
* createRefFromObject() to quickly figure out whether an
* instance already exists for a given COM pointer.
*
* This constructor calls AddRef() on the given COM object, and
* the destructor will call Release(). We require two input pointers
* for that COM object, one generic IUnknown* pointer which is used
* as the map key, and a specific interface pointer (e.g. IMachine*)
* which must support the interface given in guidInterface. All
* three values are returned by getPtr(), which gives future callers
* a chance to reuse the specific interface pointer without having
* to call QueryInterface, which can be expensive.
*
* This does _not_ check whether another instance already
* exists in the hash. This gets called only from the
* createOrFindRefFromComPtr() template function in vboxweb.h, which
* does perform that check.
*
* Preconditions: Caller must have locked g_pSessionsLockHandle.
*
* @param session Session to which the MOR will be added.
* @param pobjUnknown Pointer to IUnknown* interface for the COM object; this will be used in the hashes.
* @param pobjInterface Pointer to a specific interface for the COM object, described by guidInterface.
* @param guidInterface Interface which pobjInterface points to.
* @param pcszInterface String representation of that interface (e.g. "IMachine") for readability and logging.
*/
void *pobjInterface,
const char *pcszInterface)
{
// keep both stubs alive while this MOR exists (matching Release() calls are in destructor)
_id = ++g_iMaxManagedObjectID;
// and count globally
ULONG64 cTotal = ++g_cManagedObjects; // raise global count and make a copy for the debug message below
char sz[34];
WEBDEBUG((" * %s: MOR created for %s*=0x%lX (IUnknown*=0x%lX; COM refcount now %RI32/%RI32), new ID is %llX; now %lld objects total\n",
_id,
cTotal));
}
/**
* Destructor; removes the instance from the global hash of
* managed objects. Calls Release() on the contained COM object.
*
* Preconditions: Caller must have locked g_pSessionsLockHandle.
*/
{
// we called AddRef() on both interfaces, so call Release() on
// both as well, but in reverse order
WEBDEBUG((" * %s: deleting MOR for ID %llX (%s; COM refcount now %RI32/%RI32); now %lld objects total\n", __FUNCTION__, _id, _pcszInterface, cRefs1, cRefs2, cTotal));
// if we're being destroyed from the session's destructor,
// then that destructor is iterating over the maps, so
// don't remove us there! (data integrity + speed)
if (!_session._fDestructing)
{
}
}
/**
* Static helper method for findObjectFromRef() template that actually
* looks up the object from a given integer ID.
*
* This has been extracted into this non-template function to reduce
* code bloat as we have the actual STL map lookup only in this function.
*
* This also "touches" the timestamp in the session whose ID is encoded
* in the given integer ID, in order to prevent the session from timing
* out.
*
* Preconditions: Caller must have locked g_mutexSessions.
*
* @param strId
* @param iter
* @return
*/
bool fNullAllowed)
{
int rc = 0;
do
{
// allow NULL (== empty string) input reference, which should return a NULL pointer
{
return 0;
}
if (!SplitManagedObjectRef(id,
&sessid,
&objid))
{
break;
}
{
break;
}
// "touch" session to prevent it from timing out
{
break;
}
} while (0);
return rc;
}
/****************************************************************************
*
* interface IManagedObjectRef
*
****************************************************************************/
/**
* This is the hard-coded implementation for the IManagedObjectRef::getInterfaceName()
* that our WSDL promises to our web service clients. This method returns a
* string describing the interface that this managed object reference
* supports, e.g. "IMachine".
*
* @param soap
* @param req
* @param resp
* @return
*/
{
do
{
// findRefFromId require the lock
} while (0);
return SOAP_FAULT;
return SOAP_OK;
}
/**
* This is the hard-coded implementation for the IManagedObjectRef::release()
* that our WSDL promises to our web service clients. This method releases
* a managed object reference and removes it from our stacks.
*
* @param soap
* @param req
* @param resp
* @return
*/
{
do
{
// findRefFromId and the delete call below require the lock
{
break;
}
WEBDEBUG((" found reference; deleting!\n"));
// this removes the object from all stacks; since
// there's a ComPtr<> hidden inside the reference,
// this should also invoke Release() on the COM
// object
delete pRef;
} while (0);
return SOAP_FAULT;
return SOAP_OK;
}
/****************************************************************************
*
* interface IWebsessionManager
*
****************************************************************************/
/**
* Hard-coded implementation for IWebsessionManager::logon. As opposed to the underlying
* COM API, this is the first method that a webservice client must call before the
* webservice will do anything useful.
*
* This returns a managed object reference to the global IVirtualBox object; into this
* reference a session ID is encoded which remains constant with all managed object
* references returned by other methods.
*
* This also creates an instance of ISession, which is stored internally with the
* webservice session and can be retrieved with IWebsessionManager::getSessionObject
* (__vbox__IWebsessionManager_USCOREgetSessionObject). In order for the
* VirtualBox web service to do anything useful, one usually needs both a
* VirtualBox and an ISession object, for which these two methods are designed.
*
* When the webservice client is done, it should call IWebsessionManager::logoff. This
* will clean up internally (destroy all remaining managed object references and
* related COM objects used internally).
*
* After logon, an internal timeout ensures that if the webservice client does not
* call any methods, after a configurable number of seconds, the webservice will log
* off the client automatically. This is to ensure that the webservice does not
* drown in managed object references and eventually deny service. Still, it is
* a much better solution, both for performance and cleanliness, for the webservice
* client to clean up itself.
*
* @param
* @param vbox__IWebsessionManager_USCORElogon
* @param vbox__IWebsessionManager_USCORElogonResponse
* @return
*/
{
do
{
// WebServiceSession constructor tinkers with global MOR map and requires a write lock
// create new session; the constructor stores the new session
// in the global map automatically
// authenticate the user
pVirtualBox.asOutParam())))
{
// in the new session, create a managed object reference (MOR) for the
// global VirtualBox object; this encodes the session ID in the MOR so
// that it will be implicitly be included in all future requests of this
// webservice client
{
break;
}
p2, // IUnknown *pobjUnknown
pVirtualBox, // void *pobjInterface
}
else
} while (0);
return SOAP_FAULT;
return SOAP_OK;
}
/**
* Returns the ISession object that was created for the webservice client
* on logon.
*/
struct soap*,
{
do
{
// findSessionFromRef needs lock
} while (0);
return SOAP_FAULT;
return SOAP_OK;
}
/**
* hard-coded implementation for IWebsessionManager::logoff.
*
* @param
* @param vbox__IWebsessionManager_USCORElogon
* @param vbox__IWebsessionManager_USCORElogonResponse
* @return
*/
struct soap*,
{
do
{
// findSessionFromRef and the session destructor require the lock
{
delete pSession;
// destructor cleans up
}
} while (0);
return SOAP_FAULT;
return SOAP_OK;
}