VirtualBox

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

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

Initial commit of Guest Additions runlevels; this replaces the additionsActive flag to get more detailed information of the current Guest Additions state.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 60.2 KB
Line 
1/* $Id: GuestImpl.cpp 32020 2010-08-27 07:08:38Z vboxsync $ */
2
3/** @file
4 *
5 * VirtualBox COM class implementation
6 */
7
8/*
9 * Copyright (C) 2006-2010 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 /* Confirm a successful initialization when it's the case */
75 autoInitSpan.setSucceeded();
76
77 ULONG aMemoryBalloonSize;
78 HRESULT ret = mParent->machine()->COMGETTER(MemoryBalloonSize)(&aMemoryBalloonSize);
79 if (ret == S_OK)
80 mMemoryBalloonSize = aMemoryBalloonSize;
81 else
82 mMemoryBalloonSize = 0; /* Default is no ballooning */
83
84 BOOL fPageFusionEnabled;
85 ret = mParent->machine()->COMGETTER(PageFusionEnabled)(&fPageFusionEnabled);
86 if (ret == S_OK)
87 mfPageFusionEnabled = fPageFusionEnabled;
88 else
89 mfPageFusionEnabled = false; /* Default is no page fusion*/
90
91 mStatUpdateInterval = 0; /* Default is not to report guest statistics at all */
92
93 /* Clear statistics. */
94 for (unsigned i = 0 ; i < GUESTSTATTYPE_MAX; i++)
95 mCurrentGuestStat[i] = 0;
96
97#ifdef VBOX_WITH_GUEST_CONTROL
98 /* Init the context ID counter at 1000. */
99 mNextContextID = 1000;
100#endif
101
102 return S_OK;
103}
104
105/**
106 * Uninitializes the instance and sets the ready flag to FALSE.
107 * Called either from FinalRelease() or by the parent when it gets destroyed.
108 */
109void Guest::uninit()
110{
111 LogFlowThisFunc(("\n"));
112
113#ifdef VBOX_WITH_GUEST_CONTROL
114 /* Scope write lock as much as possible. */
115 {
116 /*
117 * Cleanup must be done *before* AutoUninitSpan to cancel all
118 * all outstanding waits in API functions (which hold AutoCaller
119 * ref counts).
120 */
121 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
122
123 /* Clean up callback data. */
124 CallbackMapIter it;
125 for (it = mCallbackMap.begin(); it != mCallbackMap.end(); it++)
126 destroyCtrlCallbackContext(it);
127
128 /* Clear process map. */
129 mGuestProcessMap.clear();
130 }
131#endif
132
133 /* Enclose the state transition Ready->InUninit->NotReady */
134 AutoUninitSpan autoUninitSpan(this);
135 if (autoUninitSpan.uninitDone())
136 return;
137
138 unconst(mParent) = NULL;
139}
140
141// IGuest properties
142/////////////////////////////////////////////////////////////////////////////
143
144STDMETHODIMP Guest::COMGETTER(OSTypeId) (BSTR *aOSTypeId)
145{
146 CheckComArgOutPointerValid(aOSTypeId);
147
148 AutoCaller autoCaller(this);
149 if (FAILED(autoCaller.rc())) return autoCaller.rc();
150
151 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
152
153 // redirect the call to IMachine if no additions are installed
154 if (mData.mAdditionsVersion.isEmpty())
155 return mParent->machine()->COMGETTER(OSTypeId)(aOSTypeId);
156
157 mData.mOSTypeId.cloneTo(aOSTypeId);
158
159 return S_OK;
160}
161
162STDMETHODIMP Guest::COMGETTER(AdditionsRunLevel) (ULONG *aRunLevel)
163{
164 AutoCaller autoCaller(this);
165 if (FAILED(autoCaller.rc())) return autoCaller.rc();
166
167 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
168
169 *aRunLevel = mData.mAdditionsRunLevel;
170
171 return S_OK;
172}
173
174STDMETHODIMP Guest::COMGETTER(AdditionsVersion) (BSTR *aAdditionsVersion)
175{
176 CheckComArgOutPointerValid(aAdditionsVersion);
177
178 AutoCaller autoCaller(this);
179 if (FAILED(autoCaller.rc())) return autoCaller.rc();
180
181 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
182
183 HRESULT hr = S_OK;
184 if ( mData.mAdditionsVersion.isEmpty()
185 /* Only try alternative way if GA are active! */
186 && mData.mAdditionsRunLevel > VBoxGuestAdditionsRunLevel_None)
187 {
188 /*
189 * If we got back an empty string from GetAdditionsVersion() we either
190 * really don't have the Guest Additions version yet or the guest is running
191 * older Guest Additions (< 3.2.0) which don't provide VMMDevReq_ReportGuestInfo2,
192 * so get the version + revision from the (hopefully) provided guest properties
193 * instead.
194 */
195 Bstr addVersion;
196 LONG64 u64Timestamp;
197 Bstr flags;
198 hr = mParent->machine()->GetGuestProperty(Bstr("/VirtualBox/GuestAdd/Version"),
199 addVersion.asOutParam(), &u64Timestamp, flags.asOutParam());
200 if (hr == S_OK)
201 {
202 Bstr addRevision;
203 hr = mParent->machine()->GetGuestProperty(Bstr("/VirtualBox/GuestAdd/Revision"),
204 addRevision.asOutParam(), &u64Timestamp, flags.asOutParam());
205 if ( hr == S_OK
206 && !addVersion.isEmpty()
207 && !addRevision.isEmpty())
208 {
209 /* Some Guest Additions versions had interchanged version + revision values,
210 * so check if the version value at least has a dot to identify it and change
211 * both values to reflect the right content. */
212 if (!Utf8Str(addVersion).contains("."))
213 {
214 Bstr addTemp = addVersion;
215 addVersion = addRevision;
216 addRevision = addTemp;
217 }
218
219 Bstr additionsVersion = BstrFmt("%ls r%ls",
220 addVersion.raw(), addRevision.raw());
221 additionsVersion.cloneTo(aAdditionsVersion);
222 }
223 /** @todo r=bird: else: Should not return failure! */
224 }
225 else
226 {
227 /* If getting the version + revision above fails or they simply aren't there
228 * because of *really* old Guest Additions we only can report the interface
229 * version to at least have something. */
230 mData.mInterfaceVersion.cloneTo(aAdditionsVersion);
231 /** @todo r=bird: hr is still indicating failure! */
232 }
233 }
234 else
235 mData.mAdditionsVersion.cloneTo(aAdditionsVersion);
236
237 return hr;
238}
239
240STDMETHODIMP Guest::COMGETTER(SupportsSeamless) (BOOL *aSupportsSeamless)
241{
242 CheckComArgOutPointerValid(aSupportsSeamless);
243
244 AutoCaller autoCaller(this);
245 if (FAILED(autoCaller.rc())) return autoCaller.rc();
246
247 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
248
249 *aSupportsSeamless = mData.mSupportsSeamless;
250
251 return S_OK;
252}
253
254STDMETHODIMP Guest::COMGETTER(SupportsGraphics) (BOOL *aSupportsGraphics)
255{
256 CheckComArgOutPointerValid(aSupportsGraphics);
257
258 AutoCaller autoCaller(this);
259 if (FAILED(autoCaller.rc())) return autoCaller.rc();
260
261 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
262
263 *aSupportsGraphics = mData.mSupportsGraphics;
264
265 return S_OK;
266}
267
268BOOL Guest::isPageFusionEnabled()
269{
270 AutoCaller autoCaller(this);
271 if (FAILED(autoCaller.rc())) return false;
272
273 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
274
275 return mfPageFusionEnabled;
276}
277
278STDMETHODIMP Guest::COMGETTER(MemoryBalloonSize) (ULONG *aMemoryBalloonSize)
279{
280 CheckComArgOutPointerValid(aMemoryBalloonSize);
281
282 AutoCaller autoCaller(this);
283 if (FAILED(autoCaller.rc())) return autoCaller.rc();
284
285 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
286
287 *aMemoryBalloonSize = mMemoryBalloonSize;
288
289 return S_OK;
290}
291
292STDMETHODIMP Guest::COMSETTER(MemoryBalloonSize) (ULONG aMemoryBalloonSize)
293{
294 AutoCaller autoCaller(this);
295 if (FAILED(autoCaller.rc())) return autoCaller.rc();
296
297 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
298
299 /* We must be 100% sure that IMachine::COMSETTER(MemoryBalloonSize)
300 * does not call us back in any way! */
301 HRESULT ret = mParent->machine()->COMSETTER(MemoryBalloonSize)(aMemoryBalloonSize);
302 if (ret == S_OK)
303 {
304 mMemoryBalloonSize = aMemoryBalloonSize;
305 /* forward the information to the VMM device */
306 VMMDev *pVMMDev = mParent->getVMMDev();
307 if (pVMMDev)
308 {
309 PPDMIVMMDEVPORT pVMMDevPort = pVMMDev->getVMMDevPort();
310 if (pVMMDevPort)
311 pVMMDevPort->pfnSetMemoryBalloon(pVMMDevPort, aMemoryBalloonSize);
312 }
313 }
314
315 return ret;
316}
317
318STDMETHODIMP Guest::COMGETTER(StatisticsUpdateInterval)(ULONG *aUpdateInterval)
319{
320 CheckComArgOutPointerValid(aUpdateInterval);
321
322 AutoCaller autoCaller(this);
323 if (FAILED(autoCaller.rc())) return autoCaller.rc();
324
325 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
326
327 *aUpdateInterval = mStatUpdateInterval;
328 return S_OK;
329}
330
331STDMETHODIMP Guest::COMSETTER(StatisticsUpdateInterval)(ULONG aUpdateInterval)
332{
333 AutoCaller autoCaller(this);
334 if (FAILED(autoCaller.rc())) return autoCaller.rc();
335
336 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
337
338 mStatUpdateInterval = aUpdateInterval;
339 /* forward the information to the VMM device */
340 VMMDev *pVMMDev = mParent->getVMMDev();
341 if (pVMMDev)
342 {
343 PPDMIVMMDEVPORT pVMMDevPort = pVMMDev->getVMMDevPort();
344 if (pVMMDevPort)
345 pVMMDevPort->pfnSetStatisticsInterval(pVMMDevPort, aUpdateInterval);
346 }
347
348 return S_OK;
349}
350
351STDMETHODIMP Guest::InternalGetStatistics(ULONG *aCpuUser, ULONG *aCpuKernel, ULONG *aCpuIdle,
352 ULONG *aMemTotal, ULONG *aMemFree, ULONG *aMemBalloon, ULONG *aMemShared,
353 ULONG *aMemCache, ULONG *aPageTotal,
354 ULONG *aMemAllocTotal, ULONG *aMemFreeTotal, ULONG *aMemBalloonTotal, ULONG *aMemSharedTotal)
355{
356 CheckComArgOutPointerValid(aCpuUser);
357 CheckComArgOutPointerValid(aCpuKernel);
358 CheckComArgOutPointerValid(aCpuIdle);
359 CheckComArgOutPointerValid(aMemTotal);
360 CheckComArgOutPointerValid(aMemFree);
361 CheckComArgOutPointerValid(aMemBalloon);
362 CheckComArgOutPointerValid(aMemShared);
363 CheckComArgOutPointerValid(aMemCache);
364 CheckComArgOutPointerValid(aPageTotal);
365 CheckComArgOutPointerValid(aMemAllocTotal);
366 CheckComArgOutPointerValid(aMemFreeTotal);
367 CheckComArgOutPointerValid(aMemBalloonTotal);
368 CheckComArgOutPointerValid(aMemSharedTotal);
369
370 AutoCaller autoCaller(this);
371 if (FAILED(autoCaller.rc())) return autoCaller.rc();
372
373 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
374
375 *aCpuUser = mCurrentGuestStat[GUESTSTATTYPE_CPUUSER];
376 *aCpuKernel = mCurrentGuestStat[GUESTSTATTYPE_CPUKERNEL];
377 *aCpuIdle = mCurrentGuestStat[GUESTSTATTYPE_CPUIDLE];
378 *aMemTotal = mCurrentGuestStat[GUESTSTATTYPE_MEMTOTAL] * (_4K/_1K); /* page (4K) -> 1KB units */
379 *aMemFree = mCurrentGuestStat[GUESTSTATTYPE_MEMFREE] * (_4K/_1K); /* page (4K) -> 1KB units */
380 *aMemBalloon = mCurrentGuestStat[GUESTSTATTYPE_MEMBALLOON] * (_4K/_1K); /* page (4K) -> 1KB units */
381 *aMemCache = mCurrentGuestStat[GUESTSTATTYPE_MEMCACHE] * (_4K/_1K); /* page (4K) -> 1KB units */
382 *aPageTotal = mCurrentGuestStat[GUESTSTATTYPE_PAGETOTAL] * (_4K/_1K); /* page (4K) -> 1KB units */
383
384 Console::SafeVMPtr pVM (mParent);
385 if (pVM.isOk())
386 {
387 uint64_t uFreeTotal, uAllocTotal, uBalloonedTotal, uSharedTotal;
388 *aMemFreeTotal = 0;
389 int rc = PGMR3QueryVMMMemoryStats(pVM.raw(), &uAllocTotal, &uFreeTotal, &uBalloonedTotal, &uSharedTotal);
390 AssertRC(rc);
391 if (rc == VINF_SUCCESS)
392 {
393 *aMemAllocTotal = (ULONG)(uAllocTotal / _1K); /* bytes -> KB */
394 *aMemFreeTotal = (ULONG)(uFreeTotal / _1K);
395 *aMemBalloonTotal = (ULONG)(uBalloonedTotal / _1K);
396 *aMemSharedTotal = (ULONG)(uSharedTotal / _1K);
397 }
398
399 /* Query the missing per-VM memory statistics. */
400 *aMemShared = 0;
401 uint64_t uTotalMem, uPrivateMem, uSharedMem, uZeroMem;
402 rc = PGMR3QueryMemoryStats(pVM.raw(), &uTotalMem, &uPrivateMem, &uSharedMem, &uZeroMem);
403 if (rc == VINF_SUCCESS)
404 {
405 *aMemShared = (ULONG)(uSharedMem / _1K);
406 }
407 }
408 else
409 {
410 *aMemFreeTotal = 0;
411 *aMemShared = 0;
412 }
413
414 return S_OK;
415}
416
417HRESULT Guest::setStatistic(ULONG aCpuId, GUESTSTATTYPE enmType, ULONG aVal)
418{
419 AutoCaller autoCaller(this);
420 if (FAILED(autoCaller.rc())) return autoCaller.rc();
421
422 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
423
424 if (enmType >= GUESTSTATTYPE_MAX)
425 return E_INVALIDARG;
426
427 mCurrentGuestStat[enmType] = aVal;
428 return S_OK;
429}
430
431STDMETHODIMP Guest::GetAdditionsStatus(ULONG aLevel, BOOL *aActive)
432{
433 AutoCaller autoCaller(this);
434 if (FAILED(autoCaller.rc())) return autoCaller.rc();
435
436 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
437
438 HRESULT rc = S_OK;
439 switch (aLevel)
440 {
441 case 0: /* System */
442 *aActive = (mData.mAdditionsRunLevel > VBoxGuestAdditionsRunLevel_None);
443 break;
444
445 case 1: /* Userland */
446 *aActive = (mData.mAdditionsRunLevel >= VBoxGuestAdditionsRunLevel_Userland);
447 break;
448
449 case 2: /* Desktop */
450 *aActive = (mData.mAdditionsRunLevel >= VBoxGuestAdditionsRunLevel_Desktop);
451 break;
452
453 default:
454 rc = setError(VBOX_E_NOT_SUPPORTED,
455 tr("Invalid status level defined: %u"), aLevel);
456 break;
457 }
458
459 return rc;
460}
461
462STDMETHODIMP Guest::SetCredentials(IN_BSTR aUserName, IN_BSTR aPassword,
463 IN_BSTR aDomain, BOOL aAllowInteractiveLogon)
464{
465 AutoCaller autoCaller(this);
466 if (FAILED(autoCaller.rc())) return autoCaller.rc();
467
468 /* forward the information to the VMM device */
469 VMMDev *pVMMDev = mParent->getVMMDev();
470 if (pVMMDev)
471 {
472 PPDMIVMMDEVPORT pVMMDevPort = pVMMDev->getVMMDevPort();
473 if (pVMMDevPort)
474 {
475 uint32_t u32Flags = VMMDEV_SETCREDENTIALS_GUESTLOGON;
476 if (!aAllowInteractiveLogon)
477 u32Flags = VMMDEV_SETCREDENTIALS_NOLOCALLOGON;
478
479 pVMMDevPort->pfnSetCredentials(pVMMDevPort,
480 Utf8Str(aUserName).c_str(),
481 Utf8Str(aPassword).c_str(),
482 Utf8Str(aDomain).c_str(),
483 u32Flags);
484 return S_OK;
485 }
486 }
487
488 return setError(VBOX_E_VM_ERROR,
489 tr("VMM device is not available (is the VM running?)"));
490}
491
492#ifdef VBOX_WITH_GUEST_CONTROL
493/**
494 * Appends environment variables to the environment block. Each var=value pair is separated
495 * by NULL (\0) sequence. The whole block will be stored in one blob and disassembled on the
496 * guest side later to fit into the HGCM param structure.
497 *
498 * @returns VBox status code.
499 *
500 * @todo
501 *
502 */
503int Guest::prepareExecuteEnv(const char *pszEnv, void **ppvList, uint32_t *pcbList, uint32_t *pcEnv)
504{
505 int rc = VINF_SUCCESS;
506 uint32_t cbLen = strlen(pszEnv);
507 if (*ppvList)
508 {
509 uint32_t cbNewLen = *pcbList + cbLen + 1; /* Include zero termination. */
510 char *pvTmp = (char*)RTMemRealloc(*ppvList, cbNewLen);
511 if (NULL == pvTmp)
512 {
513 rc = VERR_NO_MEMORY;
514 }
515 else
516 {
517 memcpy(pvTmp + *pcbList, pszEnv, cbLen);
518 pvTmp[cbNewLen - 1] = '\0'; /* Add zero termination. */
519 *ppvList = (void**)pvTmp;
520 }
521 }
522 else
523 {
524 char *pcTmp;
525 if (RTStrAPrintf(&pcTmp, "%s", pszEnv) > 0)
526 {
527 *ppvList = (void**)pcTmp;
528 /* Reset counters. */
529 *pcEnv = 0;
530 *pcbList = 0;
531 }
532 }
533 if (RT_SUCCESS(rc))
534 {
535 *pcbList += cbLen + 1; /* Include zero termination. */
536 *pcEnv += 1; /* Increase env pairs count. */
537 }
538 return rc;
539}
540
541/**
542 * Static callback function for receiving updates on guest control commands
543 * from the guest. Acts as a dispatcher for the actual class instance.
544 *
545 * @returns VBox status code.
546 *
547 * @todo
548 *
549 */
550DECLCALLBACK(int) Guest::doGuestCtrlNotification(void *pvExtension,
551 uint32_t u32Function,
552 void *pvParms,
553 uint32_t cbParms)
554{
555 using namespace guestControl;
556
557 /*
558 * No locking, as this is purely a notification which does not make any
559 * changes to the object state.
560 */
561#ifdef DEBUG_andy
562 LogFlowFunc(("pvExtension = %p, u32Function = %d, pvParms = %p, cbParms = %d\n",
563 pvExtension, u32Function, pvParms, cbParms));
564#endif
565 ComObjPtr<Guest> pGuest = reinterpret_cast<Guest *>(pvExtension);
566
567 int rc = VINF_SUCCESS;
568 if (u32Function == GUEST_DISCONNECTED)
569 {
570 //LogFlowFunc(("GUEST_DISCONNECTED\n"));
571
572 PCALLBACKDATACLIENTDISCONNECTED pCBData = reinterpret_cast<PCALLBACKDATACLIENTDISCONNECTED>(pvParms);
573 AssertPtr(pCBData);
574 AssertReturn(sizeof(CALLBACKDATACLIENTDISCONNECTED) == cbParms, VERR_INVALID_PARAMETER);
575 AssertReturn(CALLBACKDATAMAGICCLIENTDISCONNECTED == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
576
577 rc = pGuest->notifyCtrlClientDisconnected(u32Function, pCBData);
578 }
579 else if (u32Function == GUEST_EXEC_SEND_STATUS)
580 {
581 //LogFlowFunc(("GUEST_EXEC_SEND_STATUS\n"));
582
583 PCALLBACKDATAEXECSTATUS pCBData = reinterpret_cast<PCALLBACKDATAEXECSTATUS>(pvParms);
584 AssertPtr(pCBData);
585 AssertReturn(sizeof(CALLBACKDATAEXECSTATUS) == cbParms, VERR_INVALID_PARAMETER);
586 AssertReturn(CALLBACKDATAMAGICEXECSTATUS == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
587
588 rc = pGuest->notifyCtrlExecStatus(u32Function, pCBData);
589 }
590 else if (u32Function == GUEST_EXEC_SEND_OUTPUT)
591 {
592 //LogFlowFunc(("GUEST_EXEC_SEND_OUTPUT\n"));
593
594 PCALLBACKDATAEXECOUT pCBData = reinterpret_cast<PCALLBACKDATAEXECOUT>(pvParms);
595 AssertPtr(pCBData);
596 AssertReturn(sizeof(CALLBACKDATAEXECOUT) == cbParms, VERR_INVALID_PARAMETER);
597 AssertReturn(CALLBACKDATAMAGICEXECOUT == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
598
599 rc = pGuest->notifyCtrlExecOut(u32Function, pCBData);
600 }
601 else
602 rc = VERR_NOT_SUPPORTED;
603 return rc;
604}
605
606/* Function for handling the execution start/termination notification. */
607int Guest::notifyCtrlExecStatus(uint32_t u32Function,
608 PCALLBACKDATAEXECSTATUS pData)
609{
610 int vrc = VINF_SUCCESS;
611
612 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
613
614 AssertPtr(pData);
615 CallbackMapIter it = getCtrlCallbackContextByID(pData->hdr.u32ContextID);
616
617 /* Callback can be called several times. */
618 if (it != mCallbackMap.end())
619 {
620 PCALLBACKDATAEXECSTATUS pCBData = (PCALLBACKDATAEXECSTATUS)it->second.pvData;
621 AssertPtr(pCBData);
622
623 pCBData->u32PID = pData->u32PID;
624 pCBData->u32Status = pData->u32Status;
625 pCBData->u32Flags = pData->u32Flags;
626 /** @todo Copy void* buffer contents! */
627
628 Utf8Str errMsg;
629
630 /* Was progress canceled before? */
631 BOOL fCanceled;
632 ComAssert(!it->second.pProgress.isNull());
633 if ( SUCCEEDED(it->second.pProgress->COMGETTER(Canceled)(&fCanceled))
634 && !fCanceled)
635 {
636 /* Do progress handling. */
637 HRESULT hr;
638 switch (pData->u32Status)
639 {
640 case PROC_STS_STARTED:
641 LogRel(("Guest process (PID %u) started\n", pCBData->u32PID)); /** @todo Add process name */
642 hr = it->second.pProgress->SetNextOperation(BstrFmt(tr("Waiting for process to exit ...")), 1 /* Weight */);
643 AssertComRC(hr);
644 break;
645
646 case PROC_STS_TEN: /* Terminated normally. */
647 LogRel(("Guest process (PID %u) exited normally\n", pCBData->u32PID)); /** @todo Add process name */
648 hr = it->second.pProgress->notifyComplete(S_OK);
649 AssertComRC(hr);
650 LogFlowFunc(("Proccess (context ID=%u, status=%u) terminated successfully\n",
651 pData->hdr.u32ContextID, pData->u32Status));
652 break;
653
654 case PROC_STS_TEA: /* Terminated abnormally. */
655 LogRel(("Guest process (PID %u) terminated abnormally with exit code = %u\n",
656 pCBData->u32PID, pCBData->u32Flags)); /** @todo Add process name */
657 errMsg = Utf8StrFmt(Guest::tr("Process terminated abnormally with status '%u'"),
658 pCBData->u32Flags);
659 break;
660
661 case PROC_STS_TES: /* Terminated through signal. */
662 LogRel(("Guest process (PID %u) terminated through signal with exit code = %u\n",
663 pCBData->u32PID, pCBData->u32Flags)); /** @todo Add process name */
664 errMsg = Utf8StrFmt(Guest::tr("Process terminated via signal with status '%u'"),
665 pCBData->u32Flags);
666 break;
667
668 case PROC_STS_TOK:
669 LogRel(("Guest process (PID %u) timed out and was killed\n", pCBData->u32PID)); /** @todo Add process name */
670 errMsg = Utf8StrFmt(Guest::tr("Process timed out and was killed"));
671 break;
672
673 case PROC_STS_TOA:
674 LogRel(("Guest process (PID %u) timed out and could not be killed\n", pCBData->u32PID)); /** @todo Add process name */
675 errMsg = Utf8StrFmt(Guest::tr("Process timed out and could not be killed"));
676 break;
677
678 case PROC_STS_DWN:
679 LogRel(("Guest process (PID %u) exited because system is shutting down\n", pCBData->u32PID)); /** @todo Add process name */
680 errMsg = Utf8StrFmt(Guest::tr("Process exited because system is shutting down"));
681 break;
682
683 case PROC_STS_ERROR:
684 LogRel(("Guest process (PID %u) could not be started because of rc=%Rrc\n",
685 pCBData->u32PID, pCBData->u32Flags)); /** @todo Add process name */
686 errMsg = Utf8StrFmt(Guest::tr("Process execution failed with rc=%Rrc"), pCBData->u32Flags);
687 break;
688
689 default:
690 vrc = VERR_INVALID_PARAMETER;
691 break;
692 }
693
694 /* Handle process map. */
695 /** @todo What happens on/deal with PID reuse? */
696 /** @todo How to deal with multiple updates at once? */
697 if (pCBData->u32PID > 0)
698 {
699 GuestProcessMapIter it_proc = getProcessByPID(pCBData->u32PID);
700 if (it_proc == mGuestProcessMap.end())
701 {
702 /* Not found, add to map. */
703 GuestProcess newProcess;
704 newProcess.mStatus = pCBData->u32Status;
705 newProcess.mExitCode = pCBData->u32Flags; /* Contains exit code. */
706 newProcess.mFlags = 0;
707
708 mGuestProcessMap[pCBData->u32PID] = newProcess;
709 }
710 else /* Update map. */
711 {
712 it_proc->second.mStatus = pCBData->u32Status;
713 it_proc->second.mExitCode = pCBData->u32Flags; /* Contains exit code. */
714 it_proc->second.mFlags = 0;
715 }
716 }
717 }
718 else
719 errMsg = Utf8StrFmt(Guest::tr("Process execution canceled"));
720
721 if (!it->second.pProgress->getCompleted())
722 {
723 if ( errMsg.length()
724 || fCanceled) /* If canceled we have to report E_FAIL! */
725 {
726 HRESULT hr2 = it->second.pProgress->notifyComplete(VBOX_E_IPRT_ERROR,
727 COM_IIDOF(IGuest),
728 Guest::getStaticComponentName(),
729 "%s", errMsg.c_str());
730 AssertComRC(hr2);
731 LogFlowFunc(("Process (context ID=%u, status=%u) reported error: %s\n",
732 pData->hdr.u32ContextID, pData->u32Status, errMsg.c_str()));
733 }
734 }
735 }
736 else
737 LogFlowFunc(("Unexpected callback (magic=%u, context ID=%u) arrived\n", pData->hdr.u32Magic, pData->hdr.u32ContextID));
738 LogFlowFunc(("Returned with rc=%Rrc\n", vrc));
739 return vrc;
740}
741
742/* Function for handling the execution output notification. */
743int Guest::notifyCtrlExecOut(uint32_t u32Function,
744 PCALLBACKDATAEXECOUT pData)
745{
746 int rc = VINF_SUCCESS;
747
748 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
749
750 AssertPtr(pData);
751 CallbackMapIter it = getCtrlCallbackContextByID(pData->hdr.u32ContextID);
752 if (it != mCallbackMap.end())
753 {
754 PCALLBACKDATAEXECOUT pCBData = (CALLBACKDATAEXECOUT*)it->second.pvData;
755 AssertPtr(pCBData);
756
757 pCBData->u32PID = pData->u32PID;
758 pCBData->u32HandleId = pData->u32HandleId;
759 pCBData->u32Flags = pData->u32Flags;
760
761 /* Make sure we really got something! */
762 if ( pData->cbData
763 && pData->pvData)
764 {
765 /* Allocate data buffer and copy it */
766 pCBData->pvData = RTMemAlloc(pData->cbData);
767 pCBData->cbData = pData->cbData;
768
769 AssertReturn(pCBData->pvData, VERR_NO_MEMORY);
770 memcpy(pCBData->pvData, pData->pvData, pData->cbData);
771 }
772 else
773 {
774 pCBData->pvData = NULL;
775 pCBData->cbData = 0;
776 }
777
778 /* Was progress canceled before? */
779 BOOL fCanceled;
780 ComAssert(!it->second.pProgress.isNull());
781 if (SUCCEEDED(it->second.pProgress->COMGETTER(Canceled)(&fCanceled)) && fCanceled)
782 {
783 it->second.pProgress->notifyComplete(VBOX_E_IPRT_ERROR,
784 COM_IIDOF(IGuest),
785 Guest::getStaticComponentName(),
786 Guest::tr("The output operation was canceled"));
787 }
788 else
789 it->second.pProgress->notifyComplete(S_OK);
790 }
791 else
792 LogFlowFunc(("Unexpected callback (magic=%u, context ID=%u) arrived\n", pData->hdr.u32Magic, pData->hdr.u32ContextID));
793 return rc;
794}
795
796int Guest::notifyCtrlClientDisconnected(uint32_t u32Function,
797 PCALLBACKDATACLIENTDISCONNECTED pData)
798{
799 int rc = VINF_SUCCESS;
800
801 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
802 CallbackMapIter it = getCtrlCallbackContextByID(pData->hdr.u32ContextID);
803 if (it != mCallbackMap.end())
804 {
805 LogFlowFunc(("Client with context ID=%u disconnected\n", it->first));
806 destroyCtrlCallbackContext(it);
807 }
808 return rc;
809}
810
811Guest::CallbackMapIter Guest::getCtrlCallbackContextByID(uint32_t u32ContextID)
812{
813 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
814 return mCallbackMap.find(u32ContextID);
815}
816
817Guest::GuestProcessMapIter Guest::getProcessByPID(uint32_t u32PID)
818{
819 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
820 return mGuestProcessMap.find(u32PID);
821}
822
823/* No locking here; */
824void Guest::destroyCtrlCallbackContext(Guest::CallbackMapIter it)
825{
826 LogFlowFunc(("Destroying callback with CID=%u ...\n", it->first));
827
828 if (it->second.pvData)
829 {
830 RTMemFree(it->second.pvData);
831 it->second.pvData = NULL;
832 it->second.cbData = 0;
833 }
834
835 /* Notify outstanding waits for progress ... */
836 if ( it->second.pProgress
837 && !it->second.pProgress.isNull())
838 {
839 LogFlowFunc(("Handling progress for CID=%u ...\n", it->first));
840
841 /*
842 * Assume we didn't complete to make sure we clean up even if the
843 * following call fails.
844 */
845 BOOL fCompleted = FALSE;
846 it->second.pProgress->COMGETTER(Completed)(&fCompleted);
847 if (!fCompleted)
848 {
849 LogFlowFunc(("Progress of CID=%u *not* completed, cancelling ...\n", it->first));
850
851 /* Only cancel if not canceled before! */
852 BOOL fCanceled;
853 if (SUCCEEDED(it->second.pProgress->COMGETTER(Canceled)(&fCanceled)) && !fCanceled)
854 it->second.pProgress->Cancel();
855
856 /*
857 * To get waitForCompletion completed (unblocked) we have to notify it if necessary (only
858 * cancle won't work!). This could happen if the client thread (e.g. VBoxService, thread of a spawned process)
859 * is disconnecting without having the chance to sending a status message before, so we
860 * have to abort here to make sure the host never hangs/gets stuck while waiting for the
861 * progress object to become signalled.
862 */
863 it->second.pProgress->notifyComplete(VBOX_E_IPRT_ERROR,
864 COM_IIDOF(IGuest),
865 Guest::getStaticComponentName(),
866 Guest::tr("The operation was canceled because client is shutting down"));
867 }
868 /*
869 * Do *not* NULL pProgress here, because waiting function like executeProcess()
870 * will still rely on this object for checking whether they have to give up!
871 */
872 }
873}
874
875/* Adds a callback with a user provided data block and an optional progress object
876 * to the callback map. A callback is identified by a unique context ID which is used
877 * to identify a callback from the guest side. */
878uint32_t Guest::addCtrlCallbackContext(eVBoxGuestCtrlCallbackType enmType, void *pvData, uint32_t cbData, Progress *pProgress)
879{
880 AssertPtr(pProgress);
881
882 /** @todo Put this stuff into a constructor! */
883 CallbackContext context;
884 context.mType = enmType;
885 context.pvData = pvData;
886 context.cbData = cbData;
887 context.pProgress = pProgress;
888
889 /* Create a new context ID and assign it. */
890 CallbackMapIter it;
891 uint32_t uNewContext = 0;
892 do
893 {
894 /* Create a new context ID ... */
895 uNewContext = ASMAtomicIncU32(&mNextContextID);
896 if (uNewContext == UINT32_MAX)
897 ASMAtomicUoWriteU32(&mNextContextID, 1000);
898 /* Is the context ID already used? */
899 it = getCtrlCallbackContextByID(uNewContext);
900 } while(it != mCallbackMap.end());
901
902 uint32_t nCallbacks = 0;
903 if ( it == mCallbackMap.end()
904 && uNewContext > 0)
905 {
906 /* We apparently got an unused context ID, let's use it! */
907 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
908 mCallbackMap[uNewContext] = context;
909 nCallbacks = mCallbackMap.size();
910 }
911 else
912 {
913 /* Should never happen ... */
914 {
915 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
916 nCallbacks = mCallbackMap.size();
917 }
918 AssertReleaseMsg(uNewContext, ("No free context ID found! uNewContext=%u, nCallbacks=%u", uNewContext, nCallbacks));
919 }
920
921#if 0
922 if (nCallbacks > 256) /* Don't let the container size get too big! */
923 {
924 Guest::CallbackListIter it = mCallbackList.begin();
925 destroyCtrlCallbackContext(it);
926 {
927 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
928 mCallbackList.erase(it);
929 }
930 }
931#endif
932 return uNewContext;
933}
934#endif /* VBOX_WITH_GUEST_CONTROL */
935
936STDMETHODIMP Guest::ExecuteProcess(IN_BSTR aCommand, ULONG aFlags,
937 ComSafeArrayIn(IN_BSTR, aArguments), ComSafeArrayIn(IN_BSTR, aEnvironment),
938 IN_BSTR aUserName, IN_BSTR aPassword,
939 ULONG aTimeoutMS, ULONG *aPID, IProgress **aProgress)
940{
941/** @todo r=bird: Eventually we should clean up all the timeout parameters
942 * in the API and have the same way of specifying infinite waits! */
943#ifndef VBOX_WITH_GUEST_CONTROL
944 ReturnComNotImplemented();
945#else /* VBOX_WITH_GUEST_CONTROL */
946 using namespace guestControl;
947
948 CheckComArgStrNotEmptyOrNull(aCommand);
949 CheckComArgOutPointerValid(aPID);
950 CheckComArgOutPointerValid(aProgress);
951
952 /* Do not allow anonymous executions (with system rights). */
953 if (RT_UNLIKELY((aUserName) == NULL || *(aUserName) == '\0'))
954 return setError(E_INVALIDARG, tr("No user name specified"));
955
956 AutoCaller autoCaller(this);
957 if (FAILED(autoCaller.rc())) return autoCaller.rc();
958
959 if (aFlags != 0) /* Flags are not supported at the moment. */
960 return E_INVALIDARG;
961
962 HRESULT rc = S_OK;
963
964 try
965 {
966 /*
967 * Create progress object. Note that this is a multi operation
968 * object to perform the following steps:
969 * - Operation 1 (0): Create/start process.
970 * - Operation 2 (1): Wait for process to exit.
971 * If this progress completed successfully (S_OK), the process
972 * started and exited normally. In any other case an error/exception
973 * occured.
974 */
975 ComObjPtr <Progress> progress;
976 rc = progress.createObject();
977 if (SUCCEEDED(rc))
978 {
979 rc = progress->init(static_cast<IGuest*>(this),
980 BstrFmt(tr("Executing process")),
981 TRUE,
982 2, /* Number of operations. */
983 BstrFmt(tr("Starting process ..."))); /* Description of first stage. */
984 }
985 if (FAILED(rc)) return rc;
986
987 /*
988 * Prepare process execution.
989 */
990 int vrc = VINF_SUCCESS;
991 Utf8Str Utf8Command(aCommand);
992
993 /* Adjust timeout */
994 if (aTimeoutMS == 0)
995 aTimeoutMS = UINT32_MAX;
996
997 /* Prepare arguments. */
998 char **papszArgv = NULL;
999 uint32_t uNumArgs = 0;
1000 if (aArguments > 0)
1001 {
1002 com::SafeArray<IN_BSTR> args(ComSafeArrayInArg(aArguments));
1003 uNumArgs = args.size();
1004 papszArgv = (char**)RTMemAlloc(sizeof(char*) * (uNumArgs + 1));
1005 AssertReturn(papszArgv, E_OUTOFMEMORY);
1006 for (unsigned i = 0; RT_SUCCESS(vrc) && i < uNumArgs; i++)
1007 vrc = RTUtf16ToUtf8(args[i], &papszArgv[i]);
1008 papszArgv[uNumArgs] = NULL;
1009 }
1010
1011 Utf8Str Utf8UserName(aUserName);
1012 Utf8Str Utf8Password(aPassword);
1013 if (RT_SUCCESS(vrc))
1014 {
1015 uint32_t uContextID = 0;
1016
1017 char *pszArgs = NULL;
1018 if (uNumArgs > 0)
1019 vrc = RTGetOptArgvToString(&pszArgs, papszArgv, 0);
1020 if (RT_SUCCESS(vrc))
1021 {
1022 uint32_t cbArgs = pszArgs ? strlen(pszArgs) + 1 : 0; /* Include terminating zero. */
1023
1024 /* Prepare environment. */
1025 void *pvEnv = NULL;
1026 uint32_t uNumEnv = 0;
1027 uint32_t cbEnv = 0;
1028 if (aEnvironment > 0)
1029 {
1030 com::SafeArray<IN_BSTR> env(ComSafeArrayInArg(aEnvironment));
1031
1032 for (unsigned i = 0; i < env.size(); i++)
1033 {
1034 vrc = prepareExecuteEnv(Utf8Str(env[i]).c_str(), &pvEnv, &cbEnv, &uNumEnv);
1035 if (RT_FAILURE(vrc))
1036 break;
1037 }
1038 }
1039
1040 LogRel(("Executing guest process \"%s\" as user \"%s\" ...\n",
1041 Utf8Command.c_str(), Utf8UserName.c_str()));
1042
1043 if (RT_SUCCESS(vrc))
1044 {
1045 PCALLBACKDATAEXECSTATUS pData = (PCALLBACKDATAEXECSTATUS)RTMemAlloc(sizeof(CALLBACKDATAEXECSTATUS));
1046 AssertReturn(pData, VBOX_E_IPRT_ERROR);
1047 RT_ZERO(*pData);
1048 uContextID = addCtrlCallbackContext(VBOXGUESTCTRLCALLBACKTYPE_EXEC_START,
1049 pData, sizeof(CALLBACKDATAEXECSTATUS), progress);
1050 Assert(uContextID > 0);
1051
1052 VBOXHGCMSVCPARM paParms[15];
1053 int i = 0;
1054 paParms[i++].setUInt32(uContextID);
1055 paParms[i++].setPointer((void*)Utf8Command.c_str(), (uint32_t)Utf8Command.length() + 1);
1056 paParms[i++].setUInt32(aFlags);
1057 paParms[i++].setUInt32(uNumArgs);
1058 paParms[i++].setPointer((void*)pszArgs, cbArgs);
1059 paParms[i++].setUInt32(uNumEnv);
1060 paParms[i++].setUInt32(cbEnv);
1061 paParms[i++].setPointer((void*)pvEnv, cbEnv);
1062 paParms[i++].setPointer((void*)Utf8UserName.c_str(), (uint32_t)Utf8UserName.length() + 1);
1063 paParms[i++].setPointer((void*)Utf8Password.c_str(), (uint32_t)Utf8Password.length() + 1);
1064 paParms[i++].setUInt32(aTimeoutMS);
1065
1066 VMMDev *vmmDev;
1067 {
1068 /* Make sure mParent is valid, so set the read lock while using.
1069 * Do not keep this lock while doing the actual call, because in the meanwhile
1070 * another thread could request a write lock which would be a bad idea ... */
1071 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
1072
1073 /* Forward the information to the VMM device. */
1074 AssertPtr(mParent);
1075 vmmDev = mParent->getVMMDev();
1076 }
1077
1078 if (vmmDev)
1079 {
1080 LogFlowFunc(("hgcmHostCall numParms=%d\n", i));
1081 vrc = vmmDev->hgcmHostCall("VBoxGuestControlSvc", HOST_EXEC_CMD,
1082 i, paParms);
1083 }
1084 else
1085 vrc = VERR_INVALID_VM_HANDLE;
1086 RTMemFree(pvEnv);
1087 }
1088 RTStrFree(pszArgs);
1089 }
1090 if (RT_SUCCESS(vrc))
1091 {
1092 LogFlowFunc(("Waiting for HGCM callback (timeout=%ldms) ...\n", aTimeoutMS));
1093
1094 /*
1095 * Wait for the HGCM low level callback until the process
1096 * has been started (or something went wrong). This is necessary to
1097 * get the PID.
1098 */
1099 CallbackMapIter it = getCtrlCallbackContextByID(uContextID);
1100 BOOL fCanceled = FALSE;
1101 if (it != mCallbackMap.end())
1102 {
1103 ComAssert(!it->second.pProgress.isNull());
1104
1105 /*
1106 * Wait for the first stage (=0) to complete (that is starting the process).
1107 */
1108 PCALLBACKDATAEXECSTATUS pData = NULL;
1109 rc = it->second.pProgress->WaitForOperationCompletion(0, aTimeoutMS);
1110 if (SUCCEEDED(rc))
1111 {
1112 /* Was the operation canceled by one of the parties? */
1113 rc = it->second.pProgress->COMGETTER(Canceled)(&fCanceled);
1114 if (FAILED(rc)) throw rc;
1115
1116 if (!fCanceled)
1117 {
1118 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
1119
1120 pData = (PCALLBACKDATAEXECSTATUS)it->second.pvData;
1121 Assert(it->second.cbData == sizeof(CALLBACKDATAEXECSTATUS));
1122 AssertPtr(pData);
1123
1124 /* Did we get some status? */
1125 switch (pData->u32Status)
1126 {
1127 case PROC_STS_STARTED:
1128 /* Process is (still) running; get PID. */
1129 *aPID = pData->u32PID;
1130 break;
1131
1132 /* In any other case the process either already
1133 * terminated or something else went wrong, so no PID ... */
1134 case PROC_STS_TEN: /* Terminated normally. */
1135 case PROC_STS_TEA: /* Terminated abnormally. */
1136 case PROC_STS_TES: /* Terminated through signal. */
1137 case PROC_STS_TOK:
1138 case PROC_STS_TOA:
1139 case PROC_STS_DWN:
1140 /*
1141 * Process (already) ended, but we want to get the
1142 * PID anyway to retrieve the output in a later call.
1143 */
1144 *aPID = pData->u32PID;
1145 break;
1146
1147 case PROC_STS_ERROR:
1148 vrc = pData->u32Flags; /* u32Flags member contains IPRT error code. */
1149 break;
1150
1151 case PROC_STS_UNDEFINED:
1152 vrc = VERR_TIMEOUT; /* Operation did not complete within time. */
1153 break;
1154
1155 default:
1156 vrc = VERR_INVALID_PARAMETER; /* Unknown status, should never happen! */
1157 break;
1158 }
1159 }
1160 else /* Operation was canceled. */
1161 vrc = VERR_CANCELLED;
1162 }
1163 else /* Operation did not complete within time. */
1164 vrc = VERR_TIMEOUT;
1165
1166 /*
1167 * Do *not* remove the callback yet - we might wait with the IProgress object on something
1168 * else (like end of process) ...
1169 */
1170 if (RT_FAILURE(vrc))
1171 {
1172 if (vrc == VERR_FILE_NOT_FOUND) /* This is the most likely error. */
1173 rc = setError(VBOX_E_IPRT_ERROR,
1174 tr("The file '%s' was not found on guest"), Utf8Command.c_str());
1175 else if (vrc == VERR_PATH_NOT_FOUND)
1176 rc = setError(VBOX_E_IPRT_ERROR,
1177 tr("The path to file '%s' was not found on guest"), Utf8Command.c_str());
1178 else if (vrc == VERR_BAD_EXE_FORMAT)
1179 rc = setError(VBOX_E_IPRT_ERROR,
1180 tr("The file '%s' is not an executable format on guest"), Utf8Command.c_str());
1181 else if (vrc == VERR_AUTHENTICATION_FAILURE)
1182 rc = setError(VBOX_E_IPRT_ERROR,
1183 tr("The specified user '%s' was not able to logon on guest"), Utf8UserName.c_str());
1184 else if (vrc == VERR_TIMEOUT)
1185 rc = setError(VBOX_E_IPRT_ERROR,
1186 tr("The guest did not respond within time (%ums)"), aTimeoutMS);
1187 else if (vrc == VERR_CANCELLED)
1188 rc = setError(VBOX_E_IPRT_ERROR,
1189 tr("The execution operation was canceled"));
1190 else if (vrc == VERR_PERMISSION_DENIED)
1191 rc = setError(VBOX_E_IPRT_ERROR,
1192 tr("Invalid user/password credentials"));
1193 else
1194 {
1195 if (pData && pData->u32Status == PROC_STS_ERROR)
1196 rc = setError(VBOX_E_IPRT_ERROR,
1197 tr("Process could not be started: %Rrc"), pData->u32Flags);
1198 else
1199 rc = setError(E_UNEXPECTED,
1200 tr("The service call failed with error %Rrc"), vrc);
1201 }
1202 }
1203 else /* Execution went fine. */
1204 {
1205 /* Return the progress to the caller. */
1206 progress.queryInterfaceTo(aProgress);
1207 }
1208 }
1209 else /* Callback context not found; should never happen! */
1210 AssertMsg(it != mCallbackMap.end(), ("Callback context with ID %u not found!", uContextID));
1211 }
1212 else /* HGCM related error codes .*/
1213 {
1214 if (vrc == VERR_INVALID_VM_HANDLE)
1215 rc = setError(VBOX_E_VM_ERROR,
1216 tr("VMM device is not available (is the VM running?)"));
1217 else if (vrc == VERR_TIMEOUT)
1218 rc = setError(VBOX_E_VM_ERROR,
1219 tr("The guest execution service is not ready"));
1220 else if (vrc == VERR_HGCM_SERVICE_NOT_FOUND)
1221 rc = setError(VBOX_E_VM_ERROR,
1222 tr("The guest execution service is not available"));
1223 else /* HGCM call went wrong. */
1224 rc = setError(E_UNEXPECTED,
1225 tr("The HGCM call failed with error %Rrc"), vrc);
1226 }
1227
1228 for (unsigned i = 0; i < uNumArgs; i++)
1229 RTMemFree(papszArgv[i]);
1230 RTMemFree(papszArgv);
1231 }
1232
1233 if (RT_FAILURE(vrc))
1234 LogRel(("Executing guest process \"%s\" as user \"%s\" failed with %Rrc\n",
1235 Utf8Command.c_str(), Utf8UserName.c_str(), vrc));
1236 }
1237 catch (std::bad_alloc &)
1238 {
1239 rc = E_OUTOFMEMORY;
1240 }
1241 return rc;
1242#endif /* VBOX_WITH_GUEST_CONTROL */
1243}
1244
1245STDMETHODIMP Guest::GetProcessOutput(ULONG aPID, ULONG aFlags, ULONG aTimeoutMS, LONG64 aSize, ComSafeArrayOut(BYTE, aData))
1246{
1247/** @todo r=bird: Eventually we should clean up all the timeout parameters
1248 * in the API and have the same way of specifying infinite waits! */
1249#ifndef VBOX_WITH_GUEST_CONTROL
1250 ReturnComNotImplemented();
1251#else /* VBOX_WITH_GUEST_CONTROL */
1252 using namespace guestControl;
1253
1254 CheckComArgExpr(aPID, aPID > 0);
1255 if (aSize < 0)
1256 return setError(E_INVALIDARG, tr("The size argument (%lld) is negative"), aSize);
1257 if (aFlags != 0) /* Flags are not supported at the moment. */
1258 return setError(E_INVALIDARG, tr("Unknown flags (%#x)"), aFlags);
1259
1260 AutoCaller autoCaller(this);
1261 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1262
1263 HRESULT rc = S_OK;
1264
1265 try
1266 {
1267 /*
1268 * Create progress object.
1269 * This progress object, compared to the one in executeProgress() above,
1270 * is only local and is used to determine whether the operation finished
1271 * or got canceled.
1272 */
1273 ComObjPtr <Progress> progress;
1274 rc = progress.createObject();
1275 if (SUCCEEDED(rc))
1276 {
1277 rc = progress->init(static_cast<IGuest*>(this),
1278 BstrFmt(tr("Getting output of process")),
1279 TRUE);
1280 }
1281 if (FAILED(rc)) return rc;
1282
1283 /* Adjust timeout */
1284 if (aTimeoutMS == 0)
1285 aTimeoutMS = UINT32_MAX;
1286
1287 /* Search for existing PID. */
1288 PCALLBACKDATAEXECOUT pData = (CALLBACKDATAEXECOUT*)RTMemAlloc(sizeof(CALLBACKDATAEXECOUT));
1289 AssertReturn(pData, VBOX_E_IPRT_ERROR);
1290 RT_ZERO(*pData);
1291 uint32_t uContextID = addCtrlCallbackContext(VBOXGUESTCTRLCALLBACKTYPE_EXEC_OUTPUT,
1292 pData, sizeof(CALLBACKDATAEXECOUT), progress);
1293 Assert(uContextID > 0);
1294
1295 size_t cbData = (size_t)RT_MIN(aSize, _64K);
1296 com::SafeArray<BYTE> outputData(cbData);
1297
1298 VBOXHGCMSVCPARM paParms[5];
1299 int i = 0;
1300 paParms[i++].setUInt32(uContextID);
1301 paParms[i++].setUInt32(aPID);
1302 paParms[i++].setUInt32(aFlags); /** @todo Should represent stdout and/or stderr. */
1303
1304 int vrc = VINF_SUCCESS;
1305
1306 {
1307 VMMDev *vmmDev;
1308 {
1309 /* Make sure mParent is valid, so set the read lock while using.
1310 * Do not keep this lock while doing the actual call, because in the meanwhile
1311 * another thread could request a write lock which would be a bad idea ... */
1312 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
1313
1314 /* Forward the information to the VMM device. */
1315 AssertPtr(mParent);
1316 vmmDev = mParent->getVMMDev();
1317 }
1318
1319 if (vmmDev)
1320 {
1321 LogFlowFunc(("hgcmHostCall numParms=%d\n", i));
1322 vrc = vmmDev->hgcmHostCall("VBoxGuestControlSvc", HOST_EXEC_GET_OUTPUT,
1323 i, paParms);
1324 }
1325 }
1326
1327 if (RT_SUCCESS(vrc))
1328 {
1329 LogFlowFunc(("Waiting for HGCM callback (timeout=%ldms) ...\n", aTimeoutMS));
1330
1331 /*
1332 * Wait for the HGCM low level callback until the process
1333 * has been started (or something went wrong). This is necessary to
1334 * get the PID.
1335 */
1336 CallbackMapIter it = getCtrlCallbackContextByID(uContextID);
1337 BOOL fCanceled = FALSE;
1338 if (it != mCallbackMap.end())
1339 {
1340 ComAssert(!it->second.pProgress.isNull());
1341
1342 /* Wait until operation completed. */
1343 rc = it->second.pProgress->WaitForCompletion(aTimeoutMS);
1344 if (FAILED(rc)) throw rc;
1345
1346 /* Was the operation canceled by one of the parties? */
1347 rc = it->second.pProgress->COMGETTER(Canceled)(&fCanceled);
1348 if (FAILED(rc)) throw rc;
1349
1350 if (!fCanceled)
1351 {
1352 BOOL fCompleted;
1353 if ( SUCCEEDED(it->second.pProgress->COMGETTER(Completed)(&fCompleted))
1354 && fCompleted)
1355 {
1356 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
1357
1358 /* Did we get some output? */
1359 pData = (PCALLBACKDATAEXECOUT)it->second.pvData;
1360 Assert(it->second.cbData == sizeof(CALLBACKDATAEXECOUT));
1361 AssertPtr(pData);
1362
1363 if (pData->cbData)
1364 {
1365 /* Do we need to resize the array? */
1366 if (pData->cbData > cbData)
1367 outputData.resize(pData->cbData);
1368
1369 /* Fill output in supplied out buffer. */
1370 memcpy(outputData.raw(), pData->pvData, pData->cbData);
1371 outputData.resize(pData->cbData); /* Shrink to fit actual buffer size. */
1372 }
1373 else
1374 vrc = VERR_NO_DATA; /* This is not an error we want to report to COM. */
1375 }
1376 else /* If callback not called within time ... well, that's a timeout! */
1377 vrc = VERR_TIMEOUT;
1378 }
1379 else /* Operation was canceled. */
1380 {
1381 vrc = VERR_CANCELLED;
1382 }
1383
1384 if (RT_FAILURE(vrc))
1385 {
1386 if (vrc == VERR_NO_DATA)
1387 {
1388 /* This is not an error we want to report to COM. */
1389 rc = S_OK;
1390 }
1391 else if (vrc == VERR_TIMEOUT)
1392 {
1393 rc = setError(VBOX_E_IPRT_ERROR,
1394 tr("The guest did not output within time (%ums)"), aTimeoutMS);
1395 }
1396 else if (vrc == VERR_CANCELLED)
1397 {
1398 rc = setError(VBOX_E_IPRT_ERROR,
1399 tr("The output operation was canceled"));
1400 }
1401 else
1402 {
1403 rc = setError(E_UNEXPECTED,
1404 tr("The service call failed with error %Rrc"), vrc);
1405 }
1406 }
1407
1408 {
1409 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1410 /*
1411 * Destroy locally used progress object.
1412 */
1413 destroyCtrlCallbackContext(it);
1414 }
1415
1416 /* Remove callback context (not used anymore). */
1417 mCallbackMap.erase(it);
1418 }
1419 else /* PID lookup failed. */
1420 rc = setError(VBOX_E_IPRT_ERROR,
1421 tr("Process (PID %u) not found!"), aPID);
1422 }
1423 else /* HGCM operation failed. */
1424 rc = setError(E_UNEXPECTED,
1425 tr("The HGCM call failed with error %Rrc"), vrc);
1426
1427 /* Cleanup. */
1428 progress->uninit();
1429 progress.setNull();
1430
1431 /* If something failed (or there simply was no data, indicated by VERR_NO_DATA,
1432 * we return an empty array so that the frontend knows when to give up. */
1433 if (RT_FAILURE(vrc) || FAILED(rc))
1434 outputData.resize(0);
1435 outputData.detachTo(ComSafeArrayOutArg(aData));
1436 }
1437 catch (std::bad_alloc &)
1438 {
1439 rc = E_OUTOFMEMORY;
1440 }
1441 return rc;
1442#endif
1443}
1444
1445STDMETHODIMP Guest::GetProcessStatus(ULONG aPID, ULONG *aExitCode, ULONG *aFlags, ULONG *aStatus)
1446{
1447#ifndef VBOX_WITH_GUEST_CONTROL
1448 ReturnComNotImplemented();
1449#else /* VBOX_WITH_GUEST_CONTROL */
1450 using namespace guestControl;
1451
1452 AutoCaller autoCaller(this);
1453 if (FAILED(autoCaller.rc())) return autoCaller.rc();
1454
1455 HRESULT rc = S_OK;
1456
1457 try
1458 {
1459 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
1460
1461 GuestProcessMapIterConst it = getProcessByPID(aPID);
1462 if (it != mGuestProcessMap.end())
1463 {
1464 *aExitCode = it->second.mExitCode;
1465 *aFlags = it->second.mFlags;
1466 *aStatus = it->second.mStatus;
1467 }
1468 else
1469 rc = setError(VBOX_E_IPRT_ERROR,
1470 tr("Process (PID %u) not found!"), aPID);
1471 }
1472 catch (std::bad_alloc &)
1473 {
1474 rc = E_OUTOFMEMORY;
1475 }
1476 return rc;
1477#endif
1478}
1479
1480// public methods only for internal purposes
1481/////////////////////////////////////////////////////////////////////////////
1482
1483/**
1484 * Sets the general Guest Additions information like
1485 * API (interface) version and OS type. Gets called by
1486 * vmmdevUpdateGuestInfo.
1487 *
1488 * @param aInterfaceVersion
1489 * @param aOsType
1490 */
1491void Guest::setAdditionsInfo(Bstr aInterfaceVersion, VBOXOSTYPE aOsType)
1492{
1493 AutoCaller autoCaller(this);
1494 AssertComRCReturnVoid (autoCaller.rc());
1495
1496 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1497
1498 /*
1499 * Note: The Guest Additions API (interface) version is deprecated
1500 * and will not be used anymore! We might need it to at least report
1501 * something as version number if *really* ancient Guest Additions are
1502 * installed (without the guest version + revision properties having set).
1503 */
1504 mData.mInterfaceVersion = aInterfaceVersion;
1505
1506 /*
1507 * Older Additions rely on the Additions API version whether they
1508 * are assumed to be active or not. Since newer Additions do report
1509 * the Additions version *before* calling this function (by calling
1510 * VMMDevReportGuestInfo2, VMMDevReportGuestStatus, VMMDevReportGuestInfo,
1511 * in that order) we can tell apart old and new Additions here. Old
1512 * Additions never would set VMMDevReportGuestInfo2 (which set mData.mAdditionsVersion)
1513 * so they just rely on the aInterfaceVersion string (which gets set by
1514 * VMMDevReportGuestInfo).
1515 *
1516 * So only mark the Additions as being active (run level = system) when we
1517 * don't have the Additions version set.
1518 */
1519 if (mData.mAdditionsVersion.isEmpty())
1520 {
1521 mData.mAdditionsRunLevel = aInterfaceVersion.isEmpty()
1522 ? VBoxGuestAdditionsRunLevel_None
1523 : VBoxGuestAdditionsRunLevel_System;
1524 }
1525
1526 /*
1527 * Older Additions didn't have this finer grained capability bit,
1528 * so enable it by default. Newer Additions will not enable this here
1529 * and use the setSupportedFeatures function instead.
1530 */
1531 mData.mSupportsGraphics = mData.mAdditionsRunLevel > VBoxGuestAdditionsRunLevel_None;
1532
1533 /*
1534 * Note! There is a race going on between setting mAdditionsRunLevel and
1535 * mSupportsGraphics here and disabling/enabling it later according to
1536 * its real status when using new(er) Guest Additions.
1537 */
1538 mData.mOSTypeId = Global::OSTypeId (aOsType);
1539}
1540
1541/**
1542 * Sets the Guest Additions version information details.
1543 * Gets called by vmmdevUpdateGuestInfo2.
1544 *
1545 * @param aAdditionsVersion
1546 * @param aVersionName
1547 */
1548void Guest::setAdditionsInfo2(Bstr aAdditionsVersion, Bstr aVersionName, Bstr aRevision)
1549{
1550 AutoCaller autoCaller(this);
1551 AssertComRCReturnVoid (autoCaller.rc());
1552
1553 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1554
1555 if (!aVersionName.isEmpty())
1556 /*
1557 * aVersionName could be "x.y.z_BETA1_FOOBAR", so append revision manually to
1558 * become "x.y.z_BETA1_FOOBARr12345".
1559 */
1560 mData.mAdditionsVersion = BstrFmt("%ls r%ls", aVersionName.raw(), aRevision.raw());
1561 else /* aAdditionsVersion is in x.y.zr12345 format. */
1562 mData.mAdditionsVersion = aAdditionsVersion;
1563}
1564
1565/**
1566 * Sets the status of a certain Guest Additions facility.
1567 * Gets called by vmmdevUpdateGuestStatus.
1568 *
1569 * @param Facility
1570 * @param Status
1571 * @param ulFlags
1572 */
1573void Guest::setAdditionsStatus(VBoxGuestStatusFacility Facility, VBoxGuestStatusCurrent Status, ULONG ulFlags)
1574{
1575 AutoCaller autoCaller(this);
1576 AssertComRCReturnVoid (autoCaller.rc());
1577
1578 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1579
1580 uint32_t uCurFacility = Facility + (Status == VBoxGuestStatusCurrent_Active ? 0 : -1);
1581
1582 /* First check for disabled status. */
1583 if ( Facility < VBoxGuestStatusFacility_VBoxGuestDriver
1584 || ( Facility == VBoxGuestStatusFacility_All
1585 && ( Status == VBoxGuestStatusCurrent_Inactive
1586 || Status == VBoxGuestStatusCurrent_Disabled
1587 )
1588 )
1589 )
1590 {
1591 mData.mAdditionsRunLevel = VBoxGuestAdditionsRunLevel_None;
1592 }
1593 else if (uCurFacility >= VBoxGuestStatusFacility_VBoxTray)
1594 {
1595 mData.mAdditionsRunLevel = VBoxGuestAdditionsRunLevel_Desktop;
1596 }
1597 else if (uCurFacility >= VBoxGuestStatusFacility_VBoxService)
1598 {
1599 mData.mAdditionsRunLevel = VBoxGuestAdditionsRunLevel_Userland;
1600 }
1601 else if (uCurFacility >= VBoxGuestStatusFacility_VBoxGuestDriver)
1602 {
1603 mData.mAdditionsRunLevel = VBoxGuestAdditionsRunLevel_System;
1604 }
1605 else /* Should never happen! */
1606 AssertMsgFailed(("Invalid facility status/run level detected! uCurFacility=%ld\n", uCurFacility));
1607}
1608
1609/**
1610 * Sets the supported features (and whether they are active or not).
1611 *
1612 * @param fCaps Guest capability bit mask (VMMDEV_GUEST_SUPPORTS_XXX).
1613 * @param fActive No idea what this is supposed to be, it's always 0 and
1614 * not references by this method.
1615 */
1616void Guest::setSupportedFeatures(uint32_t fCaps, uint32_t fActive)
1617{
1618 AutoCaller autoCaller(this);
1619 AssertComRCReturnVoid (autoCaller.rc());
1620
1621 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1622
1623 mData.mSupportsSeamless = (fCaps & VMMDEV_GUEST_SUPPORTS_SEAMLESS);
1624 /** @todo Add VMMDEV_GUEST_SUPPORTS_GUEST_HOST_WINDOW_MAPPING */
1625 mData.mSupportsGraphics = (fCaps & VMMDEV_GUEST_SUPPORTS_GRAPHICS);
1626}
1627/* 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