diff options
Diffstat (limited to 'src/VBox/Main/src-client/GuestSessionImplTasks.cpp')
-rw-r--r-- | src/VBox/Main/src-client/GuestSessionImplTasks.cpp | 3307 |
1 files changed, 3307 insertions, 0 deletions
diff --git a/src/VBox/Main/src-client/GuestSessionImplTasks.cpp b/src/VBox/Main/src-client/GuestSessionImplTasks.cpp new file mode 100644 index 00000000..d3112ff0 --- /dev/null +++ b/src/VBox/Main/src-client/GuestSessionImplTasks.cpp @@ -0,0 +1,3307 @@ +/* $Id: GuestSessionImplTasks.cpp $ */ +/** @file + * VirtualBox Main - Guest session tasks. + */ + +/* + * 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 <https://www.gnu.org/licenses>. + * + * 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 "Global.h" +#include "AutoCaller.h" +#include "ConsoleImpl.h" +#include "ProgressImpl.h" + +#include <memory> /* For auto_ptr. */ + +#include <iprt/env.h> +#include <iprt/file.h> /* For CopyTo/From. */ +#include <iprt/dir.h> +#include <iprt/path.h> +#include <iprt/fsvfs.h> + + +/********************************************************************************************************************************* +* Defines * +*********************************************************************************************************************************/ + +/** + * (Guest Additions) ISO file flags. + * Needed for handling Guest Additions updates. + */ +#define ISOFILE_FLAG_NONE 0 +/** Copy over the file from host to the + * guest. */ +#define ISOFILE_FLAG_COPY_FROM_ISO RT_BIT(0) +/** Execute file on the guest after it has + * been successfully transferred. */ +#define ISOFILE_FLAG_EXECUTE RT_BIT(7) +/** File is optional, does not have to be + * existent on the .ISO. */ +#define ISOFILE_FLAG_OPTIONAL RT_BIT(8) + + +// session task classes +///////////////////////////////////////////////////////////////////////////// + +GuestSessionTask::GuestSessionTask(GuestSession *pSession) + : ThreadTask("GenericGuestSessionTask") +{ + mSession = pSession; + + switch (mSession->i_getGuestPathStyle()) + { + case PathStyle_DOS: + mstrGuestPathStyle = "\\"; + break; + + default: + mstrGuestPathStyle = "/"; + break; + } +} + +GuestSessionTask::~GuestSessionTask(void) +{ +} + +/** + * Creates (and initializes / sets) the progress objects of a guest session task. + * + * @returns VBox status code. + * @param cOperations Number of operation the task wants to perform. + */ +int GuestSessionTask::createAndSetProgressObject(ULONG cOperations /* = 1 */) +{ + LogFlowThisFunc(("cOperations=%ld\n", cOperations)); + + /* Create the progress object. */ + ComObjPtr<Progress> pProgress; + HRESULT hr = pProgress.createObject(); + if (FAILED(hr)) + return VERR_COM_UNEXPECTED; + + hr = pProgress->init(static_cast<IGuestSession*>(mSession), + Bstr(mDesc).raw(), + TRUE /* aCancelable */, cOperations, Bstr(mDesc).raw()); + if (FAILED(hr)) + return VERR_COM_UNEXPECTED; + + mProgress = pProgress; + + LogFlowFuncLeave(); + return VINF_SUCCESS; +} + +#if 0 /* unused */ +/** @note The task object is owned by the thread after this returns, regardless of the result. */ +int GuestSessionTask::RunAsync(const Utf8Str &strDesc, ComObjPtr<Progress> &pProgress) +{ + LogFlowThisFunc(("strDesc=%s\n", strDesc.c_str())); + + mDesc = strDesc; + mProgress = pProgress; + HRESULT hrc = createThreadWithType(RTTHREADTYPE_MAIN_HEAVY_WORKER); + + LogFlowThisFunc(("Returning hrc=%Rhrc\n", hrc)); + return Global::vboxStatusCodeToCOM(hrc); +} +#endif + +/** + * Gets a guest property from the VM. + * + * @returns VBox status code. + * @param pGuest Guest object of VM to get guest property from. + * @param strPath Guest property to path to get. + * @param strValue Where to store the guest property value on success. + */ +int GuestSessionTask::getGuestProperty(const ComObjPtr<Guest> &pGuest, + const Utf8Str &strPath, Utf8Str &strValue) +{ + ComObjPtr<Console> pConsole = pGuest->i_getConsole(); + const ComPtr<IMachine> pMachine = pConsole->i_machine(); + + Assert(!pMachine.isNull()); + Bstr strTemp, strFlags; + LONG64 i64Timestamp; + HRESULT hr = pMachine->GetGuestProperty(Bstr(strPath).raw(), + strTemp.asOutParam(), + &i64Timestamp, strFlags.asOutParam()); + if (SUCCEEDED(hr)) + { + strValue = strTemp; + return VINF_SUCCESS; + } + return VERR_NOT_FOUND; +} + +/** + * Sets the percentage of a guest session task progress. + * + * @returns VBox status code. + * @param uPercent Percentage (0-100) to set. + */ +int GuestSessionTask::setProgress(ULONG uPercent) +{ + if (mProgress.isNull()) /* Progress is optional. */ + return VINF_SUCCESS; + + BOOL fCanceled; + if ( SUCCEEDED(mProgress->COMGETTER(Canceled(&fCanceled))) + && fCanceled) + return VERR_CANCELLED; + BOOL fCompleted; + if ( SUCCEEDED(mProgress->COMGETTER(Completed(&fCompleted))) + && fCompleted) + { + AssertMsgFailed(("Setting value of an already completed progress\n")); + return VINF_SUCCESS; + } + HRESULT hr = mProgress->SetCurrentOperationProgress(uPercent); + if (FAILED(hr)) + return VERR_COM_UNEXPECTED; + + return VINF_SUCCESS; +} + +/** + * Sets the task's progress object to succeeded. + * + * @returns VBox status code. + */ +int GuestSessionTask::setProgressSuccess(void) +{ + if (mProgress.isNull()) /* Progress is optional. */ + return VINF_SUCCESS; + + BOOL fCompleted; + if ( SUCCEEDED(mProgress->COMGETTER(Completed(&fCompleted))) + && !fCompleted) + { +#ifdef VBOX_STRICT + ULONG uCurOp; mProgress->COMGETTER(Operation(&uCurOp)); + ULONG cOps; mProgress->COMGETTER(OperationCount(&cOps)); + AssertMsg(uCurOp + 1 /* Zero-based */ == cOps, ("Not all operations done yet (%u/%u)\n", uCurOp + 1, cOps)); +#endif + HRESULT hr = mProgress->i_notifyComplete(S_OK); + if (FAILED(hr)) + return VERR_COM_UNEXPECTED; /** @todo Find a better rc. */ + } + + return VINF_SUCCESS; +} + +/** + * Sets the task's progress object to an error using a string message. + * + * @returns Returns \a hr for convenience. + * @param hr Progress operation result to set. + * @param strMsg Message to set. + */ +HRESULT GuestSessionTask::setProgressErrorMsg(HRESULT hr, const Utf8Str &strMsg) +{ + LogFlowFunc(("hr=%Rhrc, strMsg=%s\n", hr, strMsg.c_str())); + + if (mProgress.isNull()) /* Progress is optional. */ + return hr; /* Return original rc. */ + + BOOL fCanceled; + BOOL fCompleted; + if ( SUCCEEDED(mProgress->COMGETTER(Canceled(&fCanceled))) + && !fCanceled + && SUCCEEDED(mProgress->COMGETTER(Completed(&fCompleted))) + && !fCompleted) + { + HRESULT hr2 = mProgress->i_notifyComplete(hr, + COM_IIDOF(IGuestSession), + GuestSession::getStaticComponentName(), + /* Make sure to hand-in the message via format string to avoid problems + * with (file) paths which e.g. contain "%s" and friends. Can happen with + * randomly generated Validation Kit stuff. */ + "%s", strMsg.c_str()); + if (FAILED(hr2)) + return hr2; + } + return hr; /* Return original rc. */ +} + +/** + * Sets the task's progress object to an error using a string message and a guest error info object. + * + * @returns Returns \a hr for convenience. + * @param hr Progress operation result to set. + * @param strMsg Message to set. + * @param guestErrorInfo Guest error info to use. + */ +HRESULT GuestSessionTask::setProgressErrorMsg(HRESULT hr, const Utf8Str &strMsg, const GuestErrorInfo &guestErrorInfo) +{ + return setProgressErrorMsg(hr, strMsg + Utf8Str(": ") + GuestBase::getErrorAsString(guestErrorInfo)); +} + +/** + * Creates a directory on the guest. + * + * @return VBox status code. + * VINF_ALREADY_EXISTS if directory on the guest already exists (\a fCanExist is \c true). + * VWRN_ALREADY_EXISTS if directory on the guest already exists but must not exist (\a fCanExist is \c false). + * @param strPath Absolute path to directory on the guest (guest style path) to create. + * @param fMode Directory mode to use for creation. + * @param enmDirectoryCreateFlags Directory creation flags. + * @param fFollowSymlinks Whether to follow symlinks on the guest or not. + * @param fCanExist Whether the directory to create is allowed to exist already. + */ +int GuestSessionTask::directoryCreateOnGuest(const com::Utf8Str &strPath, + uint32_t fMode, DirectoryCreateFlag_T enmDirectoryCreateFlags, + bool fFollowSymlinks, bool fCanExist) +{ + LogFlowFunc(("strPath=%s, enmDirectoryCreateFlags=0x%x, fMode=%RU32, fFollowSymlinks=%RTbool, fCanExist=%RTbool\n", + strPath.c_str(), enmDirectoryCreateFlags, fMode, fFollowSymlinks, fCanExist)); + + GuestFsObjData objData; + int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS; + int vrc = mSession->i_directoryQueryInfo(strPath, fFollowSymlinks, objData, &vrcGuest); + if (RT_SUCCESS(vrc)) + { + if (!fCanExist) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Guest directory \"%s\" already exists"), strPath.c_str())); + vrc = VERR_ALREADY_EXISTS; + } + else + vrc = VWRN_ALREADY_EXISTS; + } + else + { + switch (vrc) + { + case VERR_GSTCTL_GUEST_ERROR: + { + switch (vrcGuest) + { + case VERR_FILE_NOT_FOUND: + RT_FALL_THROUGH(); + case VERR_PATH_NOT_FOUND: + vrc = mSession->i_directoryCreate(strPath.c_str(), fMode, enmDirectoryCreateFlags, &vrcGuest); + break; + default: + break; + } + + if (RT_FAILURE(vrc)) + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Guest error creating directory \"%s\" on the guest: %Rrc"), + strPath.c_str(), vrcGuest)); + break; + } + + default: + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Host error creating directory \"%s\" on the guest: %Rrc"), + strPath.c_str(), vrc)); + break; + } + } + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Creates a directory on the host. + * + * @return VBox status code. VERR_ALREADY_EXISTS if directory on the guest already exists. + * @param strPath Absolute path to directory on the host (host style path) to create. + * @param fMode Directory mode to use for creation. + * @param fCreate Directory creation flags. + * @param fCanExist Whether the directory to create is allowed to exist already. + */ +int GuestSessionTask::directoryCreateOnHost(const com::Utf8Str &strPath, uint32_t fMode, uint32_t fCreate, bool fCanExist) +{ + LogFlowFunc(("strPath=%s, fMode=%RU32, fCreate=0x%x, fCanExist=%RTbool\n", strPath.c_str(), fMode, fCreate, fCanExist)); + + LogRel2(("Guest Control: Creating host directory \"%s\" ...\n", strPath.c_str())); + + int vrc = RTDirCreate(strPath.c_str(), fMode, fCreate); + if (RT_FAILURE(vrc)) + { + if (vrc == VERR_ALREADY_EXISTS) + { + if (!fCanExist) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Host directory \"%s\" already exists"), strPath.c_str())); + } + else + vrc = VINF_SUCCESS; + } + else + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Could not create host directory \"%s\": %Rrc"), + strPath.c_str(), vrc)); + } + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Main function for copying a file from guest to the host. + * + * @return VBox status code. + * @param strSrcFile Full path of source file on the host to copy. + * @param srcFile Guest file (source) to copy to the host. Must be in opened and ready state already. + * @param strDstFile Full destination path and file name (guest style) to copy file to. + * @param phDstFile Pointer to host file handle (destination) to copy to. Must be in opened and ready state already. + * @param fFileCopyFlags File copy flags. + * @param offCopy Offset (in bytes) where to start copying the source file. + * @param cbSize Size (in bytes) to copy from the source file. + */ +int GuestSessionTask::fileCopyFromGuestInner(const Utf8Str &strSrcFile, ComObjPtr<GuestFile> &srcFile, + const Utf8Str &strDstFile, PRTFILE phDstFile, + FileCopyFlag_T fFileCopyFlags, uint64_t offCopy, uint64_t cbSize) +{ + RT_NOREF(fFileCopyFlags); + + if (!cbSize) /* Nothing to copy, i.e. empty file? Bail out. */ + return VINF_SUCCESS; + + BOOL fCanceled = FALSE; + uint64_t cbWrittenTotal = 0; + uint64_t cbToRead = cbSize; + + uint32_t uTimeoutMs = 30 * 1000; /* 30s timeout. */ + + int vrc = VINF_SUCCESS; + + if (offCopy) + { + uint64_t offActual; + vrc = srcFile->i_seekAt(offCopy, GUEST_FILE_SEEKTYPE_BEGIN, uTimeoutMs, &offActual); + if (RT_FAILURE(vrc)) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Seeking to offset %RU64 of guest file \"%s\" failed: %Rrc"), + offCopy, strSrcFile.c_str(), vrc)); + return vrc; + } + } + + BYTE byBuf[_64K]; /** @todo Can we do better here? */ + while (cbToRead) + { + uint32_t cbRead; + const uint32_t cbChunk = RT_MIN(cbToRead, sizeof(byBuf)); + vrc = srcFile->i_readData(cbChunk, uTimeoutMs, byBuf, sizeof(byBuf), &cbRead); + if (RT_FAILURE(vrc)) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Reading %RU32 bytes @ %RU64 from guest \"%s\" failed: %Rrc", "", cbChunk), + cbChunk, cbWrittenTotal, strSrcFile.c_str(), vrc)); + break; + } + + vrc = RTFileWrite(*phDstFile, byBuf, cbRead, NULL /* No partial writes */); + if (RT_FAILURE(vrc)) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Writing %RU32 bytes to host file \"%s\" failed: %Rrc", "", cbRead), + cbRead, strDstFile.c_str(), vrc)); + break; + } + + AssertBreak(cbToRead >= cbRead); + cbToRead -= cbRead; + + /* Update total bytes written to the guest. */ + cbWrittenTotal += cbRead; + AssertBreak(cbWrittenTotal <= cbSize); + + /* Did the user cancel the operation above? */ + if ( SUCCEEDED(mProgress->COMGETTER(Canceled(&fCanceled))) + && fCanceled) + break; + + AssertBreakStmt(cbSize, vrc = VERR_INTERNAL_ERROR); + vrc = setProgress(((double)cbWrittenTotal / (double)cbSize) * 100); + if (RT_FAILURE(vrc)) + break; + } + + if ( SUCCEEDED(mProgress->COMGETTER(Canceled(&fCanceled))) + && fCanceled) + return VINF_SUCCESS; + + if (RT_FAILURE(vrc)) + return vrc; + + /* + * Even if we succeeded until here make sure to check whether we really transferred + * everything. + */ + if (cbWrittenTotal == 0) + { + /* If nothing was transferred but the file size was > 0 then "vbox_cat" wasn't able to write + * to the destination -> access denied. */ + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Writing guest file \"%s\" to host file \"%s\" failed: Access denied"), + strSrcFile.c_str(), strDstFile.c_str())); + vrc = VERR_ACCESS_DENIED; + } + else if (cbWrittenTotal < cbSize) + { + /* If we did not copy all let the user know. */ + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Copying guest file \"%s\" to host file \"%s\" failed (%RU64/%RU64 bytes transferred)"), + strSrcFile.c_str(), strDstFile.c_str(), cbWrittenTotal, cbSize)); + vrc = VERR_INTERRUPTED; + } + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Closes a formerly opened guest file. + * + * @returns VBox status code. + * @param file Guest file to close. + * + * @note Set a progress error message on error. + */ +int GuestSessionTask::fileClose(const ComObjPtr<GuestFile> &file) +{ + int vrcGuest; + int vrc = file->i_closeFile(&vrcGuest); + if (RT_FAILURE(vrc)) + { + Utf8Str strFilename; + HRESULT const hrc = file->getFilename(strFilename); + AssertComRCReturn(hrc, VERR_OBJECT_DESTROYED); + setProgressErrorMsg(VBOX_E_IPRT_ERROR, Utf8StrFmt(tr("Error closing guest file \"%s\": %Rrc"), + strFilename.c_str(), vrc == VERR_GSTCTL_GUEST_ERROR ? vrcGuest : vrc)); + if (RT_SUCCESS(vrc)) + vrc = vrc == VERR_GSTCTL_GUEST_ERROR ? vrcGuest : vrc; + } + + return vrc; +} + +/** + * Copies a file from the guest to the host. + * + * @return VBox status code. + * @retval VWRN_ALREADY_EXISTS if the file already exists and FileCopyFlag_NoReplace is specified, + * *or * the file at the destination has the same (or newer) modification time + * and FileCopyFlag_Update is specified. + * @param strSrc Full path of source file on the guest to copy. + * @param strDst Full destination path and file name (host style) to copy file to. + * @param fFileCopyFlags File copy flags. + */ +int GuestSessionTask::fileCopyFromGuest(const Utf8Str &strSrc, const Utf8Str &strDst, FileCopyFlag_T fFileCopyFlags) +{ + LogFlowThisFunc(("strSource=%s, strDest=%s, enmFileCopyFlags=%#x\n", strSrc.c_str(), strDst.c_str(), fFileCopyFlags)); + + GuestFileOpenInfo srcOpenInfo; + srcOpenInfo.mFilename = strSrc; + srcOpenInfo.mOpenAction = FileOpenAction_OpenExisting; + srcOpenInfo.mAccessMode = FileAccessMode_ReadOnly; + srcOpenInfo.mSharingMode = FileSharingMode_All; /** @todo Use _Read when implemented. */ + + ComObjPtr<GuestFile> srcFile; + + GuestFsObjData srcObjData; + int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS; + int vrc = mSession->i_fsQueryInfo(strSrc, TRUE /* fFollowSymlinks */, srcObjData, &vrcGuest); + if (RT_FAILURE(vrc)) + { + if (vrc == VERR_GSTCTL_GUEST_ERROR) + setProgressErrorMsg(VBOX_E_IPRT_ERROR, tr("Guest file lookup failed"), + GuestErrorInfo(GuestErrorInfo::Type_ToolStat, vrcGuest, strSrc.c_str())); + else + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Guest file lookup for \"%s\" failed: %Rrc"), strSrc.c_str(), vrc)); + } + else + { + switch (srcObjData.mType) + { + case FsObjType_File: + break; + + case FsObjType_Symlink: + if (!(fFileCopyFlags & FileCopyFlag_FollowLinks)) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Guest file \"%s\" is a symbolic link"), + strSrc.c_str())); + vrc = VERR_IS_A_SYMLINK; + } + break; + + default: + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Guest object \"%s\" is not a file (is type %#x)"), + strSrc.c_str(), srcObjData.mType)); + vrc = VERR_NOT_A_FILE; + break; + } + } + + if (RT_FAILURE(vrc)) + return vrc; + + vrc = mSession->i_fileOpen(srcOpenInfo, srcFile, &vrcGuest); + if (RT_FAILURE(vrc)) + { + if (vrc == VERR_GSTCTL_GUEST_ERROR) + setProgressErrorMsg(VBOX_E_IPRT_ERROR, tr("Guest file could not be opened"), + GuestErrorInfo(GuestErrorInfo::Type_File, vrcGuest, strSrc.c_str())); + else + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Guest file \"%s\" could not be opened: %Rrc"), strSrc.c_str(), vrc)); + } + + if (RT_FAILURE(vrc)) + return vrc; + + RTFSOBJINFO dstObjInfo; + RT_ZERO(dstObjInfo); + + bool fSkip = false; /* Whether to skip handling the file. */ + + if (RT_SUCCESS(vrc)) + { + vrc = RTPathQueryInfo(strDst.c_str(), &dstObjInfo, RTFSOBJATTRADD_NOTHING); + if (RT_SUCCESS(vrc)) + { + if (fFileCopyFlags & FileCopyFlag_NoReplace) + { + LogRel2(("Guest Control: Host file \"%s\" already exists, skipping\n", strDst.c_str())); + vrc = VWRN_ALREADY_EXISTS; + fSkip = true; + } + + if ( !fSkip + && fFileCopyFlags & FileCopyFlag_Update) + { + RTTIMESPEC srcModificationTimeTS; + RTTimeSpecSetSeconds(&srcModificationTimeTS, srcObjData.mModificationTime); + if (RTTimeSpecCompare(&srcModificationTimeTS, &dstObjInfo.ModificationTime) <= 0) + { + LogRel2(("Guest Control: Host file \"%s\" has same or newer modification date, skipping\n", strDst.c_str())); + vrc = VWRN_ALREADY_EXISTS; + fSkip = true; + } + } + } + else + { + if (vrc == VERR_PATH_NOT_FOUND) /* Destination file does not exist (yet)? */ + vrc = VERR_FILE_NOT_FOUND; /* Needed in next block further down. */ + else if (vrc != VERR_FILE_NOT_FOUND) /* Ditto. */ + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Host file lookup for \"%s\" failed: %Rrc"), strDst.c_str(), vrc)); + } + } + + if (fSkip) + { + int vrc2 = fileClose(srcFile); + if (RT_SUCCESS(vrc)) + vrc = vrc2; + + return vrc; + } + + if (RT_SUCCESS(vrc)) + { + if (RTFS_IS_FILE(dstObjInfo.Attr.fMode)) + { + if (fFileCopyFlags & FileCopyFlag_NoReplace) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, Utf8StrFmt(tr("Host file \"%s\" already exists"), strDst.c_str())); + vrc = VERR_ALREADY_EXISTS; + } + } + else if (RTFS_IS_DIRECTORY(dstObjInfo.Attr.fMode)) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, Utf8StrFmt(tr("Host destination \"%s\" is a directory"), strDst.c_str())); + vrc = VERR_IS_A_DIRECTORY; + } + else if (RTFS_IS_SYMLINK(dstObjInfo.Attr.fMode)) + { + if (!(fFileCopyFlags & FileCopyFlag_FollowLinks)) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, Utf8StrFmt(tr("Host destination \"%s\" is a symbolic link"), strDst.c_str())); + vrc = VERR_IS_A_SYMLINK; + } + } + else + { + LogFlowThisFunc(("Host file system type %#x not supported\n", dstObjInfo.Attr.fMode & RTFS_TYPE_MASK)); + vrc = VERR_NOT_SUPPORTED; + } + } + + LogFlowFunc(("vrc=%Rrc, dstFsType=%#x, pszDstFile=%s\n", vrc, dstObjInfo.Attr.fMode & RTFS_TYPE_MASK, strDst.c_str())); + + if ( RT_SUCCESS(vrc) + || vrc == VERR_FILE_NOT_FOUND) + { + LogRel2(("Guest Control: Copying file \"%s\" from guest to \"%s\" on host ...\n", strSrc.c_str(), strDst.c_str())); + + RTFILE hDstFile; + vrc = RTFileOpen(&hDstFile, strDst.c_str(), + RTFILE_O_WRITE | RTFILE_O_OPEN_CREATE | RTFILE_O_DENY_WRITE); /** @todo Use the correct open modes! */ + if (RT_SUCCESS(vrc)) + { + LogFlowThisFunc(("Copying \"%s\" to \"%s\" (%RI64 bytes) ...\n", + strSrc.c_str(), strDst.c_str(), srcObjData.mObjectSize)); + + vrc = fileCopyFromGuestInner(strSrc, srcFile, strDst, &hDstFile, fFileCopyFlags, + 0 /* Offset, unused */, (uint64_t)srcObjData.mObjectSize); + + int vrc2 = RTFileClose(hDstFile); + AssertRC(vrc2); + } + else + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Opening/creating host file \"%s\" failed: %Rrc"), strDst.c_str(), vrc)); + } + + int vrc2 = fileClose(srcFile); + if (RT_SUCCESS(vrc)) + vrc = vrc2; + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Main function for copying a file from host to the guest. + * + * @return VBox status code. + * @param strSrcFile Full path of source file on the host to copy. + * @param hVfsFile The VFS file handle to read from. + * @param strDstFile Full destination path and file name (guest style) to copy file to. + * @param fileDst Guest file (destination) to copy to the guest. Must be in opened and ready state already. + * @param fFileCopyFlags File copy flags. + * @param offCopy Offset (in bytes) where to start copying the source file. + * @param cbSize Size (in bytes) to copy from the source file. + */ +int GuestSessionTask::fileCopyToGuestInner(const Utf8Str &strSrcFile, RTVFSFILE hVfsFile, + const Utf8Str &strDstFile, ComObjPtr<GuestFile> &fileDst, + FileCopyFlag_T fFileCopyFlags, uint64_t offCopy, uint64_t cbSize) +{ + RT_NOREF(fFileCopyFlags); + + if (!cbSize) /* Nothing to copy, i.e. empty file? Bail out. */ + return VINF_SUCCESS; + + BOOL fCanceled = FALSE; + uint64_t cbWrittenTotal = 0; + uint64_t cbToRead = cbSize; + + uint32_t uTimeoutMs = 30 * 1000; /* 30s timeout. */ + + int vrc = VINF_SUCCESS; + + if (offCopy) + { + uint64_t offActual; + vrc = RTVfsFileSeek(hVfsFile, offCopy, RTFILE_SEEK_END, &offActual); + if (RT_FAILURE(vrc)) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Seeking to offset %RU64 of host file \"%s\" failed: %Rrc"), + offCopy, strSrcFile.c_str(), vrc)); + return vrc; + } + } + + BYTE byBuf[_64K]; + while (cbToRead) + { + size_t cbRead; + const uint32_t cbChunk = RT_MIN(cbToRead, sizeof(byBuf)); + vrc = RTVfsFileRead(hVfsFile, byBuf, cbChunk, &cbRead); + if (RT_FAILURE(vrc)) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Reading %RU32 bytes @ %RU64 from host file \"%s\" failed: %Rrc"), + cbChunk, cbWrittenTotal, strSrcFile.c_str(), vrc)); + break; + } + + vrc = fileDst->i_writeData(uTimeoutMs, byBuf, (uint32_t)cbRead, NULL /* No partial writes */); + if (RT_FAILURE(vrc)) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Writing %zu bytes to guest file \"%s\" failed: %Rrc"), + cbRead, strDstFile.c_str(), vrc)); + break; + } + + Assert(cbToRead >= cbRead); + cbToRead -= cbRead; + + /* Update total bytes written to the guest. */ + cbWrittenTotal += cbRead; + Assert(cbWrittenTotal <= cbSize); + + /* Did the user cancel the operation above? */ + if ( SUCCEEDED(mProgress->COMGETTER(Canceled(&fCanceled))) + && fCanceled) + break; + + AssertBreakStmt(cbSize, vrc = VERR_INTERNAL_ERROR); + vrc = setProgress(((double)cbWrittenTotal / (double)cbSize) * 100); + if (RT_FAILURE(vrc)) + break; + } + + if (RT_FAILURE(vrc)) + return vrc; + + /* + * Even if we succeeded until here make sure to check whether we really transferred + * everything. + */ + if (cbWrittenTotal == 0) + { + /* If nothing was transferred but the file size was > 0 then "vbox_cat" wasn't able to write + * to the destination -> access denied. */ + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Writing to guest file \"%s\" failed: Access denied"), + strDstFile.c_str())); + vrc = VERR_ACCESS_DENIED; + } + else if (cbWrittenTotal < cbSize) + { + /* If we did not copy all let the user know. */ + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Copying to guest file \"%s\" failed (%RU64/%RU64 bytes transferred)"), + strDstFile.c_str(), cbWrittenTotal, cbSize)); + vrc = VERR_INTERRUPTED; + } + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Copies a file from the host to the guest. + * + * @return VBox status code. + * @retval VWRN_ALREADY_EXISTS if the file already exists and FileCopyFlag_NoReplace is specified, + * *or * the file at the destination has the same (or newer) modification time + * and FileCopyFlag_Update is specified. + * @param strSrc Full path of source file on the host. + * @param strDst Full destination path and file name (guest style) to copy file to. Guest-path style. + * @param fFileCopyFlags File copy flags. + */ +int GuestSessionTask::fileCopyToGuest(const Utf8Str &strSrc, const Utf8Str &strDst, FileCopyFlag_T fFileCopyFlags) +{ + LogFlowThisFunc(("strSource=%s, strDst=%s, fFileCopyFlags=%#x\n", strSrc.c_str(), strDst.c_str(), fFileCopyFlags)); + + GuestFileOpenInfo dstOpenInfo; + dstOpenInfo.mFilename = strDst; + if (fFileCopyFlags & FileCopyFlag_NoReplace) + dstOpenInfo.mOpenAction = FileOpenAction_CreateNew; + else + dstOpenInfo.mOpenAction = FileOpenAction_CreateOrReplace; + dstOpenInfo.mAccessMode = FileAccessMode_WriteOnly; + dstOpenInfo.mSharingMode = FileSharingMode_All; /** @todo Use _Read when implemented. */ + + ComObjPtr<GuestFile> dstFile; + int vrcGuest; + int vrc = mSession->i_fileOpen(dstOpenInfo, dstFile, &vrcGuest); + if (RT_FAILURE(vrc)) + { + if (vrc == VERR_GSTCTL_GUEST_ERROR) + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Guest file \"%s\" could not be created or replaced"), strDst.c_str()), + GuestErrorInfo(GuestErrorInfo::Type_File, vrcGuest, strDst.c_str())); + else + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Guest file \"%s\" could not be created or replaced: %Rrc"), strDst.c_str(), vrc)); + return vrc; + } + + char szSrcReal[RTPATH_MAX]; + + RTFSOBJINFO srcObjInfo; + RT_ZERO(srcObjInfo); + + bool fSkip = false; /* Whether to skip handling the file. */ + + if (RT_SUCCESS(vrc)) + { + vrc = RTPathReal(strSrc.c_str(), szSrcReal, sizeof(szSrcReal)); + if (RT_FAILURE(vrc)) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Host path lookup for file \"%s\" failed: %Rrc"), + strSrc.c_str(), vrc)); + } + else + { + vrc = RTPathQueryInfo(szSrcReal, &srcObjInfo, RTFSOBJATTRADD_NOTHING); + if (RT_SUCCESS(vrc)) + { + /* Only perform a remote file query when needed. */ + if ( (fFileCopyFlags & FileCopyFlag_Update) + || (fFileCopyFlags & FileCopyFlag_NoReplace)) + { + GuestFsObjData dstObjData; + vrc = mSession->i_fileQueryInfo(strDst, RT_BOOL(fFileCopyFlags & FileCopyFlag_FollowLinks), dstObjData, + &vrcGuest); + if (RT_SUCCESS(vrc)) + { + if (fFileCopyFlags & FileCopyFlag_NoReplace) + { + LogRel2(("Guest Control: Guest file \"%s\" already exists, skipping\n", strDst.c_str())); + vrc = VWRN_ALREADY_EXISTS; + fSkip = true; + } + + if ( !fSkip + && fFileCopyFlags & FileCopyFlag_Update) + { + RTTIMESPEC dstModificationTimeTS; + RTTimeSpecSetSeconds(&dstModificationTimeTS, dstObjData.mModificationTime); + if (RTTimeSpecCompare(&dstModificationTimeTS, &srcObjInfo.ModificationTime) <= 0) + { + LogRel2(("Guest Control: Guest file \"%s\" has same or newer modification date, skipping\n", + strDst.c_str())); + vrc = VWRN_ALREADY_EXISTS; + fSkip = true; + } + } + } + else + { + if (vrc == VERR_GSTCTL_GUEST_ERROR) + { + switch (vrcGuest) + { + case VERR_FILE_NOT_FOUND: + vrc = VINF_SUCCESS; + break; + + default: + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Guest error while determining object data for guest file \"%s\": %Rrc"), + strDst.c_str(), vrcGuest)); + break; + } + } + else + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Host error while determining object data for guest file \"%s\": %Rrc"), + strDst.c_str(), vrc)); + } + } + } + else + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Host source file lookup for \"%s\" failed: %Rrc"), + szSrcReal, vrc)); + } + } + } + + if (fSkip) + { + int vrc2 = fileClose(dstFile); + if (RT_SUCCESS(vrc)) + vrc = vrc2; + + return vrc; + } + + if (RT_SUCCESS(vrc)) + { + LogRel2(("Guest Control: Copying file \"%s\" from host to \"%s\" on guest ...\n", strSrc.c_str(), strDst.c_str())); + + RTVFSFILE hSrcFile; + vrc = RTVfsFileOpenNormal(szSrcReal, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE, &hSrcFile); + if (RT_SUCCESS(vrc)) + { + LogFlowThisFunc(("Copying \"%s\" to \"%s\" (%RI64 bytes) ...\n", + szSrcReal, strDst.c_str(), srcObjInfo.cbObject)); + + vrc = fileCopyToGuestInner(szSrcReal, hSrcFile, strDst, dstFile, + fFileCopyFlags, 0 /* Offset, unused */, srcObjInfo.cbObject); + + int vrc2 = RTVfsFileRelease(hSrcFile); + AssertRC(vrc2); + } + else + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Opening host file \"%s\" failed: %Rrc"), + szSrcReal, vrc)); + } + + int vrc2 = fileClose(dstFile); + if (RT_SUCCESS(vrc)) + vrc = vrc2; + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Adds a guest file system entry to a given list. + * + * @return VBox status code. + * @param strFile Path to file system entry to add. + * @param fsObjData Guest file system information of entry to add. + */ +int FsList::AddEntryFromGuest(const Utf8Str &strFile, const GuestFsObjData &fsObjData) +{ + LogFlowFunc(("Adding \"%s\"\n", strFile.c_str())); + + FsEntry *pEntry = NULL; + try + { + pEntry = new FsEntry(); + pEntry->fMode = fsObjData.GetFileMode(); + pEntry->strPath = strFile; + + mVecEntries.push_back(pEntry); + } + catch (std::bad_alloc &) + { + if (pEntry) + delete pEntry; + return VERR_NO_MEMORY; + } + + return VINF_SUCCESS; +} + +/** + * Adds a host file system entry to a given list. + * + * @return VBox status code. + * @param strFile Path to file system entry to add. + * @param pcObjInfo File system information of entry to add. + */ +int FsList::AddEntryFromHost(const Utf8Str &strFile, PCRTFSOBJINFO pcObjInfo) +{ + LogFlowFunc(("Adding \"%s\"\n", strFile.c_str())); + + FsEntry *pEntry = NULL; + try + { + pEntry = new FsEntry(); + pEntry->fMode = pcObjInfo->Attr.fMode; + pEntry->strPath = strFile; + + mVecEntries.push_back(pEntry); + } + catch (std::bad_alloc &) + { + if (pEntry) + delete pEntry; + return VERR_NO_MEMORY; + } + + return VINF_SUCCESS; +} + +FsList::FsList(const GuestSessionTask &Task) + : mTask(Task) +{ +} + +FsList::~FsList() +{ + Destroy(); +} + +/** + * Initializes a file list. + * + * @return VBox status code. + * @param strSrcRootAbs Source root path (absolute) for this file list. + * @param strDstRootAbs Destination root path (absolute) for this file list. + * @param SourceSpec Source specification to use. + */ +int FsList::Init(const Utf8Str &strSrcRootAbs, const Utf8Str &strDstRootAbs, + const GuestSessionFsSourceSpec &SourceSpec) +{ + mSrcRootAbs = strSrcRootAbs; + mDstRootAbs = strDstRootAbs; + mSourceSpec = SourceSpec; + + /* Note: Leave the source and dest roots unmodified -- how paths will be treated + * will be done directly when working on those. See @bugref{10139}. */ + + LogFlowFunc(("mSrcRootAbs=%s, mDstRootAbs=%s, fDirCopyFlags=%#x, fFileCopyFlags=%#x\n", + mSrcRootAbs.c_str(), mDstRootAbs.c_str(), mSourceSpec.fDirCopyFlags, mSourceSpec.fFileCopyFlags)); + + return VINF_SUCCESS; +} + +/** + * Destroys a file list. + */ +void FsList::Destroy(void) +{ + LogFlowFuncEnter(); + + FsEntries::iterator itEntry = mVecEntries.begin(); + while (itEntry != mVecEntries.end()) + { + FsEntry *pEntry = *itEntry; + delete pEntry; + mVecEntries.erase(itEntry); + itEntry = mVecEntries.begin(); + } + + Assert(mVecEntries.empty()); + + LogFlowFuncLeave(); +} + +#ifdef DEBUG +/** + * Dumps a FsList to the debug log. + */ +void FsList::DumpToLog(void) +{ + LogFlowFunc(("strSrcRootAbs=%s, strDstRootAbs=%s\n", mSrcRootAbs.c_str(), mDstRootAbs.c_str())); + + FsEntries::iterator itEntry = mVecEntries.begin(); + while (itEntry != mVecEntries.end()) + { + FsEntry *pEntry = *itEntry; + LogFlowFunc(("\tstrPath=%s (fMode %#x)\n", pEntry->strPath.c_str(), pEntry->fMode)); + ++itEntry; + } + + LogFlowFuncLeave(); +} +#endif /* DEBUG */ + +/** + * Builds a guest file list from a given path (and optional filter). + * + * @return VBox status code. + * @param strPath Directory on the guest to build list from. + * @param strSubDir Current sub directory path; needed for recursion. + * Set to an empty path. + */ +int FsList::AddDirFromGuest(const Utf8Str &strPath, const Utf8Str &strSubDir /* = "" */) +{ + Utf8Str strPathAbs = strPath; + if (!strPathAbs.endsWith(PATH_STYLE_SEP_STR(mSourceSpec.enmPathStyle))) + strPathAbs += PATH_STYLE_SEP_STR(mSourceSpec.enmPathStyle); + + Utf8Str strPathSub = strSubDir; + if ( strPathSub.isNotEmpty() + && !strPathSub.endsWith(PATH_STYLE_SEP_STR(mSourceSpec.enmPathStyle))) + strPathSub += PATH_STYLE_SEP_STR(mSourceSpec.enmPathStyle); + + strPathAbs += strPathSub; + + LogFlowFunc(("Entering \"%s\" (sub \"%s\")\n", strPathAbs.c_str(), strPathSub.c_str())); + + LogRel2(("Guest Control: Handling directory \"%s\" on guest ...\n", strPathAbs.c_str())); + + GuestDirectoryOpenInfo dirOpenInfo; + dirOpenInfo.mFilter = ""; + dirOpenInfo.mPath = strPathAbs; + dirOpenInfo.mFlags = 0; /** @todo Handle flags? */ + + const ComObjPtr<GuestSession> &pSession = mTask.GetSession(); + + ComObjPtr <GuestDirectory> pDir; + int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS; + int vrc = pSession->i_directoryOpen(dirOpenInfo, pDir, &vrcGuest); + if (RT_FAILURE(vrc)) + { + switch (vrc) + { + case VERR_INVALID_PARAMETER: + break; + + case VERR_GSTCTL_GUEST_ERROR: + break; + + default: + break; + } + + return vrc; + } + + if (strPathSub.isNotEmpty()) + { + GuestFsObjData fsObjData; + fsObjData.mType = FsObjType_Directory; + + vrc = AddEntryFromGuest(strPathSub, fsObjData); + } + + if (RT_SUCCESS(vrc)) + { + ComObjPtr<GuestFsObjInfo> fsObjInfo; + while (RT_SUCCESS(vrc = pDir->i_read(fsObjInfo, &vrcGuest))) + { + FsObjType_T enmObjType = FsObjType_Unknown; /* Shut up MSC. */ + HRESULT hrc2 = fsObjInfo->COMGETTER(Type)(&enmObjType); + AssertComRC(hrc2); + + com::Bstr bstrName; + hrc2 = fsObjInfo->COMGETTER(Name)(bstrName.asOutParam()); + AssertComRC(hrc2); + + Utf8Str strEntry = strPathSub + Utf8Str(bstrName); + + LogFlowFunc(("Entry \"%s\"\n", strEntry.c_str())); + + switch (enmObjType) + { + case FsObjType_Directory: + { + if ( bstrName.equals(".") + || bstrName.equals("..")) + { + break; + } + + LogRel2(("Guest Control: Directory \"%s\"\n", strEntry.c_str())); + + if (!(mSourceSpec.fDirCopyFlags & DirectoryCopyFlag_Recursive)) + break; + + vrc = AddDirFromGuest(strPath, strEntry); + break; + } + + case FsObjType_Symlink: + { + if ( mSourceSpec.fDirCopyFlags & DirectoryCopyFlag_FollowLinks + || mSourceSpec.fFileCopyFlags & FileCopyFlag_FollowLinks) + { + /** @todo Symlink handling from guest is not implemented yet. + * See IGuestSession::symlinkRead(). */ + LogRel2(("Guest Control: Warning: Symlink support on guest side not available, skipping \"%s\"\n", + strEntry.c_str())); + } + break; + } + + case FsObjType_File: + { + LogRel2(("Guest Control: File \"%s\"\n", strEntry.c_str())); + + vrc = AddEntryFromGuest(strEntry, fsObjInfo->i_getData()); + break; + } + + default: + break; + } + } + + if (vrc == VERR_NO_MORE_FILES) /* End of listing reached? */ + vrc = VINF_SUCCESS; + } + + int vrc2 = pDir->i_closeInternal(&vrcGuest); + if (RT_SUCCESS(vrc)) + vrc = vrc2; + + return vrc; +} + +/** + * Builds a host file list from a given path. + * + * @return VBox status code. + * @param strPath Directory on the host to build list from. + * @param strSubDir Current sub directory path; needed for recursion. + * Set to an empty path. + * @param pszPathReal Scratch buffer for holding the resolved real path. + * Needed for recursion. + * @param cbPathReal Size (in bytes) of \a pszPathReal. + * @param pDirEntry Where to store looked up directory information for handled paths. + * Needed for recursion. + */ +int FsList::AddDirFromHost(const Utf8Str &strPath, const Utf8Str &strSubDir, + char *pszPathReal, size_t cbPathReal, PRTDIRENTRYEX pDirEntry) +{ + Utf8Str strPathAbs = strPath; + if (!strPathAbs.endsWith(RTPATH_SLASH_STR)) + strPathAbs += RTPATH_SLASH_STR; + + Utf8Str strPathSub = strSubDir; + if ( strPathSub.isNotEmpty() + && !strPathSub.endsWith(RTPATH_SLASH_STR)) + strPathSub += RTPATH_SLASH_STR; + + strPathAbs += strPathSub; + + LogFlowFunc(("Entering \"%s\" (sub \"%s\")\n", strPathAbs.c_str(), strPathSub.c_str())); + + LogRel2(("Guest Control: Handling directory \"%s\" on host ...\n", strPathAbs.c_str())); + + RTFSOBJINFO objInfo; + int vrc = RTPathQueryInfo(strPathAbs.c_str(), &objInfo, RTFSOBJATTRADD_NOTHING); + if (RT_SUCCESS(vrc)) + { + if (RTFS_IS_DIRECTORY(objInfo.Attr.fMode)) + { + if (strPathSub.isNotEmpty()) + vrc = AddEntryFromHost(strPathSub, &objInfo); + + if (RT_SUCCESS(vrc)) + { + RTDIR hDir; + vrc = RTDirOpen(&hDir, strPathAbs.c_str()); + if (RT_SUCCESS(vrc)) + { + do + { + /* Retrieve the next directory entry. */ + vrc = RTDirReadEx(hDir, pDirEntry, NULL, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK); + if (RT_FAILURE(vrc)) + { + if (vrc == VERR_NO_MORE_FILES) + vrc = VINF_SUCCESS; + break; + } + + Utf8Str strEntry = strPathSub + Utf8Str(pDirEntry->szName); + + LogFlowFunc(("Entry \"%s\"\n", strEntry.c_str())); + + switch (pDirEntry->Info.Attr.fMode & RTFS_TYPE_MASK) + { + case RTFS_TYPE_DIRECTORY: + { + /* Skip "." and ".." entries. */ + if (RTDirEntryExIsStdDotLink(pDirEntry)) + break; + + LogRel2(("Guest Control: Directory \"%s\"\n", strEntry.c_str())); + + if (!(mSourceSpec.fDirCopyFlags & DirectoryCopyFlag_Recursive)) + break; + + vrc = AddDirFromHost(strPath, strEntry, pszPathReal, cbPathReal, pDirEntry); + break; + } + + case RTFS_TYPE_FILE: + { + LogRel2(("Guest Control: File \"%s\"\n", strEntry.c_str())); + + vrc = AddEntryFromHost(strEntry, &pDirEntry->Info); + break; + } + + case RTFS_TYPE_SYMLINK: + { + Utf8Str strEntryAbs = strPathAbs + (const char *)pDirEntry->szName; + + vrc = RTPathReal(strEntryAbs.c_str(), pszPathReal, cbPathReal); + if (RT_SUCCESS(vrc)) + { + vrc = RTPathQueryInfo(pszPathReal, &objInfo, RTFSOBJATTRADD_NOTHING); + if (RT_SUCCESS(vrc)) + { + if (RTFS_IS_DIRECTORY(objInfo.Attr.fMode)) + { + LogRel2(("Guest Control: Symbolic link \"%s\" -> \"%s\" (directory)\n", + strEntryAbs.c_str(), pszPathReal)); + if (mSourceSpec.fDirCopyFlags & DirectoryCopyFlag_FollowLinks) + vrc = AddDirFromHost(strPath, strEntry, pszPathReal, cbPathReal, pDirEntry); + } + else if (RTFS_IS_FILE(objInfo.Attr.fMode)) + { + LogRel2(("Guest Control: Symbolic link \"%s\" -> \"%s\" (file)\n", + strEntryAbs.c_str(), pszPathReal)); + if (mSourceSpec.fFileCopyFlags & FileCopyFlag_FollowLinks) + vrc = AddEntryFromHost(strEntry, &objInfo); + } + else + vrc = VERR_NOT_SUPPORTED; + } + + if (RT_FAILURE(vrc)) + LogRel2(("Guest Control: Unable to query symbolic link info for \"%s\", rc=%Rrc\n", + pszPathReal, vrc)); + } + else + { + LogRel2(("Guest Control: Unable to resolve symlink for \"%s\", rc=%Rrc\n", strPathAbs.c_str(), vrc)); + if (vrc == VERR_FILE_NOT_FOUND) /* Broken symlink, skip. */ + vrc = VINF_SUCCESS; + } + break; + } + + default: + break; + } + + } while (RT_SUCCESS(vrc)); + + RTDirClose(hDir); + } + } + } + else if (RTFS_IS_FILE(objInfo.Attr.fMode)) + vrc = VERR_IS_A_FILE; + else if (RTFS_IS_SYMLINK(objInfo.Attr.fMode)) + vrc = VERR_IS_A_SYMLINK; + else + vrc = VERR_NOT_SUPPORTED; + } + else + LogFlowFunc(("Unable to query \"%s\", rc=%Rrc\n", strPathAbs.c_str(), vrc)); + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +GuestSessionTaskOpen::GuestSessionTaskOpen(GuestSession *pSession, uint32_t uFlags, uint32_t uTimeoutMS) + : GuestSessionTask(pSession) + , mFlags(uFlags) + , mTimeoutMS(uTimeoutMS) +{ + m_strTaskName = "gctlSesOpen"; +} + +GuestSessionTaskOpen::~GuestSessionTaskOpen(void) +{ + +} + +/** @copydoc GuestSessionTask::Run */ +int GuestSessionTaskOpen::Run(void) +{ + LogFlowThisFuncEnter(); + + AutoCaller autoCaller(mSession); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + int vrc = mSession->i_startSession(NULL /*pvrcGuest*/); + /* Nothing to do here anymore. */ + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +GuestSessionCopyTask::GuestSessionCopyTask(GuestSession *pSession) + : GuestSessionTask(pSession) +{ +} + +GuestSessionCopyTask::~GuestSessionCopyTask() +{ + FsLists::iterator itList = mVecLists.begin(); + while (itList != mVecLists.end()) + { + FsList *pFsList = (*itList); + pFsList->Destroy(); + delete pFsList; + mVecLists.erase(itList); + itList = mVecLists.begin(); + } + + Assert(mVecLists.empty()); +} + +GuestSessionTaskCopyFrom::GuestSessionTaskCopyFrom(GuestSession *pSession, GuestSessionFsSourceSet const &vecSrc, + const Utf8Str &strDest) + : GuestSessionCopyTask(pSession) +{ + m_strTaskName = "gctlCpyFrm"; + + mSources = vecSrc; + mDest = strDest; +} + +GuestSessionTaskCopyFrom::~GuestSessionTaskCopyFrom(void) +{ +} + +/** + * Initializes a copy-from-guest task. + * + * @returns HRESULT + * @param strTaskDesc Friendly task description. + */ +HRESULT GuestSessionTaskCopyFrom::Init(const Utf8Str &strTaskDesc) +{ + setTaskDesc(strTaskDesc); + + /* Create the progress object. */ + ComObjPtr<Progress> pProgress; + HRESULT hrc = pProgress.createObject(); + if (FAILED(hrc)) + return hrc; + + mProgress = pProgress; + + int vrc = VINF_SUCCESS; + + ULONG cOperations = 0; + Utf8Str strErrorInfo; + + /** + * Note: We need to build up the file/directory here instead of GuestSessionTaskCopyFrom::Run + * because the caller expects a ready-for-operation progress object on return. + * The progress object will have a variable operation count, based on the elements to + * be processed. + */ + + if (mSources.empty()) + { + strErrorInfo.printf(tr("No guest sources specified")); + vrc = VERR_INVALID_PARAMETER; + } + else if (mDest.isEmpty()) + { + strErrorInfo.printf(tr("Host destination must not be empty")); + vrc = VERR_INVALID_PARAMETER; + } + else + { + GuestSessionFsSourceSet::iterator itSrc = mSources.begin(); + while (itSrc != mSources.end()) + { + Utf8Str strSrc = itSrc->strSource; + Utf8Str strDst = mDest; + + bool fFollowSymlinks; + + if (strSrc.isEmpty()) + { + strErrorInfo.printf(tr("Guest source entry must not be empty")); + vrc = VERR_INVALID_PARAMETER; + break; + } + + if (itSrc->enmType == FsObjType_Directory) + { + fFollowSymlinks = itSrc->fDirCopyFlags & DirectoryCopyFlag_FollowLinks; + } + else + { + fFollowSymlinks = RT_BOOL(itSrc->fFileCopyFlags & FileCopyFlag_FollowLinks); + } + + LogFlowFunc(("strSrc=%s (path style is %s), strDst=%s, fFollowSymlinks=%RTbool\n", + strSrc.c_str(), GuestBase::pathStyleToStr(itSrc->enmPathStyle), strDst.c_str(), fFollowSymlinks)); + + GuestFsObjData srcObjData; + int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS; + vrc = mSession->i_fsQueryInfo(strSrc, fFollowSymlinks, srcObjData, &vrcGuest); + if (RT_FAILURE(vrc)) + { + if (vrc == VERR_GSTCTL_GUEST_ERROR) + strErrorInfo = GuestBase::getErrorAsString(tr("Guest source lookup failed"), + GuestErrorInfo(GuestErrorInfo::Type_ToolStat, vrcGuest, strSrc.c_str())); + else + strErrorInfo.printf(tr("Guest source lookup for \"%s\" failed: %Rrc"), + strSrc.c_str(), vrc); + break; + } + + if (srcObjData.mType == FsObjType_Directory) + { + if (itSrc->enmType != FsObjType_Directory) + { + strErrorInfo.printf(tr("Guest source is not a file: %s"), strSrc.c_str()); + vrc = VERR_NOT_A_FILE; + break; + } + } + else + { + if (itSrc->enmType != FsObjType_File) + { + strErrorInfo.printf(tr("Guest source is not a directory: %s"), strSrc.c_str()); + vrc = VERR_NOT_A_DIRECTORY; + break; + } + } + + FsList *pFsList = NULL; + try + { + pFsList = new FsList(*this); + vrc = pFsList->Init(strSrc, strDst, *itSrc); + if (RT_SUCCESS(vrc)) + { + switch (itSrc->enmType) + { + case FsObjType_Directory: + { + vrc = pFsList->AddDirFromGuest(strSrc); + break; + } + + case FsObjType_File: + /* The file name is already part of the actual list's source root (strSrc). */ + break; + + default: + LogRel2(("Guest Control: Warning: Unknown guest file system type %#x for source \"%s\", skipping\n", + itSrc->enmType, strSrc.c_str())); + break; + } + } + + if (RT_FAILURE(vrc)) + { + delete pFsList; + strErrorInfo.printf(tr("Error adding guest source \"%s\" to list: %Rrc"), + strSrc.c_str(), vrc); + break; + } +#ifdef DEBUG + pFsList->DumpToLog(); +#endif + mVecLists.push_back(pFsList); + } + catch (std::bad_alloc &) + { + vrc = VERR_NO_MEMORY; + break; + } + + AssertPtr(pFsList); + cOperations += (ULONG)pFsList->mVecEntries.size(); + + itSrc++; + } + } + + if (RT_SUCCESS(vrc)) + { + /* When there are no entries in the first source list, this means the source only contains a single file + * (see \a mSrcRootAbs of FsList). So use \a mSrcRootAbs directly. */ + Utf8Str const &strFirstOp = mVecLists[0]->mVecEntries.size() > 0 + ? mVecLists[0]->mVecEntries[0]->strPath : mVecLists[0]->mSrcRootAbs; + + /* Now that we know how many objects we're handling, tweak the progress description so that it + * reflects more accurately what the progress is actually doing. */ + if (cOperations > 1) + { + mDesc.printf(tr("Copying \"%s\" [and %zu %s] from guest to \"%s\" on the host ..."), + strFirstOp.c_str(), cOperations - 1, cOperations > 2 ? tr("others") : tr("other"), mDest.c_str()); + } + else + mDesc.printf(tr("Copying \"%s\" from guest to \"%s\" on the host ..."), strFirstOp.c_str(), mDest.c_str()); + + hrc = pProgress->init(static_cast<IGuestSession*>(mSession), Bstr(mDesc).raw(), + TRUE /* aCancelable */, cOperations + 1 /* Number of operations */, Bstr(strFirstOp).raw()); + } + else /* On error we go with an "empty" progress object when will be used for error handling. */ + hrc = pProgress->init(static_cast<IGuestSession*>(mSession), Bstr(mDesc).raw(), + TRUE /* aCancelable */, 1 /* cOperations */, Bstr(mDesc).raw()); + + if (FAILED(hrc)) /* Progress object creation failed -- we're doomed. */ + return hrc; + + if (RT_FAILURE(vrc)) + { + if (strErrorInfo.isEmpty()) + strErrorInfo.printf(tr("Failed with %Rrc"), vrc); + setProgressErrorMsg(VBOX_E_IPRT_ERROR, strErrorInfo); + } + + LogFlowFunc(("Returning %Rhrc (%Rrc)\n", hrc, vrc)); + return hrc; +} + +/** @copydoc GuestSessionTask::Run */ +int GuestSessionTaskCopyFrom::Run(void) +{ + LogFlowThisFuncEnter(); + + AutoCaller autoCaller(mSession); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + int vrc = VINF_SUCCESS; + + FsLists::const_iterator itList = mVecLists.begin(); + while (itList != mVecLists.end()) + { + FsList *pList = *itList; + AssertPtr(pList); + + LogFlowFunc(("List: srcRootAbs=%s, dstRootAbs=%s\n", pList->mSrcRootAbs.c_str(), pList->mDstRootAbs.c_str())); + + Utf8Str strSrcRootAbs = pList->mSrcRootAbs; + Utf8Str strDstRootAbs = pList->mDstRootAbs; + + vrc = GuestPath::BuildDestinationPath(strSrcRootAbs, mSession->i_getGuestPathStyle() /* Source */, + strDstRootAbs, PATH_STYLE_NATIVE /* Dest */); + if (RT_FAILURE(vrc)) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Building host destination root path \"%s\" failed: %Rrc"), + strDstRootAbs.c_str(), vrc)); + break; + } + + bool fCopyIntoExisting; + bool fFollowSymlinks; + + if (pList->mSourceSpec.enmType == FsObjType_Directory) + { + fCopyIntoExisting = RT_BOOL(pList->mSourceSpec.fDirCopyFlags & DirectoryCopyFlag_CopyIntoExisting); + fFollowSymlinks = RT_BOOL(pList->mSourceSpec.fDirCopyFlags & DirectoryCopyFlag_FollowLinks); + } + else if (pList->mSourceSpec.enmType == FsObjType_File) + { + fCopyIntoExisting = !RT_BOOL(pList->mSourceSpec.fFileCopyFlags & FileCopyFlag_NoReplace); + fFollowSymlinks = RT_BOOL(pList->mSourceSpec.fFileCopyFlags & FileCopyFlag_FollowLinks); + } + else + AssertFailedBreakStmt(vrc = VERR_NOT_IMPLEMENTED); + + uint32_t const fDirMode = 0700; /** @todo Play safe by default; implement ACLs. */ + uint32_t fDirCreate = 0; + + bool fDstExists = true; + + RTFSOBJINFO dstFsObjInfo; + RT_ZERO(dstFsObjInfo); + vrc = RTPathQueryInfoEx(strDstRootAbs.c_str(), &dstFsObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK /* fFlags */); + if (RT_SUCCESS(vrc)) + { + char szPathReal[RTPATH_MAX]; + vrc = RTPathReal(strDstRootAbs.c_str(), szPathReal, sizeof(szPathReal)); + if (RT_SUCCESS(vrc)) + { + vrc = RTPathQueryInfoEx(szPathReal, &dstFsObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK /* fFlags */); + if (RT_SUCCESS(vrc)) + { + LogRel2(("Guest Control: Host destination is a symbolic link \"%s\" -> \"%s\" (%s)\n", + strDstRootAbs.c_str(), szPathReal, + GuestBase::fsObjTypeToStr(GuestBase::fileModeToFsObjType(dstFsObjInfo.Attr.fMode)))); + } + + strDstRootAbs = szPathReal; + } + } + else + { + if ( vrc == VERR_FILE_NOT_FOUND + || vrc == VERR_PATH_NOT_FOUND) + { + fDstExists = false; + vrc = VINF_SUCCESS; + } + else + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Host path lookup for \"%s\" failed: %Rrc"), strDstRootAbs.c_str(), vrc)); + break; + } + } + + /* Create the root directory. */ + if (pList->mSourceSpec.enmType == FsObjType_Directory) + { + LogFlowFunc(("Directory: fDirCopyFlags=%#x, fCopyIntoExisting=%RTbool, fFollowSymlinks=%RTbool -> fDstExist=%RTbool (%s)\n", + pList->mSourceSpec.fDirCopyFlags, fCopyIntoExisting, fFollowSymlinks, + fDstExists, GuestBase::fsObjTypeToStr(GuestBase::fileModeToFsObjType(dstFsObjInfo.Attr.fMode)))); + + if (fDstExists) + { + switch (dstFsObjInfo.Attr.fMode & RTFS_TYPE_MASK) + { + case RTFS_TYPE_DIRECTORY: + { + if (!fCopyIntoExisting) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Host root directory \"%s\" already exists"), strDstRootAbs.c_str())); + vrc = VERR_ALREADY_EXISTS; + break; + } + break; + } + + case RTFS_TYPE_FILE: + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Destination \"%s\" on the host already exists and is a file"), strDstRootAbs.c_str())); + vrc = VERR_IS_A_FILE; + break; + } + + default: + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Unknown object type (%#x) on host for \"%s\""), + dstFsObjInfo.Attr.fMode & RTFS_TYPE_MASK, strDstRootAbs.c_str())); + vrc = VERR_NOT_SUPPORTED; + break; + } + } + } + + if (RT_FAILURE(vrc)) + break; + + /* Make sure the destination root directory exists. */ + if (pList->mSourceSpec.fDryRun == false) + { + vrc = directoryCreateOnHost(strDstRootAbs, fDirMode, 0 /* fCreate */, true /* fCanExist */); + if (RT_FAILURE(vrc)) + break; + } + + AssertBreakStmt(pList->mSourceSpec.enmType == FsObjType_Directory, vrc = VERR_NOT_SUPPORTED); + + /* Walk the entries. */ + FsEntries::const_iterator itEntry = pList->mVecEntries.begin(); + while (itEntry != pList->mVecEntries.end()) + { + FsEntry *pEntry = *itEntry; + AssertPtr(pEntry); + + Utf8Str strSrcAbs = strSrcRootAbs; + Utf8Str strDstAbs = strDstRootAbs; + + strSrcAbs += PATH_STYLE_SEP_STR(pList->mSourceSpec.enmPathStyle); + strSrcAbs += pEntry->strPath; + + strDstAbs += PATH_STYLE_SEP_STR(PATH_STYLE_NATIVE); + strDstAbs += pEntry->strPath; + + /* Clean up the final guest source path. */ + vrc = GuestPath::Translate(strSrcAbs, pList->mSourceSpec.enmPathStyle /* Source */, + pList->mSourceSpec.enmPathStyle /* Dest */); + if (RT_FAILURE(vrc)) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Translating guest source path \"%s\" failed: %Rrc"), + strSrcAbs.c_str(), vrc)); + break; + } + + /* Translate the final host desitnation path. */ + vrc = GuestPath::Translate(strDstAbs, mSession->i_getGuestPathStyle() /* Source */, PATH_STYLE_NATIVE /* Dest */); + if (RT_FAILURE(vrc)) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Translating host destination path \"%s\" failed: %Rrc"), + strDstAbs.c_str(), vrc)); + break; + } + + mProgress->SetNextOperation(Bstr(strSrcAbs).raw(), 1); + + switch (pEntry->fMode & RTFS_TYPE_MASK) + { + case RTFS_TYPE_DIRECTORY: + if (!pList->mSourceSpec.fDryRun) + vrc = directoryCreateOnHost(strDstAbs, fDirMode, fDirCreate, fCopyIntoExisting); + break; + + case RTFS_TYPE_FILE: + RT_FALL_THROUGH(); + case RTFS_TYPE_SYMLINK: + if (!pList->mSourceSpec.fDryRun) + vrc = fileCopyFromGuest(strSrcAbs, strDstAbs, pList->mSourceSpec.fFileCopyFlags); + break; + + default: + AssertFailed(); /* Should never happen (we already have a filtered list). */ + break; + } + + if (RT_FAILURE(vrc)) + break; + + ++itEntry; + } + } + else if (pList->mSourceSpec.enmType == FsObjType_File) + { + LogFlowFunc(("File: fFileCopyFlags=%#x, fCopyIntoExisting=%RTbool, fFollowSymlinks=%RTbool -> fDstExist=%RTbool (%s)\n", + pList->mSourceSpec.fFileCopyFlags, fCopyIntoExisting, fFollowSymlinks, + fDstExists, GuestBase::fsObjTypeToStr(GuestBase::fileModeToFsObjType(dstFsObjInfo.Attr.fMode)))); + + if (fDstExists) + { + switch (dstFsObjInfo.Attr.fMode & RTFS_TYPE_MASK) + { + case RTFS_TYPE_DIRECTORY: + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Destination \"%s\" on the host already exists and is a directory"), + strDstRootAbs.c_str())); + vrc = VERR_IS_A_DIRECTORY; + break; + } + + case RTFS_TYPE_FILE: + { + if (!fCopyIntoExisting) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Host file \"%s\" already exists"), strDstRootAbs.c_str())); + vrc = VERR_ALREADY_EXISTS; + } + break; + } + + default: + { + /** @todo Resolve symlinks? */ + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Unknown object type (%#x) on host for \"%s\""), + dstFsObjInfo.Attr.fMode & RTFS_TYPE_MASK, strDstRootAbs.c_str())); + vrc = VERR_NOT_SUPPORTED; + break; + } + } + } + + if (RT_SUCCESS(vrc)) + { + /* Translate the final host destination file path. */ + vrc = GuestPath::Translate(strDstRootAbs, + mSession->i_getGuestPathStyle() /* Dest */, PATH_STYLE_NATIVE /* Source */); + if (RT_FAILURE(vrc)) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Translating host destination path \"%s\" failed: %Rrc"), + strDstRootAbs.c_str(), vrc)); + break; + } + + if (!pList->mSourceSpec.fDryRun) + vrc = fileCopyFromGuest(strSrcRootAbs, strDstRootAbs, pList->mSourceSpec.fFileCopyFlags); + } + } + else + AssertFailedStmt(vrc = VERR_NOT_SUPPORTED); + + if (RT_FAILURE(vrc)) + break; + + ++itList; + } + + if (RT_SUCCESS(vrc)) + vrc = setProgressSuccess(); + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +GuestSessionTaskCopyTo::GuestSessionTaskCopyTo(GuestSession *pSession, GuestSessionFsSourceSet const &vecSrc, + const Utf8Str &strDest) + : GuestSessionCopyTask(pSession) +{ + m_strTaskName = "gctlCpyTo"; + + mSources = vecSrc; + mDest = strDest; +} + +GuestSessionTaskCopyTo::~GuestSessionTaskCopyTo(void) +{ +} + +/** + * Initializes a copy-to-guest task. + * + * @returns HRESULT + * @param strTaskDesc Friendly task description. + */ +HRESULT GuestSessionTaskCopyTo::Init(const Utf8Str &strTaskDesc) +{ + LogFlowFuncEnter(); + + setTaskDesc(strTaskDesc); + + /* Create the progress object. */ + ComObjPtr<Progress> pProgress; + HRESULT hrc = pProgress.createObject(); + if (FAILED(hrc)) + return hrc; + + mProgress = pProgress; + + int vrc = VINF_SUCCESS; + + ULONG cOperations = 0; + Utf8Str strErrorInfo; + + /* + * Note: We need to build up the file/directory here instead of GuestSessionTaskCopyTo::Run + * because the caller expects a ready-for-operation progress object on return. + * The progress object will have a variable operation count, based on the elements to + * be processed. + */ + + if (mSources.empty()) + { + strErrorInfo.printf(tr("No host sources specified")); + vrc = VERR_INVALID_PARAMETER; + } + else if (mDest.isEmpty()) + { + strErrorInfo.printf(tr("Guest destination must not be empty")); + vrc = VERR_INVALID_PARAMETER; + } + else + { + GuestSessionFsSourceSet::iterator itSrc = mSources.begin(); + while (itSrc != mSources.end()) + { + Utf8Str strSrc = itSrc->strSource; + Utf8Str strDst = mDest; + + bool fFollowSymlinks; + + if (strSrc.isEmpty()) + { + strErrorInfo.printf(tr("Host source entry must not be empty")); + vrc = VERR_INVALID_PARAMETER; + break; + } + + if (itSrc->enmType == FsObjType_Directory) + { + fFollowSymlinks = itSrc->fDirCopyFlags & DirectoryCopyFlag_FollowLinks; + } + else + { + fFollowSymlinks = RT_BOOL(itSrc->fFileCopyFlags & FileCopyFlag_FollowLinks); + } + + LogFlowFunc(("strSrc=%s (path style is %s), strDst=%s\n", + strSrc.c_str(), GuestBase::pathStyleToStr(itSrc->enmPathStyle), strDst.c_str())); + + RTFSOBJINFO srcFsObjInfo; + vrc = RTPathQueryInfoEx(strSrc.c_str(), &srcFsObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK /* fFlags */); + if (RT_FAILURE(vrc)) + { + strErrorInfo.printf(tr("No such host file/directory: %s"), strSrc.c_str()); + break; + } + + switch (srcFsObjInfo.Attr.fMode & RTFS_TYPE_MASK) + { + case RTFS_TYPE_DIRECTORY: + { + if (itSrc->enmType != FsObjType_Directory) + { + strErrorInfo.printf(tr("Host source \"%s\" is not a file (is a directory)"), strSrc.c_str()); + vrc = VERR_NOT_A_FILE; + } + break; + } + + case RTFS_TYPE_FILE: + { + if (itSrc->enmType == FsObjType_Directory) + { + strErrorInfo.printf(tr("Host source \"%s\" is not a directory (is a file)"), strSrc.c_str()); + vrc = VERR_NOT_A_DIRECTORY; + } + break; + } + + case RTFS_TYPE_SYMLINK: + { + if (!fFollowSymlinks) + { + strErrorInfo.printf(tr("Host source \"%s\" is a symbolic link"), strSrc.c_str()); + vrc = VERR_IS_A_SYMLINK; + break; + } + + char szPathReal[RTPATH_MAX]; + vrc = RTPathReal(strSrc.c_str(), szPathReal, sizeof(szPathReal)); + if (RT_SUCCESS(vrc)) + { + vrc = RTPathQueryInfoEx(szPathReal, &srcFsObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK); + if (RT_SUCCESS(vrc)) + { + LogRel2(("Guest Control: Host source is a symbolic link \"%s\" -> \"%s\" (%s)\n", + strSrc.c_str(), szPathReal, + GuestBase::fsObjTypeToStr(GuestBase::fileModeToFsObjType(srcFsObjInfo.Attr.fMode)))); + + /* We want to keep the symbolic link name of the source instead of the target pointing to, + * so don't touch the source's name here. */ + itSrc->enmType = GuestBase::fileModeToFsObjType(srcFsObjInfo.Attr.fMode); + } + else + { + strErrorInfo.printf(tr("Querying symbolic link info for host source \"%s\" failed"), strSrc.c_str()); + break; + } + } + else + { + strErrorInfo.printf(tr("Resolving symbolic link for host source \"%s\" failed"), strSrc.c_str()); + break; + } + break; + } + + default: + LogRel2(("Guest Control: Warning: Unknown host file system type %#x for source \"%s\", skipping\n", + srcFsObjInfo.Attr.fMode & RTFS_TYPE_MASK, strSrc.c_str())); + break; + } + + if (RT_FAILURE(vrc)) + break; + + FsList *pFsList = NULL; + try + { + pFsList = new FsList(*this); + vrc = pFsList->Init(strSrc, strDst, *itSrc); + if (RT_SUCCESS(vrc)) + { + switch (itSrc->enmType) + { + case FsObjType_Directory: + { + char szPathReal[RTPATH_MAX]; + RTDIRENTRYEX DirEntry; + vrc = pFsList->AddDirFromHost(strSrc /* strPath */, "" /* strSubDir */, + szPathReal, sizeof(szPathReal), &DirEntry); + break; + } + + case FsObjType_File: + /* The file name is already part of the actual list's source root (strSrc). */ + break; + + case FsObjType_Symlink: + AssertFailed(); /* Should never get here, as we do the resolving above. */ + break; + + default: + LogRel2(("Guest Control: Warning: Unknown source type %#x for host source \"%s\", skipping\n", + itSrc->enmType, strSrc.c_str())); + break; + } + } + + if (RT_FAILURE(vrc)) + { + delete pFsList; + strErrorInfo.printf(tr("Error adding host source \"%s\" to list: %Rrc"), + strSrc.c_str(), vrc); + break; + } +#ifdef DEBUG + pFsList->DumpToLog(); +#endif + mVecLists.push_back(pFsList); + } + catch (std::bad_alloc &) + { + vrc = VERR_NO_MEMORY; + break; + } + + AssertPtr(pFsList); + cOperations += (ULONG)pFsList->mVecEntries.size(); + + itSrc++; + } + } + + if (RT_SUCCESS(vrc)) + { + /* When there are no entries in the first source list, this means the source only contains a single file + * (see \a mSrcRootAbs of FsList). So use \a mSrcRootAbs directly. */ + Utf8Str const &strFirstOp = mVecLists[0]->mVecEntries.size() > 0 + ? mVecLists[0]->mVecEntries[0]->strPath : mVecLists[0]->mSrcRootAbs; + + /* Now that we know how many objects we're handling, tweak the progress description so that it + * reflects more accurately what the progress is actually doing. */ + if (cOperations > 1) + { + mDesc.printf(tr("Copying \"%s\" [and %zu %s] from host to \"%s\" on the guest ..."), + strFirstOp.c_str(), cOperations - 1, cOperations > 2 ? tr("others") : tr("other"), mDest.c_str()); + } + else + mDesc.printf(tr("Copying \"%s\" from host to \"%s\" on the guest ..."), strFirstOp.c_str(), mDest.c_str()); + + hrc = pProgress->init(static_cast<IGuestSession*>(mSession), Bstr(mDesc).raw(), + TRUE /* aCancelable */, cOperations + 1/* Number of operations */, + Bstr(strFirstOp).raw()); + } + else /* On error we go with an "empty" progress object when will be used for error handling. */ + hrc = pProgress->init(static_cast<IGuestSession*>(mSession), Bstr(mDesc).raw(), + TRUE /* aCancelable */, 1 /* cOperations */, Bstr(mDesc).raw()); + + if (FAILED(hrc)) /* Progress object creation failed -- we're doomed. */ + return hrc; + + if (RT_FAILURE(vrc)) + { + if (strErrorInfo.isEmpty()) + strErrorInfo.printf(tr("Failed with %Rrc"), vrc); + setProgressErrorMsg(VBOX_E_IPRT_ERROR, strErrorInfo); + } + + LogFlowFunc(("Returning %Rhrc (%Rrc)\n", hrc, vrc)); + return hrc; +} + +/** @copydoc GuestSessionTask::Run */ +int GuestSessionTaskCopyTo::Run(void) +{ + LogFlowThisFuncEnter(); + + AutoCaller autoCaller(mSession); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + int vrc = VINF_SUCCESS; + + FsLists::const_iterator itList = mVecLists.begin(); + while (itList != mVecLists.end()) + { + FsList *pList = *itList; + AssertPtr(pList); + + LogFlowFunc(("List: srcRootAbs=%s, dstRootAbs=%s\n", pList->mSrcRootAbs.c_str(), pList->mDstRootAbs.c_str())); + + Utf8Str strSrcRootAbs = pList->mSrcRootAbs; + Utf8Str strDstRootAbs = pList->mDstRootAbs; + + vrc = GuestPath::BuildDestinationPath(strSrcRootAbs, PATH_STYLE_NATIVE /* Source */, + strDstRootAbs, mSession->i_getGuestPathStyle() /* Dest */); + if (RT_FAILURE(vrc)) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Building guest destination root path \"%s\" failed: %Rrc"), + strDstRootAbs.c_str(), vrc)); + break; + } + + bool fCopyIntoExisting; + bool fFollowSymlinks; + + if (pList->mSourceSpec.enmType == FsObjType_Directory) + { + fCopyIntoExisting = RT_BOOL(pList->mSourceSpec.fDirCopyFlags & DirectoryCopyFlag_CopyIntoExisting); + fFollowSymlinks = RT_BOOL(pList->mSourceSpec.fDirCopyFlags & DirectoryCopyFlag_FollowLinks); + } + else if (pList->mSourceSpec.enmType == FsObjType_File) + { + fCopyIntoExisting = !RT_BOOL(pList->mSourceSpec.fFileCopyFlags & FileCopyFlag_NoReplace); + fFollowSymlinks = RT_BOOL(pList->mSourceSpec.fFileCopyFlags & FileCopyFlag_FollowLinks); + } + else + AssertFailedBreakStmt(vrc = VERR_NOT_IMPLEMENTED); + + uint32_t const fDirMode = 0700; /** @todo Play safe by default; implement ACLs. */ + + bool fDstExists = true; + + GuestFsObjData dstObjData; + int vrcGuest; + vrc = mSession->i_fsQueryInfo(strDstRootAbs, fFollowSymlinks, dstObjData, &vrcGuest); + if (RT_FAILURE(vrc)) + { + if (vrc == VERR_GSTCTL_GUEST_ERROR) + { + switch (vrcGuest) + { + case VERR_PATH_NOT_FOUND: + RT_FALL_THROUGH(); + case VERR_FILE_NOT_FOUND: + { + fDstExists = false; + vrc = VINF_SUCCESS; + break; + } + default: + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Querying information on guest for \"%s\" failed: %Rrc"), + strDstRootAbs.c_str(), vrcGuest)); + break; + } + } + else + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Querying information on guest for \"%s\" failed: %Rrc"), + strDstRootAbs.c_str(), vrc)); + break; + } + } + + if (pList->mSourceSpec.enmType == FsObjType_Directory) + { + LogFlowFunc(("Directory: fDirCopyFlags=%#x, fCopyIntoExisting=%RTbool, fFollowSymlinks=%RTbool -> fDstExist=%RTbool (%s)\n", + pList->mSourceSpec.fDirCopyFlags, fCopyIntoExisting, fFollowSymlinks, + fDstExists, GuestBase::fsObjTypeToStr(dstObjData.mType))); + + if (fDstExists) + { + switch (dstObjData.mType) + { + case FsObjType_Directory: + { + if (!fCopyIntoExisting) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Guest root directory \"%s\" already exists"), + strDstRootAbs.c_str())); + vrc = VERR_ALREADY_EXISTS; + } + break; + } + + case FsObjType_File: + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Destination \"%s\" on guest already exists and is a file"), + strDstRootAbs.c_str())); + vrc = VERR_IS_A_FILE; + } + + case FsObjType_Symlink: + /** @todo Resolve symlinks? */ + break; + + default: + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Unknown object type (%#x) on guest for \"%s\""), + dstObjData.mType, strDstRootAbs.c_str())); + vrc = VERR_NOT_SUPPORTED; + break; + } + } + + if (RT_FAILURE(vrc)) + break; + + /* Make sure the destination root directory exists. */ + if (pList->mSourceSpec.fDryRun == false) + { + vrc = directoryCreateOnGuest(strDstRootAbs, fDirMode, DirectoryCreateFlag_None, + fFollowSymlinks, fCopyIntoExisting); + if (RT_FAILURE(vrc)) + break; + } + + /* Walk the entries. */ + FsEntries::const_iterator itEntry = pList->mVecEntries.begin(); + while ( RT_SUCCESS(vrc) + && itEntry != pList->mVecEntries.end()) + { + FsEntry *pEntry = *itEntry; + AssertPtr(pEntry); + + Utf8Str strSrcAbs = strSrcRootAbs; + Utf8Str strDstAbs = strDstRootAbs; + + strSrcAbs += PATH_STYLE_SEP_STR(PATH_STYLE_NATIVE); + strSrcAbs += pEntry->strPath; + + strDstAbs += PATH_STYLE_SEP_STR(mSession->i_getGuestPathStyle()); + strDstAbs += pEntry->strPath; + + /* Clean up the final host source path. */ + vrc = GuestPath::Translate(strSrcAbs, pList->mSourceSpec.enmPathStyle /* Source */, + pList->mSourceSpec.enmPathStyle /* Dest */); + if (RT_FAILURE(vrc)) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Translating host source path\"%s\" failed: %Rrc"), + strSrcAbs.c_str(), vrc)); + break; + } + + /* Translate final guest destination path. */ + vrc = GuestPath::Translate(strDstAbs, + PATH_STYLE_NATIVE /* Source */, mSession->i_getGuestPathStyle() /* Dest */); + if (RT_FAILURE(vrc)) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Translating guest destination path \"%s\" failed: %Rrc"), + strDstAbs.c_str(), vrc)); + break; + } + + mProgress->SetNextOperation(Bstr(strSrcAbs).raw(), 1); + + switch (pEntry->fMode & RTFS_TYPE_MASK) + { + case RTFS_TYPE_DIRECTORY: + { + LogRel2(("Guest Control: Copying directory \"%s\" from host to \"%s\" on guest ...\n", + strSrcAbs.c_str(), strDstAbs.c_str())); + if (!pList->mSourceSpec.fDryRun) + vrc = directoryCreateOnGuest(strDstAbs, fDirMode, DirectoryCreateFlag_None, + fFollowSymlinks, fCopyIntoExisting); + break; + } + + case RTFS_TYPE_FILE: + { + if (!pList->mSourceSpec.fDryRun) + vrc = fileCopyToGuest(strSrcAbs, strDstAbs, pList->mSourceSpec.fFileCopyFlags); + break; + } + + default: + LogRel2(("Guest Control: Warning: Host file system type 0x%x for \"%s\" is not supported, skipping\n", + pEntry->fMode & RTFS_TYPE_MASK, strSrcAbs.c_str())); + break; + } + + if (RT_FAILURE(vrc)) + break; + + ++itEntry; + } + } + else if (pList->mSourceSpec.enmType == FsObjType_File) + { + LogFlowFunc(("File: fFileCopyFlags=%#x, fCopyIntoExisting=%RTbool, fFollowSymlinks=%RTbool -> fDstExist=%RTbool (%s)\n", + pList->mSourceSpec.fFileCopyFlags, fCopyIntoExisting, fFollowSymlinks, + fDstExists, GuestBase::fsObjTypeToStr(dstObjData.mType))); + + if (fDstExists) + { + switch (dstObjData.mType) + { + case FsObjType_Directory: + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Destination \"%s\" on the guest already exists and is a directory"), + strDstRootAbs.c_str())); + vrc = VERR_IS_A_DIRECTORY; + break; + } + + case FsObjType_File: + { + if (!fCopyIntoExisting) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Guest file \"%s\" already exists"), strDstRootAbs.c_str())); + vrc = VERR_ALREADY_EXISTS; + } + break; + } + + default: + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Unsupported guest file system type (%#x) for \"%s\""), + dstObjData.mType, strDstRootAbs.c_str())); + vrc = VERR_NOT_SUPPORTED; + break; + } + } + } + + if (RT_SUCCESS(vrc)) + { + /* Translate the final guest destination file path. */ + vrc = GuestPath::Translate(strDstRootAbs, + PATH_STYLE_NATIVE /* Source */, mSession->i_getGuestPathStyle() /* Dest */); + if (RT_FAILURE(vrc)) + { + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Translating guest destination path \"%s\" failed: %Rrc"), + strDstRootAbs.c_str(), vrc)); + break; + } + + if (!pList->mSourceSpec.fDryRun) + vrc = fileCopyToGuest(strSrcRootAbs, strDstRootAbs, pList->mSourceSpec.fFileCopyFlags); + } + } + else + AssertFailedStmt(vrc = VERR_NOT_SUPPORTED); + + if (RT_FAILURE(vrc)) + break; + + ++itList; + } + + if (RT_SUCCESS(vrc)) + vrc = setProgressSuccess(); + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +GuestSessionTaskUpdateAdditions::GuestSessionTaskUpdateAdditions(GuestSession *pSession, + const Utf8Str &strSource, + const ProcessArguments &aArguments, + uint32_t fFlags) + : GuestSessionTask(pSession) +{ + m_strTaskName = "gctlUpGA"; + + mSource = strSource; + mArguments = aArguments; + mFlags = fFlags; +} + +GuestSessionTaskUpdateAdditions::~GuestSessionTaskUpdateAdditions(void) +{ + +} + +/** + * Adds arguments to existing process arguments. + * Identical / already existing arguments will be filtered out. + * + * @returns VBox status code. + * @param aArgumentsDest Destination to add arguments to. + * @param aArgumentsSource Arguments to add. + */ +int GuestSessionTaskUpdateAdditions::addProcessArguments(ProcessArguments &aArgumentsDest, const ProcessArguments &aArgumentsSource) +{ + try + { + /* Filter out arguments which already are in the destination to + * not end up having them specified twice. Not the fastest method on the + * planet but does the job. */ + ProcessArguments::const_iterator itSource = aArgumentsSource.begin(); + while (itSource != aArgumentsSource.end()) + { + bool fFound = false; + ProcessArguments::iterator itDest = aArgumentsDest.begin(); + while (itDest != aArgumentsDest.end()) + { + if ((*itDest).equalsIgnoreCase((*itSource))) + { + fFound = true; + break; + } + ++itDest; + } + + if (!fFound) + aArgumentsDest.push_back((*itSource)); + + ++itSource; + } + } + catch(std::bad_alloc &) + { + return VERR_NO_MEMORY; + } + + return VINF_SUCCESS; +} + +/** + * Helper function to copy a file from a VISO to the guest. + * + * @returns VBox status code. + * @param pSession Guest session to use. + * @param hVfsIso VISO handle to use. + * @param strFileSrc Source file path on VISO to copy. + * @param strFileDst Destination file path on guest. + * @param fOptional When set to \c true, the file is optional, i.e. can be skipped + * when not found, \c false if not. + */ +int GuestSessionTaskUpdateAdditions::copyFileToGuest(GuestSession *pSession, RTVFS hVfsIso, + Utf8Str const &strFileSrc, const Utf8Str &strFileDst, bool fOptional) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertReturn(hVfsIso != NIL_RTVFS, VERR_INVALID_POINTER); + + RTVFSFILE hVfsFile = NIL_RTVFSFILE; + int vrc = RTVfsFileOpen(hVfsIso, strFileSrc.c_str(), RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE, &hVfsFile); + if (RT_SUCCESS(vrc)) + { + uint64_t cbSrcSize = 0; + vrc = RTVfsFileQuerySize(hVfsFile, &cbSrcSize); + if (RT_SUCCESS(vrc)) + { + LogRel(("Copying Guest Additions installer file \"%s\" to \"%s\" on guest ...\n", + strFileSrc.c_str(), strFileDst.c_str())); + + GuestFileOpenInfo dstOpenInfo; + dstOpenInfo.mFilename = strFileDst; + dstOpenInfo.mOpenAction = FileOpenAction_CreateOrReplace; + dstOpenInfo.mAccessMode = FileAccessMode_WriteOnly; + dstOpenInfo.mSharingMode = FileSharingMode_All; /** @todo Use _Read when implemented. */ + + ComObjPtr<GuestFile> dstFile; + int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS; + vrc = mSession->i_fileOpen(dstOpenInfo, dstFile, &vrcGuest); + if (RT_FAILURE(vrc)) + { + switch (vrc) + { + case VERR_GSTCTL_GUEST_ERROR: + setProgressErrorMsg(VBOX_E_IPRT_ERROR, GuestFile::i_guestErrorToString(vrcGuest, strFileDst.c_str())); + break; + + default: + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Guest file \"%s\" could not be opened: %Rrc"), + strFileDst.c_str(), vrc)); + break; + } + } + else + { + vrc = fileCopyToGuestInner(strFileSrc, hVfsFile, strFileDst, dstFile, FileCopyFlag_None, 0 /*offCopy*/, cbSrcSize); + + int vrc2 = fileClose(dstFile); + if (RT_SUCCESS(vrc)) + vrc = vrc2; + } + } + + RTVfsFileRelease(hVfsFile); + } + else if (fOptional) + vrc = VINF_SUCCESS; + + return vrc; +} + +/** + * Helper function to run (start) a file on the guest. + * + * @returns VBox status code. + * @param pSession Guest session to use. + * @param procInfo Guest process startup info to use. + */ +int GuestSessionTaskUpdateAdditions::runFileOnGuest(GuestSession *pSession, GuestProcessStartupInfo &procInfo) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + + LogRel(("Running %s ...\n", procInfo.mName.c_str())); + + GuestProcessTool procTool; + int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS; + int vrc = procTool.init(pSession, procInfo, false /* Async */, &vrcGuest); + if (RT_SUCCESS(vrc)) + { + if (RT_SUCCESS(vrcGuest)) + vrc = procTool.wait(GUESTPROCESSTOOL_WAIT_FLAG_NONE, &vrcGuest); + if (RT_SUCCESS(vrc)) + vrc = procTool.getTerminationStatus(); + } + + if (RT_FAILURE(vrc)) + { + switch (vrc) + { + case VERR_GSTCTL_PROCESS_EXIT_CODE: + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Running update file \"%s\" on guest failed: %Rrc"), + procInfo.mExecutable.c_str(), procTool.getRc())); + break; + + case VERR_GSTCTL_GUEST_ERROR: + setProgressErrorMsg(VBOX_E_IPRT_ERROR, tr("Running update file on guest failed"), + GuestErrorInfo(GuestErrorInfo::Type_Process, vrcGuest, procInfo.mExecutable.c_str())); + break; + + case VERR_INVALID_STATE: /** @todo Special guest control rc needed! */ + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Update file \"%s\" reported invalid running state"), + procInfo.mExecutable.c_str())); + break; + + default: + setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Error while running update file \"%s\" on guest: %Rrc"), + procInfo.mExecutable.c_str(), vrc)); + break; + } + } + + return vrc; +} + +/** + * Helper function which waits until Guest Additions services started. + * + * @returns 0 on success or VERR_TIMEOUT if guest services were not + * started on time. + * @param pGuest Guest interface to use. + */ +int GuestSessionTaskUpdateAdditions::waitForGuestSession(ComObjPtr<Guest> pGuest) +{ + int vrc = VERR_GSTCTL_GUEST_ERROR; + int rc = VERR_TIMEOUT; + + uint64_t tsStart = RTTimeSystemMilliTS(); + const uint64_t timeoutMs = 600 * 1000; + + AssertReturn(!pGuest.isNull(), VERR_TIMEOUT); + + do + { + ComObjPtr<GuestSession> pSession; + GuestCredentials guestCreds; + GuestSessionStartupInfo startupInfo; + + startupInfo.mName = "Guest Additions connection checker"; + startupInfo.mOpenTimeoutMS = 100; + + vrc = pGuest->i_sessionCreate(startupInfo, guestCreds, pSession); + if (RT_SUCCESS(vrc)) + { + int vrcGuest = VERR_GSTCTL_GUEST_ERROR; /* unused. */ + + Assert(!pSession.isNull()); + + vrc = pSession->i_startSession(&vrcGuest); + if (RT_SUCCESS(vrc)) + { + GuestSessionWaitResult_T enmWaitResult = GuestSessionWaitResult_None; + int rcGuest = 0; /* unused. */ + + /* Wait for VBoxService to start. */ + vrc = pSession->i_waitFor(GuestSessionWaitForFlag_Start, 100 /* timeout, ms */, enmWaitResult, &rcGuest); + if (RT_SUCCESS(vrc)) + { + vrc = pSession->Close(); + rc = 0; + break; + } + } + + vrc = pSession->Close(); + } + + RTThreadSleep(100); + + } while ((RTTimeSystemMilliTS() - tsStart) < timeoutMs); + + return rc; +} + +/** @copydoc GuestSessionTask::Run */ +int GuestSessionTaskUpdateAdditions::Run(void) +{ + LogFlowThisFuncEnter(); + + ComObjPtr<GuestSession> pSession = mSession; + Assert(!pSession.isNull()); + + AutoCaller autoCaller(pSession); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + int vrc = setProgress(10); + if (RT_FAILURE(vrc)) + return vrc; + + HRESULT hrc = S_OK; + + LogRel(("Automatic update of Guest Additions started, using \"%s\"\n", mSource.c_str())); + + ComObjPtr<Guest> pGuest(mSession->i_getParent()); +#if 0 + /* + * Wait for the guest being ready within 30 seconds. + */ + AdditionsRunLevelType_T addsRunLevel; + uint64_t tsStart = RTTimeSystemMilliTS(); + while ( SUCCEEDED(hrc = pGuest->COMGETTER(AdditionsRunLevel)(&addsRunLevel)) + && ( addsRunLevel != AdditionsRunLevelType_Userland + && addsRunLevel != AdditionsRunLevelType_Desktop)) + { + if ((RTTimeSystemMilliTS() - tsStart) > 30 * 1000) + { + vrc = VERR_TIMEOUT; + break; + } + + RTThreadSleep(100); /* Wait a bit. */ + } + + if (FAILED(hrc)) vrc = VERR_TIMEOUT; + if (vrc == VERR_TIMEOUT) + hrc = setProgressErrorMsg(VBOX_E_NOT_SUPPORTED, + Utf8StrFmt(tr("Guest Additions were not ready within time, giving up"))); +#else + /* + * For use with the GUI we don't want to wait, just return so that the manual .ISO mounting + * can continue. + */ + AdditionsRunLevelType_T addsRunLevel; + if ( FAILED(hrc = pGuest->COMGETTER(AdditionsRunLevel)(&addsRunLevel)) + || ( addsRunLevel != AdditionsRunLevelType_Userland + && addsRunLevel != AdditionsRunLevelType_Desktop)) + { + if (addsRunLevel == AdditionsRunLevelType_System) + hrc = setProgressErrorMsg(VBOX_E_NOT_SUPPORTED, + Utf8StrFmt(tr("Guest Additions are installed but not fully loaded yet, aborting automatic update"))); + else + hrc = setProgressErrorMsg(VBOX_E_NOT_SUPPORTED, + Utf8StrFmt(tr("Guest Additions not installed or ready, aborting automatic update"))); + vrc = VERR_NOT_SUPPORTED; + } +#endif + + if (RT_SUCCESS(vrc)) + { + /* + * Determine if we are able to update automatically. This only works + * if there are recent Guest Additions installed already. + */ + Utf8Str strAddsVer; + vrc = getGuestProperty(pGuest, "/VirtualBox/GuestAdd/Version", strAddsVer); + if ( RT_SUCCESS(vrc) + && RTStrVersionCompare(strAddsVer.c_str(), "4.1") < 0) + { + hrc = setProgressErrorMsg(VBOX_E_NOT_SUPPORTED, + Utf8StrFmt(tr("Guest has too old Guest Additions (%s) installed for automatic updating, please update manually"), + strAddsVer.c_str())); + vrc = VERR_NOT_SUPPORTED; + } + } + + Utf8Str strOSVer; + eOSType osType = eOSType_Unknown; + if (RT_SUCCESS(vrc)) + { + /* + * Determine guest OS type and the required installer image. + */ + Utf8Str strOSType; + vrc = getGuestProperty(pGuest, "/VirtualBox/GuestInfo/OS/Product", strOSType); + if (RT_SUCCESS(vrc)) + { + if ( strOSType.contains("Microsoft", Utf8Str::CaseInsensitive) + || strOSType.contains("Windows", Utf8Str::CaseInsensitive)) + { + osType = eOSType_Windows; + + /* + * Determine guest OS version. + */ + vrc = getGuestProperty(pGuest, "/VirtualBox/GuestInfo/OS/Release", strOSVer); + if (RT_FAILURE(vrc)) + { + hrc = setProgressErrorMsg(VBOX_E_NOT_SUPPORTED, + Utf8StrFmt(tr("Unable to detected guest OS version, please update manually"))); + vrc = VERR_NOT_SUPPORTED; + } + + /* Because Windows 2000 + XP and is bitching with WHQL popups even if we have signed drivers we + * can't do automated updates here. */ + /* Windows XP 64-bit (5.2) is a Windows 2003 Server actually, so skip this here. */ + if ( RT_SUCCESS(vrc) + && RTStrVersionCompare(strOSVer.c_str(), "5.0") >= 0) + { + if ( strOSVer.startsWith("5.0") /* Exclude the build number. */ + || strOSVer.startsWith("5.1") /* Exclude the build number. */) + { + /* If we don't have AdditionsUpdateFlag_WaitForUpdateStartOnly set we can't continue + * because the Windows Guest Additions installer will fail because of WHQL popups. If the + * flag is set this update routine ends successfully as soon as the installer was started + * (and the user has to deal with it in the guest). */ + if (!(mFlags & AdditionsUpdateFlag_WaitForUpdateStartOnly)) + { + hrc = setProgressErrorMsg(VBOX_E_NOT_SUPPORTED, + Utf8StrFmt(tr("Windows 2000 and XP are not supported for automatic updating due to WHQL interaction, please update manually"))); + vrc = VERR_NOT_SUPPORTED; + } + } + } + else + { + hrc = setProgressErrorMsg(VBOX_E_NOT_SUPPORTED, + Utf8StrFmt(tr("%s (%s) not supported for automatic updating, please update manually"), + strOSType.c_str(), strOSVer.c_str())); + vrc = VERR_NOT_SUPPORTED; + } + } + else if (strOSType.contains("Solaris", Utf8Str::CaseInsensitive)) + { + osType = eOSType_Solaris; + } + else /* Everything else hopefully means Linux :-). */ + osType = eOSType_Linux; + + if ( RT_SUCCESS(vrc) + && ( osType != eOSType_Windows + && osType != eOSType_Linux)) + /** @todo Support Solaris. */ + { + hrc = setProgressErrorMsg(VBOX_E_NOT_SUPPORTED, + Utf8StrFmt(tr("Detected guest OS (%s) does not support automatic Guest Additions updating, please update manually"), + strOSType.c_str())); + vrc = VERR_NOT_SUPPORTED; + } + } + } + + if (RT_SUCCESS(vrc)) + { + /* + * Try to open the .ISO file to extract all needed files. + */ + RTVFSFILE hVfsFileIso; + vrc = RTVfsFileOpenNormal(mSource.c_str(), RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE, &hVfsFileIso); + if (RT_FAILURE(vrc)) + { + hrc = setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Unable to open Guest Additions .ISO file \"%s\": %Rrc"), + mSource.c_str(), vrc)); + } + else + { + RTVFS hVfsIso; + vrc = RTFsIso9660VolOpen(hVfsFileIso, 0 /*fFlags*/, &hVfsIso, NULL); + if (RT_FAILURE(vrc)) + { + hrc = setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Unable to open file as ISO 9660 file system volume: %Rrc"), vrc)); + } + else + { + Utf8Str strUpdateDir; + + vrc = setProgress(5); + if (RT_SUCCESS(vrc)) + { + /* Try getting the installed Guest Additions version to know whether we + * can install our temporary Guest Addition data into the original installation + * directory. + * + * Because versions prior to 4.2 had bugs wrt spaces in paths we have to choose + * a different location then. + */ + bool fUseInstallDir = false; + + Utf8Str strAddsVer; + vrc = getGuestProperty(pGuest, "/VirtualBox/GuestAdd/Version", strAddsVer); + if ( RT_SUCCESS(vrc) + && RTStrVersionCompare(strAddsVer.c_str(), "4.2r80329") > 0) + { + fUseInstallDir = true; + } + + if (fUseInstallDir) + { + vrc = getGuestProperty(pGuest, "/VirtualBox/GuestAdd/InstallDir", strUpdateDir); + if (RT_SUCCESS(vrc)) + { + if (strUpdateDir.isNotEmpty()) + { + if (osType == eOSType_Windows) + { + strUpdateDir.findReplace('/', '\\'); + strUpdateDir.append("\\Update\\"); + } + else + strUpdateDir.append("/update/"); + } + /* else Older Guest Additions might not handle this property correctly. */ + } + /* Ditto. */ + } + + /** @todo Set fallback installation directory. Make this a *lot* smarter. Later. */ + if (strUpdateDir.isEmpty()) + { + if (osType == eOSType_Windows) + strUpdateDir = "C:\\Temp\\"; + else + strUpdateDir = "/tmp/"; + } + } + + /* Create the installation directory. */ + int vrcGuest = VERR_IPE_UNINITIALIZED_STATUS; + if (RT_SUCCESS(vrc)) + { + LogRel(("Guest Additions update directory is: %s\n", strUpdateDir.c_str())); + + vrc = pSession->i_directoryCreate(strUpdateDir, 755 /* Mode */, DirectoryCreateFlag_Parents, &vrcGuest); + if (RT_FAILURE(vrc)) + { + switch (vrc) + { + case VERR_GSTCTL_GUEST_ERROR: + hrc = setProgressErrorMsg(VBOX_E_IPRT_ERROR, tr("Creating installation directory on guest failed"), + GuestErrorInfo(GuestErrorInfo::Type_Directory, vrcGuest, strUpdateDir.c_str())); + break; + + default: + hrc = setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Creating installation directory \"%s\" on guest failed: %Rrc"), + strUpdateDir.c_str(), vrc)); + break; + } + } + } + + if (RT_SUCCESS(vrc)) + vrc = setProgress(10); + + if (RT_SUCCESS(vrc)) + { + /* Prepare the file(s) we want to copy over to the guest and + * (maybe) want to run. */ + switch (osType) + { + case eOSType_Windows: + { + /* Do we need to install our certificates? We do this for W2K and up. */ + bool fInstallCert = false; + + /* Only Windows 2000 and up need certificates to be installed. */ + if (RTStrVersionCompare(strOSVer.c_str(), "5.0") >= 0) + { + fInstallCert = true; + LogRel(("Certificates for auto updating WHQL drivers will be installed\n")); + } + else + LogRel(("Skipping installation of certificates for WHQL drivers\n")); + + if (fInstallCert) + { + static struct { const char *pszDst, *pszIso; } const s_aCertFiles[] = + { + { "vbox.cer", "/CERT/VBOX.CER" }, + { "vbox-sha1.cer", "/CERT/VBOX-SHA1.CER" }, + { "vbox-sha256.cer", "/CERT/VBOX-SHA256.CER" }, + { "vbox-sha256-r3.cer", "/CERT/VBOX-SHA256-R3.CER" }, + { "oracle-vbox.cer", "/CERT/ORACLE-VBOX.CER" }, + }; + uint32_t fCopyCertUtil = ISOFILE_FLAG_COPY_FROM_ISO; + for (uint32_t i = 0; i < RT_ELEMENTS(s_aCertFiles); i++) + { + /* Skip if not present on the ISO. */ + RTFSOBJINFO ObjInfo; + vrc = RTVfsQueryPathInfo(hVfsIso, s_aCertFiles[i].pszIso, &ObjInfo, RTFSOBJATTRADD_NOTHING, + RTPATH_F_ON_LINK); + if (RT_FAILURE(vrc)) + continue; + + /* Copy the certificate certificate. */ + Utf8Str const strDstCert(strUpdateDir + s_aCertFiles[i].pszDst); + mFiles.push_back(ISOFile(s_aCertFiles[i].pszIso, + strDstCert, + ISOFILE_FLAG_COPY_FROM_ISO | ISOFILE_FLAG_OPTIONAL)); + + /* Out certificate installation utility. */ + /* First pass: Copy over the file (first time only) + execute it to remove any + * existing VBox certificates. */ + GuestProcessStartupInfo siCertUtilRem; + siCertUtilRem.mName = "VirtualBox Certificate Utility, removing old VirtualBox certificates"; + /* The argv[0] should contain full path to the executable module */ + siCertUtilRem.mArguments.push_back(strUpdateDir + "VBoxCertUtil.exe"); + siCertUtilRem.mArguments.push_back(Utf8Str("remove-trusted-publisher")); + siCertUtilRem.mArguments.push_back(Utf8Str("--root")); /* Add root certificate as well. */ + siCertUtilRem.mArguments.push_back(strDstCert); + siCertUtilRem.mArguments.push_back(strDstCert); + mFiles.push_back(ISOFile("CERT/VBOXCERTUTIL.EXE", + strUpdateDir + "VBoxCertUtil.exe", + fCopyCertUtil | ISOFILE_FLAG_EXECUTE | ISOFILE_FLAG_OPTIONAL, + siCertUtilRem)); + fCopyCertUtil = 0; + /* Second pass: Only execute (but don't copy) again, this time installng the + * recent certificates just copied over. */ + GuestProcessStartupInfo siCertUtilAdd; + siCertUtilAdd.mName = "VirtualBox Certificate Utility, installing VirtualBox certificates"; + /* The argv[0] should contain full path to the executable module */ + siCertUtilAdd.mArguments.push_back(strUpdateDir + "VBoxCertUtil.exe"); + siCertUtilAdd.mArguments.push_back(Utf8Str("add-trusted-publisher")); + siCertUtilAdd.mArguments.push_back(Utf8Str("--root")); /* Add root certificate as well. */ + siCertUtilAdd.mArguments.push_back(strDstCert); + siCertUtilAdd.mArguments.push_back(strDstCert); + mFiles.push_back(ISOFile("CERT/VBOXCERTUTIL.EXE", + strUpdateDir + "VBoxCertUtil.exe", + ISOFILE_FLAG_EXECUTE | ISOFILE_FLAG_OPTIONAL, + siCertUtilAdd)); + } + } + /* The installers in different flavors, as we don't know (and can't assume) + * the guest's bitness. */ + mFiles.push_back(ISOFile("VBOXWINDOWSADDITIONS-X86.EXE", + strUpdateDir + "VBoxWindowsAdditions-x86.exe", + ISOFILE_FLAG_COPY_FROM_ISO)); + mFiles.push_back(ISOFile("VBOXWINDOWSADDITIONS-AMD64.EXE", + strUpdateDir + "VBoxWindowsAdditions-amd64.exe", + ISOFILE_FLAG_COPY_FROM_ISO)); + /* The stub loader which decides which flavor to run. */ + GuestProcessStartupInfo siInstaller; + siInstaller.mName = "VirtualBox Windows Guest Additions Installer"; + /* Set a running timeout of 5 minutes -- the Windows Guest Additions + * setup can take quite a while, so be on the safe side. */ + siInstaller.mTimeoutMS = 5 * 60 * 1000; + + /* The argv[0] should contain full path to the executable module */ + siInstaller.mArguments.push_back(strUpdateDir + "VBoxWindowsAdditions.exe"); + siInstaller.mArguments.push_back(Utf8Str("/S")); /* We want to install in silent mode. */ + siInstaller.mArguments.push_back(Utf8Str("/l")); /* ... and logging enabled. */ + /* Don't quit VBoxService during upgrade because it still is used for this + * piece of code we're in right now (that is, here!) ... */ + siInstaller.mArguments.push_back(Utf8Str("/no_vboxservice_exit")); + /* Tell the installer to report its current installation status + * using a running VBoxTray instance via balloon messages in the + * Windows taskbar. */ + siInstaller.mArguments.push_back(Utf8Str("/post_installstatus")); + /* Add optional installer command line arguments from the API to the + * installer's startup info. */ + vrc = addProcessArguments(siInstaller.mArguments, mArguments); + AssertRC(vrc); + /* If the caller does not want to wait for out guest update process to end, + * complete the progress object now so that the caller can do other work. */ + if (mFlags & AdditionsUpdateFlag_WaitForUpdateStartOnly) + siInstaller.mFlags |= ProcessCreateFlag_WaitForProcessStartOnly; + mFiles.push_back(ISOFile("VBOXWINDOWSADDITIONS.EXE", + strUpdateDir + "VBoxWindowsAdditions.exe", + ISOFILE_FLAG_COPY_FROM_ISO | ISOFILE_FLAG_EXECUTE, siInstaller)); + break; + } + case eOSType_Linux: + { + /* Copy over the installer to the guest but don't execute it. + * Execution will be done by the shell instead. */ + mFiles.push_back(ISOFile("VBOXLINUXADDITIONS.RUN", + strUpdateDir + "VBoxLinuxAdditions.run", ISOFILE_FLAG_COPY_FROM_ISO)); + + GuestProcessStartupInfo siInstaller; + siInstaller.mName = "VirtualBox Linux Guest Additions Installer"; + /* Set a running timeout of 5 minutes -- compiling modules and stuff for the Linux Guest Additions + * setup can take quite a while, so be on the safe side. */ + siInstaller.mTimeoutMS = 5 * 60 * 1000; + /* The argv[0] should contain full path to the shell we're using to execute the installer. */ + siInstaller.mArguments.push_back("/bin/sh"); + /* Now add the stuff we need in order to execute the installer. */ + siInstaller.mArguments.push_back(strUpdateDir + "VBoxLinuxAdditions.run"); + /* Make sure to add "--nox11" to the makeself wrapper in order to not getting any blocking xterm + * window spawned when doing any unattended Linux GA installations. */ + siInstaller.mArguments.push_back("--nox11"); + siInstaller.mArguments.push_back("--"); + /* Force the upgrade. Needed in order to skip the confirmation dialog about warning to upgrade. */ + siInstaller.mArguments.push_back("--force"); /** @todo We might want a dedicated "--silent" switch here. */ + /* If the caller does not want to wait for out guest update process to end, + * complete the progress object now so that the caller can do other work. */ + if (mFlags & AdditionsUpdateFlag_WaitForUpdateStartOnly) + siInstaller.mFlags |= ProcessCreateFlag_WaitForProcessStartOnly; + mFiles.push_back(ISOFile("/bin/sh" /* Source */, "/bin/sh" /* Dest */, + ISOFILE_FLAG_EXECUTE, siInstaller)); + break; + } + case eOSType_Solaris: + /** @todo Add Solaris support. */ + break; + default: + AssertReleaseMsgFailed(("Unsupported guest type: %d\n", osType)); + break; + } + } + + if (RT_SUCCESS(vrc)) + { + /* We want to spend 40% total for all copying operations. So roughly + * calculate the specific percentage step of each copied file. */ + uint8_t uOffset = 20; /* Start at 20%. */ + uint8_t uStep = 40 / (uint8_t)mFiles.size(); Assert(mFiles.size() <= 10); + + LogRel(("Copying over Guest Additions update files to the guest ...\n")); + + std::vector<ISOFile>::const_iterator itFiles = mFiles.begin(); + while (itFiles != mFiles.end()) + { + if (itFiles->fFlags & ISOFILE_FLAG_COPY_FROM_ISO) + { + bool fOptional = false; + if (itFiles->fFlags & ISOFILE_FLAG_OPTIONAL) + fOptional = true; + vrc = copyFileToGuest(pSession, hVfsIso, itFiles->strSource, itFiles->strDest, fOptional); + if (RT_FAILURE(vrc)) + { + hrc = setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Error while copying file \"%s\" to \"%s\" on the guest: %Rrc"), + itFiles->strSource.c_str(), itFiles->strDest.c_str(), vrc)); + break; + } + } + + vrc = setProgress(uOffset); + if (RT_FAILURE(vrc)) + break; + uOffset += uStep; + + ++itFiles; + } + } + + /* Done copying, close .ISO file. */ + RTVfsRelease(hVfsIso); + + if (RT_SUCCESS(vrc)) + { + /* We want to spend 35% total for all copying operations. So roughly + * calculate the specific percentage step of each copied file. */ + uint8_t uOffset = 60; /* Start at 60%. */ + uint8_t uStep = 35 / (uint8_t)mFiles.size(); Assert(mFiles.size() <= 10); + + LogRel(("Executing Guest Additions update files ...\n")); + + std::vector<ISOFile>::iterator itFiles = mFiles.begin(); + while (itFiles != mFiles.end()) + { + if (itFiles->fFlags & ISOFILE_FLAG_EXECUTE) + { + vrc = runFileOnGuest(pSession, itFiles->mProcInfo); + if (RT_FAILURE(vrc)) + break; + } + + vrc = setProgress(uOffset); + if (RT_FAILURE(vrc)) + break; + uOffset += uStep; + + ++itFiles; + } + } + + if (RT_SUCCESS(vrc)) + { + /* Linux Guest Additions will restart VBoxService during installation process. + * In this case, connection to the guest will be temporary lost until new + * kernel modules will be rebuilt, loaded and new VBoxService restarted. + * Handle this case here: check if old connection was terminated and + * new one has started. */ + if (osType == eOSType_Linux) + { + if (pSession->i_isTerminated()) + { + LogRel(("Old guest session has terminated, waiting updated guest services to start\n")); + + /* Wait for VBoxService to restart. */ + vrc = waitForGuestSession(pSession->i_getParent()); + if (RT_FAILURE(vrc)) + hrc = setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Automatic update of Guest Additions has failed: " + "guest services were not restarted, please reinstall Guest Additions"))); + } + else + { + vrc = VERR_TRY_AGAIN; + hrc = setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Old guest session is still active, guest services were not restarted " + "after installation, please reinstall Guest Additions"))); + } + } + + if (RT_SUCCESS(vrc)) + { + LogRel(("Automatic update of Guest Additions succeeded\n")); + hrc = setProgressSuccess(); + } + } + } + + RTVfsFileRelease(hVfsFileIso); + } + } + + if (RT_FAILURE(vrc)) + { + if (vrc == VERR_CANCELLED) + { + LogRel(("Automatic update of Guest Additions was canceled\n")); + + hrc = setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Installation was canceled"))); + } + else if (vrc == VERR_TIMEOUT) + { + LogRel(("Automatic update of Guest Additions has timed out\n")); + + hrc = setProgressErrorMsg(VBOX_E_IPRT_ERROR, + Utf8StrFmt(tr("Installation has timed out"))); + } + else + { + Utf8Str strError = Utf8StrFmt("No further error information available (%Rrc)", vrc); + if (!mProgress.isNull()) /* Progress object is optional. */ + { +#ifdef VBOX_STRICT + /* If we forgot to set the progress object accordingly, let us know. */ + LONG rcProgress; + AssertMsg( SUCCEEDED(mProgress->COMGETTER(ResultCode(&rcProgress))) + && FAILED(rcProgress), ("Task indicated an error (%Rrc), but progress did not indicate this (%Rhrc)\n", + vrc, rcProgress)); +#endif + com::ProgressErrorInfo errorInfo(mProgress); + if ( errorInfo.isFullAvailable() + || errorInfo.isBasicAvailable()) + { + strError = errorInfo.getText(); + } + } + + LogRel(("Automatic update of Guest Additions failed: %s (%Rhrc)\n", + strError.c_str(), hrc)); + } + + LogRel(("Please install Guest Additions manually\n")); + } + + /** @todo Clean up copied / left over installation files. */ + + LogFlowFuncLeaveRC(vrc); + return vrc; +} |