VirtualBox

source: vbox/trunk/src/VBox/Main/GuestImpl.cpp@ 28887

Last change on this file since 28887 was 28887, checked in by vboxsync, 15 years ago

Guest Control/Main: Faster (no polling on guest side), free data on shutdown.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 40.3 KB
Line 
1/* $Id: GuestImpl.cpp 28887 2010-04-29 11:19:17Z vboxsync $ */
2
3/** @file
4 *
5 * VirtualBox COM class implementation
6 */
7
8/*
9 * Copyright (C) 2006-2008 Oracle Corporation
10 *
11 * This file is part of VirtualBox Open Source Edition (OSE), as
12 * available from http://www.virtualbox.org. This file is free software;
13 * you can redistribute it and/or modify it under the terms of the GNU
14 * General Public License (GPL) as published by the Free Software
15 * Foundation, in version 2 as it comes in the "COPYING" file of the
16 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
17 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
18 */
19
20#include "GuestImpl.h"
21
22#include "Global.h"
23#include "ConsoleImpl.h"
24#include "ProgressImpl.h"
25#include "VMMDev.h"
26
27#include "AutoCaller.h"
28#include "Logging.h"
29
30#include <VBox/VMMDev.h>
31#ifdef VBOX_WITH_GUEST_CONTROL
32# include <VBox/com/array.h>
33#endif
34#include <iprt/cpp/utils.h>
35#include <iprt/getopt.h>
36#include <VBox/pgm.h>
37
38// defines
39/////////////////////////////////////////////////////////////////////////////
40
41// constructor / destructor
42/////////////////////////////////////////////////////////////////////////////
43
44DEFINE_EMPTY_CTOR_DTOR (Guest)
45
46HRESULT Guest::FinalConstruct()
47{
48 return S_OK;
49}
50
51void Guest::FinalRelease()
52{
53 uninit ();
54}
55
56// public methods only for internal purposes
57/////////////////////////////////////////////////////////////////////////////
58
59/**
60 * Initializes the guest object.
61 */
62HRESULT Guest::init (Console *aParent)
63{
64 LogFlowThisFunc(("aParent=%p\n", aParent));
65
66 ComAssertRet(aParent, E_INVALIDARG);
67
68 /* Enclose the state transition NotReady->InInit->Ready */
69 AutoInitSpan autoInitSpan(this);
70 AssertReturn(autoInitSpan.isOk(), E_FAIL);
71
72 unconst(mParent) = aParent;
73
74 /* mData.mAdditionsActive is FALSE */
75
76 /* Confirm a successful initialization when it's the case */
77 autoInitSpan.setSucceeded();
78
79 ULONG aMemoryBalloonSize;
80 HRESULT ret = mParent->machine()->COMGETTER(MemoryBalloonSize)(&aMemoryBalloonSize);
81 if (ret == S_OK)
82 mMemoryBalloonSize = aMemoryBalloonSize;
83 else
84 mMemoryBalloonSize = 0; /* Default is no ballooning */
85
86 mStatUpdateInterval = 0; /* Default is not to report guest statistics at all */
87
88 /* Clear statistics. */
89 for (unsigned i = 0 ; i < GUESTSTATTYPE_MAX; i++)
90 mCurrentGuestStat[i] = 0;
91
92#ifdef VBOX_WITH_GUEST_CONTROL
93 /* Init the context ID counter at 1000. */
94 mNextContextID = 1000;
95#endif
96
97 return S_OK;
98}
99
100/**
101 * Uninitializes the instance and sets the ready flag to FALSE.
102 * Called either from FinalRelease() or by the parent when it gets destroyed.
103 */
104void Guest::uninit()
105{
106 LogFlowThisFunc(("\n"));
107
108#ifdef VBOX_WITH_GUEST_CONTROL
109 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
110
111 /* Clean up callback data. */
112 CallbackListIter it;
113 for (it = mCallbackList.begin(); it != mCallbackList.end(); it++)
114 removeCtrlCallbackContext(it);
115
116 /* Clear process list. */
117 mGuestProcessList.clear();
118#endif
119
120 /* Enclose the state transition Ready->InUninit->NotReady */
121 AutoUninitSpan autoUninitSpan(this);
122 if (autoUninitSpan.uninitDone())
123 return;
124
125 unconst(mParent) = NULL;
126}
127
128// IGuest properties
129/////////////////////////////////////////////////////////////////////////////
130
131STDMETHODIMP Guest::COMGETTER(OSTypeId) (BSTR *aOSTypeId)
132{
133 CheckComArgOutPointerValid(aOSTypeId);
134
135 AutoCaller autoCaller(this);
136 if (FAILED(autoCaller.rc())) return autoCaller.rc();
137
138 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
139
140 // redirect the call to IMachine if no additions are installed
141 if (mData.mAdditionsVersion.isEmpty())
142 return mParent->machine()->COMGETTER(OSTypeId)(aOSTypeId);
143
144 mData.mOSTypeId.cloneTo(aOSTypeId);
145
146 return S_OK;
147}
148
149STDMETHODIMP Guest::COMGETTER(AdditionsActive) (BOOL *aAdditionsActive)
150{
151 CheckComArgOutPointerValid(aAdditionsActive);
152
153 AutoCaller autoCaller(this);
154 if (FAILED(autoCaller.rc())) return autoCaller.rc();
155
156 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
157
158 *aAdditionsActive = mData.mAdditionsActive;
159
160 return S_OK;
161}
162
163STDMETHODIMP Guest::COMGETTER(AdditionsVersion) (BSTR *aAdditionsVersion)
164{
165 CheckComArgOutPointerValid(aAdditionsVersion);
166
167 AutoCaller autoCaller(this);
168 if (FAILED(autoCaller.rc())) return autoCaller.rc();
169
170 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
171
172 mData.mAdditionsVersion.cloneTo(aAdditionsVersion);
173
174 return S_OK;
175}
176
177STDMETHODIMP Guest::COMGETTER(SupportsSeamless) (BOOL *aSupportsSeamless)
178{
179 CheckComArgOutPointerValid(aSupportsSeamless);
180
181 AutoCaller autoCaller(this);
182 if (FAILED(autoCaller.rc())) return autoCaller.rc();
183
184 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
185
186 *aSupportsSeamless = mData.mSupportsSeamless;
187
188 return S_OK;
189}
190
191STDMETHODIMP Guest::COMGETTER(SupportsGraphics) (BOOL *aSupportsGraphics)
192{
193 CheckComArgOutPointerValid(aSupportsGraphics);
194
195 AutoCaller autoCaller(this);
196 if (FAILED(autoCaller.rc())) return autoCaller.rc();
197
198 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
199
200 *aSupportsGraphics = mData.mSupportsGraphics;
201
202 return S_OK;
203}
204
205STDMETHODIMP Guest::COMGETTER(MemoryBalloonSize) (ULONG *aMemoryBalloonSize)
206{
207 CheckComArgOutPointerValid(aMemoryBalloonSize);
208
209 AutoCaller autoCaller(this);
210 if (FAILED(autoCaller.rc())) return autoCaller.rc();
211
212 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
213
214 *aMemoryBalloonSize = mMemoryBalloonSize;
215
216 return S_OK;
217}
218
219STDMETHODIMP Guest::COMSETTER(MemoryBalloonSize) (ULONG aMemoryBalloonSize)
220{
221 AutoCaller autoCaller(this);
222 if (FAILED(autoCaller.rc())) return autoCaller.rc();
223
224 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
225
226 /* We must be 100% sure that IMachine::COMSETTER(MemoryBalloonSize)
227 * does not call us back in any way! */
228 HRESULT ret = mParent->machine()->COMSETTER(MemoryBalloonSize)(aMemoryBalloonSize);
229 if (ret == S_OK)
230 {
231 mMemoryBalloonSize = aMemoryBalloonSize;
232 /* forward the information to the VMM device */
233 VMMDev *vmmDev = mParent->getVMMDev();
234 if (vmmDev)
235 vmmDev->getVMMDevPort()->pfnSetMemoryBalloon(vmmDev->getVMMDevPort(), aMemoryBalloonSize);
236 }
237
238 return ret;
239}
240
241STDMETHODIMP Guest::COMGETTER(StatisticsUpdateInterval)(ULONG *aUpdateInterval)
242{
243 CheckComArgOutPointerValid(aUpdateInterval);
244
245 AutoCaller autoCaller(this);
246 if (FAILED(autoCaller.rc())) return autoCaller.rc();
247
248 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
249
250 *aUpdateInterval = mStatUpdateInterval;
251 return S_OK;
252}
253
254STDMETHODIMP Guest::COMSETTER(StatisticsUpdateInterval)(ULONG aUpdateInterval)
255{
256 AutoCaller autoCaller(this);
257 if (FAILED(autoCaller.rc())) return autoCaller.rc();
258
259 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
260
261 mStatUpdateInterval = aUpdateInterval;
262 /* forward the information to the VMM device */
263 VMMDev *vmmDev = mParent->getVMMDev();
264 if (vmmDev)
265 vmmDev->getVMMDevPort()->pfnSetStatisticsInterval(vmmDev->getVMMDevPort(), aUpdateInterval);
266
267 return S_OK;
268}
269
270STDMETHODIMP Guest::InternalGetStatistics(ULONG *aCpuUser, ULONG *aCpuKernel, ULONG *aCpuIdle,
271 ULONG *aMemTotal, ULONG *aMemFree, ULONG *aMemBalloon,
272 ULONG *aMemCache, ULONG *aPageTotal,
273 ULONG *aMemAllocTotal, ULONG *aMemFreeTotal, ULONG *aMemBalloonTotal)
274{
275 CheckComArgOutPointerValid(aCpuUser);
276 CheckComArgOutPointerValid(aCpuKernel);
277 CheckComArgOutPointerValid(aCpuIdle);
278 CheckComArgOutPointerValid(aMemTotal);
279 CheckComArgOutPointerValid(aMemFree);
280 CheckComArgOutPointerValid(aMemBalloon);
281 CheckComArgOutPointerValid(aMemCache);
282 CheckComArgOutPointerValid(aPageTotal);
283
284 AutoCaller autoCaller(this);
285 if (FAILED(autoCaller.rc())) return autoCaller.rc();
286
287 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
288
289 *aCpuUser = mCurrentGuestStat[GUESTSTATTYPE_CPUUSER];
290 *aCpuKernel = mCurrentGuestStat[GUESTSTATTYPE_CPUKERNEL];
291 *aCpuIdle = mCurrentGuestStat[GUESTSTATTYPE_CPUIDLE];
292 *aMemTotal = mCurrentGuestStat[GUESTSTATTYPE_MEMTOTAL] * (_4K/_1K); /* page (4K) -> 1KB units */
293 *aMemFree = mCurrentGuestStat[GUESTSTATTYPE_MEMFREE] * (_4K/_1K); /* page (4K) -> 1KB units */
294 *aMemBalloon = mCurrentGuestStat[GUESTSTATTYPE_MEMBALLOON] * (_4K/_1K); /* page (4K) -> 1KB units */
295 *aMemCache = mCurrentGuestStat[GUESTSTATTYPE_MEMCACHE] * (_4K/_1K); /* page (4K) -> 1KB units */
296 *aPageTotal = mCurrentGuestStat[GUESTSTATTYPE_PAGETOTAL] * (_4K/_1K); /* page (4K) -> 1KB units */
297
298 Console::SafeVMPtr pVM (mParent);
299 if (pVM.isOk())
300 {
301 uint64_t uFreeTotal, uAllocTotal, uBalloonedTotal;
302 *aMemFreeTotal = 0;
303 int rc = PGMR3QueryVMMMemoryStats(pVM.raw(), &uAllocTotal, &uFreeTotal, &uBalloonedTotal);
304 AssertRC(rc);
305 if (rc == VINF_SUCCESS)
306 {
307 *aMemAllocTotal = (ULONG)(uAllocTotal / _1K); /* bytes -> KB */
308 *aMemFreeTotal = (ULONG)(uFreeTotal / _1K);
309 *aMemBalloonTotal = (ULONG)(uBalloonedTotal / _1K);
310 }
311 }
312 else
313 *aMemFreeTotal = 0;
314
315 return S_OK;
316}
317
318HRESULT Guest::SetStatistic(ULONG aCpuId, GUESTSTATTYPE enmType, ULONG aVal)
319{
320 AutoCaller autoCaller(this);
321 if (FAILED(autoCaller.rc())) return autoCaller.rc();
322
323 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
324
325 if (enmType >= GUESTSTATTYPE_MAX)
326 return E_INVALIDARG;
327
328 mCurrentGuestStat[enmType] = aVal;
329 return S_OK;
330}
331
332STDMETHODIMP Guest::SetCredentials(IN_BSTR aUserName, IN_BSTR aPassword,
333 IN_BSTR aDomain, BOOL aAllowInteractiveLogon)
334{
335 AutoCaller autoCaller(this);
336 if (FAILED(autoCaller.rc())) return autoCaller.rc();
337
338 /* forward the information to the VMM device */
339 VMMDev *vmmDev = mParent->getVMMDev();
340 if (vmmDev)
341 {
342 uint32_t u32Flags = VMMDEV_SETCREDENTIALS_GUESTLOGON;
343 if (!aAllowInteractiveLogon)
344 u32Flags = VMMDEV_SETCREDENTIALS_NOLOCALLOGON;
345
346 vmmDev->getVMMDevPort()->pfnSetCredentials(vmmDev->getVMMDevPort(),
347 Utf8Str(aUserName).raw(), Utf8Str(aPassword).raw(),
348 Utf8Str(aDomain).raw(), u32Flags);
349 return S_OK;
350 }
351
352 return setError(VBOX_E_VM_ERROR,
353 tr("VMM device is not available (is the VM running?)"));
354}
355
356#ifdef VBOX_WITH_GUEST_CONTROL
357/**
358 * Appends environment variables to the environment block. Each var=value pair is separated
359 * by NULL (\0) sequence. The whole block will be stored in one blob and disassembled on the
360 * guest side later to fit into the HGCM param structure.
361 *
362 * @returns VBox status code.
363 *
364 * @todo
365 *
366 */
367int Guest::prepareExecuteEnv(const char *pszEnv, void **ppvList, uint32_t *pcbList, uint32_t *pcEnv)
368{
369 int rc = VINF_SUCCESS;
370 uint32_t cbLen = strlen(pszEnv);
371 if (*ppvList)
372 {
373 uint32_t cbNewLen = *pcbList + cbLen + 1; /* Include zero termination. */
374 char *pvTmp = (char*)RTMemRealloc(*ppvList, cbNewLen);
375 if (NULL == pvTmp)
376 {
377 rc = VERR_NO_MEMORY;
378 }
379 else
380 {
381 memcpy(pvTmp + *pcbList, pszEnv, cbLen);
382 pvTmp[cbNewLen - 1] = '\0'; /* Add zero termination. */
383 *ppvList = (void**)pvTmp;
384 }
385 }
386 else
387 {
388 char *pcTmp;
389 if (RTStrAPrintf(&pcTmp, "%s", pszEnv) > 0)
390 {
391 *ppvList = (void**)pcTmp;
392 /* Reset counters. */
393 *pcEnv = 0;
394 *pcbList = 0;
395 }
396 }
397 if (RT_SUCCESS(rc))
398 {
399 *pcbList += cbLen + 1; /* Include zero termination. */
400 *pcEnv += 1; /* Increase env pairs count. */
401 }
402 return rc;
403}
404
405/**
406 * Static callback function for receiving updates on guest control commands
407 * from the guest. Acts as a dispatcher for the actual class instance.
408 *
409 * @returns VBox status code.
410 *
411 * @todo
412 *
413 */
414DECLCALLBACK(int) Guest::doGuestCtrlNotification(void *pvExtension,
415 uint32_t u32Function,
416 void *pvParms,
417 uint32_t cbParms)
418{
419 using namespace guestControl;
420
421 /*
422 * No locking, as this is purely a notification which does not make any
423 * changes to the object state.
424 */
425 LogFlowFunc(("pvExtension = %p, u32Function = %d, pvParms = %p, cbParms = %d\n",
426 pvExtension, u32Function, pvParms, cbParms));
427 ComObjPtr<Guest> pGuest = reinterpret_cast<Guest *>(pvExtension);
428
429 int rc = VINF_SUCCESS;
430 if (u32Function == GUEST_EXEC_SEND_STATUS)
431 {
432 LogFlowFunc(("GUEST_EXEC_SEND_STATUS\n"));
433
434 PHOSTEXECCALLBACKDATA pCBData = reinterpret_cast<PHOSTEXECCALLBACKDATA>(pvParms);
435 AssertPtr(pCBData);
436 AssertReturn(sizeof(HOSTEXECCALLBACKDATA) == cbParms, VERR_INVALID_PARAMETER);
437 AssertReturn(HOSTEXECCALLBACKDATAMAGIC == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
438
439 rc = pGuest->notifyCtrlExec(u32Function, pCBData);
440 }
441 else if (u32Function == GUEST_EXEC_SEND_OUTPUT)
442 {
443 LogFlowFunc(("GUEST_EXEC_SEND_OUTPUT\n"));
444
445 PHOSTEXECOUTCALLBACKDATA pCBData = reinterpret_cast<PHOSTEXECOUTCALLBACKDATA>(pvParms);
446 AssertPtr(pCBData);
447 AssertReturn(sizeof(HOSTEXECOUTCALLBACKDATA) == cbParms, VERR_INVALID_PARAMETER);
448 AssertReturn(HOSTEXECOUTCALLBACKDATAMAGIC == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
449
450 rc = pGuest->notifyCtrlExecOut(u32Function, pCBData);
451 }
452 else
453 rc = VERR_NOT_SUPPORTED;
454 return rc;
455}
456
457/* Function for handling the execution start/termination notification. */
458int Guest::notifyCtrlExec(uint32_t u32Function,
459 PHOSTEXECCALLBACKDATA pData)
460{
461 LogFlowFuncEnter();
462 int rc = VINF_SUCCESS;
463
464 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
465
466 AssertPtr(pData);
467 CallbackListIter it = getCtrlCallbackContextByID(pData->hdr.u32ContextID);
468
469 /* Callback can be called several times. */
470 if (it != mCallbackList.end())
471 {
472 PHOSTEXECCALLBACKDATA pCBData = (HOSTEXECCALLBACKDATA*)it->pvData;
473 AssertPtr(pCBData);
474
475 pCBData->u32PID = pData->u32PID;
476 pCBData->u32Status = pData->u32Status;
477 pCBData->u32Flags = pData->u32Flags;
478 /** @todo Copy void* buffer contents! */
479
480 /* Do progress handling. */
481 Utf8Str errMsg;
482 HRESULT rc2 = S_OK;
483 switch (pData->u32Status)
484 {
485 case PROC_STS_STARTED:
486 break;
487
488 case PROC_STS_TEN: /* Terminated normally. */
489 if (!it->pProgress->getCompleted())
490 it->pProgress->notifyComplete(S_OK);
491 break;
492
493 case PROC_STS_TEA: /* Terminated abnormally. */
494 errMsg = Utf8StrFmt(Guest::tr("Process terminated abnormally with status '%u'"),
495 pCBData->u32Flags);
496 break;
497
498 case PROC_STS_TES: /* Terminated through signal. */
499 errMsg = Utf8StrFmt(Guest::tr("Process terminated via signal with status '%u'"),
500 pCBData->u32Flags);
501 break;
502
503 case PROC_STS_TOK:
504 errMsg = Utf8StrFmt(Guest::tr("Process timed out and was killed"));
505 break;
506
507 case PROC_STS_TOA:
508 errMsg = Utf8StrFmt(Guest::tr("Process timed out and could not be killed"));
509 break;
510
511 case PROC_STS_DWN:
512 errMsg = Utf8StrFmt(Guest::tr("Process exited because system is shutting down"));
513 break;
514
515 default:
516 break;
517 }
518
519 /* Handle process list. */
520 /** @todo What happens on/deal with PID reuse? */
521 /** @todo How to deal with multiple updates at once? */
522 GuestProcessIter it_proc = getProcessByPID(pCBData->u32PID);
523 if (it_proc == mGuestProcessList.end())
524 {
525 /* Not found, add to list. */
526 GuestProcess p;
527 p.mPID = pCBData->u32PID;
528 p.mStatus = pCBData->u32Status;
529 p.mExitCode = pCBData->u32Flags; /* Contains exit code. */
530 p.mFlags = 0;
531
532 mGuestProcessList.push_back(p);
533 }
534 else /* Update list. */
535 {
536 it_proc->mStatus = pCBData->u32Status;
537 it_proc->mExitCode = pCBData->u32Flags; /* Contains exit code. */
538 it_proc->mFlags = 0;
539 }
540
541 if ( !it->pProgress.isNull()
542 && errMsg.length())
543 {
544 if (!it->pProgress->getCompleted())
545 {
546 it->pProgress->notifyComplete(E_FAIL /** @todo Find a better rc! */, COM_IIDOF(IGuest),
547 (CBSTR)Guest::getComponentName(), errMsg.c_str());
548 LogFlowFunc(("Callback (context ID=%u, status=%u) progress marked as completed\n",
549 pData->hdr.u32ContextID, pData->u32Status));
550 }
551 else
552 LogFlowFunc(("Callback (context ID=%u, status=%u) progress already marked as completed\n",
553 pData->hdr.u32ContextID, pData->u32Status));
554 }
555 ASMAtomicWriteBool(&it->bCalled, true);
556 }
557 else
558 LogFlowFunc(("Unexpected callback (magic=%u, context ID=%u) arrived\n", pData->hdr.u32Magic, pData->hdr.u32ContextID));
559 LogFlowFuncLeave();
560 return rc;
561}
562
563/* Function for handling the execution output notification. */
564int Guest::notifyCtrlExecOut(uint32_t u32Function,
565 PHOSTEXECOUTCALLBACKDATA pData)
566{
567 LogFlowFuncEnter();
568 int rc = VINF_SUCCESS;
569
570 AssertPtr(pData);
571 CallbackListIter it = getCtrlCallbackContextByID(pData->hdr.u32ContextID);
572 if (it != mCallbackList.end())
573 {
574 Assert(!it->bCalled);
575 PHOSTEXECOUTCALLBACKDATA pCBData = (HOSTEXECOUTCALLBACKDATA*)it->pvData;
576 AssertPtr(pCBData);
577
578 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
579
580 pCBData->u32PID = pData->u32PID;
581 pCBData->u32HandleId = pData->u32HandleId;
582 pCBData->u32Flags = pData->u32Flags;
583
584 /* Make sure we really got something! */
585 if ( pData->cbData
586 && pData->pvData)
587 {
588 /* Allocate data buffer and copy it */
589 pCBData->pvData = RTMemAlloc(pData->cbData);
590 AssertReturn(pCBData->pvData, VERR_NO_MEMORY);
591 memcpy(pCBData->pvData, pData->pvData, pData->cbData);
592 pCBData->cbData = pData->cbData;
593 }
594 else
595 {
596 pCBData->pvData = NULL;
597 pCBData->cbData = 0;
598 }
599 ASMAtomicWriteBool(&it->bCalled, true);
600 }
601 else
602 LogFlowFunc(("Unexpected callback (magic=%u, context ID=%u) arrived\n", pData->hdr.u32Magic, pData->hdr.u32ContextID));
603 LogFlowFuncLeave();
604 return rc;
605}
606
607Guest::CallbackListIter Guest::getCtrlCallbackContextByID(uint32_t u32ContextID)
608{
609 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
610
611 /** @todo Maybe use a map instead of list for fast context lookup. */
612 CallbackListIter it;
613 for (it = mCallbackList.begin(); it != mCallbackList.end(); it++)
614 {
615 if (it->mContextID == u32ContextID)
616 return (it);
617 }
618 return it;
619}
620
621Guest::GuestProcessIter Guest::getProcessByPID(uint32_t u32PID)
622{
623 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
624
625 /** @todo Maybe use a map instead of list for fast context lookup. */
626 GuestProcessIter it;
627 for (it = mGuestProcessList.begin(); it != mGuestProcessList.end(); it++)
628 {
629 if (it->mPID == u32PID)
630 return (it);
631 }
632 return it;
633}
634
635void Guest::removeCtrlCallbackContext(Guest::CallbackListIter it)
636{
637 LogFlowFuncEnter();
638 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
639 if (it->pvData)
640 {
641 RTMemFree(it->pvData);
642 it->pvData = NULL;
643 it->cbData = 0;
644
645 /* Notify outstanding waits for progress ... */
646 if ( !it->pProgress.isNull()
647 && !it->pProgress->getCompleted())
648 {
649 it->pProgress->notifyComplete(S_OK);
650 it->pProgress = NULL;
651 }
652 }
653 LogFlowFuncLeave();
654}
655
656/* Adds a callback with a user provided data block and an optional progress object
657 * to the callback list. A callback is identified by a unique context ID which is used
658 * to identify a callback from the guest side. */
659uint32_t Guest::addCtrlCallbackContext(eVBoxGuestCtrlCallbackType enmType, void *pvData, uint32_t cbData, Progress *pProgress)
660{
661 LogFlowFuncEnter();
662 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
663 uint32_t uNewContext = ASMAtomicIncU32(&mNextContextID);
664 if (uNewContext == UINT32_MAX)
665 ASMAtomicUoWriteU32(&mNextContextID, 1000);
666
667 CallbackContext context;
668 context.mContextID = uNewContext;
669 context.mType = enmType;
670 context.bCalled = false;
671 context.pvData = pvData;
672 context.cbData = cbData;
673 context.pProgress = pProgress;
674
675 mCallbackList.push_back(context);
676 if (mCallbackList.size() > 256) /* Don't let the container size get too big! */
677 {
678 Guest::CallbackListIter it = mCallbackList.begin();
679 removeCtrlCallbackContext(it);
680 mCallbackList.erase(it);
681 }
682
683 LogFlowFuncLeave();
684 return uNewContext;
685}
686#endif /* VBOX_WITH_GUEST_CONTROL */
687
688STDMETHODIMP Guest::ExecuteProcess(IN_BSTR aCommand, ULONG aFlags,
689 ComSafeArrayIn(IN_BSTR, aArguments), ComSafeArrayIn(IN_BSTR, aEnvironment),
690 IN_BSTR aStdIn, IN_BSTR aStdOut, IN_BSTR aStdErr,
691 IN_BSTR aUserName, IN_BSTR aPassword,
692 ULONG aTimeoutMS, ULONG *aPID, IProgress **aProgress)
693{
694#ifndef VBOX_WITH_GUEST_CONTROL
695 ReturnComNotImplemented();
696#else /* VBOX_WITH_GUEST_CONTROL */
697 using namespace guestControl;
698
699 CheckComArgStrNotEmptyOrNull(aCommand);
700 CheckComArgOutPointerValid(aPID);
701 CheckComArgOutPointerValid(aProgress);
702 if (aFlags != 0) /* Flags are not supported at the moment. */
703 return E_INVALIDARG;
704
705 HRESULT rc = S_OK;
706
707 try
708 {
709 AutoCaller autoCaller(this);
710 if (FAILED(autoCaller.rc())) return autoCaller.rc();
711
712 /*
713 * Create progress object.
714 */
715 ComObjPtr <Progress> progress;
716 rc = progress.createObject();
717 if (SUCCEEDED(rc))
718 {
719 rc = progress->init(static_cast<IGuest*>(this),
720 BstrFmt(tr("Executing process")),
721 FALSE);
722 }
723 if (FAILED(rc)) return rc;
724
725 /*
726 * Prepare process execution.
727 */
728 int vrc = VINF_SUCCESS;
729 Utf8Str Utf8Command(aCommand);
730
731 /* Adjust timeout */
732 if (aTimeoutMS == 0)
733 aTimeoutMS = UINT32_MAX;
734
735 /* Prepare arguments. */
736 char **papszArgv = NULL;
737 uint32_t uNumArgs = 0;
738 if (aArguments > 0)
739 {
740 com::SafeArray<IN_BSTR> args(ComSafeArrayInArg(aArguments));
741 uNumArgs = args.size();
742 papszArgv = (char**)RTMemAlloc(sizeof(char*) * (uNumArgs + 1));
743 AssertReturn(papszArgv, VERR_NO_MEMORY);
744 for (unsigned i = 0; RT_SUCCESS(vrc) && i < uNumArgs; i++)
745 vrc = RTStrAPrintf(&papszArgv[i], "%s", Utf8Str(args[i]).raw());
746 papszArgv[uNumArgs] = NULL;
747 }
748
749 Utf8Str Utf8StdIn(aStdIn);
750 Utf8Str Utf8StdOut(aStdOut);
751 Utf8Str Utf8StdErr(aStdErr);
752 Utf8Str Utf8UserName(aUserName);
753 Utf8Str Utf8Password(aPassword);
754 if (RT_SUCCESS(vrc))
755 {
756 uint32_t uContextID = 0;
757
758 char *pszArgs = NULL;
759 if (uNumArgs > 0)
760 vrc = RTGetOptArgvToString(&pszArgs, papszArgv, 0);
761 if (RT_SUCCESS(vrc))
762 {
763 uint32_t cbArgs = pszArgs ? strlen(pszArgs) + 1 : 0; /* Include terminating zero. */
764
765 /* Prepare environment. */
766 void *pvEnv = NULL;
767 uint32_t uNumEnv = 0;
768 uint32_t cbEnv = 0;
769 if (aEnvironment > 0)
770 {
771 com::SafeArray<IN_BSTR> env(ComSafeArrayInArg(aEnvironment));
772
773 for (unsigned i = 0; i < env.size(); i++)
774 {
775 vrc = prepareExecuteEnv(Utf8Str(env[i]).raw(), &pvEnv, &cbEnv, &uNumEnv);
776 if (RT_FAILURE(vrc))
777 break;
778 }
779 }
780
781 if (RT_SUCCESS(vrc))
782 {
783 PHOSTEXECCALLBACKDATA pData = (HOSTEXECCALLBACKDATA*)RTMemAlloc(sizeof(HOSTEXECCALLBACKDATA));
784 AssertReturn(pData, VBOX_E_IPRT_ERROR);
785 uContextID = addCtrlCallbackContext(VBOXGUESTCTRLCALLBACKTYPE_EXEC_START,
786 pData, sizeof(HOSTEXECCALLBACKDATA), progress);
787 Assert(uContextID > 0);
788
789 VBOXHGCMSVCPARM paParms[15];
790 int i = 0;
791 paParms[i++].setUInt32(uContextID);
792 paParms[i++].setPointer((void*)Utf8Command.raw(), (uint32_t)strlen(Utf8Command.raw()) + 1);
793 paParms[i++].setUInt32(aFlags);
794 paParms[i++].setUInt32(uNumArgs);
795 paParms[i++].setPointer((void*)pszArgs, cbArgs);
796 paParms[i++].setUInt32(uNumEnv);
797 paParms[i++].setUInt32(cbEnv);
798 paParms[i++].setPointer((void*)pvEnv, cbEnv);
799 paParms[i++].setPointer((void*)Utf8StdIn.raw(), (uint32_t)strlen(Utf8StdIn.raw()) + 1);
800 paParms[i++].setPointer((void*)Utf8StdOut.raw(), (uint32_t)strlen(Utf8StdOut.raw()) + 1);
801 paParms[i++].setPointer((void*)Utf8StdErr.raw(), (uint32_t)strlen(Utf8StdErr.raw()) + 1);
802 paParms[i++].setPointer((void*)Utf8UserName.raw(), (uint32_t)strlen(Utf8UserName.raw()) + 1);
803 paParms[i++].setPointer((void*)Utf8Password.raw(), (uint32_t)strlen(Utf8Password.raw()) + 1);
804 paParms[i++].setUInt32(aTimeoutMS);
805
806 /* Make sure mParent is valid, so set a read lock in this scope. */
807 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
808
809 /* Forward the information to the VMM device. */
810 AssertPtr(mParent);
811 VMMDev *vmmDev = mParent->getVMMDev();
812 if (vmmDev)
813 {
814 LogFlowFunc(("hgcmHostCall numParms=%d\n", i));
815 vrc = vmmDev->hgcmHostCall("VBoxGuestControlSvc", HOST_EXEC_CMD,
816 i, paParms);
817 }
818 else
819 vrc = VERR_INVALID_VM_HANDLE;
820 RTMemFree(pvEnv);
821 }
822 RTStrFree(pszArgs);
823 }
824 if (RT_SUCCESS(vrc))
825 {
826 LogFlowFunc(("Waiting for HGCM callback (timeout=%ldms) ...\n", aTimeoutMS));
827
828 /*
829 * Wait for the HGCM low level callback until the process
830 * has been started (or something went wrong). This is necessary to
831 * get the PID.
832 */
833 CallbackListIter it = getCtrlCallbackContextByID(uContextID);
834 if (it != mCallbackList.end())
835 {
836 uint64_t u64Started = RTTimeMilliTS();
837 while (!it->bCalled)
838 {
839 unsigned cMsWait;
840 if (aTimeoutMS == RT_INDEFINITE_WAIT)
841 cMsWait = 10;
842 else
843 {
844 uint64_t cMsElapsed = RTTimeMilliTS() - u64Started;
845 if (cMsElapsed >= aTimeoutMS)
846 break; /* Timed out. */
847 cMsWait = RT_MIN(10, aTimeoutMS - (uint32_t)cMsElapsed);
848 }
849 RTThreadSleep(cMsWait);
850 }
851 }
852
853 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
854
855 /* Did we get some status? */
856 PHOSTEXECCALLBACKDATA pData = (HOSTEXECCALLBACKDATA*)it->pvData;
857 Assert(it->cbData == sizeof(HOSTEXECCALLBACKDATA));
858 AssertPtr(pData);
859 if (it->bCalled)
860 {
861 switch (pData->u32Status)
862 {
863 case PROC_STS_STARTED:
864 /* Process is (still) running; get PID. */
865 *aPID = pData->u32PID;
866 break;
867
868 /* In any other case the process either already
869 * terminated or something else went wrong, so no PID ... */
870 case PROC_STS_TEN: /* Terminated normally. */
871 case PROC_STS_TEA: /* Terminated abnormally. */
872 case PROC_STS_TES: /* Terminated through signal. */
873 case PROC_STS_TOK:
874 case PROC_STS_TOA:
875 case PROC_STS_DWN:
876 /*
877 * Process (already) ended, but we want to get the
878 * PID anyway to retrieve the output in a later call.
879 */
880 *aPID = pData->u32PID;
881 break;
882
883 case PROC_STS_ERROR:
884 vrc = pData->u32Flags; /* u32Flags member contains IPRT error code. */
885 break;
886
887 default:
888 vrc = VERR_INVALID_PARAMETER; /* Unknown status, should never happen! */
889 break;
890 }
891 }
892 else /* If callback not called within time ... well, that's a timeout! */
893 vrc = VERR_TIMEOUT;
894
895 alock.release();
896
897 /*
898 * Do *not* remove the callback yet - we might wait with the IProgress object on something
899 * else (like end of process) ...
900 */
901 if (RT_FAILURE(vrc))
902 {
903 if (vrc == VERR_FILE_NOT_FOUND) /* This is the most likely error. */
904 {
905 rc = setError(VBOX_E_IPRT_ERROR,
906 tr("The file '%s' was not found on guest"), Utf8Command.raw());
907 }
908 else if (vrc == VERR_BAD_EXE_FORMAT)
909 {
910 rc = setError(VBOX_E_IPRT_ERROR,
911 tr("The file '%s' is not an executable format on guest"), Utf8Command.raw());
912 }
913 else if (vrc == VERR_LOGON_FAILURE)
914 {
915 rc = setError(VBOX_E_IPRT_ERROR,
916 tr("The specified user '%s' was not able to logon on guest"), Utf8UserName.raw());
917 }
918 else if (vrc == VERR_TIMEOUT)
919 {
920 rc = setError(VBOX_E_IPRT_ERROR,
921 tr("The guest did not respond within time (%ums)"), aTimeoutMS);
922 }
923 else if (vrc == VERR_INVALID_PARAMETER)
924 {
925 rc = setError(VBOX_E_IPRT_ERROR,
926 tr("The guest reported an unknown process status (%u)"), pData->u32Status);
927 }
928 else
929 {
930 rc = setError(E_UNEXPECTED,
931 tr("The service call failed with error %Rrc"), vrc);
932 }
933 }
934 else
935 {
936 /* Return the progress to the caller. */
937 progress.queryInterfaceTo(aProgress);
938 }
939 }
940 else
941 {
942 if (vrc == VERR_INVALID_VM_HANDLE)
943 {
944 rc = setError(VBOX_E_VM_ERROR,
945 tr("VMM device is not available (is the VM running?)"));
946 }
947 else if (vrc == VERR_TIMEOUT)
948 {
949 rc = setError(VBOX_E_VM_ERROR,
950 tr("The guest execution service is not ready"));
951 }
952 else /* HGCM call went wrong. */
953 {
954 rc = setError(E_UNEXPECTED,
955 tr("The HGCM call failed with error %Rrc"), vrc);
956 }
957 }
958
959 for (unsigned i = 0; i < uNumArgs; i++)
960 RTMemFree(papszArgv[i]);
961 RTMemFree(papszArgv);
962 }
963 }
964 catch (std::bad_alloc &)
965 {
966 rc = E_OUTOFMEMORY;
967 }
968 return rc;
969#endif /* VBOX_WITH_GUEST_CONTROL */
970}
971
972STDMETHODIMP Guest::GetProcessOutput(ULONG aPID, ULONG aFlags, ULONG aTimeoutMS, ULONG64 aSize, ComSafeArrayOut(BYTE, aData))
973{
974#ifndef VBOX_WITH_GUEST_CONTROL
975 ReturnComNotImplemented();
976#else /* VBOX_WITH_GUEST_CONTROL */
977 using namespace guestControl;
978
979 HRESULT rc = S_OK;
980
981 try
982 {
983 /* Adjust timeout */
984 if (aTimeoutMS == 0)
985 aTimeoutMS = UINT32_MAX;
986
987 /* Search for existing PID. */
988 PHOSTEXECOUTCALLBACKDATA pData = (HOSTEXECOUTCALLBACKDATA*)RTMemAlloc(sizeof(HOSTEXECOUTCALLBACKDATA));
989 AssertReturn(pData, VBOX_E_IPRT_ERROR);
990 uint32_t uContextID = addCtrlCallbackContext(VBOXGUESTCTRLCALLBACKTYPE_EXEC_OUTPUT,
991 pData, sizeof(HOSTEXECOUTCALLBACKDATA), NULL /* pProgress */);
992 Assert(uContextID > 0);
993
994 size_t cbData = (size_t)RT_MIN(aSize, _64K);
995 com::SafeArray<BYTE> outputData(cbData);
996
997 VBOXHGCMSVCPARM paParms[5];
998 int i = 0;
999 paParms[i++].setUInt32(uContextID);
1000 paParms[i++].setUInt32(aPID);
1001 paParms[i++].setUInt32(aFlags); /** @todo Should represent stdout and/or stderr. */
1002
1003 int vrc = VINF_SUCCESS;
1004
1005 {
1006 /* Make sure mParent is valid, so set the read lock while using. */
1007 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
1008
1009 /* Forward the information to the VMM device. */
1010 AssertPtr(mParent);
1011 VMMDev *vmmDev = mParent->getVMMDev();
1012 if (vmmDev)
1013 {
1014 LogFlowFunc(("hgcmHostCall numParms=%d\n", i));
1015 vrc = vmmDev->hgcmHostCall("VBoxGuestControlSvc", HOST_EXEC_GET_OUTPUT,
1016 i, paParms);
1017 }
1018 }
1019
1020 if (RT_SUCCESS(vrc))
1021 {
1022 LogFlowFunc(("Waiting for HGCM callback (timeout=%ldms) ...\n", aTimeoutMS));
1023
1024 /*
1025 * Wait for the HGCM low level callback until the process
1026 * has been started (or something went wrong). This is necessary to
1027 * get the PID.
1028 */
1029 CallbackListIter it = getCtrlCallbackContextByID(uContextID);
1030 if (it != mCallbackList.end())
1031 {
1032 uint64_t u64Started = RTTimeMilliTS();
1033 while (!it->bCalled)
1034 {
1035 unsigned cMsWait;
1036 if (aTimeoutMS == RT_INDEFINITE_WAIT)
1037 cMsWait = 10;
1038 else
1039 {
1040 uint64_t cMsElapsed = RTTimeMilliTS() - u64Started;
1041 if (cMsElapsed >= aTimeoutMS)
1042 break; /* timed out */
1043 cMsWait = RT_MIN(10, aTimeoutMS - (uint32_t)cMsElapsed);
1044 }
1045 RTThreadSleep(cMsWait);
1046 }
1047
1048 if (it->bCalled)
1049 {
1050 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
1051
1052 /* Did we get some output? */
1053 pData = (HOSTEXECOUTCALLBACKDATA*)it->pvData;
1054 Assert(it->cbData == sizeof(HOSTEXECOUTCALLBACKDATA));
1055 AssertPtr(pData);
1056
1057 if (pData->cbData)
1058 {
1059 /* Do we need to resize the array? */
1060 if (pData->cbData > cbData)
1061 outputData.resize(pData->cbData);
1062
1063 /* Fill output in supplied out buffer. */
1064 memcpy(outputData.raw(), pData->pvData, pData->cbData);
1065 outputData.resize(pData->cbData); /* Shrink to fit actual buffer size. */
1066 }
1067 else
1068 vrc = VERR_NO_DATA; /* This is not an error we want to report to COM. */
1069 }
1070 else /* If callback not called within time ... well, that's a timeout! */
1071 vrc = VERR_TIMEOUT;
1072
1073 if (RT_FAILURE(vrc))
1074 {
1075 if (vrc == VERR_NO_DATA)
1076 {
1077 /* This is not an error we want to report to COM. */
1078 }
1079 else if (vrc == VERR_TIMEOUT)
1080 {
1081 rc = setError(VBOX_E_IPRT_ERROR,
1082 tr("The guest did not output within time (%ums)"), aTimeoutMS);
1083 }
1084 else
1085 {
1086 rc = setError(E_UNEXPECTED,
1087 tr("The service call failed with error %Rrc"), vrc);
1088 }
1089 }
1090 }
1091 else
1092 rc = setError(VBOX_E_IPRT_ERROR,
1093 tr("Process (PID %u) not found!"), aPID);
1094 }
1095 else
1096 rc = setError(E_UNEXPECTED,
1097 tr("The HGCM call failed with error %Rrc"), vrc);
1098
1099 /* If something failed (or there simply was no data, indicated by VERR_NO_DATA,
1100 * we return an empty array so that the frontend knows when to give up. */
1101 if (RT_FAILURE(vrc) || FAILED(rc))
1102 outputData.resize(0);
1103 outputData.detachTo(ComSafeArrayOutArg(aData));
1104 }
1105 catch (std::bad_alloc &)
1106 {
1107 rc = E_OUTOFMEMORY;
1108 }
1109 return rc;
1110#endif
1111}
1112
1113STDMETHODIMP Guest::GetProcessStatus(ULONG aPID, ULONG *aExitCode, ULONG *aFlags, ULONG *aStatus)
1114{
1115#ifndef VBOX_WITH_GUEST_CONTROL
1116 ReturnComNotImplemented();
1117#else /* VBOX_WITH_GUEST_CONTROL */
1118 using namespace guestControl;
1119
1120 HRESULT rc = S_OK;
1121
1122 try
1123 {
1124 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
1125
1126 GuestProcessIterConst it;
1127 for (it = mGuestProcessList.begin(); it != mGuestProcessList.end(); it++)
1128 {
1129 if (it->mPID == aPID)
1130 break;
1131 }
1132
1133 if (it != mGuestProcessList.end())
1134 {
1135 *aExitCode = it->mExitCode;
1136 *aFlags = it->mFlags;
1137 *aStatus = it->mStatus;
1138 }
1139 else
1140 rc = setError(VBOX_E_IPRT_ERROR,
1141 tr("Process (PID %u) not found!"), aPID);
1142 }
1143 catch (std::bad_alloc &)
1144 {
1145 rc = E_OUTOFMEMORY;
1146 }
1147 return rc;
1148#endif
1149}
1150
1151// public methods only for internal purposes
1152/////////////////////////////////////////////////////////////////////////////
1153
1154void Guest::setAdditionsVersion(Bstr aVersion, VBOXOSTYPE aOsType)
1155{
1156 AutoCaller autoCaller(this);
1157 AssertComRCReturnVoid (autoCaller.rc());
1158
1159 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1160
1161 mData.mAdditionsVersion = aVersion;
1162 mData.mAdditionsActive = !aVersion.isEmpty();
1163 /* Older Additions didn't have this finer grained capability bit,
1164 * so enable it by default. Newer Additions will disable it immediately
1165 * if relevant. */
1166 mData.mSupportsGraphics = mData.mAdditionsActive;
1167
1168 mData.mOSTypeId = Global::OSTypeId (aOsType);
1169}
1170
1171void Guest::setSupportsSeamless (BOOL aSupportsSeamless)
1172{
1173 AutoCaller autoCaller(this);
1174 AssertComRCReturnVoid (autoCaller.rc());
1175
1176 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1177
1178 mData.mSupportsSeamless = aSupportsSeamless;
1179}
1180
1181void Guest::setSupportsGraphics (BOOL aSupportsGraphics)
1182{
1183 AutoCaller autoCaller(this);
1184 AssertComRCReturnVoid (autoCaller.rc());
1185
1186 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1187
1188 mData.mSupportsGraphics = aSupportsGraphics;
1189}
1190/* vi: set tabstop=4 shiftwidth=4 expandtab: */
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