VirtualBox

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

Last change on this file since 69496 was 69496, checked in by vboxsync, 7 years ago

*: scm --update-copyright-year

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 79.6 KB
Line 
1/* $Id: GuestProcessImpl.cpp 69496 2017-10-28 14:55:58Z vboxsync $ */
2/** @file
3 * VirtualBox Main - Guest process handling.
4 */
5
6/*
7 * Copyright (C) 2012-2017 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18/**
19 * Locking rules:
20 * - When the main dispatcher (callbackDispatcher) is called it takes the
21 * WriteLock while dispatching to the various on* methods.
22 * - All other outer functions (accessible by Main) must not own a lock
23 * while waiting for a callback or for an event.
24 * - Only keep Read/WriteLocks as short as possible and only when necessary.
25 */
26
27
28/*********************************************************************************************************************************
29* Header Files *
30*********************************************************************************************************************************/
31#define LOG_GROUP LOG_GROUP_GUEST_CONTROL //LOG_GROUP_MAIN_GUESTPROCESS
32#include "LoggingNew.h"
33
34#ifndef VBOX_WITH_GUEST_CONTROL
35# error "VBOX_WITH_GUEST_CONTROL must defined in this file"
36#endif
37#include "GuestProcessImpl.h"
38#include "GuestSessionImpl.h"
39#include "GuestCtrlImplPrivate.h"
40#include "ConsoleImpl.h"
41#include "VirtualBoxErrorInfoImpl.h"
42
43#include "Global.h"
44#include "AutoCaller.h"
45#include "VBoxEvents.h"
46#include "ThreadTask.h"
47
48#include <memory> /* For auto_ptr. */
49
50#include <iprt/asm.h>
51#include <iprt/cpp/utils.h> /* For unconst(). */
52#include <iprt/getopt.h>
53
54#include <VBox/com/listeners.h>
55
56#include <VBox/com/array.h>
57
58
59class GuestProcessTask : public ThreadTask
60{
61public:
62
63 GuestProcessTask(GuestProcess *pProcess)
64 : ThreadTask("GenericGuestProcessTask")
65 , mProcess(pProcess)
66 , mRC(VINF_SUCCESS) { }
67
68 virtual ~GuestProcessTask(void) { }
69
70 int i_rc(void) const { return mRC; }
71 bool i_isOk(void) const { return RT_SUCCESS(mRC); }
72 const ComObjPtr<GuestProcess> &i_process(void) const { return mProcess; }
73
74protected:
75
76 const ComObjPtr<GuestProcess> mProcess;
77 int mRC;
78};
79
80class GuestProcessStartTask : public GuestProcessTask
81{
82public:
83
84 GuestProcessStartTask(GuestProcess *pProcess)
85 : GuestProcessTask(pProcess)
86 {
87 m_strTaskName = "gctlPrcStart";
88 }
89
90 void handler()
91 {
92 GuestProcess::i_startProcessThreadTask(this);
93 }
94};
95
96/**
97 * Internal listener class to serve events in an
98 * active manner, e.g. without polling delays.
99 */
100class GuestProcessListener
101{
102public:
103
104 GuestProcessListener(void)
105 {
106 }
107
108 virtual ~GuestProcessListener(void)
109 {
110 }
111
112 HRESULT init(GuestProcess *pProcess)
113 {
114 AssertPtrReturn(pProcess, E_POINTER);
115 mProcess = pProcess;
116 return S_OK;
117 }
118
119 void uninit(void)
120 {
121 mProcess = NULL;
122 }
123
124 STDMETHOD(HandleEvent)(VBoxEventType_T aType, IEvent *aEvent)
125 {
126 switch (aType)
127 {
128 case VBoxEventType_OnGuestProcessStateChanged:
129 case VBoxEventType_OnGuestProcessInputNotify:
130 case VBoxEventType_OnGuestProcessOutput:
131 {
132 AssertPtrReturn(mProcess, E_POINTER);
133 int rc2 = mProcess->signalWaitEvent(aType, aEvent);
134 RT_NOREF(rc2);
135#ifdef LOG_ENABLED
136 LogFlowThisFunc(("Signalling events of type=%RU32, pProcess=%p resulted in rc=%Rrc\n",
137 aType, &mProcess, rc2));
138#endif
139 break;
140 }
141
142 default:
143 AssertMsgFailed(("Unhandled event %RU32\n", aType));
144 break;
145 }
146
147 return S_OK;
148 }
149
150private:
151
152 GuestProcess *mProcess;
153};
154typedef ListenerImpl<GuestProcessListener, GuestProcess*> GuestProcessListenerImpl;
155
156VBOX_LISTENER_DECLARE(GuestProcessListenerImpl)
157
158// constructor / destructor
159/////////////////////////////////////////////////////////////////////////////
160
161DEFINE_EMPTY_CTOR_DTOR(GuestProcess)
162
163HRESULT GuestProcess::FinalConstruct(void)
164{
165 LogFlowThisFuncEnter();
166 return BaseFinalConstruct();
167}
168
169void GuestProcess::FinalRelease(void)
170{
171 LogFlowThisFuncEnter();
172 uninit();
173 BaseFinalRelease();
174 LogFlowThisFuncLeave();
175}
176
177// public initializer/uninitializer for internal purposes only
178/////////////////////////////////////////////////////////////////////////////
179
180int GuestProcess::init(Console *aConsole, GuestSession *aSession, ULONG aProcessID,
181 const GuestProcessStartupInfo &aProcInfo, const GuestEnvironment *pBaseEnv)
182{
183 LogFlowThisFunc(("aConsole=%p, aSession=%p, aProcessID=%RU32 pBaseEnv=%p\n",
184 aConsole, aSession, aProcessID, pBaseEnv));
185
186 AssertPtrReturn(aConsole, VERR_INVALID_POINTER);
187 AssertPtrReturn(aSession, VERR_INVALID_POINTER);
188
189 /* Enclose the state transition NotReady->InInit->Ready. */
190 AutoInitSpan autoInitSpan(this);
191 AssertReturn(autoInitSpan.isOk(), VERR_OBJECT_DESTROYED);
192
193 HRESULT hr;
194
195 int vrc = bindToSession(aConsole, aSession, aProcessID /* Object ID */);
196 if (RT_SUCCESS(vrc))
197 {
198 hr = unconst(mEventSource).createObject();
199 if (FAILED(hr))
200 vrc = VERR_NO_MEMORY;
201 else
202 {
203 hr = mEventSource->init();
204 if (FAILED(hr))
205 vrc = VERR_COM_UNEXPECTED;
206 }
207 }
208
209 if (RT_SUCCESS(vrc))
210 {
211 try
212 {
213 GuestProcessListener *pListener = new GuestProcessListener();
214 ComObjPtr<GuestProcessListenerImpl> thisListener;
215 hr = thisListener.createObject();
216 if (SUCCEEDED(hr))
217 hr = thisListener->init(pListener, this);
218
219 if (SUCCEEDED(hr))
220 {
221 com::SafeArray <VBoxEventType_T> eventTypes;
222 eventTypes.push_back(VBoxEventType_OnGuestProcessStateChanged);
223 eventTypes.push_back(VBoxEventType_OnGuestProcessInputNotify);
224 eventTypes.push_back(VBoxEventType_OnGuestProcessOutput);
225 hr = mEventSource->RegisterListener(thisListener,
226 ComSafeArrayAsInParam(eventTypes),
227 TRUE /* Active listener */);
228 if (SUCCEEDED(hr))
229 {
230 vrc = baseInit();
231 if (RT_SUCCESS(vrc))
232 {
233 mLocalListener = thisListener;
234 }
235 }
236 else
237 vrc = VERR_COM_UNEXPECTED;
238 }
239 else
240 vrc = VERR_COM_UNEXPECTED;
241 }
242 catch(std::bad_alloc &)
243 {
244 vrc = VERR_NO_MEMORY;
245 }
246 }
247
248 if (RT_SUCCESS(vrc))
249 {
250 mData.mProcess = aProcInfo;
251 mData.mpSessionBaseEnv = pBaseEnv;
252 if (pBaseEnv)
253 pBaseEnv->retainConst();
254 mData.mExitCode = 0;
255 mData.mPID = 0;
256 mData.mLastError = VINF_SUCCESS;
257 mData.mStatus = ProcessStatus_Undefined;
258 /* Everything else will be set by the actual starting routine. */
259
260 /* Confirm a successful initialization when it's the case. */
261 autoInitSpan.setSucceeded();
262
263 return vrc;
264 }
265
266 autoInitSpan.setFailed();
267 return vrc;
268}
269
270/**
271 * Uninitializes the instance.
272 * Called from FinalRelease() or IGuestSession::uninit().
273 */
274void GuestProcess::uninit(void)
275{
276 /* Enclose the state transition Ready->InUninit->NotReady. */
277 AutoUninitSpan autoUninitSpan(this);
278 if (autoUninitSpan.uninitDone())
279 return;
280
281 LogFlowThisFunc(("mExe=%s, PID=%RU32\n", mData.mProcess.mExecutable.c_str(), mData.mPID));
282
283 /* Terminate process if not already done yet. */
284 int guestRc = VINF_SUCCESS;
285 int vrc = i_terminateProcess(30 * 1000, &guestRc); /** @todo Make timeouts configurable. */
286 /* Note: Don't return here yet; first uninit all other stuff in
287 * case of failure. */
288
289 if (mData.mpSessionBaseEnv)
290 {
291 mData.mpSessionBaseEnv->releaseConst();
292 mData.mpSessionBaseEnv = NULL;
293 }
294
295 baseUninit();
296
297 LogFlowThisFunc(("Returning rc=%Rrc, guestRc=%Rrc\n",
298 vrc, guestRc));
299 RT_NOREF_PV(vrc);
300}
301
302// implementation of public getters/setters for attributes
303/////////////////////////////////////////////////////////////////////////////
304HRESULT GuestProcess::getArguments(std::vector<com::Utf8Str> &aArguments)
305{
306 LogFlowThisFuncEnter();
307
308 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
309 aArguments = mData.mProcess.mArguments;
310 return S_OK;
311}
312
313HRESULT GuestProcess::getEnvironment(std::vector<com::Utf8Str> &aEnvironment)
314{
315#ifndef VBOX_WITH_GUEST_CONTROL
316 ReturnComNotImplemented();
317#else
318 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); /* (Paranoia since both environment objects are immutable.) */
319 HRESULT hrc;
320 if (mData.mpSessionBaseEnv)
321 {
322 int vrc;
323 if (mData.mProcess.mEnvironmentChanges.count() == 0)
324 vrc = mData.mpSessionBaseEnv->queryPutEnvArray(&aEnvironment);
325 else
326 {
327 GuestEnvironment TmpEnv;
328 vrc = TmpEnv.copy(*mData.mpSessionBaseEnv);
329 if (RT_SUCCESS(vrc))
330 {
331 vrc = TmpEnv.applyChanges(mData.mProcess.mEnvironmentChanges);
332 if (RT_SUCCESS(vrc))
333 vrc = TmpEnv.queryPutEnvArray(&aEnvironment);
334 }
335 }
336 hrc = Global::vboxStatusCodeToCOM(vrc);
337 }
338 else
339 hrc = setError(VBOX_E_NOT_SUPPORTED, tr("The base environment feature is not supported by the guest additions"));
340 LogFlowThisFuncLeave();
341 return hrc;
342#endif
343}
344
345HRESULT GuestProcess::getEventSource(ComPtr<IEventSource> &aEventSource)
346{
347 LogFlowThisFuncEnter();
348
349 // no need to lock - lifetime constant
350 mEventSource.queryInterfaceTo(aEventSource.asOutParam());
351
352 LogFlowThisFuncLeave();
353 return S_OK;
354}
355
356HRESULT GuestProcess::getExecutablePath(com::Utf8Str &aExecutablePath)
357{
358 LogFlowThisFuncEnter();
359
360 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
361
362 aExecutablePath = mData.mProcess.mExecutable;
363
364 return S_OK;
365}
366
367HRESULT GuestProcess::getExitCode(LONG *aExitCode)
368{
369 LogFlowThisFuncEnter();
370
371 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
372
373 *aExitCode = mData.mExitCode;
374
375 return S_OK;
376}
377
378HRESULT GuestProcess::getName(com::Utf8Str &aName)
379{
380 LogFlowThisFuncEnter();
381
382 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
383
384 aName = mData.mProcess.mName;
385
386 return S_OK;
387}
388
389HRESULT GuestProcess::getPID(ULONG *aPID)
390{
391 LogFlowThisFuncEnter();
392
393 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
394
395 *aPID = mData.mPID;
396
397 return S_OK;
398}
399
400HRESULT GuestProcess::getStatus(ProcessStatus_T *aStatus)
401{
402 LogFlowThisFuncEnter();
403
404 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
405
406 *aStatus = mData.mStatus;
407
408 return S_OK;
409}
410
411// private methods
412/////////////////////////////////////////////////////////////////////////////
413
414int GuestProcess::i_callbackDispatcher(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCb)
415{
416 AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER);
417 AssertPtrReturn(pSvcCb, VERR_INVALID_POINTER);
418#ifdef DEBUG
419 LogFlowThisFunc(("uPID=%RU32, uContextID=%RU32, uFunction=%RU32, pSvcCb=%p\n",
420 mData.mPID, pCbCtx->uContextID, pCbCtx->uFunction, pSvcCb));
421#endif
422
423 int vrc;
424 switch (pCbCtx->uFunction)
425 {
426 case GUEST_DISCONNECTED:
427 {
428 vrc = i_onGuestDisconnected(pCbCtx, pSvcCb);
429 break;
430 }
431
432 case GUEST_EXEC_STATUS:
433 {
434 vrc = i_onProcessStatusChange(pCbCtx, pSvcCb);
435 break;
436 }
437
438 case GUEST_EXEC_OUTPUT:
439 {
440 vrc = i_onProcessOutput(pCbCtx, pSvcCb);
441 break;
442 }
443
444 case GUEST_EXEC_INPUT_STATUS:
445 {
446 vrc = i_onProcessInputStatus(pCbCtx, pSvcCb);
447 break;
448 }
449
450 default:
451 /* Silently ignore not implemented functions. */
452 vrc = VERR_NOT_SUPPORTED;
453 break;
454 }
455
456#ifdef DEBUG
457 LogFlowFuncLeaveRC(vrc);
458#endif
459 return vrc;
460}
461
462/**
463 * Checks if the current assigned PID matches another PID (from a callback).
464 *
465 * In protocol v1 we don't have the possibility to terminate/kill
466 * processes so it can happen that a formerly started process A
467 * (which has the context ID 0 (session=0, process=0, count=0) will
468 * send a delayed message to the host if this process has already
469 * been discarded there and the same context ID was reused by
470 * a process B. Process B in turn then has a different guest PID.
471 *
472 * Note: This also can happen when restoring from a saved state which
473 * had a guest process running.
474 *
475 * @return IPRT status code.
476 * @param uPID PID to check.
477 */
478inline int GuestProcess::i_checkPID(uint32_t uPID)
479{
480 int rc = VINF_SUCCESS;
481
482 /* Was there a PID assigned yet? */
483 if (mData.mPID)
484 {
485 if (RT_UNLIKELY(mData.mPID != uPID))
486 {
487 LogFlowFunc(("Stale guest process (PID=%RU32) sent data to a newly started process (pProcesS=%p, PID=%RU32, status=%RU32)\n",
488 uPID, this, mData.mPID, mData.mStatus));
489 rc = VERR_NOT_FOUND;
490 }
491 }
492
493 return rc;
494}
495
496/* static */
497Utf8Str GuestProcess::i_guestErrorToString(int guestRc)
498{
499 Utf8Str strError;
500
501 /** @todo pData->u32Flags: int vs. uint32 -- IPRT errors are *negative* !!! */
502 switch (guestRc)
503 {
504 case VERR_FILE_NOT_FOUND: /* This is the most likely error. */
505 strError += Utf8StrFmt(tr("The specified file was not found on guest"));
506 break;
507
508 case VERR_INVALID_VM_HANDLE:
509 strError += Utf8StrFmt(tr("VMM device is not available (is the VM running?)"));
510 break;
511
512 case VERR_HGCM_SERVICE_NOT_FOUND:
513 strError += Utf8StrFmt(tr("The guest execution service is not available"));
514 break;
515
516 case VERR_PATH_NOT_FOUND:
517 strError += Utf8StrFmt(tr("Could not resolve path to specified file was not found on guest"));
518 break;
519
520 case VERR_BAD_EXE_FORMAT:
521 strError += Utf8StrFmt(tr("The specified file is not an executable format on guest"));
522 break;
523
524 case VERR_AUTHENTICATION_FAILURE:
525 strError += Utf8StrFmt(tr("The specified user was not able to logon on guest"));
526 break;
527
528 case VERR_INVALID_NAME:
529 strError += Utf8StrFmt(tr("The specified file is an invalid name"));
530 break;
531
532 case VERR_TIMEOUT:
533 strError += Utf8StrFmt(tr("The guest did not respond within time"));
534 break;
535
536 case VERR_CANCELLED:
537 strError += Utf8StrFmt(tr("The execution operation was canceled"));
538 break;
539
540 case VERR_PERMISSION_DENIED: /** @todo r=bird: This is probably completely and utterly misleading. VERR_AUTHENTICATION_FAILURE could have this message. */
541 strError += Utf8StrFmt(tr("Invalid user/password credentials"));
542 break;
543
544 case VERR_MAX_PROCS_REACHED:
545 strError += Utf8StrFmt(tr("Maximum number of concurrent guest processes has been reached"));
546 break;
547
548 case VERR_NOT_FOUND:
549 strError += Utf8StrFmt(tr("The guest execution service is not ready (yet)"));
550 break;
551
552 default:
553 strError += Utf8StrFmt("%Rrc", guestRc);
554 break;
555 }
556
557 return strError;
558}
559
560/**
561 * Returns @c true if the passed in error code indicates an error which came
562 * from the guest side, or @c false if not.
563 *
564 * @return bool @c true if the passed in error code indicates an error which came
565 * from the guest side, or @c false if not.
566 * @param rc Error code to check.
567 */
568/* static */
569bool GuestProcess::i_isGuestError(int rc)
570{
571 return ( rc == VERR_GSTCTL_GUEST_ERROR
572 || rc == VWRN_GSTCTL_PROCESS_EXIT_CODE);
573}
574
575inline bool GuestProcess::i_isAlive(void)
576{
577 return ( mData.mStatus == ProcessStatus_Started
578 || mData.mStatus == ProcessStatus_Paused
579 || mData.mStatus == ProcessStatus_Terminating);
580}
581
582inline bool GuestProcess::i_hasEnded(void)
583{
584 return ( mData.mStatus == ProcessStatus_TerminatedNormally
585 || mData.mStatus == ProcessStatus_TerminatedSignal
586 || mData.mStatus == ProcessStatus_TerminatedAbnormally
587 || mData.mStatus == ProcessStatus_TimedOutKilled
588 || mData.mStatus == ProcessStatus_TimedOutAbnormally
589 || mData.mStatus == ProcessStatus_Down
590 || mData.mStatus == ProcessStatus_Error);
591}
592
593int GuestProcess::i_onGuestDisconnected(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData)
594{
595 AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER);
596 AssertPtrReturn(pSvcCbData, VERR_INVALID_POINTER);
597
598 int vrc = i_setProcessStatus(ProcessStatus_Down, VINF_SUCCESS);
599
600 LogFlowFuncLeaveRC(vrc);
601 return vrc;
602}
603
604int GuestProcess::i_onProcessInputStatus(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData)
605{
606 AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER);
607 AssertPtrReturn(pSvcCbData, VERR_INVALID_POINTER);
608 /* pCallback is optional. */
609
610 if (pSvcCbData->mParms < 5)
611 return VERR_INVALID_PARAMETER;
612
613 CALLBACKDATA_PROC_INPUT dataCb;
614 /* pSvcCb->mpaParms[0] always contains the context ID. */
615 int vrc = pSvcCbData->mpaParms[1].getUInt32(&dataCb.uPID);
616 AssertRCReturn(vrc, vrc);
617 vrc = pSvcCbData->mpaParms[2].getUInt32(&dataCb.uStatus);
618 AssertRCReturn(vrc, vrc);
619 vrc = pSvcCbData->mpaParms[3].getUInt32(&dataCb.uFlags);
620 AssertRCReturn(vrc, vrc);
621 vrc = pSvcCbData->mpaParms[4].getUInt32(&dataCb.uProcessed);
622 AssertRCReturn(vrc, vrc);
623
624 LogFlowThisFunc(("uPID=%RU32, uStatus=%RU32, uFlags=%RI32, cbProcessed=%RU32\n",
625 dataCb.uPID, dataCb.uStatus, dataCb.uFlags, dataCb.uProcessed));
626
627 vrc = i_checkPID(dataCb.uPID);
628 if (RT_SUCCESS(vrc))
629 {
630 ProcessInputStatus_T inputStatus = ProcessInputStatus_Undefined;
631 switch (dataCb.uStatus)
632 {
633 case INPUT_STS_WRITTEN:
634 inputStatus = ProcessInputStatus_Written;
635 break;
636 case INPUT_STS_ERROR:
637 inputStatus = ProcessInputStatus_Broken;
638 break;
639 case INPUT_STS_TERMINATED:
640 inputStatus = ProcessInputStatus_Broken;
641 break;
642 case INPUT_STS_OVERFLOW:
643 inputStatus = ProcessInputStatus_Overflow;
644 break;
645 case INPUT_STS_UNDEFINED:
646 /* Fall through is intentional. */
647 default:
648 AssertMsg(!dataCb.uProcessed, ("Processed data is not 0 in undefined input state\n"));
649 break;
650 }
651
652 if (inputStatus != ProcessInputStatus_Undefined)
653 {
654 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
655
656 /* Copy over necessary data before releasing lock again. */
657 uint32_t uPID = mData.mPID;
658 /** @todo Also handle mSession? */
659
660 alock.release(); /* Release lock before firing off event. */
661
662 fireGuestProcessInputNotifyEvent(mEventSource, mSession, this,
663 uPID, 0 /* StdIn */, dataCb.uProcessed, inputStatus);
664 }
665 }
666
667 LogFlowFuncLeaveRC(vrc);
668 return vrc;
669}
670
671int GuestProcess::i_onProcessNotifyIO(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData)
672{
673 AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER);
674 AssertPtrReturn(pSvcCbData, VERR_INVALID_POINTER);
675
676 return VERR_NOT_IMPLEMENTED;
677}
678
679int GuestProcess::i_onProcessStatusChange(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData)
680{
681 AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER);
682 AssertPtrReturn(pSvcCbData, VERR_INVALID_POINTER);
683
684 if (pSvcCbData->mParms < 5)
685 return VERR_INVALID_PARAMETER;
686
687 CALLBACKDATA_PROC_STATUS dataCb;
688 /* pSvcCb->mpaParms[0] always contains the context ID. */
689 int vrc = pSvcCbData->mpaParms[1].getUInt32(&dataCb.uPID);
690 AssertRCReturn(vrc, vrc);
691 vrc = pSvcCbData->mpaParms[2].getUInt32(&dataCb.uStatus);
692 AssertRCReturn(vrc, vrc);
693 vrc = pSvcCbData->mpaParms[3].getUInt32(&dataCb.uFlags);
694 AssertRCReturn(vrc, vrc);
695 vrc = pSvcCbData->mpaParms[4].getPointer(&dataCb.pvData, &dataCb.cbData);
696 AssertRCReturn(vrc, vrc);
697
698 LogFlowThisFunc(("uPID=%RU32, uStatus=%RU32, uFlags=%RU32\n",
699 dataCb.uPID, dataCb.uStatus, dataCb.uFlags));
700
701 vrc = i_checkPID(dataCb.uPID);
702 if (RT_SUCCESS(vrc))
703 {
704 ProcessStatus_T procStatus = ProcessStatus_Undefined;
705 int procRc = VINF_SUCCESS;
706
707 switch (dataCb.uStatus)
708 {
709 case PROC_STS_STARTED:
710 {
711 procStatus = ProcessStatus_Started;
712
713 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
714 mData.mPID = dataCb.uPID; /* Set the process PID. */
715 break;
716 }
717
718 case PROC_STS_TEN:
719 {
720 procStatus = ProcessStatus_TerminatedNormally;
721
722 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
723 mData.mExitCode = dataCb.uFlags; /* Contains the exit code. */
724 break;
725 }
726
727 case PROC_STS_TES:
728 {
729 procStatus = ProcessStatus_TerminatedSignal;
730
731 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
732 mData.mExitCode = dataCb.uFlags; /* Contains the signal. */
733 break;
734 }
735
736 case PROC_STS_TEA:
737 {
738 procStatus = ProcessStatus_TerminatedAbnormally;
739 break;
740 }
741
742 case PROC_STS_TOK:
743 {
744 procStatus = ProcessStatus_TimedOutKilled;
745 break;
746 }
747
748 case PROC_STS_TOA:
749 {
750 procStatus = ProcessStatus_TimedOutAbnormally;
751 break;
752 }
753
754 case PROC_STS_DWN:
755 {
756 procStatus = ProcessStatus_Down;
757 break;
758 }
759
760 case PROC_STS_ERROR:
761 {
762 procRc = dataCb.uFlags; /* mFlags contains the IPRT error sent from the guest. */
763 procStatus = ProcessStatus_Error;
764 break;
765 }
766
767 case PROC_STS_UNDEFINED:
768 default:
769 {
770 /* Silently skip this request. */
771 procStatus = ProcessStatus_Undefined;
772 break;
773 }
774 }
775
776 LogFlowThisFunc(("Got rc=%Rrc, procSts=%RU32, procRc=%Rrc\n",
777 vrc, procStatus, procRc));
778
779 /* Set the process status. */
780 int rc2 = i_setProcessStatus(procStatus, procRc);
781 if (RT_SUCCESS(vrc))
782 vrc = rc2;
783 }
784
785 LogFlowFuncLeaveRC(vrc);
786 return vrc;
787}
788
789int GuestProcess::i_onProcessOutput(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData)
790{
791 RT_NOREF(pCbCtx);
792 AssertPtrReturn(pSvcCbData, VERR_INVALID_POINTER);
793
794 if (pSvcCbData->mParms < 5)
795 return VERR_INVALID_PARAMETER;
796
797 CALLBACKDATA_PROC_OUTPUT dataCb;
798 /* pSvcCb->mpaParms[0] always contains the context ID. */
799 int vrc = pSvcCbData->mpaParms[1].getUInt32(&dataCb.uPID);
800 AssertRCReturn(vrc, vrc);
801 vrc = pSvcCbData->mpaParms[2].getUInt32(&dataCb.uHandle);
802 AssertRCReturn(vrc, vrc);
803 vrc = pSvcCbData->mpaParms[3].getUInt32(&dataCb.uFlags);
804 AssertRCReturn(vrc, vrc);
805 vrc = pSvcCbData->mpaParms[4].getPointer(&dataCb.pvData, &dataCb.cbData);
806 AssertRCReturn(vrc, vrc);
807
808 LogFlowThisFunc(("uPID=%RU32, uHandle=%RU32, uFlags=%RI32, pvData=%p, cbData=%RU32\n",
809 dataCb.uPID, dataCb.uHandle, dataCb.uFlags, dataCb.pvData, dataCb.cbData));
810
811 vrc = i_checkPID(dataCb.uPID);
812 if (RT_SUCCESS(vrc))
813 {
814 com::SafeArray<BYTE> data((size_t)dataCb.cbData);
815 if (dataCb.cbData)
816 data.initFrom((BYTE*)dataCb.pvData, dataCb.cbData);
817
818 fireGuestProcessOutputEvent(mEventSource, mSession, this,
819 mData.mPID, dataCb.uHandle, dataCb.cbData, ComSafeArrayAsInParam(data));
820 }
821
822 LogFlowFuncLeaveRC(vrc);
823 return vrc;
824}
825
826/**
827 * Called by IGuestSession right before this process gets
828 * removed from the public process list.
829 */
830int GuestProcess::i_onRemove(void)
831{
832 LogFlowThisFuncEnter();
833
834 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
835
836 int vrc = VINF_SUCCESS;
837
838 /*
839 * Note: The event source stuff holds references to this object,
840 * so make sure that this is cleaned up *before* calling uninit().
841 */
842 if (!mEventSource.isNull())
843 {
844 mEventSource->UnregisterListener(mLocalListener);
845
846 mLocalListener.setNull();
847 unconst(mEventSource).setNull();
848 }
849
850 LogFlowFuncLeaveRC(vrc);
851 return vrc;
852}
853
854int GuestProcess::i_readData(uint32_t uHandle, uint32_t uSize, uint32_t uTimeoutMS,
855 void *pvData, size_t cbData, uint32_t *pcbRead, int *pGuestRc)
856{
857 LogFlowThisFunc(("uPID=%RU32, uHandle=%RU32, uSize=%RU32, uTimeoutMS=%RU32, pvData=%p, cbData=%RU32, pGuestRc=%p\n",
858 mData.mPID, uHandle, uSize, uTimeoutMS, pvData, cbData, pGuestRc));
859 AssertReturn(uSize, VERR_INVALID_PARAMETER);
860 AssertPtrReturn(pvData, VERR_INVALID_POINTER);
861 AssertReturn(cbData >= uSize, VERR_INVALID_PARAMETER);
862 /* pcbRead is optional. */
863
864 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
865
866 if ( mData.mStatus != ProcessStatus_Started
867 /* Skip reading if the process wasn't started with the appropriate
868 * flags. */
869 || ( ( uHandle == OUTPUT_HANDLE_ID_STDOUT
870 || uHandle == OUTPUT_HANDLE_ID_STDOUT_DEPRECATED)
871 && !(mData.mProcess.mFlags & ProcessCreateFlag_WaitForStdOut))
872 || ( uHandle == OUTPUT_HANDLE_ID_STDERR
873 && !(mData.mProcess.mFlags & ProcessCreateFlag_WaitForStdErr))
874 )
875 {
876 if (pcbRead)
877 *pcbRead = 0;
878 if (pGuestRc)
879 *pGuestRc = VINF_SUCCESS;
880 return VINF_SUCCESS; /* Nothing to read anymore. */
881 }
882
883 int vrc;
884
885 GuestWaitEvent *pEvent = NULL;
886 GuestEventTypes eventTypes;
887 try
888 {
889 /*
890 * On Guest Additions < 4.3 there is no guarantee that the process status
891 * change arrives *after* the output event, e.g. if this was the last output
892 * block being read and the process will report status "terminate".
893 * So just skip checking for process status change and only wait for the
894 * output event.
895 */
896 if (mSession->i_getProtocolVersion() >= 2)
897 eventTypes.push_back(VBoxEventType_OnGuestProcessStateChanged);
898 eventTypes.push_back(VBoxEventType_OnGuestProcessOutput);
899
900 vrc = registerWaitEvent(eventTypes, &pEvent);
901 }
902 catch (std::bad_alloc)
903 {
904 vrc = VERR_NO_MEMORY;
905 }
906
907 if (RT_FAILURE(vrc))
908 return vrc;
909
910 if (RT_SUCCESS(vrc))
911 {
912 VBOXHGCMSVCPARM paParms[8];
913 int i = 0;
914 paParms[i++].setUInt32(pEvent->ContextID());
915 paParms[i++].setUInt32(mData.mPID);
916 paParms[i++].setUInt32(uHandle);
917 paParms[i++].setUInt32(0 /* Flags, none set yet. */);
918
919 alock.release(); /* Drop the write lock before sending. */
920
921 vrc = sendCommand(HOST_EXEC_GET_OUTPUT, i, paParms);
922 }
923
924 if (RT_SUCCESS(vrc))
925 vrc = i_waitForOutput(pEvent, uHandle, uTimeoutMS,
926 pvData, cbData, pcbRead);
927
928 unregisterWaitEvent(pEvent);
929
930 LogFlowFuncLeaveRC(vrc);
931 return vrc;
932}
933
934/* Does not do locking; caller is responsible for that! */
935int GuestProcess::i_setProcessStatus(ProcessStatus_T procStatus, int procRc)
936{
937 LogFlowThisFuncEnter();
938
939 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
940
941 LogFlowThisFunc(("oldStatus=%RU32, newStatus=%RU32, procRc=%Rrc\n",
942 mData.mStatus, procStatus, procRc));
943
944 if (procStatus == ProcessStatus_Error)
945 {
946 AssertMsg(RT_FAILURE(procRc), ("Guest rc must be an error (%Rrc)\n", procRc));
947 /* Do not allow overwriting an already set error. If this happens
948 * this means we forgot some error checking/locking somewhere. */
949 AssertMsg(RT_SUCCESS(mData.mLastError), ("Guest rc already set (to %Rrc)\n", mData.mLastError));
950 }
951 else
952 AssertMsg(RT_SUCCESS(procRc), ("Guest rc must not be an error (%Rrc)\n", procRc));
953
954 int rc = VINF_SUCCESS;
955
956 if (mData.mStatus != procStatus) /* Was there a process status change? */
957 {
958 mData.mStatus = procStatus;
959 mData.mLastError = procRc;
960
961 ComObjPtr<VirtualBoxErrorInfo> errorInfo;
962 HRESULT hr = errorInfo.createObject();
963 ComAssertComRC(hr);
964 if (RT_FAILURE(mData.mLastError))
965 {
966 hr = errorInfo->initEx(VBOX_E_IPRT_ERROR, mData.mLastError,
967 COM_IIDOF(IGuestProcess), getComponentName(),
968 i_guestErrorToString(mData.mLastError));
969 ComAssertComRC(hr);
970 }
971
972 /* Copy over necessary data before releasing lock again. */
973 uint32_t uPID = mData.mPID;
974 /** @todo Also handle mSession? */
975
976 alock.release(); /* Release lock before firing off event. */
977
978 fireGuestProcessStateChangedEvent(mEventSource, mSession, this,
979 uPID, procStatus, errorInfo);
980#if 0
981 /*
982 * On Guest Additions < 4.3 there is no guarantee that outstanding
983 * requests will be delivered to the host after the process has ended,
984 * so just cancel all waiting events here to not let clients run
985 * into timeouts.
986 */
987 if ( mSession->getProtocolVersion() < 2
988 && hasEnded())
989 {
990 LogFlowThisFunc(("Process ended, canceling outstanding wait events ...\n"));
991 rc = cancelWaitEvents();
992 }
993#endif
994 }
995
996 return rc;
997}
998
999/* static */
1000HRESULT GuestProcess::i_setErrorExternal(VirtualBoxBase *pInterface, int guestRc)
1001{
1002 AssertPtr(pInterface);
1003 AssertMsg(RT_FAILURE(guestRc), ("Guest rc does not indicate a failure when setting error\n"));
1004
1005 return pInterface->setError(VBOX_E_IPRT_ERROR, GuestProcess::i_guestErrorToString(guestRc).c_str());
1006}
1007
1008int GuestProcess::i_startProcess(uint32_t cMsTimeout, int *pGuestRc)
1009{
1010 LogFlowThisFunc(("cMsTimeout=%RU32, procExe=%s, procTimeoutMS=%RU32, procFlags=%x, sessionID=%RU32\n",
1011 cMsTimeout, mData.mProcess.mExecutable.c_str(), mData.mProcess.mTimeoutMS, mData.mProcess.mFlags,
1012 mSession->i_getId()));
1013
1014 /* Wait until the caller function (if kicked off by a thread)
1015 * has returned and continue operation. */
1016 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1017
1018 mData.mStatus = ProcessStatus_Starting;
1019
1020 int vrc;
1021
1022 GuestWaitEvent *pEvent = NULL;
1023 GuestEventTypes eventTypes;
1024 try
1025 {
1026 eventTypes.push_back(VBoxEventType_OnGuestProcessStateChanged);
1027 vrc = registerWaitEvent(eventTypes, &pEvent);
1028 }
1029 catch (std::bad_alloc)
1030 {
1031 vrc = VERR_NO_MEMORY;
1032 }
1033 if (RT_FAILURE(vrc))
1034 return vrc;
1035
1036 vrc = i_startProcessInner(cMsTimeout, alock, pEvent, pGuestRc);
1037
1038 unregisterWaitEvent(pEvent);
1039
1040 LogFlowFuncLeaveRC(vrc);
1041 return vrc;
1042}
1043
1044int GuestProcess::i_startProcessInner(uint32_t cMsTimeout, AutoWriteLock &rLock, GuestWaitEvent *pEvent, int *pGuestRc)
1045{
1046 GuestSession *pSession = mSession;
1047 AssertPtr(pSession);
1048 uint32_t const uProtocol = pSession->i_getProtocolVersion();
1049
1050 const GuestCredentials &sessionCreds = pSession->i_getCredentials();
1051
1052
1053 /* Prepare arguments. */
1054 size_t cArgs = mData.mProcess.mArguments.size();
1055 if (cArgs >= 128*1024)
1056 return VERR_BUFFER_OVERFLOW;
1057
1058 char *pszArgs = NULL;
1059 int vrc = VINF_SUCCESS;
1060 if (cArgs)
1061 {
1062 char const **papszArgv = (char const **)RTMemAlloc((cArgs + 1) * sizeof(papszArgv[0]));
1063 AssertReturn(papszArgv, VERR_NO_MEMORY);
1064
1065 for (size_t i = 0; i < cArgs; i++)
1066 {
1067 papszArgv[i] = mData.mProcess.mArguments[i].c_str();
1068 AssertPtr(papszArgv[i]);
1069 }
1070 papszArgv[cArgs] = NULL;
1071
1072 if (uProtocol < UINT32_C(0xdeadbeef) ) /** @todo implement a way of sending argv[0], best idea is a new command. */
1073 vrc = RTGetOptArgvToString(&pszArgs, papszArgv + 1, RTGETOPTARGV_CNV_QUOTE_BOURNE_SH);
1074 else
1075 vrc = RTGetOptArgvToString(&pszArgs, papszArgv, RTGETOPTARGV_CNV_QUOTE_BOURNE_SH);
1076
1077 RTMemFree(papszArgv);
1078 if (RT_FAILURE(vrc))
1079 return vrc;
1080
1081 /* Note! No returns after this. */
1082 }
1083
1084 /* Calculate arguments size (in bytes). */
1085 size_t cbArgs = pszArgs ? strlen(pszArgs) + 1 : 0; /* Include terminating zero. */
1086
1087 /* Prepare environment. The guest service dislikes the empty string at the end, so drop it. */
1088 size_t cbEnvBlock;
1089 char *pszzEnvBlock;
1090 vrc = mData.mProcess.mEnvironmentChanges.queryUtf8Block(&pszzEnvBlock, &cbEnvBlock);
1091 if (RT_SUCCESS(vrc))
1092 {
1093 Assert(cbEnvBlock > 0);
1094 cbEnvBlock--;
1095
1096 /* Prepare HGCM call. */
1097 VBOXHGCMSVCPARM paParms[16];
1098 int i = 0;
1099 paParms[i++].setUInt32(pEvent->ContextID());
1100 paParms[i++].setCppString(mData.mProcess.mExecutable);
1101 paParms[i++].setUInt32(mData.mProcess.mFlags);
1102 paParms[i++].setUInt32((uint32_t)mData.mProcess.mArguments.size());
1103 paParms[i++].setPointer(pszArgs, (uint32_t)cbArgs);
1104 paParms[i++].setUInt32(mData.mProcess.mEnvironmentChanges.count());
1105 paParms[i++].setUInt32((uint32_t)cbEnvBlock);
1106 paParms[i++].setPointer(pszzEnvBlock, (uint32_t)cbEnvBlock);
1107 if (uProtocol < 2)
1108 {
1109 /* In protocol v1 (VBox < 4.3) the credentials were part of the execution
1110 * call. In newer protocols these credentials are part of the opened guest
1111 * session, so not needed anymore here. */
1112 paParms[i++].setCppString(sessionCreds.mUser);
1113 paParms[i++].setCppString(sessionCreds.mPassword);
1114 }
1115 /*
1116 * If the WaitForProcessStartOnly flag is set, we only want to define and wait for a timeout
1117 * until the process was started - the process itself then gets an infinite timeout for execution.
1118 * This is handy when we want to start a process inside a worker thread within a certain timeout
1119 * but let the started process perform lengthly operations then.
1120 */
1121 if (mData.mProcess.mFlags & ProcessCreateFlag_WaitForProcessStartOnly)
1122 paParms[i++].setUInt32(UINT32_MAX /* Infinite timeout */);
1123 else
1124 paParms[i++].setUInt32(mData.mProcess.mTimeoutMS);
1125 if (uProtocol >= 2)
1126 {
1127 paParms[i++].setUInt32(mData.mProcess.mPriority);
1128 /* CPU affinity: We only support one CPU affinity block at the moment,
1129 * so that makes up to 64 CPUs total. This can be more in the future. */
1130 paParms[i++].setUInt32(1);
1131 /* The actual CPU affinity blocks. */
1132 paParms[i++].setPointer((void *)&mData.mProcess.mAffinity, sizeof(mData.mProcess.mAffinity));
1133 }
1134
1135 rLock.release(); /* Drop the write lock before sending. */
1136
1137 vrc = sendCommand(HOST_EXEC_CMD, i, paParms);
1138 if (RT_FAILURE(vrc))
1139 {
1140 int rc2 = i_setProcessStatus(ProcessStatus_Error, vrc);
1141 AssertRC(rc2);
1142 }
1143
1144 mData.mProcess.mEnvironmentChanges.freeUtf8Block(pszzEnvBlock);
1145 }
1146
1147 RTStrFree(pszArgs);
1148
1149 if (RT_SUCCESS(vrc))
1150 vrc = i_waitForStatusChange(pEvent, cMsTimeout,
1151 NULL /* Process status */, pGuestRc);
1152 return vrc;
1153}
1154
1155int GuestProcess::i_startProcessAsync(void)
1156{
1157 LogFlowThisFuncEnter();
1158
1159 int vrc = VINF_SUCCESS;
1160 HRESULT hr = S_OK;
1161
1162 GuestProcessStartTask* pTask = NULL;
1163 try
1164 {
1165 pTask = new GuestProcessStartTask(this);
1166 if (!pTask->i_isOk())
1167 {
1168 delete pTask;
1169 LogFlow(("GuestProcess: Could not create GuestProcessStartTask object \n"));
1170 throw VERR_MEMOBJ_INIT_FAILED;
1171 }
1172 LogFlow(("GuestProcess: Successfully created GuestProcessStartTask object \n"));
1173 //this function delete pTask in case of exceptions, so there is no need in the call of delete operator
1174 hr = pTask->createThread();
1175 }
1176 catch(std::bad_alloc &)
1177 {
1178 vrc = VERR_NO_MEMORY;
1179 }
1180 catch(int eVRC)
1181 {
1182 vrc = eVRC;
1183 LogFlow(("GuestSession: Could not create thread for GuestProcessStartTask task %Rrc\n", vrc));
1184 }
1185
1186 LogFlowFuncLeaveRC(vrc);
1187 return vrc;
1188}
1189
1190/* static */
1191void GuestProcess::i_startProcessThreadTask(GuestProcessStartTask *pTask)
1192{
1193 LogFlowFunc(("pTask=%p\n", pTask));
1194
1195 const ComObjPtr<GuestProcess> pProcess(pTask->i_process());
1196 Assert(!pProcess.isNull());
1197
1198 AutoCaller autoCaller(pProcess);
1199 if (FAILED(autoCaller.rc()))
1200 return;
1201
1202 int vrc = pProcess->i_startProcess(30 * 1000 /* 30s timeout */,
1203 NULL /* Guest rc, ignored */);
1204/** @todo
1205 *
1206 * r=bird: what's up with vrc here? Safe to ignore it?
1207 *
1208 */
1209
1210 /* Nothing to do here anymore. */
1211
1212 LogFlowFunc(("pProcess=%p vrc=%Rrc (ignored)\n", (GuestProcess *)pProcess, vrc));
1213 NOREF(vrc);
1214}
1215
1216int GuestProcess::i_terminateProcess(uint32_t uTimeoutMS, int *pGuestRc)
1217{
1218 /* pGuestRc is optional. */
1219 LogFlowThisFunc(("uTimeoutMS=%RU32\n", uTimeoutMS));
1220
1221 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1222
1223 int vrc = VINF_SUCCESS;
1224
1225 if (mData.mStatus != ProcessStatus_Started)
1226 {
1227 LogFlowThisFunc(("Process not in started state (state is %RU32), skipping termination\n",
1228 mData.mStatus));
1229 }
1230 else
1231 {
1232 AssertPtr(mSession);
1233 /* Note: VBox < 4.3 (aka protocol version 1) does not
1234 * support this, so just skip. */
1235 if (mSession->i_getProtocolVersion() < 2)
1236 vrc = VERR_NOT_SUPPORTED;
1237
1238 if (RT_SUCCESS(vrc))
1239 {
1240 GuestWaitEvent *pEvent = NULL;
1241 GuestEventTypes eventTypes;
1242 try
1243 {
1244 eventTypes.push_back(VBoxEventType_OnGuestProcessStateChanged);
1245
1246 vrc = registerWaitEvent(eventTypes, &pEvent);
1247 }
1248 catch (std::bad_alloc)
1249 {
1250 vrc = VERR_NO_MEMORY;
1251 }
1252
1253 if (RT_FAILURE(vrc))
1254 return vrc;
1255
1256 VBOXHGCMSVCPARM paParms[4];
1257 int i = 0;
1258 paParms[i++].setUInt32(pEvent->ContextID());
1259 paParms[i++].setUInt32(mData.mPID);
1260
1261 alock.release(); /* Drop the write lock before sending. */
1262
1263 vrc = sendCommand(HOST_EXEC_TERMINATE, i, paParms);
1264 if (RT_SUCCESS(vrc))
1265 vrc = i_waitForStatusChange(pEvent, uTimeoutMS,
1266 NULL /* ProcessStatus */, pGuestRc);
1267 unregisterWaitEvent(pEvent);
1268 }
1269 }
1270
1271 LogFlowFuncLeaveRC(vrc);
1272 return vrc;
1273}
1274
1275/* static */
1276ProcessWaitResult_T GuestProcess::i_waitFlagsToResultEx(uint32_t fWaitFlags,
1277 ProcessStatus_T oldStatus, ProcessStatus_T newStatus,
1278 uint32_t uProcFlags, uint32_t uProtocol)
1279{
1280 ProcessWaitResult_T waitResult = ProcessWaitResult_None;
1281
1282 switch (newStatus)
1283 {
1284 case ProcessStatus_TerminatedNormally:
1285 case ProcessStatus_TerminatedSignal:
1286 case ProcessStatus_TerminatedAbnormally:
1287 case ProcessStatus_Down:
1288 /* Nothing to wait for anymore. */
1289 waitResult = ProcessWaitResult_Terminate;
1290 break;
1291
1292 case ProcessStatus_TimedOutKilled:
1293 case ProcessStatus_TimedOutAbnormally:
1294 /* Dito. */
1295 waitResult = ProcessWaitResult_Timeout;
1296 break;
1297
1298 case ProcessStatus_Started:
1299 switch (oldStatus)
1300 {
1301 case ProcessStatus_Undefined:
1302 case ProcessStatus_Starting:
1303 /* Also wait for process start. */
1304 if (fWaitFlags & ProcessWaitForFlag_Start)
1305 waitResult = ProcessWaitResult_Start;
1306 else
1307 {
1308 /*
1309 * If ProcessCreateFlag_WaitForProcessStartOnly was specified on process creation the
1310 * caller is not interested in getting further process statuses -- so just don't notify
1311 * anything here anymore and return.
1312 */
1313 if (uProcFlags & ProcessCreateFlag_WaitForProcessStartOnly)
1314 waitResult = ProcessWaitResult_Start;
1315 }
1316 break;
1317
1318 case ProcessStatus_Started:
1319 /* Only wait for process start. */
1320 if (fWaitFlags == ProcessWaitForFlag_Start)
1321 waitResult = ProcessWaitResult_Start;
1322 break;
1323
1324 default:
1325 AssertMsgFailed(("Unhandled old status %RU32 before new status 'started'\n",
1326 oldStatus));
1327 waitResult = ProcessWaitResult_Start;
1328 break;
1329 }
1330 break;
1331
1332 case ProcessStatus_Error:
1333 /* Nothing to wait for anymore. */
1334 waitResult = ProcessWaitResult_Error;
1335 break;
1336
1337 case ProcessStatus_Undefined:
1338 case ProcessStatus_Starting:
1339 case ProcessStatus_Terminating:
1340 case ProcessStatus_Paused:
1341 /* No result available yet, leave wait
1342 * flags untouched. */
1343 break;
1344 }
1345
1346 if (newStatus == ProcessStatus_Started)
1347 {
1348 /**
1349 * Filter out waits which are *not* supported using
1350 * older guest control Guest Additions.
1351 *
1352 ** @todo ProcessWaitForFlag_Std* flags are not implemented yet.
1353 */
1354 if (uProtocol < 99) /* See @todo above. */
1355 {
1356 if ( waitResult == ProcessWaitResult_None
1357 /* We don't support waiting for stdin, out + err,
1358 * just skip waiting then. */
1359 && ( (fWaitFlags & ProcessWaitForFlag_StdIn)
1360 || (fWaitFlags & ProcessWaitForFlag_StdOut)
1361 || (fWaitFlags & ProcessWaitForFlag_StdErr)
1362 )
1363 )
1364 {
1365 /* Use _WaitFlagNotSupported because we don't know what to tell the caller. */
1366 waitResult = ProcessWaitResult_WaitFlagNotSupported;
1367 }
1368 }
1369 }
1370
1371#ifdef DEBUG
1372 LogFlowFunc(("oldStatus=%RU32, newStatus=%RU32, fWaitFlags=0x%x, waitResult=%RU32\n",
1373 oldStatus, newStatus, fWaitFlags, waitResult));
1374#endif
1375 return waitResult;
1376}
1377
1378ProcessWaitResult_T GuestProcess::i_waitFlagsToResult(uint32_t fWaitFlags)
1379{
1380 AssertPtr(mSession);
1381 return GuestProcess::i_waitFlagsToResultEx(fWaitFlags,
1382 mData.mStatus /* curStatus */, mData.mStatus /* newStatus */,
1383 mData.mProcess.mFlags, mSession->i_getProtocolVersion());
1384}
1385
1386int GuestProcess::i_waitFor(uint32_t fWaitFlags, ULONG uTimeoutMS,
1387 ProcessWaitResult_T &waitResult, int *pGuestRc)
1388{
1389 AssertReturn(fWaitFlags, VERR_INVALID_PARAMETER);
1390
1391 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
1392
1393 LogFlowThisFunc(("fWaitFlags=0x%x, uTimeoutMS=%RU32, procStatus=%RU32, procRc=%Rrc, pGuestRc=%p\n",
1394 fWaitFlags, uTimeoutMS, mData.mStatus, mData.mLastError, pGuestRc));
1395
1396 /* Did some error occur before? Then skip waiting and return. */
1397 ProcessStatus_T curStatus = mData.mStatus;
1398 if (curStatus == ProcessStatus_Error)
1399 {
1400 waitResult = ProcessWaitResult_Error;
1401 AssertMsg(RT_FAILURE(mData.mLastError),
1402 ("No error rc (%Rrc) set when guest process indicated an error\n", mData.mLastError));
1403 if (pGuestRc)
1404 *pGuestRc = mData.mLastError; /* Return last set error. */
1405 LogFlowThisFunc(("Process is in error state (guestRc=%Rrc)\n", mData.mLastError));
1406 return VERR_GSTCTL_GUEST_ERROR;
1407 }
1408
1409 waitResult = i_waitFlagsToResult(fWaitFlags);
1410
1411 /* No waiting needed? Return immediately using the last set error. */
1412 if (waitResult != ProcessWaitResult_None)
1413 {
1414 if (pGuestRc)
1415 *pGuestRc = mData.mLastError; /* Return last set error (if any). */
1416 LogFlowThisFunc(("Nothing to wait for (guestRc=%Rrc)\n", mData.mLastError));
1417 return RT_SUCCESS(mData.mLastError) ? VINF_SUCCESS : VERR_GSTCTL_GUEST_ERROR;
1418 }
1419
1420 /* Adjust timeout. Passing 0 means RT_INDEFINITE_WAIT. */
1421 if (!uTimeoutMS)
1422 uTimeoutMS = RT_INDEFINITE_WAIT;
1423
1424 int vrc;
1425
1426 GuestWaitEvent *pEvent = NULL;
1427 GuestEventTypes eventTypes;
1428 try
1429 {
1430 eventTypes.push_back(VBoxEventType_OnGuestProcessStateChanged);
1431
1432 vrc = registerWaitEvent(eventTypes, &pEvent);
1433 }
1434 catch (std::bad_alloc)
1435 {
1436 vrc = VERR_NO_MEMORY;
1437 }
1438
1439 if (RT_FAILURE(vrc))
1440 return vrc;
1441
1442 alock.release(); /* Release lock before waiting. */
1443
1444 /*
1445 * Do the actual waiting.
1446 */
1447 ProcessStatus_T newStatus = ProcessStatus_Undefined;
1448 uint64_t u64StartMS = RTTimeMilliTS();
1449 for (;;)
1450 {
1451 uint64_t u64ElapsedMS = RTTimeMilliTS() - u64StartMS;
1452 if ( uTimeoutMS != RT_INDEFINITE_WAIT
1453 && u64ElapsedMS >= uTimeoutMS)
1454 {
1455 vrc = VERR_TIMEOUT;
1456 break;
1457 }
1458
1459 vrc = i_waitForStatusChange(pEvent,
1460 uTimeoutMS == RT_INDEFINITE_WAIT
1461 ? RT_INDEFINITE_WAIT : uTimeoutMS - (uint32_t)u64ElapsedMS,
1462 &newStatus, pGuestRc);
1463 if (RT_SUCCESS(vrc))
1464 {
1465 alock.acquire();
1466
1467 waitResult = i_waitFlagsToResultEx(fWaitFlags, curStatus, newStatus,
1468 mData.mProcess.mFlags, mSession->i_getProtocolVersion());
1469#ifdef DEBUG
1470 LogFlowThisFunc(("Got new status change: fWaitFlags=0x%x, newStatus=%RU32, waitResult=%RU32\n",
1471 fWaitFlags, newStatus, waitResult));
1472#endif
1473 if (ProcessWaitResult_None != waitResult) /* We got a waiting result. */
1474 break;
1475 }
1476 else /* Waiting failed, bail out. */
1477 break;
1478
1479 alock.release(); /* Don't hold lock in next waiting round. */
1480 }
1481
1482 unregisterWaitEvent(pEvent);
1483
1484 LogFlowThisFunc(("Returned waitResult=%RU32, newStatus=%RU32, rc=%Rrc\n",
1485 waitResult, newStatus, vrc));
1486 return vrc;
1487}
1488
1489int GuestProcess::i_waitForInputNotify(GuestWaitEvent *pEvent, uint32_t uHandle, uint32_t uTimeoutMS,
1490 ProcessInputStatus_T *pInputStatus, uint32_t *pcbProcessed)
1491{
1492 RT_NOREF(uHandle);
1493 AssertPtrReturn(pEvent, VERR_INVALID_POINTER);
1494
1495 VBoxEventType_T evtType;
1496 ComPtr<IEvent> pIEvent;
1497 int vrc = waitForEvent(pEvent, uTimeoutMS,
1498 &evtType, pIEvent.asOutParam());
1499 if (RT_SUCCESS(vrc))
1500 {
1501 if (evtType == VBoxEventType_OnGuestProcessInputNotify)
1502 {
1503 ComPtr<IGuestProcessInputNotifyEvent> pProcessEvent = pIEvent;
1504 Assert(!pProcessEvent.isNull());
1505
1506 if (pInputStatus)
1507 {
1508 HRESULT hr2 = pProcessEvent->COMGETTER(Status)(pInputStatus);
1509 ComAssertComRC(hr2);
1510 }
1511 if (pcbProcessed)
1512 {
1513 HRESULT hr2 = pProcessEvent->COMGETTER(Processed)((ULONG*)pcbProcessed);
1514 ComAssertComRC(hr2);
1515 }
1516 }
1517 else
1518 vrc = VWRN_GSTCTL_OBJECTSTATE_CHANGED;
1519 }
1520
1521 LogFlowThisFunc(("Returning pEvent=%p, uHandle=%RU32, rc=%Rrc\n",
1522 pEvent, uHandle, vrc));
1523 return vrc;
1524}
1525
1526int GuestProcess::i_waitForOutput(GuestWaitEvent *pEvent, uint32_t uHandle, uint32_t uTimeoutMS,
1527 void *pvData, size_t cbData, uint32_t *pcbRead)
1528{
1529 AssertPtrReturn(pEvent, VERR_INVALID_POINTER);
1530 /* pvData is optional. */
1531 /* cbData is optional. */
1532 /* pcbRead is optional. */
1533
1534 LogFlowThisFunc(("cEventTypes=%zu, pEvent=%p, uHandle=%RU32, uTimeoutMS=%RU32, pvData=%p, cbData=%zu, pcbRead=%p\n",
1535 pEvent->TypeCount(), pEvent, uHandle, uTimeoutMS, pvData, cbData, pcbRead));
1536
1537 int vrc;
1538
1539 VBoxEventType_T evtType;
1540 ComPtr<IEvent> pIEvent;
1541 do
1542 {
1543 vrc = waitForEvent(pEvent, uTimeoutMS,
1544 &evtType, pIEvent.asOutParam());
1545 if (RT_SUCCESS(vrc))
1546 {
1547 if (evtType == VBoxEventType_OnGuestProcessOutput)
1548 {
1549 ComPtr<IGuestProcessOutputEvent> pProcessEvent = pIEvent;
1550 Assert(!pProcessEvent.isNull());
1551
1552 ULONG uHandleEvent;
1553 HRESULT hr = pProcessEvent->COMGETTER(Handle)(&uHandleEvent);
1554 if ( SUCCEEDED(hr)
1555 && uHandleEvent == uHandle)
1556 {
1557 if (pvData)
1558 {
1559 com::SafeArray <BYTE> data;
1560 hr = pProcessEvent->COMGETTER(Data)(ComSafeArrayAsOutParam(data));
1561 ComAssertComRC(hr);
1562 size_t cbRead = data.size();
1563 if (cbRead)
1564 {
1565 if (cbRead <= cbData)
1566 {
1567 /* Copy data from event into our buffer. */
1568 memcpy(pvData, data.raw(), data.size());
1569 }
1570 else
1571 vrc = VERR_BUFFER_OVERFLOW;
1572
1573 LogFlowThisFunc(("Read %zu bytes (uHandle=%RU32), rc=%Rrc\n",
1574 cbRead, uHandleEvent, vrc));
1575 }
1576 }
1577
1578 if ( RT_SUCCESS(vrc)
1579 && pcbRead)
1580 {
1581 ULONG cbRead;
1582 hr = pProcessEvent->COMGETTER(Processed)(&cbRead);
1583 ComAssertComRC(hr);
1584 *pcbRead = (uint32_t)cbRead;
1585 }
1586
1587 break;
1588 }
1589 else if (FAILED(hr))
1590 vrc = VERR_COM_UNEXPECTED;
1591 }
1592 else
1593 vrc = VWRN_GSTCTL_OBJECTSTATE_CHANGED;
1594 }
1595
1596 } while (vrc == VINF_SUCCESS);
1597
1598 if ( vrc != VINF_SUCCESS
1599 && pcbRead)
1600 {
1601 *pcbRead = 0;
1602 }
1603
1604 LogFlowFuncLeaveRC(vrc);
1605 return vrc;
1606}
1607
1608int GuestProcess::i_waitForStatusChange(GuestWaitEvent *pEvent, uint32_t uTimeoutMS,
1609 ProcessStatus_T *pProcessStatus, int *pGuestRc)
1610{
1611 AssertPtrReturn(pEvent, VERR_INVALID_POINTER);
1612 /* pProcessStatus is optional. */
1613 /* pGuestRc is optional. */
1614
1615 VBoxEventType_T evtType;
1616 ComPtr<IEvent> pIEvent;
1617 int vrc = waitForEvent(pEvent, uTimeoutMS,
1618 &evtType, pIEvent.asOutParam());
1619 if (RT_SUCCESS(vrc))
1620 {
1621 Assert(evtType == VBoxEventType_OnGuestProcessStateChanged);
1622 ComPtr<IGuestProcessStateChangedEvent> pProcessEvent = pIEvent;
1623 Assert(!pProcessEvent.isNull());
1624
1625 ProcessStatus_T procStatus;
1626 HRESULT hr = pProcessEvent->COMGETTER(Status)(&procStatus);
1627 ComAssertComRC(hr);
1628 if (pProcessStatus)
1629 *pProcessStatus = procStatus;
1630
1631 ComPtr<IVirtualBoxErrorInfo> errorInfo;
1632 hr = pProcessEvent->COMGETTER(Error)(errorInfo.asOutParam());
1633 ComAssertComRC(hr);
1634
1635 LONG lGuestRc;
1636 hr = errorInfo->COMGETTER(ResultDetail)(&lGuestRc);
1637 ComAssertComRC(hr);
1638
1639 LogFlowThisFunc(("Got procStatus=%RU32, guestRc=%RI32 (%Rrc)\n",
1640 procStatus, lGuestRc, lGuestRc));
1641
1642 if (RT_FAILURE((int)lGuestRc))
1643 vrc = VERR_GSTCTL_GUEST_ERROR;
1644
1645 if (pGuestRc)
1646 *pGuestRc = (int)lGuestRc;
1647 }
1648
1649 LogFlowFuncLeaveRC(vrc);
1650 return vrc;
1651}
1652
1653/* static */
1654bool GuestProcess::i_waitResultImpliesEx(ProcessWaitResult_T waitResult,
1655 ProcessStatus_T procStatus, uint32_t uProcFlags,
1656 uint32_t uProtocol)
1657{
1658 /** @todo r=bird: If you subscribe to HN, which the 'u' in 'uProcFlags'
1659 * indicates, you should actually be using 'fProc'! */
1660 RT_NOREF(uProtocol, uProcFlags);
1661 bool fImplies;
1662
1663 switch (waitResult)
1664 {
1665 case ProcessWaitResult_Start:
1666 fImplies = procStatus == ProcessStatus_Started;
1667 break;
1668
1669 case ProcessWaitResult_Terminate:
1670 fImplies = ( procStatus == ProcessStatus_TerminatedNormally
1671 || procStatus == ProcessStatus_TerminatedSignal
1672 || procStatus == ProcessStatus_TerminatedAbnormally
1673 || procStatus == ProcessStatus_TimedOutKilled
1674 || procStatus == ProcessStatus_TimedOutAbnormally
1675 || procStatus == ProcessStatus_Down
1676 || procStatus == ProcessStatus_Error);
1677 break;
1678
1679 default:
1680 fImplies = false;
1681 break;
1682 }
1683
1684 return fImplies;
1685}
1686
1687int GuestProcess::i_writeData(uint32_t uHandle, uint32_t uFlags,
1688 void *pvData, size_t cbData, uint32_t uTimeoutMS, uint32_t *puWritten, int *pGuestRc)
1689{
1690 LogFlowThisFunc(("uPID=%RU32, uHandle=%RU32, uFlags=%RU32, pvData=%p, cbData=%RU32, uTimeoutMS=%RU32, puWritten=%p, pGuestRc=%p\n",
1691 mData.mPID, uHandle, uFlags, pvData, cbData, uTimeoutMS, puWritten, pGuestRc));
1692 /* All is optional. There can be 0 byte writes. */
1693 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1694
1695 if (mData.mStatus != ProcessStatus_Started)
1696 {
1697 if (puWritten)
1698 *puWritten = 0;
1699 if (pGuestRc)
1700 *pGuestRc = VINF_SUCCESS;
1701 return VINF_SUCCESS; /* Not available for writing (anymore). */
1702 }
1703
1704 int vrc;
1705
1706 GuestWaitEvent *pEvent = NULL;
1707 GuestEventTypes eventTypes;
1708 try
1709 {
1710 /*
1711 * On Guest Additions < 4.3 there is no guarantee that the process status
1712 * change arrives *after* the input event, e.g. if this was the last input
1713 * block being written and the process will report status "terminate".
1714 * So just skip checking for process status change and only wait for the
1715 * input event.
1716 */
1717 if (mSession->i_getProtocolVersion() >= 2)
1718 eventTypes.push_back(VBoxEventType_OnGuestProcessStateChanged);
1719 eventTypes.push_back(VBoxEventType_OnGuestProcessInputNotify);
1720
1721 vrc = registerWaitEvent(eventTypes, &pEvent);
1722 }
1723 catch (std::bad_alloc)
1724 {
1725 vrc = VERR_NO_MEMORY;
1726 }
1727
1728 if (RT_FAILURE(vrc))
1729 return vrc;
1730
1731 VBOXHGCMSVCPARM paParms[5];
1732 int i = 0;
1733 paParms[i++].setUInt32(pEvent->ContextID());
1734 paParms[i++].setUInt32(mData.mPID);
1735 paParms[i++].setUInt32(uFlags);
1736 paParms[i++].setPointer(pvData, (uint32_t)cbData);
1737 paParms[i++].setUInt32((uint32_t)cbData);
1738
1739 alock.release(); /* Drop the write lock before sending. */
1740
1741 uint32_t cbProcessed = 0;
1742 vrc = sendCommand(HOST_EXEC_SET_INPUT, i, paParms);
1743 if (RT_SUCCESS(vrc))
1744 {
1745 ProcessInputStatus_T inputStatus;
1746 vrc = i_waitForInputNotify(pEvent, uHandle, uTimeoutMS,
1747 &inputStatus, &cbProcessed);
1748 if (RT_SUCCESS(vrc))
1749 {
1750 /** @todo Set guestRc. */
1751
1752 if (puWritten)
1753 *puWritten = cbProcessed;
1754 }
1755 /** @todo Error handling. */
1756 }
1757
1758 unregisterWaitEvent(pEvent);
1759
1760 LogFlowThisFunc(("Returning cbProcessed=%RU32, rc=%Rrc\n",
1761 cbProcessed, vrc));
1762 return vrc;
1763}
1764
1765// implementation of public methods
1766/////////////////////////////////////////////////////////////////////////////
1767
1768HRESULT GuestProcess::read(ULONG aHandle, ULONG aToRead, ULONG aTimeoutMS, std::vector<BYTE> &aData)
1769{
1770 LogFlowThisFuncEnter();
1771
1772 if (aToRead == 0)
1773 return setError(E_INVALIDARG, tr("The size to read is zero"));
1774
1775 aData.resize(aToRead);
1776
1777 HRESULT hr = S_OK;
1778
1779 uint32_t cbRead; int guestRc;
1780 int vrc = i_readData(aHandle, aToRead, aTimeoutMS, &aData.front(), aToRead, &cbRead, &guestRc);
1781 if (RT_SUCCESS(vrc))
1782 {
1783 if (aData.size() != cbRead)
1784 aData.resize(cbRead);
1785 }
1786 else
1787 {
1788 aData.resize(0);
1789
1790 switch (vrc)
1791 {
1792 case VERR_GSTCTL_GUEST_ERROR:
1793 hr = GuestProcess::i_setErrorExternal(this, guestRc);
1794 break;
1795
1796 default:
1797 hr = setError(VBOX_E_IPRT_ERROR,
1798 tr("Reading from process \"%s\" (PID %RU32) failed: %Rrc"),
1799 mData.mProcess.mExecutable.c_str(), mData.mPID, vrc);
1800 break;
1801 }
1802 }
1803
1804 LogFlowThisFunc(("rc=%Rrc, cbRead=%RU32\n", vrc, cbRead));
1805
1806 LogFlowFuncLeaveRC(vrc);
1807 return hr;
1808}
1809
1810HRESULT GuestProcess::terminate()
1811{
1812 HRESULT hr = S_OK;
1813
1814 int guestRc;
1815 int vrc = i_terminateProcess(30 * 1000 /* Timeout in ms */,
1816 &guestRc);
1817 if (RT_FAILURE(vrc))
1818 {
1819 switch (vrc)
1820 {
1821 case VERR_GSTCTL_GUEST_ERROR:
1822 hr = GuestProcess::i_setErrorExternal(this, guestRc);
1823 break;
1824
1825 case VERR_NOT_SUPPORTED:
1826 hr = setError(VBOX_E_IPRT_ERROR,
1827 tr("Terminating process \"%s\" (PID %RU32) not supported by installed Guest Additions"),
1828 mData.mProcess.mExecutable.c_str(), mData.mPID);
1829 break;
1830
1831 default:
1832 hr = setError(VBOX_E_IPRT_ERROR,
1833 tr("Terminating process \"%s\" (PID %RU32) failed: %Rrc"),
1834 mData.mProcess.mExecutable.c_str(), mData.mPID, vrc);
1835 break;
1836 }
1837 }
1838
1839 /* Remove process from guest session list. Now only API clients
1840 * still can hold references to it. */
1841 AssertPtr(mSession);
1842 int rc2 = mSession->i_processRemoveFromList(this);
1843 if (RT_SUCCESS(vrc))
1844 vrc = rc2;
1845
1846 LogFlowFuncLeaveRC(vrc);
1847 return hr;
1848}
1849
1850HRESULT GuestProcess::waitFor(ULONG aWaitFor,
1851 ULONG aTimeoutMS,
1852 ProcessWaitResult_T *aReason)
1853{
1854 /*
1855 * Note: Do not hold any locks here while waiting!
1856 */
1857 HRESULT hr = S_OK;
1858
1859 int guestRc;
1860 ProcessWaitResult_T waitResult;
1861 int vrc = i_waitFor(aWaitFor, aTimeoutMS, waitResult, &guestRc);
1862 if (RT_SUCCESS(vrc))
1863 {
1864 *aReason = waitResult;
1865 }
1866 else
1867 {
1868 switch (vrc)
1869 {
1870 case VERR_GSTCTL_GUEST_ERROR:
1871 hr = GuestProcess::i_setErrorExternal(this, guestRc);
1872 break;
1873
1874 case VERR_TIMEOUT:
1875 *aReason = ProcessWaitResult_Timeout;
1876 break;
1877
1878 default:
1879 hr = setError(VBOX_E_IPRT_ERROR,
1880 tr("Waiting for process \"%s\" (PID %RU32) failed: %Rrc"),
1881 mData.mProcess.mExecutable.c_str(), mData.mPID, vrc);
1882 break;
1883 }
1884 }
1885
1886 LogFlowFuncLeaveRC(vrc);
1887 return hr;
1888}
1889
1890HRESULT GuestProcess::waitForArray(const std::vector<ProcessWaitForFlag_T> &aWaitFor,
1891 ULONG aTimeoutMS, ProcessWaitResult_T *aReason)
1892{
1893 /*
1894 * Note: Do not hold any locks here while waiting!
1895 */
1896 uint32_t fWaitFor = ProcessWaitForFlag_None;
1897 for (size_t i = 0; i < aWaitFor.size(); i++)
1898 fWaitFor |= aWaitFor[i];
1899
1900 return WaitFor(fWaitFor, aTimeoutMS, aReason);
1901}
1902
1903HRESULT GuestProcess::write(ULONG aHandle, ULONG aFlags, const std::vector<BYTE> &aData,
1904 ULONG aTimeoutMS, ULONG *aWritten)
1905{
1906 LogFlowThisFuncEnter();
1907
1908 HRESULT hr = S_OK;
1909
1910 uint32_t cbWritten; int guestRc;
1911 uint32_t cbData = (uint32_t)aData.size();
1912 void *pvData = cbData > 0? (void *)&aData.front(): NULL;
1913 int vrc = i_writeData(aHandle, aFlags, pvData, cbData, aTimeoutMS, &cbWritten, &guestRc);
1914 if (RT_FAILURE(vrc))
1915 {
1916 switch (vrc)
1917 {
1918 case VERR_GSTCTL_GUEST_ERROR:
1919 hr = GuestProcess::i_setErrorExternal(this, guestRc);
1920 break;
1921
1922 default:
1923 hr = setError(VBOX_E_IPRT_ERROR,
1924 tr("Writing to process \"%s\" (PID %RU32) failed: %Rrc"),
1925 mData.mProcess.mExecutable.c_str(), mData.mPID, vrc);
1926 break;
1927 }
1928 }
1929
1930 LogFlowThisFunc(("rc=%Rrc, aWritten=%RU32\n", vrc, cbWritten));
1931
1932 *aWritten = (ULONG)cbWritten;
1933
1934 LogFlowFuncLeaveRC(vrc);
1935 return hr;
1936}
1937
1938HRESULT GuestProcess::writeArray(ULONG aHandle, const std::vector<ProcessInputFlag_T> &aFlags,
1939 const std::vector<BYTE> &aData, ULONG aTimeoutMS, ULONG *aWritten)
1940{
1941 LogFlowThisFuncEnter();
1942
1943 /*
1944 * Note: Do not hold any locks here while writing!
1945 */
1946 ULONG fWrite = ProcessInputFlag_None;
1947 for (size_t i = 0; i < aFlags.size(); i++)
1948 fWrite |= aFlags[i];
1949
1950 return write(aHandle, fWrite, aData, aTimeoutMS, aWritten);
1951}
1952
1953///////////////////////////////////////////////////////////////////////////////
1954
1955GuestProcessTool::GuestProcessTool(void)
1956 : pSession(NULL),
1957 pProcess(NULL)
1958{
1959}
1960
1961GuestProcessTool::~GuestProcessTool(void)
1962{
1963 i_terminate(30 * 1000, NULL /* pGuestRc */);
1964}
1965
1966int GuestProcessTool::Init(GuestSession *pGuestSession, const GuestProcessStartupInfo &startupInfo,
1967 bool fAsync, int *pGuestRc)
1968{
1969 LogFlowThisFunc(("pGuestSession=%p, exe=%s, fAsync=%RTbool\n",
1970 pGuestSession, startupInfo.mExecutable.c_str(), fAsync));
1971
1972 AssertPtrReturn(pGuestSession, VERR_INVALID_POINTER);
1973 Assert(startupInfo.mArguments[0] == startupInfo.mExecutable);
1974
1975 pSession = pGuestSession;
1976 mStartupInfo = startupInfo;
1977
1978 /* Make sure the process is hidden. */
1979 mStartupInfo.mFlags |= ProcessCreateFlag_Hidden;
1980
1981 int vrc = pSession->i_processCreateExInternal(mStartupInfo, pProcess);
1982 if (RT_SUCCESS(vrc))
1983 {
1984 int vrcGuest = VINF_SUCCESS;
1985 vrc = fAsync
1986 ? pProcess->i_startProcessAsync()
1987 : pProcess->i_startProcess(30 * 1000 /* 30s timeout */, &vrcGuest);
1988
1989 if ( RT_SUCCESS(vrc)
1990 && !fAsync
1991 && RT_FAILURE(vrcGuest)
1992 )
1993 {
1994 if (pGuestRc)
1995 *pGuestRc = vrcGuest;
1996 vrc = VERR_GSTCTL_GUEST_ERROR;
1997 }
1998 }
1999
2000 LogFlowFuncLeaveRC(vrc);
2001 return vrc;
2002}
2003
2004int GuestProcessTool::i_getCurrentBlock(uint32_t uHandle, GuestProcessStreamBlock &strmBlock)
2005{
2006 const GuestProcessStream *pStream = NULL;
2007 if (uHandle == OUTPUT_HANDLE_ID_STDOUT)
2008 pStream = &mStdOut;
2009 else if (uHandle == OUTPUT_HANDLE_ID_STDERR)
2010 pStream = &mStdErr;
2011
2012 if (!pStream)
2013 return VERR_INVALID_PARAMETER;
2014
2015 int vrc;
2016 do
2017 {
2018 /* Try parsing the data to see if the current block is complete. */
2019 vrc = mStdOut.ParseBlock(strmBlock);
2020 if (strmBlock.GetCount())
2021 break;
2022 } while (RT_SUCCESS(vrc));
2023
2024 LogFlowThisFunc(("rc=%Rrc, %RU64 pairs\n",
2025 vrc, strmBlock.GetCount()));
2026 return vrc;
2027}
2028
2029int GuestProcessTool::i_getRc(void) const
2030{
2031 LONG exitCode = -1;
2032 HRESULT hr = pProcess->COMGETTER(ExitCode(&exitCode));
2033 AssertComRC(hr);
2034
2035 return GuestProcessTool::i_exitCodeToRc(mStartupInfo, exitCode);
2036}
2037
2038bool GuestProcessTool::i_isRunning(void)
2039{
2040 AssertReturn(!pProcess.isNull(), false);
2041
2042 ProcessStatus_T procStatus = ProcessStatus_Undefined;
2043 HRESULT hr = pProcess->COMGETTER(Status(&procStatus));
2044 AssertComRC(hr);
2045
2046 if ( procStatus == ProcessStatus_Started
2047 || procStatus == ProcessStatus_Paused
2048 || procStatus == ProcessStatus_Terminating)
2049 {
2050 return true;
2051 }
2052
2053 return false;
2054}
2055
2056/* static */
2057int GuestProcessTool::i_run( GuestSession *pGuestSession,
2058 const GuestProcessStartupInfo &startupInfo,
2059 int *pGuestRc /* = NULL */)
2060{
2061 int guestRc;
2062
2063 GuestProcessToolErrorInfo errorInfo;
2064 int vrc = i_runErrorInfo(pGuestSession, startupInfo, errorInfo);
2065 if (RT_SUCCESS(vrc))
2066 {
2067 if (errorInfo.guestRc == VWRN_GSTCTL_PROCESS_EXIT_CODE)
2068 {
2069 guestRc = GuestProcessTool::i_exitCodeToRc(startupInfo, errorInfo.lExitCode);
2070 }
2071 else
2072 guestRc = errorInfo.guestRc;
2073
2074 if (pGuestRc)
2075 *pGuestRc = guestRc;
2076 }
2077
2078 return vrc;
2079}
2080
2081/**
2082 * Static helper function to start and wait for a certain toolbox tool.
2083 *
2084 * @return IPRT status code.
2085 * @param pGuestSession Guest control session to use for starting the toolbox tool in.
2086 * @param startupInfo Startup information about the toolbox tool.
2087 * @param errorInfo Error information returned for error handling.
2088 */
2089/* static */
2090int GuestProcessTool::i_runErrorInfo( GuestSession *pGuestSession,
2091 const GuestProcessStartupInfo &startupInfo,
2092 GuestProcessToolErrorInfo &errorInfo)
2093{
2094 return i_runExErrorInfo(pGuestSession, startupInfo,
2095 NULL /* paStrmOutObjects */, 0 /* cStrmOutObjects */,
2096 errorInfo);
2097}
2098
2099/**
2100 * Static helper function to start and wait for output of a certain toolbox tool.
2101 *
2102 * @return IPRT status code.
2103 * @param pGuestSession Guest control session to use for starting the toolbox tool in.
2104 * @param startupInfo Startup information about the toolbox tool.
2105 * @param paStrmOutObjects Pointer to stream objects array to use for retrieving the output of the toolbox tool.
2106 * Optional.
2107 * @param cStrmOutObjects Number of stream objects passed in. Optional.
2108 * @param pGuestRc Error code returned from the guest side if VERR_GSTCTL_GUEST_ERROR is returned. Optional.
2109 */
2110/* static */
2111int GuestProcessTool::i_runEx( GuestSession *pGuestSession,
2112 const GuestProcessStartupInfo &startupInfo,
2113 GuestCtrlStreamObjects *paStrmOutObjects,
2114 uint32_t cStrmOutObjects,
2115 int *pGuestRc /* = NULL */)
2116{
2117 int guestRc;
2118
2119 GuestProcessToolErrorInfo errorInfo;
2120 int vrc = GuestProcessTool::i_runExErrorInfo(pGuestSession, startupInfo, paStrmOutObjects, cStrmOutObjects, errorInfo);
2121 if (RT_SUCCESS(vrc))
2122 {
2123 if (errorInfo.guestRc == VWRN_GSTCTL_PROCESS_EXIT_CODE)
2124 {
2125 guestRc = GuestProcessTool::i_exitCodeToRc(startupInfo, errorInfo.lExitCode);
2126 }
2127 else
2128 guestRc = errorInfo.guestRc;
2129
2130 /* Return VERR_GSTCTL_GUEST_ERROR if we retrieved a guest return code. */
2131 if (RT_FAILURE(guestRc))
2132 vrc = VERR_GSTCTL_GUEST_ERROR;
2133
2134 if (pGuestRc)
2135 *pGuestRc = guestRc;
2136 }
2137
2138 return vrc;
2139}
2140
2141/**
2142 * Static helper function to start and wait for output of a certain toolbox tool.
2143 *
2144 * This is the extended version, which addds the possibility of retrieving parsable so-called guest stream
2145 * objects. Those objects are issued on the guest side as part of VBoxService's toolbox tools (think of a BusyBox-like approach)
2146 * on stdout and can be used on the host side to retrieve more information about the actual command issued on the guest side.
2147 *
2148 * @return IPRT status code.
2149 * @param pGuestSession Guest control session to use for starting the toolbox tool in.
2150 * @param startupInfo Startup information about the toolbox tool.
2151 * @param paStrmOutObjects Pointer to stream objects array to use for retrieving the output of the toolbox tool.
2152 * Optional.
2153 * @param cStrmOutObjects Number of stream objects passed in. Optional.
2154 * @param errorInfo Error information returned for error handling.
2155 */
2156/* static */
2157int GuestProcessTool::i_runExErrorInfo( GuestSession *pGuestSession,
2158 const GuestProcessStartupInfo &startupInfo,
2159 GuestCtrlStreamObjects *paStrmOutObjects,
2160 uint32_t cStrmOutObjects,
2161 GuestProcessToolErrorInfo &errorInfo)
2162{
2163 AssertPtrReturn(pGuestSession, VERR_INVALID_POINTER);
2164 /* paStrmOutObjects is optional. */
2165
2166 /** @todo Check if this is a valid toolbox. */
2167
2168 GuestProcessTool procTool;
2169 int vrc = procTool.Init(pGuestSession, startupInfo, false /* Async */, &errorInfo.guestRc);
2170 if (RT_SUCCESS(vrc))
2171 {
2172 while (cStrmOutObjects--)
2173 {
2174 try
2175 {
2176 GuestProcessStreamBlock strmBlk;
2177 vrc = procTool.i_waitEx( paStrmOutObjects
2178 ? GUESTPROCESSTOOL_FLAG_STDOUT_BLOCK
2179 : GUESTPROCESSTOOL_FLAG_NONE, &strmBlk, &errorInfo.guestRc);
2180 if (paStrmOutObjects)
2181 paStrmOutObjects->push_back(strmBlk);
2182 }
2183 catch (std::bad_alloc)
2184 {
2185 vrc = VERR_NO_MEMORY;
2186 }
2187 }
2188 }
2189
2190 if (RT_SUCCESS(vrc))
2191 {
2192 /* Make sure the process runs until completion. */
2193 vrc = procTool.i_wait(GUESTPROCESSTOOL_FLAG_NONE, &errorInfo.guestRc);
2194 if (RT_SUCCESS(vrc))
2195 errorInfo.guestRc = procTool.i_terminatedOk(&errorInfo.lExitCode);
2196 }
2197
2198 LogFlowFunc(("Returned rc=%Rrc, guestRc=%Rrc, exitCode=%ld\n", vrc, errorInfo.guestRc, errorInfo.lExitCode));
2199 return vrc;
2200}
2201
2202/**
2203 * Reports if the tool has been run correctly.
2204 *
2205 * @return Will return VWRN_GSTCTL_PROCESS_EXIT_CODE if the tool process returned an exit code <> 0,
2206 * VERR_GSTCTL_PROCESS_WRONG_STATE if the tool process is in a wrong state (e.g. still running),
2207 * or VINF_SUCCESS otherwise.
2208 *
2209 * @param plExitCode Exit code of the tool. Optional.
2210 */
2211int GuestProcessTool::i_terminatedOk(LONG *plExitCode /* = NULL */)
2212{
2213 Assert(!pProcess.isNull());
2214 /* pExitCode is optional. */
2215
2216 int vrc;
2217 if (!i_isRunning())
2218 {
2219 LONG lExitCode = -1;
2220 HRESULT hr = pProcess->COMGETTER(ExitCode(&lExitCode));
2221 AssertComRC(hr);
2222
2223 if (plExitCode)
2224 *plExitCode = lExitCode;
2225
2226 vrc = (lExitCode != 0)
2227 ? VWRN_GSTCTL_PROCESS_EXIT_CODE : VINF_SUCCESS;
2228 }
2229 else
2230 vrc = VERR_GSTCTL_PROCESS_WRONG_STATE;
2231
2232 LogFlowFuncLeaveRC(vrc);
2233 return vrc;
2234}
2235
2236int GuestProcessTool::i_wait(uint32_t fFlags, int *pGuestRc)
2237{
2238 return i_waitEx(fFlags, NULL /* pStrmBlkOut */, pGuestRc);
2239}
2240
2241int GuestProcessTool::i_waitEx(uint32_t fFlags, GuestProcessStreamBlock *pStrmBlkOut, int *pGuestRc)
2242{
2243 LogFlowThisFunc(("fFlags=0x%x, pStreamBlock=%p, pGuestRc=%p\n",
2244 fFlags, pStrmBlkOut, pGuestRc));
2245
2246 /* Can we parse the next block without waiting? */
2247 int vrc;
2248 if (fFlags & GUESTPROCESSTOOL_FLAG_STDOUT_BLOCK)
2249 {
2250 AssertPtr(pStrmBlkOut);
2251 vrc = i_getCurrentBlock(OUTPUT_HANDLE_ID_STDOUT, *pStrmBlkOut);
2252 if (RT_SUCCESS(vrc))
2253 return vrc;
2254 /* else do the waiting below. */
2255 }
2256
2257 /* Do the waiting. */
2258 uint32_t fWaitFlags = ProcessWaitForFlag_Terminate;
2259 if (mStartupInfo.mFlags & ProcessCreateFlag_WaitForStdOut)
2260 fWaitFlags |= ProcessWaitForFlag_StdOut;
2261 if (mStartupInfo.mFlags & ProcessCreateFlag_WaitForStdErr)
2262 fWaitFlags |= ProcessWaitForFlag_StdErr;
2263
2264 /** @todo Decrease timeout while running. */
2265 uint64_t u64StartMS = RTTimeMilliTS();
2266 uint32_t uTimeoutMS = mStartupInfo.mTimeoutMS;
2267
2268 int vrcGuest = VINF_SUCCESS;
2269 bool fDone = false;
2270
2271 BYTE byBuf[_64K];
2272 uint32_t cbRead;
2273
2274 bool fHandleStdOut = false;
2275 bool fHandleStdErr = false;
2276
2277 /**
2278 * Updates the elapsed time and checks if a
2279 * timeout happened, then breaking out of the loop.
2280 */
2281#define UPDATE_AND_CHECK_ELAPSED_TIME() \
2282 u64ElapsedMS = RTTimeMilliTS() - u64StartMS; \
2283 if ( uTimeoutMS != RT_INDEFINITE_WAIT \
2284 && u64ElapsedMS >= uTimeoutMS) \
2285 { \
2286 vrc = VERR_TIMEOUT; \
2287 break; \
2288 }
2289
2290 /**
2291 * Returns the remaining time (in ms).
2292 */
2293#define GET_REMAINING_TIME \
2294 uTimeoutMS == RT_INDEFINITE_WAIT \
2295 ? RT_INDEFINITE_WAIT : uTimeoutMS - (uint32_t)u64ElapsedMS \
2296
2297 ProcessWaitResult_T waitRes = ProcessWaitResult_None;
2298 do
2299 {
2300 uint64_t u64ElapsedMS;
2301 UPDATE_AND_CHECK_ELAPSED_TIME();
2302
2303 vrc = pProcess->i_waitFor(fWaitFlags, GET_REMAINING_TIME,
2304 waitRes, &vrcGuest);
2305 if (RT_FAILURE(vrc))
2306 break;
2307
2308 switch (waitRes)
2309 {
2310 case ProcessWaitResult_StdIn:
2311 vrc = VERR_NOT_IMPLEMENTED;
2312 break;
2313
2314 case ProcessWaitResult_StdOut:
2315 fHandleStdOut = true;
2316 break;
2317
2318 case ProcessWaitResult_StdErr:
2319 fHandleStdErr = true;
2320 break;
2321
2322 case ProcessWaitResult_WaitFlagNotSupported:
2323 if (fWaitFlags & ProcessWaitForFlag_StdOut)
2324 fHandleStdOut = true;
2325 if (fWaitFlags & ProcessWaitForFlag_StdErr)
2326 fHandleStdErr = true;
2327 /* Since waiting for stdout / stderr is not supported by the guest,
2328 * wait a bit to not hog the CPU too much when polling for data. */
2329 RTThreadSleep(1); /* Optional, don't check rc. */
2330 break;
2331
2332 case ProcessWaitResult_Error:
2333 vrc = VERR_GSTCTL_GUEST_ERROR;
2334 break;
2335
2336 case ProcessWaitResult_Terminate:
2337 fDone = true;
2338 break;
2339
2340 case ProcessWaitResult_Timeout:
2341 vrc = VERR_TIMEOUT;
2342 break;
2343
2344 case ProcessWaitResult_Start:
2345 case ProcessWaitResult_Status:
2346 /* Not used here, just skip. */
2347 break;
2348
2349 default:
2350 AssertMsgFailed(("Unhandled process wait result %RU32\n", waitRes));
2351 break;
2352 }
2353
2354 if (RT_FAILURE(vrc))
2355 break;
2356
2357 if (fHandleStdOut)
2358 {
2359 UPDATE_AND_CHECK_ELAPSED_TIME();
2360
2361 cbRead = 0;
2362 vrc = pProcess->i_readData(OUTPUT_HANDLE_ID_STDOUT, sizeof(byBuf),
2363 GET_REMAINING_TIME,
2364 byBuf, sizeof(byBuf),
2365 &cbRead, &vrcGuest);
2366 if ( RT_FAILURE(vrc)
2367 || vrc == VWRN_GSTCTL_OBJECTSTATE_CHANGED)
2368 break;
2369
2370 if (cbRead)
2371 {
2372 LogFlowThisFunc(("Received %RU32 bytes from stdout\n", cbRead));
2373 vrc = mStdOut.AddData(byBuf, cbRead);
2374
2375 if ( RT_SUCCESS(vrc)
2376 && (fFlags & GUESTPROCESSTOOL_FLAG_STDOUT_BLOCK))
2377 {
2378 AssertPtr(pStrmBlkOut);
2379 vrc = i_getCurrentBlock(OUTPUT_HANDLE_ID_STDOUT, *pStrmBlkOut);
2380
2381 /* When successful, break out of the loop because we're done
2382 * with reading the first stream block. */
2383 if (RT_SUCCESS(vrc))
2384 fDone = true;
2385 }
2386 }
2387
2388 fHandleStdOut = false;
2389 }
2390
2391 if (fHandleStdErr)
2392 {
2393 UPDATE_AND_CHECK_ELAPSED_TIME();
2394
2395 cbRead = 0;
2396 vrc = pProcess->i_readData(OUTPUT_HANDLE_ID_STDERR, sizeof(byBuf),
2397 GET_REMAINING_TIME,
2398 byBuf, sizeof(byBuf),
2399 &cbRead, &vrcGuest);
2400 if ( RT_FAILURE(vrc)
2401 || vrc == VWRN_GSTCTL_OBJECTSTATE_CHANGED)
2402 break;
2403
2404 if (cbRead)
2405 {
2406 LogFlowThisFunc(("Received %RU32 bytes from stderr\n", cbRead));
2407 vrc = mStdErr.AddData(byBuf, cbRead);
2408 }
2409
2410 fHandleStdErr = false;
2411 }
2412
2413 } while (!fDone && RT_SUCCESS(vrc));
2414
2415#undef UPDATE_AND_CHECK_ELAPSED_TIME
2416#undef GET_REMAINING_TIME
2417
2418 if (RT_FAILURE(vrcGuest))
2419 vrc = VERR_GSTCTL_GUEST_ERROR;
2420
2421 LogFlowThisFunc(("Loop ended with rc=%Rrc, vrcGuest=%Rrc, waitRes=%RU32\n",
2422 vrc, vrcGuest, waitRes));
2423 if (pGuestRc)
2424 *pGuestRc = vrcGuest;
2425
2426 LogFlowFuncLeaveRC(vrc);
2427 return vrc;
2428}
2429
2430int GuestProcessTool::i_terminate(uint32_t uTimeoutMS, int *pGuestRc)
2431{
2432 LogFlowThisFuncEnter();
2433
2434 int rc = VINF_SUCCESS;
2435 if (!pProcess.isNull())
2436 {
2437 rc = pProcess->i_terminateProcess(uTimeoutMS, pGuestRc);
2438 pProcess.setNull();
2439 }
2440 else
2441 rc = VERR_NOT_FOUND;
2442
2443 LogFlowFuncLeaveRC(rc);
2444 return rc;
2445}
2446
2447/**
2448 * Converts a toolbox tool's exit code to an IPRT error code.
2449 *
2450 * @return int Returned IPRT error for the particular tool.
2451 * @param startupInfo Startup info of the toolbox tool to lookup error code for.
2452 * @param lExitCode The toolbox tool's exit code to lookup IPRT error for.
2453 */
2454/* static */
2455int GuestProcessTool::i_exitCodeToRc(const GuestProcessStartupInfo &startupInfo, LONG lExitCode)
2456{
2457 if (startupInfo.mArguments.size() == 0)
2458 {
2459 AssertFailed();
2460 return VERR_GENERAL_FAILURE; /* Should not happen. */
2461 }
2462
2463 return i_exitCodeToRc(startupInfo.mArguments[0].c_str(), lExitCode);
2464}
2465
2466/**
2467 * Converts a toolbox tool's exit code to an IPRT error code.
2468 *
2469 * @return Returned IPRT error for the particular tool.
2470 * @param pszTool Name of toolbox tool to lookup error code for.
2471 * @param lExitCode The toolbox tool's exit code to lookup IPRT error for.
2472 */
2473/* static */
2474int GuestProcessTool::i_exitCodeToRc(const char *pszTool, LONG lExitCode)
2475{
2476 AssertPtrReturn(pszTool, VERR_INVALID_POINTER);
2477
2478 LogFlowFunc(("%s: %ld\n", pszTool, lExitCode));
2479
2480 if (lExitCode == 0) /* No error? Bail out early. */
2481 return VINF_SUCCESS;
2482
2483 if (!RTStrICmp(pszTool, VBOXSERVICE_TOOL_CAT))
2484 {
2485 switch (lExitCode)
2486 {
2487 case VBOXSERVICETOOLBOX_CAT_EXITCODE_ACCESS_DENIED: return VERR_ACCESS_DENIED;
2488 case VBOXSERVICETOOLBOX_CAT_EXITCODE_FILE_NOT_FOUND: return VERR_FILE_NOT_FOUND;
2489 case VBOXSERVICETOOLBOX_CAT_EXITCODE_PATH_NOT_FOUND: return VERR_PATH_NOT_FOUND;
2490 case VBOXSERVICETOOLBOX_CAT_EXITCODE_SHARING_VIOLATION: return VERR_SHARING_VIOLATION;
2491 default:
2492 break;
2493 }
2494 }
2495 else if (!RTStrICmp(pszTool, VBOXSERVICE_TOOL_STAT))
2496 {
2497 switch (lExitCode)
2498 {
2499 case VBOXSERVICETOOLBOX_STAT_EXITCODE_ACCESS_DENIED: return VERR_ACCESS_DENIED;
2500 case VBOXSERVICETOOLBOX_STAT_EXITCODE_FILE_NOT_FOUND: return VERR_FILE_NOT_FOUND;
2501 case VBOXSERVICETOOLBOX_STAT_EXITCODE_PATH_NOT_FOUND: return VERR_PATH_NOT_FOUND;
2502 default:
2503 break;
2504 }
2505 }
2506
2507 AssertMsgFailed(("Error code %ld for tool '%s' not handled\n", lExitCode, pszTool));
2508 return VERR_GENERAL_FAILURE;
2509}
2510
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