VirtualBox

source: vbox/trunk/src/VBox/Main/webservice/vboxweb.cpp@ 35454

最後變更 在這個檔案從35454是 35454,由 vboxsync 提交於 14 年 前

Main/webservice: use VirtualBoxClient to detect VBoxSVC crashes and recover correctly by deleting the sessions, plus some minor cleanups

  • 屬性 filesplitter.c 設為 Makefile.kmk
  • 屬性 svn:eol-style 設為 native
  • 屬性 svn:keywords 設為 Id Revision
檔案大小: 62.2 KB
 
1/**
2 * vboxweb.cpp:
3 * hand-coded parts of the webservice server. This is linked with the
4 * generated code in out/.../src/VBox/Main/webservice/methodmaps.cpp
5 * (plus static gSOAP server code) to implement the actual webservice
6 * server, to which clients can connect.
7 *
8 * Copyright (C) 2006-2011 Oracle Corporation
9 *
10 * This file is part of VirtualBox Open Source Edition (OSE), as
11 * available from http://www.alldomusa.eu.org. This file is free software;
12 * you can redistribute it and/or modify it under the terms of the GNU
13 * General Public License (GPL) as published by the Free Software
14 * Foundation, in version 2 as it comes in the "COPYING" file of the
15 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
16 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
17 */
18
19// shared webservice header
20#include "vboxweb.h"
21
22// vbox headers
23#include <VBox/com/com.h>
24#include <VBox/com/array.h>
25#include <VBox/com/ErrorInfo.h>
26#include <VBox/com/errorprint.h>
27#include <VBox/com/EventQueue.h>
28#include <VBox/com/listeners.h>
29#include <VBox/VBoxAuth.h>
30#include <VBox/version.h>
31
32#include <iprt/buildconfig.h>
33#include <iprt/ctype.h>
34#include <iprt/getopt.h>
35#include <iprt/initterm.h>
36#include <iprt/ldr.h>
37#include <iprt/message.h>
38#include <iprt/process.h>
39#include <iprt/rand.h>
40#include <iprt/semaphore.h>
41#include <iprt/string.h>
42#include <iprt/thread.h>
43#include <iprt/time.h>
44
45// workaround for compile problems on gcc 4.1
46#ifdef __GNUC__
47#pragma GCC visibility push(default)
48#endif
49
50// gSOAP headers (must come after vbox includes because it checks for conflicting defs)
51#include "soapH.h"
52
53// standard headers
54#include <map>
55#include <list>
56
57#ifdef __GNUC__
58#pragma GCC visibility pop
59#endif
60
61// include generated namespaces table
62#include "vboxwebsrv.nsmap"
63
64/****************************************************************************
65 *
66 * private typedefs
67 *
68 ****************************************************************************/
69
70typedef std::map<uint64_t, ManagedObjectRef*>
71 ManagedObjectsMapById;
72typedef std::map<uint64_t, ManagedObjectRef*>::iterator
73 ManagedObjectsIteratorById;
74typedef std::map<uintptr_t, ManagedObjectRef*>
75 ManagedObjectsMapByPtr;
76
77typedef std::map<uint64_t, WebServiceSession*>
78 SessionsMap;
79typedef std::map<uint64_t, WebServiceSession*>::iterator
80 SessionsMapIterator;
81
82int fntWatchdog(RTTHREAD ThreadSelf, void *pvUser);
83
84/****************************************************************************
85 *
86 * Read-only global variables
87 *
88 ****************************************************************************/
89
90ComPtr<IVirtualBoxClient> g_pVirtualBoxClient = NULL;
91ComPtr<IVirtualBox> g_pVirtualBox = NULL;
92
93// generated strings in methodmaps.cpp
94extern const char *g_pcszISession,
95 *g_pcszIVirtualBox;
96
97// globals for vboxweb command-line arguments
98#define DEFAULT_TIMEOUT_SECS 300
99#define DEFAULT_TIMEOUT_SECS_STRING "300"
100int g_iWatchdogTimeoutSecs = DEFAULT_TIMEOUT_SECS;
101int g_iWatchdogCheckInterval = 5;
102
103const char *g_pcszBindToHost = NULL; // host; NULL = localhost
104unsigned int g_uBindToPort = 18083; // port
105unsigned int g_uBacklog = 100; // backlog = max queue size for requests
106unsigned int g_cMaxWorkerThreads = 100; // max. no. of worker threads
107unsigned int g_cMaxKeepAlive = 100; // maximum number of soap requests in one connection
108
109bool g_fVerbose = false; // be verbose
110PRTSTREAM g_pStrmLog = NULL;
111
112#if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) || defined (RT_OS_SOLARIS) || defined(RT_OS_FREEBSD)
113bool g_fDaemonize = false; // run in background.
114#endif
115
116const WSDLT_ID g_EmptyWSDLID; // for NULL MORs
117
118/****************************************************************************
119 *
120 * Writeable global variables
121 *
122 ****************************************************************************/
123
124// The one global SOAP queue created by main().
125class SoapQ;
126SoapQ *g_pSoapQ = NULL;
127
128// this mutex protects the auth lib and authentication
129util::WriteLockHandle *g_pAuthLibLockHandle;
130
131// this mutex protects the global VirtualBox reference
132util::RWLockHandle *g_pVirtualBoxLockHandle;
133
134// this mutex protects all of the below
135util::WriteLockHandle *g_pSessionsLockHandle;
136
137SessionsMap g_mapSessions;
138ULONG64 g_iMaxManagedObjectID = 0;
139ULONG64 g_cManagedObjects = 0;
140
141// this mutex protects g_mapThreads
142util::RWLockHandle *g_pThreadsLockHandle;
143
144// this mutex synchronizes logging
145util::WriteLockHandle *g_pWebLogLockHandle;
146
147// Threads map, so we can quickly map an RTTHREAD struct to a logger prefix
148typedef std::map<RTTHREAD, com::Utf8Str> ThreadsMap;
149ThreadsMap g_mapThreads;
150
151/****************************************************************************
152 *
153 * Command line help
154 *
155 ****************************************************************************/
156
157static const RTGETOPTDEF g_aOptions[]
158 = {
159 { "--help", 'h', RTGETOPT_REQ_NOTHING }, /* for DisplayHelp() */
160#if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) || defined (RT_OS_SOLARIS) || defined(RT_OS_FREEBSD)
161 { "--background", 'b', RTGETOPT_REQ_NOTHING },
162#endif
163 { "--host", 'H', RTGETOPT_REQ_STRING },
164 { "--port", 'p', RTGETOPT_REQ_UINT32 },
165 { "--timeout", 't', RTGETOPT_REQ_UINT32 },
166 { "--check-interval", 'i', RTGETOPT_REQ_UINT32 },
167 { "--threads", 'T', RTGETOPT_REQ_UINT32 },
168 { "--keepalive", 'k', RTGETOPT_REQ_UINT32 },
169 { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
170 { "--pidfile", 'P', RTGETOPT_REQ_STRING },
171 { "--logfile", 'F', RTGETOPT_REQ_STRING },
172 };
173
174void DisplayHelp()
175{
176 RTStrmPrintf(g_pStdErr, "\nUsage: vboxwebsrv [options]\n\nSupported options (default values in brackets):\n");
177 for (unsigned i = 0;
178 i < RT_ELEMENTS(g_aOptions);
179 ++i)
180 {
181 std::string str(g_aOptions[i].pszLong);
182 str += ", -";
183 str += g_aOptions[i].iShort;
184 str += ":";
185
186 const char *pcszDescr = "";
187
188 switch (g_aOptions[i].iShort)
189 {
190 case 'h':
191 pcszDescr = "Print this help message and exit.";
192 break;
193
194#if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) || defined (RT_OS_SOLARIS) || defined(RT_OS_FREEBSD)
195 case 'b':
196 pcszDescr = "Run in background (daemon mode).";
197 break;
198#endif
199
200 case 'H':
201 pcszDescr = "The host to bind to (localhost).";
202 break;
203
204 case 'p':
205 pcszDescr = "The port to bind to (18083).";
206 break;
207
208 case 't':
209 pcszDescr = "Session timeout in seconds; 0 = disable timeouts (" DEFAULT_TIMEOUT_SECS_STRING ").";
210 break;
211
212 case 'T':
213 pcszDescr = "Maximum number of worker threads to run in parallel (100).";
214 break;
215
216 case 'k':
217 pcszDescr = "Maximum number of requests before a socket will be closed (100).";
218 break;
219
220 case 'i':
221 pcszDescr = "Frequency of timeout checks in seconds (5).";
222 break;
223
224 case 'v':
225 pcszDescr = "Be verbose.";
226 break;
227
228 case 'P':
229 pcszDescr = "Name of the PID file which is created when the daemon was started.";
230 break;
231
232 case 'F':
233 pcszDescr = "Name of file to write log to (no file).";
234 break;
235 }
236
237 RTStrmPrintf(g_pStdErr, "%-23s%s\n", str.c_str(), pcszDescr);
238 }
239}
240
241/****************************************************************************
242 *
243 * SoapQ, SoapThread (multithreading)
244 *
245 ****************************************************************************/
246
247class SoapQ;
248
249class SoapThread
250{
251public:
252 /**
253 * Constructor. Creates the new thread and makes it call process() for processing the queue.
254 * @param u Thread number. (So we can count from 1 and be readable.)
255 * @param q SoapQ instance which has the queue to process.
256 * @param soap struct soap instance from main() which we copy here.
257 */
258 SoapThread(size_t u,
259 SoapQ &q,
260 const struct soap *soap)
261 : m_u(u),
262 m_strThread(com::Utf8StrFmt("SoapQWrk%02d", m_u)),
263 m_pQ(&q)
264 {
265 // make a copy of the soap struct for the new thread
266 m_soap = soap_copy(soap);
267
268 /* The soap.max_keep_alive value can be set to the maximum keep-alive calls allowed,
269 * which is important to avoid a client from holding a thread indefinitely.
270 * http://www.cs.fsu.edu/~engelen/soapdoc2.html#sec:keepalive
271 *
272 * Strings with 8-bit content can hold ASCII (default) or UTF8. The latter is
273 * possible by enabling the SOAP_C_UTFSTRING flag.
274 */
275 soap_set_omode(m_soap, SOAP_IO_KEEPALIVE | SOAP_C_UTFSTRING);
276 soap_set_imode(m_soap, SOAP_IO_KEEPALIVE | SOAP_C_UTFSTRING);
277 m_soap->max_keep_alive = g_cMaxKeepAlive;
278
279 int rc = RTThreadCreate(&m_pThread,
280 fntWrapper,
281 this, // pvUser
282 0, // cbStack,
283 RTTHREADTYPE_MAIN_HEAVY_WORKER,
284 0,
285 m_strThread.c_str());
286 if (RT_FAILURE(rc))
287 {
288 RTMsgError("Cannot start worker thread %d: %Rrc\n", u, rc);
289 exit(1);
290 }
291 }
292
293 void process();
294
295 /**
296 * Static function that can be passed to RTThreadCreate and that calls
297 * process() on the SoapThread instance passed as the thread parameter.
298 * @param pThread
299 * @param pvThread
300 * @return
301 */
302 static int fntWrapper(RTTHREAD pThread, void *pvThread)
303 {
304 SoapThread *pst = (SoapThread*)pvThread;
305 pst->process(); // this never returns really
306 return 0;
307 }
308
309 size_t m_u; // thread number
310 com::Utf8Str m_strThread; // thread name ("SoapQWrkXX")
311 SoapQ *m_pQ; // the single SOAP queue that all the threads service
312 struct soap *m_soap; // copy of the soap structure for this thread (from soap_copy())
313 RTTHREAD m_pThread; // IPRT thread struct for this thread
314};
315
316/**
317 * SOAP queue encapsulation. There is only one instance of this, to
318 * which add() adds a queue item (called on the main thread),
319 * and from which get() fetch items, called from each queue thread.
320 */
321class SoapQ
322{
323public:
324
325 /**
326 * Constructor. Creates the soap queue.
327 * @param pSoap
328 */
329 SoapQ(const struct soap *pSoap)
330 : m_soap(pSoap),
331 m_mutex(util::LOCKCLASS_OBJECTSTATE), // lowest lock order, no other may be held while this is held
332 m_cIdleThreads(0)
333 {
334 RTSemEventMultiCreate(&m_event);
335 }
336
337 ~SoapQ()
338 {
339 RTSemEventMultiDestroy(m_event);
340 }
341
342 /**
343 * Adds the given socket to the SOAP queue and posts the
344 * member event sem to wake up the workers. Called on the main thread
345 * whenever a socket has work to do. Creates a new SOAP thread on the
346 * first call or when all existing threads are busy.
347 * @param s Socket from soap_accept() which has work to do.
348 */
349 uint32_t add(int s)
350 {
351 uint32_t cItems;
352 util::AutoWriteLock qlock(m_mutex COMMA_LOCKVAL_SRC_POS);
353
354 // if no threads have yet been created, or if all threads are busy,
355 // create a new SOAP thread
356 if ( !m_cIdleThreads
357 // but only if we're not exceeding the global maximum (default is 100)
358 && (m_llAllThreads.size() < g_cMaxWorkerThreads)
359 )
360 {
361 SoapThread *pst = new SoapThread(m_llAllThreads.size() + 1,
362 *this,
363 m_soap);
364 m_llAllThreads.push_back(pst);
365 util::AutoWriteLock thrLock(g_pThreadsLockHandle COMMA_LOCKVAL_SRC_POS);
366 g_mapThreads[pst->m_pThread] = com::Utf8StrFmt("[%3u]", pst->m_u);
367 ++m_cIdleThreads;
368 }
369
370 // enqueue the socket of this connection and post eventsem so that
371 // one of the threads (possibly the one just created) can pick it up
372 m_llSocketsQ.push_back(s);
373 cItems = m_llSocketsQ.size();
374 qlock.release();
375
376 // unblock one of the worker threads
377 RTSemEventMultiSignal(m_event);
378
379 return cItems;
380 }
381
382 /**
383 * Blocks the current thread until work comes in; then returns
384 * the SOAP socket which has work to do. This reduces m_cIdleThreads
385 * by one, and the caller MUST call done() when it's done processing.
386 * Called from the worker threads.
387 * @param cIdleThreads out: no. of threads which are currently idle (not counting the caller)
388 * @param cThreads out: total no. of SOAP threads running
389 * @return
390 */
391 int get(size_t &cIdleThreads, size_t &cThreads)
392 {
393 while (1)
394 {
395 // wait for something to happen
396 RTSemEventMultiWait(m_event, RT_INDEFINITE_WAIT);
397
398 util::AutoWriteLock qlock(m_mutex COMMA_LOCKVAL_SRC_POS);
399 if (m_llSocketsQ.size())
400 {
401 int socket = m_llSocketsQ.front();
402 m_llSocketsQ.pop_front();
403 cIdleThreads = --m_cIdleThreads;
404 cThreads = m_llAllThreads.size();
405
406 // reset the multi event only if the queue is now empty; otherwise
407 // another thread will also wake up when we release the mutex and
408 // process another one
409 if (m_llSocketsQ.size() == 0)
410 RTSemEventMultiReset(m_event);
411
412 qlock.release();
413
414 return socket;
415 }
416
417 // nothing to do: keep looping
418 }
419 }
420
421 /**
422 * To be called by a worker thread after fetching an item from the
423 * queue via get() and having finished its lengthy processing.
424 */
425 void done()
426 {
427 util::AutoWriteLock qlock(m_mutex COMMA_LOCKVAL_SRC_POS);
428 ++m_cIdleThreads;
429 }
430
431 const struct soap *m_soap; // soap structure created by main(), passed to constructor
432
433 util::WriteLockHandle m_mutex;
434 RTSEMEVENTMULTI m_event; // posted by add(), blocked on by get()
435
436 std::list<SoapThread*> m_llAllThreads; // all the threads created by the constructor
437 size_t m_cIdleThreads; // threads which are currently idle (statistics)
438
439 // A std::list abused as a queue; this contains the actual jobs to do,
440 // each int being a socket from soap_accept()
441 std::list<int> m_llSocketsQ;
442};
443
444/**
445 * Thread function for each of the SOAP queue worker threads. This keeps
446 * running, blocks on the event semaphore in SoapThread.SoapQ and picks
447 * up a socket from the queue therein, which has been put there by
448 * beginProcessing().
449 */
450void SoapThread::process()
451{
452 WebLog("New SOAP thread started\n");
453
454 while (1)
455 {
456 // wait for a socket to arrive on the queue
457 size_t cIdleThreads = 0, cThreads = 0;
458 m_soap->socket = m_pQ->get(cIdleThreads, cThreads);
459
460 WebLog("Processing connection from IP=%lu.%lu.%lu.%lu socket=%d (%d out of %d threads idle)\n",
461 (m_soap->ip >> 24) & 0xFF,
462 (m_soap->ip >> 16) & 0xFF,
463 (m_soap->ip >> 8) & 0xFF,
464 m_soap->ip & 0xFF,
465 m_soap->socket,
466 cIdleThreads,
467 cThreads);
468
469 // process the request; this goes into the COM code in methodmaps.cpp
470 soap_serve(m_soap);
471
472 soap_destroy(m_soap); // clean up class instances
473 soap_end(m_soap); // clean up everything and close socket
474
475 // tell the queue we're idle again
476 m_pQ->done();
477 }
478}
479
480/****************************************************************************
481 *
482 * VirtualBoxClient event listener
483 *
484 ****************************************************************************/
485
486class VirtualBoxClientEventListener
487{
488public:
489 VirtualBoxClientEventListener()
490 {
491 }
492
493 virtual ~VirtualBoxClientEventListener()
494 {
495 }
496
497 STDMETHOD(HandleEvent)(VBoxEventType_T aType, IEvent *aEvent)
498 {
499 switch (aType)
500 {
501 case VBoxEventType_OnVBoxSVCAvailabilityChanged:
502 {
503 ComPtr<IVBoxSVCAvailabilityChangedEvent> pVSACEv = aEvent;
504 Assert(pVSACEv);
505 BOOL fAvailable = FALSE;
506 pVSACEv->COMGETTER(Available)(&fAvailable);
507 if (!fAvailable)
508 {
509 WebLog("VBoxSVC became unavailable\n");
510 {
511 util::AutoWriteLock vlock(g_pVirtualBoxLockHandle COMMA_LOCKVAL_SRC_POS);
512 g_pVirtualBox = NULL;
513 }
514 {
515 // we're messing with sessions, so lock them
516 util::AutoWriteLock lock(g_pSessionsLockHandle COMMA_LOCKVAL_SRC_POS);
517 WEBDEBUG(("SVC unavailable: deleting %d sessions\n", g_mapSessions.size()));
518
519 SessionsMap::iterator it = g_mapSessions.begin(),
520 itEnd = g_mapSessions.end();
521 while (it != itEnd)
522 {
523 WebServiceSession *pSession = it->second;
524 WEBDEBUG(("SVC unavailable: Session %llX stale, deleting\n", pSession->getID()));
525 delete pSession;
526 it = g_mapSessions.begin();
527 }
528 }
529 }
530 else
531 {
532 WebLog("VBoxSVC became available\n");
533 util::AutoWriteLock vlock(g_pVirtualBoxLockHandle COMMA_LOCKVAL_SRC_POS);
534 HRESULT hrc = g_pVirtualBoxClient->COMGETTER(VirtualBox)(g_pVirtualBox.asOutParam());
535 AssertComRC(hrc);
536 }
537 break;
538 }
539 default:
540 AssertFailed();
541 }
542
543 return S_OK;
544 }
545
546private:
547};
548
549typedef ListenerImpl<VirtualBoxClientEventListener> VirtualBoxClientEventListenerImpl;
550
551VBOX_LISTENER_DECLARE(VirtualBoxClientEventListenerImpl)
552
553/**
554 * Implementation for WEBLOG macro defined in vboxweb.h; this prints a message
555 * to the console and optionally to the file that may have been given to the
556 * vboxwebsrv command line.
557 * @param pszFormat
558 */
559void WebLog(const char *pszFormat, ...)
560{
561 va_list args;
562 va_start(args, pszFormat);
563 char *psz = NULL;
564 RTStrAPrintfV(&psz, pszFormat, args);
565 va_end(args);
566
567 const char *pcszPrefix = "[ ]";
568 util::AutoReadLock thrLock(g_pThreadsLockHandle COMMA_LOCKVAL_SRC_POS);
569 ThreadsMap::iterator it = g_mapThreads.find(RTThreadSelf());
570 if (it != g_mapThreads.end())
571 pcszPrefix = it->second.c_str();
572 thrLock.release();
573
574 // make a timestamp
575 RTTIMESPEC ts;
576 RTTimeLocalNow(&ts);
577 RTTIME t;
578 RTTimeExplode(&t, &ts);
579
580 com::Utf8StrFmt strPrefix("%04d-%02d-%02d %02d:%02d:%02d %s",
581 t.i32Year, t.u8Month, t.u8MonthDay,
582 t.u8Hour, t.u8Minute, t.u8Second,
583 pcszPrefix);
584
585 // synchronize the actual output
586 util::AutoWriteLock logLock(g_pWebLogLockHandle COMMA_LOCKVAL_SRC_POS);
587 // terminal
588 RTPrintf("%s %s", strPrefix.c_str(), psz);
589
590 // log file
591 if (g_pStrmLog)
592 {
593 RTStrmPrintf(g_pStrmLog, "%s %s", strPrefix.c_str(), psz);
594 RTStrmFlush(g_pStrmLog);
595 }
596
597#ifdef DEBUG
598 // logger instance
599 RTLogLoggerEx(LOG_INSTANCE, RTLOGGRPFLAGS_DJ, LOG_GROUP, "%s %s", pcszPrefix, psz);
600#endif
601 logLock.release();
602
603 RTStrFree(psz);
604}
605
606/**
607 * Helper for printing SOAP error messages.
608 * @param soap
609 */
610void WebLogSoapError(struct soap *soap)
611{
612 if (soap_check_state(soap))
613 {
614 WebLog("Error: soap struct not initialized\n");
615 return;
616 }
617
618 const char *pcszFaultString = *soap_faultstring(soap);
619 const char **ppcszDetail = soap_faultcode(soap);
620 WebLog("#### SOAP FAULT: %s [%s]\n",
621 pcszFaultString ? pcszFaultString : "[no fault string available]",
622 (ppcszDetail && *ppcszDetail) ? *ppcszDetail : "no details available");
623}
624
625/****************************************************************************
626 *
627 * SOAP queue pumper thread
628 *
629 ****************************************************************************/
630
631void doQueuesLoop()
632{
633 // set up gSOAP
634 struct soap soap;
635 soap_init(&soap);
636
637 soap.bind_flags |= SO_REUSEADDR;
638 // avoid EADDRINUSE on bind()
639
640 int m, s; // master and slave sockets
641 m = soap_bind(&soap,
642 g_pcszBindToHost ? g_pcszBindToHost : "localhost", // safe default host
643 g_uBindToPort, // port
644 g_uBacklog); // backlog = max queue size for requests
645 if (m < 0)
646 WebLogSoapError(&soap);
647 else
648 {
649 WebLog("Socket connection successful: host = %s, port = %u, master socket = %d\n",
650 (g_pcszBindToHost) ? g_pcszBindToHost : "default (localhost)",
651 g_uBindToPort,
652 m);
653
654 // initialize thread queue, mutex and eventsem
655 g_pSoapQ = new SoapQ(&soap);
656
657 for (uint64_t i = 1;
658 ;
659 i++)
660 {
661 // call gSOAP to handle incoming SOAP connection
662 s = soap_accept(&soap);
663 if (s < 0)
664 {
665 WebLogSoapError(&soap);
666 break;
667 }
668
669 // add the socket to the queue and tell worker threads to
670 // pick up the jobn
671 size_t cItemsOnQ = g_pSoapQ->add(s);
672 WebLog("Request %llu on socket %d queued for processing (%d items on Q)\n", i, s, cItemsOnQ);
673 }
674 }
675 soap_done(&soap); // close master socket and detach environment
676}
677
678/**
679 * Thread function for the "queue pumper" thread started from main(). This implements
680 * the loop that takes SOAP calls from HTTP and serves them by handing sockets to the
681 * SOAP queue worker threads.
682 */
683int fntQPumper(RTTHREAD ThreadSelf, void *pvUser)
684{
685 // store a log prefix for this thread
686 util::AutoWriteLock thrLock(g_pThreadsLockHandle COMMA_LOCKVAL_SRC_POS);
687 g_mapThreads[RTThreadSelf()] = "[ P ]";
688 thrLock.release();
689
690 doQueuesLoop();
691
692 return 0;
693}
694
695/**
696 * Start up the webservice server. This keeps running and waits
697 * for incoming SOAP connections; for each request that comes in,
698 * it calls method implementation code, most of it in the generated
699 * code in methodmaps.cpp.
700 *
701 * @param argc
702 * @param argv[]
703 * @return
704 */
705int main(int argc, char *argv[])
706{
707 // initialize runtime
708 int rc = RTR3Init();
709 if (RT_FAILURE(rc))
710 return RTMsgInitFailure(rc);
711
712 // store a log prefix for this thread
713 g_mapThreads[RTThreadSelf()] = "[M ]";
714
715 RTStrmPrintf(g_pStdErr, VBOX_PRODUCT " web service version " VBOX_VERSION_STRING "\n"
716 "(C) 2005-" VBOX_C_YEAR " " VBOX_VENDOR "\n"
717 "All rights reserved.\n");
718
719 int c;
720 const char *pszPidFile = NULL;
721 RTGETOPTUNION ValueUnion;
722 RTGETOPTSTATE GetState;
723 RTGetOptInit(&GetState, argc, argv, g_aOptions, RT_ELEMENTS(g_aOptions), 1, 0 /*fFlags*/);
724 while ((c = RTGetOpt(&GetState, &ValueUnion)))
725 {
726 switch (c)
727 {
728 case 'H':
729 if (!ValueUnion.psz || !*ValueUnion.psz)
730 {
731 /* Normalize NULL/empty string to NULL, which will be
732 * interpreted as "localhost" below. */
733 g_pcszBindToHost = NULL;
734 }
735 else
736 g_pcszBindToHost = ValueUnion.psz;
737 break;
738
739 case 'p':
740 g_uBindToPort = ValueUnion.u32;
741 break;
742
743 case 't':
744 g_iWatchdogTimeoutSecs = ValueUnion.u32;
745 break;
746
747 case 'i':
748 g_iWatchdogCheckInterval = ValueUnion.u32;
749 break;
750
751 case 'F':
752 {
753 int rc2 = RTStrmOpen(ValueUnion.psz, "a", &g_pStrmLog);
754 if (rc2)
755 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Cannot open log file \"%s\" for writing: %Rrc", ValueUnion.psz, rc2);
756
757 WebLog(VBOX_PRODUCT " Webservice Version %s\n"
758 "Opened log file \"%s\"\n", VBOX_VERSION_STRING, ValueUnion.psz);
759 break;
760 }
761
762 case 'P':
763 pszPidFile = ValueUnion.psz;
764 break;
765
766 case 'T':
767 g_cMaxWorkerThreads = ValueUnion.u32;
768 break;
769
770 case 'k':
771 g_cMaxKeepAlive = ValueUnion.u32;
772 break;
773
774 case 'h':
775 DisplayHelp();
776 return 0;
777
778 case 'v':
779 g_fVerbose = true;
780 break;
781
782#if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) || defined (RT_OS_SOLARIS) || defined(RT_OS_FREEBSD)
783 case 'b':
784 g_fDaemonize = true;
785 break;
786#endif
787 case 'V':
788 RTPrintf("%sr%s\n", RTBldCfgVersion(), RTBldCfgRevisionStr());
789 return 0;
790
791 default:
792 rc = RTGetOptPrintError(c, &ValueUnion);
793 return rc;
794 }
795 }
796
797#if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) || defined (RT_OS_SOLARIS) || defined(RT_OS_FREEBSD)
798 if (g_fDaemonize)
799 {
800 rc = RTProcDaemonizeUsingFork(false /* fNoChDir */, false /* fNoClose */, pszPidFile);
801 if (RT_FAILURE(rc))
802 return RTMsgErrorExit(RTEXITCODE_FAILURE, "failed to daemonize, rc=%Rrc. exiting.", rc);
803 }
804#endif
805
806 // initialize COM/XPCOM
807 HRESULT hrc = com::Initialize();
808 if (FAILED(hrc))
809 return RTMsgErrorExit(RTEXITCODE_FAILURE, "failed to initialize COM! hrc=%Rhrc\n", hrc);
810
811 hrc = g_pVirtualBoxClient.createInprocObject(CLSID_VirtualBoxClient);
812 if (FAILED(hrc))
813 {
814 RTMsgError("failed to create the VirtualBoxClient object!");
815 com::ErrorInfo info;
816 if (!info.isFullAvailable() && !info.isBasicAvailable())
817 {
818 com::GluePrintRCMessage(hrc);
819 RTMsgError("Most likely, the VirtualBox COM server is not running or failed to start.");
820 }
821 else
822 com::GluePrintErrorInfo(info);
823 return RTEXITCODE_FAILURE;
824 }
825
826 hrc = g_pVirtualBoxClient->COMGETTER(VirtualBox)(g_pVirtualBox.asOutParam());
827 if (FAILED(hrc))
828 {
829 RTMsgError("Failed to get VirtualBox object (rc=%Rhrc)!", hrc);
830 return RTEXITCODE_FAILURE;
831 }
832
833 /* VirtualBoxClient events registration. */
834 IEventListener *vboxClientListener = NULL;
835 {
836 ComPtr<IEventSource> pES;
837 CHECK_ERROR(g_pVirtualBoxClient, COMGETTER(EventSource)(pES.asOutParam()));
838 vboxClientListener = new VirtualBoxClientEventListenerImpl();
839 com::SafeArray<VBoxEventType_T> eventTypes;
840 eventTypes.push_back(VBoxEventType_OnVBoxSVCAvailabilityChanged);
841 CHECK_ERROR(pES, RegisterListener(vboxClientListener, ComSafeArrayAsInParam(eventTypes), true));
842 }
843
844 // create the global mutexes
845 g_pAuthLibLockHandle = new util::WriteLockHandle(util::LOCKCLASS_WEBSERVICE);
846 g_pVirtualBoxLockHandle = new util::RWLockHandle(util::LOCKCLASS_WEBSERVICE);
847 g_pSessionsLockHandle = new util::WriteLockHandle(util::LOCKCLASS_WEBSERVICE);
848 g_pThreadsLockHandle = new util::RWLockHandle(util::LOCKCLASS_OBJECTSTATE);
849 g_pWebLogLockHandle = new util::WriteLockHandle(util::LOCKCLASS_WEBSERVICE);
850
851 // SOAP queue pumper thread
852 rc = RTThreadCreate(NULL,
853 fntQPumper,
854 NULL, // pvUser
855 0, // cbStack (default)
856 RTTHREADTYPE_MAIN_WORKER,
857 0, // flags
858 "SoapQPumper");
859 if (RT_FAILURE(rc))
860 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Cannot start SOAP queue pumper thread: %Rrc", rc);
861
862 // watchdog thread
863 if (g_iWatchdogTimeoutSecs > 0)
864 {
865 // start our watchdog thread
866 rc = RTThreadCreate(NULL,
867 fntWatchdog,
868 NULL,
869 0,
870 RTTHREADTYPE_MAIN_WORKER,
871 0,
872 "Watchdog");
873 if (RT_FAILURE(rc))
874 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Cannot start watchdog thread: %Rrc", rc);
875 }
876
877 com::EventQueue *pQ = com::EventQueue::getMainEventQueue();
878 for (;;)
879 {
880 // we have to process main event queue
881 WEBDEBUG(("Pumping COM event queue\n"));
882 rc = pQ->processEventQueue(RT_INDEFINITE_WAIT);
883 if (RT_FAILURE(rc))
884 RTMsgError("processEventQueue -> %Rrc", rc);
885 }
886
887 /* VirtualBoxClient events unregistration. */
888 if (vboxClientListener)
889 {
890 ComPtr<IEventSource> pES;
891 CHECK_ERROR(g_pVirtualBoxClient, COMGETTER(EventSource)(pES.asOutParam()));
892 if (!pES.isNull())
893 CHECK_ERROR(pES, UnregisterListener(vboxClientListener));
894 vboxClientListener->Release();
895 }
896
897 com::Shutdown();
898
899 return 0;
900}
901
902/****************************************************************************
903 *
904 * Watchdog thread
905 *
906 ****************************************************************************/
907
908/**
909 * Watchdog thread, runs in the background while the webservice is alive.
910 *
911 * This gets started by main() and runs in the background to check all sessions
912 * for whether they have been no requests in a configurable timeout period. In
913 * that case, the session is automatically logged off.
914 */
915int fntWatchdog(RTTHREAD ThreadSelf, void *pvUser)
916{
917 // store a log prefix for this thread
918 util::AutoWriteLock thrLock(g_pThreadsLockHandle COMMA_LOCKVAL_SRC_POS);
919 g_mapThreads[RTThreadSelf()] = "[W ]";
920 thrLock.release();
921
922 WEBDEBUG(("Watchdog thread started\n"));
923
924 while (1)
925 {
926 WEBDEBUG(("Watchdog: sleeping %d seconds\n", g_iWatchdogCheckInterval));
927 RTThreadSleep(g_iWatchdogCheckInterval * 1000);
928
929 time_t tNow;
930 time(&tNow);
931
932 // we're messing with sessions, so lock them
933 util::AutoWriteLock lock(g_pSessionsLockHandle COMMA_LOCKVAL_SRC_POS);
934 WEBDEBUG(("Watchdog: checking %d sessions\n", g_mapSessions.size()));
935
936 SessionsMap::iterator it = g_mapSessions.begin(),
937 itEnd = g_mapSessions.end();
938 while (it != itEnd)
939 {
940 WebServiceSession *pSession = it->second;
941 WEBDEBUG(("Watchdog: tNow: %d, session timestamp: %d\n", tNow, pSession->getLastObjectLookup()));
942 if ( tNow
943 > pSession->getLastObjectLookup() + g_iWatchdogTimeoutSecs
944 )
945 {
946 WEBDEBUG(("Watchdog: Session %llX timed out, deleting\n", pSession->getID()));
947 delete pSession;
948 it = g_mapSessions.begin();
949 }
950 else
951 ++it;
952 }
953 }
954
955 WEBDEBUG(("Watchdog thread ending\n"));
956 return 0;
957}
958
959/****************************************************************************
960 *
961 * SOAP exceptions
962 *
963 ****************************************************************************/
964
965/**
966 * Helper function to raise a SOAP fault. Called by the other helper
967 * functions, which raise specific SOAP faults.
968 *
969 * @param soap
970 * @param str
971 * @param extype
972 * @param ex
973 */
974void RaiseSoapFault(struct soap *soap,
975 const char *pcsz,
976 int extype,
977 void *ex)
978{
979 // raise the fault
980 soap_sender_fault(soap, pcsz, NULL);
981
982 struct SOAP_ENV__Detail *pDetail = (struct SOAP_ENV__Detail*)soap_malloc(soap, sizeof(struct SOAP_ENV__Detail));
983
984 // without the following, gSOAP crashes miserably when sending out the
985 // data because it will try to serialize all fields (stupid documentation)
986 memset(pDetail, 0, sizeof(struct SOAP_ENV__Detail));
987
988 // fill extended info depending on SOAP version
989 if (soap->version == 2) // SOAP 1.2 is used
990 {
991 soap->fault->SOAP_ENV__Detail = pDetail;
992 soap->fault->SOAP_ENV__Detail->__type = extype;
993 soap->fault->SOAP_ENV__Detail->fault = ex;
994 soap->fault->SOAP_ENV__Detail->__any = NULL; // no other XML data
995 }
996 else
997 {
998 soap->fault->detail = pDetail;
999 soap->fault->detail->__type = extype;
1000 soap->fault->detail->fault = ex;
1001 soap->fault->detail->__any = NULL; // no other XML data
1002 }
1003}
1004
1005/**
1006 * Raises a SOAP fault that signals that an invalid object was passed.
1007 *
1008 * @param soap
1009 * @param obj
1010 */
1011void RaiseSoapInvalidObjectFault(struct soap *soap,
1012 WSDLT_ID obj)
1013{
1014 _vbox__InvalidObjectFault *ex = soap_new__vbox__InvalidObjectFault(soap, 1);
1015 ex->badObjectID = obj;
1016
1017 std::string str("VirtualBox error: ");
1018 str += "Invalid managed object reference \"" + obj + "\"";
1019
1020 RaiseSoapFault(soap,
1021 str.c_str(),
1022 SOAP_TYPE__vbox__InvalidObjectFault,
1023 ex);
1024}
1025
1026/**
1027 * Return a safe C++ string from the given COM string,
1028 * without crashing if the COM string is empty.
1029 * @param bstr
1030 * @return
1031 */
1032std::string ConvertComString(const com::Bstr &bstr)
1033{
1034 com::Utf8Str ustr(bstr);
1035 return ustr.c_str(); // @todo r=dj since the length is known, we can probably use a better std::string allocator
1036}
1037
1038/**
1039 * Return a safe C++ string from the given COM UUID,
1040 * without crashing if the UUID is empty.
1041 * @param bstr
1042 * @return
1043 */
1044std::string ConvertComString(const com::Guid &uuid)
1045{
1046 com::Utf8Str ustr(uuid.toString());
1047 return ustr.c_str(); // @todo r=dj since the length is known, we can probably use a better std::string allocator
1048}
1049
1050/**
1051 * Raises a SOAP runtime fault. Implementation for the RaiseSoapRuntimeFault template
1052 * function in vboxweb.h.
1053 *
1054 * @param pObj
1055 */
1056void RaiseSoapRuntimeFault2(struct soap *soap,
1057 HRESULT apirc,
1058 IUnknown *pObj,
1059 const com::Guid &iid)
1060{
1061 com::ErrorInfo info(pObj, iid.ref());
1062
1063 WEBDEBUG((" error, raising SOAP exception\n"));
1064
1065 RTStrmPrintf(g_pStdErr, "API return code: 0x%08X (%Rhrc)\n", apirc, apirc);
1066 RTStrmPrintf(g_pStdErr, "COM error info result code: 0x%lX\n", info.getResultCode());
1067 RTStrmPrintf(g_pStdErr, "COM error info text: %ls\n", info.getText().raw());
1068
1069 // allocated our own soap fault struct
1070 _vbox__RuntimeFault *ex = soap_new__vbox__RuntimeFault(soap, 1);
1071 // some old vbox methods return errors without setting an error in the error info,
1072 // so use the error info code if it's set and the HRESULT from the method otherwise
1073 if (S_OK == (ex->resultCode = info.getResultCode()))
1074 ex->resultCode = apirc;
1075 ex->text = ConvertComString(info.getText());
1076 ex->component = ConvertComString(info.getComponent());
1077 ex->interfaceID = ConvertComString(info.getInterfaceID());
1078
1079 // compose descriptive message
1080 com::Utf8StrFmt str("VirtualBox error: %s (0x%lX)", ex->text.c_str(), ex->resultCode);
1081
1082 RaiseSoapFault(soap,
1083 str.c_str(),
1084 SOAP_TYPE__vbox__RuntimeFault,
1085 ex);
1086}
1087
1088/****************************************************************************
1089 *
1090 * splitting and merging of object IDs
1091 *
1092 ****************************************************************************/
1093
1094uint64_t str2ulonglong(const char *pcsz)
1095{
1096 uint64_t u = 0;
1097 RTStrToUInt64Full(pcsz, 16, &u);
1098 return u;
1099}
1100
1101/**
1102 * Splits a managed object reference (in string form, as
1103 * passed in from a SOAP method call) into two integers for
1104 * session and object IDs, respectively.
1105 *
1106 * @param id
1107 * @param sessid
1108 * @param objid
1109 * @return
1110 */
1111bool SplitManagedObjectRef(const WSDLT_ID &id,
1112 uint64_t *pSessid,
1113 uint64_t *pObjid)
1114{
1115 // 64-bit numbers in hex have 16 digits; hence
1116 // the object-ref string must have 16 + "-" + 16 characters
1117 std::string str;
1118 if ( (id.length() == 33)
1119 && (id[16] == '-')
1120 )
1121 {
1122 char psz[34];
1123 memcpy(psz, id.c_str(), 34);
1124 psz[16] = '\0';
1125 if (pSessid)
1126 *pSessid = str2ulonglong(psz);
1127 if (pObjid)
1128 *pObjid = str2ulonglong(psz + 17);
1129 return true;
1130 }
1131
1132 return false;
1133}
1134
1135/**
1136 * Creates a managed object reference (in string form) from
1137 * two integers representing a session and object ID, respectively.
1138 *
1139 * @param sz Buffer with at least 34 bytes space to receive MOR string.
1140 * @param sessid
1141 * @param objid
1142 * @return
1143 */
1144void MakeManagedObjectRef(char *sz,
1145 uint64_t &sessid,
1146 uint64_t &objid)
1147{
1148 RTStrFormatNumber(sz, sessid, 16, 16, 0, RTSTR_F_64BIT | RTSTR_F_ZEROPAD);
1149 sz[16] = '-';
1150 RTStrFormatNumber(sz + 17, objid, 16, 16, 0, RTSTR_F_64BIT | RTSTR_F_ZEROPAD);
1151}
1152
1153/****************************************************************************
1154 *
1155 * class WebServiceSession
1156 *
1157 ****************************************************************************/
1158
1159class WebServiceSessionPrivate
1160{
1161 public:
1162 ManagedObjectsMapById _mapManagedObjectsById;
1163 ManagedObjectsMapByPtr _mapManagedObjectsByPtr;
1164};
1165
1166/**
1167 * Constructor for the session object.
1168 *
1169 * Preconditions: Caller must have locked g_pSessionsLockHandle.
1170 *
1171 * @param username
1172 * @param password
1173 */
1174WebServiceSession::WebServiceSession()
1175 : _fDestructing(false),
1176 _pISession(NULL),
1177 _tLastObjectLookup(0)
1178{
1179 _pp = new WebServiceSessionPrivate;
1180 _uSessionID = RTRandU64();
1181
1182 // register this session globally
1183 Assert(g_pSessionsLockHandle->isWriteLockOnCurrentThread());
1184 g_mapSessions[_uSessionID] = this;
1185}
1186
1187/**
1188 * Destructor. Cleans up and destroys all contained managed object references on the way.
1189 *
1190 * Preconditions: Caller must have locked g_pSessionsLockHandle.
1191 */
1192WebServiceSession::~WebServiceSession()
1193{
1194 // delete us from global map first so we can't be found
1195 // any more while we're cleaning up
1196 Assert(g_pSessionsLockHandle->isWriteLockOnCurrentThread());
1197 g_mapSessions.erase(_uSessionID);
1198
1199 // notify ManagedObjectRef destructor so it won't
1200 // remove itself from the maps; this avoids rebalancing
1201 // the map's tree on every delete as well
1202 _fDestructing = true;
1203
1204 // if (_pISession)
1205 // {
1206 // delete _pISession;
1207 // _pISession = NULL;
1208 // }
1209
1210 ManagedObjectsMapById::iterator it,
1211 end = _pp->_mapManagedObjectsById.end();
1212 for (it = _pp->_mapManagedObjectsById.begin();
1213 it != end;
1214 ++it)
1215 {
1216 ManagedObjectRef *pRef = it->second;
1217 delete pRef; // this frees the contained ComPtr as well
1218 }
1219
1220 delete _pp;
1221}
1222
1223/**
1224 * Authenticate the username and password against an authentication authority.
1225 *
1226 * @return 0 if the user was successfully authenticated, or an error code
1227 * otherwise.
1228 */
1229
1230int WebServiceSession::authenticate(const char *pcszUsername,
1231 const char *pcszPassword,
1232 IVirtualBox **ppVirtualBox)
1233{
1234 int rc = VERR_WEB_NOT_AUTHENTICATED;
1235 ComPtr<IVirtualBox> pVirtualBox;
1236 {
1237 util::AutoReadLock vlock(g_pVirtualBoxLockHandle COMMA_LOCKVAL_SRC_POS);
1238 pVirtualBox = g_pVirtualBox;
1239 }
1240 pVirtualBox.queryInterfaceTo(ppVirtualBox);
1241 if (pVirtualBox.isNull())
1242 return rc;
1243
1244 util::AutoReadLock lock(g_pAuthLibLockHandle COMMA_LOCKVAL_SRC_POS);
1245
1246 static bool fAuthLibLoaded = false;
1247 static PAUTHENTRY pfnAuthEntry = NULL;
1248 static PAUTHENTRY2 pfnAuthEntry2 = NULL;
1249 static PAUTHENTRY3 pfnAuthEntry3 = NULL;
1250
1251 if (!fAuthLibLoaded)
1252 {
1253 // retrieve authentication library from system properties
1254 ComPtr<ISystemProperties> systemProperties;
1255 pVirtualBox->COMGETTER(SystemProperties)(systemProperties.asOutParam());
1256
1257 com::Bstr authLibrary;
1258 systemProperties->COMGETTER(WebServiceAuthLibrary)(authLibrary.asOutParam());
1259 com::Utf8Str filename = authLibrary;
1260
1261 WEBDEBUG(("external authentication library is '%ls'\n", authLibrary.raw()));
1262
1263 if (filename == "null")
1264 // authentication disabled, let everyone in:
1265 fAuthLibLoaded = true;
1266 else
1267 {
1268 RTLDRMOD hlibAuth = 0;
1269 do
1270 {
1271 rc = RTLdrLoad(filename.c_str(), &hlibAuth);
1272 if (RT_FAILURE(rc))
1273 {
1274 WEBDEBUG(("%s() Failed to load external authentication library. Error code: %Rrc\n", __FUNCTION__, rc));
1275 break;
1276 }
1277
1278 if (RT_FAILURE(rc = RTLdrGetSymbol(hlibAuth, AUTHENTRY3_NAME, (void**)&pfnAuthEntry3)))
1279 WEBDEBUG(("%s(): Could not resolve import '%s'. Error code: %Rrc\n", __FUNCTION__, AUTHENTRY3_NAME, rc));
1280
1281 if (RT_FAILURE(rc = RTLdrGetSymbol(hlibAuth, AUTHENTRY2_NAME, (void**)&pfnAuthEntry2)))
1282 WEBDEBUG(("%s(): Could not resolve import '%s'. Error code: %Rrc\n", __FUNCTION__, AUTHENTRY2_NAME, rc));
1283
1284 if (RT_FAILURE(rc = RTLdrGetSymbol(hlibAuth, AUTHENTRY_NAME, (void**)&pfnAuthEntry)))
1285 WEBDEBUG(("%s(): Could not resolve import '%s'. Error code: %Rrc\n", __FUNCTION__, AUTHENTRY_NAME, rc));
1286
1287 if (pfnAuthEntry || pfnAuthEntry2 || pfnAuthEntry3)
1288 fAuthLibLoaded = true;
1289
1290 } while (0);
1291 }
1292 }
1293
1294 rc = VERR_WEB_NOT_AUTHENTICATED;
1295 AuthResult result;
1296 if (pfnAuthEntry3)
1297 {
1298 result = pfnAuthEntry3("webservice", NULL, AuthGuestNotAsked, pcszUsername, pcszPassword, NULL, true, 0);
1299 WEBDEBUG(("%s(): result of AuthEntry(): %d\n", __FUNCTION__, result));
1300 if (result == AuthResultAccessGranted)
1301 rc = 0;
1302 }
1303 else if (pfnAuthEntry2)
1304 {
1305 result = pfnAuthEntry2(NULL, AuthGuestNotAsked, pcszUsername, pcszPassword, NULL, true, 0);
1306 WEBDEBUG(("%s(): result of VRDPAuth2(): %d\n", __FUNCTION__, result));
1307 if (result == AuthResultAccessGranted)
1308 rc = 0;
1309 }
1310 else if (pfnAuthEntry)
1311 {
1312 result = pfnAuthEntry(NULL, AuthGuestNotAsked, pcszUsername, pcszPassword, NULL);
1313 WEBDEBUG(("%s(): result of VRDPAuth(%s, [%d]): %d\n", __FUNCTION__, pcszUsername, strlen(pcszPassword), result));
1314 if (result == AuthResultAccessGranted)
1315 rc = 0;
1316 }
1317 else if (fAuthLibLoaded)
1318 // fAuthLibLoaded = true but both pointers are NULL:
1319 // then the authlib was "null" and auth was disabled
1320 rc = 0;
1321 else
1322 {
1323 WEBDEBUG(("Could not resolve AuthEntry, VRDPAuth2 or VRDPAuth entry point"));
1324 }
1325
1326 lock.release();
1327
1328 if (!rc)
1329 {
1330 do
1331 {
1332 // now create the ISession object that this webservice session can use
1333 // (and of which IWebsessionManager::getSessionObject returns a managed object reference)
1334 ComPtr<ISession> session;
1335 rc = g_pVirtualBoxClient->COMGETTER(Session)(session.asOutParam());
1336 if (FAILED(rc))
1337 {
1338 WEBDEBUG(("ERROR: cannot create session object!"));
1339 break;
1340 }
1341
1342 ComPtr<IUnknown> p2 = session;
1343 _pISession = new ManagedObjectRef(*this,
1344 p2, // IUnknown *pobjUnknown
1345 session, // void *pobjInterface
1346 com::Guid(COM_IIDOF(ISession)),
1347 g_pcszISession);
1348
1349 if (g_fVerbose)
1350 {
1351 ISession *p = session;
1352 WEBDEBUG((" * %s: created session object with comptr 0x%lX, MOR = %s\n", __FUNCTION__, p, _pISession->getWSDLID().c_str()));
1353 }
1354 } while (0);
1355 }
1356
1357 return rc;
1358}
1359
1360/**
1361 * Look up, in this session, whether a ManagedObjectRef has already been
1362 * created for the given COM pointer.
1363 *
1364 * Note how we require that a ComPtr<IUnknown> is passed, which causes a
1365 * queryInterface call when the caller passes in a different type, since
1366 * a ComPtr<IUnknown> will point to something different than a
1367 * ComPtr<IVirtualBox>, for example. As we store the ComPtr<IUnknown> in
1368 * our private hash table, we must search for one too.
1369 *
1370 * Preconditions: Caller must have locked g_pSessionsLockHandle.
1371 *
1372 * @param pcu pointer to a COM object.
1373 * @return The existing ManagedObjectRef that represents the COM object, or NULL if there's none yet.
1374 */
1375ManagedObjectRef* WebServiceSession::findRefFromPtr(const IUnknown *pObject)
1376{
1377 Assert(g_pSessionsLockHandle->isWriteLockOnCurrentThread());
1378
1379 uintptr_t ulp = (uintptr_t)pObject;
1380 // WEBDEBUG((" %s: looking up 0x%lX\n", __FUNCTION__, ulp));
1381 ManagedObjectsMapByPtr::iterator it = _pp->_mapManagedObjectsByPtr.find(ulp);
1382 if (it != _pp->_mapManagedObjectsByPtr.end())
1383 {
1384 ManagedObjectRef *pRef = it->second;
1385 WEBDEBUG((" %s: found existing ref %s (%s) for COM obj 0x%lX\n", __FUNCTION__, pRef->getWSDLID().c_str(), pRef->getInterfaceName(), ulp));
1386 return pRef;
1387 }
1388
1389 return NULL;
1390}
1391
1392/**
1393 * Static method which attempts to find the session for which the given managed
1394 * object reference was created, by splitting the reference into the session and
1395 * object IDs and then looking up the session object for that session ID.
1396 *
1397 * Preconditions: Caller must have locked g_pSessionsLockHandle in read mode.
1398 *
1399 * @param id Managed object reference (with combined session and object IDs).
1400 * @return
1401 */
1402WebServiceSession* WebServiceSession::findSessionFromRef(const WSDLT_ID &id)
1403{
1404 Assert(g_pSessionsLockHandle->isWriteLockOnCurrentThread());
1405
1406 WebServiceSession *pSession = NULL;
1407 uint64_t sessid;
1408 if (SplitManagedObjectRef(id,
1409 &sessid,
1410 NULL))
1411 {
1412 SessionsMapIterator it = g_mapSessions.find(sessid);
1413 if (it != g_mapSessions.end())
1414 pSession = it->second;
1415 }
1416 return pSession;
1417}
1418
1419/**
1420 *
1421 */
1422const WSDLT_ID& WebServiceSession::getSessionWSDLID() const
1423{
1424 return _pISession->getWSDLID();
1425}
1426
1427/**
1428 * Touches the webservice session to prevent it from timing out.
1429 *
1430 * Each webservice session has an internal timestamp that records
1431 * the last request made to it from the client that started it.
1432 * If no request was made within a configurable timeframe, then
1433 * the client is logged off automatically,
1434 * by calling IWebsessionManager::logoff()
1435 */
1436void WebServiceSession::touch()
1437{
1438 time(&_tLastObjectLookup);
1439}
1440
1441
1442/****************************************************************************
1443 *
1444 * class ManagedObjectRef
1445 *
1446 ****************************************************************************/
1447
1448/**
1449 * Constructor, which assigns a unique ID to this managed object
1450 * reference and stores it two global hashes:
1451 *
1452 * a) G_mapManagedObjectsById, which maps ManagedObjectID's to
1453 * instances of this class; this hash is then used by the
1454 * findObjectFromRef() template function in vboxweb.h
1455 * to quickly retrieve the COM object from its managed
1456 * object ID (mostly in the context of the method mappers
1457 * in methodmaps.cpp, when a web service client passes in
1458 * a managed object ID);
1459 *
1460 * b) G_mapManagedObjectsByComPtr, which maps COM pointers to
1461 * instances of this class; this hash is used by
1462 * createRefFromObject() to quickly figure out whether an
1463 * instance already exists for a given COM pointer.
1464 *
1465 * This constructor calls AddRef() on the given COM object, and
1466 * the destructor will call Release(). We require two input pointers
1467 * for that COM object, one generic IUnknown* pointer which is used
1468 * as the map key, and a specific interface pointer (e.g. IMachine*)
1469 * which must support the interface given in guidInterface. All
1470 * three values are returned by getPtr(), which gives future callers
1471 * a chance to reuse the specific interface pointer without having
1472 * to call QueryInterface, which can be expensive.
1473 *
1474 * This does _not_ check whether another instance already
1475 * exists in the hash. This gets called only from the
1476 * createOrFindRefFromComPtr() template function in vboxweb.h, which
1477 * does perform that check.
1478 *
1479 * Preconditions: Caller must have locked g_pSessionsLockHandle.
1480 *
1481 * @param session Session to which the MOR will be added.
1482 * @param pobjUnknown Pointer to IUnknown* interface for the COM object; this will be used in the hashes.
1483 * @param pobjInterface Pointer to a specific interface for the COM object, described by guidInterface.
1484 * @param guidInterface Interface which pobjInterface points to.
1485 * @param pcszInterface String representation of that interface (e.g. "IMachine") for readability and logging.
1486 */
1487ManagedObjectRef::ManagedObjectRef(WebServiceSession &session,
1488 IUnknown *pobjUnknown,
1489 void *pobjInterface,
1490 const com::Guid &guidInterface,
1491 const char *pcszInterface)
1492 : _session(session),
1493 _pobjUnknown(pobjUnknown),
1494 _pobjInterface(pobjInterface),
1495 _guidInterface(guidInterface),
1496 _pcszInterface(pcszInterface)
1497{
1498 Assert(pobjUnknown);
1499 Assert(pobjInterface);
1500
1501 // keep both stubs alive while this MOR exists (matching Release() calls are in destructor)
1502 uint32_t cRefs1 = pobjUnknown->AddRef();
1503 uint32_t cRefs2 = ((IUnknown*)pobjInterface)->AddRef();
1504 _ulp = (uintptr_t)pobjUnknown;
1505
1506 Assert(g_pSessionsLockHandle->isWriteLockOnCurrentThread());
1507 _id = ++g_iMaxManagedObjectID;
1508 // and count globally
1509 ULONG64 cTotal = ++g_cManagedObjects; // raise global count and make a copy for the debug message below
1510
1511 char sz[34];
1512 MakeManagedObjectRef(sz, session._uSessionID, _id);
1513 _strID = sz;
1514
1515 session._pp->_mapManagedObjectsById[_id] = this;
1516 session._pp->_mapManagedObjectsByPtr[_ulp] = this;
1517
1518 session.touch();
1519
1520 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",
1521 __FUNCTION__,
1522 pcszInterface,
1523 pobjInterface,
1524 pobjUnknown,
1525 cRefs1,
1526 cRefs2,
1527 _id,
1528 cTotal));
1529}
1530
1531/**
1532 * Destructor; removes the instance from the global hash of
1533 * managed objects. Calls Release() on the contained COM object.
1534 *
1535 * Preconditions: Caller must have locked g_pSessionsLockHandle.
1536 */
1537ManagedObjectRef::~ManagedObjectRef()
1538{
1539 Assert(g_pSessionsLockHandle->isWriteLockOnCurrentThread());
1540 ULONG64 cTotal = --g_cManagedObjects;
1541
1542 Assert(_pobjUnknown);
1543 Assert(_pobjInterface);
1544
1545 // we called AddRef() on both interfaces, so call Release() on
1546 // both as well, but in reverse order
1547 uint32_t cRefs2 = ((IUnknown*)_pobjInterface)->Release();
1548 uint32_t cRefs1 = _pobjUnknown->Release();
1549 WEBDEBUG((" * %s: deleting MOR for ID %llX (%s; COM refcount now %RI32/%RI32); now %lld objects total\n", __FUNCTION__, _id, _pcszInterface, cRefs1, cRefs2, cTotal));
1550
1551 // if we're being destroyed from the session's destructor,
1552 // then that destructor is iterating over the maps, so
1553 // don't remove us there! (data integrity + speed)
1554 if (!_session._fDestructing)
1555 {
1556 WEBDEBUG((" * %s: removing from session maps\n", __FUNCTION__));
1557 _session._pp->_mapManagedObjectsById.erase(_id);
1558 if (_session._pp->_mapManagedObjectsByPtr.erase(_ulp) != 1)
1559 WEBDEBUG((" WARNING: could not find %llX in _mapManagedObjectsByPtr\n", _ulp));
1560 }
1561}
1562
1563/**
1564 * Static helper method for findObjectFromRef() template that actually
1565 * looks up the object from a given integer ID.
1566 *
1567 * This has been extracted into this non-template function to reduce
1568 * code bloat as we have the actual STL map lookup only in this function.
1569 *
1570 * This also "touches" the timestamp in the session whose ID is encoded
1571 * in the given integer ID, in order to prevent the session from timing
1572 * out.
1573 *
1574 * Preconditions: Caller must have locked g_mutexSessions.
1575 *
1576 * @param strId
1577 * @param iter
1578 * @return
1579 */
1580int ManagedObjectRef::findRefFromId(const WSDLT_ID &id,
1581 ManagedObjectRef **pRef,
1582 bool fNullAllowed)
1583{
1584 int rc = 0;
1585
1586 do
1587 {
1588 // allow NULL (== empty string) input reference, which should return a NULL pointer
1589 if (!id.length() && fNullAllowed)
1590 {
1591 *pRef = NULL;
1592 return 0;
1593 }
1594
1595 uint64_t sessid;
1596 uint64_t objid;
1597 WEBDEBUG((" %s(): looking up objref %s\n", __FUNCTION__, id.c_str()));
1598 if (!SplitManagedObjectRef(id,
1599 &sessid,
1600 &objid))
1601 {
1602 rc = VERR_WEB_INVALID_MANAGED_OBJECT_REFERENCE;
1603 break;
1604 }
1605
1606 SessionsMapIterator it = g_mapSessions.find(sessid);
1607 if (it == g_mapSessions.end())
1608 {
1609 WEBDEBUG((" %s: cannot find session for objref %s\n", __FUNCTION__, id.c_str()));
1610 rc = VERR_WEB_INVALID_SESSION_ID;
1611 break;
1612 }
1613
1614 WebServiceSession *pSess = it->second;
1615 // "touch" session to prevent it from timing out
1616 pSess->touch();
1617
1618 ManagedObjectsIteratorById iter = pSess->_pp->_mapManagedObjectsById.find(objid);
1619 if (iter == pSess->_pp->_mapManagedObjectsById.end())
1620 {
1621 WEBDEBUG((" %s: cannot find comobj for objref %s\n", __FUNCTION__, id.c_str()));
1622 rc = VERR_WEB_INVALID_OBJECT_ID;
1623 break;
1624 }
1625
1626 *pRef = iter->second;
1627
1628 } while (0);
1629
1630 return rc;
1631}
1632
1633/****************************************************************************
1634 *
1635 * interface IManagedObjectRef
1636 *
1637 ****************************************************************************/
1638
1639/**
1640 * This is the hard-coded implementation for the IManagedObjectRef::getInterfaceName()
1641 * that our WSDL promises to our web service clients. This method returns a
1642 * string describing the interface that this managed object reference
1643 * supports, e.g. "IMachine".
1644 *
1645 * @param soap
1646 * @param req
1647 * @param resp
1648 * @return
1649 */
1650int __vbox__IManagedObjectRef_USCOREgetInterfaceName(
1651 struct soap *soap,
1652 _vbox__IManagedObjectRef_USCOREgetInterfaceName *req,
1653 _vbox__IManagedObjectRef_USCOREgetInterfaceNameResponse *resp)
1654{
1655 HRESULT rc = S_OK;
1656 WEBDEBUG(("-- entering %s\n", __FUNCTION__));
1657
1658 do
1659 {
1660 // findRefFromId require the lock
1661 util::AutoWriteLock lock(g_pSessionsLockHandle COMMA_LOCKVAL_SRC_POS);
1662
1663 ManagedObjectRef *pRef;
1664 if (!ManagedObjectRef::findRefFromId(req->_USCOREthis, &pRef, false))
1665 resp->returnval = pRef->getInterfaceName();
1666
1667 } while (0);
1668
1669 WEBDEBUG(("-- leaving %s, rc: 0x%lX\n", __FUNCTION__, rc));
1670 if (FAILED(rc))
1671 return SOAP_FAULT;
1672 return SOAP_OK;
1673}
1674
1675/**
1676 * This is the hard-coded implementation for the IManagedObjectRef::release()
1677 * that our WSDL promises to our web service clients. This method releases
1678 * a managed object reference and removes it from our stacks.
1679 *
1680 * @param soap
1681 * @param req
1682 * @param resp
1683 * @return
1684 */
1685int __vbox__IManagedObjectRef_USCORErelease(
1686 struct soap *soap,
1687 _vbox__IManagedObjectRef_USCORErelease *req,
1688 _vbox__IManagedObjectRef_USCOREreleaseResponse *resp)
1689{
1690 HRESULT rc = S_OK;
1691 WEBDEBUG(("-- entering %s\n", __FUNCTION__));
1692
1693 do
1694 {
1695 // findRefFromId and the delete call below require the lock
1696 util::AutoWriteLock lock(g_pSessionsLockHandle COMMA_LOCKVAL_SRC_POS);
1697
1698 ManagedObjectRef *pRef;
1699 if ((rc = ManagedObjectRef::findRefFromId(req->_USCOREthis, &pRef, false)))
1700 {
1701 RaiseSoapInvalidObjectFault(soap, req->_USCOREthis);
1702 break;
1703 }
1704
1705 WEBDEBUG((" found reference; deleting!\n"));
1706 // this removes the object from all stacks; since
1707 // there's a ComPtr<> hidden inside the reference,
1708 // this should also invoke Release() on the COM
1709 // object
1710 delete pRef;
1711 } while (0);
1712
1713 WEBDEBUG(("-- leaving %s, rc: 0x%lX\n", __FUNCTION__, rc));
1714 if (FAILED(rc))
1715 return SOAP_FAULT;
1716 return SOAP_OK;
1717}
1718
1719/****************************************************************************
1720 *
1721 * interface IWebsessionManager
1722 *
1723 ****************************************************************************/
1724
1725/**
1726 * Hard-coded implementation for IWebsessionManager::logon. As opposed to the underlying
1727 * COM API, this is the first method that a webservice client must call before the
1728 * webservice will do anything useful.
1729 *
1730 * This returns a managed object reference to the global IVirtualBox object; into this
1731 * reference a session ID is encoded which remains constant with all managed object
1732 * references returned by other methods.
1733 *
1734 * This also creates an instance of ISession, which is stored internally with the
1735 * webservice session and can be retrieved with IWebsessionManager::getSessionObject
1736 * (__vbox__IWebsessionManager_USCOREgetSessionObject). In order for the
1737 * VirtualBox web service to do anything useful, one usually needs both a
1738 * VirtualBox and an ISession object, for which these two methods are designed.
1739 *
1740 * When the webservice client is done, it should call IWebsessionManager::logoff. This
1741 * will clean up internally (destroy all remaining managed object references and
1742 * related COM objects used internally).
1743 *
1744 * After logon, an internal timeout ensures that if the webservice client does not
1745 * call any methods, after a configurable number of seconds, the webservice will log
1746 * off the client automatically. This is to ensure that the webservice does not
1747 * drown in managed object references and eventually deny service. Still, it is
1748 * a much better solution, both for performance and cleanliness, for the webservice
1749 * client to clean up itself.
1750 *
1751 * @param
1752 * @param vbox__IWebsessionManager_USCORElogon
1753 * @param vbox__IWebsessionManager_USCORElogonResponse
1754 * @return
1755 */
1756int __vbox__IWebsessionManager_USCORElogon(
1757 struct soap *soap,
1758 _vbox__IWebsessionManager_USCORElogon *req,
1759 _vbox__IWebsessionManager_USCORElogonResponse *resp)
1760{
1761 HRESULT rc = S_OK;
1762 WEBDEBUG(("-- entering %s\n", __FUNCTION__));
1763
1764 do
1765 {
1766 // WebServiceSession constructor tinkers with global MOR map and requires a write lock
1767 util::AutoWriteLock lock(g_pSessionsLockHandle COMMA_LOCKVAL_SRC_POS);
1768
1769 // create new session; the constructor stores the new session
1770 // in the global map automatically
1771 WebServiceSession *pSession = new WebServiceSession();
1772 ComPtr<IVirtualBox> pVirtualBox;
1773
1774 // authenticate the user
1775 if (!(pSession->authenticate(req->username.c_str(),
1776 req->password.c_str(),
1777 pVirtualBox.asOutParam())))
1778 {
1779 // in the new session, create a managed object reference (MOR) for the
1780 // global VirtualBox object; this encodes the session ID in the MOR so
1781 // that it will be implicitly be included in all future requests of this
1782 // webservice client
1783 ComPtr<IUnknown> p2 = pVirtualBox;
1784 if (pVirtualBox.isNull() || p2.isNull())
1785 {
1786 rc = E_FAIL;
1787 break;
1788 }
1789 ManagedObjectRef *pRef = new ManagedObjectRef(*pSession,
1790 p2, // IUnknown *pobjUnknown
1791 pVirtualBox, // void *pobjInterface
1792 COM_IIDOF(IVirtualBox),
1793 g_pcszIVirtualBox);
1794 resp->returnval = pRef->getWSDLID();
1795 WEBDEBUG(("VirtualBox object ref is %s\n", resp->returnval.c_str()));
1796 }
1797 else
1798 rc = E_FAIL;
1799 } while (0);
1800
1801 WEBDEBUG(("-- leaving %s, rc: 0x%lX\n", __FUNCTION__, rc));
1802 if (FAILED(rc))
1803 return SOAP_FAULT;
1804 return SOAP_OK;
1805}
1806
1807/**
1808 * Returns the ISession object that was created for the webservice client
1809 * on logon.
1810 */
1811int __vbox__IWebsessionManager_USCOREgetSessionObject(
1812 struct soap*,
1813 _vbox__IWebsessionManager_USCOREgetSessionObject *req,
1814 _vbox__IWebsessionManager_USCOREgetSessionObjectResponse *resp)
1815{
1816 HRESULT rc = S_OK;
1817 WEBDEBUG(("-- entering %s\n", __FUNCTION__));
1818
1819 do
1820 {
1821 // findSessionFromRef needs lock
1822 util::AutoWriteLock lock(g_pSessionsLockHandle COMMA_LOCKVAL_SRC_POS);
1823
1824 WebServiceSession* pSession;
1825 if ((pSession = WebServiceSession::findSessionFromRef(req->refIVirtualBox)))
1826 resp->returnval = pSession->getSessionWSDLID();
1827
1828 } while (0);
1829
1830 WEBDEBUG(("-- leaving %s, rc: 0x%lX\n", __FUNCTION__, rc));
1831 if (FAILED(rc))
1832 return SOAP_FAULT;
1833 return SOAP_OK;
1834}
1835
1836/**
1837 * hard-coded implementation for IWebsessionManager::logoff.
1838 *
1839 * @param
1840 * @param vbox__IWebsessionManager_USCORElogon
1841 * @param vbox__IWebsessionManager_USCORElogonResponse
1842 * @return
1843 */
1844int __vbox__IWebsessionManager_USCORElogoff(
1845 struct soap*,
1846 _vbox__IWebsessionManager_USCORElogoff *req,
1847 _vbox__IWebsessionManager_USCORElogoffResponse *resp)
1848{
1849 HRESULT rc = S_OK;
1850 WEBDEBUG(("-- entering %s\n", __FUNCTION__));
1851
1852 do
1853 {
1854 // findSessionFromRef and the session destructor require the lock
1855 util::AutoWriteLock lock(g_pSessionsLockHandle COMMA_LOCKVAL_SRC_POS);
1856
1857 WebServiceSession* pSession;
1858 if ((pSession = WebServiceSession::findSessionFromRef(req->refIVirtualBox)))
1859 {
1860 delete pSession;
1861 // destructor cleans up
1862
1863 WEBDEBUG(("session destroyed, %d sessions left open\n", g_mapSessions.size()));
1864 }
1865 } while (0);
1866
1867 WEBDEBUG(("-- leaving %s, rc: 0x%lX\n", __FUNCTION__, rc));
1868 if (FAILED(rc))
1869 return SOAP_FAULT;
1870 return SOAP_OK;
1871}
1872
注意: 瀏覽 TracBrowser 來幫助您使用儲存庫瀏覽器

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette