VirtualBox

source: vbox/trunk/src/VBox/Main/src-client/GuestProcessImpl.cpp@ 42160

Last change on this file since 42160 was 42160, checked in by vboxsync, 13 years ago

Guest Control 2.0: Update.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 19.0 KB
Line 
1
2/* $Id: GuestProcessImpl.cpp 42160 2012-07-16 11:41:10Z vboxsync $ */
3/** @file
4 * VirtualBox Main - XXX.
5 */
6
7/*
8 * Copyright (C) 2012 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
20/*******************************************************************************
21* Header Files *
22*******************************************************************************/
23#include "GuestProcessImpl.h"
24#include "GuestSessionImpl.h"
25#include "ConsoleImpl.h"
26
27#include "Global.h"
28#include "AutoCaller.h"
29#include "Logging.h"
30#include "VMMDev.h"
31
32#include <iprt/asm.h>
33#include <iprt/getopt.h>
34#include <VBox/VMMDev.h>
35#include <VBox/com/array.h>
36
37
38struct GuestProcessTask
39{
40 GuestProcessTask(GuestProcess *pProcess)
41 : mProcess(pProcess) { }
42
43 ~GuestProcessTask(void) { }
44
45 int rc() const { return mRC; }
46 bool isOk() const { return RT_SUCCESS(rc()); }
47
48 const ComObjPtr<GuestProcess> mProcess;
49
50private:
51 int mRC;
52};
53
54struct GuestProcessStartTask : public GuestProcessTask
55{
56 GuestProcessStartTask(GuestProcess *pProcess)
57 : GuestProcessTask(pProcess) { }
58};
59
60
61// constructor / destructor
62/////////////////////////////////////////////////////////////////////////////
63
64DEFINE_EMPTY_CTOR_DTOR(GuestProcess)
65
66HRESULT GuestProcess::FinalConstruct(void)
67{
68 LogFlowThisFunc(("\n"));
69
70 mData.mExitCode = 0;
71 mData.mNextContextID = 0;
72 mData.mPID = 0;
73 mData.mProcessID = 0;
74 mData.mStatus = ProcessStatus_Undefined;
75 mData.mStarted = false;
76
77 return BaseFinalConstruct();
78}
79
80void GuestProcess::FinalRelease(void)
81{
82 LogFlowThisFuncEnter();
83 uninit();
84 BaseFinalRelease();
85 LogFlowThisFuncLeave();
86}
87
88// public initializer/uninitializer for internal purposes only
89/////////////////////////////////////////////////////////////////////////////
90
91int GuestProcess::init(Console *aConsole, GuestSession *aSession, uint32_t aProcessID, const GuestProcessInfo &aProcInfo)
92{
93 AssertPtrReturn(aSession, VERR_INVALID_POINTER);
94
95 /* Enclose the state transition NotReady->InInit->Ready. */
96 AutoInitSpan autoInitSpan(this);
97 AssertReturn(autoInitSpan.isOk(), VERR_OBJECT_DESTROYED);
98
99 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
100
101 mData.mConsole = aConsole;
102 mData.mParent = aSession;
103 mData.mProcessID = aProcessID;
104 mData.mStatus = ProcessStatus_Starting;
105 /* Everything else will be set by the actual starting routine. */
106
107 /* Asynchronously start the process on the guest by kicking off a
108 * worker thread. */
109 std::auto_ptr<GuestProcessStartTask> pTask(new GuestProcessStartTask(this));
110 AssertReturn(pTask->isOk(), pTask->rc());
111
112 int rc = RTThreadCreate(NULL, GuestProcess::startProcessThread,
113 (void *)pTask.get(), 0,
114 RTTHREADTYPE_MAIN_WORKER, 0,
115 "gctlPrcStart");
116 if (RT_SUCCESS(rc))
117 {
118 /* task is now owned by startProcessThread(), so release it. */
119 pTask.release();
120
121 /* Confirm a successful initialization when it's the case. */
122 autoInitSpan.setSucceeded();
123 }
124
125 return rc;
126}
127
128/**
129 * Uninitializes the instance.
130 * Called from FinalRelease().
131 */
132void GuestProcess::uninit(void)
133{
134 LogFlowThisFunc(("\n"));
135
136 /* Enclose the state transition Ready->InUninit->NotReady. */
137 AutoUninitSpan autoUninitSpan(this);
138 if (autoUninitSpan.uninitDone())
139 return;
140}
141
142// implementation of public getters/setters for attributes
143/////////////////////////////////////////////////////////////////////////////
144
145STDMETHODIMP GuestProcess::COMGETTER(Arguments)(ComSafeArrayOut(BSTR, aArguments))
146{
147#ifndef VBOX_WITH_GUEST_CONTROL
148 ReturnComNotImplemented();
149#else
150 AutoCaller autoCaller(this);
151 if (FAILED(autoCaller.rc())) return autoCaller.rc();
152
153 CheckComArgOutSafeArrayPointerValid(aArguments);
154
155 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
156
157 com::SafeArray<BSTR> collection(mData.mProcess.mArguments.size());
158 size_t s = 0;
159 for (ProcessArguments::const_iterator it = mData.mProcess.mArguments.begin();
160 it != mData.mProcess.mArguments.end();
161 ++it, ++s)
162 {
163 collection[s] = Bstr((*it)).raw();
164 }
165
166 collection.detachTo(ComSafeArrayOutArg(aArguments));
167
168 return S_OK;
169#endif /* VBOX_WITH_GUEST_CONTROL */
170}
171
172STDMETHODIMP GuestProcess::COMGETTER(Environment)(ComSafeArrayOut(BSTR, aEnvironment))
173{
174#ifndef VBOX_WITH_GUEST_CONTROL
175 ReturnComNotImplemented();
176#else
177 AutoCaller autoCaller(this);
178 if (FAILED(autoCaller.rc())) return autoCaller.rc();
179
180 CheckComArgOutSafeArrayPointerValid(aEnvironment);
181
182 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
183
184 com::SafeArray<BSTR> collection(mData.mProcess.mEnvironment.size());
185 size_t s = 0;
186 for (ProcessEnvironmentMap::const_iterator it = mData.mProcess.mEnvironment.begin();
187 it != mData.mProcess.mEnvironment.end();
188 ++it, ++s)
189 {
190 collection[s] = Bstr(it->first + "=" + it->second).raw();
191 }
192
193 collection.detachTo(ComSafeArrayOutArg(aEnvironment));
194
195 return S_OK;
196#endif /* VBOX_WITH_GUEST_CONTROL */
197}
198
199STDMETHODIMP GuestProcess::COMGETTER(ExecutablePath)(BSTR *aExecutablePath)
200{
201#ifndef VBOX_WITH_GUEST_CONTROL
202 ReturnComNotImplemented();
203#else
204 AutoCaller autoCaller(this);
205 if (FAILED(autoCaller.rc())) return autoCaller.rc();
206
207 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
208
209 mData.mProcess.mCommand.cloneTo(aExecutablePath);
210
211 return S_OK;
212#endif /* VBOX_WITH_GUEST_CONTROL */
213}
214
215STDMETHODIMP GuestProcess::COMGETTER(ExitCode)(LONG *aExitCode)
216{
217#ifndef VBOX_WITH_GUEST_CONTROL
218 ReturnComNotImplemented();
219#else
220 AutoCaller autoCaller(this);
221 if (FAILED(autoCaller.rc())) return autoCaller.rc();
222
223 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
224
225 *aExitCode = mData.mExitCode;
226
227 return S_OK;
228#endif /* VBOX_WITH_GUEST_CONTROL */
229}
230
231STDMETHODIMP GuestProcess::COMGETTER(Pid)(ULONG *aPID)
232{
233#ifndef VBOX_WITH_GUEST_CONTROL
234 ReturnComNotImplemented();
235#else
236 AutoCaller autoCaller(this);
237 if (FAILED(autoCaller.rc())) return autoCaller.rc();
238
239 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
240
241 *aPID = mData.mPID;
242
243 return S_OK;
244#endif /* VBOX_WITH_GUEST_CONTROL */
245}
246
247STDMETHODIMP GuestProcess::COMGETTER(Status)(ProcessStatus_T *aStatus)
248{
249#ifndef VBOX_WITH_GUEST_CONTROL
250 ReturnComNotImplemented();
251#else
252 AutoCaller autoCaller(this);
253 if (FAILED(autoCaller.rc())) return autoCaller.rc();
254
255 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
256
257 *aStatus = mData.mStatus;
258
259 return S_OK;
260#endif /* VBOX_WITH_GUEST_CONTROL */
261}
262
263// private methods
264/////////////////////////////////////////////////////////////////////////////
265
266/*
267
268 SYNC TO ASK:
269 Everything which involves HGCM communication (start, read/write/status(?)/...)
270 either can be called synchronously or asynchronously by running in a Main worker
271 thread.
272
273 Rules:
274 - Only one async operation per process a time can be around.
275
276*/
277
278int GuestProcess::callbackAdd(const GuestCtrlCallback& theCallback, uint32_t *puContextID)
279{
280 const ComObjPtr<GuestSession> pSession(mData.mParent);
281 Assert(!pSession.isNull());
282 ULONG uSessionID = 0;
283 HRESULT hr = pSession->COMGETTER(Id)(&uSessionID);
284 ComAssertComRC(hr);
285
286 /* Create a new context ID and assign it. */
287 int rc = VERR_NOT_FOUND;
288 uint32_t uNewContextID = 0;
289 uint32_t uTries = 0;
290 for (;;)
291 {
292 /* Create a new context ID ... */
293 uNewContextID = VBOX_GUESTCTRL_CONTEXTID_MAKE(uSessionID,
294 mData.mProcessID, ASMAtomicIncU32(&mData.mNextContextID));
295 if (uNewContextID == UINT32_MAX)
296 ASMAtomicUoWriteU32(&mData.mNextContextID, 0);
297 /* Is the context ID already used? Try next ID ... */
298 if (!callbackExists(uNewContextID))
299 {
300 /* Callback with context ID was not found. This means
301 * we can use this context ID for our new callback we want
302 * to add below. */
303 rc = VINF_SUCCESS;
304 break;
305 }
306
307 if (++uTries == UINT32_MAX)
308 break; /* Don't try too hard. */
309 }
310
311 if (RT_SUCCESS(rc))
312 {
313 /* Add callback with new context ID to our callback map. */
314 mData.mCallbacks[uNewContextID] = theCallback;
315 Assert(mData.mCallbacks.size());
316
317 /* Report back new context ID. */
318 if (puContextID)
319 *puContextID = uNewContextID;
320 }
321
322 return rc;
323}
324
325bool GuestProcess::callbackExists(uint32_t uContextID)
326{
327 AssertReturn(uContextID, false);
328
329 GuestCtrlCallbacks::const_iterator it = mData.mCallbacks.find(uContextID);
330 return (it == mData.mCallbacks.end()) ? false : true;
331}
332
333bool GuestProcess::isReady(void)
334{
335 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
336
337 if (mData.mStatus == ProcessStatus_Started)
338 {
339 Assert(mData.mPID); /* PID must not be 0. */
340 return true;
341 }
342
343 return false;
344}
345
346/**
347 * Appends environment variables to the environment block.
348 *
349 * Each var=value pair is separated by the null character ('\\0'). The whole
350 * block will be stored in one blob and disassembled on the guest side later to
351 * fit into the HGCM param structure.
352 *
353 * @returns VBox status code.
354 *
355 * @param pszEnvVar The environment variable=value to append to the
356 * environment block.
357 * @param ppvList This is actually a pointer to a char pointer
358 * variable which keeps track of the environment block
359 * that we're constructing.
360 * @param pcbList Pointer to the variable holding the current size of
361 * the environment block. (List is a misnomer, go
362 * ahead a be confused.)
363 * @param pcEnvVars Pointer to the variable holding count of variables
364 * stored in the environment block.
365 */
366int GuestProcess::prepareExecuteEnv(const char *pszEnv, void **ppvList, uint32_t *pcbList, uint32_t *pcEnvVars)
367{
368 int rc = VINF_SUCCESS;
369 uint32_t cchEnv = strlen(pszEnv); Assert(cchEnv >= 2);
370 if (*ppvList)
371 {
372 uint32_t cbNewLen = *pcbList + cchEnv + 1; /* Include zero termination. */
373 char *pvTmp = (char *)RTMemRealloc(*ppvList, cbNewLen);
374 if (pvTmp == NULL)
375 rc = VERR_NO_MEMORY;
376 else
377 {
378 memcpy(pvTmp + *pcbList, pszEnv, cchEnv);
379 pvTmp[cbNewLen - 1] = '\0'; /* Add zero termination. */
380 *ppvList = (void **)pvTmp;
381 }
382 }
383 else
384 {
385 char *pszTmp;
386 if (RTStrAPrintf(&pszTmp, "%s", pszEnv) >= 0)
387 {
388 *ppvList = (void **)pszTmp;
389 /* Reset counters. */
390 *pcEnvVars = 0;
391 *pcbList = 0;
392 }
393 }
394 if (RT_SUCCESS(rc))
395 {
396 *pcbList += cchEnv + 1; /* Include zero termination. */
397 *pcEnvVars += 1; /* Increase env variable count. */
398 }
399 return rc;
400}
401
402int GuestProcess::readData(ULONG aHandle, ULONG aSize, ULONG aTimeoutMS, ComSafeArrayOut(BYTE, aData))
403{
404 LogFlowFuncEnter();
405
406 LogFlowFuncLeave();
407 return 0;
408}
409
410int GuestProcess::startProcess(void)
411{
412 LogFlowFuncEnter();
413
414 AssertReturn(!mData.mStarted, VERR_ALREADY_EXISTS);
415
416 int rc;
417 uint32_t uContextID;
418
419 {
420 /* Wait until the caller function (if kicked off by a thread)
421 * has returned and continue operation. */
422 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
423
424 /* Create callback and add it to the map. */
425 GuestCtrlCallback callbackStart;
426 rc = callbackStart.Init(VBOXGUESTCTRLCALLBACKTYPE_EXEC_START);
427 if (RT_FAILURE(rc))
428 return rc;
429
430 rc = callbackAdd(callbackStart, &uContextID);
431 Assert(uContextID);
432 }
433
434 if (RT_SUCCESS(rc))
435 {
436 ComObjPtr<GuestSession> pSession(mData.mParent);
437 Assert(!pSession.isNull());
438
439 const GuestCredentials &sessionCreds = pSession->getCredentials();
440
441 /* Prepare arguments. */
442 char *pszArgs = NULL;
443 size_t cArgs = mData.mProcess.mArguments.size();
444 if (cArgs)
445 {
446 char **papszArgv = (char**)RTMemAlloc(sizeof(char*) * (cArgs + 1));
447 AssertReturn(papszArgv, VERR_NO_MEMORY);
448 for (size_t i = 0; RT_SUCCESS(rc) && i < cArgs; i++)
449 rc = RTStrDupEx(&papszArgv[i], mData.mProcess.mArguments[i].c_str());
450 papszArgv[cArgs] = NULL;
451
452 if (RT_SUCCESS(rc))
453 rc = RTGetOptArgvToString(&pszArgs, papszArgv, RTGETOPTARGV_CNV_QUOTE_MS_CRT);
454 }
455 uint32_t cbArgs = pszArgs ? strlen(pszArgs) + 1 : 0; /* Include terminating zero. */
456
457 /* Prepare environment. */
458 void *pvEnv = NULL;
459 size_t cEnv = mData.mProcess.mEnvironment.size();
460 uint32_t cbEnv = 0;
461 if ( RT_SUCCESS(rc)
462 && cEnv)
463 {
464 uint32_t cEnvBuild = 0;
465 ProcessEnvironmentMap::const_iterator itEnv = mData.mProcess.mEnvironment.begin();
466 for (; itEnv != mData.mProcess.mEnvironment.end() && RT_SUCCESS(rc); itEnv++)
467 {
468 char *pszEnv;
469 if (!RTStrAPrintf(&pszEnv, "%s=%s", itEnv->first, itEnv->second))
470 break;
471 AssertPtr(pszEnv);
472 rc = prepareExecuteEnv(pszEnv, &pvEnv, &cbEnv, &cEnvBuild);
473 RTStrFree(pszEnv);
474 }
475 Assert(cEnv == cEnvBuild);
476 }
477
478 if (RT_SUCCESS(rc))
479 {
480 /* Prepare HGCM call. */
481 VBOXHGCMSVCPARM paParms[15];
482 int i = 0;
483 paParms[i++].setUInt32(uContextID);
484 paParms[i++].setPointer((void*)mData.mProcess.mCommand.c_str(),
485 (uint32_t)mData.mProcess.mCommand.length() + 1);
486 paParms[i++].setUInt32(mData.mProcess.mFlags);
487 paParms[i++].setUInt32(mData.mProcess.mArguments.size());
488 paParms[i++].setPointer((void*)pszArgs, cbArgs);
489 paParms[i++].setUInt32(mData.mProcess.mEnvironment.size());
490 paParms[i++].setUInt32(cbEnv);
491 paParms[i++].setPointer((void*)pvEnv, cbEnv);
492 paParms[i++].setPointer((void*)sessionCreds.mUser.c_str(), (uint32_t)sessionCreds.mUser.length() + 1);
493 paParms[i++].setPointer((void*)sessionCreds.mPassword.c_str(), (uint32_t)sessionCreds.mPassword.length() + 1);
494
495 /*
496 * If the WaitForProcessStartOnly flag is set, we only want to define and wait for a timeout
497 * until the process was started - the process itself then gets an infinite timeout for execution.
498 * This is handy when we want to start a process inside a worker thread within a certain timeout
499 * but let the started process perform lengthly operations then.
500 */
501 if (mData.mProcess.mFlags & ProcessCreateFlag_WaitForProcessStartOnly)
502 paParms[i++].setUInt32(UINT32_MAX /* Infinite timeout */);
503 else
504 paParms[i++].setUInt32(mData.mProcess.mTimeoutMS);
505
506 const ComObjPtr<Console> pConsole(mData.mConsole);
507 Assert(!pConsole.isNull());
508
509 VMMDev *pVMMDev = NULL;
510 {
511 /* Make sure mParent is valid, so set the read lock while using.
512 * Do not keep this lock while doing the actual call, because in the meanwhile
513 * another thread could request a write lock which would be a bad idea ... */
514 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
515
516 /* Forward the information to the VMM device. */
517 pVMMDev = pConsole->getVMMDev();
518 }
519
520 LogFlowFunc(("hgcmHostCall numParms=%d, CID=%RU32\n", i, uContextID));
521 rc = pVMMDev->hgcmHostCall("VBoxGuestControlSvc", HOST_EXEC_CMD,
522 i, paParms);
523 }
524
525 if (pvEnv)
526 RTMemFree(pvEnv);
527 if (pszArgs)
528 RTStrFree(pszArgs);
529 }
530
531 LogFlowFuncLeave();
532 return rc;
533}
534
535DECLCALLBACK(int) GuestProcess::startProcessThread(RTTHREAD Thread, void *pvUser)
536{
537 LogFlowFuncEnter();
538
539 const ComObjPtr<GuestProcess> pProcess = static_cast<GuestProcess*>(pvUser);
540 Assert(!pProcess.isNull());
541
542 int rc = pProcess->startProcess();
543
544 LogFlowFuncLeave();
545 return rc;
546}
547
548int GuestProcess::terminateProcess(void)
549{
550 LogFlowFuncEnter();
551
552 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
553
554 LogFlowFuncLeave();
555 return 0;
556}
557
558int GuestProcess::waitFor(ComSafeArrayIn(ProcessWaitForFlag_T, aFlags), ULONG aTimeoutMS, ProcessWaitReason_T *aReason)
559{
560 LogFlowFuncEnter();
561
562 LogFlowFuncLeave();
563 return 0;
564}
565
566int GuestProcess::writeData(ULONG aHandle, ComSafeArrayIn(BYTE, aData), ULONG aTimeoutMS, ULONG *aWritten)
567{
568 LogFlowFuncEnter();
569
570 LogFlowFuncLeave();
571 return 0;
572}
573
574// implementation of public methods
575/////////////////////////////////////////////////////////////////////////////
576
577STDMETHODIMP GuestProcess::Read(ULONG aHandle, ULONG aSize, ULONG aTimeoutMS, ComSafeArrayOut(BYTE, aData))
578{
579#ifndef VBOX_WITH_GUEST_CONTROL
580 ReturnComNotImplemented();
581#else
582
583 AutoCaller autoCaller(this);
584 if (FAILED(autoCaller.rc())) return autoCaller.rc();
585
586 int rc = readData(aHandle, aSize, aTimeoutMS, ComSafeArrayOutArg(aData));
587 /** @todo Do setError() here. */
588 return RT_SUCCESS(rc) ? S_OK : VBOX_E_IPRT_ERROR;
589#endif /* VBOX_WITH_GUEST_CONTROL */
590}
591
592STDMETHODIMP GuestProcess::Terminate(void)
593{
594#ifndef VBOX_WITH_GUEST_CONTROL
595 ReturnComNotImplemented();
596#else
597 AutoCaller autoCaller(this);
598 if (FAILED(autoCaller.rc())) return autoCaller.rc();
599
600 int rc = terminateProcess();
601 /** @todo Do setError() here. */
602 return RT_SUCCESS(rc) ? S_OK : VBOX_E_IPRT_ERROR;
603#endif /* VBOX_WITH_GUEST_CONTROL */
604}
605
606STDMETHODIMP GuestProcess::WaitFor(ComSafeArrayIn(ProcessWaitForFlag_T, aFlags), ULONG aTimeoutMS, ProcessWaitReason_T *aReason)
607{
608#ifndef VBOX_WITH_GUEST_CONTROL
609 ReturnComNotImplemented();
610#else
611 AutoCaller autoCaller(this);
612 if (FAILED(autoCaller.rc())) return autoCaller.rc();
613
614 int rc = waitFor(ComSafeArrayInArg(aFlags), aTimeoutMS, aReason);
615 /** @todo Do setError() here. */
616 return RT_SUCCESS(rc) ? S_OK : VBOX_E_IPRT_ERROR;
617#endif /* VBOX_WITH_GUEST_CONTROL */
618}
619
620STDMETHODIMP GuestProcess::Write(ULONG aHandle, ComSafeArrayIn(BYTE, aData), ULONG aTimeoutMS, ULONG *aWritten)
621{
622#ifndef VBOX_WITH_GUEST_CONTROL
623 ReturnComNotImplemented();
624#else
625 AutoCaller autoCaller(this);
626 if (FAILED(autoCaller.rc())) return autoCaller.rc();
627
628 int rc = writeData(aHandle, ComSafeArrayInArg(aData), aTimeoutMS, aWritten);
629 /** @todo Do setError() here. */
630 return RT_SUCCESS(rc) ? S_OK : VBOX_E_IPRT_ERROR;
631#endif /* VBOX_WITH_GUEST_CONTROL */
632}
633
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