/* $Id: GuestSessionImpl.cpp $ */ /** @file * VirtualBox Main - Guest session handling. */ /* * Copyright (C) 2012-2022 Oracle and/or its affiliates. * * This file is part of VirtualBox base platform packages, as * available from https://www.virtualbox.org. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation, in version 3 of the * License. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * * SPDX-License-Identifier: GPL-3.0-only */ /********************************************************************************************************************************* * Header Files * *********************************************************************************************************************************/ #define LOG_GROUP LOG_GROUP_MAIN_GUESTSESSION #include "LoggingNew.h" #include "GuestImpl.h" #ifndef VBOX_WITH_GUEST_CONTROL # error "VBOX_WITH_GUEST_CONTROL must defined in this file" #endif #include "GuestSessionImpl.h" #include "GuestSessionImplTasks.h" #include "GuestCtrlImplPrivate.h" #include "VirtualBoxErrorInfoImpl.h" #include "Global.h" #include "AutoCaller.h" #include "ProgressImpl.h" #include "VBoxEvents.h" #include "VMMDev.h" #include "ThreadTask.h" #include /* For auto_ptr. */ #include /* For unconst(). */ #include #include #include /* For CopyTo/From. */ #include #include #include #include #include /** * Base class representing an internal * asynchronous session task. */ class GuestSessionTaskInternal : public ThreadTask { public: GuestSessionTaskInternal(GuestSession *pSession) : ThreadTask("GenericGuestSessionTaskInternal") , mSession(pSession) , mRC(VINF_SUCCESS) { } virtual ~GuestSessionTaskInternal(void) { } /** Returns the last set result code. */ int rc(void) const { return mRC; } /** Returns whether the last set result code indicates success or not. */ bool isOk(void) const { return RT_SUCCESS(mRC); } /** Returns the task's guest session object. */ const ComObjPtr &Session(void) const { return mSession; } protected: /** Guest session the task belongs to. */ const ComObjPtr mSession; /** The last set result code. */ int mRC; }; /** * Class for asynchronously starting a guest session. */ class GuestSessionTaskInternalStart : public GuestSessionTaskInternal { public: GuestSessionTaskInternalStart(GuestSession *pSession) : GuestSessionTaskInternal(pSession) { m_strTaskName = "gctlSesStart"; } void handler() { /* Ignore rc */ GuestSession::i_startSessionThreadTask(this); } }; /** * Internal listener class to serve events in an * active manner, e.g. without polling delays. */ class GuestSessionListener { public: GuestSessionListener(void) { } virtual ~GuestSessionListener(void) { } HRESULT init(GuestSession *pSession) { AssertPtrReturn(pSession, E_POINTER); mSession = pSession; return S_OK; } void uninit(void) { mSession = NULL; } STDMETHOD(HandleEvent)(VBoxEventType_T aType, IEvent *aEvent) { switch (aType) { case VBoxEventType_OnGuestSessionStateChanged: { AssertPtrReturn(mSession, E_POINTER); int rc2 = mSession->signalWaitEvent(aType, aEvent); RT_NOREF(rc2); #ifdef DEBUG_andy LogFlowFunc(("Signalling events of type=%RU32, session=%p resulted in rc=%Rrc\n", aType, mSession, rc2)); #endif break; } default: AssertMsgFailed(("Unhandled event %RU32\n", aType)); break; } return S_OK; } private: GuestSession *mSession; }; typedef ListenerImpl GuestSessionListenerImpl; VBOX_LISTENER_DECLARE(GuestSessionListenerImpl) // constructor / destructor ///////////////////////////////////////////////////////////////////////////// DEFINE_EMPTY_CTOR_DTOR(GuestSession) HRESULT GuestSession::FinalConstruct(void) { LogFlowThisFuncEnter(); return BaseFinalConstruct(); } void GuestSession::FinalRelease(void) { LogFlowThisFuncEnter(); uninit(); BaseFinalRelease(); LogFlowThisFuncLeave(); } // public initializer/uninitializer for internal purposes only ///////////////////////////////////////////////////////////////////////////// /** * Initializes a guest session but does *not* open in on the guest side * yet. This needs to be done via the openSession() / openSessionAsync calls. * * @returns VBox status code. * @param pGuest Guest object the guest session belongs to. * @param ssInfo Guest session startup info to use. * @param guestCreds Guest credentials to use for starting a guest session * with a specific guest account. */ int GuestSession::init(Guest *pGuest, const GuestSessionStartupInfo &ssInfo, const GuestCredentials &guestCreds) { LogFlowThisFunc(("pGuest=%p, ssInfo=%p, guestCreds=%p\n", pGuest, &ssInfo, &guestCreds)); /* Enclose the state transition NotReady->InInit->Ready. */ AutoInitSpan autoInitSpan(this); AssertReturn(autoInitSpan.isOk(), VERR_OBJECT_DESTROYED); AssertPtrReturn(pGuest, VERR_INVALID_POINTER); /* * Initialize our data members from the input. */ mParent = pGuest; /* Copy over startup info. */ /** @todo Use an overloaded copy operator. Later. */ mData.mSession.mID = ssInfo.mID; mData.mSession.mIsInternal = ssInfo.mIsInternal; mData.mSession.mName = ssInfo.mName; mData.mSession.mOpenFlags = ssInfo.mOpenFlags; mData.mSession.mOpenTimeoutMS = ssInfo.mOpenTimeoutMS; /* Copy over session credentials. */ /** @todo Use an overloaded copy operator. Later. */ mData.mCredentials.mUser = guestCreds.mUser; mData.mCredentials.mPassword = guestCreds.mPassword; mData.mCredentials.mDomain = guestCreds.mDomain; /* Initialize the remainder of the data. */ mData.mRC = VINF_SUCCESS; mData.mStatus = GuestSessionStatus_Undefined; mData.mpBaseEnvironment = NULL; /* * Register an object for the session itself to clearly * distinguish callbacks which are for this session directly, or for * objects (like files, directories, ...) which are bound to this session. */ int rc = i_objectRegister(NULL /* pObject */, SESSIONOBJECTTYPE_SESSION, &mData.mObjectID); if (RT_SUCCESS(rc)) { rc = mData.mEnvironmentChanges.initChangeRecord(pGuest->i_isGuestInWindowsNtFamily() ? RTENV_CREATE_F_ALLOW_EQUAL_FIRST_IN_VAR : 0); if (RT_SUCCESS(rc)) { rc = RTCritSectInit(&mWaitEventCritSect); AssertRC(rc); } } if (RT_SUCCESS(rc)) rc = i_determineProtocolVersion(); if (RT_SUCCESS(rc)) { /* * */ HRESULT hr = unconst(mEventSource).createObject(); if (SUCCEEDED(hr)) hr = mEventSource->init(); if (SUCCEEDED(hr)) { try { GuestSessionListener *pListener = new GuestSessionListener(); ComObjPtr thisListener; hr = thisListener.createObject(); if (SUCCEEDED(hr)) hr = thisListener->init(pListener, this); /* thisListener takes ownership of pListener. */ if (SUCCEEDED(hr)) { com::SafeArray eventTypes; eventTypes.push_back(VBoxEventType_OnGuestSessionStateChanged); hr = mEventSource->RegisterListener(thisListener, ComSafeArrayAsInParam(eventTypes), TRUE /* Active listener */); if (SUCCEEDED(hr)) { mLocalListener = thisListener; /* * Mark this object as operational and return success. */ autoInitSpan.setSucceeded(); LogFlowThisFunc(("mName=%s mID=%RU32 mIsInternal=%RTbool rc=VINF_SUCCESS\n", mData.mSession.mName.c_str(), mData.mSession.mID, mData.mSession.mIsInternal)); return VINF_SUCCESS; } } } catch (std::bad_alloc &) { hr = E_OUTOFMEMORY; } } rc = Global::vboxStatusCodeFromCOM(hr); } autoInitSpan.setFailed(); LogThisFunc(("Failed! mName=%s mID=%RU32 mIsInternal=%RTbool => rc=%Rrc\n", mData.mSession.mName.c_str(), mData.mSession.mID, mData.mSession.mIsInternal, rc)); return rc; } /** * Uninitializes the instance. * Called from FinalRelease(). */ void GuestSession::uninit(void) { /* Enclose the state transition Ready->InUninit->NotReady. */ AutoUninitSpan autoUninitSpan(this); if (autoUninitSpan.uninitDone()) return; LogFlowThisFuncEnter(); /* Call i_onRemove to take care of the object cleanups. */ i_onRemove(); AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); /* Unregister the session's object ID. */ i_objectUnregister(mData.mObjectID); Assert(mData.mObjects.size () == 0); mData.mObjects.clear(); mData.mEnvironmentChanges.reset(); if (mData.mpBaseEnvironment) { mData.mpBaseEnvironment->releaseConst(); mData.mpBaseEnvironment = NULL; } /* Unitialize our local listener. */ mLocalListener.setNull(); baseUninit(); LogFlowFuncLeave(); } // implementation of public getters/setters for attributes ///////////////////////////////////////////////////////////////////////////// HRESULT GuestSession::getUser(com::Utf8Str &aUser) { LogFlowThisFuncEnter(); AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); aUser = mData.mCredentials.mUser; LogFlowThisFuncLeave(); return S_OK; } HRESULT GuestSession::getDomain(com::Utf8Str &aDomain) { LogFlowThisFuncEnter(); AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); aDomain = mData.mCredentials.mDomain; LogFlowThisFuncLeave(); return S_OK; } HRESULT GuestSession::getName(com::Utf8Str &aName) { LogFlowThisFuncEnter(); AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); aName = mData.mSession.mName; LogFlowThisFuncLeave(); return S_OK; } HRESULT GuestSession::getId(ULONG *aId) { LogFlowThisFuncEnter(); AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); *aId = mData.mSession.mID; LogFlowThisFuncLeave(); return S_OK; } HRESULT GuestSession::getStatus(GuestSessionStatus_T *aStatus) { LogFlowThisFuncEnter(); AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); *aStatus = mData.mStatus; LogFlowThisFuncLeave(); return S_OK; } HRESULT GuestSession::getTimeout(ULONG *aTimeout) { LogFlowThisFuncEnter(); AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); *aTimeout = mData.mTimeout; LogFlowThisFuncLeave(); return S_OK; } HRESULT GuestSession::setTimeout(ULONG aTimeout) { LogFlowThisFuncEnter(); AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); mData.mTimeout = aTimeout; LogFlowThisFuncLeave(); return S_OK; } HRESULT GuestSession::getProtocolVersion(ULONG *aProtocolVersion) { LogFlowThisFuncEnter(); AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); *aProtocolVersion = mData.mProtocolVersion; LogFlowThisFuncLeave(); return S_OK; } HRESULT GuestSession::getEnvironmentChanges(std::vector &aEnvironmentChanges) { LogFlowThisFuncEnter(); int vrc; { AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); vrc = mData.mEnvironmentChanges.queryPutEnvArray(&aEnvironmentChanges); } LogFlowFuncLeaveRC(vrc); return Global::vboxStatusCodeToCOM(vrc); } HRESULT GuestSession::setEnvironmentChanges(const std::vector &aEnvironmentChanges) { LogFlowThisFuncEnter(); int vrc; size_t idxError = ~(size_t)0; { AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); mData.mEnvironmentChanges.reset(); vrc = mData.mEnvironmentChanges.applyPutEnvArray(aEnvironmentChanges, &idxError); } LogFlowFuncLeaveRC(vrc); if (RT_SUCCESS(vrc)) return S_OK; if (vrc == VERR_ENV_INVALID_VAR_NAME) return setError(E_INVALIDARG, tr("Invalid environment variable name '%s', index %zu"), aEnvironmentChanges[idxError].c_str(), idxError); return setErrorBoth(Global::vboxStatusCodeToCOM(vrc), vrc, tr("Failed to apply '%s', index %zu (%Rrc)"), aEnvironmentChanges[idxError].c_str(), idxError, vrc); } HRESULT GuestSession::getEnvironmentBase(std::vector &aEnvironmentBase) { LogFlowThisFuncEnter(); AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); HRESULT hrc; if (mData.mpBaseEnvironment) { int vrc = mData.mpBaseEnvironment->queryPutEnvArray(&aEnvironmentBase); hrc = Global::vboxStatusCodeToCOM(vrc); } else if (mData.mProtocolVersion < 99999) hrc = setError(VBOX_E_NOT_SUPPORTED, tr("The base environment feature is not supported by the Guest Additions")); else hrc = setError(VBOX_E_INVALID_OBJECT_STATE, tr("The base environment has not yet been reported by the guest")); LogFlowFuncLeave(); return hrc; } HRESULT GuestSession::getProcesses(std::vector > &aProcesses) { LogFlowThisFuncEnter(); AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); aProcesses.resize(mData.mProcesses.size()); size_t i = 0; for (SessionProcesses::iterator it = mData.mProcesses.begin(); it != mData.mProcesses.end(); ++it, ++i) { it->second.queryInterfaceTo(aProcesses[i].asOutParam()); } LogFlowFunc(("mProcesses=%zu\n", aProcesses.size())); return S_OK; } HRESULT GuestSession::getPathStyle(PathStyle_T *aPathStyle) { *aPathStyle = i_getGuestPathStyle(); return S_OK; } HRESULT GuestSession::getCurrentDirectory(com::Utf8Str &aCurrentDirectory) { RT_NOREF(aCurrentDirectory); ReturnComNotImplemented(); } HRESULT GuestSession::setCurrentDirectory(const com::Utf8Str &aCurrentDirectory) { RT_NOREF(aCurrentDirectory); ReturnComNotImplemented(); } HRESULT GuestSession::getUserHome(com::Utf8Str &aUserHome) { HRESULT hr = i_isStartedExternal(); if (FAILED(hr)) return hr; int rcGuest = VERR_IPE_UNINITIALIZED_STATUS; int vrc = i_pathUserHome(aUserHome, &rcGuest); if (RT_FAILURE(vrc)) { switch (vrc) { case VERR_GSTCTL_GUEST_ERROR: { switch (rcGuest) { case VERR_NOT_SUPPORTED: hr = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Getting the user's home path is not supported by installed Guest Additions")); break; default: hr = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Getting the user's home path failed on the guest: %Rrc"), rcGuest); break; } break; } default: hr = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Getting the user's home path failed: %Rrc"), vrc); break; } } return hr; } HRESULT GuestSession::getUserDocuments(com::Utf8Str &aUserDocuments) { HRESULT hr = i_isStartedExternal(); if (FAILED(hr)) return hr; int rcGuest = VERR_IPE_UNINITIALIZED_STATUS; int vrc = i_pathUserDocuments(aUserDocuments, &rcGuest); if (RT_FAILURE(vrc)) { switch (vrc) { case VERR_GSTCTL_GUEST_ERROR: { switch (rcGuest) { case VERR_NOT_SUPPORTED: hr = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Getting the user's documents path is not supported by installed Guest Additions")); break; default: hr = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Getting the user's documents path failed on the guest: %Rrc"), rcGuest); break; } break; } default: hr = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Getting the user's documents path failed: %Rrc"), vrc); break; } } return hr; } HRESULT GuestSession::getDirectories(std::vector > &aDirectories) { LogFlowThisFuncEnter(); AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); aDirectories.resize(mData.mDirectories.size()); size_t i = 0; for (SessionDirectories::iterator it = mData.mDirectories.begin(); it != mData.mDirectories.end(); ++it, ++i) { it->second.queryInterfaceTo(aDirectories[i].asOutParam()); } LogFlowFunc(("mDirectories=%zu\n", aDirectories.size())); return S_OK; } HRESULT GuestSession::getFiles(std::vector > &aFiles) { LogFlowThisFuncEnter(); AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); aFiles.resize(mData.mFiles.size()); size_t i = 0; for(SessionFiles::iterator it = mData.mFiles.begin(); it != mData.mFiles.end(); ++it, ++i) it->second.queryInterfaceTo(aFiles[i].asOutParam()); LogFlowFunc(("mDirectories=%zu\n", aFiles.size())); return S_OK; } HRESULT GuestSession::getEventSource(ComPtr &aEventSource) { LogFlowThisFuncEnter(); // no need to lock - lifetime constant mEventSource.queryInterfaceTo(aEventSource.asOutParam()); LogFlowThisFuncLeave(); return S_OK; } // private methods /////////////////////////////////////////////////////////////////////////////// /** * Closes a guest session on the guest. * * @returns VBox status code. * @param uFlags Guest session close flags. * @param uTimeoutMS Timeout (in ms) to wait. * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR * was returned. Optional. * * @note Takes the read lock. */ int GuestSession::i_closeSession(uint32_t uFlags, uint32_t uTimeoutMS, int *prcGuest) { AssertPtrReturn(prcGuest, VERR_INVALID_POINTER); LogFlowThisFunc(("uFlags=%x, uTimeoutMS=%RU32\n", uFlags, uTimeoutMS)); AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); /* Guest Additions < 4.3 don't support closing dedicated guest sessions, skip. */ if (mData.mProtocolVersion < 2) { LogFlowThisFunc(("Installed Guest Additions don't support closing dedicated sessions, skipping\n")); return VINF_SUCCESS; } /** @todo uFlags validation. */ if (mData.mStatus != GuestSessionStatus_Started) { LogFlowThisFunc(("Session ID=%RU32 not started (anymore), status now is: %RU32\n", mData.mSession.mID, mData.mStatus)); return VINF_SUCCESS; } int vrc; GuestWaitEvent *pEvent = NULL; GuestEventTypes eventTypes; try { eventTypes.push_back(VBoxEventType_OnGuestSessionStateChanged); vrc = registerWaitEventEx(mData.mSession.mID, mData.mObjectID, eventTypes, &pEvent); } catch (std::bad_alloc &) { vrc = VERR_NO_MEMORY; } if (RT_FAILURE(vrc)) return vrc; LogFlowThisFunc(("Sending closing request to guest session ID=%RU32, uFlags=%x\n", mData.mSession.mID, uFlags)); alock.release(); VBOXHGCMSVCPARM paParms[4]; int i = 0; HGCMSvcSetU32(&paParms[i++], pEvent->ContextID()); HGCMSvcSetU32(&paParms[i++], uFlags); vrc = i_sendMessage(HOST_MSG_SESSION_CLOSE, i, paParms, VBOX_GUESTCTRL_DST_BOTH); if (RT_SUCCESS(vrc)) vrc = i_waitForStatusChange(pEvent, GuestSessionWaitForFlag_Terminate, uTimeoutMS, NULL /* Session status */, prcGuest); unregisterWaitEvent(pEvent); LogFlowFuncLeaveRC(vrc); return vrc; } /** * Internal worker function for public APIs that handle copying elements from * guest to the host. * * @return HRESULT * @param SourceSet Source set specifying what to copy. * @param strDestination Destination path on the host. Host path style. * @param pProgress Progress object returned to the caller. */ HRESULT GuestSession::i_copyFromGuest(const GuestSessionFsSourceSet &SourceSet, const com::Utf8Str &strDestination, ComPtr &pProgress) { HRESULT hrc = i_isStartedExternal(); if (FAILED(hrc)) return hrc; LogFlowThisFuncEnter(); /* Validate stuff. */ if (RT_UNLIKELY(SourceSet.size() == 0 || *(SourceSet[0].strSource.c_str()) == '\0')) /* At least one source must be present. */ return setError(E_INVALIDARG, tr("No source(s) specified")); if (RT_UNLIKELY((strDestination.c_str()) == NULL || *(strDestination.c_str()) == '\0')) return setError(E_INVALIDARG, tr("No destination specified")); GuestSessionFsSourceSet::const_iterator itSrc = SourceSet.begin(); while (itSrc != SourceSet.end()) { LogRel2(("Guest Control: Copying '%s' from guest to '%s' on the host (type: %s, filter: %s)\n", itSrc->strSource.c_str(), strDestination.c_str(), GuestBase::fsObjTypeToStr(itSrc->enmType), itSrc->strFilter.c_str())); ++itSrc; } /* Create a task and return the progress obejct for it. */ GuestSessionTaskCopyFrom *pTask = NULL; try { pTask = new GuestSessionTaskCopyFrom(this /* GuestSession */, SourceSet, strDestination); } catch (std::bad_alloc &) { return setError(E_OUTOFMEMORY, tr("Failed to create GuestSessionTaskCopyFrom object")); } try { hrc = pTask->Init(Utf8StrFmt(tr("Copying to \"%s\" on the host"), strDestination.c_str())); } catch (std::bad_alloc &) { hrc = E_OUTOFMEMORY; } if (SUCCEEDED(hrc)) { ComObjPtr ptrProgressObj = pTask->GetProgressObject(); /* Kick off the worker thread. Note! Consumes pTask. */ hrc = pTask->createThreadWithType(RTTHREADTYPE_MAIN_HEAVY_WORKER); pTask = NULL; if (SUCCEEDED(hrc)) hrc = ptrProgressObj.queryInterfaceTo(pProgress.asOutParam()); else hrc = setError(hrc, tr("Starting thread for copying from guest to the host failed")); } else { hrc = setError(hrc, tr("Initializing GuestSessionTaskCopyFrom object failed")); delete pTask; } LogFlowFunc(("Returning %Rhrc\n", hrc)); return hrc; } /** * Internal worker function for public APIs that handle copying elements from * host to the guest. * * @return HRESULT * @param SourceSet Source set specifying what to copy. * @param strDestination Destination path on the guest. Guest path style. * @param pProgress Progress object returned to the caller. */ HRESULT GuestSession::i_copyToGuest(const GuestSessionFsSourceSet &SourceSet, const com::Utf8Str &strDestination, ComPtr &pProgress) { HRESULT hrc = i_isStartedExternal(); if (FAILED(hrc)) return hrc; LogFlowThisFuncEnter(); GuestSessionFsSourceSet::const_iterator itSrc = SourceSet.begin(); while (itSrc != SourceSet.end()) { LogRel2(("Guest Control: Copying '%s' from host to '%s' on the guest (type: %s, filter: %s)\n", itSrc->strSource.c_str(), strDestination.c_str(), GuestBase::fsObjTypeToStr(itSrc->enmType), itSrc->strFilter.c_str())); ++itSrc; } /* Create a task and return the progress object for it. */ GuestSessionTaskCopyTo *pTask = NULL; try { pTask = new GuestSessionTaskCopyTo(this /* GuestSession */, SourceSet, strDestination); } catch (std::bad_alloc &) { return setError(E_OUTOFMEMORY, tr("Failed to create GuestSessionTaskCopyTo object")); } try { hrc = pTask->Init(Utf8StrFmt(tr("Copying to \"%s\" on the guest"), strDestination.c_str())); } catch (std::bad_alloc &) { hrc = E_OUTOFMEMORY; } if (SUCCEEDED(hrc)) { ComObjPtr ptrProgressObj = pTask->GetProgressObject(); /* Kick off the worker thread. Note! Consumes pTask. */ hrc = pTask->createThreadWithType(RTTHREADTYPE_MAIN_HEAVY_WORKER); pTask = NULL; if (SUCCEEDED(hrc)) hrc = ptrProgressObj.queryInterfaceTo(pProgress.asOutParam()); else hrc = setError(hrc, tr("Starting thread for copying from host to the guest failed")); } else { hrc = setError(hrc, tr("Initializing GuestSessionTaskCopyTo object failed")); delete pTask; } LogFlowFunc(("Returning %Rhrc\n", hrc)); return hrc; } /** * Validates and extracts directory copy flags from a comma-separated string. * * @return COM status, error set on failure * @param strFlags String to extract flags from. * @param fStrict Whether to set an error when an unknown / invalid flag is detected. * @param pfFlags Where to store the extracted (and validated) flags. */ HRESULT GuestSession::i_directoryCopyFlagFromStr(const com::Utf8Str &strFlags, bool fStrict, DirectoryCopyFlag_T *pfFlags) { unsigned fFlags = DirectoryCopyFlag_None; /* Validate and set flags. */ if (strFlags.isNotEmpty()) { const char *pszNext = strFlags.c_str(); for (;;) { /* Find the next keyword, ignoring all whitespace. */ pszNext = RTStrStripL(pszNext); const char * const pszComma = strchr(pszNext, ','); size_t cchKeyword = pszComma ? pszComma - pszNext : strlen(pszNext); while (cchKeyword > 0 && RT_C_IS_SPACE(pszNext[cchKeyword - 1])) cchKeyword--; if (cchKeyword > 0) { /* Convert keyword to flag. */ #define MATCH_KEYWORD(a_szKeyword) ( cchKeyword == sizeof(a_szKeyword) - 1U \ && memcmp(pszNext, a_szKeyword, sizeof(a_szKeyword) - 1U) == 0) if (MATCH_KEYWORD("CopyIntoExisting")) fFlags |= (unsigned)DirectoryCopyFlag_CopyIntoExisting; else if (MATCH_KEYWORD("Recursive")) fFlags |= (unsigned)DirectoryCopyFlag_Recursive; else if (MATCH_KEYWORD("FollowLinks")) fFlags |= (unsigned)DirectoryCopyFlag_FollowLinks; else if (fStrict) return setError(E_INVALIDARG, tr("Invalid directory copy flag: %.*s"), (int)cchKeyword, pszNext); #undef MATCH_KEYWORD } if (!pszComma) break; pszNext = pszComma + 1; } } if (pfFlags) *pfFlags = (DirectoryCopyFlag_T)fFlags; return S_OK; } /** * Creates a directory on the guest. * * @returns VBox status code. * @param strPath Path on guest to directory to create. * @param uMode Creation mode to use (octal, 0777 max). * @param uFlags Directory creation flags to use. * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR * was returned. Optional. */ int GuestSession::i_directoryCreate(const Utf8Str &strPath, uint32_t uMode, uint32_t uFlags, int *prcGuest) { AssertPtrReturn(prcGuest, VERR_INVALID_POINTER); LogFlowThisFunc(("strPath=%s, uMode=%x, uFlags=%x\n", strPath.c_str(), uMode, uFlags)); int vrc = VINF_SUCCESS; GuestProcessStartupInfo procInfo; procInfo.mFlags = ProcessCreateFlag_Hidden; procInfo.mExecutable = Utf8Str(VBOXSERVICE_TOOL_MKDIR); try { procInfo.mArguments.push_back(procInfo.mExecutable); /* Set argv0. */ /* Construct arguments. */ if (uFlags) { if (uFlags & DirectoryCreateFlag_Parents) procInfo.mArguments.push_back(Utf8Str("--parents")); /* We also want to create the parent directories. */ else vrc = VERR_INVALID_PARAMETER; } if ( RT_SUCCESS(vrc) && uMode) { procInfo.mArguments.push_back(Utf8Str("--mode")); /* Set the creation mode. */ char szMode[16]; if (RTStrPrintf(szMode, sizeof(szMode), "%o", uMode)) { procInfo.mArguments.push_back(Utf8Str(szMode)); } else vrc = VERR_BUFFER_OVERFLOW; } procInfo.mArguments.push_back("--"); /* '--version' is a valid directory name. */ procInfo.mArguments.push_back(strPath); /* The directory we want to create. */ } catch (std::bad_alloc &) { vrc = VERR_NO_MEMORY; } if (RT_SUCCESS(vrc)) vrc = GuestProcessTool::run(this, procInfo, prcGuest); LogFlowFuncLeaveRC(vrc); return vrc; } /** * Checks if a directory on the guest exists. * * @returns \c true if directory exists on the guest, \c false if not. * @param strPath Path of directory to check. */ bool GuestSession::i_directoryExists(const Utf8Str &strPath) { GuestFsObjData objDataIgnored; int rcGuestIgnored; int rc = i_directoryQueryInfo(strPath, true /* fFollowSymlinks */, objDataIgnored, &rcGuestIgnored); return RT_SUCCESS(rc); } /** * Checks if a directory object exists and optionally returns its object. * * @returns \c true if directory object exists, or \c false if not. * @param uDirID ID of directory object to check. * @param pDir Where to return the found directory object on success. */ inline bool GuestSession::i_directoryExists(uint32_t uDirID, ComObjPtr *pDir) { SessionDirectories::const_iterator it = mData.mDirectories.find(uDirID); if (it != mData.mDirectories.end()) { if (pDir) *pDir = it->second; return true; } return false; } /** * Queries information about a directory on the guest. * * @returns VBox status code, or VERR_NOT_A_DIRECTORY if the file system object exists but is not a directory. * @param strPath Path to directory to query information for. * @param fFollowSymlinks Whether to follow symlinks or not. * @param objData Where to store the information returned on success. * @param prcGuest Guest rc, when returning VERR_GSTCTL_GUEST_ERROR. */ int GuestSession::i_directoryQueryInfo(const Utf8Str &strPath, bool fFollowSymlinks, GuestFsObjData &objData, int *prcGuest) { AssertPtrReturn(prcGuest, VERR_INVALID_POINTER); LogFlowThisFunc(("strPath=%s, fFollowSymlinks=%RTbool\n", strPath.c_str(), fFollowSymlinks)); int vrc = i_fsQueryInfo(strPath, fFollowSymlinks, objData, prcGuest); if (RT_SUCCESS(vrc)) { vrc = objData.mType == FsObjType_Directory ? VINF_SUCCESS : VERR_NOT_A_DIRECTORY; } LogFlowFuncLeaveRC(vrc); return vrc; } /** * Unregisters a directory object from a guest session. * * @returns VBox status code. VERR_NOT_FOUND if the directory is not registered (anymore). * @param pDirectory Directory object to unregister from session. * * @note Takes the write lock. */ int GuestSession::i_directoryUnregister(GuestDirectory *pDirectory) { AssertPtrReturn(pDirectory, VERR_INVALID_POINTER); LogFlowThisFunc(("pDirectory=%p\n", pDirectory)); AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); const uint32_t idObject = pDirectory->getObjectID(); LogFlowFunc(("Removing directory (objectID=%RU32) ...\n", idObject)); int rc = i_objectUnregister(idObject); if (RT_FAILURE(rc)) return rc; SessionDirectories::iterator itDirs = mData.mDirectories.find(idObject); AssertReturn(itDirs != mData.mDirectories.end(), VERR_NOT_FOUND); /* Make sure to consume the pointer before the one of the iterator gets released. */ ComObjPtr pDirConsumed = pDirectory; LogFlowFunc(("Removing directory ID=%RU32 (session %RU32, now total %zu directories)\n", idObject, mData.mSession.mID, mData.mDirectories.size())); rc = pDirConsumed->i_onUnregister(); AssertRCReturn(rc, rc); mData.mDirectories.erase(itDirs); alock.release(); /* Release lock before firing off event. */ // ::FireGuestDirectoryRegisteredEvent(mEventSource, this /* Session */, pDirConsumed, false /* Process unregistered */); pDirConsumed.setNull(); LogFlowFuncLeaveRC(rc); return rc; } /** * Removes a directory on the guest. * * @returns VBox status code. * @param strPath Path of directory on guest to remove. * @param fFlags Directory remove flags to use. * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR * was returned. Optional. * * @note Takes the read lock. */ int GuestSession::i_directoryRemove(const Utf8Str &strPath, uint32_t fFlags, int *prcGuest) { AssertReturn(!(fFlags & ~DIRREMOVEREC_FLAG_VALID_MASK), VERR_INVALID_PARAMETER); AssertPtrReturn(prcGuest, VERR_INVALID_POINTER); LogFlowThisFunc(("strPath=%s, uFlags=0x%x\n", strPath.c_str(), fFlags)); AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); GuestWaitEvent *pEvent = NULL; int vrc = registerWaitEvent(mData.mSession.mID, mData.mObjectID, &pEvent); if (RT_FAILURE(vrc)) return vrc; /* Prepare HGCM call. */ VBOXHGCMSVCPARM paParms[8]; int i = 0; HGCMSvcSetU32(&paParms[i++], pEvent->ContextID()); HGCMSvcSetPv(&paParms[i++], (void*)strPath.c_str(), (ULONG)strPath.length() + 1); HGCMSvcSetU32(&paParms[i++], fFlags); alock.release(); /* Drop lock before sending. */ vrc = i_sendMessage(HOST_MSG_DIR_REMOVE, i, paParms); if (RT_SUCCESS(vrc)) { vrc = pEvent->Wait(30 * 1000); if ( vrc == VERR_GSTCTL_GUEST_ERROR && prcGuest) *prcGuest = pEvent->GuestResult(); } unregisterWaitEvent(pEvent); LogFlowFuncLeaveRC(vrc); return vrc; } /** * Creates a temporary directory / file on the guest. * * @returns VBox status code. * @returns VERR_GSTCTL_GUEST_ERROR on received guest error. * @param strTemplate Name template to use. * \sa RTDirCreateTemp / RTDirCreateTempSecure. * @param strPath Path where to create the temporary directory / file. * @param fDirectory Whether to create a temporary directory or file. * @param strName Where to return the created temporary name on success. * @param fMode File mode to use for creation (octal, umask-style). * Ignored when \a fSecure is specified. * @param fSecure Whether to perform a secure creation or not. * @param prcGuest Guest rc, when returning VERR_GSTCTL_GUEST_ERROR. */ int GuestSession::i_fsCreateTemp(const Utf8Str &strTemplate, const Utf8Str &strPath, bool fDirectory, Utf8Str &strName, uint32_t fMode, bool fSecure, int *prcGuest) { AssertPtrReturn(prcGuest, VERR_INVALID_POINTER); AssertReturn(fSecure || !(fMode & ~07777), VERR_INVALID_PARAMETER); LogFlowThisFunc(("strTemplate=%s, strPath=%s, fDirectory=%RTbool, fMode=%o, fSecure=%RTbool\n", strTemplate.c_str(), strPath.c_str(), fDirectory, fMode, fSecure)); GuestProcessStartupInfo procInfo; procInfo.mFlags = ProcessCreateFlag_WaitForStdOut; try { procInfo.mExecutable = Utf8Str(VBOXSERVICE_TOOL_MKTEMP); procInfo.mArguments.push_back(procInfo.mExecutable); /* Set argv0. */ procInfo.mArguments.push_back(Utf8Str("--machinereadable")); if (fDirectory) procInfo.mArguments.push_back(Utf8Str("-d")); if (strPath.length()) /* Otherwise use /tmp or equivalent. */ { procInfo.mArguments.push_back(Utf8Str("-t")); procInfo.mArguments.push_back(strPath); } /* Note: Secure flag and mode cannot be specified at the same time. */ if (fSecure) { procInfo.mArguments.push_back(Utf8Str("--secure")); } else { procInfo.mArguments.push_back(Utf8Str("--mode")); /* Note: Pass the mode unmodified down to the guest. See @ticketref{21394}. */ char szMode[16]; int vrc2 = RTStrPrintf2(szMode, sizeof(szMode), "%d", fMode); AssertRCReturn(vrc2, vrc2); procInfo.mArguments.push_back(szMode); } procInfo.mArguments.push_back("--"); /* strTemplate could be '--help'. */ procInfo.mArguments.push_back(strTemplate); } catch (std::bad_alloc &) { Log(("Out of memory!\n")); return VERR_NO_MEMORY; } /** @todo Use an internal HGCM command for this operation, since * we now can run in a user-dedicated session. */ int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS; GuestCtrlStreamObjects stdOut; int vrc = GuestProcessTool::runEx(this, procInfo, &stdOut, 1 /* cStrmOutObjects */, &vrcGuest); if (!GuestProcess::i_isGuestError(vrc)) { GuestFsObjData objData; if (!stdOut.empty()) { vrc = objData.FromMkTemp(stdOut.at(0)); if (RT_FAILURE(vrc)) { vrcGuest = vrc; if (prcGuest) *prcGuest = vrcGuest; vrc = VERR_GSTCTL_GUEST_ERROR; } } else vrc = VERR_BROKEN_PIPE; if (RT_SUCCESS(vrc)) strName = objData.mName; } else if (prcGuest) *prcGuest = vrcGuest; LogFlowThisFunc(("Returning vrc=%Rrc, vrcGuest=%Rrc\n", vrc, vrcGuest)); return vrc; } /** * Open a directory on the guest. * * @returns VBox status code. * @returns VERR_GSTCTL_GUEST_ERROR on received guest error. * @param openInfo Open information to use. * @param pDirectory Where to return the guest directory object on success. * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR * was returned. Optional. * * @note Takes the write lock. */ int GuestSession::i_directoryOpen(const GuestDirectoryOpenInfo &openInfo, ComObjPtr &pDirectory, int *prcGuest) { AssertPtrReturn(prcGuest, VERR_INVALID_POINTER); LogFlowThisFunc(("strPath=%s, strPath=%s, uFlags=%x\n", openInfo.mPath.c_str(), openInfo.mFilter.c_str(), openInfo.mFlags)); AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); /* Create the directory object. */ HRESULT hr = pDirectory.createObject(); if (FAILED(hr)) return Global::vboxStatusCodeFromCOM(hr); /* Register a new object ID. */ uint32_t idObject; int vrc = i_objectRegister(pDirectory, SESSIONOBJECTTYPE_DIRECTORY, &idObject); if (RT_FAILURE(vrc)) { pDirectory.setNull(); return vrc; } /* We need to release the write lock first before initializing the directory object below, * as we're starting a guest process as part of it. This in turn will try to acquire the session's * write lock. */ alock.release(); Console *pConsole = mParent->i_getConsole(); AssertPtr(pConsole); vrc = pDirectory->init(pConsole, this /* Parent */, idObject, openInfo); if (RT_FAILURE(vrc)) { /* Make sure to acquire the write lock again before unregistering the object. */ alock.acquire(); int vrc2 = i_objectUnregister(idObject); AssertRC(vrc2); pDirectory.setNull(); } else { /* Make sure to acquire the write lock again before continuing. */ alock.acquire(); try { /* Add the created directory to our map. */ mData.mDirectories[idObject] = pDirectory; LogFlowFunc(("Added new guest directory \"%s\" (Session: %RU32) (now total %zu directories)\n", openInfo.mPath.c_str(), mData.mSession.mID, mData.mDirectories.size())); alock.release(); /* Release lock before firing off event. */ /** @todo Fire off a VBoxEventType_OnGuestDirectoryRegistered event? */ } catch (std::bad_alloc &) { vrc = VERR_NO_MEMORY; } } if (RT_SUCCESS(vrc)) { /* Nothing further to do here yet. */ if (prcGuest) *prcGuest = VINF_SUCCESS; } LogFlowFuncLeaveRC(vrc); return vrc; } /** * Dispatches a host callback to its corresponding object. * * @return VBox status code. VERR_NOT_FOUND if no corresponding object was found. * @param pCtxCb Host callback context. * @param pSvcCb Service callback data. * * @note Takes the read lock. */ int GuestSession::i_dispatchToObject(PVBOXGUESTCTRLHOSTCBCTX pCtxCb, PVBOXGUESTCTRLHOSTCALLBACK pSvcCb) { LogFlowFunc(("pCtxCb=%p, pSvcCb=%p\n", pCtxCb, pSvcCb)); AssertPtrReturn(pCtxCb, VERR_INVALID_POINTER); AssertPtrReturn(pSvcCb, VERR_INVALID_POINTER); AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); /* * Find the object. */ int rc = VERR_NOT_FOUND; const uint32_t idObject = VBOX_GUESTCTRL_CONTEXTID_GET_OBJECT(pCtxCb->uContextID); SessionObjects::const_iterator itObj = mData.mObjects.find(idObject); if (itObj != mData.mObjects.end()) { /* Set protocol version so that pSvcCb can be interpreted right. */ pCtxCb->uProtocol = mData.mProtocolVersion; switch (itObj->second.enmType) { /* Note: The session object is special, as it does not inherit from GuestObject we could call * its dispatcher for -- so treat this separately and call it directly. */ case SESSIONOBJECTTYPE_SESSION: { alock.release(); rc = i_dispatchToThis(pCtxCb, pSvcCb); break; } case SESSIONOBJECTTYPE_DIRECTORY: { ComObjPtr pObj((GuestDirectory *)itObj->second.pObject); AssertReturn(!pObj.isNull(), VERR_INVALID_POINTER); alock.release(); rc = pObj->i_callbackDispatcher(pCtxCb, pSvcCb); break; } case SESSIONOBJECTTYPE_FILE: { ComObjPtr pObj((GuestFile *)itObj->second.pObject); AssertReturn(!pObj.isNull(), VERR_INVALID_POINTER); alock.release(); rc = pObj->i_callbackDispatcher(pCtxCb, pSvcCb); break; } case SESSIONOBJECTTYPE_PROCESS: { ComObjPtr pObj((GuestProcess *)itObj->second.pObject); AssertReturn(!pObj.isNull(), VERR_INVALID_POINTER); alock.release(); rc = pObj->i_callbackDispatcher(pCtxCb, pSvcCb); break; } default: AssertMsgFailed(("%d\n", itObj->second.enmType)); rc = VERR_INTERNAL_ERROR_4; break; } } LogFlowFuncLeaveRC(rc); return rc; } /** * Main handler for guest session messages from the guest. * * @returns VBox status code. * @param pCbCtx Host callback context from HGCM service. * @param pSvcCbData HGCM service callback data. * * @note No locking! */ int GuestSession::i_dispatchToThis(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData) { AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER); AssertPtrReturn(pSvcCbData, VERR_INVALID_POINTER); LogFlowThisFunc(("sessionID=%RU32, CID=%RU32, uMessage=%RU32, pSvcCb=%p\n", mData.mSession.mID, pCbCtx->uContextID, pCbCtx->uMessage, pSvcCbData)); int rc; switch (pCbCtx->uMessage) { case GUEST_MSG_DISCONNECTED: /** @todo Handle closing all guest objects. */ rc = VERR_INTERNAL_ERROR; break; case GUEST_MSG_SESSION_NOTIFY: /* Guest Additions >= 4.3.0. */ { rc = i_onSessionStatusChange(pCbCtx, pSvcCbData); break; } default: rc = dispatchGeneric(pCbCtx, pSvcCbData); break; } LogFlowFuncLeaveRC(rc); return rc; } /** * Validates and extracts file copy flags from a comma-separated string. * * @return COM status, error set on failure * @param strFlags String to extract flags from. * @param fStrict Whether to set an error when an unknown / invalid flag is detected. * @param pfFlags Where to store the extracted (and validated) flags. */ HRESULT GuestSession::i_fileCopyFlagFromStr(const com::Utf8Str &strFlags, bool fStrict, FileCopyFlag_T *pfFlags) { unsigned fFlags = (unsigned)FileCopyFlag_None; /* Validate and set flags. */ if (strFlags.isNotEmpty()) { const char *pszNext = strFlags.c_str(); for (;;) { /* Find the next keyword, ignoring all whitespace. */ pszNext = RTStrStripL(pszNext); const char * const pszComma = strchr(pszNext, ','); size_t cchKeyword = pszComma ? pszComma - pszNext : strlen(pszNext); while (cchKeyword > 0 && RT_C_IS_SPACE(pszNext[cchKeyword - 1])) cchKeyword--; if (cchKeyword > 0) { /* Convert keyword to flag. */ #define MATCH_KEYWORD(a_szKeyword) ( cchKeyword == sizeof(a_szKeyword) - 1U \ && memcmp(pszNext, a_szKeyword, sizeof(a_szKeyword) - 1U) == 0) if (MATCH_KEYWORD("NoReplace")) fFlags |= (unsigned)FileCopyFlag_NoReplace; else if (MATCH_KEYWORD("FollowLinks")) fFlags |= (unsigned)FileCopyFlag_FollowLinks; else if (MATCH_KEYWORD("Update")) fFlags |= (unsigned)FileCopyFlag_Update; else if (fStrict) return setError(E_INVALIDARG, tr("Invalid file copy flag: %.*s"), (int)cchKeyword, pszNext); #undef MATCH_KEYWORD } if (!pszComma) break; pszNext = pszComma + 1; } } if (pfFlags) *pfFlags = (FileCopyFlag_T)fFlags; return S_OK; } /** * Checks if a file object exists and optionally returns its object. * * @returns \c true if file object exists, or \c false if not. * @param uFileID ID of file object to check. * @param pFile Where to return the found file object on success. */ inline bool GuestSession::i_fileExists(uint32_t uFileID, ComObjPtr *pFile) { SessionFiles::const_iterator it = mData.mFiles.find(uFileID); if (it != mData.mFiles.end()) { if (pFile) *pFile = it->second; return true; } return false; } /** * Unregisters a file object from a guest session. * * @returns VBox status code. VERR_NOT_FOUND if the file is not registered (anymore). * @param pFile File object to unregister from session. * * @note Takes the write lock. */ int GuestSession::i_fileUnregister(GuestFile *pFile) { AssertPtrReturn(pFile, VERR_INVALID_POINTER); LogFlowThisFunc(("pFile=%p\n", pFile)); AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); const uint32_t idObject = pFile->getObjectID(); LogFlowFunc(("Removing file (objectID=%RU32) ...\n", idObject)); int rc = i_objectUnregister(idObject); if (RT_FAILURE(rc)) return rc; SessionFiles::iterator itFiles = mData.mFiles.find(idObject); AssertReturn(itFiles != mData.mFiles.end(), VERR_NOT_FOUND); /* Make sure to consume the pointer before the one of the iterator gets released. */ ComObjPtr pFileConsumed = pFile; LogFlowFunc(("Removing file ID=%RU32 (session %RU32, now total %zu files)\n", pFileConsumed->getObjectID(), mData.mSession.mID, mData.mFiles.size())); rc = pFileConsumed->i_onUnregister(); AssertRCReturn(rc, rc); mData.mFiles.erase(itFiles); alock.release(); /* Release lock before firing off event. */ ::FireGuestFileRegisteredEvent(mEventSource, this, pFileConsumed, false /* Unregistered */); pFileConsumed.setNull(); LogFlowFuncLeaveRC(rc); return rc; } /** * Removes a file from the guest. * * @returns VBox status code. * @returns VERR_GSTCTL_GUEST_ERROR on received guest error. * @param strPath Path of file on guest to remove. * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR * was returned. Optional. */ int GuestSession::i_fileRemove(const Utf8Str &strPath, int *prcGuest) { LogFlowThisFunc(("strPath=%s\n", strPath.c_str())); GuestProcessStartupInfo procInfo; GuestProcessStream streamOut; procInfo.mFlags = ProcessCreateFlag_WaitForStdOut; procInfo.mExecutable = Utf8Str(VBOXSERVICE_TOOL_RM); try { procInfo.mArguments.push_back(procInfo.mExecutable); /* Set argv0. */ procInfo.mArguments.push_back(Utf8Str("--machinereadable")); procInfo.mArguments.push_back("--"); /* strPath could be '--help', which is a valid filename. */ procInfo.mArguments.push_back(strPath); /* The file we want to remove. */ } catch (std::bad_alloc &) { return VERR_NO_MEMORY; } int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS; GuestCtrlStreamObjects stdOut; int vrc = GuestProcessTool::runEx(this, procInfo, &stdOut, 1 /* cStrmOutObjects */, &vrcGuest); if (GuestProcess::i_isGuestError(vrc)) { if (!stdOut.empty()) { GuestFsObjData objData; vrc = objData.FromRm(stdOut.at(0)); if (RT_FAILURE(vrc)) { vrcGuest = vrc; if (prcGuest) *prcGuest = vrcGuest; vrc = VERR_GSTCTL_GUEST_ERROR; } } else vrc = VERR_BROKEN_PIPE; } else if (prcGuest) *prcGuest = vrcGuest; LogFlowThisFunc(("Returning vrc=%Rrc, vrcGuest=%Rrc\n", vrc, vrcGuest)); return vrc; } /** * Opens a file on the guest. * * @returns VBox status code. * @returns VERR_GSTCTL_GUEST_ERROR on received guest error. * @param aPath File path on guest to open. * @param aAccessMode Access mode to use. * @param aOpenAction Open action to use. * @param aSharingMode Sharing mode to use. * @param aCreationMode Creation mode to use. * @param aFlags Open flags to use. * @param pFile Where to return the file object on success. * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR * was returned. Optional. * * @note Takes the write lock. */ int GuestSession::i_fileOpenEx(const com::Utf8Str &aPath, FileAccessMode_T aAccessMode, FileOpenAction_T aOpenAction, FileSharingMode_T aSharingMode, ULONG aCreationMode, const std::vector &aFlags, ComObjPtr &pFile, int *prcGuest) { GuestFileOpenInfo openInfo; openInfo.mFilename = aPath; openInfo.mCreationMode = aCreationMode; openInfo.mAccessMode = aAccessMode; openInfo.mOpenAction = aOpenAction; openInfo.mSharingMode = aSharingMode; /* Combine and validate flags. */ for (size_t i = 0; i < aFlags.size(); i++) openInfo.mfOpenEx |= aFlags[i]; /* Validation is done in i_fileOpen(). */ return i_fileOpen(openInfo, pFile, prcGuest); } /** * Opens a file on the guest. * * @returns VBox status code. * @returns VERR_GSTCTL_GUEST_ERROR on received guest error. * @param openInfo Open information to use for opening the file. * @param pFile Where to return the file object on success. * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR * was returned. Optional. * * @note Takes the write lock. */ int GuestSession::i_fileOpen(const GuestFileOpenInfo &openInfo, ComObjPtr &pFile, int *prcGuest) { LogFlowThisFunc(("strFile=%s, enmAccessMode=0x%x, enmOpenAction=0x%x, uCreationMode=%RU32, mfOpenEx=%RU32\n", openInfo.mFilename.c_str(), openInfo.mAccessMode, openInfo.mOpenAction, openInfo.mCreationMode, openInfo.mfOpenEx)); AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); /* Guest Additions < 4.3 don't support handling guest files, skip. */ if (mData.mProtocolVersion < 2) { if (prcGuest) *prcGuest = VERR_NOT_SUPPORTED; return VERR_GSTCTL_GUEST_ERROR; } if (!openInfo.IsValid()) return VERR_INVALID_PARAMETER; /* Create the directory object. */ HRESULT hr = pFile.createObject(); if (FAILED(hr)) return VERR_COM_UNEXPECTED; /* Register a new object ID. */ uint32_t idObject; int rc = i_objectRegister(pFile, SESSIONOBJECTTYPE_FILE, &idObject); if (RT_FAILURE(rc)) { pFile.setNull(); return rc; } Console *pConsole = mParent->i_getConsole(); AssertPtr(pConsole); rc = pFile->init(pConsole, this /* GuestSession */, idObject, openInfo); if (RT_FAILURE(rc)) return rc; /* * Since this is a synchronous guest call we have to * register the file object first, releasing the session's * lock and then proceed with the actual opening command * -- otherwise the file's opening callback would hang * because the session's lock still is in place. */ try { /* Add the created file to our vector. */ mData.mFiles[idObject] = pFile; LogFlowFunc(("Added new guest file \"%s\" (Session: %RU32) (now total %zu files)\n", openInfo.mFilename.c_str(), mData.mSession.mID, mData.mFiles.size())); alock.release(); /* Release lock before firing off event. */ ::FireGuestFileRegisteredEvent(mEventSource, this, pFile, true /* Registered */); } catch (std::bad_alloc &) { rc = VERR_NO_MEMORY; } if (RT_SUCCESS(rc)) { int rcGuest = VERR_IPE_UNINITIALIZED_STATUS; rc = pFile->i_openFile(30 * 1000 /* 30s timeout */, &rcGuest); if ( rc == VERR_GSTCTL_GUEST_ERROR && prcGuest) { *prcGuest = rcGuest; } } LogFlowFuncLeaveRC(rc); return rc; } /** * Queries information from a file on the guest. * * @returns IPRT status code. VERR_NOT_A_FILE if the queried file system object on the guest is not a file, * or VERR_GSTCTL_GUEST_ERROR if prcGuest contains more error information from the guest. * @param strPath Absolute path of file to query information for. * @param fFollowSymlinks Whether or not to follow symbolic links on the guest. * @param objData Where to store the acquired information. * @param prcGuest Where to store the guest rc. Optional. */ int GuestSession::i_fileQueryInfo(const Utf8Str &strPath, bool fFollowSymlinks, GuestFsObjData &objData, int *prcGuest) { LogFlowThisFunc(("strPath=%s fFollowSymlinks=%RTbool\n", strPath.c_str(), fFollowSymlinks)); int vrc = i_fsQueryInfo(strPath, fFollowSymlinks, objData, prcGuest); if (RT_SUCCESS(vrc)) { vrc = objData.mType == FsObjType_File ? VINF_SUCCESS : VERR_NOT_A_FILE; } LogFlowFuncLeaveRC(vrc); return vrc; } /** * Queries the size of a file on the guest. * * @returns VBox status code. * @returns VERR_GSTCTL_GUEST_ERROR on received guest error. * @retval VERR_ * @param strPath Path of file on guest to query size for. * @param fFollowSymlinks \c true when wanting to follow symbolic links, \c false if not. * @param pllSize Where to return the queried file size on success. * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR * was returned. Optional. */ int GuestSession::i_fileQuerySize(const Utf8Str &strPath, bool fFollowSymlinks, int64_t *pllSize, int *prcGuest) { AssertPtrReturn(pllSize, VERR_INVALID_POINTER); GuestFsObjData objData; int vrc = i_fileQueryInfo(strPath, fFollowSymlinks, objData, prcGuest); if (RT_SUCCESS(vrc)) *pllSize = objData.mObjectSize; return vrc; } /** * Queries information of a file system object (file, directory, ...). * * @return IPRT status code. * @param strPath Path to file system object to query information for. * @param fFollowSymlinks Whether to follow symbolic links or not. * @param objData Where to return the file system object data, if found. * @param prcGuest Guest rc, when returning VERR_GSTCTL_GUEST_ERROR. * Any other return code indicates some host side error. */ int GuestSession::i_fsQueryInfo(const Utf8Str &strPath, bool fFollowSymlinks, GuestFsObjData &objData, int *prcGuest) { LogFlowThisFunc(("strPath=%s\n", strPath.c_str())); /** @todo Merge this with IGuestFile::queryInfo(). */ GuestProcessStartupInfo procInfo; procInfo.mFlags = ProcessCreateFlag_WaitForStdOut; try { procInfo.mExecutable = Utf8Str(VBOXSERVICE_TOOL_STAT); procInfo.mArguments.push_back(procInfo.mExecutable); /* Set argv0. */ procInfo.mArguments.push_back(Utf8Str("--machinereadable")); if (fFollowSymlinks) procInfo.mArguments.push_back(Utf8Str("-L")); procInfo.mArguments.push_back("--"); /* strPath could be '--help', which is a valid filename. */ procInfo.mArguments.push_back(strPath); } catch (std::bad_alloc &) { Log(("Out of memory!\n")); return VERR_NO_MEMORY; } int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS; GuestCtrlStreamObjects stdOut; int vrc = GuestProcessTool::runEx(this, procInfo, &stdOut, 1 /* cStrmOutObjects */, &vrcGuest); if (!GuestProcess::i_isGuestError(vrc)) { if (!stdOut.empty()) { vrc = objData.FromStat(stdOut.at(0)); if (RT_FAILURE(vrc)) { vrcGuest = vrc; if (prcGuest) *prcGuest = vrcGuest; vrc = VERR_GSTCTL_GUEST_ERROR; } } else vrc = VERR_BROKEN_PIPE; } else if (prcGuest) *prcGuest = vrcGuest; LogFlowThisFunc(("Returning vrc=%Rrc, vrcGuest=%Rrc\n", vrc, vrcGuest)); return vrc; } /** * Returns the guest credentials of a guest session. * * @returns Guest credentials. */ const GuestCredentials& GuestSession::i_getCredentials(void) { return mData.mCredentials; } /** * Returns the guest session (friendly) name. * * @returns Guest session name. */ Utf8Str GuestSession::i_getName(void) { return mData.mSession.mName; } /** * Returns a stringified error description for a given guest result code. * * @returns Stringified error description. */ /* static */ Utf8Str GuestSession::i_guestErrorToString(int rcGuest) { Utf8Str strError; /** @todo pData->u32Flags: int vs. uint32 -- IPRT errors are *negative* !!! */ switch (rcGuest) { case VERR_INVALID_VM_HANDLE: strError.printf(tr("VMM device is not available (is the VM running?)")); break; case VERR_HGCM_SERVICE_NOT_FOUND: strError.printf(tr("The guest execution service is not available")); break; case VERR_ACCOUNT_RESTRICTED: strError.printf(tr("The specified user account on the guest is restricted and can't be used to logon")); break; case VERR_AUTHENTICATION_FAILURE: strError.printf(tr("The specified user was not able to logon on guest")); break; case VERR_TIMEOUT: strError.printf(tr("The guest did not respond within time")); break; case VERR_CANCELLED: strError.printf(tr("The session operation was canceled")); break; case VERR_GSTCTL_MAX_CID_OBJECTS_REACHED: strError.printf(tr("Maximum number of concurrent guest processes has been reached")); break; case VERR_NOT_FOUND: strError.printf(tr("The guest execution service is not ready (yet)")); break; default: strError.printf("%Rrc", rcGuest); break; } return strError; } /** * Returns whether the session is in a started state or not. * * @returns \c true if in a started state, or \c false if not. */ bool GuestSession::i_isStarted(void) const { return (mData.mStatus == GuestSessionStatus_Started); } /** * Checks if this session is ready state where it can handle * all session-bound actions (like guest processes, guest files). * Only used by official API methods. Will set an external * error when not ready. */ HRESULT GuestSession::i_isStartedExternal(void) { AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); /** @todo Be a bit more informative. */ if (!i_isStarted()) return setError(E_UNEXPECTED, tr("Session is not in started state")); return S_OK; } /** * Returns whether a guest session status implies a terminated state or not. * * @returns \c true if it's a terminated state, or \c false if not. */ /* static */ bool GuestSession::i_isTerminated(GuestSessionStatus_T enmStatus) { switch (enmStatus) { case GuestSessionStatus_Terminated: RT_FALL_THROUGH(); case GuestSessionStatus_TimedOutKilled: RT_FALL_THROUGH(); case GuestSessionStatus_TimedOutAbnormally: RT_FALL_THROUGH(); case GuestSessionStatus_Down: RT_FALL_THROUGH(); case GuestSessionStatus_Error: return true; default: break; } return false; } /** * Returns whether the session is in a terminated state or not. * * @returns \c true if in a terminated state, or \c false if not. */ bool GuestSession::i_isTerminated(void) const { return GuestSession::i_isTerminated(mData.mStatus); } /** * Called by IGuest right before this session gets removed from * the public session list. * * @note Takes the write lock. */ int GuestSession::i_onRemove(void) { LogFlowThisFuncEnter(); AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); int vrc = i_objectsUnregister(); /* * Note: The event source stuff holds references to this object, * so make sure that this is cleaned up *before* calling uninit. */ if (!mEventSource.isNull()) { mEventSource->UnregisterListener(mLocalListener); mLocalListener.setNull(); unconst(mEventSource).setNull(); } LogFlowFuncLeaveRC(vrc); return vrc; } /** * Handles guest session status changes from the guest. * * @returns VBox status code. * @param pCbCtx Host callback context from HGCM service. * @param pSvcCbData HGCM service callback data. * * @note Takes the read lock (for session ID lookup). */ int GuestSession::i_onSessionStatusChange(PVBOXGUESTCTRLHOSTCBCTX pCbCtx, PVBOXGUESTCTRLHOSTCALLBACK pSvcCbData) { AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER); /* pCallback is optional. */ AssertPtrReturn(pSvcCbData, VERR_INVALID_POINTER); if (pSvcCbData->mParms < 3) return VERR_INVALID_PARAMETER; CALLBACKDATA_SESSION_NOTIFY dataCb; /* pSvcCb->mpaParms[0] always contains the context ID. */ int vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[1], &dataCb.uType); AssertRCReturn(vrc, vrc); vrc = HGCMSvcGetU32(&pSvcCbData->mpaParms[2], &dataCb.uResult); AssertRCReturn(vrc, vrc); AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); LogFlowThisFunc(("ID=%RU32, uType=%RU32, rcGuest=%Rrc\n", mData.mSession.mID, dataCb.uType, dataCb.uResult)); GuestSessionStatus_T sessionStatus = GuestSessionStatus_Undefined; int rcGuest = dataCb.uResult; /** @todo uint32_t vs. int. */ switch (dataCb.uType) { case GUEST_SESSION_NOTIFYTYPE_ERROR: sessionStatus = GuestSessionStatus_Error; LogRel(("Guest Control: Error starting Session '%s' (%Rrc) \n", mData.mSession.mName.c_str(), rcGuest)); break; case GUEST_SESSION_NOTIFYTYPE_STARTED: sessionStatus = GuestSessionStatus_Started; #if 0 /** @todo If we get some environment stuff along with this kind notification: */ const char *pszzEnvBlock = ...; uint32_t cbEnvBlock = ...; if (!mData.mpBaseEnvironment) { GuestEnvironment *pBaseEnv; try { pBaseEnv = new GuestEnvironment(); } catch (std::bad_alloc &) { pBaseEnv = NULL; } if (pBaseEnv) { int vrc = pBaseEnv->initNormal(Guest.i_isGuestInWindowsNtFamily() ? RTENV_CREATE_F_ALLOW_EQUAL_FIRST_IN_VAR : 0); if (RT_SUCCESS(vrc)) vrc = pBaseEnv->copyUtf8Block(pszzEnvBlock, cbEnvBlock); if (RT_SUCCESS(vrc)) mData.mpBaseEnvironment = pBaseEnv; else pBaseEnv->release(); } } #endif LogRel(("Guest Control: Session '%s' was successfully started\n", mData.mSession.mName.c_str())); break; case GUEST_SESSION_NOTIFYTYPE_TEN: LogRel(("Guest Control: Session '%s' was terminated normally with exit code %#x\n", mData.mSession.mName.c_str(), dataCb.uResult)); sessionStatus = GuestSessionStatus_Terminated; break; case GUEST_SESSION_NOTIFYTYPE_TEA: LogRel(("Guest Control: Session '%s' was terminated abnormally\n", mData.mSession.mName.c_str())); sessionStatus = GuestSessionStatus_Terminated; /* dataCb.uResult is undefined. */ break; case GUEST_SESSION_NOTIFYTYPE_TES: LogRel(("Guest Control: Session '%s' was terminated via signal %#x\n", mData.mSession.mName.c_str(), dataCb.uResult)); sessionStatus = GuestSessionStatus_Terminated; break; case GUEST_SESSION_NOTIFYTYPE_TOK: sessionStatus = GuestSessionStatus_TimedOutKilled; LogRel(("Guest Control: Session '%s' timed out and was killed\n", mData.mSession.mName.c_str())); break; case GUEST_SESSION_NOTIFYTYPE_TOA: sessionStatus = GuestSessionStatus_TimedOutAbnormally; LogRel(("Guest Control: Session '%s' timed out and was not killed successfully\n", mData.mSession.mName.c_str())); break; case GUEST_SESSION_NOTIFYTYPE_DWN: sessionStatus = GuestSessionStatus_Down; LogRel(("Guest Control: Session '%s' got killed as guest service/OS is down\n", mData.mSession.mName.c_str())); break; case GUEST_SESSION_NOTIFYTYPE_UNDEFINED: default: vrc = VERR_NOT_SUPPORTED; break; } /* Leave the lock, as i_setSessionStatus() below will require a write lock for actually * committing the session state. */ alock.release(); if (RT_SUCCESS(vrc)) { if (RT_FAILURE(rcGuest)) sessionStatus = GuestSessionStatus_Error; } /* Set the session status. */ if (RT_SUCCESS(vrc)) vrc = i_setSessionStatus(sessionStatus, rcGuest); LogFlowThisFunc(("ID=%RU32, rcGuest=%Rrc\n", mData.mSession.mID, rcGuest)); LogFlowFuncLeaveRC(vrc); return vrc; } /** * Returns the path separation style used on the guest. * * @returns Separation style used on the guest. */ PathStyle_T GuestSession::i_getGuestPathStyle(void) { PathStyle_T enmPathStyle; VBOXOSTYPE enmOsType = mParent->i_getGuestOSType(); if (enmOsType < VBOXOSTYPE_DOS) { LogFlowFunc(("returns PathStyle_Unknown\n")); enmPathStyle = PathStyle_Unknown; } else if (enmOsType < VBOXOSTYPE_Linux) { LogFlowFunc(("returns PathStyle_DOS\n")); enmPathStyle = PathStyle_DOS; } else { LogFlowFunc(("returns PathStyle_UNIX\n")); enmPathStyle = PathStyle_UNIX; } return enmPathStyle; } /** * Returns the path separation style used on the host. * * @returns Separation style used on the host. */ /* static */ PathStyle_T GuestSession::i_getHostPathStyle(void) { #if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS return PathStyle_DOS; #else return PathStyle_UNIX; #endif } /** * Starts the guest session on the guest. * * @returns VBox status code. * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR * was returned. Optional. * * @note Takes the read and write locks. */ int GuestSession::i_startSession(int *prcGuest) { AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); LogFlowThisFunc(("mID=%RU32, mName=%s, uProtocolVersion=%RU32, openFlags=%x, openTimeoutMS=%RU32\n", mData.mSession.mID, mData.mSession.mName.c_str(), mData.mProtocolVersion, mData.mSession.mOpenFlags, mData.mSession.mOpenTimeoutMS)); /* Guest Additions < 4.3 don't support opening dedicated guest sessions. Simply return success here. */ if (mData.mProtocolVersion < 2) { alock.release(); /* Release lock before changing status. */ /* ignore rc */ i_setSessionStatus(GuestSessionStatus_Started, VINF_SUCCESS); LogFlowThisFunc(("Installed Guest Additions don't support opening dedicated sessions, skipping\n")); return VINF_SUCCESS; } if (mData.mStatus != GuestSessionStatus_Undefined) return VINF_SUCCESS; /** @todo mData.mSession.uFlags validation. */ alock.release(); /* Release lock before changing status. */ /* Set current session status. */ int vrc = i_setSessionStatus(GuestSessionStatus_Starting, VINF_SUCCESS); if (RT_FAILURE(vrc)) return vrc; GuestWaitEvent *pEvent = NULL; GuestEventTypes eventTypes; try { eventTypes.push_back(VBoxEventType_OnGuestSessionStateChanged); vrc = registerWaitEventEx(mData.mSession.mID, mData.mObjectID, eventTypes, &pEvent); } catch (std::bad_alloc &) { vrc = VERR_NO_MEMORY; } if (RT_FAILURE(vrc)) return vrc; alock.acquire(); /* Re-acquire lock before accessing session attributes below. */ VBOXHGCMSVCPARM paParms[8]; int i = 0; HGCMSvcSetU32(&paParms[i++], pEvent->ContextID()); HGCMSvcSetU32(&paParms[i++], mData.mProtocolVersion); HGCMSvcSetPv(&paParms[i++], (void*)mData.mCredentials.mUser.c_str(), (ULONG)mData.mCredentials.mUser.length() + 1); HGCMSvcSetPv(&paParms[i++], (void*)mData.mCredentials.mPassword.c_str(), (ULONG)mData.mCredentials.mPassword.length() + 1); HGCMSvcSetPv(&paParms[i++], (void*)mData.mCredentials.mDomain.c_str(), (ULONG)mData.mCredentials.mDomain.length() + 1); HGCMSvcSetU32(&paParms[i++], mData.mSession.mOpenFlags); alock.release(); /* Drop lock before sending. */ vrc = i_sendMessage(HOST_MSG_SESSION_CREATE, i, paParms, VBOX_GUESTCTRL_DST_ROOT_SVC); if (RT_SUCCESS(vrc)) { vrc = i_waitForStatusChange(pEvent, GuestSessionWaitForFlag_Start, 30 * 1000 /* 30s timeout */, NULL /* Session status */, prcGuest); } else { /* * Unable to start guest session - update its current state. * Since there is no (official API) way to recover a failed guest session * this also marks the end state. Internally just calling this * same function again will work though. */ /* ignore rc */ i_setSessionStatus(GuestSessionStatus_Error, vrc); } unregisterWaitEvent(pEvent); LogFlowFuncLeaveRC(vrc); return vrc; } /** * Starts the guest session asynchronously in a separate worker thread. * * @returns IPRT status code. */ int GuestSession::i_startSessionAsync(void) { LogFlowThisFuncEnter(); /* Create task: */ GuestSessionTaskInternalStart *pTask = NULL; try { pTask = new GuestSessionTaskInternalStart(this); } catch (std::bad_alloc &) { return VERR_NO_MEMORY; } if (pTask->isOk()) { /* Kick off the thread: */ HRESULT hrc = pTask->createThread(); pTask = NULL; /* Not valid anymore, not even on failure! */ if (SUCCEEDED(hrc)) { LogFlowFuncLeaveRC(VINF_SUCCESS); return VINF_SUCCESS; } LogFlow(("GuestSession: Failed to create thread for GuestSessionTaskInternalOpen task.\n")); } else LogFlow(("GuestSession: GuestSessionTaskInternalStart creation failed: %Rhrc.\n", pTask->rc())); LogFlowFuncLeaveRC(VERR_GENERAL_FAILURE); return VERR_GENERAL_FAILURE; } /** * Static function to start a guest session asynchronously. * * @returns IPRT status code. * @param pTask Task object to use for starting the guest session. */ /* static */ int GuestSession::i_startSessionThreadTask(GuestSessionTaskInternalStart *pTask) { LogFlowFunc(("pTask=%p\n", pTask)); AssertPtr(pTask); const ComObjPtr pSession(pTask->Session()); Assert(!pSession.isNull()); AutoCaller autoCaller(pSession); if (FAILED(autoCaller.rc())) return VERR_COM_INVALID_OBJECT_STATE; int vrc = pSession->i_startSession(NULL /* Guest rc, ignored */); /* Nothing to do here anymore. */ LogFlowFuncLeaveRC(vrc); return vrc; } /** * Registers an object with the session, i.e. allocates an object ID. * * @return VBox status code. * @retval VERR_GSTCTL_MAX_OBJECTS_REACHED if the maximum of concurrent objects * is reached. * @param pObject Guest object to register (weak pointer). Optional. * @param enmType Session object type to register. * @param pidObject Where to return the object ID on success. Optional. */ int GuestSession::i_objectRegister(GuestObject *pObject, SESSIONOBJECTTYPE enmType, uint32_t *pidObject) { /* pObject can be NULL. */ /* pidObject is optional. */ /* * Pick a random bit as starting point. If it's in use, search forward * for a free one, wrapping around. We've reserved both the zero'th and * max-1 IDs (see Data constructor). */ uint32_t idObject = RTRandU32Ex(1, VBOX_GUESTCTRL_MAX_OBJECTS - 2); AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); if (!ASMBitTestAndSet(&mData.bmObjectIds[0], idObject)) { /* likely */ } else if (mData.mObjects.size() < VBOX_GUESTCTRL_MAX_OBJECTS - 2 /* First and last are not used */) { /* Forward search. */ int iHit = ASMBitNextClear(&mData.bmObjectIds[0], VBOX_GUESTCTRL_MAX_OBJECTS, idObject); if (iHit < 0) iHit = ASMBitFirstClear(&mData.bmObjectIds[0], VBOX_GUESTCTRL_MAX_OBJECTS); AssertLogRelMsgReturn(iHit >= 0, ("object count: %#zu\n", mData.mObjects.size()), VERR_GSTCTL_MAX_CID_OBJECTS_REACHED); idObject = iHit; AssertLogRelMsgReturn(!ASMBitTestAndSet(&mData.bmObjectIds[0], idObject), ("idObject=%#x\n", idObject), VERR_INTERNAL_ERROR_2); } else { LogFunc(("Maximum number of objects reached (enmType=%RU32, %zu objects)\n", enmType, mData.mObjects.size())); return VERR_GSTCTL_MAX_CID_OBJECTS_REACHED; } Log2Func(("enmType=%RU32 -> idObject=%RU32 (%zu objects)\n", enmType, idObject, mData.mObjects.size())); try { mData.mObjects[idObject].pObject = pObject; /* Can be NULL. */ mData.mObjects[idObject].enmType = enmType; mData.mObjects[idObject].msBirth = RTTimeMilliTS(); } catch (std::bad_alloc &) { ASMBitClear(&mData.bmObjectIds[0], idObject); return VERR_NO_MEMORY; } if (pidObject) *pidObject = idObject; return VINF_SUCCESS; } /** * Unregisters an object from the session objects list. * * @retval VINF_SUCCESS on success. * @retval VERR_NOT_FOUND if the object ID was not found. * @param idObject Object ID to unregister. * * @note Takes the write lock. */ int GuestSession::i_objectUnregister(uint32_t idObject) { AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); int rc = VINF_SUCCESS; AssertMsgStmt(ASMBitTestAndClear(&mData.bmObjectIds, idObject), ("idObject=%#x\n", idObject), rc = VERR_NOT_FOUND); SessionObjects::iterator ItObj = mData.mObjects.find(idObject); AssertMsgReturn(ItObj != mData.mObjects.end(), ("idObject=%#x\n", idObject), VERR_NOT_FOUND); mData.mObjects.erase(ItObj); return rc; } /** * Unregisters all objects from the session list. * * @returns VBox status code. * * @note Takes the write lock. */ int GuestSession::i_objectsUnregister(void) { AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); LogFlowThisFunc(("Unregistering directories (%zu total)\n", mData.mDirectories.size())); SessionDirectories::iterator itDirs; while ((itDirs = mData.mDirectories.begin()) != mData.mDirectories.end()) { alock.release(); i_directoryUnregister(itDirs->second); alock.acquire(); } Assert(mData.mDirectories.size() == 0); mData.mDirectories.clear(); LogFlowThisFunc(("Unregistering files (%zu total)\n", mData.mFiles.size())); SessionFiles::iterator itFiles; while ((itFiles = mData.mFiles.begin()) != mData.mFiles.end()) { alock.release(); i_fileUnregister(itFiles->second); alock.acquire(); } Assert(mData.mFiles.size() == 0); mData.mFiles.clear(); LogFlowThisFunc(("Unregistering processes (%zu total)\n", mData.mProcesses.size())); SessionProcesses::iterator itProcs; while ((itProcs = mData.mProcesses.begin()) != mData.mProcesses.end()) { alock.release(); i_processUnregister(itProcs->second); alock.acquire(); } Assert(mData.mProcesses.size() == 0); mData.mProcesses.clear(); return VINF_SUCCESS; } /** * Notifies all registered objects about a guest session status change. * * @returns VBox status code. * @param enmSessionStatus Session status to notify objects about. */ int GuestSession::i_objectsNotifyAboutStatusChange(GuestSessionStatus_T enmSessionStatus) { LogFlowThisFunc(("enmSessionStatus=%RU32\n", enmSessionStatus)); int vrc = VINF_SUCCESS; SessionObjects::iterator itObjs = mData.mObjects.begin(); while (itObjs != mData.mObjects.end()) { GuestObject *pObj = itObjs->second.pObject; if (pObj) /* pObject can be NULL (weak pointer). */ { int vrc2 = pObj->i_onSessionStatusChange(enmSessionStatus); if (RT_SUCCESS(vrc)) vrc = vrc2; /* If the session got terminated, make sure to cancel all wait events for * the current object. */ if (i_isTerminated()) pObj->cancelWaitEvents(); } ++itObjs; } LogFlowFuncLeaveRC(vrc); return vrc; } /** * Renames a path on the guest. * * @returns VBox status code. * @returns VERR_GSTCTL_GUEST_ERROR on received guest error. * @param strSource Source path on guest to rename. * @param strDest Destination path on guest to rename \a strSource to. * @param uFlags Renaming flags. * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR * was returned. Optional. * @note Takes the read lock. */ int GuestSession::i_pathRename(const Utf8Str &strSource, const Utf8Str &strDest, uint32_t uFlags, int *prcGuest) { AssertReturn(!(uFlags & ~PATHRENAME_FLAG_VALID_MASK), VERR_INVALID_PARAMETER); LogFlowThisFunc(("strSource=%s, strDest=%s, uFlags=0x%x\n", strSource.c_str(), strDest.c_str(), uFlags)); AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); GuestWaitEvent *pEvent = NULL; int vrc = registerWaitEvent(mData.mSession.mID, mData.mObjectID, &pEvent); if (RT_FAILURE(vrc)) return vrc; /* Prepare HGCM call. */ VBOXHGCMSVCPARM paParms[8]; int i = 0; HGCMSvcSetU32(&paParms[i++], pEvent->ContextID()); HGCMSvcSetPv(&paParms[i++], (void*)strSource.c_str(), (ULONG)strSource.length() + 1); HGCMSvcSetPv(&paParms[i++], (void*)strDest.c_str(), (ULONG)strDest.length() + 1); HGCMSvcSetU32(&paParms[i++], uFlags); alock.release(); /* Drop lock before sending. */ vrc = i_sendMessage(HOST_MSG_PATH_RENAME, i, paParms); if (RT_SUCCESS(vrc)) { vrc = pEvent->Wait(30 * 1000); if ( vrc == VERR_GSTCTL_GUEST_ERROR && prcGuest) *prcGuest = pEvent->GuestResult(); } unregisterWaitEvent(pEvent); LogFlowFuncLeaveRC(vrc); return vrc; } /** * Returns the user's absolute documents path, if any. * * @returns VBox status code. * @param strPath Where to store the user's document path. * @param prcGuest Guest rc, when returning VERR_GSTCTL_GUEST_ERROR. * Any other return code indicates some host side error. * * @note Takes the read lock. */ int GuestSession::i_pathUserDocuments(Utf8Str &strPath, int *prcGuest) { AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); /** @todo Cache the user's document path? */ GuestWaitEvent *pEvent = NULL; int vrc = registerWaitEvent(mData.mSession.mID, mData.mObjectID, &pEvent); if (RT_FAILURE(vrc)) return vrc; /* Prepare HGCM call. */ VBOXHGCMSVCPARM paParms[2]; int i = 0; HGCMSvcSetU32(&paParms[i++], pEvent->ContextID()); alock.release(); /* Drop lock before sending. */ vrc = i_sendMessage(HOST_MSG_PATH_USER_DOCUMENTS, i, paParms); if (RT_SUCCESS(vrc)) { vrc = pEvent->Wait(30 * 1000); if (RT_SUCCESS(vrc)) { strPath = pEvent->Payload().ToString(); } else { if (vrc == VERR_GSTCTL_GUEST_ERROR) { if (prcGuest) *prcGuest = pEvent->GuestResult(); } } } unregisterWaitEvent(pEvent); LogFlowFuncLeaveRC(vrc); return vrc; } /** * Returns the user's absolute home path, if any. * * @returns VBox status code. * @param strPath Where to store the user's home path. * @param prcGuest Guest rc, when returning VERR_GSTCTL_GUEST_ERROR. * Any other return code indicates some host side error. * * @note Takes the read lock. */ int GuestSession::i_pathUserHome(Utf8Str &strPath, int *prcGuest) { AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); /** @todo Cache the user's home path? */ GuestWaitEvent *pEvent = NULL; int vrc = registerWaitEvent(mData.mSession.mID, mData.mObjectID, &pEvent); if (RT_FAILURE(vrc)) return vrc; /* Prepare HGCM call. */ VBOXHGCMSVCPARM paParms[2]; int i = 0; HGCMSvcSetU32(&paParms[i++], pEvent->ContextID()); alock.release(); /* Drop lock before sending. */ vrc = i_sendMessage(HOST_MSG_PATH_USER_HOME, i, paParms); if (RT_SUCCESS(vrc)) { vrc = pEvent->Wait(30 * 1000); if (RT_SUCCESS(vrc)) { strPath = pEvent->Payload().ToString(); } else { if (vrc == VERR_GSTCTL_GUEST_ERROR) { if (prcGuest) *prcGuest = pEvent->GuestResult(); } } } unregisterWaitEvent(pEvent); LogFlowFuncLeaveRC(vrc); return vrc; } /** * Unregisters a process object from a guest session. * * @returns VBox status code. VERR_NOT_FOUND if the process is not registered (anymore). * @param pProcess Process object to unregister from session. * * @note Takes the write lock. */ int GuestSession::i_processUnregister(GuestProcess *pProcess) { AssertPtrReturn(pProcess, VERR_INVALID_POINTER); LogFlowThisFunc(("pProcess=%p\n", pProcess)); AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); const uint32_t idObject = pProcess->getObjectID(); LogFlowFunc(("Removing process (objectID=%RU32) ...\n", idObject)); int rc = i_objectUnregister(idObject); if (RT_FAILURE(rc)) return rc; SessionProcesses::iterator itProcs = mData.mProcesses.find(idObject); AssertReturn(itProcs != mData.mProcesses.end(), VERR_NOT_FOUND); /* Make sure to consume the pointer before the one of the iterator gets released. */ ComObjPtr pProc = pProcess; ULONG uPID; HRESULT hr = pProc->COMGETTER(PID)(&uPID); ComAssertComRC(hr); LogFlowFunc(("Removing process ID=%RU32 (session %RU32, guest PID %RU32, now total %zu processes)\n", idObject, mData.mSession.mID, uPID, mData.mProcesses.size())); rc = pProcess->i_onUnregister(); AssertRCReturn(rc, rc); mData.mProcesses.erase(itProcs); alock.release(); /* Release lock before firing off event. */ ::FireGuestProcessRegisteredEvent(mEventSource, this /* Session */, pProc, uPID, false /* Process unregistered */); pProc.setNull(); LogFlowFuncLeaveRC(rc); return rc; } /** * Creates but does *not* start the process yet. * * See GuestProcess::startProcess() or GuestProcess::startProcessAsync() for * starting the process. * * @returns IPRT status code. * @param procInfo Process startup info to use for starting the process. * @param pProcess Where to return the created guest process object on success. * * @note Takes the write lock. */ int GuestSession::i_processCreateEx(GuestProcessStartupInfo &procInfo, ComObjPtr &pProcess) { LogFlowFunc(("mExe=%s, mFlags=%x, mTimeoutMS=%RU32\n", procInfo.mExecutable.c_str(), procInfo.mFlags, procInfo.mTimeoutMS)); #ifdef DEBUG if (procInfo.mArguments.size()) { LogFlowFunc(("Arguments:")); ProcessArguments::const_iterator it = procInfo.mArguments.begin(); while (it != procInfo.mArguments.end()) { LogFlow((" %s", (*it).c_str())); ++it; } LogFlow(("\n")); } #endif /* Validate flags. */ if (procInfo.mFlags) { if ( !(procInfo.mFlags & ProcessCreateFlag_IgnoreOrphanedProcesses) && !(procInfo.mFlags & ProcessCreateFlag_WaitForProcessStartOnly) && !(procInfo.mFlags & ProcessCreateFlag_Hidden) && !(procInfo.mFlags & ProcessCreateFlag_Profile) && !(procInfo.mFlags & ProcessCreateFlag_WaitForStdOut) && !(procInfo.mFlags & ProcessCreateFlag_WaitForStdErr)) { return VERR_INVALID_PARAMETER; } } if ( (procInfo.mFlags & ProcessCreateFlag_WaitForProcessStartOnly) && ( (procInfo.mFlags & ProcessCreateFlag_WaitForStdOut) || (procInfo.mFlags & ProcessCreateFlag_WaitForStdErr) ) ) { return VERR_INVALID_PARAMETER; } if (procInfo.mPriority) { if (!(procInfo.mPriority & ProcessPriority_Default)) return VERR_INVALID_PARAMETER; } /* Adjust timeout. * If set to 0, we define an infinite timeout (unlimited process run time). */ if (procInfo.mTimeoutMS == 0) procInfo.mTimeoutMS = UINT32_MAX; /** @todo Implement process priority + affinity. */ AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); /* Create the process object. */ HRESULT hr = pProcess.createObject(); if (FAILED(hr)) return VERR_COM_UNEXPECTED; /* Register a new object ID. */ uint32_t idObject; int rc = i_objectRegister(pProcess, SESSIONOBJECTTYPE_PROCESS, &idObject); if (RT_FAILURE(rc)) { pProcess.setNull(); return rc; } rc = pProcess->init(mParent->i_getConsole() /* Console */, this /* Session */, idObject, procInfo, mData.mpBaseEnvironment); if (RT_FAILURE(rc)) return rc; /* Add the created process to our map. */ try { mData.mProcesses[idObject] = pProcess; LogFlowFunc(("Added new process (Session: %RU32) with process ID=%RU32 (now total %zu processes)\n", mData.mSession.mID, idObject, mData.mProcesses.size())); alock.release(); /* Release lock before firing off event. */ ::FireGuestProcessRegisteredEvent(mEventSource, this /* Session */, pProcess, 0 /* PID */, true /* Process registered */); } catch (std::bad_alloc &) { rc = VERR_NO_MEMORY; } return rc; } /** * Checks if a process object exists and optionally returns its object. * * @returns \c true if process object exists, or \c false if not. * @param uProcessID ID of process object to check. * @param pProcess Where to return the found process object on success. * * @note No locking done! */ inline bool GuestSession::i_processExists(uint32_t uProcessID, ComObjPtr *pProcess) { SessionProcesses::const_iterator it = mData.mProcesses.find(uProcessID); if (it != mData.mProcesses.end()) { if (pProcess) *pProcess = it->second; return true; } return false; } /** * Returns the process object from a guest PID. * * @returns VBox status code. * @param uPID Guest PID to get process object for. * @param pProcess Where to return the process object on success. * * @note No locking done! */ inline int GuestSession::i_processGetByPID(ULONG uPID, ComObjPtr *pProcess) { AssertReturn(uPID, false); /* pProcess is optional. */ SessionProcesses::iterator itProcs = mData.mProcesses.begin(); for (; itProcs != mData.mProcesses.end(); ++itProcs) { ComObjPtr pCurProc = itProcs->second; AutoCaller procCaller(pCurProc); if (!procCaller.isOk()) return VERR_COM_INVALID_OBJECT_STATE; ULONG uCurPID; HRESULT hr = pCurProc->COMGETTER(PID)(&uCurPID); ComAssertComRC(hr); if (uCurPID == uPID) { if (pProcess) *pProcess = pCurProc; return VINF_SUCCESS; } } return VERR_NOT_FOUND; } /** * Sends a message to the HGCM host service. * * @returns VBox status code. * @param uMessage Message ID to send. * @param uParms Number of parameters in \a paParms to send. * @param paParms Array of HGCM parameters to send. * @param fDst Host message destination flags of type VBOX_GUESTCTRL_DST_XXX. */ int GuestSession::i_sendMessage(uint32_t uMessage, uint32_t uParms, PVBOXHGCMSVCPARM paParms, uint64_t fDst /*= VBOX_GUESTCTRL_DST_SESSION*/) { LogFlowThisFuncEnter(); #ifndef VBOX_GUESTCTRL_TEST_CASE ComObjPtr pConsole = mParent->i_getConsole(); Assert(!pConsole.isNull()); /* Forward the information to the VMM device. */ VMMDev *pVMMDev = pConsole->i_getVMMDev(); AssertPtr(pVMMDev); LogFlowThisFunc(("uMessage=%RU32 (%s), uParms=%RU32\n", uMessage, GstCtrlHostMsgtoStr((guestControl::eHostMsg)uMessage), uParms)); /* HACK ALERT! We extend the first parameter to 64-bit and use the two topmost bits for call destination information. */ Assert(fDst == VBOX_GUESTCTRL_DST_SESSION || fDst == VBOX_GUESTCTRL_DST_ROOT_SVC || fDst == VBOX_GUESTCTRL_DST_BOTH); Assert(paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT); paParms[0].type = VBOX_HGCM_SVC_PARM_64BIT; paParms[0].u.uint64 = (uint64_t)paParms[0].u.uint32 | fDst; /* Make the call. */ int vrc = pVMMDev->hgcmHostCall(HGCMSERVICE_NAME, uMessage, uParms, paParms); if (RT_FAILURE(vrc)) { /** @todo What to do here? */ } #else /* Not needed within testcases. */ int vrc = VINF_SUCCESS; #endif LogFlowFuncLeaveRC(vrc); return vrc; } /** * Sets the guest session's current status. * * @returns VBox status code. * @param sessionStatus Session status to set. * @param sessionRc Session result to set (for error handling). * * @note Takes the write lock. */ int GuestSession::i_setSessionStatus(GuestSessionStatus_T sessionStatus, int sessionRc) { LogFlowThisFunc(("oldStatus=%RU32, newStatus=%RU32, sessionRc=%Rrc\n", mData.mStatus, sessionStatus, sessionRc)); if (sessionStatus == GuestSessionStatus_Error) { AssertMsg(RT_FAILURE(sessionRc), ("Guest rc must be an error (%Rrc)\n", sessionRc)); /* Do not allow overwriting an already set error. If this happens * this means we forgot some error checking/locking somewhere. */ AssertMsg(RT_SUCCESS(mData.mRC), ("Guest rc already set (to %Rrc)\n", mData.mRC)); } else AssertMsg(RT_SUCCESS(sessionRc), ("Guest rc must not be an error (%Rrc)\n", sessionRc)); AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); int vrc = VINF_SUCCESS; if (mData.mStatus != sessionStatus) { mData.mStatus = sessionStatus; mData.mRC = sessionRc; /* Make sure to notify all underlying objects first. */ vrc = i_objectsNotifyAboutStatusChange(sessionStatus); ComObjPtr errorInfo; HRESULT hr = errorInfo.createObject(); ComAssertComRC(hr); int rc2 = errorInfo->initEx(VBOX_E_IPRT_ERROR, sessionRc, COM_IIDOF(IGuestSession), getComponentName(), i_guestErrorToString(sessionRc)); AssertRC(rc2); alock.release(); /* Release lock before firing off event. */ ::FireGuestSessionStateChangedEvent(mEventSource, this, mData.mSession.mID, sessionStatus, errorInfo); } LogFlowFuncLeaveRC(vrc); return vrc; } /** @todo Unused --remove? */ int GuestSession::i_signalWaiters(GuestSessionWaitResult_T enmWaitResult, int rc /*= VINF_SUCCESS */) { RT_NOREF(enmWaitResult, rc); /*LogFlowThisFunc(("enmWaitResult=%d, rc=%Rrc, mWaitCount=%RU32, mWaitEvent=%p\n", enmWaitResult, rc, mData.mWaitCount, mData.mWaitEvent));*/ /* Note: No write locking here -- already done in the caller. */ int vrc = VINF_SUCCESS; /*if (mData.mWaitEvent) vrc = mData.mWaitEvent->Signal(enmWaitResult, rc);*/ LogFlowFuncLeaveRC(vrc); return vrc; } /** * Shuts down (and optionally powers off / reboots) the guest. * Needs supported Guest Additions installed. * * @returns VBox status code. VERR_NOT_SUPPORTED if not supported by Guest Additions. * @param fFlags Guest shutdown flags. * @param prcGuest Guest rc, when returning VERR_GSTCTL_GUEST_ERROR. * Any other return code indicates some host side error. * * @note Takes the read lock. */ int GuestSession::i_shutdown(uint32_t fFlags, int *prcGuest) { AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); AssertPtrReturn(mParent, VERR_INVALID_POINTER); if (!(mParent->i_getGuestControlFeatures0() & VBOX_GUESTCTRL_GF_0_SHUTDOWN)) return VERR_NOT_SUPPORTED; LogRel(("Guest Control: Shutting down guest (flags = %#x) ...\n", fFlags)); GuestWaitEvent *pEvent = NULL; int vrc = registerWaitEvent(mData.mSession.mID, mData.mObjectID, &pEvent); if (RT_FAILURE(vrc)) return vrc; /* Prepare HGCM call. */ VBOXHGCMSVCPARM paParms[2]; int i = 0; HGCMSvcSetU32(&paParms[i++], pEvent->ContextID()); HGCMSvcSetU32(&paParms[i++], fFlags); alock.release(); /* Drop lock before sending. */ int rcGuest = VERR_IPE_UNINITIALIZED_STATUS; vrc = i_sendMessage(HOST_MSG_SHUTDOWN, i, paParms); if (RT_SUCCESS(vrc)) { vrc = pEvent->Wait(30 * 1000); if (RT_FAILURE(vrc)) { if (vrc == VERR_GSTCTL_GUEST_ERROR) rcGuest = pEvent->GuestResult(); } } if (RT_FAILURE(vrc)) { LogRel(("Guest Control: Shutting down guest failed, rc=%Rrc\n", vrc == VERR_GSTCTL_GUEST_ERROR ? rcGuest : vrc)); if ( vrc == VERR_GSTCTL_GUEST_ERROR && prcGuest) *prcGuest = rcGuest; } unregisterWaitEvent(pEvent); LogFlowFuncLeaveRC(vrc); return vrc; } /** * Determines the protocol version (sets mData.mProtocolVersion). * * This is called from the init method prior to to establishing a guest * session. * * @returns VBox status code. */ int GuestSession::i_determineProtocolVersion(void) { /* * We currently do this based on the reported Guest Additions version, * ASSUMING that VBoxService and VBoxDrv are at the same version. */ ComObjPtr pGuest = mParent; AssertReturn(!pGuest.isNull(), VERR_NOT_SUPPORTED); uint32_t uGaVersion = pGuest->i_getAdditionsVersion(); /* Everyone supports version one, if they support anything at all. */ mData.mProtocolVersion = 1; /* Guest control 2.0 was introduced with 4.3.0. */ if (uGaVersion >= VBOX_FULL_VERSION_MAKE(4,3,0)) mData.mProtocolVersion = 2; /* Guest control 2.0. */ LogFlowThisFunc(("uGaVersion=%u.%u.%u => mProtocolVersion=%u\n", VBOX_FULL_VERSION_GET_MAJOR(uGaVersion), VBOX_FULL_VERSION_GET_MINOR(uGaVersion), VBOX_FULL_VERSION_GET_BUILD(uGaVersion), mData.mProtocolVersion)); /* * Inform the user about outdated Guest Additions (VM release log). */ if (mData.mProtocolVersion < 2) LogRelMax(3, ("Warning: Guest Additions v%u.%u.%u only supports the older guest control protocol version %u.\n" " Please upgrade GAs to the current version to get full guest control capabilities.\n", VBOX_FULL_VERSION_GET_MAJOR(uGaVersion), VBOX_FULL_VERSION_GET_MINOR(uGaVersion), VBOX_FULL_VERSION_GET_BUILD(uGaVersion), mData.mProtocolVersion)); return VINF_SUCCESS; } /** * Waits for guest session events. * * @returns VBox status code. * @retval VERR_GSTCTL_GUEST_ERROR on received guest error. * @retval VERR_TIMEOUT when a timeout has occurred. * @param fWaitFlags Wait flags to use. * @param uTimeoutMS Timeout (in ms) to wait. * @param waitResult Where to return the wait result on success. * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR * was returned. Optional. * * @note Takes the read lock. */ int GuestSession::i_waitFor(uint32_t fWaitFlags, ULONG uTimeoutMS, GuestSessionWaitResult_T &waitResult, int *prcGuest) { LogFlowThisFuncEnter(); AssertReturn(fWaitFlags, VERR_INVALID_PARAMETER); /*LogFlowThisFunc(("fWaitFlags=0x%x, uTimeoutMS=%RU32, mStatus=%RU32, mWaitCount=%RU32, mWaitEvent=%p, prcGuest=%p\n", fWaitFlags, uTimeoutMS, mData.mStatus, mData.mWaitCount, mData.mWaitEvent, prcGuest));*/ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); /* Did some error occur before? Then skip waiting and return. */ if (mData.mStatus == GuestSessionStatus_Error) { waitResult = GuestSessionWaitResult_Error; AssertMsg(RT_FAILURE(mData.mRC), ("No error rc (%Rrc) set when guest session indicated an error\n", mData.mRC)); if (prcGuest) *prcGuest = mData.mRC; /* Return last set error. */ return VERR_GSTCTL_GUEST_ERROR; } /* Guest Additions < 4.3 don't support session handling, skip. */ if (mData.mProtocolVersion < 2) { waitResult = GuestSessionWaitResult_WaitFlagNotSupported; LogFlowThisFunc(("Installed Guest Additions don't support waiting for dedicated sessions, skipping\n")); return VINF_SUCCESS; } waitResult = GuestSessionWaitResult_None; if (fWaitFlags & GuestSessionWaitForFlag_Terminate) { switch (mData.mStatus) { case GuestSessionStatus_Terminated: case GuestSessionStatus_Down: waitResult = GuestSessionWaitResult_Terminate; break; case GuestSessionStatus_TimedOutKilled: case GuestSessionStatus_TimedOutAbnormally: waitResult = GuestSessionWaitResult_Timeout; break; case GuestSessionStatus_Error: /* Handled above. */ break; case GuestSessionStatus_Started: waitResult = GuestSessionWaitResult_Start; break; case GuestSessionStatus_Undefined: case GuestSessionStatus_Starting: /* Do the waiting below. */ break; default: AssertMsgFailed(("Unhandled session status %RU32\n", mData.mStatus)); return VERR_NOT_IMPLEMENTED; } } else if (fWaitFlags & GuestSessionWaitForFlag_Start) { switch (mData.mStatus) { case GuestSessionStatus_Started: case GuestSessionStatus_Terminating: case GuestSessionStatus_Terminated: case GuestSessionStatus_Down: waitResult = GuestSessionWaitResult_Start; break; case GuestSessionStatus_Error: waitResult = GuestSessionWaitResult_Error; break; case GuestSessionStatus_TimedOutKilled: case GuestSessionStatus_TimedOutAbnormally: waitResult = GuestSessionWaitResult_Timeout; break; case GuestSessionStatus_Undefined: case GuestSessionStatus_Starting: /* Do the waiting below. */ break; default: AssertMsgFailed(("Unhandled session status %RU32\n", mData.mStatus)); return VERR_NOT_IMPLEMENTED; } } LogFlowThisFunc(("sessionStatus=%RU32, sessionRc=%Rrc, waitResult=%RU32\n", mData.mStatus, mData.mRC, waitResult)); /* No waiting needed? Return immediately using the last set error. */ if (waitResult != GuestSessionWaitResult_None) { if (prcGuest) *prcGuest = mData.mRC; /* Return last set error (if any). */ return RT_SUCCESS(mData.mRC) ? VINF_SUCCESS : VERR_GSTCTL_GUEST_ERROR; } int vrc = VINF_SUCCESS; uint64_t const tsStart = RTTimeMilliTS(); uint64_t tsNow = tsStart; while (tsNow - tsStart < uTimeoutMS) { GuestWaitEvent *pEvent = NULL; GuestEventTypes eventTypes; try { eventTypes.push_back(VBoxEventType_OnGuestSessionStateChanged); vrc = registerWaitEventEx(mData.mSession.mID, mData.mObjectID, eventTypes, &pEvent); } catch (std::bad_alloc &) { vrc = VERR_NO_MEMORY; } if (RT_FAILURE(vrc)) return vrc; alock.release(); /* Release lock before waiting. */ GuestSessionStatus_T sessionStatus; vrc = i_waitForStatusChange(pEvent, fWaitFlags, uTimeoutMS - (tsNow - tsStart), &sessionStatus, prcGuest); if (RT_SUCCESS(vrc)) { switch (sessionStatus) { case GuestSessionStatus_Started: waitResult = GuestSessionWaitResult_Start; break; case GuestSessionStatus_Starting: RT_FALL_THROUGH(); case GuestSessionStatus_Terminating: if (fWaitFlags & GuestSessionWaitForFlag_Status) /* Any status wanted? */ waitResult = GuestSessionWaitResult_Status; /* else: Wait another round until we get the event(s) fWaitFlags defines. */ break; case GuestSessionStatus_Terminated: waitResult = GuestSessionWaitResult_Terminate; break; case GuestSessionStatus_TimedOutKilled: RT_FALL_THROUGH(); case GuestSessionStatus_TimedOutAbnormally: waitResult = GuestSessionWaitResult_Timeout; break; case GuestSessionStatus_Down: waitResult = GuestSessionWaitResult_Terminate; break; case GuestSessionStatus_Error: waitResult = GuestSessionWaitResult_Error; break; default: waitResult = GuestSessionWaitResult_Status; break; } } unregisterWaitEvent(pEvent); /* Wait result not None, e.g. some result acquired or a wait error occurred? Bail out. */ if ( waitResult != GuestSessionWaitResult_None || RT_FAILURE(vrc)) break; tsNow = RTTimeMilliTS(); alock.acquire(); /* Re-acquire lock before waiting for the next event. */ } if (tsNow - tsStart >= uTimeoutMS) { waitResult = GuestSessionWaitResult_None; /* Paranoia. */ vrc = VERR_TIMEOUT; } LogFlowFuncLeaveRC(vrc); return vrc; } /** * Waits for guest session status changes. * * @returns VBox status code. * @retval VERR_GSTCTL_GUEST_ERROR on received guest error. * @retval VERR_WRONG_ORDER when an unexpected event type has been received. * @param pEvent Wait event to use for waiting. * @param fWaitFlags Wait flags to use. * @param uTimeoutMS Timeout (in ms) to wait. * @param pSessionStatus Where to return the guest session status. * @param prcGuest Where to return the guest error when VERR_GSTCTL_GUEST_ERROR * was returned. Optional. */ int GuestSession::i_waitForStatusChange(GuestWaitEvent *pEvent, uint32_t fWaitFlags, uint32_t uTimeoutMS, GuestSessionStatus_T *pSessionStatus, int *prcGuest) { RT_NOREF(fWaitFlags); AssertPtrReturn(pEvent, VERR_INVALID_POINTER); VBoxEventType_T evtType; ComPtr pIEvent; int vrc = waitForEvent(pEvent, uTimeoutMS, &evtType, pIEvent.asOutParam()); if (RT_SUCCESS(vrc)) { if (evtType == VBoxEventType_OnGuestSessionStateChanged) { ComPtr pChangedEvent = pIEvent; Assert(!pChangedEvent.isNull()); GuestSessionStatus_T sessionStatus; pChangedEvent->COMGETTER(Status)(&sessionStatus); if (pSessionStatus) *pSessionStatus = sessionStatus; ComPtr errorInfo; HRESULT hr = pChangedEvent->COMGETTER(Error)(errorInfo.asOutParam()); ComAssertComRC(hr); LONG lGuestRc; hr = errorInfo->COMGETTER(ResultDetail)(&lGuestRc); ComAssertComRC(hr); if (RT_FAILURE((int)lGuestRc)) vrc = VERR_GSTCTL_GUEST_ERROR; if (prcGuest) *prcGuest = (int)lGuestRc; LogFlowThisFunc(("Status changed event for session ID=%RU32, new status is: %RU32 (%Rrc)\n", mData.mSession.mID, sessionStatus, RT_SUCCESS((int)lGuestRc) ? VINF_SUCCESS : (int)lGuestRc)); } else /** @todo Re-visit this. Can this happen more frequently? */ AssertMsgFailedReturn(("Got unexpected event type %#x\n", evtType), VERR_WRONG_ORDER); } /* waitForEvent may also return VERR_GSTCTL_GUEST_ERROR like we do above, so make prcGuest is set. */ else if (vrc == VERR_GSTCTL_GUEST_ERROR && prcGuest) *prcGuest = pEvent->GuestResult(); Assert(vrc != VERR_GSTCTL_GUEST_ERROR || !prcGuest || *prcGuest != (int)0xcccccccc); LogFlowFuncLeaveRC(vrc); return vrc; } // implementation of public methods ///////////////////////////////////////////////////////////////////////////// HRESULT GuestSession::close() { LogFlowThisFuncEnter(); /* Note: Don't check if the session is ready via i_isStartedExternal() here; * the session (already) could be in a stopped / aborted state. */ int vrc = VINF_SUCCESS; /* Shut up MSVC. */ int rcGuest = VINF_SUCCESS; uint32_t msTimeout = RT_MS_10SEC; /* 10s timeout by default */ for (int i = 0; i < 3; i++) { if (i) { LogRel(("Guest Control: Closing session '%s' timed out (%RU32s timeout, attempt %d/10), retrying ...\n", mData.mSession.mName.c_str(), msTimeout / RT_MS_1SEC, i + 1)); msTimeout += RT_MS_5SEC; /* Slightly increase the timeout. */ } /* Close session on guest. */ vrc = i_closeSession(0 /* Flags */, msTimeout, &rcGuest); if ( RT_SUCCESS(vrc) || vrc != VERR_TIMEOUT) /* If something else happened there is no point in retrying further. */ break; } /* On failure don't return here, instead do all the cleanup * work first and then return an error. */ /* Destroy session + remove ourselves from the session list. */ AssertPtr(mParent); int vrc2 = mParent->i_sessionDestroy(mData.mSession.mID); if (vrc2 == VERR_NOT_FOUND) /* Not finding the session anymore isn't critical. */ vrc2 = VINF_SUCCESS; if (RT_SUCCESS(vrc)) vrc = vrc2; LogFlowThisFunc(("Returning rc=%Rrc, rcGuest=%Rrc\n", vrc, rcGuest)); if (RT_FAILURE(vrc)) { if (vrc == VERR_GSTCTL_GUEST_ERROR) { GuestErrorInfo ge(GuestErrorInfo::Type_Session, rcGuest, mData.mSession.mName.c_str()); return setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Closing guest session failed: %s"), GuestBase::getErrorAsString(ge).c_str()); } return setError(VBOX_E_IPRT_ERROR, tr("Closing guest session \"%s\" failed with %Rrc"), mData.mSession.mName.c_str(), vrc); } return S_OK; } HRESULT GuestSession::fileCopy(const com::Utf8Str &aSource, const com::Utf8Str &aDestination, const std::vector &aFlags, ComPtr &aProgress) { RT_NOREF(aSource, aDestination, aFlags, aProgress); ReturnComNotImplemented(); } HRESULT GuestSession::fileCopyFromGuest(const com::Utf8Str &aSource, const com::Utf8Str &aDestination, const std::vector &aFlags, ComPtr &aProgress) { uint32_t fFlags = FileCopyFlag_None; if (aFlags.size()) { for (size_t i = 0; i < aFlags.size(); i++) fFlags |= aFlags[i]; const uint32_t fValidFlags = FileCopyFlag_None | FileCopyFlag_NoReplace | FileCopyFlag_FollowLinks | FileCopyFlag_Update; if (fFlags & ~fValidFlags) return setError(E_INVALIDARG,tr("Unknown flags: flags value %#x, invalid: %#x"), fFlags, fFlags & ~fValidFlags); } GuestSessionFsSourceSet SourceSet; GuestSessionFsSourceSpec source; source.strSource = aSource; source.enmType = FsObjType_File; source.enmPathStyle = i_getGuestPathStyle(); source.fDryRun = false; /** @todo Implement support for a dry run. */ source.fDirCopyFlags = DirectoryCopyFlag_None; source.fFileCopyFlags = (FileCopyFlag_T)fFlags; SourceSet.push_back(source); return i_copyFromGuest(SourceSet, aDestination, aProgress); } HRESULT GuestSession::fileCopyToGuest(const com::Utf8Str &aSource, const com::Utf8Str &aDestination, const std::vector &aFlags, ComPtr &aProgress) { uint32_t fFlags = FileCopyFlag_None; if (aFlags.size()) { for (size_t i = 0; i < aFlags.size(); i++) fFlags |= aFlags[i]; const uint32_t fValidFlags = FileCopyFlag_None | FileCopyFlag_NoReplace | FileCopyFlag_FollowLinks | FileCopyFlag_Update; if (fFlags & ~fValidFlags) return setError(E_INVALIDARG,tr("Unknown flags: flags value %#x, invalid: %#x"), fFlags, fFlags & ~fValidFlags); } GuestSessionFsSourceSet SourceSet; GuestSessionFsSourceSpec source; source.strSource = aSource; source.enmType = FsObjType_File; source.enmPathStyle = GuestSession::i_getHostPathStyle(); source.fDryRun = false; /** @todo Implement support for a dry run. */ source.fDirCopyFlags = DirectoryCopyFlag_None; source.fFileCopyFlags = (FileCopyFlag_T)fFlags; SourceSet.push_back(source); return i_copyToGuest(SourceSet, aDestination, aProgress); } HRESULT GuestSession::copyFromGuest(const std::vector &aSources, const std::vector &aFilters, const std::vector &aFlags, const com::Utf8Str &aDestination, ComPtr &aProgress) { const size_t cSources = aSources.size(); if ( (aFilters.size() && aFilters.size() != cSources) || (aFlags.size() && aFlags.size() != cSources)) { return setError(E_INVALIDARG, tr("Parameter array sizes don't match to the number of sources specified")); } GuestSessionFsSourceSet SourceSet; std::vector::const_iterator itSource = aSources.begin(); std::vector::const_iterator itFilter = aFilters.begin(); std::vector::const_iterator itFlags = aFlags.begin(); const bool fContinueOnErrors = false; /** @todo Do we want a flag for that? */ const bool fFollowSymlinks = true; /** @todo Ditto. */ while (itSource != aSources.end()) { GuestFsObjData objData; int rcGuest = VERR_IPE_UNINITIALIZED_STATUS; int vrc = i_fsQueryInfo(*(itSource), fFollowSymlinks, objData, &rcGuest); if ( RT_FAILURE(vrc) && !fContinueOnErrors) { if (GuestProcess::i_isGuestError(vrc)) { GuestErrorInfo ge(GuestErrorInfo::Type_Process, rcGuest, (*itSource).c_str()); return setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Querying type for guest source failed: %s"), GuestBase::getErrorAsString(ge).c_str()); } return setError(E_FAIL, tr("Querying type for guest source \"%s\" failed: %Rrc"), (*itSource).c_str(), vrc); } Utf8Str strFlags; if (itFlags != aFlags.end()) { strFlags = *itFlags; ++itFlags; } Utf8Str strFilter; if (itFilter != aFilters.end()) { strFilter = *itFilter; ++itFilter; } GuestSessionFsSourceSpec source; source.strSource = *itSource; source.strFilter = strFilter; source.enmType = objData.mType; source.enmPathStyle = i_getGuestPathStyle(); source.fDryRun = false; /** @todo Implement support for a dry run. */ /* Check both flag groups here, as copying a directory also could mean to explicitly * *not* replacing any existing files (or just copy files which are newer, for instance). */ GuestSession::i_directoryCopyFlagFromStr(strFlags, false /* fStrict */, &source.fDirCopyFlags); GuestSession::i_fileCopyFlagFromStr(strFlags, false /* fStrict */, &source.fFileCopyFlags); SourceSet.push_back(source); ++itSource; } return i_copyFromGuest(SourceSet, aDestination, aProgress); } HRESULT GuestSession::copyToGuest(const std::vector &aSources, const std::vector &aFilters, const std::vector &aFlags, const com::Utf8Str &aDestination, ComPtr &aProgress) { const size_t cSources = aSources.size(); if ( (aFilters.size() && aFilters.size() != cSources) || (aFlags.size() && aFlags.size() != cSources)) { return setError(E_INVALIDARG, tr("Parameter array sizes don't match to the number of sources specified")); } GuestSessionFsSourceSet SourceSet; std::vector::const_iterator itSource = aSources.begin(); std::vector::const_iterator itFilter = aFilters.begin(); std::vector::const_iterator itFlags = aFlags.begin(); const bool fContinueOnErrors = false; /** @todo Do we want a flag for that? */ while (itSource != aSources.end()) { RTFSOBJINFO objInfo; int vrc = RTPathQueryInfo((*itSource).c_str(), &objInfo, RTFSOBJATTRADD_NOTHING); if ( RT_FAILURE(vrc) && !fContinueOnErrors) { return setError(E_FAIL, tr("Unable to query type for source '%s' (%Rrc)"), (*itSource).c_str(), vrc); } Utf8Str strFlags; if (itFlags != aFlags.end()) { strFlags = *itFlags; ++itFlags; } Utf8Str strFilter; if (itFilter != aFilters.end()) { strFilter = *itFilter; ++itFilter; } GuestSessionFsSourceSpec source; source.strSource = *itSource; source.strFilter = strFilter; source.enmType = GuestBase::fileModeToFsObjType(objInfo.Attr.fMode); source.enmPathStyle = GuestSession::i_getHostPathStyle(); source.fDryRun = false; /** @todo Implement support for a dry run. */ GuestSession::i_directoryCopyFlagFromStr(strFlags, false /* fStrict */, &source.fDirCopyFlags); GuestSession::i_fileCopyFlagFromStr(strFlags, false /* fStrict */, &source.fFileCopyFlags); SourceSet.push_back(source); ++itSource; } /* (Re-)Validate stuff. */ if (RT_UNLIKELY(SourceSet.size() == 0)) /* At least one source must be present. */ return setError(E_INVALIDARG, tr("No sources specified")); if (RT_UNLIKELY(SourceSet[0].strSource.isEmpty())) return setError(E_INVALIDARG, tr("First source entry is empty")); if (RT_UNLIKELY(aDestination.isEmpty())) return setError(E_INVALIDARG, tr("No destination specified")); return i_copyToGuest(SourceSet, aDestination, aProgress); } HRESULT GuestSession::directoryCopy(const com::Utf8Str &aSource, const com::Utf8Str &aDestination, const std::vector &aFlags, ComPtr &aProgress) { RT_NOREF(aSource, aDestination, aFlags, aProgress); ReturnComNotImplemented(); } HRESULT GuestSession::directoryCopyFromGuest(const com::Utf8Str &aSource, const com::Utf8Str &aDestination, const std::vector &aFlags, ComPtr &aProgress) { uint32_t fFlags = DirectoryCopyFlag_None; if (aFlags.size()) { for (size_t i = 0; i < aFlags.size(); i++) fFlags |= aFlags[i]; const uint32_t fValidFlags = DirectoryCopyFlag_None | DirectoryCopyFlag_CopyIntoExisting | DirectoryCopyFlag_Recursive | DirectoryCopyFlag_FollowLinks; if (fFlags & ~fValidFlags) return setError(E_INVALIDARG,tr("Unknown flags: flags value %#x, invalid: %#x"), fFlags, fFlags & ~fValidFlags); } GuestSessionFsSourceSet SourceSet; GuestSessionFsSourceSpec source; source.strSource = aSource; source.enmType = FsObjType_Directory; source.enmPathStyle = i_getGuestPathStyle(); source.fDryRun = false; /** @todo Implement support for a dry run. */ source.fDirCopyFlags = (DirectoryCopyFlag_T)fFlags; source.fFileCopyFlags = FileCopyFlag_None; /* Overwrite existing files. */ SourceSet.push_back(source); return i_copyFromGuest(SourceSet, aDestination, aProgress); } HRESULT GuestSession::directoryCopyToGuest(const com::Utf8Str &aSource, const com::Utf8Str &aDestination, const std::vector &aFlags, ComPtr &aProgress) { uint32_t fFlags = DirectoryCopyFlag_None; if (aFlags.size()) { for (size_t i = 0; i < aFlags.size(); i++) fFlags |= aFlags[i]; const uint32_t fValidFlags = DirectoryCopyFlag_None | DirectoryCopyFlag_CopyIntoExisting | DirectoryCopyFlag_Recursive | DirectoryCopyFlag_FollowLinks; if (fFlags & ~fValidFlags) return setError(E_INVALIDARG,tr("Unknown flags: flags value %#x, invalid: %#x"), fFlags, fFlags & ~fValidFlags); } GuestSessionFsSourceSet SourceSet; GuestSessionFsSourceSpec source; source.strSource = aSource; source.enmType = FsObjType_Directory; source.enmPathStyle = GuestSession::i_getHostPathStyle(); source.fDryRun = false; /** @todo Implement support for a dry run. */ source.fDirCopyFlags = (DirectoryCopyFlag_T)fFlags; source.fFileCopyFlags = FileCopyFlag_None; /* Overwrite existing files. */ SourceSet.push_back(source); return i_copyToGuest(SourceSet, aDestination, aProgress); } HRESULT GuestSession::directoryCreate(const com::Utf8Str &aPath, ULONG aMode, const std::vector &aFlags) { if (RT_UNLIKELY((aPath.c_str()) == NULL || *(aPath.c_str()) == '\0')) return setError(E_INVALIDARG, tr("No directory to create specified")); uint32_t fFlags = DirectoryCreateFlag_None; if (aFlags.size()) { for (size_t i = 0; i < aFlags.size(); i++) fFlags |= aFlags[i]; if (fFlags & ~DirectoryCreateFlag_Parents) return setError(E_INVALIDARG, tr("Unknown flags (%#x)"), fFlags); } HRESULT hrc = i_isStartedExternal(); if (FAILED(hrc)) return hrc; LogFlowThisFuncEnter(); ComObjPtr pDirectory; int rcGuest = VERR_IPE_UNINITIALIZED_STATUS; int vrc = i_directoryCreate(aPath, (uint32_t)aMode, fFlags, &rcGuest); if (RT_FAILURE(vrc)) { if (GuestProcess::i_isGuestError(vrc)) { GuestErrorInfo ge(GuestErrorInfo::Type_Directory, rcGuest, aPath.c_str()); return setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Guest directory creation failed: %s"), GuestBase::getErrorAsString(ge).c_str()); } switch (vrc) { case VERR_INVALID_PARAMETER: hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Guest directory creation failed: Invalid parameters given")); break; case VERR_BROKEN_PIPE: hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Guest directory creation failed: Unexpectedly aborted")); break; default: hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Guest directory creation failed: %Rrc"), vrc); break; } } return hrc; } HRESULT GuestSession::directoryCreateTemp(const com::Utf8Str &aTemplateName, ULONG aMode, const com::Utf8Str &aPath, BOOL aSecure, com::Utf8Str &aDirectory) { if (RT_UNLIKELY((aTemplateName.c_str()) == NULL || *(aTemplateName.c_str()) == '\0')) return setError(E_INVALIDARG, tr("No template specified")); if (RT_UNLIKELY((aPath.c_str()) == NULL || *(aPath.c_str()) == '\0')) return setError(E_INVALIDARG, tr("No directory name specified")); if (!aSecure) /* Ignore what mode is specified when a secure temp thing needs to be created. */ if (RT_UNLIKELY(aMode & ~07777)) return setError(E_INVALIDARG, tr("Mode invalid (must be specified in octal mode)")); HRESULT hrc = i_isStartedExternal(); if (FAILED(hrc)) return hrc; LogFlowThisFuncEnter(); int rcGuest = VERR_IPE_UNINITIALIZED_STATUS; int vrc = i_fsCreateTemp(aTemplateName, aPath, true /* Directory */, aDirectory, aMode, RT_BOOL(aSecure), &rcGuest); if (!RT_SUCCESS(vrc)) { switch (vrc) { case VERR_GSTCTL_GUEST_ERROR: { GuestErrorInfo ge(GuestErrorInfo::Type_ToolMkTemp, rcGuest, aPath.c_str()); hrc = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Temporary guest directory creation failed: %s"), GuestBase::getErrorAsString(ge).c_str()); break; } default: hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Temporary guest directory creation \"%s\" with template \"%s\" failed: %Rrc"), aPath.c_str(), aTemplateName.c_str(), vrc); break; } } return hrc; } HRESULT GuestSession::directoryExists(const com::Utf8Str &aPath, BOOL aFollowSymlinks, BOOL *aExists) { if (RT_UNLIKELY(aPath.isEmpty())) return setError(E_INVALIDARG, tr("Empty path")); HRESULT hrc = i_isStartedExternal(); if (FAILED(hrc)) return hrc; LogFlowThisFuncEnter(); GuestFsObjData objData; int rcGuest = VERR_IPE_UNINITIALIZED_STATUS; int vrc = i_directoryQueryInfo(aPath, aFollowSymlinks != FALSE, objData, &rcGuest); if (RT_SUCCESS(vrc)) *aExists = TRUE; else { switch (vrc) { case VERR_GSTCTL_GUEST_ERROR: { switch (rcGuest) { case VERR_PATH_NOT_FOUND: *aExists = FALSE; break; default: { GuestErrorInfo ge(GuestErrorInfo::Type_ToolStat, rcGuest, aPath.c_str()); hrc = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Querying directory existence failed: %s"), GuestBase::getErrorAsString(ge).c_str()); break; } } break; } case VERR_NOT_A_DIRECTORY: { *aExists = FALSE; break; } default: hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Querying directory existence \"%s\" failed: %Rrc"), aPath.c_str(), vrc); break; } } return hrc; } HRESULT GuestSession::directoryOpen(const com::Utf8Str &aPath, const com::Utf8Str &aFilter, const std::vector &aFlags, ComPtr &aDirectory) { if (RT_UNLIKELY((aPath.c_str()) == NULL || *(aPath.c_str()) == '\0')) return setError(E_INVALIDARG, tr("No directory to open specified")); if (RT_UNLIKELY((aFilter.c_str()) != NULL && *(aFilter.c_str()) != '\0')) return setError(E_INVALIDARG, tr("Directory filters are not implemented yet")); uint32_t fFlags = DirectoryOpenFlag_None; if (aFlags.size()) { for (size_t i = 0; i < aFlags.size(); i++) fFlags |= aFlags[i]; if (fFlags) return setError(E_INVALIDARG, tr("Open flags (%#x) not implemented yet"), fFlags); } HRESULT hrc = i_isStartedExternal(); if (FAILED(hrc)) return hrc; LogFlowThisFuncEnter(); GuestDirectoryOpenInfo openInfo; openInfo.mPath = aPath; openInfo.mFilter = aFilter; openInfo.mFlags = fFlags; ComObjPtr pDirectory; int rcGuest = VERR_IPE_UNINITIALIZED_STATUS; int vrc = i_directoryOpen(openInfo, pDirectory, &rcGuest); if (RT_SUCCESS(vrc)) { /* Return directory object to the caller. */ hrc = pDirectory.queryInterfaceTo(aDirectory.asOutParam()); } else { switch (vrc) { case VERR_INVALID_PARAMETER: hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Opening guest directory \"%s\" failed; invalid parameters given"), aPath.c_str()); break; case VERR_GSTCTL_GUEST_ERROR: { GuestErrorInfo ge(GuestErrorInfo::Type_Directory, rcGuest, aPath.c_str()); hrc = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Opening guest directory failed: %s"), GuestBase::getErrorAsString(ge).c_str()); break; } default: hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Opening guest directory \"%s\" failed: %Rrc"), aPath.c_str(), vrc); break; } } return hrc; } HRESULT GuestSession::directoryRemove(const com::Utf8Str &aPath) { if (RT_UNLIKELY(aPath.c_str() == NULL || *aPath.c_str() == '\0')) return setError(E_INVALIDARG, tr("No directory to remove specified")); HRESULT hrc = i_isStartedExternal(); if (FAILED(hrc)) return hrc; LogFlowThisFuncEnter(); /* No flags; only remove the directory when empty. */ uint32_t fFlags = DIRREMOVEREC_FLAG_NONE; int rcGuest = VERR_IPE_UNINITIALIZED_STATUS; int vrc = i_directoryRemove(aPath, fFlags, &rcGuest); if (RT_FAILURE(vrc)) { switch (vrc) { case VERR_NOT_SUPPORTED: hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Handling removing guest directories not supported by installed Guest Additions")); break; case VERR_GSTCTL_GUEST_ERROR: { GuestErrorInfo ge(GuestErrorInfo::Type_Directory, rcGuest, aPath.c_str()); hrc = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Removing guest directory failed: %s"), GuestBase::getErrorAsString(ge).c_str()); break; } default: hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Removing guest directory \"%s\" failed: %Rrc"), aPath.c_str(), vrc); break; } } return hrc; } HRESULT GuestSession::directoryRemoveRecursive(const com::Utf8Str &aPath, const std::vector &aFlags, ComPtr &aProgress) { if (RT_UNLIKELY(aPath.c_str() == NULL || *aPath.c_str() == '\0')) return setError(E_INVALIDARG, tr("No directory to remove recursively specified")); /* By defautl remove recursively as the function name implies. */ uint32_t fFlags = DIRREMOVEREC_FLAG_RECURSIVE; if (aFlags.size()) { for (size_t i = 0; i < aFlags.size(); i++) { switch (aFlags[i]) { case DirectoryRemoveRecFlag_None: /* Skip. */ continue; case DirectoryRemoveRecFlag_ContentAndDir: fFlags |= DIRREMOVEREC_FLAG_CONTENT_AND_DIR; break; case DirectoryRemoveRecFlag_ContentOnly: fFlags |= DIRREMOVEREC_FLAG_CONTENT_ONLY; break; default: return setError(E_INVALIDARG, tr("Invalid flags specified")); } } } HRESULT hrc = i_isStartedExternal(); if (FAILED(hrc)) return hrc; LogFlowThisFuncEnter(); ComObjPtr pProgress; hrc = pProgress.createObject(); if (SUCCEEDED(hrc)) hrc = pProgress->init(static_cast(this), Bstr(tr("Removing guest directory")).raw(), TRUE /*aCancelable*/); if (FAILED(hrc)) return hrc; /* Note: At the moment we don't supply progress information while * deleting a guest directory recursively. So just complete * the progress object right now. */ /** @todo Implement progress reporting on guest directory deletion! */ hrc = pProgress->i_notifyComplete(S_OK); if (FAILED(hrc)) return hrc; int rcGuest = VERR_IPE_UNINITIALIZED_STATUS; int vrc = i_directoryRemove(aPath, fFlags, &rcGuest); if (RT_FAILURE(vrc)) { switch (vrc) { case VERR_NOT_SUPPORTED: hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Handling removing guest directories recursively not supported by installed Guest Additions")); break; case VERR_GSTCTL_GUEST_ERROR: { GuestErrorInfo ge(GuestErrorInfo::Type_Directory, rcGuest, aPath.c_str()); hrc = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Recursively removing guest directory failed: %s"), GuestBase::getErrorAsString(ge).c_str()); break; } default: hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Recursively removing guest directory \"%s\" failed: %Rrc"), aPath.c_str(), vrc); break; } } else { pProgress.queryInterfaceTo(aProgress.asOutParam()); } return hrc; } HRESULT GuestSession::environmentScheduleSet(const com::Utf8Str &aName, const com::Utf8Str &aValue) { LogFlowThisFuncEnter(); int vrc; { AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); vrc = mData.mEnvironmentChanges.setVariable(aName, aValue); } HRESULT hrc; if (RT_SUCCESS(vrc)) hrc = S_OK; else if (vrc == VERR_ENV_INVALID_VAR_NAME) hrc = setError(E_INVALIDARG, tr("Invalid environment variable name '%s'"), aName.c_str()); else hrc = setErrorVrc(vrc, tr("Failed to schedule setting environment variable '%s' to '%s'"), aName.c_str(), aValue.c_str()); LogFlowThisFuncLeave(); return hrc; } HRESULT GuestSession::environmentScheduleUnset(const com::Utf8Str &aName) { LogFlowThisFuncEnter(); int vrc; { AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); vrc = mData.mEnvironmentChanges.unsetVariable(aName); } HRESULT hrc; if (RT_SUCCESS(vrc)) hrc = S_OK; else if (vrc == VERR_ENV_INVALID_VAR_NAME) hrc = setError(E_INVALIDARG, tr("Invalid environment variable name '%s'"), aName.c_str()); else hrc = setErrorVrc(vrc, tr("Failed to schedule unsetting environment variable '%s'"), aName.c_str()); LogFlowThisFuncLeave(); return hrc; } HRESULT GuestSession::environmentGetBaseVariable(const com::Utf8Str &aName, com::Utf8Str &aValue) { LogFlowThisFuncEnter(); AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); HRESULT hrc; if (mData.mpBaseEnvironment) { int vrc = mData.mpBaseEnvironment->getVariable(aName, &aValue); if (RT_SUCCESS(vrc)) hrc = S_OK; else if (vrc == VERR_ENV_INVALID_VAR_NAME) hrc = setError(E_INVALIDARG, tr("Invalid environment variable name '%s'"), aName.c_str()); else hrc = setErrorVrc(vrc); } else if (mData.mProtocolVersion < 99999) hrc = setError(VBOX_E_NOT_SUPPORTED, tr("The base environment feature is not supported by the Guest Additions")); else hrc = setError(VBOX_E_INVALID_OBJECT_STATE, tr("The base environment has not yet been reported by the guest")); LogFlowThisFuncLeave(); return hrc; } HRESULT GuestSession::environmentDoesBaseVariableExist(const com::Utf8Str &aName, BOOL *aExists) { LogFlowThisFuncEnter(); *aExists = FALSE; AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); HRESULT hrc; if (mData.mpBaseEnvironment) { hrc = S_OK; *aExists = mData.mpBaseEnvironment->doesVariableExist(aName); } else if (mData.mProtocolVersion < 99999) hrc = setError(VBOX_E_NOT_SUPPORTED, tr("The base environment feature is not supported by the Guest Additions")); else hrc = setError(VBOX_E_INVALID_OBJECT_STATE, tr("The base environment has not yet been reported by the guest")); LogFlowThisFuncLeave(); return hrc; } HRESULT GuestSession::fileCreateTemp(const com::Utf8Str &aTemplateName, ULONG aMode, const com::Utf8Str &aPath, BOOL aSecure, ComPtr &aFile) { RT_NOREF(aTemplateName, aMode, aPath, aSecure, aFile); ReturnComNotImplemented(); } HRESULT GuestSession::fileExists(const com::Utf8Str &aPath, BOOL aFollowSymlinks, BOOL *aExists) { /* By default we return non-existent. */ *aExists = FALSE; if (RT_UNLIKELY((aPath.c_str()) == NULL || *(aPath.c_str()) == '\0')) return S_OK; HRESULT hrc = i_isStartedExternal(); if (FAILED(hrc)) return hrc; LogFlowThisFuncEnter(); GuestFsObjData objData; int rcGuest = VERR_IPE_UNINITIALIZED_STATUS; int vrc = i_fileQueryInfo(aPath, RT_BOOL(aFollowSymlinks), objData, &rcGuest); if (RT_SUCCESS(vrc)) { *aExists = TRUE; return S_OK; } switch (vrc) { case VERR_GSTCTL_GUEST_ERROR: { switch (rcGuest) { case VERR_PATH_NOT_FOUND: RT_FALL_THROUGH(); case VERR_FILE_NOT_FOUND: break; default: { GuestErrorInfo ge(GuestErrorInfo::Type_ToolStat, rcGuest, aPath.c_str()); hrc = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Querying guest file existence failed: %s"), GuestBase::getErrorAsString(ge).c_str()); break; } } break; } case VERR_NOT_A_FILE: break; default: hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Querying guest file information for \"%s\" failed: %Rrc"), aPath.c_str(), vrc); break; } return hrc; } HRESULT GuestSession::fileOpen(const com::Utf8Str &aPath, FileAccessMode_T aAccessMode, FileOpenAction_T aOpenAction, ULONG aCreationMode, ComPtr &aFile) { LogFlowThisFuncEnter(); const std::vector EmptyFlags; return fileOpenEx(aPath, aAccessMode, aOpenAction, FileSharingMode_All, aCreationMode, EmptyFlags, aFile); } HRESULT GuestSession::fileOpenEx(const com::Utf8Str &aPath, FileAccessMode_T aAccessMode, FileOpenAction_T aOpenAction, FileSharingMode_T aSharingMode, ULONG aCreationMode, const std::vector &aFlags, ComPtr &aFile) { if (RT_UNLIKELY((aPath.c_str()) == NULL || *(aPath.c_str()) == '\0')) return setError(E_INVALIDARG, tr("No file to open specified")); HRESULT hrc = i_isStartedExternal(); if (FAILED(hrc)) return hrc; LogFlowThisFuncEnter(); /* Validate aAccessMode. */ switch (aAccessMode) { case FileAccessMode_ReadOnly: RT_FALL_THRU(); case FileAccessMode_WriteOnly: RT_FALL_THRU(); case FileAccessMode_ReadWrite: break; case FileAccessMode_AppendOnly: RT_FALL_THRU(); case FileAccessMode_AppendRead: return setError(E_NOTIMPL, tr("Append access modes are not yet implemented")); default: return setError(E_INVALIDARG, tr("Unknown FileAccessMode value %u (%#x)"), aAccessMode, aAccessMode); } /* Validate aOpenAction to the old format. */ switch (aOpenAction) { case FileOpenAction_OpenExisting: RT_FALL_THRU(); case FileOpenAction_OpenOrCreate: RT_FALL_THRU(); case FileOpenAction_CreateNew: RT_FALL_THRU(); case FileOpenAction_CreateOrReplace: RT_FALL_THRU(); case FileOpenAction_OpenExistingTruncated: RT_FALL_THRU(); case FileOpenAction_AppendOrCreate: break; default: return setError(E_INVALIDARG, tr("Unknown FileOpenAction value %u (%#x)"), aAccessMode, aAccessMode); } /* Validate aSharingMode. */ switch (aSharingMode) { case FileSharingMode_All: break; case FileSharingMode_Read: case FileSharingMode_Write: case FileSharingMode_ReadWrite: case FileSharingMode_Delete: case FileSharingMode_ReadDelete: case FileSharingMode_WriteDelete: return setError(E_NOTIMPL, tr("Only FileSharingMode_All is currently implemented")); default: return setError(E_INVALIDARG, tr("Unknown FileOpenAction value %u (%#x)"), aAccessMode, aAccessMode); } /* Combine and validate flags. */ uint32_t fOpenEx = 0; for (size_t i = 0; i < aFlags.size(); i++) fOpenEx |= aFlags[i]; if (fOpenEx) return setError(E_INVALIDARG, tr("Unsupported FileOpenExFlag value(s) in aFlags (%#x)"), fOpenEx); ComObjPtr pFile; int rcGuest = VERR_IPE_UNINITIALIZED_STATUS; int vrc = i_fileOpenEx(aPath, aAccessMode, aOpenAction, aSharingMode, aCreationMode, aFlags, pFile, &rcGuest); if (RT_SUCCESS(vrc)) /* Return directory object to the caller. */ hrc = pFile.queryInterfaceTo(aFile.asOutParam()); else { switch (vrc) { case VERR_NOT_SUPPORTED: hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Handling guest files not supported by installed Guest Additions")); break; case VERR_GSTCTL_GUEST_ERROR: { GuestErrorInfo ge(GuestErrorInfo::Type_File, rcGuest, aPath.c_str()); hrc = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Opening guest file failed: %s"), GuestBase::getErrorAsString(ge).c_str()); break; } default: hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Opening guest file \"%s\" failed: %Rrc"), aPath.c_str(), vrc); break; } } return hrc; } HRESULT GuestSession::fileQuerySize(const com::Utf8Str &aPath, BOOL aFollowSymlinks, LONG64 *aSize) { if (aPath.isEmpty()) return setError(E_INVALIDARG, tr("No path specified")); HRESULT hrc = i_isStartedExternal(); if (FAILED(hrc)) return hrc; int64_t llSize; int rcGuest = VERR_IPE_UNINITIALIZED_STATUS; int vrc = i_fileQuerySize(aPath, aFollowSymlinks != FALSE, &llSize, &rcGuest); if (RT_SUCCESS(vrc)) { *aSize = llSize; } else { if (GuestProcess::i_isGuestError(vrc)) { GuestErrorInfo ge(GuestErrorInfo::Type_ToolStat, rcGuest, aPath.c_str()); hrc = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Querying guest file size failed: %s"), GuestBase::getErrorAsString(ge).c_str()); } else hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Querying guest file size of \"%s\" failed: %Rrc"), vrc, aPath.c_str()); } return hrc; } HRESULT GuestSession::fsQueryFreeSpace(const com::Utf8Str &aPath, LONG64 *aFreeSpace) { RT_NOREF(aPath, aFreeSpace); return E_NOTIMPL; } HRESULT GuestSession::fsQueryInfo(const com::Utf8Str &aPath, ComPtr &aInfo) { RT_NOREF(aPath, aInfo); return E_NOTIMPL; } HRESULT GuestSession::fsObjExists(const com::Utf8Str &aPath, BOOL aFollowSymlinks, BOOL *aExists) { if (aPath.isEmpty()) return setError(E_INVALIDARG, tr("No path specified")); HRESULT hrc = i_isStartedExternal(); if (FAILED(hrc)) return hrc; LogFlowThisFunc(("aPath=%s, aFollowSymlinks=%RTbool\n", aPath.c_str(), RT_BOOL(aFollowSymlinks))); *aExists = false; GuestFsObjData objData; int rcGuest = VERR_IPE_UNINITIALIZED_STATUS; int vrc = i_fsQueryInfo(aPath, aFollowSymlinks != FALSE, objData, &rcGuest); if (RT_SUCCESS(vrc)) { *aExists = TRUE; } else { if (GuestProcess::i_isGuestError(vrc)) { if ( rcGuest == VERR_NOT_A_FILE || rcGuest == VERR_PATH_NOT_FOUND || rcGuest == VERR_FILE_NOT_FOUND || rcGuest == VERR_INVALID_NAME) { hrc = S_OK; /* Ignore these vrc values. */ } else { GuestErrorInfo ge(GuestErrorInfo::Type_ToolStat, rcGuest, aPath.c_str()); hrc = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Querying guest file existence information failed: %s"), GuestBase::getErrorAsString(ge).c_str()); } } else hrc = setErrorVrc(vrc, tr("Querying guest file existence information for \"%s\" failed: %Rrc"), aPath.c_str(), vrc); } return hrc; } HRESULT GuestSession::fsObjQueryInfo(const com::Utf8Str &aPath, BOOL aFollowSymlinks, ComPtr &aInfo) { if (aPath.isEmpty()) return setError(E_INVALIDARG, tr("No path specified")); HRESULT hrc = i_isStartedExternal(); if (FAILED(hrc)) return hrc; LogFlowThisFunc(("aPath=%s, aFollowSymlinks=%RTbool\n", aPath.c_str(), RT_BOOL(aFollowSymlinks))); GuestFsObjData Info; int rcGuest = VERR_IPE_UNINITIALIZED_STATUS; int vrc = i_fsQueryInfo(aPath, aFollowSymlinks != FALSE, Info, &rcGuest); if (RT_SUCCESS(vrc)) { ComObjPtr ptrFsObjInfo; hrc = ptrFsObjInfo.createObject(); if (SUCCEEDED(hrc)) { vrc = ptrFsObjInfo->init(Info); if (RT_SUCCESS(vrc)) hrc = ptrFsObjInfo.queryInterfaceTo(aInfo.asOutParam()); else hrc = setErrorVrc(vrc); } } else { if (GuestProcess::i_isGuestError(vrc)) { GuestErrorInfo ge(GuestErrorInfo::Type_ToolStat, rcGuest, aPath.c_str()); hrc = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Querying guest file information failed: %s"), GuestBase::getErrorAsString(ge).c_str()); } else hrc = setErrorVrc(vrc, tr("Querying guest file information for \"%s\" failed: %Rrc"), aPath.c_str(), vrc); } return hrc; } HRESULT GuestSession::fsObjRemove(const com::Utf8Str &aPath) { if (RT_UNLIKELY(aPath.isEmpty())) return setError(E_INVALIDARG, tr("No path specified")); HRESULT hrc = i_isStartedExternal(); if (FAILED(hrc)) return hrc; LogFlowThisFunc(("aPath=%s\n", aPath.c_str())); int rcGuest = VERR_IPE_UNINITIALIZED_STATUS; int vrc = i_fileRemove(aPath, &rcGuest); if (RT_FAILURE(vrc)) { if (GuestProcess::i_isGuestError(vrc)) { GuestErrorInfo ge(GuestErrorInfo::Type_ToolRm, rcGuest, aPath.c_str()); hrc = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Removing guest file failed: %s"), GuestBase::getErrorAsString(ge).c_str()); } else hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Removing guest file \"%s\" failed: %Rrc"), aPath.c_str(), vrc); } return hrc; } HRESULT GuestSession::fsObjRemoveArray(const std::vector &aPaths, ComPtr &aProgress) { RT_NOREF(aPaths, aProgress); return E_NOTIMPL; } HRESULT GuestSession::fsObjRename(const com::Utf8Str &aSource, const com::Utf8Str &aDestination, const std::vector &aFlags) { if (RT_UNLIKELY(aSource.isEmpty())) return setError(E_INVALIDARG, tr("No source path specified")); if (RT_UNLIKELY(aDestination.isEmpty())) return setError(E_INVALIDARG, tr("No destination path specified")); HRESULT hrc = i_isStartedExternal(); if (FAILED(hrc)) return hrc; /* Combine, validate and convert flags. */ uint32_t fApiFlags = 0; for (size_t i = 0; i < aFlags.size(); i++) fApiFlags |= aFlags[i]; if (fApiFlags & ~((uint32_t)FsObjRenameFlag_NoReplace | (uint32_t)FsObjRenameFlag_Replace)) return setError(E_INVALIDARG, tr("Unknown rename flag: %#x"), fApiFlags); LogFlowThisFunc(("aSource=%s, aDestination=%s\n", aSource.c_str(), aDestination.c_str())); AssertCompile(FsObjRenameFlag_NoReplace == 0); AssertCompile(FsObjRenameFlag_Replace != 0); uint32_t fBackend; if ((fApiFlags & ((uint32_t)FsObjRenameFlag_NoReplace | (uint32_t)FsObjRenameFlag_Replace)) == FsObjRenameFlag_Replace) fBackend = PATHRENAME_FLAG_REPLACE; else fBackend = PATHRENAME_FLAG_NO_REPLACE; /* Call worker to do the job. */ int rcGuest = VERR_IPE_UNINITIALIZED_STATUS; int vrc = i_pathRename(aSource, aDestination, fBackend, &rcGuest); if (RT_FAILURE(vrc)) { switch (vrc) { case VERR_NOT_SUPPORTED: hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Handling renaming guest paths not supported by installed Guest Additions")); break; case VERR_GSTCTL_GUEST_ERROR: { GuestErrorInfo ge(GuestErrorInfo::Type_Process, rcGuest, aSource.c_str()); hrc = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Renaming guest path failed: %s"), GuestBase::getErrorAsString(ge).c_str()); break; } default: hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Renaming guest path \"%s\" failed: %Rrc"), aSource.c_str(), vrc); break; } } return hrc; } HRESULT GuestSession::fsObjMove(const com::Utf8Str &aSource, const com::Utf8Str &aDestination, const std::vector &aFlags, ComPtr &aProgress) { RT_NOREF(aSource, aDestination, aFlags, aProgress); ReturnComNotImplemented(); } HRESULT GuestSession::fsObjMoveArray(const std::vector &aSource, const com::Utf8Str &aDestination, const std::vector &aFlags, ComPtr &aProgress) { RT_NOREF(aSource, aDestination, aFlags, aProgress); ReturnComNotImplemented(); } HRESULT GuestSession::fsObjCopyArray(const std::vector &aSource, const com::Utf8Str &aDestination, const std::vector &aFlags, ComPtr &aProgress) { RT_NOREF(aSource, aDestination, aFlags, aProgress); ReturnComNotImplemented(); } HRESULT GuestSession::fsObjSetACL(const com::Utf8Str &aPath, BOOL aFollowSymlinks, const com::Utf8Str &aAcl, ULONG aMode) { RT_NOREF(aPath, aFollowSymlinks, aAcl, aMode); ReturnComNotImplemented(); } HRESULT GuestSession::processCreate(const com::Utf8Str &aExecutable, const std::vector &aArguments, const std::vector &aEnvironment, const std::vector &aFlags, ULONG aTimeoutMS, ComPtr &aGuestProcess) { LogFlowThisFuncEnter(); std::vector affinityIgnored; return processCreateEx(aExecutable, aArguments, aEnvironment, aFlags, aTimeoutMS, ProcessPriority_Default, affinityIgnored, aGuestProcess); } HRESULT GuestSession::processCreateEx(const com::Utf8Str &aExecutable, const std::vector &aArguments, const std::vector &aEnvironment, const std::vector &aFlags, ULONG aTimeoutMS, ProcessPriority_T aPriority, const std::vector &aAffinity, ComPtr &aGuestProcess) { HRESULT hr = i_isStartedExternal(); if (FAILED(hr)) return hr; /* * Must have an executable to execute. If none is given, we try use the * zero'th argument. */ const char *pszExecutable = aExecutable.c_str(); if (RT_UNLIKELY(pszExecutable == NULL || *pszExecutable == '\0')) { if (aArguments.size() > 0) pszExecutable = aArguments[0].c_str(); if (pszExecutable == NULL || *pszExecutable == '\0') return setError(E_INVALIDARG, tr("No command to execute specified")); } /* The rest of the input is being validated in i_processCreateEx(). */ LogFlowThisFuncEnter(); /* * Build the process startup info. */ GuestProcessStartupInfo procInfo; /* Executable and arguments. */ procInfo.mExecutable = pszExecutable; if (aArguments.size()) { for (size_t i = 0; i < aArguments.size(); i++) procInfo.mArguments.push_back(aArguments[i]); } else /* If no arguments were given, add the executable as argv[0] by default. */ procInfo.mArguments.push_back(procInfo.mExecutable); /* Combine the environment changes associated with the ones passed in by the caller, giving priority to the latter. The changes are putenv style and will be applied to the standard environment for the guest user. */ int vrc = procInfo.mEnvironmentChanges.copy(mData.mEnvironmentChanges); if (RT_SUCCESS(vrc)) { size_t idxError = ~(size_t)0; vrc = procInfo.mEnvironmentChanges.applyPutEnvArray(aEnvironment, &idxError); if (RT_SUCCESS(vrc)) { /* Convert the flag array into a mask. */ if (aFlags.size()) for (size_t i = 0; i < aFlags.size(); i++) procInfo.mFlags |= aFlags[i]; procInfo.mTimeoutMS = aTimeoutMS; /** @todo use RTCPUSET instead of archaic 64-bit variables! */ if (aAffinity.size()) for (size_t i = 0; i < aAffinity.size(); i++) if (aAffinity[i]) procInfo.mAffinity |= (uint64_t)1 << i; procInfo.mPriority = aPriority; /* * Create a guest process object. */ ComObjPtr pProcess; vrc = i_processCreateEx(procInfo, pProcess); if (RT_SUCCESS(vrc)) { ComPtr pIProcess; hr = pProcess.queryInterfaceTo(pIProcess.asOutParam()); if (SUCCEEDED(hr)) { /* * Start the process. */ vrc = pProcess->i_startProcessAsync(); if (RT_SUCCESS(vrc)) { aGuestProcess = pIProcess; LogFlowFuncLeaveRC(vrc); return S_OK; } hr = setErrorVrc(vrc, tr("Failed to start guest process: %Rrc"), vrc); } } else if (vrc == VERR_GSTCTL_MAX_CID_OBJECTS_REACHED) hr = setErrorVrc(vrc, tr("Maximum number of concurrent guest processes per session (%u) reached"), VBOX_GUESTCTRL_MAX_OBJECTS); else hr = setErrorVrc(vrc, tr("Failed to create guest process object: %Rrc"), vrc); } else hr = setErrorBoth(vrc == VERR_ENV_INVALID_VAR_NAME ? E_INVALIDARG : Global::vboxStatusCodeToCOM(vrc), vrc, tr("Failed to apply environment variable '%s', index %u (%Rrc)'"), aEnvironment[idxError].c_str(), idxError, vrc); } else hr = setErrorVrc(vrc, tr("Failed to set up the environment: %Rrc"), vrc); LogFlowFuncLeaveRC(vrc); return hr; } HRESULT GuestSession::processGet(ULONG aPid, ComPtr &aGuestProcess) { if (aPid == 0) return setError(E_INVALIDARG, tr("No valid process ID (PID) specified")); LogFlowThisFunc(("PID=%RU32\n", aPid)); AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); HRESULT hr = S_OK; ComObjPtr pProcess; int rc = i_processGetByPID(aPid, &pProcess); if (RT_FAILURE(rc)) hr = setError(E_INVALIDARG, tr("No process with PID %RU32 found"), aPid); /* This will set (*aProcess) to NULL if pProgress is NULL. */ HRESULT hr2 = pProcess.queryInterfaceTo(aGuestProcess.asOutParam()); if (SUCCEEDED(hr)) hr = hr2; LogFlowThisFunc(("aProcess=%p, hr=%Rhrc\n", (IGuestProcess*)aGuestProcess, hr)); return hr; } HRESULT GuestSession::symlinkCreate(const com::Utf8Str &aSource, const com::Utf8Str &aTarget, SymlinkType_T aType) { RT_NOREF(aSource, aTarget, aType); ReturnComNotImplemented(); } HRESULT GuestSession::symlinkExists(const com::Utf8Str &aSymlink, BOOL *aExists) { RT_NOREF(aSymlink, aExists); ReturnComNotImplemented(); } HRESULT GuestSession::symlinkRead(const com::Utf8Str &aSymlink, const std::vector &aFlags, com::Utf8Str &aTarget) { RT_NOREF(aSymlink, aFlags, aTarget); ReturnComNotImplemented(); } HRESULT GuestSession::waitFor(ULONG aWaitFor, ULONG aTimeoutMS, GuestSessionWaitResult_T *aReason) { /* Note: No call to i_isStartedExternal() needed here, as the session might not has been started (yet). */ LogFlowThisFuncEnter(); HRESULT hrc = S_OK; /* * Note: Do not hold any locks here while waiting! */ int rcGuest = VERR_IPE_UNINITIALIZED_STATUS; GuestSessionWaitResult_T waitResult; int vrc = i_waitFor(aWaitFor, aTimeoutMS, waitResult, &rcGuest); if (RT_SUCCESS(vrc)) *aReason = waitResult; else { switch (vrc) { case VERR_GSTCTL_GUEST_ERROR: { GuestErrorInfo ge(GuestErrorInfo::Type_Session, rcGuest, mData.mSession.mName.c_str()); hrc = setErrorBoth(VBOX_E_IPRT_ERROR, rcGuest, tr("Waiting for guest process failed: %s"), GuestBase::getErrorAsString(ge).c_str()); break; } case VERR_TIMEOUT: *aReason = GuestSessionWaitResult_Timeout; break; default: { const char *pszSessionName = mData.mSession.mName.c_str(); hrc = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Waiting for guest session \"%s\" failed: %Rrc"), pszSessionName ? pszSessionName : tr("Unnamed"), vrc); break; } } } LogFlowFuncLeaveRC(vrc); return hrc; } HRESULT GuestSession::waitForArray(const std::vector &aWaitFor, ULONG aTimeoutMS, GuestSessionWaitResult_T *aReason) { /* Note: No call to i_isStartedExternal() needed here, as the session might not has been started (yet). */ LogFlowThisFuncEnter(); /* * Note: Do not hold any locks here while waiting! */ uint32_t fWaitFor = GuestSessionWaitForFlag_None; for (size_t i = 0; i < aWaitFor.size(); i++) fWaitFor |= aWaitFor[i]; return WaitFor(fWaitFor, aTimeoutMS, aReason); }