VirtualBox

source: vbox/trunk/src/VBox/Main/src-client/GuestSessionImpl.cpp@ 77586

Last change on this file since 77586 was 77583, checked in by vboxsync, 6 years ago

Guest Control/Main: Renamed IGuestSession::i_isReadyExternal() -> IGuestSession::i_isStartedExternal() to match terminology, and added i_isStarted() + i_isTerminated() [build fix].

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 134.9 KB
Line 
1/* $Id: GuestSessionImpl.cpp 77583 2019-03-06 14:23:25Z vboxsync $ */
2/** @file
3 * VirtualBox Main - Guest session handling.
4 */
5
6/*
7 * Copyright (C) 2012-2019 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.215389.xyz. 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/*********************************************************************************************************************************
20* Header Files *
21*********************************************************************************************************************************/
22#define LOG_GROUP LOG_GROUP_MAIN_GUESTSESSION
23#include "LoggingNew.h"
24
25#include "GuestImpl.h"
26#ifndef VBOX_WITH_GUEST_CONTROL
27# error "VBOX_WITH_GUEST_CONTROL must defined in this file"
28#endif
29#include "GuestSessionImpl.h"
30#include "GuestSessionImplTasks.h"
31#include "GuestCtrlImplPrivate.h"
32#include "VirtualBoxErrorInfoImpl.h"
33
34#include "Global.h"
35#include "AutoCaller.h"
36#include "ProgressImpl.h"
37#include "VBoxEvents.h"
38#include "VMMDev.h"
39#include "ThreadTask.h"
40
41#include <memory> /* For auto_ptr. */
42
43#include <iprt/cpp/utils.h> /* For unconst(). */
44#include <iprt/ctype.h>
45#include <iprt/env.h>
46#include <iprt/file.h> /* For CopyTo/From. */
47#include <iprt/path.h>
48#include <iprt/rand.h>
49
50#include <VBox/com/array.h>
51#include <VBox/com/listeners.h>
52#include <VBox/version.h>
53
54
55/**
56 * Base class representing an internal
57 * asynchronous session task.
58 */
59class GuestSessionTaskInternal : public ThreadTask
60{
61public:
62
63 GuestSessionTaskInternal(GuestSession *pSession)
64 : ThreadTask("GenericGuestSessionTaskInternal")
65 , mSession(pSession)
66 , mRC(VINF_SUCCESS) { }
67
68 virtual ~GuestSessionTaskInternal(void) { }
69
70 int rc(void) const { return mRC; }
71 bool isOk(void) const { return RT_SUCCESS(mRC); }
72 const ComObjPtr<GuestSession> &Session(void) const { return mSession; }
73
74protected:
75
76 const ComObjPtr<GuestSession> mSession;
77 int mRC;
78};
79
80/**
81 * Class for asynchronously starting a guest session.
82 */
83class GuestSessionTaskInternalStart : public GuestSessionTaskInternal
84{
85public:
86
87 GuestSessionTaskInternalStart(GuestSession *pSession)
88 : GuestSessionTaskInternal(pSession)
89 {
90 m_strTaskName = "gctlSesStart";
91 }
92
93 void handler()
94 {
95 /* Ignore rc */ GuestSession::i_startSessionThreadTask(this);
96 }
97};
98
99/**
100 * Internal listener class to serve events in an
101 * active manner, e.g. without polling delays.
102 */
103class GuestSessionListener
104{
105public:
106
107 GuestSessionListener(void)
108 {
109 }
110
111 virtual ~GuestSessionListener(void)
112 {
113 }
114
115 HRESULT init(GuestSession *pSession)
116 {
117 AssertPtrReturn(pSession, E_POINTER);
118 mSession = pSession;
119 return S_OK;
120 }
121
122 void uninit(void)
123 {
124 mSession = NULL;
125 }
126
127 STDMETHOD(HandleEvent)(VBoxEventType_T aType, IEvent *aEvent)
128 {
129 switch (aType)
130 {
131 case VBoxEventType_OnGuestSessionStateChanged:
132 {
133 AssertPtrReturn(mSession, E_POINTER);
134 int rc2 = mSession->signalWaitEvent(aType, aEvent);
135 RT_NOREF(rc2);
136#ifdef DEBUG_andy
137 LogFlowFunc(("Signalling events of type=%RU32, session=%p resulted in rc=%Rrc\n",
138 aType, mSession, rc2));
139#endif
140 break;
141 }
142
143 default:
144 AssertMsgFailed(("Unhandled event %RU32\n", aType));
145 break;
146 }
147
148 return S_OK;
149 }
150
151private:
152
153 GuestSession *mSession;
154};
155typedef ListenerImpl<GuestSessionListener, GuestSession*> GuestSessionListenerImpl;
156
157VBOX_LISTENER_DECLARE(GuestSessionListenerImpl)
158
159// constructor / destructor
160/////////////////////////////////////////////////////////////////////////////
161
162DEFINE_EMPTY_CTOR_DTOR(GuestSession)
163
164HRESULT GuestSession::FinalConstruct(void)
165{
166 LogFlowThisFuncEnter();
167 return BaseFinalConstruct();
168}
169
170void GuestSession::FinalRelease(void)
171{
172 LogFlowThisFuncEnter();
173 uninit();
174 BaseFinalRelease();
175 LogFlowThisFuncLeave();
176}
177
178// public initializer/uninitializer for internal purposes only
179/////////////////////////////////////////////////////////////////////////////
180
181/**
182 * Initializes a guest session but does *not* open in on the guest side
183 * yet. This needs to be done via the openSession() / openSessionAsync calls.
184 *
185 * @return IPRT status code.
186 ** @todo Docs!
187 */
188int GuestSession::init(Guest *pGuest, const GuestSessionStartupInfo &ssInfo,
189 const GuestCredentials &guestCreds)
190{
191 LogFlowThisFunc(("pGuest=%p, ssInfo=%p, guestCreds=%p\n",
192 pGuest, &ssInfo, &guestCreds));
193
194 /* Enclose the state transition NotReady->InInit->Ready. */
195 AutoInitSpan autoInitSpan(this);
196 AssertReturn(autoInitSpan.isOk(), VERR_OBJECT_DESTROYED);
197
198 AssertPtrReturn(pGuest, VERR_INVALID_POINTER);
199
200 /*
201 * Initialize our data members from the input.
202 */
203 mParent = pGuest;
204
205 /* Copy over startup info. */
206 /** @todo Use an overloaded copy operator. Later. */
207 mData.mSession.mID = ssInfo.mID;
208 mData.mSession.mIsInternal = ssInfo.mIsInternal;
209 mData.mSession.mName = ssInfo.mName;
210 mData.mSession.mOpenFlags = ssInfo.mOpenFlags;
211 mData.mSession.mOpenTimeoutMS = ssInfo.mOpenTimeoutMS;
212
213 /* Copy over session credentials. */
214 /** @todo Use an overloaded copy operator. Later. */
215 mData.mCredentials.mUser = guestCreds.mUser;
216 mData.mCredentials.mPassword = guestCreds.mPassword;
217 mData.mCredentials.mDomain = guestCreds.mDomain;
218
219 /* Initialize the remainder of the data. */
220 mData.mRC = VINF_SUCCESS;
221 mData.mStatus = GuestSessionStatus_Undefined;
222 mData.mpBaseEnvironment = NULL;
223
224 /*
225 * Register an object for the session itself to clearly
226 * distinguish callbacks which are for this session directly, or for
227 * objects (like files, directories, ...) which are bound to this session.
228 */
229 int rc = i_objectRegister(SESSIONOBJECTTYPE_SESSION, &mData.mObjectID);
230 if (RT_SUCCESS(rc))
231 {
232 rc = mData.mEnvironmentChanges.initChangeRecord();
233 if (RT_SUCCESS(rc))
234 {
235 rc = RTCritSectInit(&mWaitEventCritSect);
236 AssertRC(rc);
237 }
238 }
239
240 if (RT_SUCCESS(rc))
241 rc = i_determineProtocolVersion();
242
243 if (RT_SUCCESS(rc))
244 {
245 /*
246 * <Replace this if you figure out what the code is doing.>
247 */
248 HRESULT hr = unconst(mEventSource).createObject();
249 if (SUCCEEDED(hr))
250 hr = mEventSource->init();
251 if (SUCCEEDED(hr))
252 {
253 try
254 {
255 GuestSessionListener *pListener = new GuestSessionListener();
256 ComObjPtr<GuestSessionListenerImpl> thisListener;
257 hr = thisListener.createObject();
258 if (SUCCEEDED(hr))
259 hr = thisListener->init(pListener, this); /* thisListener takes ownership of pListener. */
260 if (SUCCEEDED(hr))
261 {
262 com::SafeArray <VBoxEventType_T> eventTypes;
263 eventTypes.push_back(VBoxEventType_OnGuestSessionStateChanged);
264 hr = mEventSource->RegisterListener(thisListener,
265 ComSafeArrayAsInParam(eventTypes),
266 TRUE /* Active listener */);
267 if (SUCCEEDED(hr))
268 {
269 mLocalListener = thisListener;
270
271 /*
272 * Mark this object as operational and return success.
273 */
274 autoInitSpan.setSucceeded();
275 LogFlowThisFunc(("mName=%s mID=%RU32 mIsInternal=%RTbool rc=VINF_SUCCESS\n",
276 mData.mSession.mName.c_str(), mData.mSession.mID, mData.mSession.mIsInternal));
277 return VINF_SUCCESS;
278 }
279 }
280 }
281 catch (std::bad_alloc &)
282 {
283 hr = E_OUTOFMEMORY;
284 }
285 }
286 rc = Global::vboxStatusCodeFromCOM(hr);
287 }
288
289 autoInitSpan.setFailed();
290 LogThisFunc(("Failed! mName=%s mID=%RU32 mIsInternal=%RTbool => rc=%Rrc\n",
291 mData.mSession.mName.c_str(), mData.mSession.mID, mData.mSession.mIsInternal, rc));
292 return rc;
293}
294
295/**
296 * Uninitializes the instance.
297 * Called from FinalRelease().
298 */
299void GuestSession::uninit(void)
300{
301 /* Enclose the state transition Ready->InUninit->NotReady. */
302 AutoUninitSpan autoUninitSpan(this);
303 if (autoUninitSpan.uninitDone())
304 return;
305
306 LogFlowThisFuncEnter();
307
308 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
309
310 LogFlowThisFunc(("Closing directories (%zu total)\n",
311 mData.mDirectories.size()));
312 for (SessionDirectories::iterator itDirs = mData.mDirectories.begin();
313 itDirs != mData.mDirectories.end(); ++itDirs)
314 {
315 itDirs->second->i_onRemove();
316 itDirs->second->uninit();
317 }
318 mData.mDirectories.clear();
319
320 LogFlowThisFunc(("Closing files (%zu total)\n",
321 mData.mFiles.size()));
322 for (SessionFiles::iterator itFiles = mData.mFiles.begin();
323 itFiles != mData.mFiles.end(); ++itFiles)
324 {
325 itFiles->second->i_onRemove();
326 itFiles->second->uninit();
327 }
328 mData.mFiles.clear();
329
330 LogFlowThisFunc(("Closing processes (%zu total)\n",
331 mData.mProcesses.size()));
332 for (SessionProcesses::iterator itProcs = mData.mProcesses.begin();
333 itProcs != mData.mProcesses.end(); ++itProcs)
334 {
335 itProcs->second->i_onRemove();
336 itProcs->second->uninit();
337 }
338 mData.mProcesses.clear();
339
340 /* Unregister the session's object ID. */
341 i_objectUnregister(mData.mObjectID);
342
343 mData.mObjects.clear();
344
345 mData.mEnvironmentChanges.reset();
346
347 if (mData.mpBaseEnvironment)
348 {
349 mData.mpBaseEnvironment->releaseConst();
350 mData.mpBaseEnvironment = NULL;
351 }
352
353 /* Unitialize our local listener. */
354 mLocalListener.setNull();
355
356 baseUninit();
357
358 LogFlowFuncLeave();
359}
360
361// implementation of public getters/setters for attributes
362/////////////////////////////////////////////////////////////////////////////
363
364HRESULT GuestSession::getUser(com::Utf8Str &aUser)
365{
366 LogFlowThisFuncEnter();
367
368 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
369
370 aUser = mData.mCredentials.mUser;
371
372 LogFlowThisFuncLeave();
373 return S_OK;
374}
375
376HRESULT GuestSession::getDomain(com::Utf8Str &aDomain)
377{
378 LogFlowThisFuncEnter();
379
380 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
381
382 aDomain = mData.mCredentials.mDomain;
383
384 LogFlowThisFuncLeave();
385 return S_OK;
386}
387
388HRESULT GuestSession::getName(com::Utf8Str &aName)
389{
390 LogFlowThisFuncEnter();
391
392 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
393
394 aName = mData.mSession.mName;
395
396 LogFlowThisFuncLeave();
397 return S_OK;
398}
399
400HRESULT GuestSession::getId(ULONG *aId)
401{
402 LogFlowThisFuncEnter();
403
404 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
405
406 *aId = mData.mSession.mID;
407
408 LogFlowThisFuncLeave();
409 return S_OK;
410}
411
412HRESULT GuestSession::getStatus(GuestSessionStatus_T *aStatus)
413{
414 LogFlowThisFuncEnter();
415
416 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
417
418 *aStatus = mData.mStatus;
419
420 LogFlowThisFuncLeave();
421 return S_OK;
422}
423
424HRESULT GuestSession::getTimeout(ULONG *aTimeout)
425{
426 LogFlowThisFuncEnter();
427
428 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
429
430 *aTimeout = mData.mTimeout;
431
432 LogFlowThisFuncLeave();
433 return S_OK;
434}
435
436HRESULT GuestSession::setTimeout(ULONG aTimeout)
437{
438 LogFlowThisFuncEnter();
439
440 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
441
442 mData.mTimeout = aTimeout;
443
444 LogFlowThisFuncLeave();
445 return S_OK;
446}
447
448HRESULT GuestSession::getProtocolVersion(ULONG *aProtocolVersion)
449{
450 LogFlowThisFuncEnter();
451
452 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
453
454 *aProtocolVersion = mData.mProtocolVersion;
455
456 LogFlowThisFuncLeave();
457 return S_OK;
458}
459
460HRESULT GuestSession::getEnvironmentChanges(std::vector<com::Utf8Str> &aEnvironmentChanges)
461{
462 LogFlowThisFuncEnter();
463
464 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
465
466 int vrc = mData.mEnvironmentChanges.queryPutEnvArray(&aEnvironmentChanges);
467
468 LogFlowFuncLeaveRC(vrc);
469 return Global::vboxStatusCodeToCOM(vrc);
470}
471
472HRESULT GuestSession::setEnvironmentChanges(const std::vector<com::Utf8Str> &aEnvironmentChanges)
473{
474 LogFlowThisFuncEnter();
475
476 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
477
478 mData.mEnvironmentChanges.reset();
479 int vrc = mData.mEnvironmentChanges.applyPutEnvArray(aEnvironmentChanges);
480
481 LogFlowFuncLeaveRC(vrc);
482 return Global::vboxStatusCodeToCOM(vrc);
483}
484
485HRESULT GuestSession::getEnvironmentBase(std::vector<com::Utf8Str> &aEnvironmentBase)
486{
487 LogFlowThisFuncEnter();
488
489 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
490 HRESULT hrc;
491 if (mData.mpBaseEnvironment)
492 {
493 int vrc = mData.mpBaseEnvironment->queryPutEnvArray(&aEnvironmentBase);
494 hrc = Global::vboxStatusCodeToCOM(vrc);
495 }
496 else if (mData.mProtocolVersion < 99999)
497 hrc = setError(VBOX_E_NOT_SUPPORTED, tr("The base environment feature is not supported by the guest additions"));
498 else
499 hrc = setError(VBOX_E_INVALID_OBJECT_STATE, tr("The base environment has not yet been reported by the guest"));
500
501 LogFlowFuncLeave();
502 return hrc;
503}
504
505HRESULT GuestSession::getProcesses(std::vector<ComPtr<IGuestProcess> > &aProcesses)
506{
507 LogFlowThisFuncEnter();
508
509 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
510
511 aProcesses.resize(mData.mProcesses.size());
512 size_t i = 0;
513 for(SessionProcesses::iterator it = mData.mProcesses.begin();
514 it != mData.mProcesses.end();
515 ++it, ++i)
516 {
517 it->second.queryInterfaceTo(aProcesses[i].asOutParam());
518 }
519
520 LogFlowFunc(("mProcesses=%zu\n", aProcesses.size()));
521 return S_OK;
522}
523
524HRESULT GuestSession::getPathStyle(PathStyle_T *aPathStyle)
525{
526 *aPathStyle = i_getPathStyle();
527 return S_OK;
528}
529
530HRESULT GuestSession::getCurrentDirectory(com::Utf8Str &aCurrentDirectory)
531{
532 RT_NOREF(aCurrentDirectory);
533 ReturnComNotImplemented();
534}
535
536HRESULT GuestSession::setCurrentDirectory(const com::Utf8Str &aCurrentDirectory)
537{
538 RT_NOREF(aCurrentDirectory);
539 ReturnComNotImplemented();
540}
541
542HRESULT GuestSession::getUserHome(com::Utf8Str &aUserHome)
543{
544 HRESULT hr = i_isStartedExternal();
545 if (FAILED(hr))
546 return hr;
547
548 int rcGuest;
549 int vrc = i_pathUserHome(aUserHome, &rcGuest);
550 if (RT_FAILURE(vrc))
551 {
552 switch (vrc)
553 {
554 case VERR_GSTCTL_GUEST_ERROR:
555 {
556 switch (rcGuest)
557 {
558 case VERR_NOT_SUPPORTED:
559 hr = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest,
560 tr("Getting the user's home path is not supported by installed Guest Additions"));
561 break;
562
563 default:
564 hr = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest,
565 tr("Getting the user's home path failed on the guest: %Rrc"), rcGuest);
566 break;
567 }
568 break;
569 }
570
571 default:
572 hr = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Getting the user's home path failed: %Rrc"), vrc);
573 break;
574 }
575 }
576
577 return hr;
578}
579
580HRESULT GuestSession::getUserDocuments(com::Utf8Str &aUserDocuments)
581{
582 HRESULT hr = i_isStartedExternal();
583 if (FAILED(hr))
584 return hr;
585
586 int rcGuest;
587 int vrc = i_pathUserDocuments(aUserDocuments, &rcGuest);
588 if (RT_FAILURE(vrc))
589 {
590 switch (vrc)
591 {
592 case VERR_GSTCTL_GUEST_ERROR:
593 {
594 switch (rcGuest)
595 {
596 case VERR_NOT_SUPPORTED:
597 hr = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest,
598 tr("Getting the user's documents path is not supported by installed Guest Additions"));
599 break;
600
601 default:
602 hr = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest,
603 tr("Getting the user's documents path failed on the guest: %Rrc"), rcGuest);
604 break;
605 }
606 break;
607 }
608
609 default:
610 hr = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Getting the user's documents path failed: %Rrc"), vrc);
611 break;
612 }
613 }
614
615 return hr;
616}
617
618HRESULT GuestSession::getDirectories(std::vector<ComPtr<IGuestDirectory> > &aDirectories)
619{
620 LogFlowThisFuncEnter();
621
622 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
623
624 aDirectories.resize(mData.mDirectories.size());
625 size_t i = 0;
626 for(SessionDirectories::iterator it = mData.mDirectories.begin();
627 it != mData.mDirectories.end();
628 ++it, ++i)
629 {
630 it->second.queryInterfaceTo(aDirectories[i].asOutParam());
631 }
632
633 LogFlowFunc(("mDirectories=%zu\n", aDirectories.size()));
634 return S_OK;
635}
636
637HRESULT GuestSession::getFiles(std::vector<ComPtr<IGuestFile> > &aFiles)
638{
639 LogFlowThisFuncEnter();
640
641 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
642
643 aFiles.resize(mData.mFiles.size());
644 size_t i = 0;
645 for(SessionFiles::iterator it = mData.mFiles.begin(); it != mData.mFiles.end(); ++it, ++i)
646 it->second.queryInterfaceTo(aFiles[i].asOutParam());
647
648 LogFlowFunc(("mDirectories=%zu\n", aFiles.size()));
649
650 return S_OK;
651}
652
653HRESULT GuestSession::getEventSource(ComPtr<IEventSource> &aEventSource)
654{
655 LogFlowThisFuncEnter();
656
657 // no need to lock - lifetime constant
658 mEventSource.queryInterfaceTo(aEventSource.asOutParam());
659
660 LogFlowThisFuncLeave();
661 return S_OK;
662}
663
664// private methods
665///////////////////////////////////////////////////////////////////////////////
666
667int GuestSession::i_closeSession(uint32_t uFlags, uint32_t uTimeoutMS, int *prcGuest)
668{
669 AssertPtrReturn(prcGuest, VERR_INVALID_POINTER);
670
671 LogFlowThisFunc(("uFlags=%x, uTimeoutMS=%RU32\n", uFlags, uTimeoutMS));
672
673 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
674
675 /* Guest Additions < 4.3 don't support closing dedicated
676 guest sessions, skip. */
677 if (mData.mProtocolVersion < 2)
678 {
679 LogFlowThisFunc(("Installed Guest Additions don't support closing dedicated sessions, skipping\n"));
680 return VINF_SUCCESS;
681 }
682
683 /** @todo uFlags validation. */
684
685 if (mData.mStatus != GuestSessionStatus_Started)
686 {
687 LogFlowThisFunc(("Session ID=%RU32 not started (anymore), status now is: %RU32\n",
688 mData.mSession.mID, mData.mStatus));
689 return VINF_SUCCESS;
690 }
691
692 int vrc;
693
694 GuestWaitEvent *pEvent = NULL;
695 GuestEventTypes eventTypes;
696 try
697 {
698 eventTypes.push_back(VBoxEventType_OnGuestSessionStateChanged);
699
700 vrc = registerWaitEventEx(mData.mSession.mID, mData.mObjectID, eventTypes, &pEvent);
701 }
702 catch (std::bad_alloc &)
703 {
704 vrc = VERR_NO_MEMORY;
705 }
706
707 if (RT_FAILURE(vrc))
708 return vrc;
709
710 LogFlowThisFunc(("Sending closing request to guest session ID=%RU32, uFlags=%x\n",
711 mData.mSession.mID, uFlags));
712
713 VBOXHGCMSVCPARM paParms[4];
714 int i = 0;
715 HGCMSvcSetU32(&paParms[i++], pEvent->ContextID());
716 HGCMSvcSetU32(&paParms[i++], uFlags);
717
718 alock.release(); /* Drop the write lock before waiting. */
719
720 vrc = i_sendMessage(HOST_MSG_SESSION_CLOSE, i, paParms, VBOX_GUESTCTRL_DST_BOTH);
721 if (RT_SUCCESS(vrc))
722 vrc = i_waitForStatusChange(pEvent, GuestSessionWaitForFlag_Terminate, uTimeoutMS,
723 NULL /* Session status */, prcGuest);
724
725 unregisterWaitEvent(pEvent);
726
727 LogFlowFuncLeaveRC(vrc);
728 return vrc;
729}
730
731/**
732 * Internal worker function for public APIs that handle copying elements from
733 * guest to the host.
734 *
735 * @return HRESULT
736 * @param SourceSet Source set specifying what to copy.
737 * @param strDestination Destination path on the host. Host path style.
738 * @param pProgress Progress object returned to the caller.
739 */
740HRESULT GuestSession::i_copyFromGuest(const GuestSessionFsSourceSet &SourceSet,
741 const com::Utf8Str &strDestination, ComPtr<IProgress> &pProgress)
742{
743 HRESULT hrc = i_isStartedExternal();
744 if (FAILED(hrc))
745 return hrc;
746
747 LogFlowThisFuncEnter();
748
749 /* Validate stuff. */
750 if (RT_UNLIKELY(SourceSet.size() == 0 || *(SourceSet[0].strSource.c_str()) == '\0')) /* At least one source must be present. */
751 return setError(E_INVALIDARG, tr("No source(s) specified"));
752 if (RT_UNLIKELY((strDestination.c_str()) == NULL || *(strDestination.c_str()) == '\0'))
753 return setError(E_INVALIDARG, tr("No destination specified"));
754
755 try
756 {
757 GuestSessionTaskCopyFrom *pTask = NULL;
758 ComObjPtr<Progress> pProgressObj;
759 try
760 {
761 pTask = new GuestSessionTaskCopyFrom(this /* GuestSession */, SourceSet, strDestination);
762 }
763 catch(...)
764 {
765 hrc = setError(VBOX_E_IPRT_ERROR, tr("Failed to create SessionTaskCopyFrom object"));
766 throw;
767 }
768
769 hrc = pTask->Init(Utf8StrFmt(tr("Copying to \"%s\" on the host"), strDestination.c_str()));
770 if (FAILED(hrc))
771 {
772 delete pTask;
773 hrc = setError(VBOX_E_IPRT_ERROR,
774 tr("Creating progress object for SessionTaskCopyFrom object failed"));
775 throw hrc;
776 }
777
778 hrc = pTask->createThreadWithType(RTTHREADTYPE_MAIN_HEAVY_WORKER);
779 if (SUCCEEDED(hrc))
780 {
781 /* Return progress to the caller. */
782 pProgressObj = pTask->GetProgressObject();
783 hrc = pProgressObj.queryInterfaceTo(pProgress.asOutParam());
784 }
785 else
786 hrc = setError(hrc, tr("Starting thread for copying from guest to \"%s\" on the host failed"), strDestination.c_str());
787
788 }
789 catch (std::bad_alloc &)
790 {
791 hrc = E_OUTOFMEMORY;
792 }
793 catch (HRESULT eHR)
794 {
795 hrc = eHR;
796 }
797
798 LogFlowFunc(("Returning %Rhrc\n", hrc));
799 return hrc;
800}
801
802/**
803 * Internal worker function for public APIs that handle copying elements from
804 * host to the guest.
805 *
806 * @return HRESULT
807 * @param SourceSet Source set specifying what to copy.
808 * @param strDestination Destination path on the guest. Guest path style.
809 * @param pProgress Progress object returned to the caller.
810 */
811HRESULT GuestSession::i_copyToGuest(const GuestSessionFsSourceSet &SourceSet,
812 const com::Utf8Str &strDestination, ComPtr<IProgress> &pProgress)
813{
814 HRESULT hrc = i_isStartedExternal();
815 if (FAILED(hrc))
816 return hrc;
817
818 LogFlowThisFuncEnter();
819
820 /* Validate stuff. */
821 if (RT_UNLIKELY(SourceSet.size() == 0 || *(SourceSet[0].strSource.c_str()) == '\0')) /* At least one source must be present. */
822 return setError(E_INVALIDARG, tr("No source(s) specified"));
823 if (RT_UNLIKELY((strDestination.c_str()) == NULL || *(strDestination.c_str()) == '\0'))
824 return setError(E_INVALIDARG, tr("No destination specified"));
825
826 try
827 {
828 GuestSessionTaskCopyTo *pTask = NULL;
829 ComObjPtr<Progress> pProgressObj;
830 try
831 {
832 pTask = new GuestSessionTaskCopyTo(this /* GuestSession */, SourceSet, strDestination);
833 }
834 catch(...)
835 {
836 hrc = setError(VBOX_E_IPRT_ERROR, tr("Failed to create SessionTaskCopyTo object"));
837 throw;
838 }
839
840 hrc = pTask->Init(Utf8StrFmt(tr("Copying to \"%s\" on the guest"), strDestination.c_str()));
841 if (FAILED(hrc))
842 {
843 delete pTask;
844 hrc = setError(VBOX_E_IPRT_ERROR,
845 tr("Creating progress object for SessionTaskCopyTo object failed"));
846 throw hrc;
847 }
848
849 hrc = pTask->createThreadWithType(RTTHREADTYPE_MAIN_HEAVY_WORKER);
850 if (SUCCEEDED(hrc))
851 {
852 /* Return progress to the caller. */
853 pProgressObj = pTask->GetProgressObject();
854 hrc = pProgressObj.queryInterfaceTo(pProgress.asOutParam());
855 }
856 else
857 hrc = setError(hrc, tr("Starting thread for copying from host to \"%s\" on the guest failed"), strDestination.c_str());
858
859 }
860 catch (std::bad_alloc &)
861 {
862 hrc = E_OUTOFMEMORY;
863 }
864 catch (HRESULT eHR)
865 {
866 hrc = eHR;
867 }
868
869 LogFlowFunc(("Returning %Rhrc\n", hrc));
870 return hrc;
871}
872
873/**
874 * Validates and extracts directory copy flags from a comma-separated string.
875 *
876 * @return COM status, error set on failure
877 * @param strFlags String to extract flags from.
878 * @param pfFlags Where to store the extracted (and validated) flags.
879 */
880HRESULT GuestSession::i_directoryCopyFlagFromStr(const com::Utf8Str &strFlags, DirectoryCopyFlag_T *pfFlags)
881{
882 unsigned fFlags = DirectoryCopyFlag_None;
883
884 /* Validate and set flags. */
885 if (strFlags.isNotEmpty())
886 {
887 const char *pszNext = strFlags.c_str();
888 for (;;)
889 {
890 /* Find the next keyword, ignoring all whitespace. */
891 pszNext = RTStrStripL(pszNext);
892
893 const char * const pszComma = strchr(pszNext, ',');
894 size_t cchKeyword = pszComma ? pszComma - pszNext : strlen(pszNext);
895 while (cchKeyword > 0 && RT_C_IS_SPACE(pszNext[cchKeyword - 1]))
896 cchKeyword--;
897
898 if (cchKeyword > 0)
899 {
900 /* Convert keyword to flag. */
901#define MATCH_KEYWORD(a_szKeyword) ( cchKeyword == sizeof(a_szKeyword) - 1U \
902 && memcmp(pszNext, a_szKeyword, sizeof(a_szKeyword) - 1U) == 0)
903 if (MATCH_KEYWORD("CopyIntoExisting"))
904 fFlags |= (unsigned)DirectoryCopyFlag_CopyIntoExisting;
905 else
906 return setError(E_INVALIDARG, tr("Invalid directory copy flag: %.*s"), (int)cchKeyword, pszNext);
907#undef MATCH_KEYWORD
908 }
909 if (!pszComma)
910 break;
911 pszNext = pszComma + 1;
912 }
913 }
914
915 if (pfFlags)
916 *pfFlags = (DirectoryCopyFlag_T)fFlags;
917 return S_OK;
918}
919
920int GuestSession::i_directoryCreate(const Utf8Str &strPath, uint32_t uMode,
921 uint32_t uFlags, int *prcGuest)
922{
923 AssertPtrReturn(prcGuest, VERR_INVALID_POINTER);
924
925 LogFlowThisFunc(("strPath=%s, uMode=%x, uFlags=%x\n", strPath.c_str(), uMode, uFlags));
926
927 int vrc = VINF_SUCCESS;
928
929 GuestProcessStartupInfo procInfo;
930 procInfo.mFlags = ProcessCreateFlag_Hidden;
931 procInfo.mExecutable = Utf8Str(VBOXSERVICE_TOOL_MKDIR);
932
933 try
934 {
935 procInfo.mArguments.push_back(procInfo.mExecutable); /* Set argv0. */
936
937 /* Construct arguments. */
938 if (uFlags)
939 {
940 if (uFlags & DirectoryCreateFlag_Parents)
941 procInfo.mArguments.push_back(Utf8Str("--parents")); /* We also want to create the parent directories. */
942 else
943 vrc = VERR_INVALID_PARAMETER;
944 }
945
946 if ( RT_SUCCESS(vrc)
947 && uMode)
948 {
949 procInfo.mArguments.push_back(Utf8Str("--mode")); /* Set the creation mode. */
950
951 char szMode[16];
952 if (RTStrPrintf(szMode, sizeof(szMode), "%o", uMode))
953 {
954 procInfo.mArguments.push_back(Utf8Str(szMode));
955 }
956 else
957 vrc = VERR_BUFFER_OVERFLOW;
958 }
959
960 procInfo.mArguments.push_back("--"); /* '--version' is a valid directory name. */
961 procInfo.mArguments.push_back(strPath); /* The directory we want to create. */
962 }
963 catch (std::bad_alloc &)
964 {
965 vrc = VERR_NO_MEMORY;
966 }
967
968 if (RT_SUCCESS(vrc))
969 vrc = GuestProcessTool::run(this, procInfo, prcGuest);
970
971 LogFlowFuncLeaveRC(vrc);
972 return vrc;
973}
974
975inline bool GuestSession::i_directoryExists(uint32_t uDirID, ComObjPtr<GuestDirectory> *pDir)
976{
977 SessionDirectories::const_iterator it = mData.mDirectories.find(uDirID);
978 if (it != mData.mDirectories.end())
979 {
980 if (pDir)
981 *pDir = it->second;
982 return true;
983 }
984 return false;
985}
986
987int GuestSession::i_directoryQueryInfo(const Utf8Str &strPath, bool fFollowSymlinks,
988 GuestFsObjData &objData, int *prcGuest)
989{
990 AssertPtrReturn(prcGuest, VERR_INVALID_POINTER);
991
992 LogFlowThisFunc(("strPath=%s, fFollowSymlinks=%RTbool\n", strPath.c_str(), fFollowSymlinks));
993
994 int vrc = i_fsQueryInfo(strPath, fFollowSymlinks, objData, prcGuest);
995 if (RT_SUCCESS(vrc))
996 {
997 vrc = objData.mType == FsObjType_Directory
998 ? VINF_SUCCESS : VERR_NOT_A_DIRECTORY;
999 }
1000
1001 LogFlowFuncLeaveRC(vrc);
1002 return vrc;
1003}
1004
1005/**
1006 * Unregisters a directory object from a session.
1007 *
1008 * @return VBox status code. VERR_NOT_FOUND if the directory is not registered (anymore).
1009 * @param pDirectory Directory object to unregister from session.
1010 */
1011int GuestSession::i_directoryUnregister(GuestDirectory *pDirectory)
1012{
1013 AssertPtrReturn(pDirectory, VERR_INVALID_POINTER);
1014
1015 LogFlowThisFunc(("pDirectory=%p\n", pDirectory));
1016
1017 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1018
1019 const uint32_t idObject = pDirectory->getObjectID();
1020
1021 LogFlowFunc(("Removing directory (objectID=%RU32) ...\n", idObject));
1022
1023 int rc = i_objectUnregister(idObject);
1024 if (RT_FAILURE(rc))
1025 return rc;
1026
1027 SessionDirectories::iterator itDirs = mData.mDirectories.find(idObject);
1028 AssertReturn(itDirs != mData.mDirectories.end(), VERR_NOT_FOUND);
1029
1030 /* Make sure to consume the pointer before the one of the iterator gets released. */
1031 ComObjPtr<GuestDirectory> pDirConsumed = pDirectory;
1032
1033 LogFlowFunc(("Removing directory ID=%RU32 (session %RU32, now total %zu directories)\n",
1034 idObject, mData.mSession.mID, mData.mDirectories.size()));
1035
1036 rc = pDirConsumed->i_onRemove();
1037 AssertRCReturn(rc, rc);
1038
1039 mData.mDirectories.erase(itDirs);
1040
1041 alock.release(); /* Release lock before firing off event. */
1042
1043// fireGuestDirectoryRegisteredEvent(mEventSource, this /* Session */, pDirConsumed, false /* Process unregistered */);
1044
1045 pDirConsumed.setNull();
1046
1047 LogFlowFuncLeaveRC(rc);
1048 return rc;
1049}
1050
1051int GuestSession::i_directoryRemove(const Utf8Str &strPath, uint32_t uFlags, int *prcGuest)
1052{
1053 AssertReturn(!(uFlags & ~DIRREMOVEREC_FLAG_VALID_MASK), VERR_INVALID_PARAMETER);
1054 AssertPtrReturn(prcGuest, VERR_INVALID_POINTER);
1055
1056 LogFlowThisFunc(("strPath=%s, uFlags=0x%x\n", strPath.c_str(), uFlags));
1057
1058 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1059
1060 GuestWaitEvent *pEvent = NULL;
1061 int vrc = registerWaitEvent(mData.mSession.mID, mData.mObjectID, &pEvent);
1062 if (RT_FAILURE(vrc))
1063 return vrc;
1064
1065 /* Prepare HGCM call. */
1066 VBOXHGCMSVCPARM paParms[8];
1067 int i = 0;
1068 HGCMSvcSetU32(&paParms[i++], pEvent->ContextID());
1069 HGCMSvcSetPv(&paParms[i++], (void*)strPath.c_str(),
1070 (ULONG)strPath.length() + 1);
1071 HGCMSvcSetU32(&paParms[i++], uFlags);
1072
1073 alock.release(); /* Drop write lock before sending. */
1074
1075 vrc = i_sendMessage(HOST_MSG_DIR_REMOVE, i, paParms);
1076 if (RT_SUCCESS(vrc))
1077 {
1078 vrc = pEvent->Wait(30 * 1000);
1079 if ( vrc == VERR_GSTCTL_GUEST_ERROR
1080 && prcGuest)
1081 *prcGuest = pEvent->GuestResult();
1082 }
1083
1084 unregisterWaitEvent(pEvent);
1085
1086 LogFlowFuncLeaveRC(vrc);
1087 return vrc;
1088}
1089
1090int GuestSession::i_fsCreateTemp(const Utf8Str &strTemplate, const Utf8Str &strPath, bool fDirectory, Utf8Str &strName,
1091 int *prcGuest)
1092{
1093 AssertPtrReturn(prcGuest, VERR_INVALID_POINTER);
1094
1095 LogFlowThisFunc(("strTemplate=%s, strPath=%s, fDirectory=%RTbool\n",
1096 strTemplate.c_str(), strPath.c_str(), fDirectory));
1097
1098 GuestProcessStartupInfo procInfo;
1099 procInfo.mFlags = ProcessCreateFlag_WaitForStdOut;
1100 try
1101 {
1102 procInfo.mExecutable = Utf8Str(VBOXSERVICE_TOOL_MKTEMP);
1103 procInfo.mArguments.push_back(procInfo.mExecutable); /* Set argv0. */
1104 procInfo.mArguments.push_back(Utf8Str("--machinereadable"));
1105 if (fDirectory)
1106 procInfo.mArguments.push_back(Utf8Str("-d"));
1107 if (strPath.length()) /* Otherwise use /tmp or equivalent. */
1108 {
1109 procInfo.mArguments.push_back(Utf8Str("-t"));
1110 procInfo.mArguments.push_back(strPath);
1111 }
1112 procInfo.mArguments.push_back("--"); /* strTemplate could be '--help'. */
1113 procInfo.mArguments.push_back(strTemplate);
1114 }
1115 catch (std::bad_alloc &)
1116 {
1117 Log(("Out of memory!\n"));
1118 return VERR_NO_MEMORY;
1119 }
1120
1121 /** @todo Use an internal HGCM command for this operation, since
1122 * we now can run in a user-dedicated session. */
1123 int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
1124 GuestCtrlStreamObjects stdOut;
1125 int vrc = GuestProcessTool::runEx(this, procInfo, &stdOut, 1 /* cStrmOutObjects */, &vrcGuest);
1126 if (!GuestProcess::i_isGuestError(vrc))
1127 {
1128 GuestFsObjData objData;
1129 if (!stdOut.empty())
1130 {
1131 vrc = objData.FromMkTemp(stdOut.at(0));
1132 if (RT_FAILURE(vrc))
1133 {
1134 vrcGuest = vrc;
1135 if (prcGuest)
1136 *prcGuest = vrc;
1137 vrc = VERR_GSTCTL_GUEST_ERROR;
1138 }
1139 }
1140 else
1141 vrc = VERR_BROKEN_PIPE;
1142
1143 if (RT_SUCCESS(vrc))
1144 strName = objData.mName;
1145 }
1146 else if (prcGuest)
1147 *prcGuest = vrcGuest;
1148
1149 LogFlowThisFunc(("Returning vrc=%Rrc, vrcGuest=%Rrc\n", vrc, vrcGuest));
1150 return vrc;
1151}
1152
1153int GuestSession::i_directoryOpen(const GuestDirectoryOpenInfo &openInfo,
1154 ComObjPtr<GuestDirectory> &pDirectory, int *prcGuest)
1155{
1156 AssertPtrReturn(prcGuest, VERR_INVALID_POINTER);
1157
1158 LogFlowThisFunc(("strPath=%s, strPath=%s, uFlags=%x\n",
1159 openInfo.mPath.c_str(), openInfo.mFilter.c_str(), openInfo.mFlags));
1160
1161 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1162
1163 /* Register a new object ID. */
1164 uint32_t idObject;
1165 int rc = i_objectRegister(SESSIONOBJECTTYPE_DIRECTORY, &idObject);
1166 if (RT_FAILURE(rc))
1167 return rc;
1168
1169 /* Create the directory object. */
1170 HRESULT hr = pDirectory.createObject();
1171 if (FAILED(hr))
1172 return VERR_COM_UNEXPECTED;
1173
1174 Console *pConsole = mParent->i_getConsole();
1175 AssertPtr(pConsole);
1176
1177 int vrc = pDirectory->init(pConsole, this /* Parent */, idObject, openInfo);
1178 if (RT_FAILURE(vrc))
1179 return vrc;
1180
1181 /*
1182 * Since this is a synchronous guest call we have to
1183 * register the file object first, releasing the session's
1184 * lock and then proceed with the actual opening command
1185 * -- otherwise the file's opening callback would hang
1186 * because the session's lock still is in place.
1187 */
1188 try
1189 {
1190 /* Add the created directory to our map. */
1191 mData.mDirectories[idObject] = pDirectory;
1192
1193 LogFlowFunc(("Added new guest directory \"%s\" (Session: %RU32) (now total %zu directories)\n",
1194 openInfo.mPath.c_str(), mData.mSession.mID, mData.mDirectories.size()));
1195
1196 alock.release(); /* Release lock before firing off event. */
1197
1198 /** @todo Fire off a VBoxEventType_OnGuestDirectoryRegistered event? */
1199 }
1200 catch (std::bad_alloc &)
1201 {
1202 rc = VERR_NO_MEMORY;
1203 }
1204
1205 if (RT_SUCCESS(rc))
1206 {
1207 /* Nothing further to do here yet. */
1208 if (prcGuest)
1209 *prcGuest = VINF_SUCCESS;
1210 }
1211
1212 LogFlowFuncLeaveRC(vrc);
1213 return vrc;
1214}
1215
1216/**
1217 * Dispatches a host callback to its corresponding object.
1218 *
1219 * @return VBox status code. VERR_NOT_FOUND if no corresponding object was found.
1220 * @param pCtxCb Host callback context.
1221 * @param pSvcCb Service callback data.
1222 */
1223int GuestSession::i_dispatchToObject(PVBOXGUESTCTRLHOSTCBCTX pCtxCb, PVBOXGUESTCTRLHOSTCALLBACK pSvcCb)
1224{
1225 LogFlowFunc(("pCtxCb=%p, pSvcCb=%p\n", pCtxCb, pSvcCb));
1226
1227 AssertPtrReturn(pCtxCb, VERR_INVALID_POINTER);
1228 AssertPtrReturn(pSvcCb, VERR_INVALID_POINTER);
1229
1230 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
1231
1232 /*
1233 * Find the object.
1234 */
1235 int rc = VERR_NOT_FOUND;
1236 const uint32_t idObject = VBOX_GUESTCTRL_CONTEXTID_GET_OBJECT(pCtxCb->uContextID);
1237 SessionObjects::const_iterator itObjs = mData.mObjects.find(idObject);
1238 if (itObjs != mData.mObjects.end())
1239 {
1240 /* Set protocol version so that pSvcCb can be interpreted right. */
1241 pCtxCb->uProtocol = mData.mProtocolVersion;
1242
1243 switch (itObjs->second.enmType)
1244 {
1245 case SESSIONOBJECTTYPE_ANONYMOUS:
1246 rc = VERR_NOT_SUPPORTED;
1247 break;
1248
1249 case SESSIONOBJECTTYPE_SESSION:
1250 {
1251 alock.release();
1252
1253 rc = i_dispatchToThis(pCtxCb, pSvcCb);
1254 break;
1255 }
1256 case SESSIONOBJECTTYPE_DIRECTORY:
1257 {
1258 SessionDirectories::const_iterator itDir = mData.mDirectories.find(idObject);
1259 if (itDir != mData.mDirectories.end())
1260 {
1261 ComObjPtr<GuestDirectory> pDirectory(itDir->second);
1262 Assert(!pDirectory.isNull());
1263
1264 alock.release();
1265
1266 rc = pDirectory->i_callbackDispatcher(pCtxCb, pSvcCb);
1267 }
1268 break;
1269 }
1270 case SESSIONOBJECTTYPE_FILE:
1271 {
1272 SessionFiles::const_iterator itFile = mData.mFiles.find(idObject);
1273 if (itFile != mData.mFiles.end())
1274 {
1275 ComObjPtr<GuestFile> pFile(itFile->second);
1276 Assert(!pFile.isNull());
1277
1278 alock.release();
1279
1280 rc = pFile->i_callbackDispatcher(pCtxCb, pSvcCb);
1281 }
1282 break;
1283 }
1284 case SESSIONOBJECTTYPE_PROCESS:
1285 {
1286 SessionProcesses::const_iterator itProc = mData.mProcesses.find(idObject);
1287 if (itProc != mData.mProcesses.end())
1288 {
1289 ComObjPtr<GuestProcess> pProcess(itProc->second);
1290 Assert(!pProcess.isNull());
1291
1292 alock.release();
1293
1294 rc = pProcess->i_callbackDispatcher(pCtxCb, pSvcCb);
1295 }
1296 break;
1297 }
1298 default:
1299 AssertMsgFailed(("%d\n", itObjs->second.enmType));
1300 rc = VERR_INTERNAL_ERROR_4;
1301 break;
1302 }
1303 }
1304
1305 LogFlowFuncLeaveRC(rc);
1306 return rc;
1307}
1308
1309int GuestSession::i_dispatchToThis(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCb)
1310{
1311 AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER);
1312 AssertPtrReturn(pSvcCb, VERR_INVALID_POINTER);
1313
1314 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1315
1316 LogFlowThisFunc(("sessionID=%RU32, CID=%RU32, uMessage=%RU32, pSvcCb=%p\n",
1317 mData.mSession.mID, pCbCtx->uContextID, pCbCtx->uMessage, pSvcCb));
1318 int rc;
1319 switch (pCbCtx->uMessage)
1320 {
1321 case GUEST_MSG_DISCONNECTED:
1322 /** @todo Handle closing all guest objects. */
1323 rc = VERR_INTERNAL_ERROR;
1324 break;
1325
1326 case GUEST_MSG_SESSION_NOTIFY: /* Guest Additions >= 4.3.0. */
1327 {
1328 rc = i_onSessionStatusChange(pCbCtx, pSvcCb);
1329 break;
1330 }
1331
1332 default:
1333 rc = dispatchGeneric(pCbCtx, pSvcCb);
1334 break;
1335 }
1336
1337 LogFlowFuncLeaveRC(rc);
1338 return rc;
1339}
1340
1341/**
1342 * Validates and extracts file copy flags from a comma-separated string.
1343 *
1344 * @return COM status, error set on failure
1345 * @param strFlags String to extract flags from.
1346 * @param pfFlags Where to store the extracted (and validated) flags.
1347 */
1348HRESULT GuestSession::i_fileCopyFlagFromStr(const com::Utf8Str &strFlags, FileCopyFlag_T *pfFlags)
1349{
1350 unsigned fFlags = (unsigned)FileCopyFlag_None;
1351
1352 /* Validate and set flags. */
1353 if (strFlags.isNotEmpty())
1354 {
1355 const char *pszNext = strFlags.c_str();
1356 for (;;)
1357 {
1358 /* Find the next keyword, ignoring all whitespace. */
1359 pszNext = RTStrStripL(pszNext);
1360
1361 const char * const pszComma = strchr(pszNext, ',');
1362 size_t cchKeyword = pszComma ? pszComma - pszNext : strlen(pszNext);
1363 while (cchKeyword > 0 && RT_C_IS_SPACE(pszNext[cchKeyword - 1]))
1364 cchKeyword--;
1365
1366 if (cchKeyword > 0)
1367 {
1368 /* Convert keyword to flag. */
1369#define MATCH_KEYWORD(a_szKeyword) ( cchKeyword == sizeof(a_szKeyword) - 1U \
1370 && memcmp(pszNext, a_szKeyword, sizeof(a_szKeyword) - 1U) == 0)
1371 if (MATCH_KEYWORD("NoReplace"))
1372 fFlags |= (unsigned)FileCopyFlag_NoReplace;
1373 else if (MATCH_KEYWORD("FollowLinks"))
1374 fFlags |= (unsigned)FileCopyFlag_FollowLinks;
1375 else if (MATCH_KEYWORD("Update"))
1376 fFlags |= (unsigned)FileCopyFlag_Update;
1377 else
1378 return setError(E_INVALIDARG, tr("Invalid file copy flag: %.*s"), (int)cchKeyword, pszNext);
1379#undef MATCH_KEYWORD
1380 }
1381 if (!pszComma)
1382 break;
1383 pszNext = pszComma + 1;
1384 }
1385 }
1386
1387 if (pfFlags)
1388 *pfFlags = (FileCopyFlag_T)fFlags;
1389 return S_OK;
1390}
1391
1392inline bool GuestSession::i_fileExists(uint32_t uFileID, ComObjPtr<GuestFile> *pFile)
1393{
1394 SessionFiles::const_iterator it = mData.mFiles.find(uFileID);
1395 if (it != mData.mFiles.end())
1396 {
1397 if (pFile)
1398 *pFile = it->second;
1399 return true;
1400 }
1401 return false;
1402}
1403
1404/**
1405 * Unregisters a file object from a session.
1406 *
1407 * @return VBox status code. VERR_NOT_FOUND if the file is not registered (anymore).
1408 * @param pFile File object to unregister from session.
1409 */
1410int GuestSession::i_fileUnregister(GuestFile *pFile)
1411{
1412 AssertPtrReturn(pFile, VERR_INVALID_POINTER);
1413
1414 LogFlowThisFunc(("pFile=%p\n", pFile));
1415
1416 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1417
1418 const uint32_t idObject = pFile->getObjectID();
1419
1420 LogFlowFunc(("Removing file (objectID=%RU32) ...\n", idObject));
1421
1422 int rc = i_objectUnregister(idObject);
1423 if (RT_FAILURE(rc))
1424 return rc;
1425
1426 SessionFiles::iterator itFiles = mData.mFiles.find(idObject);
1427 AssertReturn(itFiles != mData.mFiles.end(), VERR_NOT_FOUND);
1428
1429 /* Make sure to consume the pointer before the one of the iterator gets released. */
1430 ComObjPtr<GuestFile> pFileConsumed = pFile;
1431
1432 LogFlowFunc(("Removing file ID=%RU32 (session %RU32, now total %zu files)\n",
1433 pFileConsumed->getObjectID(), mData.mSession.mID, mData.mFiles.size()));
1434
1435 rc = pFileConsumed->i_onRemove();
1436 AssertRCReturn(rc, rc);
1437
1438 mData.mFiles.erase(itFiles);
1439
1440 alock.release(); /* Release lock before firing off event. */
1441
1442 fireGuestFileRegisteredEvent(mEventSource, this, pFileConsumed, false /* Unregistered */);
1443
1444 pFileConsumed.setNull();
1445
1446 LogFlowFuncLeaveRC(rc);
1447 return rc;
1448}
1449
1450int GuestSession::i_fileRemove(const Utf8Str &strPath, int *prcGuest)
1451{
1452 LogFlowThisFunc(("strPath=%s\n", strPath.c_str()));
1453
1454 int vrc = VINF_SUCCESS;
1455
1456 GuestProcessStartupInfo procInfo;
1457 GuestProcessStream streamOut;
1458
1459 procInfo.mFlags = ProcessCreateFlag_WaitForStdOut;
1460 procInfo.mExecutable = Utf8Str(VBOXSERVICE_TOOL_RM);
1461
1462 try
1463 {
1464 procInfo.mArguments.push_back(procInfo.mExecutable); /* Set argv0. */
1465 procInfo.mArguments.push_back(Utf8Str("--machinereadable"));
1466 procInfo.mArguments.push_back("--"); /* strPath could be '--help', which is a valid filename. */
1467 procInfo.mArguments.push_back(strPath); /* The file we want to remove. */
1468 }
1469 catch (std::bad_alloc &)
1470 {
1471 vrc = VERR_NO_MEMORY;
1472 }
1473
1474 if (RT_SUCCESS(vrc))
1475 vrc = GuestProcessTool::run(this, procInfo, prcGuest);
1476
1477 LogFlowFuncLeaveRC(vrc);
1478 return vrc;
1479}
1480
1481int GuestSession::i_fileOpenEx(const com::Utf8Str &aPath, FileAccessMode_T aAccessMode, FileOpenAction_T aOpenAction,
1482 FileSharingMode_T aSharingMode, ULONG aCreationMode, const std::vector<FileOpenExFlag_T> &aFlags,
1483 ComObjPtr<GuestFile> &pFile, int *prcGuest)
1484{
1485 GuestFileOpenInfo openInfo;
1486 RT_ZERO(openInfo);
1487
1488 openInfo.mFilename = aPath;
1489 openInfo.mCreationMode = aCreationMode;
1490 openInfo.mAccessMode = aAccessMode;
1491 openInfo.mOpenAction = aOpenAction;
1492 openInfo.mSharingMode = aSharingMode;
1493
1494 /* Combine and validate flags. */
1495 uint32_t fOpenEx = 0;
1496 for (size_t i = 0; i < aFlags.size(); i++)
1497 fOpenEx = aFlags[i];
1498 if (fOpenEx)
1499 return VERR_INVALID_PARAMETER; /* FileOpenExFlag not implemented yet. */
1500 openInfo.mfOpenEx = fOpenEx;
1501
1502 return i_fileOpen(openInfo, pFile, prcGuest);
1503}
1504
1505int GuestSession::i_fileOpen(const GuestFileOpenInfo &openInfo, ComObjPtr<GuestFile> &pFile, int *prcGuest)
1506{
1507 LogFlowThisFunc(("strFile=%s, enmAccessMode=0x%x, enmOpenAction=0x%x, uCreationMode=%RU32, mfOpenEx=%RU32\n",
1508 openInfo.mFilename.c_str(), openInfo.mAccessMode, openInfo.mOpenAction, openInfo.mCreationMode,
1509 openInfo.mfOpenEx));
1510
1511 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1512
1513 /* Guest Additions < 4.3 don't support handling guest files, skip. */
1514 if (mData.mProtocolVersion < 2)
1515 {
1516 if (prcGuest)
1517 *prcGuest = VERR_NOT_SUPPORTED;
1518 return VERR_GSTCTL_GUEST_ERROR;
1519 }
1520
1521 /* Register a new object ID. */
1522 uint32_t idObject;
1523 int rc = i_objectRegister(SESSIONOBJECTTYPE_FILE, &idObject);
1524 if (RT_FAILURE(rc))
1525 return rc;
1526
1527 /* Create the directory object. */
1528 HRESULT hr = pFile.createObject();
1529 if (FAILED(hr))
1530 return VERR_COM_UNEXPECTED;
1531
1532 Console *pConsole = mParent->i_getConsole();
1533 AssertPtr(pConsole);
1534
1535 rc = pFile->init(pConsole, this /* GuestSession */, idObject, openInfo);
1536 if (RT_FAILURE(rc))
1537 return rc;
1538
1539 /*
1540 * Since this is a synchronous guest call we have to
1541 * register the file object first, releasing the session's
1542 * lock and then proceed with the actual opening command
1543 * -- otherwise the file's opening callback would hang
1544 * because the session's lock still is in place.
1545 */
1546 try
1547 {
1548 /* Add the created file to our vector. */
1549 mData.mFiles[idObject] = pFile;
1550
1551 LogFlowFunc(("Added new guest file \"%s\" (Session: %RU32) (now total %zu files)\n",
1552 openInfo.mFilename.c_str(), mData.mSession.mID, mData.mFiles.size()));
1553
1554 alock.release(); /* Release lock before firing off event. */
1555
1556 fireGuestFileRegisteredEvent(mEventSource, this, pFile, true /* Registered */);
1557 }
1558 catch (std::bad_alloc &)
1559 {
1560 rc = VERR_NO_MEMORY;
1561 }
1562
1563 if (RT_SUCCESS(rc))
1564 {
1565 int rcGuest;
1566 rc = pFile->i_openFile(30 * 1000 /* 30s timeout */, &rcGuest);
1567 if ( rc == VERR_GSTCTL_GUEST_ERROR
1568 && prcGuest)
1569 {
1570 *prcGuest = rcGuest;
1571 }
1572 }
1573
1574 LogFlowFuncLeaveRC(rc);
1575 return rc;
1576}
1577
1578/**
1579 * Queries information from a file on the guest.
1580 *
1581 * @returns IPRT status code. VERR_NOT_A_FILE if the queried file system object on the guest is not a file,
1582 * or VERR_GSTCTL_GUEST_ERROR if prcGuest contains more error information from the guest.
1583 * @param strPath Absolute path of file to query information for.
1584 * @param fFollowSymlinks Whether or not to follow symbolic links on the guest.
1585 * @param objData Where to store the acquired information.
1586 * @param prcGuest Where to store the guest rc. Optional.
1587 */
1588int GuestSession::i_fileQueryInfo(const Utf8Str &strPath, bool fFollowSymlinks, GuestFsObjData &objData, int *prcGuest)
1589{
1590 LogFlowThisFunc(("strPath=%s fFollowSymlinks=%RTbool\n", strPath.c_str(), fFollowSymlinks));
1591
1592 int vrc = i_fsQueryInfo(strPath, fFollowSymlinks, objData, prcGuest);
1593 if (RT_SUCCESS(vrc))
1594 {
1595 vrc = objData.mType == FsObjType_File
1596 ? VINF_SUCCESS : VERR_NOT_A_FILE;
1597 }
1598
1599 LogFlowFuncLeaveRC(vrc);
1600 return vrc;
1601}
1602
1603int GuestSession::i_fileQuerySize(const Utf8Str &strPath, bool fFollowSymlinks, int64_t *pllSize, int *prcGuest)
1604{
1605 AssertPtrReturn(pllSize, VERR_INVALID_POINTER);
1606
1607 GuestFsObjData objData;
1608 int vrc = i_fileQueryInfo(strPath, fFollowSymlinks, objData, prcGuest);
1609 if (RT_SUCCESS(vrc))
1610 *pllSize = objData.mObjectSize;
1611
1612 return vrc;
1613}
1614
1615/**
1616 * Queries information of a file system object (file, directory, ...).
1617 *
1618 * @return IPRT status code.
1619 * @param strPath Path to file system object to query information for.
1620 * @param fFollowSymlinks Whether to follow symbolic links or not.
1621 * @param objData Where to return the file system object data, if found.
1622 * @param prcGuest Guest rc, when returning VERR_GSTCTL_GUEST_ERROR.
1623 * Any other return code indicates some host side error.
1624 */
1625int GuestSession::i_fsQueryInfo(const Utf8Str &strPath, bool fFollowSymlinks, GuestFsObjData &objData, int *prcGuest)
1626{
1627 LogFlowThisFunc(("strPath=%s\n", strPath.c_str()));
1628
1629 /** @todo Merge this with IGuestFile::queryInfo(). */
1630 GuestProcessStartupInfo procInfo;
1631 procInfo.mFlags = ProcessCreateFlag_WaitForStdOut;
1632 try
1633 {
1634 procInfo.mExecutable = Utf8Str(VBOXSERVICE_TOOL_STAT);
1635 procInfo.mArguments.push_back(procInfo.mExecutable); /* Set argv0. */
1636 procInfo.mArguments.push_back(Utf8Str("--machinereadable"));
1637 if (fFollowSymlinks)
1638 procInfo.mArguments.push_back(Utf8Str("-L"));
1639 procInfo.mArguments.push_back("--"); /* strPath could be '--help', which is a valid filename. */
1640 procInfo.mArguments.push_back(strPath);
1641 }
1642 catch (std::bad_alloc &)
1643 {
1644 Log(("Out of memory!\n"));
1645 return VERR_NO_MEMORY;
1646 }
1647
1648 int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS;
1649 GuestCtrlStreamObjects stdOut;
1650 int vrc = GuestProcessTool::runEx(this, procInfo,
1651 &stdOut, 1 /* cStrmOutObjects */,
1652 &vrcGuest);
1653 if (!GuestProcess::i_isGuestError(vrc))
1654 {
1655 if (!stdOut.empty())
1656 {
1657 vrc = objData.FromStat(stdOut.at(0));
1658 if (RT_FAILURE(vrc))
1659 {
1660 vrcGuest = vrc;
1661 if (prcGuest)
1662 *prcGuest = vrc;
1663 vrc = VERR_GSTCTL_GUEST_ERROR;
1664 }
1665 }
1666 else
1667 vrc = VERR_BROKEN_PIPE;
1668 }
1669 else if (prcGuest)
1670 *prcGuest = vrcGuest;
1671
1672 LogFlowThisFunc(("Returning vrc=%Rrc, vrcGuest=%Rrc\n", vrc, vrcGuest));
1673 return vrc;
1674}
1675
1676const GuestCredentials& GuestSession::i_getCredentials(void)
1677{
1678 return mData.mCredentials;
1679}
1680
1681Utf8Str GuestSession::i_getName(void)
1682{
1683 return mData.mSession.mName;
1684}
1685
1686/* static */
1687Utf8Str GuestSession::i_guestErrorToString(int rcGuest)
1688{
1689 Utf8Str strError;
1690
1691 /** @todo pData->u32Flags: int vs. uint32 -- IPRT errors are *negative* !!! */
1692 switch (rcGuest)
1693 {
1694 case VERR_INVALID_VM_HANDLE:
1695 strError += Utf8StrFmt(tr("VMM device is not available (is the VM running?)"));
1696 break;
1697
1698 case VERR_HGCM_SERVICE_NOT_FOUND:
1699 strError += Utf8StrFmt(tr("The guest execution service is not available"));
1700 break;
1701
1702 case VERR_ACCOUNT_RESTRICTED:
1703 strError += Utf8StrFmt(tr("The specified user account on the guest is restricted and can't be used to logon"));
1704 break;
1705
1706 case VERR_AUTHENTICATION_FAILURE:
1707 strError += Utf8StrFmt(tr("The specified user was not able to logon on guest"));
1708 break;
1709
1710 case VERR_TIMEOUT:
1711 strError += Utf8StrFmt(tr("The guest did not respond within time"));
1712 break;
1713
1714 case VERR_CANCELLED:
1715 strError += Utf8StrFmt(tr("The session operation was canceled"));
1716 break;
1717
1718 case VERR_GSTCTL_MAX_CID_OBJECTS_REACHED:
1719 strError += Utf8StrFmt(tr("Maximum number of concurrent guest processes has been reached"));
1720 break;
1721
1722 case VERR_NOT_FOUND:
1723 strError += Utf8StrFmt(tr("The guest execution service is not ready (yet)"));
1724 break;
1725
1726 default:
1727 strError += Utf8StrFmt("%Rrc", rcGuest);
1728 break;
1729 }
1730
1731 return strError;
1732}
1733
1734/**
1735 * Returns whether the session is in a started state or not.
1736 *
1737 * @returns \c true if in a started state, or \c false if not.
1738 */
1739bool GuestSession::i_isStarted(void) const
1740{
1741 return (mData.mStatus == GuestSessionStatus_Started);
1742}
1743
1744/**
1745 * Checks if this session is ready state where it can handle
1746 * all session-bound actions (like guest processes, guest files).
1747 * Only used by official API methods. Will set an external
1748 * error when not ready.
1749 */
1750HRESULT GuestSession::i_isStartedExternal(void)
1751{
1752 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
1753
1754 /** @todo Be a bit more informative. */
1755 if (!i_isStarted())
1756 return setError(E_UNEXPECTED, tr("Session is not in started state"));
1757
1758 return S_OK;
1759}
1760
1761/**
1762 * Returns whether the session is in a terminated state or not.
1763 *
1764 * @returns \c true if in a terminated state, or \c false if not.
1765 */
1766bool GuestSession::i_isTerminated(void) const
1767{
1768 switch (mData.mStatus)
1769 {
1770 case GuestSessionStatus_Terminated:
1771 RT_FALL_THROUGH();
1772 case GuestSessionStatus_TimedOutKilled:
1773 RT_FALL_THROUGH();
1774 case GuestSessionStatus_TimedOutAbnormally:
1775 RT_FALL_THROUGH();
1776 case GuestSessionStatus_Down:
1777 RT_FALL_THROUGH();
1778 case GuestSessionStatus_Error:
1779 return true;
1780
1781 default:
1782 break;
1783 }
1784
1785 return false;
1786}
1787
1788/**
1789 * Called by IGuest right before this session gets removed from
1790 * the public session list.
1791 */
1792int GuestSession::i_onRemove(void)
1793{
1794 LogFlowThisFuncEnter();
1795
1796 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1797
1798 int vrc = VINF_SUCCESS;
1799
1800 /*
1801 * Note: The event source stuff holds references to this object,
1802 * so make sure that this is cleaned up *before* calling uninit.
1803 */
1804 if (!mEventSource.isNull())
1805 {
1806 mEventSource->UnregisterListener(mLocalListener);
1807
1808 mLocalListener.setNull();
1809 unconst(mEventSource).setNull();
1810 }
1811
1812 LogFlowFuncLeaveRC(vrc);
1813 return vrc;
1814}
1815
1816/** No locking! */
1817int GuestSession::i_onSessionStatusChange(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData)
1818{
1819 AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER);
1820 /* pCallback is optional. */
1821 AssertPtrReturn(pSvcCbData, VERR_INVALID_POINTER);
1822
1823 if (pSvcCbData->mParms < 3)
1824 return VERR_INVALID_PARAMETER;
1825
1826 CALLBACKDATA_SESSION_NOTIFY dataCb;
1827 /* pSvcCb->mpaParms[0] always contains the context ID. */
1828 int vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[1], &dataCb.uType);
1829 AssertRCReturn(vrc, vrc);
1830 vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[2], &dataCb.uResult);
1831 AssertRCReturn(vrc, vrc);
1832
1833 LogFlowThisFunc(("ID=%RU32, uType=%RU32, rcGuest=%Rrc\n",
1834 mData.mSession.mID, dataCb.uType, dataCb.uResult));
1835
1836 GuestSessionStatus_T sessionStatus = GuestSessionStatus_Undefined;
1837
1838 int rcGuest = dataCb.uResult; /** @todo uint32_t vs. int. */
1839 switch (dataCb.uType)
1840 {
1841 case GUEST_SESSION_NOTIFYTYPE_ERROR:
1842 sessionStatus = GuestSessionStatus_Error;
1843 break;
1844
1845 case GUEST_SESSION_NOTIFYTYPE_STARTED:
1846 sessionStatus = GuestSessionStatus_Started;
1847#if 0 /** @todo If we get some environment stuff along with this kind notification: */
1848 const char *pszzEnvBlock = ...;
1849 uint32_t cbEnvBlock = ...;
1850 if (!mData.mpBaseEnvironment)
1851 {
1852 GuestEnvironment *pBaseEnv;
1853 try { pBaseEnv = new GuestEnvironment(); } catch (std::bad_alloc &) { pBaseEnv = NULL; }
1854 if (pBaseEnv)
1855 {
1856 int vrc = pBaseEnv->initNormal();
1857 if (RT_SUCCESS(vrc))
1858 vrc = pBaseEnv->copyUtf8Block(pszzEnvBlock, cbEnvBlock);
1859 if (RT_SUCCESS(vrc))
1860 mData.mpBaseEnvironment = pBaseEnv;
1861 else
1862 pBaseEnv->release();
1863 }
1864 }
1865#endif
1866 break;
1867
1868 case GUEST_SESSION_NOTIFYTYPE_TEN:
1869 case GUEST_SESSION_NOTIFYTYPE_TES:
1870 case GUEST_SESSION_NOTIFYTYPE_TEA:
1871 sessionStatus = GuestSessionStatus_Terminated;
1872 break;
1873
1874 case GUEST_SESSION_NOTIFYTYPE_TOK:
1875 sessionStatus = GuestSessionStatus_TimedOutKilled;
1876 break;
1877
1878 case GUEST_SESSION_NOTIFYTYPE_TOA:
1879 sessionStatus = GuestSessionStatus_TimedOutAbnormally;
1880 break;
1881
1882 case GUEST_SESSION_NOTIFYTYPE_DWN:
1883 sessionStatus = GuestSessionStatus_Down;
1884 break;
1885
1886 case GUEST_SESSION_NOTIFYTYPE_UNDEFINED:
1887 default:
1888 vrc = VERR_NOT_SUPPORTED;
1889 break;
1890 }
1891
1892 if (RT_SUCCESS(vrc))
1893 {
1894 if (RT_FAILURE(rcGuest))
1895 sessionStatus = GuestSessionStatus_Error;
1896 }
1897
1898 /* Set the session status. */
1899 if (RT_SUCCESS(vrc))
1900 vrc = i_setSessionStatus(sessionStatus, rcGuest);
1901
1902 LogFlowThisFunc(("ID=%RU32, rcGuest=%Rrc\n", mData.mSession.mID, rcGuest));
1903
1904 LogFlowFuncLeaveRC(vrc);
1905 return vrc;
1906}
1907
1908PathStyle_T GuestSession::i_getPathStyle(void)
1909{
1910 PathStyle_T enmPathStyle;
1911
1912 VBOXOSTYPE enmOsType = mParent->i_getGuestOSType();
1913 if (enmOsType < VBOXOSTYPE_DOS)
1914 {
1915 LogFlowFunc(("returns PathStyle_Unknown\n"));
1916 enmPathStyle = PathStyle_Unknown;
1917 }
1918 else if (enmOsType < VBOXOSTYPE_Linux)
1919 {
1920 LogFlowFunc(("returns PathStyle_DOS\n"));
1921 enmPathStyle = PathStyle_DOS;
1922 }
1923 else
1924 {
1925 LogFlowFunc(("returns PathStyle_UNIX\n"));
1926 enmPathStyle = PathStyle_UNIX;
1927 }
1928
1929 return enmPathStyle;
1930}
1931
1932int GuestSession::i_startSession(int *prcGuest)
1933{
1934 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
1935
1936 LogFlowThisFunc(("mID=%RU32, mName=%s, uProtocolVersion=%RU32, openFlags=%x, openTimeoutMS=%RU32\n",
1937 mData.mSession.mID, mData.mSession.mName.c_str(), mData.mProtocolVersion,
1938 mData.mSession.mOpenFlags, mData.mSession.mOpenTimeoutMS));
1939
1940 /* Guest Additions < 4.3 don't support opening dedicated
1941 guest sessions. Simply return success here. */
1942 if (mData.mProtocolVersion < 2)
1943 {
1944 mData.mStatus = GuestSessionStatus_Started;
1945
1946 LogFlowThisFunc(("Installed Guest Additions don't support opening dedicated sessions, skipping\n"));
1947 return VINF_SUCCESS;
1948 }
1949
1950 if (mData.mStatus != GuestSessionStatus_Undefined)
1951 return VINF_SUCCESS;
1952
1953 /** @todo mData.mSession.uFlags validation. */
1954
1955 /* Set current session status. */
1956 mData.mStatus = GuestSessionStatus_Starting;
1957 mData.mRC = VINF_SUCCESS; /* Clear previous error, if any. */
1958
1959 int vrc;
1960
1961 GuestWaitEvent *pEvent = NULL;
1962 GuestEventTypes eventTypes;
1963 try
1964 {
1965 eventTypes.push_back(VBoxEventType_OnGuestSessionStateChanged);
1966
1967 vrc = registerWaitEventEx(mData.mSession.mID, mData.mObjectID, eventTypes, &pEvent);
1968 }
1969 catch (std::bad_alloc &)
1970 {
1971 vrc = VERR_NO_MEMORY;
1972 }
1973
1974 if (RT_FAILURE(vrc))
1975 return vrc;
1976
1977 VBOXHGCMSVCPARM paParms[8];
1978
1979 int i = 0;
1980 HGCMSvcSetU32(&paParms[i++], pEvent->ContextID());
1981 HGCMSvcSetU32(&paParms[i++], mData.mProtocolVersion);
1982 HGCMSvcSetPv(&paParms[i++], (void*)mData.mCredentials.mUser.c_str(),
1983 (ULONG)mData.mCredentials.mUser.length() + 1);
1984 HGCMSvcSetPv(&paParms[i++], (void*)mData.mCredentials.mPassword.c_str(),
1985 (ULONG)mData.mCredentials.mPassword.length() + 1);
1986 HGCMSvcSetPv(&paParms[i++], (void*)mData.mCredentials.mDomain.c_str(),
1987 (ULONG)mData.mCredentials.mDomain.length() + 1);
1988 HGCMSvcSetU32(&paParms[i++], mData.mSession.mOpenFlags);
1989
1990 alock.release(); /* Drop write lock before sending. */
1991
1992 vrc = i_sendMessage(HOST_MSG_SESSION_CREATE, i, paParms, VBOX_GUESTCTRL_DST_ROOT_SVC);
1993 if (RT_SUCCESS(vrc))
1994 {
1995 vrc = i_waitForStatusChange(pEvent, GuestSessionWaitForFlag_Start,
1996 30 * 1000 /* 30s timeout */,
1997 NULL /* Session status */, prcGuest);
1998 }
1999 else
2000 {
2001 /*
2002 * Unable to start guest session - update its current state.
2003 * Since there is no (official API) way to recover a failed guest session
2004 * this also marks the end state. Internally just calling this
2005 * same function again will work though.
2006 */
2007 mData.mStatus = GuestSessionStatus_Error;
2008 mData.mRC = vrc;
2009 }
2010
2011 unregisterWaitEvent(pEvent);
2012
2013 LogFlowFuncLeaveRC(vrc);
2014 return vrc;
2015}
2016
2017/**
2018 * Starts the guest session asynchronously in a separate thread.
2019 *
2020 * @returns IPRT status code.
2021 */
2022int GuestSession::i_startSessionAsync(void)
2023{
2024 LogFlowThisFuncEnter();
2025
2026 int vrc;
2027 GuestSessionTaskInternalStart* pTask = NULL;
2028 try
2029 {
2030 pTask = new GuestSessionTaskInternalStart(this);
2031 if (!pTask->isOk())
2032 {
2033 delete pTask;
2034 LogFlow(("GuestSession: Could not create GuestSessionTaskInternalStart object\n"));
2035 throw VERR_MEMOBJ_INIT_FAILED;
2036 }
2037
2038 /* Asynchronously open the session on the guest by kicking off a worker thread. */
2039 /* Note: This function deletes pTask in case of exceptions, so there is no need in the call of delete operator. */
2040 HRESULT hrc = pTask->createThread();
2041 vrc = Global::vboxStatusCodeFromCOM(hrc);
2042 }
2043 catch(std::bad_alloc &)
2044 {
2045 vrc = VERR_NO_MEMORY;
2046 }
2047 catch(int eVRC)
2048 {
2049 vrc = eVRC;
2050 LogFlow(("GuestSession: Could not create thread for GuestSessionTaskInternalOpen task %Rrc\n", vrc));
2051 }
2052
2053 LogFlowFuncLeaveRC(vrc);
2054 return vrc;
2055}
2056
2057/**
2058 * Static function to start a guest session asynchronously.
2059 *
2060 * @returns IPRT status code.
2061 * @param pTask Task object to use for starting the guest session.
2062 */
2063/* static */
2064int GuestSession::i_startSessionThreadTask(GuestSessionTaskInternalStart *pTask)
2065{
2066 LogFlowFunc(("pTask=%p\n", pTask));
2067 AssertPtr(pTask);
2068
2069 const ComObjPtr<GuestSession> pSession(pTask->Session());
2070 Assert(!pSession.isNull());
2071
2072 AutoCaller autoCaller(pSession);
2073 if (FAILED(autoCaller.rc()))
2074 return VERR_COM_INVALID_OBJECT_STATE;
2075
2076 int vrc = pSession->i_startSession(NULL /* Guest rc, ignored */);
2077 /* Nothing to do here anymore. */
2078
2079 LogFlowFuncLeaveRC(vrc);
2080 return vrc;
2081}
2082
2083/**
2084 * Registers an object with the session, i.e. allocates an object ID.
2085 *
2086 * @return VBox status code.
2087 * @retval VERR_GSTCTL_MAX_OBJECTS_REACHED if the maximum of concurrent objects
2088 * is reached.
2089 * @param enmType Session object type to register.
2090 * @param pidObject Where to return the object ID on success.
2091 */
2092int GuestSession::i_objectRegister(SESSIONOBJECTTYPE enmType, uint32_t *pidObject)
2093{
2094 /*
2095 * Pick a random bit as starting point. If it's in use, search forward
2096 * for a free one, wrapping around. We've reserved both the zero'th and
2097 * max-1 IDs (see Data constructor).
2098 */
2099 uint32_t idObject = RTRandU32Ex(1, VBOX_GUESTCTRL_MAX_OBJECTS - 2);
2100 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
2101 if (!ASMBitTestAndSet(&mData.bmObjectIds[0], idObject))
2102 { /* likely */ }
2103 else if (mData.mObjects.size() < VBOX_GUESTCTRL_MAX_OBJECTS - 2 /* First and last are not used */)
2104 {
2105 /* Forward search. */
2106 int iHit = ASMBitNextClear(&mData.bmObjectIds[0], VBOX_GUESTCTRL_MAX_OBJECTS, idObject);
2107 if (iHit < 0)
2108 iHit = ASMBitFirstClear(&mData.bmObjectIds[0], VBOX_GUESTCTRL_MAX_OBJECTS);
2109 AssertLogRelMsgReturn(iHit >= 0, ("object count: %#zu\n", mData.mObjects.size()), VERR_GSTCTL_MAX_CID_OBJECTS_REACHED);
2110 idObject = iHit;
2111 AssertLogRelMsgReturn(!ASMBitTestAndSet(&mData.bmObjectIds[0], idObject), ("idObject=%#x\n", idObject), VERR_INTERNAL_ERROR_2);
2112 }
2113 else
2114 {
2115 LogFunc(("Maximum number of objects reached (enmType=%RU32, %zu objects)\n", enmType, mData.mObjects.size()));
2116 return VERR_GSTCTL_MAX_CID_OBJECTS_REACHED;
2117 }
2118
2119 Log2Func(("enmType=%RU32 -> idObject=%RU32 (%zu objects)\n", enmType, idObject, mData.mObjects.size()));
2120
2121 try
2122 {
2123 mData.mObjects[idObject].enmType = enmType;
2124 mData.mObjects[idObject].msBirth = RTTimeMilliTS();
2125 }
2126 catch (std::bad_alloc &)
2127 {
2128 ASMBitClear(&mData.bmObjectIds[0], idObject);
2129 return VERR_NO_MEMORY;
2130 }
2131
2132 alock.release();
2133
2134 *pidObject = idObject;
2135 return VINF_SUCCESS;
2136}
2137
2138/**
2139 * Unregisters an object from a session.
2140 *
2141 * @retval VINF_SUCCESS on success.
2142 * @retval VERR_NOT_FOUND if the object ID was not found.
2143 * @param idObject Object ID to unregister.
2144 */
2145int GuestSession::i_objectUnregister(uint32_t idObject)
2146{
2147 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
2148
2149 int rc = VINF_SUCCESS;
2150 AssertMsgStmt(ASMBitTestAndClear(&mData.bmObjectIds, idObject), ("idObject=%#x\n", idObject), rc = VERR_NOT_FOUND);
2151
2152 SessionObjects::iterator ItObj = mData.mObjects.find(idObject);
2153 AssertMsgReturn(ItObj != mData.mObjects.end(), ("idObject=%#x\n", idObject), VERR_NOT_FOUND);
2154 mData.mObjects.erase(ItObj);
2155
2156 return rc;
2157}
2158
2159int GuestSession::i_pathRename(const Utf8Str &strSource, const Utf8Str &strDest, uint32_t uFlags, int *prcGuest)
2160{
2161 AssertReturn(!(uFlags & ~PATHRENAME_FLAG_VALID_MASK), VERR_INVALID_PARAMETER);
2162
2163 LogFlowThisFunc(("strSource=%s, strDest=%s, uFlags=0x%x\n",
2164 strSource.c_str(), strDest.c_str(), uFlags));
2165
2166 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
2167
2168 GuestWaitEvent *pEvent = NULL;
2169 int vrc = registerWaitEvent(mData.mSession.mID, mData.mObjectID, &pEvent);
2170 if (RT_FAILURE(vrc))
2171 return vrc;
2172
2173 /* Prepare HGCM call. */
2174 VBOXHGCMSVCPARM paParms[8];
2175 int i = 0;
2176 HGCMSvcSetU32(&paParms[i++], pEvent->ContextID());
2177 HGCMSvcSetPv(&paParms[i++], (void*)strSource.c_str(),
2178 (ULONG)strSource.length() + 1);
2179 HGCMSvcSetPv(&paParms[i++], (void*)strDest.c_str(),
2180 (ULONG)strDest.length() + 1);
2181 HGCMSvcSetU32(&paParms[i++], uFlags);
2182
2183 alock.release(); /* Drop write lock before sending. */
2184
2185 vrc = i_sendMessage(HOST_MSG_PATH_RENAME, i, paParms);
2186 if (RT_SUCCESS(vrc))
2187 {
2188 vrc = pEvent->Wait(30 * 1000);
2189 if ( vrc == VERR_GSTCTL_GUEST_ERROR
2190 && prcGuest)
2191 *prcGuest = pEvent->GuestResult();
2192 }
2193
2194 unregisterWaitEvent(pEvent);
2195
2196 LogFlowFuncLeaveRC(vrc);
2197 return vrc;
2198}
2199
2200/**
2201 * Returns the user's absolute documents path, if any.
2202 *
2203 * @return VBox status code.
2204 * @param strPath Where to store the user's document path.
2205 * @param prcGuest Guest rc, when returning VERR_GSTCTL_GUEST_ERROR.
2206 * Any other return code indicates some host side error.
2207 */
2208int GuestSession::i_pathUserDocuments(Utf8Str &strPath, int *prcGuest)
2209{
2210 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
2211
2212 /** @todo Cache the user's document path? */
2213
2214 GuestWaitEvent *pEvent = NULL;
2215 int vrc = registerWaitEvent(mData.mSession.mID, mData.mObjectID, &pEvent);
2216 if (RT_FAILURE(vrc))
2217 return vrc;
2218
2219 /* Prepare HGCM call. */
2220 VBOXHGCMSVCPARM paParms[2];
2221 int i = 0;
2222 HGCMSvcSetU32(&paParms[i++], pEvent->ContextID());
2223
2224 alock.release(); /* Drop write lock before sending. */
2225
2226 vrc = i_sendMessage(HOST_MSG_PATH_USER_DOCUMENTS, i, paParms);
2227 if (RT_SUCCESS(vrc))
2228 {
2229 vrc = pEvent->Wait(30 * 1000);
2230 if (RT_SUCCESS(vrc))
2231 {
2232 strPath = pEvent->Payload().ToString();
2233 }
2234 else
2235 {
2236 if (vrc == VERR_GSTCTL_GUEST_ERROR)
2237 {
2238 if (prcGuest)
2239 *prcGuest = pEvent->GuestResult();
2240 }
2241 }
2242 }
2243
2244 unregisterWaitEvent(pEvent);
2245
2246 LogFlowFuncLeaveRC(vrc);
2247 return vrc;
2248}
2249
2250/**
2251 * Returns the user's absolute home path, if any.
2252 *
2253 * @return VBox status code.
2254 * @param strPath Where to store the user's home path.
2255 * @param prcGuest Guest rc, when returning VERR_GSTCTL_GUEST_ERROR.
2256 * Any other return code indicates some host side error.
2257 */
2258int GuestSession::i_pathUserHome(Utf8Str &strPath, int *prcGuest)
2259{
2260 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
2261
2262 /** @todo Cache the user's home path? */
2263
2264 GuestWaitEvent *pEvent = NULL;
2265 int vrc = registerWaitEvent(mData.mSession.mID, mData.mObjectID, &pEvent);
2266 if (RT_FAILURE(vrc))
2267 return vrc;
2268
2269 /* Prepare HGCM call. */
2270 VBOXHGCMSVCPARM paParms[2];
2271 int i = 0;
2272 HGCMSvcSetU32(&paParms[i++], pEvent->ContextID());
2273
2274 alock.release(); /* Drop write lock before sending. */
2275
2276 vrc = i_sendMessage(HOST_MSG_PATH_USER_HOME, i, paParms);
2277 if (RT_SUCCESS(vrc))
2278 {
2279 vrc = pEvent->Wait(30 * 1000);
2280 if (RT_SUCCESS(vrc))
2281 {
2282 strPath = pEvent->Payload().ToString();
2283 }
2284 else
2285 {
2286 if (vrc == VERR_GSTCTL_GUEST_ERROR)
2287 {
2288 if (prcGuest)
2289 *prcGuest = pEvent->GuestResult();
2290 }
2291 }
2292 }
2293
2294 unregisterWaitEvent(pEvent);
2295
2296 LogFlowFuncLeaveRC(vrc);
2297 return vrc;
2298}
2299
2300/**
2301 * Unregisters a process object from a session.
2302 *
2303 * @return VBox status code. VERR_NOT_FOUND if the process is not registered (anymore).
2304 * @param pProcess Process object to unregister from session.
2305 */
2306int GuestSession::i_processUnregister(GuestProcess *pProcess)
2307{
2308 AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
2309
2310 LogFlowThisFunc(("pProcess=%p\n", pProcess));
2311
2312 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
2313
2314 const uint32_t idObject = pProcess->getObjectID();
2315
2316 LogFlowFunc(("Removing process (objectID=%RU32) ...\n", idObject));
2317
2318 int rc = i_objectUnregister(idObject);
2319 if (RT_FAILURE(rc))
2320 return rc;
2321
2322 SessionProcesses::iterator itProcs = mData.mProcesses.find(idObject);
2323 AssertReturn(itProcs != mData.mProcesses.end(), VERR_NOT_FOUND);
2324
2325 /* Make sure to consume the pointer before the one of the iterator gets released. */
2326 ComObjPtr<GuestProcess> pProc = pProcess;
2327
2328 ULONG uPID;
2329 HRESULT hr = pProc->COMGETTER(PID)(&uPID);
2330 ComAssertComRC(hr);
2331
2332 LogFlowFunc(("Removing process ID=%RU32 (session %RU32, guest PID %RU32, now total %zu processes)\n",
2333 idObject, mData.mSession.mID, uPID, mData.mProcesses.size()));
2334
2335 rc = pProcess->i_onRemove();
2336 AssertRCReturn(rc, rc);
2337
2338 mData.mProcesses.erase(itProcs);
2339
2340 alock.release(); /* Release lock before firing off event. */
2341
2342 fireGuestProcessRegisteredEvent(mEventSource, this /* Session */, pProc, uPID, false /* Process unregistered */);
2343
2344 pProc.setNull();
2345
2346 LogFlowFuncLeaveRC(rc);
2347 return rc;
2348}
2349
2350/**
2351 * Creates but does *not* start the process yet.
2352 *
2353 * See GuestProcess::startProcess() or GuestProcess::startProcessAsync() for
2354 * starting the process.
2355 *
2356 * @return IPRT status code.
2357 * @param procInfo
2358 * @param pProcess
2359 */
2360int GuestSession::i_processCreateEx(GuestProcessStartupInfo &procInfo, ComObjPtr<GuestProcess> &pProcess)
2361{
2362 LogFlowFunc(("mExe=%s, mFlags=%x, mTimeoutMS=%RU32\n",
2363 procInfo.mExecutable.c_str(), procInfo.mFlags, procInfo.mTimeoutMS));
2364#ifdef DEBUG
2365 if (procInfo.mArguments.size())
2366 {
2367 LogFlowFunc(("Arguments:"));
2368 ProcessArguments::const_iterator it = procInfo.mArguments.begin();
2369 while (it != procInfo.mArguments.end())
2370 {
2371 LogFlow((" %s", (*it).c_str()));
2372 ++it;
2373 }
2374 LogFlow(("\n"));
2375 }
2376#endif
2377
2378 /* Validate flags. */
2379 if (procInfo.mFlags)
2380 {
2381 if ( !(procInfo.mFlags & ProcessCreateFlag_IgnoreOrphanedProcesses)
2382 && !(procInfo.mFlags & ProcessCreateFlag_WaitForProcessStartOnly)
2383 && !(procInfo.mFlags & ProcessCreateFlag_Hidden)
2384 && !(procInfo.mFlags & ProcessCreateFlag_Profile)
2385 && !(procInfo.mFlags & ProcessCreateFlag_WaitForStdOut)
2386 && !(procInfo.mFlags & ProcessCreateFlag_WaitForStdErr))
2387 {
2388 return VERR_INVALID_PARAMETER;
2389 }
2390 }
2391
2392 if ( (procInfo.mFlags & ProcessCreateFlag_WaitForProcessStartOnly)
2393 && ( (procInfo.mFlags & ProcessCreateFlag_WaitForStdOut)
2394 || (procInfo.mFlags & ProcessCreateFlag_WaitForStdErr)
2395 )
2396 )
2397 {
2398 return VERR_INVALID_PARAMETER;
2399 }
2400
2401 if (procInfo.mPriority)
2402 {
2403 if (!(procInfo.mPriority & ProcessPriority_Default))
2404 return VERR_INVALID_PARAMETER;
2405 }
2406
2407 /* Adjust timeout.
2408 * If set to 0, we define an infinite timeout (unlimited process run time). */
2409 if (procInfo.mTimeoutMS == 0)
2410 procInfo.mTimeoutMS = UINT32_MAX;
2411
2412 /** @todo Implement process priority + affinity. */
2413
2414 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
2415
2416 /* Register a new object ID. */
2417 uint32_t idObject;
2418 int rc = i_objectRegister(SESSIONOBJECTTYPE_PROCESS, &idObject);
2419 if (RT_FAILURE(rc))
2420 return rc;
2421
2422 /* Create the process object. */
2423 HRESULT hr = pProcess.createObject();
2424 if (FAILED(hr))
2425 return VERR_COM_UNEXPECTED;
2426
2427 rc = pProcess->init(mParent->i_getConsole() /* Console */, this /* Session */, idObject,
2428 procInfo, mData.mpBaseEnvironment);
2429 if (RT_FAILURE(rc))
2430 return rc;
2431
2432 /* Add the created process to our map. */
2433 try
2434 {
2435 mData.mProcesses[idObject] = pProcess;
2436
2437 LogFlowFunc(("Added new process (Session: %RU32) with process ID=%RU32 (now total %zu processes)\n",
2438 mData.mSession.mID, idObject, mData.mProcesses.size()));
2439
2440 alock.release(); /* Release lock before firing off event. */
2441
2442 fireGuestProcessRegisteredEvent(mEventSource, this /* Session */, pProcess, 0 /* PID */, true /* Process registered */);
2443 }
2444 catch (std::bad_alloc &)
2445 {
2446 rc = VERR_NO_MEMORY;
2447 }
2448
2449 return rc;
2450}
2451
2452inline bool GuestSession::i_processExists(uint32_t uProcessID, ComObjPtr<GuestProcess> *pProcess)
2453{
2454 SessionProcesses::const_iterator it = mData.mProcesses.find(uProcessID);
2455 if (it != mData.mProcesses.end())
2456 {
2457 if (pProcess)
2458 *pProcess = it->second;
2459 return true;
2460 }
2461 return false;
2462}
2463
2464inline int GuestSession::i_processGetByPID(ULONG uPID, ComObjPtr<GuestProcess> *pProcess)
2465{
2466 AssertReturn(uPID, false);
2467 /* pProcess is optional. */
2468
2469 SessionProcesses::iterator itProcs = mData.mProcesses.begin();
2470 for (; itProcs != mData.mProcesses.end(); ++itProcs)
2471 {
2472 ComObjPtr<GuestProcess> pCurProc = itProcs->second;
2473 AutoCaller procCaller(pCurProc);
2474 if (procCaller.rc())
2475 return VERR_COM_INVALID_OBJECT_STATE;
2476
2477 ULONG uCurPID;
2478 HRESULT hr = pCurProc->COMGETTER(PID)(&uCurPID);
2479 ComAssertComRC(hr);
2480
2481 if (uCurPID == uPID)
2482 {
2483 if (pProcess)
2484 *pProcess = pCurProc;
2485 return VINF_SUCCESS;
2486 }
2487 }
2488
2489 return VERR_NOT_FOUND;
2490}
2491
2492int GuestSession::i_sendMessage(uint32_t uMessage, uint32_t uParms, PVBOXHGCMSVCPARM paParms,
2493 uint64_t fDst /*= VBOX_GUESTCTRL_DST_SESSION*/)
2494{
2495 LogFlowThisFuncEnter();
2496
2497#ifndef VBOX_GUESTCTRL_TEST_CASE
2498 ComObjPtr<Console> pConsole = mParent->i_getConsole();
2499 Assert(!pConsole.isNull());
2500
2501 /* Forward the information to the VMM device. */
2502 VMMDev *pVMMDev = pConsole->i_getVMMDev();
2503 AssertPtr(pVMMDev);
2504
2505 LogFlowThisFunc(("uMessage=%RU32 (%s), uParms=%RU32\n", uMessage, GstCtrlHostMsgtoStr((guestControl::eHostMsg)uMessage), uParms));
2506
2507 /* HACK ALERT! We extend the first parameter to 64-bit and use the
2508 two topmost bits for call destination information. */
2509 Assert(fDst == VBOX_GUESTCTRL_DST_SESSION || fDst == VBOX_GUESTCTRL_DST_ROOT_SVC || fDst == VBOX_GUESTCTRL_DST_BOTH);
2510 Assert(paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT);
2511 paParms[0].type = VBOX_HGCM_SVC_PARM_64BIT;
2512 paParms[0].u.uint64 = (uint64_t)paParms[0].u.uint32 | fDst;
2513
2514 /* Make the call. */
2515 int vrc = pVMMDev->hgcmHostCall(HGCMSERVICE_NAME, uMessage, uParms, paParms);
2516 if (RT_FAILURE(vrc))
2517 {
2518 /** @todo What to do here? */
2519 }
2520#else
2521 /* Not needed within testcases. */
2522 int vrc = VINF_SUCCESS;
2523#endif
2524 LogFlowFuncLeaveRC(vrc);
2525 return vrc;
2526}
2527
2528/* static */
2529HRESULT GuestSession::i_setErrorExternal(VirtualBoxBase *pInterface, int rcGuest)
2530{
2531 AssertPtr(pInterface);
2532 AssertMsg(RT_FAILURE(rcGuest), ("Guest rc does not indicate a failure when setting error\n"));
2533
2534 return pInterface->setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, GuestSession::i_guestErrorToString(rcGuest).c_str());
2535}
2536
2537/* Does not do locking; caller is responsible for that! */
2538int GuestSession::i_setSessionStatus(GuestSessionStatus_T sessionStatus, int sessionRc)
2539{
2540 LogFlowThisFunc(("oldStatus=%RU32, newStatus=%RU32, sessionRc=%Rrc\n",
2541 mData.mStatus, sessionStatus, sessionRc));
2542
2543 if (sessionStatus == GuestSessionStatus_Error)
2544 {
2545 AssertMsg(RT_FAILURE(sessionRc), ("Guest rc must be an error (%Rrc)\n", sessionRc));
2546 /* Do not allow overwriting an already set error. If this happens
2547 * this means we forgot some error checking/locking somewhere. */
2548 AssertMsg(RT_SUCCESS(mData.mRC), ("Guest rc already set (to %Rrc)\n", mData.mRC));
2549 }
2550 else
2551 AssertMsg(RT_SUCCESS(sessionRc), ("Guest rc must not be an error (%Rrc)\n", sessionRc));
2552
2553 if (mData.mStatus != sessionStatus)
2554 {
2555 mData.mStatus = sessionStatus;
2556 mData.mRC = sessionRc;
2557
2558 ComObjPtr<VirtualBoxErrorInfo> errorInfo;
2559 HRESULT hr = errorInfo.createObject();
2560 ComAssertComRC(hr);
2561 int rc2 = errorInfo->initEx(VBOX_E_IPRT_ERROR, sessionRc,
2562 COM_IIDOF(IGuestSession), getComponentName(),
2563 i_guestErrorToString(sessionRc));
2564 AssertRC(rc2);
2565
2566 fireGuestSessionStateChangedEvent(mEventSource, this,
2567 mData.mSession.mID, sessionStatus, errorInfo);
2568 }
2569
2570 return VINF_SUCCESS;
2571}
2572
2573int GuestSession::i_signalWaiters(GuestSessionWaitResult_T enmWaitResult, int rc /*= VINF_SUCCESS */)
2574{
2575 RT_NOREF(enmWaitResult, rc);
2576
2577 /*LogFlowThisFunc(("enmWaitResult=%d, rc=%Rrc, mWaitCount=%RU32, mWaitEvent=%p\n",
2578 enmWaitResult, rc, mData.mWaitCount, mData.mWaitEvent));*/
2579
2580 /* Note: No write locking here -- already done in the caller. */
2581
2582 int vrc = VINF_SUCCESS;
2583 /*if (mData.mWaitEvent)
2584 vrc = mData.mWaitEvent->Signal(enmWaitResult, rc);*/
2585 LogFlowFuncLeaveRC(vrc);
2586 return vrc;
2587}
2588
2589/**
2590 * Determines the protocol version (sets mData.mProtocolVersion).
2591 *
2592 * This is called from the init method prior to to establishing a guest
2593 * session.
2594 *
2595 * @return IPRT status code.
2596 */
2597int GuestSession::i_determineProtocolVersion(void)
2598{
2599 /*
2600 * We currently do this based on the reported guest additions version,
2601 * ASSUMING that VBoxService and VBoxDrv are at the same version.
2602 */
2603 ComObjPtr<Guest> pGuest = mParent;
2604 AssertReturn(!pGuest.isNull(), VERR_NOT_SUPPORTED);
2605 uint32_t uGaVersion = pGuest->i_getAdditionsVersion();
2606
2607 /* Everyone supports version one, if they support anything at all. */
2608 mData.mProtocolVersion = 1;
2609
2610 /* Guest control 2.0 was introduced with 4.3.0. */
2611 if (uGaVersion >= VBOX_FULL_VERSION_MAKE(4,3,0))
2612 mData.mProtocolVersion = 2; /* Guest control 2.0. */
2613
2614 LogFlowThisFunc(("uGaVersion=%u.%u.%u => mProtocolVersion=%u\n",
2615 VBOX_FULL_VERSION_GET_MAJOR(uGaVersion), VBOX_FULL_VERSION_GET_MINOR(uGaVersion),
2616 VBOX_FULL_VERSION_GET_BUILD(uGaVersion), mData.mProtocolVersion));
2617
2618 /*
2619 * Inform the user about outdated guest additions (VM release log).
2620 */
2621 if (mData.mProtocolVersion < 2)
2622 LogRelMax(3, (tr("Warning: Guest Additions v%u.%u.%u only supports the older guest control protocol version %u.\n"
2623 " Please upgrade GAs to the current version to get full guest control capabilities.\n"),
2624 VBOX_FULL_VERSION_GET_MAJOR(uGaVersion), VBOX_FULL_VERSION_GET_MINOR(uGaVersion),
2625 VBOX_FULL_VERSION_GET_BUILD(uGaVersion), mData.mProtocolVersion));
2626
2627 return VINF_SUCCESS;
2628}
2629
2630int GuestSession::i_waitFor(uint32_t fWaitFlags, ULONG uTimeoutMS, GuestSessionWaitResult_T &waitResult, int *prcGuest)
2631{
2632 LogFlowThisFuncEnter();
2633
2634 AssertReturn(fWaitFlags, VERR_INVALID_PARAMETER);
2635
2636 /*LogFlowThisFunc(("fWaitFlags=0x%x, uTimeoutMS=%RU32, mStatus=%RU32, mWaitCount=%RU32, mWaitEvent=%p, prcGuest=%p\n",
2637 fWaitFlags, uTimeoutMS, mData.mStatus, mData.mWaitCount, mData.mWaitEvent, prcGuest));*/
2638
2639 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
2640
2641 /* Did some error occur before? Then skip waiting and return. */
2642 if (mData.mStatus == GuestSessionStatus_Error)
2643 {
2644 waitResult = GuestSessionWaitResult_Error;
2645 AssertMsg(RT_FAILURE(mData.mRC), ("No error rc (%Rrc) set when guest session indicated an error\n", mData.mRC));
2646 if (prcGuest)
2647 *prcGuest = mData.mRC; /* Return last set error. */
2648 return VERR_GSTCTL_GUEST_ERROR;
2649 }
2650
2651 /* Guest Additions < 4.3 don't support session handling, skip. */
2652 if (mData.mProtocolVersion < 2)
2653 {
2654 waitResult = GuestSessionWaitResult_WaitFlagNotSupported;
2655
2656 LogFlowThisFunc(("Installed Guest Additions don't support waiting for dedicated sessions, skipping\n"));
2657 return VINF_SUCCESS;
2658 }
2659
2660 waitResult = GuestSessionWaitResult_None;
2661 if (fWaitFlags & GuestSessionWaitForFlag_Terminate)
2662 {
2663 switch (mData.mStatus)
2664 {
2665 case GuestSessionStatus_Terminated:
2666 case GuestSessionStatus_Down:
2667 waitResult = GuestSessionWaitResult_Terminate;
2668 break;
2669
2670 case GuestSessionStatus_TimedOutKilled:
2671 case GuestSessionStatus_TimedOutAbnormally:
2672 waitResult = GuestSessionWaitResult_Timeout;
2673 break;
2674
2675 case GuestSessionStatus_Error:
2676 /* Handled above. */
2677 break;
2678
2679 case GuestSessionStatus_Started:
2680 waitResult = GuestSessionWaitResult_Start;
2681 break;
2682
2683 case GuestSessionStatus_Undefined:
2684 case GuestSessionStatus_Starting:
2685 /* Do the waiting below. */
2686 break;
2687
2688 default:
2689 AssertMsgFailed(("Unhandled session status %RU32\n", mData.mStatus));
2690 return VERR_NOT_IMPLEMENTED;
2691 }
2692 }
2693 else if (fWaitFlags & GuestSessionWaitForFlag_Start)
2694 {
2695 switch (mData.mStatus)
2696 {
2697 case GuestSessionStatus_Started:
2698 case GuestSessionStatus_Terminating:
2699 case GuestSessionStatus_Terminated:
2700 case GuestSessionStatus_Down:
2701 waitResult = GuestSessionWaitResult_Start;
2702 break;
2703
2704 case GuestSessionStatus_Error:
2705 waitResult = GuestSessionWaitResult_Error;
2706 break;
2707
2708 case GuestSessionStatus_TimedOutKilled:
2709 case GuestSessionStatus_TimedOutAbnormally:
2710 waitResult = GuestSessionWaitResult_Timeout;
2711 break;
2712
2713 case GuestSessionStatus_Undefined:
2714 case GuestSessionStatus_Starting:
2715 /* Do the waiting below. */
2716 break;
2717
2718 default:
2719 AssertMsgFailed(("Unhandled session status %RU32\n", mData.mStatus));
2720 return VERR_NOT_IMPLEMENTED;
2721 }
2722 }
2723
2724 LogFlowThisFunc(("sessionStatus=%RU32, sessionRc=%Rrc, waitResult=%RU32\n",
2725 mData.mStatus, mData.mRC, waitResult));
2726
2727 /* No waiting needed? Return immediately using the last set error. */
2728 if (waitResult != GuestSessionWaitResult_None)
2729 {
2730 if (prcGuest)
2731 *prcGuest = mData.mRC; /* Return last set error (if any). */
2732 return RT_SUCCESS(mData.mRC) ? VINF_SUCCESS : VERR_GSTCTL_GUEST_ERROR;
2733 }
2734
2735 int vrc;
2736
2737 GuestWaitEvent *pEvent = NULL;
2738 GuestEventTypes eventTypes;
2739 try
2740 {
2741 eventTypes.push_back(VBoxEventType_OnGuestSessionStateChanged);
2742
2743 vrc = registerWaitEventEx(mData.mSession.mID, mData.mObjectID, eventTypes, &pEvent);
2744 }
2745 catch (std::bad_alloc &)
2746 {
2747 vrc = VERR_NO_MEMORY;
2748 }
2749
2750 if (RT_FAILURE(vrc))
2751 return vrc;
2752
2753 alock.release(); /* Release lock before waiting. */
2754
2755 GuestSessionStatus_T sessionStatus;
2756 vrc = i_waitForStatusChange(pEvent, fWaitFlags,
2757 uTimeoutMS, &sessionStatus, prcGuest);
2758 if (RT_SUCCESS(vrc))
2759 {
2760 switch (sessionStatus)
2761 {
2762 case GuestSessionStatus_Started:
2763 waitResult = GuestSessionWaitResult_Start;
2764 break;
2765
2766 case GuestSessionStatus_Terminated:
2767 waitResult = GuestSessionWaitResult_Terminate;
2768 break;
2769
2770 case GuestSessionStatus_TimedOutKilled:
2771 case GuestSessionStatus_TimedOutAbnormally:
2772 waitResult = GuestSessionWaitResult_Timeout;
2773 break;
2774
2775 case GuestSessionStatus_Down:
2776 waitResult = GuestSessionWaitResult_Terminate;
2777 break;
2778
2779 case GuestSessionStatus_Error:
2780 waitResult = GuestSessionWaitResult_Error;
2781 break;
2782
2783 default:
2784 waitResult = GuestSessionWaitResult_Status;
2785 break;
2786 }
2787 }
2788
2789 unregisterWaitEvent(pEvent);
2790
2791 LogFlowFuncLeaveRC(vrc);
2792 return vrc;
2793}
2794
2795int GuestSession::i_waitForStatusChange(GuestWaitEvent *pEvent, uint32_t fWaitFlags, uint32_t uTimeoutMS,
2796 GuestSessionStatus_T *pSessionStatus, int *prcGuest)
2797{
2798 RT_NOREF(fWaitFlags);
2799 AssertPtrReturn(pEvent, VERR_INVALID_POINTER);
2800
2801 VBoxEventType_T evtType;
2802 ComPtr<IEvent> pIEvent;
2803 int vrc = waitForEvent(pEvent, uTimeoutMS,
2804 &evtType, pIEvent.asOutParam());
2805 if (RT_SUCCESS(vrc))
2806 {
2807 Assert(evtType == VBoxEventType_OnGuestSessionStateChanged);
2808
2809 ComPtr<IGuestSessionStateChangedEvent> pChangedEvent = pIEvent;
2810 Assert(!pChangedEvent.isNull());
2811
2812 GuestSessionStatus_T sessionStatus;
2813 pChangedEvent->COMGETTER(Status)(&sessionStatus);
2814 if (pSessionStatus)
2815 *pSessionStatus = sessionStatus;
2816
2817 ComPtr<IVirtualBoxErrorInfo> errorInfo;
2818 HRESULT hr = pChangedEvent->COMGETTER(Error)(errorInfo.asOutParam());
2819 ComAssertComRC(hr);
2820
2821 LONG lGuestRc;
2822 hr = errorInfo->COMGETTER(ResultDetail)(&lGuestRc);
2823 ComAssertComRC(hr);
2824 if (RT_FAILURE((int)lGuestRc))
2825 vrc = VERR_GSTCTL_GUEST_ERROR;
2826 if (prcGuest)
2827 *prcGuest = (int)lGuestRc;
2828
2829 LogFlowThisFunc(("Status changed event for session ID=%RU32, new status is: %RU32 (%Rrc)\n",
2830 mData.mSession.mID, sessionStatus,
2831 RT_SUCCESS((int)lGuestRc) ? VINF_SUCCESS : (int)lGuestRc));
2832 }
2833
2834 LogFlowFuncLeaveRC(vrc);
2835 return vrc;
2836}
2837
2838// implementation of public methods
2839/////////////////////////////////////////////////////////////////////////////
2840
2841HRESULT GuestSession::close()
2842{
2843 AutoCaller autoCaller(this);
2844 if (FAILED(autoCaller.rc())) return autoCaller.rc();
2845
2846 LogFlowThisFuncEnter();
2847
2848 /* Note: Don't check if the session is ready via i_isReadyExternal() here;
2849 * the session (already) could be in a stopped / aborted state. */
2850
2851 /* Close session on guest. */
2852 int rcGuest = VINF_SUCCESS;
2853 int vrc = i_closeSession(0 /* Flags */, 30 * 1000 /* Timeout */, &rcGuest);
2854 /* On failure don't return here, instead do all the cleanup
2855 * work first and then return an error. */
2856
2857 /* Remove ourselves from the session list. */
2858 AssertPtr(mParent);
2859 int vrc2 = mParent->i_sessionRemove(mData.mSession.mID);
2860 if (vrc2 == VERR_NOT_FOUND) /* Not finding the session anymore isn't critical. */
2861 vrc2 = VINF_SUCCESS;
2862
2863 if (RT_SUCCESS(vrc))
2864 vrc = vrc2;
2865
2866 LogFlowThisFunc(("Returning rc=%Rrc, rcGuest=%Rrc\n", vrc, rcGuest));
2867
2868 if (RT_FAILURE(vrc))
2869 {
2870 if (vrc == VERR_GSTCTL_GUEST_ERROR)
2871 return GuestSession::i_setErrorExternal(this, rcGuest);
2872 return setError(VBOX_E_IPRT_ERROR, tr("Closing guest session failed with %Rrc"), vrc);
2873 }
2874
2875 return S_OK;
2876}
2877
2878HRESULT GuestSession::fileCopy(const com::Utf8Str &aSource, const com::Utf8Str &aDestination,
2879 const std::vector<FileCopyFlag_T> &aFlags, ComPtr<IProgress> &aProgress)
2880{
2881 RT_NOREF(aSource, aDestination, aFlags, aProgress);
2882 ReturnComNotImplemented();
2883}
2884
2885HRESULT GuestSession::fileCopyFromGuest(const com::Utf8Str &aSource, const com::Utf8Str &aDestination,
2886 const std::vector<FileCopyFlag_T> &aFlags,
2887 ComPtr<IProgress> &aProgress)
2888{
2889 AutoCaller autoCaller(this);
2890 if (FAILED(autoCaller.rc())) return autoCaller.rc();
2891
2892 uint32_t fFlags = FileCopyFlag_None;
2893 if (aFlags.size())
2894 {
2895 for (size_t i = 0; i < aFlags.size(); i++)
2896 fFlags |= aFlags[i];
2897 }
2898
2899 GuestSessionFsSourceSet SourceSet;
2900
2901 GuestSessionFsSourceSpec source;
2902 source.strSource = aSource;
2903 source.enmType = FsObjType_File;
2904 source.enmPathStyle = i_getPathStyle();
2905 source.fDryRun = false; /** @todo Implement support for a dry run. */
2906 source.Type.File.fCopyFlags = (FileCopyFlag_T)fFlags;
2907
2908 SourceSet.push_back(source);
2909
2910 return i_copyFromGuest(SourceSet, aDestination, aProgress);
2911}
2912
2913HRESULT GuestSession::fileCopyToGuest(const com::Utf8Str &aSource, const com::Utf8Str &aDestination,
2914 const std::vector<FileCopyFlag_T> &aFlags, ComPtr<IProgress> &aProgress)
2915{
2916 AutoCaller autoCaller(this);
2917 if (FAILED(autoCaller.rc())) return autoCaller.rc();
2918
2919 uint32_t fFlags = FileCopyFlag_None;
2920 if (aFlags.size())
2921 {
2922 for (size_t i = 0; i < aFlags.size(); i++)
2923 fFlags |= aFlags[i];
2924 }
2925
2926 GuestSessionFsSourceSet SourceSet;
2927
2928 GuestSessionFsSourceSpec source;
2929 source.strSource = aSource;
2930 source.enmType = FsObjType_File;
2931 source.enmPathStyle = i_getPathStyle();
2932 source.fDryRun = false; /** @todo Implement support for a dry run. */
2933 source.Type.File.fCopyFlags = (FileCopyFlag_T)fFlags;
2934
2935 SourceSet.push_back(source);
2936
2937 return i_copyToGuest(SourceSet, aDestination, aProgress);
2938}
2939
2940HRESULT GuestSession::copyFromGuest(const std::vector<com::Utf8Str> &aSources, const std::vector<com::Utf8Str> &aFilters,
2941 const std::vector<com::Utf8Str> &aFlags, const com::Utf8Str &aDestination,
2942 ComPtr<IProgress> &aProgress)
2943{
2944 AutoCaller autoCaller(this);
2945 if (FAILED(autoCaller.rc())) return autoCaller.rc();
2946
2947 const size_t cSources = aSources.size();
2948 if ( (aFilters.size() && aFilters.size() != cSources)
2949 || (aFlags.size() && aFlags.size() != cSources))
2950 {
2951 return setError(E_INVALIDARG, tr("Parameter array sizes don't match to the number of sources specified"));
2952 }
2953
2954 GuestSessionFsSourceSet SourceSet;
2955
2956 std::vector<com::Utf8Str>::const_iterator itSource = aSources.begin();
2957 std::vector<com::Utf8Str>::const_iterator itFilter = aFilters.begin();
2958 std::vector<com::Utf8Str>::const_iterator itFlags = aFlags.begin();
2959
2960 const bool fContinueOnErrors = false; /** @todo Do we want a flag for that? */
2961 const bool fFollowSymlinks = true; /** @todo Ditto. */
2962
2963 while (itSource != aSources.end())
2964 {
2965 GuestFsObjData objData;
2966 int rcGuest;
2967 int vrc = i_fsQueryInfo(*(itSource), fFollowSymlinks, objData, &rcGuest);
2968 if ( RT_FAILURE(vrc)
2969 && !fContinueOnErrors)
2970 {
2971 if (GuestProcess::i_isGuestError(vrc))
2972 return setError(E_FAIL, tr("Unable to query type for source '%s': %s"), (*itSource).c_str(),
2973 GuestProcess::i_guestErrorToString(rcGuest).c_str());
2974 else
2975 return setError(E_FAIL, tr("Unable to query type for source '%s' (%Rrc)"), (*itSource).c_str(), vrc);
2976 }
2977
2978 Utf8Str strFlags;
2979 if (itFlags != aFlags.end())
2980 {
2981 strFlags = *itFlags;
2982 ++itFlags;
2983 }
2984
2985 Utf8Str strFilter;
2986 if (itFilter != aFilters.end())
2987 {
2988 strFilter = *itFilter;
2989 ++itFilter;
2990 }
2991
2992 GuestSessionFsSourceSpec source;
2993 source.strSource = *itSource;
2994 source.strFilter = strFilter;
2995 source.enmType = objData.mType;
2996 source.enmPathStyle = i_getPathStyle();
2997 source.fDryRun = false; /** @todo Implement support for a dry run. */
2998
2999 HRESULT hrc;
3000 if (source.enmType == FsObjType_Directory)
3001 {
3002 hrc = GuestSession::i_directoryCopyFlagFromStr(strFlags, &source.Type.Dir.fCopyFlags);
3003 source.Type.Dir.fRecursive = true; /* Implicit. */
3004 }
3005 else if (source.enmType == FsObjType_File)
3006 hrc = GuestSession::i_fileCopyFlagFromStr(strFlags, &source.Type.File.fCopyFlags);
3007 else
3008 return setError(E_INVALIDARG, tr("Source type %d invalid / not supported"), source.enmType);
3009 if (FAILED(hrc))
3010 return hrc;
3011
3012 SourceSet.push_back(source);
3013
3014 ++itSource;
3015 }
3016
3017 return i_copyFromGuest(SourceSet, aDestination, aProgress);
3018}
3019
3020HRESULT GuestSession::copyToGuest(const std::vector<com::Utf8Str> &aSources, const std::vector<com::Utf8Str> &aFilters,
3021 const std::vector<com::Utf8Str> &aFlags, const com::Utf8Str &aDestination,
3022 ComPtr<IProgress> &aProgress)
3023{
3024 AutoCaller autoCaller(this);
3025 if (FAILED(autoCaller.rc())) return autoCaller.rc();
3026
3027 const size_t cSources = aSources.size();
3028 if ( (aFilters.size() && aFilters.size() != cSources)
3029 || (aFlags.size() && aFlags.size() != cSources))
3030 {
3031 return setError(E_INVALIDARG, tr("Parameter array sizes don't match to the number of sources specified"));
3032 }
3033
3034 GuestSessionFsSourceSet SourceSet;
3035
3036 std::vector<com::Utf8Str>::const_iterator itSource = aSources.begin();
3037 std::vector<com::Utf8Str>::const_iterator itFilter = aFilters.begin();
3038 std::vector<com::Utf8Str>::const_iterator itFlags = aFlags.begin();
3039
3040 const bool fContinueOnErrors = false; /** @todo Do we want a flag for that? */
3041
3042 while (itSource != aSources.end())
3043 {
3044 RTFSOBJINFO objInfo;
3045 int vrc = RTPathQueryInfo((*itSource).c_str(), &objInfo, RTFSOBJATTRADD_NOTHING);
3046 if ( RT_FAILURE(vrc)
3047 && !fContinueOnErrors)
3048 {
3049 return setError(E_FAIL, tr("Unable to query type for source '%s' (%Rrc)"), (*itSource).c_str(), vrc);
3050 }
3051
3052 Utf8Str strFlags;
3053 if (itFlags != aFlags.end())
3054 {
3055 strFlags = *itFlags;
3056 ++itFlags;
3057 }
3058
3059 Utf8Str strFilter;
3060 if (itFilter != aFilters.end())
3061 {
3062 strFilter = *itFilter;
3063 ++itFilter;
3064 }
3065
3066 GuestSessionFsSourceSpec source;
3067 source.strSource = *itSource;
3068 source.strFilter = strFilter;
3069 source.enmType = GuestBase::fileModeToFsObjType(objInfo.Attr.fMode);
3070 source.enmPathStyle = i_getPathStyle();
3071 source.fDryRun = false; /** @todo Implement support for a dry run. */
3072
3073 HRESULT hrc;
3074 if (source.enmType == FsObjType_Directory)
3075 {
3076 hrc = GuestSession::i_directoryCopyFlagFromStr(strFlags, &source.Type.Dir.fCopyFlags);
3077 source.Type.Dir.fFollowSymlinks = true; /** @todo Add a flag for that in DirectoryCopyFlag_T. Later. */
3078 source.Type.Dir.fRecursive = true; /* Implicit. */
3079 }
3080 else if (source.enmType == FsObjType_File)
3081 hrc = GuestSession::i_fileCopyFlagFromStr(strFlags, &source.Type.File.fCopyFlags);
3082 else
3083 return setError(E_INVALIDARG, tr("Source type %d invalid / not supported"), source.enmType);
3084 if (FAILED(hrc))
3085 return hrc;
3086
3087 SourceSet.push_back(source);
3088
3089 ++itSource;
3090 }
3091
3092 return i_copyToGuest(SourceSet, aDestination, aProgress);
3093}
3094
3095HRESULT GuestSession::directoryCopy(const com::Utf8Str &aSource, const com::Utf8Str &aDestination,
3096 const std::vector<DirectoryCopyFlag_T> &aFlags, ComPtr<IProgress> &aProgress)
3097{
3098 RT_NOREF(aSource, aDestination, aFlags, aProgress);
3099 ReturnComNotImplemented();
3100}
3101
3102HRESULT GuestSession::directoryCopyFromGuest(const com::Utf8Str &aSource, const com::Utf8Str &aDestination,
3103 const std::vector<DirectoryCopyFlag_T> &aFlags, ComPtr<IProgress> &aProgress)
3104{
3105 AutoCaller autoCaller(this);
3106 if (FAILED(autoCaller.rc())) return autoCaller.rc();
3107
3108 uint32_t fFlags = DirectoryCopyFlag_None;
3109 if (aFlags.size())
3110 {
3111 for (size_t i = 0; i < aFlags.size(); i++)
3112 fFlags |= aFlags[i];
3113 }
3114
3115 GuestSessionFsSourceSet SourceSet;
3116
3117 GuestSessionFsSourceSpec source;
3118 source.strSource = aSource;
3119 source.enmType = FsObjType_Directory;
3120 source.enmPathStyle = i_getPathStyle();
3121 source.fDryRun = false; /** @todo Implement support for a dry run. */
3122 source.Type.Dir.fCopyFlags = (DirectoryCopyFlag_T)fFlags;
3123 source.Type.Dir.fRecursive = true; /* Implicit. */
3124
3125 SourceSet.push_back(source);
3126
3127 return i_copyFromGuest(SourceSet, aDestination, aProgress);
3128}
3129
3130HRESULT GuestSession::directoryCopyToGuest(const com::Utf8Str &aSource, const com::Utf8Str &aDestination,
3131 const std::vector<DirectoryCopyFlag_T> &aFlags, ComPtr<IProgress> &aProgress)
3132{
3133 AutoCaller autoCaller(this);
3134 if (FAILED(autoCaller.rc())) return autoCaller.rc();
3135
3136 uint32_t fFlags = DirectoryCopyFlag_None;
3137 if (aFlags.size())
3138 {
3139 for (size_t i = 0; i < aFlags.size(); i++)
3140 fFlags |= aFlags[i];
3141 }
3142
3143 GuestSessionFsSourceSet SourceSet;
3144
3145 GuestSessionFsSourceSpec source;
3146 source.strSource = aSource;
3147 source.enmType = FsObjType_Directory;
3148 source.enmPathStyle = i_getPathStyle();
3149 source.fDryRun = false; /** @todo Implement support for a dry run. */
3150 source.Type.Dir.fCopyFlags = (DirectoryCopyFlag_T)fFlags;
3151 source.Type.Dir.fFollowSymlinks = true; /** @todo Add a flag for that in DirectoryCopyFlag_T. Later. */
3152 source.Type.Dir.fRecursive = true; /* Implicit. */
3153
3154 SourceSet.push_back(source);
3155
3156 return i_copyToGuest(SourceSet, aDestination, aProgress);
3157}
3158
3159HRESULT GuestSession::directoryCreate(const com::Utf8Str &aPath, ULONG aMode,
3160 const std::vector<DirectoryCreateFlag_T> &aFlags)
3161{
3162 AutoCaller autoCaller(this);
3163 if (FAILED(autoCaller.rc())) return autoCaller.rc();
3164
3165 if (RT_UNLIKELY((aPath.c_str()) == NULL || *(aPath.c_str()) == '\0'))
3166 return setError(E_INVALIDARG, tr("No directory to create specified"));
3167
3168 uint32_t fFlags = DirectoryCreateFlag_None;
3169 if (aFlags.size())
3170 {
3171 for (size_t i = 0; i < aFlags.size(); i++)
3172 fFlags |= aFlags[i];
3173
3174 if (fFlags)
3175 if (!(fFlags & DirectoryCreateFlag_Parents))
3176 return setError(E_INVALIDARG, tr("Unknown flags (%#x)"), fFlags);
3177 }
3178
3179 HRESULT hrc = i_isStartedExternal();
3180 if (FAILED(hrc))
3181 return hrc;
3182
3183 LogFlowThisFuncEnter();
3184
3185 ComObjPtr <GuestDirectory> pDirectory; int rcGuest;
3186 int vrc = i_directoryCreate(aPath, (uint32_t)aMode, fFlags, &rcGuest);
3187 if (RT_FAILURE(vrc))
3188 {
3189 if (GuestProcess::i_isGuestError(vrc))
3190 hrc = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest,
3191 tr("Directory creation failed: %s"), GuestDirectory::i_guestErrorToString(rcGuest).c_str());
3192 else
3193 {
3194 switch (vrc)
3195 {
3196 case VERR_INVALID_PARAMETER:
3197 hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Directory creation failed: Invalid parameters given"));
3198 break;
3199
3200 case VERR_BROKEN_PIPE:
3201 hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Directory creation failed: Unexpectedly aborted"));
3202 break;
3203
3204 default:
3205 hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Directory creation failed: %Rrc"), vrc);
3206 break;
3207 }
3208 }
3209 }
3210
3211 return hrc;
3212}
3213
3214HRESULT GuestSession::directoryCreateTemp(const com::Utf8Str &aTemplateName, ULONG aMode, const com::Utf8Str &aPath,
3215 BOOL aSecure, com::Utf8Str &aDirectory)
3216{
3217 RT_NOREF(aMode, aSecure);
3218
3219 AutoCaller autoCaller(this);
3220 if (FAILED(autoCaller.rc())) return autoCaller.rc();
3221
3222 if (RT_UNLIKELY((aTemplateName.c_str()) == NULL || *(aTemplateName.c_str()) == '\0'))
3223 return setError(E_INVALIDARG, tr("No template specified"));
3224 if (RT_UNLIKELY((aPath.c_str()) == NULL || *(aPath.c_str()) == '\0'))
3225 return setError(E_INVALIDARG, tr("No directory name specified"));
3226
3227 HRESULT hrc = i_isStartedExternal();
3228 if (FAILED(hrc))
3229 return hrc;
3230
3231 LogFlowThisFuncEnter();
3232
3233 int rcGuest;
3234 int vrc = i_fsCreateTemp(aTemplateName, aPath, true /* Directory */, aDirectory, &rcGuest);
3235 if (!RT_SUCCESS(vrc))
3236 {
3237 switch (vrc)
3238 {
3239 case VERR_GSTCTL_GUEST_ERROR:
3240 hrc = GuestProcess::i_setErrorExternal(this, rcGuest);
3241 break;
3242
3243 default:
3244 hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Temporary directory creation \"%s\" with template \"%s\" failed: %Rrc"),
3245 aPath.c_str(), aTemplateName.c_str(), vrc);
3246 break;
3247 }
3248 }
3249
3250 return hrc;
3251}
3252
3253HRESULT GuestSession::directoryExists(const com::Utf8Str &aPath, BOOL aFollowSymlinks, BOOL *aExists)
3254{
3255 AutoCaller autoCaller(this);
3256 if (FAILED(autoCaller.rc())) return autoCaller.rc();
3257
3258 if (RT_UNLIKELY((aPath.c_str()) == NULL || *(aPath.c_str()) == '\0'))
3259 return setError(E_INVALIDARG, tr("No directory to check existence for specified"));
3260
3261 HRESULT hrc = i_isStartedExternal();
3262 if (FAILED(hrc))
3263 return hrc;
3264
3265 LogFlowThisFuncEnter();
3266
3267 GuestFsObjData objData; int rcGuest;
3268 int vrc = i_directoryQueryInfo(aPath, aFollowSymlinks != FALSE, objData, &rcGuest);
3269 if (RT_SUCCESS(vrc))
3270 *aExists = objData.mType == FsObjType_Directory;
3271 else
3272 {
3273 switch (vrc)
3274 {
3275 case VERR_GSTCTL_GUEST_ERROR:
3276 {
3277 switch (rcGuest)
3278 {
3279 case VERR_PATH_NOT_FOUND:
3280 *aExists = FALSE;
3281 break;
3282 default:
3283 hrc = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Querying directory existence \"%s\" failed: %s"),
3284 aPath.c_str(), GuestProcess::i_guestErrorToString(rcGuest).c_str());
3285 break;
3286 }
3287 break;
3288 }
3289
3290 default:
3291 hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Querying directory existence \"%s\" failed: %Rrc"),
3292 aPath.c_str(), vrc);
3293 break;
3294 }
3295 }
3296
3297 return hrc;
3298}
3299
3300HRESULT GuestSession::directoryOpen(const com::Utf8Str &aPath, const com::Utf8Str &aFilter,
3301 const std::vector<DirectoryOpenFlag_T> &aFlags, ComPtr<IGuestDirectory> &aDirectory)
3302{
3303 AutoCaller autoCaller(this);
3304 if (FAILED(autoCaller.rc())) return autoCaller.rc();
3305
3306 if (RT_UNLIKELY((aPath.c_str()) == NULL || *(aPath.c_str()) == '\0'))
3307 return setError(E_INVALIDARG, tr("No directory to open specified"));
3308 if (RT_UNLIKELY((aFilter.c_str()) != NULL && *(aFilter.c_str()) != '\0'))
3309 return setError(E_INVALIDARG, tr("Directory filters are not implemented yet"));
3310
3311 uint32_t fFlags = DirectoryOpenFlag_None;
3312 if (aFlags.size())
3313 {
3314 for (size_t i = 0; i < aFlags.size(); i++)
3315 fFlags |= aFlags[i];
3316
3317 if (fFlags)
3318 return setError(E_INVALIDARG, tr("Open flags (%#x) not implemented yet"), fFlags);
3319 }
3320
3321 HRESULT hrc = i_isStartedExternal();
3322 if (FAILED(hrc))
3323 return hrc;
3324
3325 LogFlowThisFuncEnter();
3326
3327 GuestDirectoryOpenInfo openInfo;
3328 openInfo.mPath = aPath;
3329 openInfo.mFilter = aFilter;
3330 openInfo.mFlags = fFlags;
3331
3332 ComObjPtr <GuestDirectory> pDirectory; int rcGuest;
3333 int vrc = i_directoryOpen(openInfo, pDirectory, &rcGuest);
3334 if (RT_SUCCESS(vrc))
3335 {
3336 /* Return directory object to the caller. */
3337 hrc = pDirectory.queryInterfaceTo(aDirectory.asOutParam());
3338 }
3339 else
3340 {
3341 switch (vrc)
3342 {
3343 case VERR_INVALID_PARAMETER:
3344 hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Opening directory \"%s\" failed; invalid parameters given"),
3345 aPath.c_str());
3346 break;
3347
3348 case VERR_GSTCTL_GUEST_ERROR:
3349 hrc = GuestDirectory::i_setErrorExternal(this, rcGuest);
3350 break;
3351
3352 default:
3353 hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Opening directory \"%s\" failed: %Rrc"), aPath.c_str(), vrc);
3354 break;
3355 }
3356 }
3357
3358 return hrc;
3359}
3360
3361HRESULT GuestSession::directoryRemove(const com::Utf8Str &aPath)
3362{
3363 AutoCaller autoCaller(this);
3364 if (FAILED(autoCaller.rc())) return autoCaller.rc();
3365
3366 if (RT_UNLIKELY((aPath.c_str()) == NULL || *(aPath.c_str()) == '\0'))
3367 return setError(E_INVALIDARG, tr("No directory to remove specified"));
3368
3369 HRESULT hrc = i_isStartedExternal();
3370 if (FAILED(hrc))
3371 return hrc;
3372
3373 LogFlowThisFuncEnter();
3374
3375 /* No flags; only remove the directory when empty. */
3376 uint32_t uFlags = 0;
3377
3378 int rcGuest;
3379 int vrc = i_directoryRemove(aPath, uFlags, &rcGuest);
3380 if (RT_FAILURE(vrc))
3381 {
3382 switch (vrc)
3383 {
3384 case VERR_NOT_SUPPORTED:
3385 hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
3386 tr("Handling removing guest directories not supported by installed Guest Additions"));
3387 break;
3388
3389 case VERR_GSTCTL_GUEST_ERROR:
3390 hrc = GuestDirectory::i_setErrorExternal(this, rcGuest);
3391 break;
3392
3393 default:
3394 hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Removing guest directory \"%s\" failed: %Rrc"), aPath.c_str(), vrc);
3395 break;
3396 }
3397 }
3398
3399 return hrc;
3400}
3401
3402HRESULT GuestSession::directoryRemoveRecursive(const com::Utf8Str &aPath, const std::vector<DirectoryRemoveRecFlag_T> &aFlags,
3403 ComPtr<IProgress> &aProgress)
3404{
3405 RT_NOREF(aFlags);
3406
3407 AutoCaller autoCaller(this);
3408 if (FAILED(autoCaller.rc())) return autoCaller.rc();
3409
3410 if (RT_UNLIKELY((aPath.c_str()) == NULL || *(aPath.c_str()) == '\0'))
3411 return setError(E_INVALIDARG, tr("No directory to remove recursively specified"));
3412
3413 uint32_t fFlags = DIRREMOVEREC_FLAG_NONE;
3414 if (aFlags.size())
3415 {
3416 for (size_t i = 0; i < aFlags.size(); i++)
3417 {
3418 switch (aFlags[i])
3419 {
3420 case DirectoryRemoveRecFlag_ContentAndDir:
3421 fFlags = DIRREMOVEREC_FLAG_RECURSIVE | DIRREMOVEREC_FLAG_CONTENT_AND_DIR;
3422 break;
3423
3424 case DirectoryRemoveRecFlag_ContentOnly:
3425 fFlags = DIRREMOVEREC_FLAG_RECURSIVE | DIRREMOVEREC_FLAG_CONTENT_ONLY;
3426 break;
3427
3428 default:
3429 return setError(E_INVALIDARG, tr("Invalid flags specified"));
3430 }
3431 }
3432 }
3433
3434 HRESULT hrc = i_isStartedExternal();
3435 if (FAILED(hrc))
3436 return hrc;
3437
3438 LogFlowThisFuncEnter();
3439
3440 ComObjPtr<Progress> pProgress;
3441 hrc = pProgress.createObject();
3442 if (SUCCEEDED(hrc))
3443 hrc = pProgress->init(static_cast<IGuestSession *>(this),
3444 Bstr(tr("Removing guest directory")).raw(),
3445 TRUE /*aCancelable*/);
3446 if (FAILED(hrc))
3447 return hrc;
3448
3449 /* Note: At the moment we don't supply progress information while
3450 * deleting a guest directory recursively. So just complete
3451 * the progress object right now. */
3452 /** @todo Implement progress reporting on guest directory deletion! */
3453 hrc = pProgress->i_notifyComplete(S_OK);
3454 if (FAILED(hrc))
3455 return hrc;
3456
3457 int rcGuest;
3458 int vrc = i_directoryRemove(aPath, fFlags, &rcGuest);
3459 if (RT_FAILURE(vrc))
3460 {
3461 switch (vrc)
3462 {
3463 case VERR_NOT_SUPPORTED:
3464 hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
3465 tr("Handling removing guest directories recursively not supported by installed Guest Additions"));
3466 break;
3467
3468 case VERR_GSTCTL_GUEST_ERROR:
3469 hrc = GuestFile::i_setErrorExternal(this, rcGuest);
3470 break;
3471
3472 default:
3473 hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Recursively removing guest directory \"%s\" failed: %Rrc"),
3474 aPath.c_str(), vrc);
3475 break;
3476 }
3477 }
3478 else
3479 {
3480 pProgress.queryInterfaceTo(aProgress.asOutParam());
3481 }
3482
3483 return hrc;
3484}
3485
3486HRESULT GuestSession::environmentScheduleSet(const com::Utf8Str &aName, const com::Utf8Str &aValue)
3487{
3488 AutoCaller autoCaller(this);
3489 if (FAILED(autoCaller.rc())) return autoCaller.rc();
3490
3491 HRESULT hrc;
3492 if (RT_LIKELY(aName.isNotEmpty()))
3493 {
3494 if (RT_LIKELY(strchr(aName.c_str(), '=') == NULL))
3495 {
3496 LogFlowThisFuncEnter();
3497
3498 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3499 int vrc = mData.mEnvironmentChanges.setVariable(aName, aValue);
3500 if (RT_SUCCESS(vrc))
3501 hrc = S_OK;
3502 else
3503 hrc = setErrorVrc(vrc);
3504 }
3505 else
3506 hrc = setError(E_INVALIDARG, tr("The equal char is not allowed in environment variable names"));
3507 }
3508 else
3509 hrc = setError(E_INVALIDARG, tr("No variable name specified"));
3510
3511 LogFlowThisFuncLeave();
3512 return hrc;
3513}
3514
3515HRESULT GuestSession::environmentScheduleUnset(const com::Utf8Str &aName)
3516{
3517 AutoCaller autoCaller(this);
3518 if (FAILED(autoCaller.rc())) return autoCaller.rc();
3519
3520 HRESULT hrc;
3521 if (RT_LIKELY(aName.isNotEmpty()))
3522 {
3523 if (RT_LIKELY(strchr(aName.c_str(), '=') == NULL))
3524 {
3525 LogFlowThisFuncEnter();
3526
3527 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
3528 int vrc = mData.mEnvironmentChanges.unsetVariable(aName);
3529 if (RT_SUCCESS(vrc))
3530 hrc = S_OK;
3531 else
3532 hrc = setErrorVrc(vrc);
3533 }
3534 else
3535 hrc = setError(E_INVALIDARG, tr("The equal char is not allowed in environment variable names"));
3536 }
3537 else
3538 hrc = setError(E_INVALIDARG, tr("No variable name specified"));
3539
3540 LogFlowThisFuncLeave();
3541 return hrc;
3542}
3543
3544HRESULT GuestSession::environmentGetBaseVariable(const com::Utf8Str &aName, com::Utf8Str &aValue)
3545{
3546 AutoCaller autoCaller(this);
3547 if (FAILED(autoCaller.rc())) return autoCaller.rc();
3548
3549 HRESULT hrc;
3550 if (RT_LIKELY(aName.isNotEmpty()))
3551 {
3552 if (RT_LIKELY(strchr(aName.c_str(), '=') == NULL))
3553 {
3554 LogFlowThisFuncEnter();
3555
3556 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3557 if (mData.mpBaseEnvironment)
3558 {
3559 int vrc = mData.mpBaseEnvironment->getVariable(aName, &aValue);
3560 if (RT_SUCCESS(vrc))
3561 hrc = S_OK;
3562 else
3563 hrc = setErrorVrc(vrc);
3564 }
3565 else if (mData.mProtocolVersion < 99999)
3566 hrc = setError(VBOX_E_NOT_SUPPORTED, tr("The base environment feature is not supported by the guest additions"));
3567 else
3568 hrc = setError(VBOX_E_INVALID_OBJECT_STATE, tr("The base environment has not yet been reported by the guest"));
3569 }
3570 else
3571 hrc = setError(E_INVALIDARG, tr("The equal char is not allowed in environment variable names"));
3572 }
3573 else
3574 hrc = setError(E_INVALIDARG, tr("No variable name specified"));
3575
3576 LogFlowThisFuncLeave();
3577 return hrc;
3578}
3579
3580HRESULT GuestSession::environmentDoesBaseVariableExist(const com::Utf8Str &aName, BOOL *aExists)
3581{
3582 AutoCaller autoCaller(this);
3583 if (FAILED(autoCaller.rc())) return autoCaller.rc();
3584
3585 *aExists = FALSE;
3586
3587 HRESULT hrc;
3588 if (RT_LIKELY(aName.isNotEmpty()))
3589 {
3590 if (RT_LIKELY(strchr(aName.c_str(), '=') == NULL))
3591 {
3592 LogFlowThisFuncEnter();
3593
3594 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
3595 if (mData.mpBaseEnvironment)
3596 {
3597 hrc = S_OK;
3598 *aExists = mData.mpBaseEnvironment->doesVariableExist(aName);
3599 }
3600 else if (mData.mProtocolVersion < 99999)
3601 hrc = setError(VBOX_E_NOT_SUPPORTED, tr("The base environment feature is not supported by the guest additions"));
3602 else
3603 hrc = setError(VBOX_E_INVALID_OBJECT_STATE, tr("The base environment has not yet been reported by the guest"));
3604 }
3605 else
3606 hrc = setError(E_INVALIDARG, tr("The equal char is not allowed in environment variable names"));
3607 }
3608 else
3609 hrc = setError(E_INVALIDARG, tr("No variable name specified"));
3610
3611 LogFlowThisFuncLeave();
3612 return hrc;
3613}
3614
3615HRESULT GuestSession::fileCreateTemp(const com::Utf8Str &aTemplateName, ULONG aMode, const com::Utf8Str &aPath, BOOL aSecure,
3616 ComPtr<IGuestFile> &aFile)
3617{
3618 RT_NOREF(aTemplateName, aMode, aPath, aSecure, aFile);
3619 ReturnComNotImplemented();
3620}
3621
3622HRESULT GuestSession::fileExists(const com::Utf8Str &aPath, BOOL aFollowSymlinks, BOOL *aExists)
3623{
3624 AutoCaller autoCaller(this);
3625 if (FAILED(autoCaller.rc())) return autoCaller.rc();
3626
3627 /* By default we return non-existent. */
3628 *aExists = FALSE;
3629
3630 if (RT_UNLIKELY((aPath.c_str()) == NULL || *(aPath.c_str()) == '\0'))
3631 return S_OK;
3632
3633 HRESULT hrc = i_isStartedExternal();
3634 if (FAILED(hrc))
3635 return hrc;
3636
3637 LogFlowThisFuncEnter();
3638
3639 GuestFsObjData objData; int rcGuest;
3640 int vrc = i_fileQueryInfo(aPath, RT_BOOL(aFollowSymlinks), objData, &rcGuest);
3641 if (RT_SUCCESS(vrc))
3642 {
3643 *aExists = TRUE;
3644 return S_OK;
3645 }
3646
3647 switch (vrc)
3648 {
3649 case VERR_GSTCTL_GUEST_ERROR:
3650 {
3651 switch (rcGuest)
3652 {
3653 case VERR_PATH_NOT_FOUND:
3654 RT_FALL_THROUGH();
3655 case VERR_FILE_NOT_FOUND:
3656 break;
3657
3658 default:
3659 hrc = GuestProcess::i_setErrorExternal(this, rcGuest);
3660 break;
3661 }
3662
3663 break;
3664 }
3665
3666 case VERR_NOT_A_FILE:
3667 break;
3668
3669 default:
3670 hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Querying file information for \"%s\" failed: %Rrc"),
3671 aPath.c_str(), vrc);
3672 break;
3673 }
3674
3675 return hrc;
3676}
3677
3678HRESULT GuestSession::fileOpen(const com::Utf8Str &aPath, FileAccessMode_T aAccessMode, FileOpenAction_T aOpenAction,
3679 ULONG aCreationMode, ComPtr<IGuestFile> &aFile)
3680{
3681 LogFlowThisFuncEnter();
3682
3683 const std::vector<FileOpenExFlag_T> EmptyFlags;
3684 return fileOpenEx(aPath, aAccessMode, aOpenAction, FileSharingMode_All, aCreationMode, EmptyFlags, aFile);
3685}
3686
3687HRESULT GuestSession::fileOpenEx(const com::Utf8Str &aPath, FileAccessMode_T aAccessMode, FileOpenAction_T aOpenAction,
3688 FileSharingMode_T aSharingMode, ULONG aCreationMode,
3689 const std::vector<FileOpenExFlag_T> &aFlags, ComPtr<IGuestFile> &aFile)
3690{
3691 AutoCaller autoCaller(this);
3692 if (FAILED(autoCaller.rc())) return autoCaller.rc();
3693
3694 if (RT_UNLIKELY((aPath.c_str()) == NULL || *(aPath.c_str()) == '\0'))
3695 return setError(E_INVALIDARG, tr("No file to open specified"));
3696
3697 HRESULT hrc = i_isStartedExternal();
3698 if (FAILED(hrc))
3699 return hrc;
3700
3701 LogFlowThisFuncEnter();
3702
3703 GuestFileOpenInfo openInfo;
3704 openInfo.mFilename = aPath;
3705 openInfo.mCreationMode = aCreationMode;
3706
3707 /* Validate aAccessMode. */
3708 switch (aAccessMode)
3709 {
3710 case FileAccessMode_ReadOnly:
3711 RT_FALL_THRU();
3712 case FileAccessMode_WriteOnly:
3713 RT_FALL_THRU();
3714 case FileAccessMode_ReadWrite:
3715 openInfo.mAccessMode = aAccessMode;
3716 break;
3717 case FileAccessMode_AppendOnly:
3718 RT_FALL_THRU();
3719 case FileAccessMode_AppendRead:
3720 return setError(E_NOTIMPL, tr("Append access modes are not yet implemented"));
3721 default:
3722 return setError(E_INVALIDARG, tr("Unknown FileAccessMode value %u (%#x)"), aAccessMode, aAccessMode);
3723 }
3724
3725 /* Validate aOpenAction to the old format. */
3726 switch (aOpenAction)
3727 {
3728 case FileOpenAction_OpenExisting:
3729 RT_FALL_THRU();
3730 case FileOpenAction_OpenOrCreate:
3731 RT_FALL_THRU();
3732 case FileOpenAction_CreateNew:
3733 RT_FALL_THRU();
3734 case FileOpenAction_CreateOrReplace:
3735 RT_FALL_THRU();
3736 case FileOpenAction_OpenExistingTruncated:
3737 RT_FALL_THRU();
3738 case FileOpenAction_AppendOrCreate:
3739 openInfo.mOpenAction = aOpenAction;
3740 break;
3741 default:
3742 return setError(E_INVALIDARG, tr("Unknown FileOpenAction value %u (%#x)"), aAccessMode, aAccessMode);
3743 }
3744
3745 /* Validate aSharingMode. */
3746 switch (aSharingMode)
3747 {
3748 case FileSharingMode_All:
3749 openInfo.mSharingMode = aSharingMode;
3750 break;
3751 case FileSharingMode_Read:
3752 case FileSharingMode_Write:
3753 case FileSharingMode_ReadWrite:
3754 case FileSharingMode_Delete:
3755 case FileSharingMode_ReadDelete:
3756 case FileSharingMode_WriteDelete:
3757 return setError(E_NOTIMPL, tr("Only FileSharingMode_All is currently implemented"));
3758
3759 default:
3760 return setError(E_INVALIDARG, tr("Unknown FileOpenAction value %u (%#x)"), aAccessMode, aAccessMode);
3761 }
3762
3763 /* Combine and validate flags. */
3764 uint32_t fOpenEx = 0;
3765 for (size_t i = 0; i < aFlags.size(); i++)
3766 fOpenEx = aFlags[i];
3767 if (fOpenEx)
3768 return setError(E_INVALIDARG, tr("Unsupported FileOpenExFlag value(s) in aFlags (%#x)"), fOpenEx);
3769 openInfo.mfOpenEx = fOpenEx;
3770
3771 ComObjPtr <GuestFile> pFile;
3772 int rcGuest;
3773 int vrc = i_fileOpenEx(aPath, aAccessMode, aOpenAction, aSharingMode, aCreationMode, aFlags, pFile, &rcGuest);
3774 if (RT_SUCCESS(vrc))
3775 /* Return directory object to the caller. */
3776 hrc = pFile.queryInterfaceTo(aFile.asOutParam());
3777 else
3778 {
3779 switch (vrc)
3780 {
3781 case VERR_NOT_SUPPORTED:
3782 hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
3783 tr("Handling guest files not supported by installed Guest Additions"));
3784 break;
3785
3786 case VERR_GSTCTL_GUEST_ERROR:
3787 hrc = GuestFile::i_setErrorExternal(this, rcGuest);
3788 break;
3789
3790 default:
3791 hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Opening guest file \"%s\" failed: %Rrc"), aPath.c_str(), vrc);
3792 break;
3793 }
3794 }
3795
3796 return hrc;
3797}
3798
3799HRESULT GuestSession::fileQuerySize(const com::Utf8Str &aPath, BOOL aFollowSymlinks, LONG64 *aSize)
3800{
3801 AutoCaller autoCaller(this);
3802 if (FAILED(autoCaller.rc())) return autoCaller.rc();
3803
3804 if (aPath.isEmpty())
3805 return setError(E_INVALIDARG, tr("No path specified"));
3806
3807 HRESULT hrc = i_isStartedExternal();
3808 if (FAILED(hrc))
3809 return hrc;
3810
3811 int64_t llSize; int rcGuest;
3812 int vrc = i_fileQuerySize(aPath, aFollowSymlinks != FALSE, &llSize, &rcGuest);
3813 if (RT_SUCCESS(vrc))
3814 {
3815 *aSize = llSize;
3816 }
3817 else
3818 {
3819 if (GuestProcess::i_isGuestError(vrc))
3820 hrc = GuestProcess::i_setErrorExternal(this, rcGuest);
3821 else
3822 hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Querying file size failed: %Rrc"), vrc);
3823 }
3824
3825 return hrc;
3826}
3827
3828HRESULT GuestSession::fsObjExists(const com::Utf8Str &aPath, BOOL aFollowSymlinks, BOOL *aExists)
3829{
3830 AutoCaller autoCaller(this);
3831 if (FAILED(autoCaller.rc())) return autoCaller.rc();
3832
3833 if (aPath.isEmpty())
3834 return setError(E_INVALIDARG, tr("No path specified"));
3835
3836 HRESULT hrc = i_isStartedExternal();
3837 if (FAILED(hrc))
3838 return hrc;
3839
3840 LogFlowThisFunc(("aPath=%s, aFollowSymlinks=%RTbool\n", aPath.c_str(), RT_BOOL(aFollowSymlinks)));
3841
3842 *aExists = false;
3843
3844 GuestFsObjData objData;
3845 int rcGuest;
3846 int vrc = i_fsQueryInfo(aPath, aFollowSymlinks != FALSE, objData, &rcGuest);
3847 if (RT_SUCCESS(vrc))
3848 {
3849 *aExists = TRUE;
3850 }
3851 else
3852 {
3853 if (GuestProcess::i_isGuestError(vrc))
3854 {
3855 if ( rcGuest == VERR_NOT_A_FILE
3856 || rcGuest == VERR_PATH_NOT_FOUND
3857 || rcGuest == VERR_FILE_NOT_FOUND
3858 || rcGuest == VERR_INVALID_NAME)
3859 {
3860 hrc = S_OK; /* Ignore these vrc values. */
3861 }
3862 else
3863 hrc = GuestProcess::i_setErrorExternal(this, rcGuest);
3864 }
3865 else
3866 hrc = setErrorVrc(vrc, tr("Querying file information for \"%s\" failed: %Rrc"), aPath.c_str(), vrc);
3867 }
3868
3869 return hrc;
3870}
3871
3872HRESULT GuestSession::fsObjQueryInfo(const com::Utf8Str &aPath, BOOL aFollowSymlinks, ComPtr<IGuestFsObjInfo> &aInfo)
3873{
3874 AutoCaller autoCaller(this);
3875 if (FAILED(autoCaller.rc())) return autoCaller.rc();
3876
3877 if (aPath.isEmpty())
3878 return setError(E_INVALIDARG, tr("No path specified"));
3879
3880 HRESULT hrc = i_isStartedExternal();
3881 if (FAILED(hrc))
3882 return hrc;
3883
3884 LogFlowThisFunc(("aPath=%s, aFollowSymlinks=%RTbool\n", aPath.c_str(), RT_BOOL(aFollowSymlinks)));
3885
3886 GuestFsObjData Info; int rcGuest;
3887 int vrc = i_fsQueryInfo(aPath, aFollowSymlinks != FALSE, Info, &rcGuest);
3888 if (RT_SUCCESS(vrc))
3889 {
3890 ComObjPtr<GuestFsObjInfo> ptrFsObjInfo;
3891 hrc = ptrFsObjInfo.createObject();
3892 if (SUCCEEDED(hrc))
3893 {
3894 vrc = ptrFsObjInfo->init(Info);
3895 if (RT_SUCCESS(vrc))
3896 hrc = ptrFsObjInfo.queryInterfaceTo(aInfo.asOutParam());
3897 else
3898 hrc = setErrorVrc(vrc);
3899 }
3900 }
3901 else
3902 {
3903 if (GuestProcess::i_isGuestError(vrc))
3904 hrc = GuestProcess::i_setErrorExternal(this, rcGuest);
3905 else
3906 hrc = setErrorVrc(vrc, tr("Querying file information for \"%s\" failed: %Rrc"), aPath.c_str(), vrc);
3907 }
3908
3909 return hrc;
3910}
3911
3912HRESULT GuestSession::fsObjRemove(const com::Utf8Str &aPath)
3913{
3914 AutoCaller autoCaller(this);
3915 if (FAILED(autoCaller.rc())) return autoCaller.rc();
3916
3917 if (RT_UNLIKELY(aPath.isEmpty()))
3918 return setError(E_INVALIDARG, tr("No path specified"));
3919
3920 HRESULT hrc = i_isStartedExternal();
3921 if (FAILED(hrc))
3922 return hrc;
3923
3924 LogFlowThisFunc(("aPath=%s\n", aPath.c_str()));
3925
3926 int rcGuest;
3927 int vrc = i_fileRemove(aPath, &rcGuest);
3928 if (RT_FAILURE(vrc))
3929 {
3930 if (GuestProcess::i_isGuestError(vrc))
3931 hrc = GuestProcess::i_setErrorExternal(this, rcGuest);
3932 else
3933 hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Removing file \"%s\" failed: %Rrc"), aPath.c_str(), vrc);
3934 }
3935
3936 return hrc;
3937}
3938
3939HRESULT GuestSession::fsObjRemoveArray(const std::vector<com::Utf8Str> &aPaths, ComPtr<IProgress> &aProgress)
3940{
3941 AutoCaller autoCaller(this);
3942 if (FAILED(autoCaller.rc())) return autoCaller.rc();
3943
3944 RT_NOREF(aPaths, aProgress);
3945
3946 return E_NOTIMPL;
3947}
3948
3949HRESULT GuestSession::fsObjRename(const com::Utf8Str &aSource,
3950 const com::Utf8Str &aDestination,
3951 const std::vector<FsObjRenameFlag_T> &aFlags)
3952{
3953 AutoCaller autoCaller(this);
3954 if (FAILED(autoCaller.rc())) return autoCaller.rc();
3955
3956 if (RT_UNLIKELY(aSource.isEmpty()))
3957 return setError(E_INVALIDARG, tr("No source path specified"));
3958
3959 if (RT_UNLIKELY(aDestination.isEmpty()))
3960 return setError(E_INVALIDARG, tr("No destination path specified"));
3961
3962 HRESULT hrc = i_isStartedExternal();
3963 if (FAILED(hrc))
3964 return hrc;
3965
3966 /* Combine, validate and convert flags. */
3967 uint32_t fApiFlags = 0;
3968 for (size_t i = 0; i < aFlags.size(); i++)
3969 fApiFlags |= aFlags[i];
3970 if (fApiFlags & ~((uint32_t)FsObjRenameFlag_NoReplace | (uint32_t)FsObjRenameFlag_Replace))
3971 return setError(E_INVALIDARG, tr("Unknown rename flag: %#x"), fApiFlags);
3972
3973 LogFlowThisFunc(("aSource=%s, aDestination=%s\n", aSource.c_str(), aDestination.c_str()));
3974
3975 AssertCompile(FsObjRenameFlag_NoReplace == 0);
3976 AssertCompile(FsObjRenameFlag_Replace != 0);
3977 uint32_t fBackend;
3978 if ((fApiFlags & ((uint32_t)FsObjRenameFlag_NoReplace | (uint32_t)FsObjRenameFlag_Replace)) == FsObjRenameFlag_Replace)
3979 fBackend = PATHRENAME_FLAG_REPLACE;
3980 else
3981 fBackend = PATHRENAME_FLAG_NO_REPLACE;
3982
3983 /* Call worker to do the job. */
3984 int rcGuest;
3985 int vrc = i_pathRename(aSource, aDestination, fBackend, &rcGuest);
3986 if (RT_FAILURE(vrc))
3987 {
3988 switch (vrc)
3989 {
3990 case VERR_NOT_SUPPORTED:
3991 hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
3992 tr("Handling renaming guest directories not supported by installed Guest Additions"));
3993 break;
3994
3995 case VERR_GSTCTL_GUEST_ERROR:
3996 hrc = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Renaming guest directory failed: %Rrc"), rcGuest);
3997 break;
3998
3999 default:
4000 hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Renaming guest directory \"%s\" failed: %Rrc"),
4001 aSource.c_str(), vrc);
4002 break;
4003 }
4004 }
4005
4006 return hrc;
4007}
4008
4009HRESULT GuestSession::fsObjMove(const com::Utf8Str &aSource, const com::Utf8Str &aDestination,
4010 const std::vector<FsObjMoveFlag_T> &aFlags, ComPtr<IProgress> &aProgress)
4011{
4012 RT_NOREF(aSource, aDestination, aFlags, aProgress);
4013 ReturnComNotImplemented();
4014}
4015
4016HRESULT GuestSession::fsObjMoveArray(const std::vector<com::Utf8Str> &aSource,
4017 const com::Utf8Str &aDestination,
4018 const std::vector<FsObjMoveFlag_T> &aFlags,
4019 ComPtr<IProgress> &aProgress)
4020{
4021 RT_NOREF(aSource, aDestination, aFlags, aProgress);
4022 ReturnComNotImplemented();
4023}
4024
4025HRESULT GuestSession::fsObjCopyArray(const std::vector<com::Utf8Str> &aSource,
4026 const com::Utf8Str &aDestination,
4027 const std::vector<FileCopyFlag_T> &aFlags,
4028 ComPtr<IProgress> &aProgress)
4029{
4030 RT_NOREF(aSource, aDestination, aFlags, aProgress);
4031 ReturnComNotImplemented();
4032}
4033
4034HRESULT GuestSession::fsObjSetACL(const com::Utf8Str &aPath, BOOL aFollowSymlinks, const com::Utf8Str &aAcl, ULONG aMode)
4035{
4036 RT_NOREF(aPath, aFollowSymlinks, aAcl, aMode);
4037 ReturnComNotImplemented();
4038}
4039
4040
4041HRESULT GuestSession::processCreate(const com::Utf8Str &aExecutable, const std::vector<com::Utf8Str> &aArguments,
4042 const std::vector<com::Utf8Str> &aEnvironment,
4043 const std::vector<ProcessCreateFlag_T> &aFlags,
4044 ULONG aTimeoutMS, ComPtr<IGuestProcess> &aGuestProcess)
4045{
4046 LogFlowThisFuncEnter();
4047
4048 std::vector<LONG> affinityIgnored;
4049 return processCreateEx(aExecutable, aArguments, aEnvironment, aFlags, aTimeoutMS, ProcessPriority_Default,
4050 affinityIgnored, aGuestProcess);
4051}
4052
4053HRESULT GuestSession::processCreateEx(const com::Utf8Str &aExecutable, const std::vector<com::Utf8Str> &aArguments,
4054 const std::vector<com::Utf8Str> &aEnvironment,
4055 const std::vector<ProcessCreateFlag_T> &aFlags, ULONG aTimeoutMS,
4056 ProcessPriority_T aPriority, const std::vector<LONG> &aAffinity,
4057 ComPtr<IGuestProcess> &aGuestProcess)
4058{
4059 AutoCaller autoCaller(this);
4060 if (FAILED(autoCaller.rc())) return autoCaller.rc();
4061
4062 HRESULT hr = i_isStartedExternal();
4063 if (FAILED(hr))
4064 return hr;
4065
4066 /*
4067 * Must have an executable to execute. If none is given, we try use the
4068 * zero'th argument.
4069 */
4070 const char *pszExecutable = aExecutable.c_str();
4071 if (RT_UNLIKELY(pszExecutable == NULL || *pszExecutable == '\0'))
4072 {
4073 if (aArguments.size() > 0)
4074 pszExecutable = aArguments[0].c_str();
4075 if (pszExecutable == NULL || *pszExecutable == '\0')
4076 return setError(E_INVALIDARG, tr("No command to execute specified"));
4077 }
4078
4079 /* The rest of the input is being validated in i_processCreateEx(). */
4080
4081 LogFlowThisFuncEnter();
4082
4083 /*
4084 * Build the process startup info.
4085 */
4086 GuestProcessStartupInfo procInfo;
4087
4088 /* Executable and arguments. */
4089 procInfo.mExecutable = pszExecutable;
4090 if (aArguments.size())
4091 for (size_t i = 0; i < aArguments.size(); i++)
4092 procInfo.mArguments.push_back(aArguments[i]);
4093
4094 /* Combine the environment changes associated with the ones passed in by
4095 the caller, giving priority to the latter. The changes are putenv style
4096 and will be applied to the standard environment for the guest user. */
4097 int vrc = procInfo.mEnvironmentChanges.copy(mData.mEnvironmentChanges);
4098 if (RT_SUCCESS(vrc))
4099 vrc = procInfo.mEnvironmentChanges.applyPutEnvArray(aEnvironment);
4100 if (RT_SUCCESS(vrc))
4101 {
4102 /* Convert the flag array into a mask. */
4103 if (aFlags.size())
4104 for (size_t i = 0; i < aFlags.size(); i++)
4105 procInfo.mFlags |= aFlags[i];
4106
4107 procInfo.mTimeoutMS = aTimeoutMS;
4108
4109 /** @todo use RTCPUSET instead of archaic 64-bit variables! */
4110 if (aAffinity.size())
4111 for (size_t i = 0; i < aAffinity.size(); i++)
4112 if (aAffinity[i])
4113 procInfo.mAffinity |= (uint64_t)1 << i;
4114
4115 procInfo.mPriority = aPriority;
4116
4117 /*
4118 * Create a guest process object.
4119 */
4120 ComObjPtr<GuestProcess> pProcess;
4121 vrc = i_processCreateEx(procInfo, pProcess);
4122 if (RT_SUCCESS(vrc))
4123 {
4124 ComPtr<IGuestProcess> pIProcess;
4125 hr = pProcess.queryInterfaceTo(pIProcess.asOutParam());
4126 if (SUCCEEDED(hr))
4127 {
4128 /*
4129 * Start the process.
4130 */
4131 vrc = pProcess->i_startProcessAsync();
4132 if (RT_SUCCESS(vrc))
4133 {
4134 aGuestProcess = pIProcess;
4135
4136 LogFlowFuncLeaveRC(vrc);
4137 return S_OK;
4138 }
4139
4140 hr = setErrorVrc(vrc, tr("Failed to start guest process: %Rrc"), vrc);
4141 }
4142 }
4143 else if (vrc == VERR_GSTCTL_MAX_CID_OBJECTS_REACHED)
4144 hr = setErrorVrc(vrc, tr("Maximum number of concurrent guest processes per session (%u) reached"),
4145 VBOX_GUESTCTRL_MAX_OBJECTS);
4146 else
4147 hr = setErrorVrc(vrc, tr("Failed to create guest process object: %Rrc"), vrc);
4148 }
4149 else
4150 hr = setErrorVrc(vrc, tr("Failed to set up the environment: %Rrc"), vrc);
4151
4152 LogFlowFuncLeaveRC(vrc);
4153 return hr;
4154}
4155
4156HRESULT GuestSession::processGet(ULONG aPid, ComPtr<IGuestProcess> &aGuestProcess)
4157
4158{
4159 AutoCaller autoCaller(this);
4160 if (FAILED(autoCaller.rc())) return autoCaller.rc();
4161
4162 if (aPid == 0)
4163 return setError(E_INVALIDARG, tr("No valid process ID (PID) specified"));
4164
4165 LogFlowThisFunc(("PID=%RU32\n", aPid));
4166
4167 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
4168
4169 HRESULT hr = S_OK;
4170
4171 ComObjPtr<GuestProcess> pProcess;
4172 int rc = i_processGetByPID(aPid, &pProcess);
4173 if (RT_FAILURE(rc))
4174 hr = setError(E_INVALIDARG, tr("No process with PID %RU32 found"), aPid);
4175
4176 /* This will set (*aProcess) to NULL if pProgress is NULL. */
4177 HRESULT hr2 = pProcess.queryInterfaceTo(aGuestProcess.asOutParam());
4178 if (SUCCEEDED(hr))
4179 hr = hr2;
4180
4181 LogFlowThisFunc(("aProcess=%p, hr=%Rhrc\n", (IGuestProcess*)aGuestProcess, hr));
4182 return hr;
4183}
4184
4185HRESULT GuestSession::symlinkCreate(const com::Utf8Str &aSource, const com::Utf8Str &aTarget, SymlinkType_T aType)
4186{
4187 RT_NOREF(aSource, aTarget, aType);
4188 ReturnComNotImplemented();
4189}
4190
4191HRESULT GuestSession::symlinkExists(const com::Utf8Str &aSymlink, BOOL *aExists)
4192
4193{
4194 RT_NOREF(aSymlink, aExists);
4195 ReturnComNotImplemented();
4196}
4197
4198HRESULT GuestSession::symlinkRead(const com::Utf8Str &aSymlink, const std::vector<SymlinkReadFlag_T> &aFlags,
4199 com::Utf8Str &aTarget)
4200{
4201 RT_NOREF(aSymlink, aFlags, aTarget);
4202 ReturnComNotImplemented();
4203}
4204
4205HRESULT GuestSession::waitFor(ULONG aWaitFor, ULONG aTimeoutMS, GuestSessionWaitResult_T *aReason)
4206{
4207 AutoCaller autoCaller(this);
4208 if (FAILED(autoCaller.rc())) return autoCaller.rc();
4209
4210 /* Note: No call to i_isReadyExternal() needed here, as the session might not has been started (yet). */
4211
4212 LogFlowThisFuncEnter();
4213
4214 HRESULT hrc = S_OK;
4215
4216 /*
4217 * Note: Do not hold any locks here while waiting!
4218 */
4219 int rcGuest; GuestSessionWaitResult_T waitResult;
4220 int vrc = i_waitFor(aWaitFor, aTimeoutMS, waitResult, &rcGuest);
4221 if (RT_SUCCESS(vrc))
4222 *aReason = waitResult;
4223 else
4224 {
4225 switch (vrc)
4226 {
4227 case VERR_GSTCTL_GUEST_ERROR:
4228 hrc = GuestSession::i_setErrorExternal(this, rcGuest);
4229 break;
4230
4231 case VERR_TIMEOUT:
4232 *aReason = GuestSessionWaitResult_Timeout;
4233 break;
4234
4235 default:
4236 {
4237 const char *pszSessionName = mData.mSession.mName.c_str();
4238 hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc,
4239 tr("Waiting for guest session \"%s\" failed: %Rrc"),
4240 pszSessionName ? pszSessionName : tr("Unnamed"), vrc);
4241 break;
4242 }
4243 }
4244 }
4245
4246 LogFlowFuncLeaveRC(vrc);
4247 return hrc;
4248}
4249
4250HRESULT GuestSession::waitForArray(const std::vector<GuestSessionWaitForFlag_T> &aWaitFor, ULONG aTimeoutMS,
4251 GuestSessionWaitResult_T *aReason)
4252{
4253 AutoCaller autoCaller(this);
4254 if (FAILED(autoCaller.rc())) return autoCaller.rc();
4255
4256 /* Note: No call to i_isReadyExternal() needed here, as the session might not has been started (yet). */
4257
4258 LogFlowThisFuncEnter();
4259
4260 /*
4261 * Note: Do not hold any locks here while waiting!
4262 */
4263 uint32_t fWaitFor = GuestSessionWaitForFlag_None;
4264 for (size_t i = 0; i < aWaitFor.size(); i++)
4265 fWaitFor |= aWaitFor[i];
4266
4267 return WaitFor(fWaitFor, aTimeoutMS, aReason);
4268}
Note: See TracBrowser for help on using the repository browser.

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