summaryrefslogtreecommitdiffstats
path: root/src/VBox/Main/src-client/GuestSessionImplTasks.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Main/src-client/GuestSessionImplTasks.cpp')
-rw-r--r--src/VBox/Main/src-client/GuestSessionImplTasks.cpp3363
1 files changed, 3363 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..59fbf06c
--- /dev/null
+++ b/src/VBox/Main/src-client/GuestSessionImplTasks.cpp
@@ -0,0 +1,3363 @@
+/* $Id: GuestSessionImplTasks.cpp $ */
+/** @file
+ * VirtualBox Main - Guest session tasks.
+ */
+
+/*
+ * Copyright (C) 2012-2023 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 hrc = pProgress.createObject();
+ if (FAILED(hrc))
+ return VERR_COM_UNEXPECTED;
+
+ hrc = pProgress->init(static_cast<IGuestSession*>(mSession),
+ Bstr(mDesc).raw(),
+ TRUE /* aCancelable */, cOperations, Bstr(mDesc).raw());
+ if (FAILED(hrc))
+ 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 hrc = pMachine->GetGuestProperty(Bstr(strPath).raw(), strTemp.asOutParam(), &i64Timestamp, strFlags.asOutParam());
+ if (SUCCEEDED(hrc))
+ {
+ 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 hrc = mProgress->SetCurrentOperationProgress(uPercent);
+ if (FAILED(hrc))
+ 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 hrc = mProgress->i_notifyComplete(S_OK);
+ if (FAILED(hrc))
+ return VERR_COM_UNEXPECTED; /** @todo Find a better vrc. */
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Sets the task's progress object to an error using a string message.
+ *
+ * @returns Returns \a hrc for convenience.
+ * @param hrc Progress operation result to set.
+ * @param strMsg Message to set.
+ */
+HRESULT GuestSessionTask::setProgressErrorMsg(HRESULT hrc, const Utf8Str &strMsg)
+{
+ LogFlowFunc(("hrc=%Rhrc, strMsg=%s\n", hrc, strMsg.c_str()));
+
+ if (mProgress.isNull()) /* Progress is optional. */
+ return hrc; /* Return original status. */
+
+ BOOL fCanceled;
+ BOOL fCompleted;
+ if ( SUCCEEDED(mProgress->COMGETTER(Canceled(&fCanceled)))
+ && !fCanceled
+ && SUCCEEDED(mProgress->COMGETTER(Completed(&fCompleted)))
+ && !fCompleted)
+ {
+ HRESULT hrc2 = mProgress->i_notifyComplete(hrc,
+ 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(hrc2))
+ return hrc2;
+ }
+ return hrc; /* Return original status. */
+}
+
+/**
+ * Sets the task's progress object to an error using a string message and a guest error info object.
+ *
+ * @returns Returns \a hrc for convenience.
+ * @param hrc Progress operation result to set.
+ * @param strMsg Message to set.
+ * @param guestErrorInfo Guest error info to use.
+ */
+HRESULT GuestSessionTask::setProgressErrorMsg(HRESULT hrc, const Utf8Str &strMsg, const GuestErrorInfo &guestErrorInfo)
+{
+ return setProgressErrorMsg(hrc, 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\", vrc=%Rrc\n",
+ pszPathReal, vrc));
+ }
+ else
+ {
+ LogRel2(("Guest Control: Unable to resolve symlink for \"%s\", vrc=%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\", vrc=%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.hrc())) return autoCaller.hrc();
+
+ 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.hrc())) return autoCaller.hrc();
+
+ 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.hrc())) return autoCaller.hrc();
+
+ 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.
+ * @param fSilent Whether to set progress into failure state in case of error.
+ */
+int GuestSessionTaskUpdateAdditions::runFileOnGuest(GuestSession *pSession, GuestProcessStartupInfo &procInfo, bool fSilent)
+{
+ 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)
+ && !fSilent)
+ {
+ 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 vrc 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 checks Guest Additions installation status.
+ *
+ * @returns IPRT status code.
+ * @param pSession Guest session to use.
+ * @param osType Guest type.
+ */
+int GuestSessionTaskUpdateAdditions::checkGuestAdditionsStatus(GuestSession *pSession, eOSType osType)
+{
+ int vrc = VINF_SUCCESS;
+ HRESULT hrc;
+
+ if (osType == eOSType_Linux)
+ {
+ const Utf8Str ksStatusScript = Utf8Str("/sbin/rcvboxadd");
+
+ /* Check if Guest Additions kernel modules were loaded. */
+ GuestProcessStartupInfo procInfo;
+ procInfo.mFlags = ProcessCreateFlag_None;
+ procInfo.mExecutable = Utf8Str("/bin/sh");;
+ procInfo.mArguments.push_back(procInfo.mExecutable); /* Set argv0. */
+ procInfo.mArguments.push_back(ksStatusScript);
+ procInfo.mArguments.push_back("status-kernel");
+
+ vrc = runFileOnGuest(pSession, procInfo, true /* fSilent */);
+ if (RT_SUCCESS(vrc))
+ {
+ /* Replace the last argument with corresponding value and check
+ * if Guest Additions user services were started. */
+ procInfo.mArguments.pop_back();
+ procInfo.mArguments.push_back("status-user");
+
+ vrc = runFileOnGuest(pSession, procInfo, true /* fSilent */);
+ if (RT_FAILURE(vrc))
+ hrc = setProgressErrorMsg(VBOX_E_GSTCTL_GUEST_ERROR,
+ Utf8StrFmt(tr("Automatic update of Guest Additions has failed: "
+ "files were installed, but user services were not reloaded automatically. "
+ "Please consider rebooting the guest")));
+ }
+ else
+ hrc = setProgressErrorMsg(VBOX_E_GSTCTL_GUEST_ERROR,
+ Utf8StrFmt(tr("Automatic update of Guest Additions has failed: "
+ "files were installed, but kernel modules were not reloaded automatically. "
+ "Please consider rebooting the guest")));
+ }
+
+ 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.
+ * @param osType Guest type.
+ */
+int GuestSessionTaskUpdateAdditions::waitForGuestSession(ComObjPtr<Guest> pGuest, eOSType osType)
+{
+ int vrc = VERR_GSTCTL_GUEST_ERROR;
+ int vrcRet = VERR_TIMEOUT;
+
+ uint64_t tsStart = RTTimeSystemMilliTS();
+ const uint64_t cMsTimeout = 10 * RT_MS_1MIN;
+
+ 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))
+ {
+ Assert(!pSession.isNull());
+
+ int vrcGuest = VERR_GSTCTL_GUEST_ERROR; /* unused. */
+ vrc = pSession->i_startSession(&vrcGuest);
+ if (RT_SUCCESS(vrc))
+ {
+ /* Wait for VBoxService to start. */
+ GuestSessionWaitResult_T enmWaitResult = GuestSessionWaitResult_None;
+ int vrcGuest2 = VINF_SUCCESS; /* unused. */
+ vrc = pSession->i_waitFor(GuestSessionWaitForFlag_Start, 100 /* timeout, ms */, enmWaitResult, &vrcGuest2);
+ if (RT_SUCCESS(vrc))
+ {
+ /* Make sure Guest Additions were reloaded on the guest side. */
+ vrc = checkGuestAdditionsStatus(pSession, osType);
+ if (RT_SUCCESS(vrc))
+ LogRel(("Guest Additions were successfully reloaded after installation\n"));
+ else
+ LogRel(("Guest Additions were failed to reload after installation, please consider rebooting the guest\n"));
+
+ vrc = pSession->Close();
+ vrcRet = VINF_SUCCESS;
+ break;
+ }
+ }
+
+ vrc = pSession->Close();
+ }
+
+ RTThreadSleep(100);
+
+ } while ((RTTimeSystemMilliTS() - tsStart) < cMsTimeout);
+
+ return vrcRet;
+}
+
+/** @copydoc GuestSessionTask::Run */
+int GuestSessionTaskUpdateAdditions::Run(void)
+{
+ LogFlowThisFuncEnter();
+
+ ComObjPtr<GuestSession> pSession = mSession;
+ Assert(!pSession.isNull());
+
+ AutoCaller autoCaller(pSession);
+ if (FAILED(autoCaller.hrc())) return autoCaller.hrc();
+
+ 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(), osType);
+ 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 manually")));
+ }
+ 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 manually")));
+ }
+ }
+
+ 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;
+}