From f215e02bf85f68d3a6106c2a1f4f7f063f819064 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 11 Apr 2024 10:17:27 +0200 Subject: Adding upstream version 7.0.14-dfsg. Signed-off-by: Daniel Baumann --- .../GuestHost/DragAndDrop/DnDTransferObject.cpp | 706 +++++++++++++++++++++ 1 file changed, 706 insertions(+) create mode 100644 src/VBox/GuestHost/DragAndDrop/DnDTransferObject.cpp (limited to 'src/VBox/GuestHost/DragAndDrop/DnDTransferObject.cpp') diff --git a/src/VBox/GuestHost/DragAndDrop/DnDTransferObject.cpp b/src/VBox/GuestHost/DragAndDrop/DnDTransferObject.cpp new file mode 100644 index 00000000..eadaa395 --- /dev/null +++ b/src/VBox/GuestHost/DragAndDrop/DnDTransferObject.cpp @@ -0,0 +1,706 @@ +/* $Id: DnDTransferObject.cpp $ */ +/** @file + * DnD - Transfer object implemenation for handling creation/reading/writing to files and directories on host or guest side. + */ + +/* + * Copyright (C) 2014-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 . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_GUEST_DND +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + + +/********************************************************************************************************************************* +* Prototypes * +*********************************************************************************************************************************/ +static int dndTransferObjectCloseInternal(PDNDTRANSFEROBJECT pObj); +static int dndTransferObjectQueryInfoInternal(PDNDTRANSFEROBJECT pObj); + + +/** + * Initializes the object, internal version. + * + * @returns VBox status code. + * @param pObj DnD transfer object to initialize. + */ +static int dndTransferObjectInitInternal(PDNDTRANSFEROBJECT pObj) +{ + AssertPtrReturn(pObj, VERR_INVALID_POINTER); + + pObj->enmType = DNDTRANSFEROBJTYPE_UNKNOWN; + pObj->idxDst = 0; + pObj->pszPath = NULL; + + RT_ZERO(pObj->u); + + return VINF_SUCCESS; +} + +/** + * Initializes the object. + * + * @returns VBox status code. + * @param pObj DnD transfer object to initialize. + */ +int DnDTransferObjectInit(PDNDTRANSFEROBJECT pObj) +{ + return dndTransferObjectInitInternal(pObj); +} + +/** + * Initializes the object with an expected object type and file path. + * + * @returns VBox status code. + * @param pObj DnD transfer object to initialize. + * @param enmType Type we expect this object to be. + * @param pcszPathSrcAbs Absolute source (local) path of file this object represents. Can be empty (e.g. for root stuff). + * @param pcszPathDst Relative path of file this object represents at the destination. + * Together with \a pcszPathSrcAbs this represents the complete absolute local path. + */ +int DnDTransferObjectInitEx(PDNDTRANSFEROBJECT pObj, + DNDTRANSFEROBJTYPE enmType, const char *pcszPathSrcAbs, const char *pcszPathDst) +{ + AssertPtrReturn(pObj, VERR_INVALID_POINTER); + AssertReturn(pObj->enmType == DNDTRANSFEROBJTYPE_UNKNOWN, VERR_WRONG_ORDER); /* Already initialized? */ + /* pcszPathSrcAbs can be empty. */ + AssertPtrReturn(pcszPathDst, VERR_INVALID_POINTER); + + int rc = dndTransferObjectInitInternal(pObj); + AssertRCReturn(rc, rc); + + rc = DnDPathValidate(pcszPathDst, false /* Does not need to exist */); + AssertRCReturn(rc, rc); + + char szPath[RTPATH_MAX + 1]; + + /* Save the index (in characters) where the first destination segment starts. */ + if ( pcszPathSrcAbs + && RTStrNLen(pcszPathSrcAbs, RTSTR_MAX)) + { + rc = DnDPathValidate(pcszPathSrcAbs, false /* Does not need to exist */); + if (RT_FAILURE(rc)) + return rc; + + rc = RTStrCopy(szPath, sizeof(szPath), pcszPathSrcAbs); + if (RT_SUCCESS(rc)) + rc = RTPathEnsureTrailingSeparator(szPath, sizeof(szPath)) == 0 ? VERR_BUFFER_OVERFLOW : VINF_SUCCESS; + + /* Save the index (in characters) where the destination part starts. */ + pObj->idxDst = (uint16_t)RTStrNLen(szPath, RTPATH_MAX); + AssertReturn(pObj->idxDst <= RTPATH_MAX, VERR_INVALID_PARAMETER); + } + else + { + szPath[0] = '\0'; /* Init empty string. */ + pObj->idxDst = 0; + } + + if (RT_FAILURE(rc)) + return rc; + + /* Append the destination part. */ + rc = RTPathAppend(szPath, sizeof(szPath), pcszPathDst); + if ( RT_SUCCESS(rc) + && enmType == DNDTRANSFEROBJTYPE_DIRECTORY) + rc = RTPathEnsureTrailingSeparator(szPath, sizeof(szPath)) == 0 ? VERR_BUFFER_OVERFLOW : VINF_SUCCESS; + + if (RT_FAILURE(rc)) + return rc; + + pObj->pszPath = RTStrDup(szPath); + if (!pObj->pszPath) + return VERR_NO_MEMORY; + + /* Convert paths into transport format. */ + rc = DnDPathConvert(pObj->pszPath, strlen(pObj->pszPath), DNDPATHCONVERT_FLAGS_TRANSPORT); + if (RT_FAILURE(rc)) + { + RTStrFree(pObj->pszPath); + pObj->pszPath = NULL; + return rc; + } + + LogFlowFunc(("enmType=%RU32, pcszPathSrcAbs=%s, pcszPathDst=%s -> pszPath=%s\n", + enmType, pcszPathSrcAbs, pcszPathDst, pObj->pszPath)); + + pObj->enmType = enmType; + + return VINF_SUCCESS; +} + +/** + * Destroys a DnD transfer object. + * + * @param pObj DnD transfer object to destroy. + */ +void DnDTransferObjectDestroy(PDNDTRANSFEROBJECT pObj) +{ + if (!pObj) + return; + + DnDTransferObjectReset(pObj); +} + +/** + * Closes the object's internal handles (to files / ...). + * + * @returns VBox status code. + * @param pObj DnD transfer object to close internally. + */ +static int dndTransferObjectCloseInternal(PDNDTRANSFEROBJECT pObj) +{ + AssertPtrReturn(pObj, VERR_INVALID_POINTER); + + int rc = VINF_SUCCESS; + + if (pObj->pszPath) + LogRel2(("DnD: Closing '%s'\n", pObj->pszPath)); + + switch (pObj->enmType) + { + case DNDTRANSFEROBJTYPE_FILE: + { + if (RTFileIsValid(pObj->u.File.hFile)) + { + rc = RTFileClose(pObj->u.File.hFile); + if (RT_SUCCESS(rc)) + { + pObj->u.File.hFile = NIL_RTFILE; + RT_ZERO(pObj->u.File.objInfo); + } + else + LogRel(("DnD: Closing file '%s' failed with %Rrc\n", pObj->pszPath, rc)); + } + break; + } + + case DNDTRANSFEROBJTYPE_DIRECTORY: + { + if (RTDirIsValid(pObj->u.Dir.hDir)) + { + rc = RTDirClose(pObj->u.Dir.hDir); + if (RT_SUCCESS(rc)) + { + pObj->u.Dir.hDir = NIL_RTDIR; + RT_ZERO(pObj->u.Dir.objInfo); + } + else + LogRel(("DnD: Closing directory '%s' failed with %Rrc\n", pObj->pszPath, rc)); + } + break; + } + + default: + break; + } + + return rc; +} + +/** + * Closes the object. + * This also closes the internal handles associated with the object (to files / ...). + * + * @returns VBox status code. + * @param pObj DnD transfer object to close. + */ +int DnDTransferObjectClose(PDNDTRANSFEROBJECT pObj) +{ + AssertPtrReturn(pObj, VERR_INVALID_POINTER); + + return dndTransferObjectCloseInternal(pObj); +} + +/** + * Returns the absolute source path of the object. + * + * @return Absolute source path of the object. + * @param pObj DnD transfer object to get source path for. + */ +const char *DnDTransferObjectGetSourcePath(PDNDTRANSFEROBJECT pObj) +{ + AssertPtrReturn(pObj, NULL); + return pObj->pszPath; +} + +/** + * Returns the (relative) destination path of the object, in transport style. + * + * @return Relative destination path of the object, or NULL if not set. + * @param pObj DnD transfer object to get destination path for. + */ +const char *DnDTransferObjectGetDestPath(PDNDTRANSFEROBJECT pObj) +{ + AssertPtrReturn(pObj, NULL); + + if (!pObj->pszPath) + return NULL; + + AssertReturn(strlen(pObj->pszPath) >= pObj->idxDst, NULL); + + return &pObj->pszPath[pObj->idxDst]; +} + +/** + * Returns the (relative) destination path of the object, extended version. + * + * @return VBox status code, or VERR_NOT_FOUND if not initialized yet. + * @param pObj DnD transfer object to get destination path for. + * @param enmStyle Which path style to return. + * @param pszBuf Where to store the path. + * @param cbBuf Size (in bytes) where to store the path. + */ +int DnDTransferObjectGetDestPathEx(PDNDTRANSFEROBJECT pObj, DNDTRANSFEROBJPATHSTYLE enmStyle, char *pszBuf, size_t cbBuf) +{ + AssertPtrReturn(pObj, VERR_INVALID_POINTER); + AssertPtrReturn(pszBuf, VERR_INVALID_POINTER); + AssertReturn(cbBuf, VERR_INVALID_PARAMETER); + + if (!pObj->pszPath) + return VERR_NOT_FOUND; + + AssertReturn(strlen(pObj->pszPath) >= pObj->idxDst, VERR_INTERNAL_ERROR); + + int rc = RTStrCopy(pszBuf, cbBuf, &pObj->pszPath[pObj->idxDst]); + if ( RT_SUCCESS(rc) + && enmStyle == DNDTRANSFEROBJPATHSTYLE_DOS) + rc = DnDPathConvert(pszBuf, cbBuf, DNDPATHCONVERT_FLAGS_TO_DOS); + + return rc; +} + +/** + * Returns the directory / file mode of the object. + * + * @return File / directory mode. + * @param pObj DnD transfer object to get directory / file mode for. + */ +RTFMODE DnDTransferObjectGetMode(PDNDTRANSFEROBJECT pObj) +{ + AssertPtrReturn(pObj, 0); + + switch (pObj->enmType) + { + case DNDTRANSFEROBJTYPE_FILE: + return pObj->u.File.objInfo.Attr.fMode; + + case DNDTRANSFEROBJTYPE_DIRECTORY: + return pObj->u.Dir.objInfo.Attr.fMode; + + default: + break; + } + + return 0; +} + +/** + * Returns the bytes already processed (read / written). + * + * Note: Only applies if the object is of type DnDTransferObjectType_File. + * + * @return Bytes already processed (read / written). + * @param pObj DnD transfer object to get processed bytes for. + */ +uint64_t DnDTransferObjectGetProcessed(PDNDTRANSFEROBJECT pObj) +{ + if (pObj->enmType == DNDTRANSFEROBJTYPE_FILE) + return pObj->u.File.cbProcessed; + + return 0; +} + +/** + * Returns the file's logical size (in bytes). + * + * Note: Only applies if the object is of type DnDTransferObjectType_File. + * + * @return The file's logical size (in bytes). + * @param pObj DnD transfer object to get size for. + */ +uint64_t DnDTransferObjectGetSize(PDNDTRANSFEROBJECT pObj) +{ + if (pObj->enmType == DNDTRANSFEROBJTYPE_FILE) + return pObj->u.File.cbToProcess; + + return 0; +} + +/** + * Returns the object's type. + * + * @return The object's type. + * @param pObj DnD transfer object to get type for. + */ +DNDTRANSFEROBJTYPE DnDTransferObjectGetType(PDNDTRANSFEROBJECT pObj) +{ + return pObj->enmType; +} + +/** + * Returns whether the processing of the object is complete or not. + * For file objects this means that all bytes have been processed. + * + * @return True if complete, False if not. + * @param pObj DnD transfer object to get completion status for. + */ +bool DnDTransferObjectIsComplete(PDNDTRANSFEROBJECT pObj) +{ + bool fComplete; + + switch (pObj->enmType) + { + case DNDTRANSFEROBJTYPE_FILE: + Assert(pObj->u.File.cbProcessed <= pObj->u.File.cbToProcess); + fComplete = pObj->u.File.cbProcessed == pObj->u.File.cbToProcess; + break; + + case DNDTRANSFEROBJTYPE_DIRECTORY: + fComplete = true; + break; + + default: + fComplete = true; + break; + } + + return fComplete; +} + +/** + * Returns whether the object is in an open state or not. + * @param pObj DnD transfer object to get open status for. + */ +bool DnDTransferObjectIsOpen(PDNDTRANSFEROBJECT pObj) +{ + switch (pObj->enmType) + { + case DNDTRANSFEROBJTYPE_FILE: return RTFileIsValid(pObj->u.File.hFile); + case DNDTRANSFEROBJTYPE_DIRECTORY: return RTDirIsValid(pObj->u.Dir.hDir); + default: break; + } + + return false; +} + +/** + * Open the object with a specific file type, and, depending on the type, specifying additional parameters. + * + * @return IPRT status code. + * @param pObj DnD transfer object to open. + * @param fOpen Open mode to use; only valid for file objects. + * @param fMode File mode to set; only valid for file objects. Depends on fOpen and and can be 0. + * @param fFlags Additional DnD transfer object flags. + */ +int DnDTransferObjectOpen(PDNDTRANSFEROBJECT pObj, uint64_t fOpen, RTFMODE fMode, DNDTRANSFEROBJECTFLAGS fFlags) +{ + AssertPtrReturn(pObj, VERR_INVALID_POINTER); + AssertReturn(fOpen, VERR_INVALID_FLAGS); + /* fMode is optional. */ + AssertReturn(!(fFlags & ~DNDTRANSFEROBJECT_FLAGS_VALID_MASK), VERR_INVALID_FLAGS); + RT_NOREF1(fFlags); + + int rc = VINF_SUCCESS; + + LogFlowFunc(("pszPath=%s, fOpen=0x%x, fMode=0x%x, fFlags=0x%x\n", pObj->pszPath, fOpen, fMode, fFlags)); + + switch (pObj->enmType) + { + case DNDTRANSFEROBJTYPE_FILE: + { + LogRel2(("DnD: Opening file '%s'\n", pObj->pszPath)); + + /* + * Open files on the source with RTFILE_O_DENY_WRITE to prevent races + * where the OS writes to the file while the destination side transfers + * it over. + */ + rc = RTFileOpen(&pObj->u.File.hFile, pObj->pszPath, fOpen); + if (RT_SUCCESS(rc)) + { + if ( (fOpen & RTFILE_O_WRITE) /* Only set the file mode on write. */ + && fMode /* Some file mode to set specified? */) + { + rc = RTFileSetMode(pObj->u.File.hFile, fMode); + if (RT_FAILURE(rc)) + LogRel(("DnD: Setting mode %#x for file '%s' failed with %Rrc\n", fMode, pObj->pszPath, rc)); + } + else if (fOpen & RTFILE_O_READ) + { + rc = dndTransferObjectQueryInfoInternal(pObj); + } + } + else + LogRel(("DnD: Opening file '%s' failed with %Rrc\n", pObj->pszPath, rc)); + + if (RT_SUCCESS(rc)) + { + LogFlowFunc(("File cbObject=%RU64, fMode=0x%x\n", + pObj->u.File.objInfo.cbObject, pObj->u.File.objInfo.Attr.fMode)); + pObj->u.File.cbToProcess = pObj->u.File.objInfo.cbObject; + pObj->u.File.cbProcessed = 0; + } + + break; + } + + case DNDTRANSFEROBJTYPE_DIRECTORY: + { + LogRel2(("DnD: Opening directory '%s'\n", pObj->pszPath)); + + rc = RTDirOpen(&pObj->u.Dir.hDir, pObj->pszPath); + if (RT_SUCCESS(rc)) + { + rc = dndTransferObjectQueryInfoInternal(pObj); + } + else + LogRel(("DnD: Opening directory '%s' failed with %Rrc\n", pObj->pszPath, rc)); + break; + } + + default: + rc = VERR_NOT_IMPLEMENTED; + break; + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Queries information about the object using a specific view, internal version. + * + * @return IPRT status code. + * @param pObj DnD transfer object to query info for. + */ +static int dndTransferObjectQueryInfoInternal(PDNDTRANSFEROBJECT pObj) +{ + int rc; + + switch (pObj->enmType) + { + case DNDTRANSFEROBJTYPE_FILE: + AssertMsgReturn(RTFileIsValid(pObj->u.File.hFile), ("Object has invalid file handle\n"), VERR_INVALID_STATE); + rc = RTFileQueryInfo(pObj->u.File.hFile, &pObj->u.File.objInfo, RTFSOBJATTRADD_NOTHING); + break; + + case DNDTRANSFEROBJTYPE_DIRECTORY: + AssertMsgReturn(RTDirIsValid(pObj->u.Dir.hDir), ("Object has invalid directory handle\n"), VERR_INVALID_STATE); + rc = RTDirQueryInfo(pObj->u.Dir.hDir, &pObj->u.Dir.objInfo, RTFSOBJATTRADD_NOTHING); + break; + + default: + rc = VERR_NOT_IMPLEMENTED; + break; + } + + if (RT_FAILURE(rc)) + LogRel(("DnD: Querying information for '%s' failed with %Rrc\n", pObj->pszPath, rc)); + + return rc; +} + +/** + * Queries information about the object using a specific view. + * + * @return IPRT status code. + * @param pObj DnD transfer object to query info for. + */ +int DnDTransferObjectQueryInfo(PDNDTRANSFEROBJECT pObj) +{ + AssertPtrReturn(pObj, VERR_INVALID_POINTER); + return dndTransferObjectQueryInfoInternal(pObj); +} + +/** + * Reads data from the object. Only applies to files objects. + * + * @return IPRT status code. + * @param pObj DnD transfer object to read data from. + * @param pvBuf Buffer where to store the read data. + * @param cbBuf Size (in bytes) of the buffer. + * @param pcbRead Pointer where to store how many bytes were read. Optional. + */ +int DnDTransferObjectRead(PDNDTRANSFEROBJECT pObj, void *pvBuf, size_t cbBuf, uint32_t *pcbRead) +{ + AssertPtrReturn(pObj, VERR_INVALID_POINTER); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + AssertReturn(cbBuf, VERR_INVALID_PARAMETER); + /* pcbRead is optional. */ + + size_t cbRead = 0; + + int rc; + switch (pObj->enmType) + { + case DNDTRANSFEROBJTYPE_FILE: + { + rc = RTFileRead(pObj->u.File.hFile, pvBuf, cbBuf, &cbRead); + if (RT_SUCCESS(rc)) + { + pObj->u.File.cbProcessed += cbRead; + Assert(pObj->u.File.cbProcessed <= pObj->u.File.cbToProcess); + + /* End of file reached or error occurred? */ + if ( pObj->u.File.cbToProcess + && pObj->u.File.cbProcessed == pObj->u.File.cbToProcess) + { + rc = VINF_EOF; + } + } + else + LogRel(("DnD: Reading from file '%s' failed with %Rrc\n", pObj->pszPath, rc)); + break; + } + + case DNDTRANSFEROBJTYPE_DIRECTORY: + { + rc = VINF_SUCCESS; + break; + } + + default: + rc = VERR_NOT_IMPLEMENTED; + break; + } + + if (RT_SUCCESS(rc)) + { + if (pcbRead) + *pcbRead = (uint32_t)cbRead; + } + + LogFlowFunc(("Returning cbRead=%zu, rc=%Rrc\n", cbRead, rc)); + return rc; +} + +/** + * Resets the object's state and closes all related handles. + * + * @param pObj DnD transfer object to reset. + */ +void DnDTransferObjectReset(PDNDTRANSFEROBJECT pObj) +{ + AssertPtrReturnVoid(pObj); + + LogFlowFuncEnter(); + + int vrc2 = dndTransferObjectCloseInternal(pObj); + AssertRCReturnVoid(vrc2); + + pObj->enmType = DNDTRANSFEROBJTYPE_UNKNOWN; + pObj->idxDst = 0; + + RTStrFree(pObj->pszPath); + pObj->pszPath = NULL; + + RT_ZERO(pObj->u); +} + +/** + * Sets the bytes to process by the object. + * + * Note: Only applies if the object is of type DnDTransferObjectType_File. + * + * @return IPRT return code. + * @param pObj DnD transfer object to set size for. + * @param cbSize Size (in bytes) to process. + */ +int DnDTransferObjectSetSize(PDNDTRANSFEROBJECT pObj, uint64_t cbSize) +{ + AssertPtrReturn(pObj, VERR_INVALID_POINTER); + AssertReturn(pObj->enmType == DNDTRANSFEROBJTYPE_FILE, VERR_INVALID_PARAMETER); + + /** @todo Implement sparse file support here. */ + + pObj->u.File.cbToProcess = cbSize; + return VINF_SUCCESS; +} + +/** + * Writes data to an object. Only applies to file objects. + * + * @return IPRT status code. + * @param pObj DnD transfer object to write to. + * @param pvBuf Buffer of data to write. + * @param cbBuf Size (in bytes) of data to write. + * @param pcbWritten Pointer where to store how many bytes were written. Optional. + */ +int DnDTransferObjectWrite(PDNDTRANSFEROBJECT pObj, const void *pvBuf, size_t cbBuf, uint32_t *pcbWritten) +{ + AssertPtrReturn(pObj, VERR_INVALID_POINTER); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + AssertReturn(cbBuf, VERR_INVALID_PARAMETER); + /* pcbWritten is optional. */ + + size_t cbWritten = 0; + + int rc; + switch (pObj->enmType) + { + case DNDTRANSFEROBJTYPE_FILE: + { + rc = RTFileWrite(pObj->u.File.hFile, pvBuf, cbBuf, &cbWritten); + if (RT_SUCCESS(rc)) + { + pObj->u.File.cbProcessed += cbWritten; + } + else + LogRel(("DnD: Writing to file '%s' failed with %Rrc\n", pObj->pszPath, rc)); + break; + } + + case DNDTRANSFEROBJTYPE_DIRECTORY: + { + rc = VINF_SUCCESS; + break; + } + + default: + rc = VERR_NOT_IMPLEMENTED; + break; + } + + if (RT_SUCCESS(rc)) + { + if (pcbWritten) + *pcbWritten = (uint32_t)cbWritten; + } + + LogFlowFunc(("Returning cbWritten=%zu, rc=%Rrc\n", cbWritten, rc)); + return rc; +} + -- cgit v1.2.3