VirtualBox

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

Last change on this file since 31598 was 31598, checked in by vboxsync, 14 years ago

EventQueue: Don't return VERR_TIMEOUT if we processed a message in waitForEventsOnDarwin. return VINF_INTERRUPTED when encountering EINTR.

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

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