/* $Id: DnDDroppedFiles.cpp 85027 2020-07-01 14:30:46Z vboxsync $ */ /** @file * DnD - Directory handling. */ /* * Copyright (C) 2014-2020 Oracle Corporation * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; * you can redistribute it and/or modify it under the terms of the GNU * General Public License (GPL) as published by the Free Software * Foundation, in version 2 as it comes in the "COPYING" file of the * VirtualBox OSE distribution. VirtualBox OSE is distributed in the * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. */ /********************************************************************************************************************************* * Header Files * *********************************************************************************************************************************/ #define LOG_GROUP LOG_GROUP_GUEST_DND #include #include #include #include #include #include #include #include DnDDroppedFiles::DnDDroppedFiles(void) : m_fOpen(0) , m_hDir(NULL) { } DnDDroppedFiles::DnDDroppedFiles(const char *pszPath, DNDURIDROPPEDFILEFLAGS fFlags /* = DNDURIDROPPEDFILE_FLAGS_NONE */) : m_fOpen(0) , m_hDir(NULL) { OpenEx(pszPath, fFlags); } DnDDroppedFiles::~DnDDroppedFiles(void) { /* Only make sure to not leak any handles and stuff, don't delete any * directories / files here. */ closeInternal(); } /** * Adds a file reference to a dropped files directory. * * @returns VBox status code. * @param pszFile Path of file entry to add. */ int DnDDroppedFiles::AddFile(const char *pszFile) { AssertPtrReturn(pszFile, VERR_INVALID_POINTER); if (!this->m_lstFiles.contains(pszFile)) this->m_lstFiles.append(pszFile); return VINF_SUCCESS; } /** * Adds a directory reference to a dropped files directory. * Note: This does *not* (recursively) add sub entries. * * @returns VBox status code. * @param pszDir Path of directory entry to add. */ int DnDDroppedFiles::AddDir(const char *pszDir) { AssertPtrReturn(pszDir, VERR_INVALID_POINTER); if (!this->m_lstDirs.contains(pszDir)) this->m_lstDirs.append(pszDir); return VINF_SUCCESS; } /** * Closes the dropped files directory handle, internal version. * * @returns VBox status code. */ int DnDDroppedFiles::closeInternal(void) { int rc; if (this->m_hDir != NULL) { rc = RTDirClose(this->m_hDir); if (RT_SUCCESS(rc)) this->m_hDir = NULL; } else rc = VINF_SUCCESS; LogFlowFuncLeaveRC(rc); return rc; } /** * Closes the dropped files directory handle. * * @returns VBox status code. */ int DnDDroppedFiles::Close(void) { return closeInternal(); } /** * Returns the absolute path of the dropped files directory. * * @returns Pointer to absolute path of the dropped files directory. */ const char *DnDDroppedFiles::GetDirAbs(void) const { return this->m_strPathAbs.c_str(); } /** * Returns whether the dropped files directory has been opened or not. * * @returns \c true if open, \c false if not. */ bool DnDDroppedFiles::IsOpen(void) const { return (this->m_hDir != NULL); } /** * Opens (creates) the dropped files directory. * * @returns VBox status code. * @param pszPath Absolute path where to create the dropped files directory. * @param fFlags Dropped files flags to use for this directory. */ int DnDDroppedFiles::OpenEx(const char *pszPath, DNDURIDROPPEDFILEFLAGS fFlags /* = DNDURIDROPPEDFILE_FLAGS_NONE */) { AssertPtrReturn(pszPath, VERR_INVALID_POINTER); AssertReturn(fFlags == 0, VERR_INVALID_PARAMETER); /* Flags not supported yet. */ int rc; do { char szDropDir[RTPATH_MAX]; RTStrPrintf(szDropDir, sizeof(szDropDir), "%s", pszPath); /** @todo On Windows we also could use the registry to override * this path, on Posix a dotfile and/or a guest property * can be used. */ /* Append our base drop directory. */ rc = RTPathAppend(szDropDir, sizeof(szDropDir), "VirtualBox Dropped Files"); /** @todo Make this tag configurable? */ if (RT_FAILURE(rc)) break; /* Create it when necessary. */ if (!RTDirExists(szDropDir)) { rc = RTDirCreateFullPath(szDropDir, RTFS_UNIX_IRWXU); if (RT_FAILURE(rc)) break; } /* The actually drop directory consist of the current time stamp and a * unique number when necessary. */ char szTime[64]; RTTIMESPEC time; if (!RTTimeSpecToString(RTTimeNow(&time), szTime, sizeof(szTime))) { rc = VERR_BUFFER_OVERFLOW; break; } rc = DnDPathSanitizeFilename(szTime, sizeof(szTime)); if (RT_FAILURE(rc)) break; rc = RTPathAppend(szDropDir, sizeof(szDropDir), szTime); if (RT_FAILURE(rc)) break; /* Create it (only accessible by the current user) */ rc = RTDirCreateUniqueNumbered(szDropDir, sizeof(szDropDir), RTFS_UNIX_IRWXU, 3, '-'); if (RT_SUCCESS(rc)) { RTDIR hDir; rc = RTDirOpen(&hDir, szDropDir); if (RT_SUCCESS(rc)) { this->m_hDir = hDir; this->m_strPathAbs = szDropDir; this->m_fOpen = fFlags; } } } while (0); LogFlowFuncLeaveRC(rc); return rc; } /** * Opens (creates) the dropped files directory in the system's temp directory. * * @returns VBox status code. * @param fFlags Dropped files flags to use for this directory. */ int DnDDroppedFiles::OpenTemp(DNDURIDROPPEDFILEFLAGS fFlags /* = DNDURIDROPPEDFILE_FLAGS_NONE */) { AssertReturn(fFlags == 0, VERR_INVALID_PARAMETER); /* Flags not supported yet. */ /* * Get the user's temp directory. Don't use the user's root directory (or * something inside it) because we don't know for how long/if the data will * be kept after the guest OS used it. */ char szTemp[RTPATH_MAX]; int rc = RTPathTemp(szTemp, sizeof(szTemp)); if (RT_SUCCESS(rc)) rc = OpenEx(szTemp, fFlags); return rc; } /** * Resets a droppped files directory. * * @returns VBox status code. * @param fDelete Whether to physically delete the directory and its content * or just clear the internal references. */ int DnDDroppedFiles::Reset(bool fDelete) { int rc = closeInternal(); if (RT_SUCCESS(rc)) { if (fDelete) { rc = Rollback(); } else { this->m_lstDirs.clear(); this->m_lstFiles.clear(); } } LogFlowFuncLeaveRC(rc); return rc; } /** * Re-opens a droppes files directory. * * @returns VBox status code, or VERR_NOT_FOUND if the dropped files directory has not been opened before. */ int DnDDroppedFiles::Reopen(void) { if (this->m_strPathAbs.isEmpty()) return VERR_NOT_FOUND; return OpenEx(this->m_strPathAbs.c_str(), this->m_fOpen); } /** * Performs a rollback of a dropped files directory. * This cleans the directory by physically deleting all files / directories which have been added before. * * @returns VBox status code. */ int DnDDroppedFiles::Rollback(void) { if (this->m_strPathAbs.isEmpty()) return VINF_SUCCESS; int rc = VINF_SUCCESS; /* Rollback by removing any stuff created. * Note: Only remove empty directories, never ever delete * anything recursive here! Steam (tm) knows best ... :-) */ int rc2; for (size_t i = 0; i < this->m_lstFiles.size(); i++) { rc2 = RTFileDelete(this->m_lstFiles.at(i).c_str()); if (RT_SUCCESS(rc2)) this->m_lstFiles.removeAt(i); else if (RT_SUCCESS(rc)) rc = rc2; /* Keep going. */ } for (size_t i = 0; i < this->m_lstDirs.size(); i++) { rc2 = RTDirRemove(this->m_lstDirs.at(i).c_str()); if (RT_SUCCESS(rc2)) this->m_lstDirs.removeAt(i); else if (RT_SUCCESS(rc)) rc = rc2; /* Keep going. */ } if (RT_SUCCESS(rc)) { Assert(this->m_lstFiles.isEmpty()); Assert(this->m_lstDirs.isEmpty()); rc2 = closeInternal(); if (RT_SUCCESS(rc2)) { /* Try to remove the empty root dropped files directory as well. * Might return VERR_DIR_NOT_EMPTY or similar. */ rc2 = RTDirRemove(this->m_strPathAbs.c_str()); } if (RT_SUCCESS(rc)) rc = rc2; } LogFlowFuncLeaveRC(rc); return rc; }