/* $Id: VFSExplorerImpl.cpp 37678 2011-06-29 08:24:09Z vboxsync $ */ /** @file * * IVFSExplorer COM class implementations. */ /* * Copyright (C) 2009 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. */ #include #include #include #include #include #include #include #include #include "VFSExplorerImpl.h" #include "VirtualBoxImpl.h" #include "ProgressImpl.h" #include "AutoCaller.h" #include "Logging.h" #include //////////////////////////////////////////////////////////////////////////////// // // VFSExplorer definitions // //////////////////////////////////////////////////////////////////////////////// /* opaque private instance data of VFSExplorer class */ struct VFSExplorer::Data { struct DirEntry { DirEntry(Utf8Str strName, VFSFileType_T fileType, uint64_t cbSize, uint32_t fMode) : name(strName) , type(fileType) , size(cbSize) , mode(fMode) {} Utf8Str name; VFSFileType_T type; uint64_t size; uint32_t mode; }; VFSType_T storageType; Utf8Str strUsername; Utf8Str strPassword; Utf8Str strHostname; Utf8Str strPath; Utf8Str strBucket; std::list entryList; }; VFSExplorer::VFSExplorer() : mVirtualBox(NULL) { } VFSExplorer::~VFSExplorer() { } /** * VFSExplorer COM initializer. * @param * @return */ HRESULT VFSExplorer::init(VFSType_T aType, Utf8Str aFilePath, Utf8Str aHostname, Utf8Str aUsername, Utf8Str aPassword, VirtualBox *aVirtualBox) { /* Enclose the state transition NotReady->InInit->Ready */ AutoInitSpan autoInitSpan(this); AssertReturn(autoInitSpan.isOk(), E_FAIL); /* Weak reference to a VirtualBox object */ unconst(mVirtualBox) = aVirtualBox; /* initialize data */ m = new Data; m->storageType = aType; m->strPath = aFilePath; m->strHostname = aHostname; m->strUsername = aUsername; m->strPassword = aPassword; if (m->storageType == VFSType_S3) { size_t bpos = aFilePath.find("/", 1); if (bpos != Utf8Str::npos) { m->strBucket = aFilePath.substr(1, bpos - 1); /* The bucket without any slashes */ aFilePath = aFilePath.substr(bpos); /* The rest of the file path */ } } /* Confirm a successful initialization */ autoInitSpan.setSucceeded(); return S_OK; } /** * VFSExplorer COM uninitializer. * @return */ void VFSExplorer::uninit() { delete m; m = NULL; } /** * Public method implementation. * @param * @return */ STDMETHODIMP VFSExplorer::COMGETTER(Path)(BSTR *aPath) { if (!aPath) return E_POINTER; AutoCaller autoCaller(this); if (FAILED(autoCaller.rc())) return autoCaller.rc(); AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); Bstr bstrPath(m->strPath); bstrPath.cloneTo(aPath); return S_OK; } STDMETHODIMP VFSExplorer::COMGETTER(Type)(VFSType_T *aType) { if (!aType) return E_POINTER; AutoCaller autoCaller(this); if (FAILED(autoCaller.rc())) return autoCaller.rc(); AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); *aType = m->storageType; return S_OK; } struct VFSExplorer::TaskVFSExplorer { enum TaskType { Update, Delete }; TaskVFSExplorer(TaskType aTaskType, VFSExplorer *aThat, Progress *aProgress) : taskType(aTaskType), pVFSExplorer(aThat), progress(aProgress), rc(S_OK) {} ~TaskVFSExplorer() {} int startThread(); static int taskThread(RTTHREAD aThread, void *pvUser); static int uploadProgress(unsigned uPercent, void *pvUser); TaskType taskType; VFSExplorer *pVFSExplorer; ComObjPtr progress; HRESULT rc; /* task data */ std::list filenames; }; int VFSExplorer::TaskVFSExplorer::startThread() { int vrc = RTThreadCreate(NULL, VFSExplorer::TaskVFSExplorer::taskThread, this, 0, RTTHREADTYPE_MAIN_HEAVY_WORKER, 0, "Explorer::Task"); if (RT_FAILURE(vrc)) return VFSExplorer::setErrorStatic(E_FAIL, Utf8StrFmt("Could not create taskThreadVFS (%Rrc)\n", vrc)); return vrc; } /* static */ DECLCALLBACK(int) VFSExplorer::TaskVFSExplorer::taskThread(RTTHREAD /* aThread */, void *pvUser) { std::auto_ptr task(static_cast(pvUser)); AssertReturn(task.get(), VERR_GENERAL_FAILURE); VFSExplorer *pVFSExplorer = task->pVFSExplorer; LogFlowFuncEnter(); LogFlowFunc(("VFSExplorer %p\n", pVFSExplorer)); HRESULT rc = S_OK; switch(task->taskType) { case TaskVFSExplorer::Update: { if (pVFSExplorer->m->storageType == VFSType_File) rc = pVFSExplorer->updateFS(task.get()); else if (pVFSExplorer->m->storageType == VFSType_S3) #ifdef VBOX_WITH_S3 rc = pVFSExplorer->updateS3(task.get()); #else rc = VERR_NOT_IMPLEMENTED; #endif break; } case TaskVFSExplorer::Delete: { if (pVFSExplorer->m->storageType == VFSType_File) rc = pVFSExplorer->deleteFS(task.get()); else if (pVFSExplorer->m->storageType == VFSType_S3) #ifdef VBOX_WITH_S3 rc = pVFSExplorer->deleteS3(task.get()); #else rc = VERR_NOT_IMPLEMENTED; #endif break; } default: AssertMsgFailed(("Invalid task type %u specified!\n", task->taskType)); break; } LogFlowFunc(("rc=%Rhrc\n", rc)); NOREF(rc); LogFlowFuncLeave(); return VINF_SUCCESS; } /* static */ int VFSExplorer::TaskVFSExplorer::uploadProgress(unsigned uPercent, void *pvUser) { VFSExplorer::TaskVFSExplorer* pTask = *(VFSExplorer::TaskVFSExplorer**)pvUser; if (pTask && !pTask->progress.isNull()) { BOOL fCanceled; pTask->progress->COMGETTER(Canceled)(&fCanceled); if (fCanceled) return -1; pTask->progress->SetCurrentOperationProgress(uPercent); } return VINF_SUCCESS; } VFSFileType_T VFSExplorer::RTToVFSFileType(int aType) const { int a = aType & RTFS_TYPE_MASK; VFSFileType_T t = VFSFileType_Unknown; if ((a & RTFS_TYPE_DIRECTORY) == RTFS_TYPE_DIRECTORY) t = VFSFileType_Directory; else if ((a & RTFS_TYPE_FILE) == RTFS_TYPE_FILE) t = VFSFileType_File; else if ((a & RTFS_TYPE_SYMLINK) == RTFS_TYPE_SYMLINK) t = VFSFileType_SymLink; else if ((a & RTFS_TYPE_FIFO) == RTFS_TYPE_FIFO) t = VFSFileType_Fifo; else if ((a & RTFS_TYPE_DEV_CHAR) == RTFS_TYPE_DEV_CHAR) t = VFSFileType_DevChar; else if ((a & RTFS_TYPE_DEV_BLOCK) == RTFS_TYPE_DEV_BLOCK) t = VFSFileType_DevBlock; else if ((a & RTFS_TYPE_SOCKET) == RTFS_TYPE_SOCKET) t = VFSFileType_Socket; else if ((a & RTFS_TYPE_WHITEOUT) == RTFS_TYPE_WHITEOUT) t = VFSFileType_WhiteOut; return t; } HRESULT VFSExplorer::updateFS(TaskVFSExplorer *aTask) { LogFlowFuncEnter(); AutoCaller autoCaller(this); if (FAILED(autoCaller.rc())) return autoCaller.rc(); AutoWriteLock appLock(this COMMA_LOCKVAL_SRC_POS); HRESULT rc = S_OK; std::list fileList; char *pszPath = NULL; PRTDIR pDir = NULL; try { int vrc = RTDirOpen(&pDir, m->strPath.c_str()); if (RT_FAILURE(vrc)) throw setError(VBOX_E_FILE_ERROR, tr ("Can't open directory '%s' (%Rrc)"), pszPath, vrc); if (aTask->progress) aTask->progress->SetCurrentOperationProgress(33); RTDIRENTRYEX entry; while (RT_SUCCESS(vrc)) { vrc = RTDirReadEx(pDir, &entry, NULL, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK); if (RT_SUCCESS(vrc)) { Utf8Str name(entry.szName); if ( name != "." && name != "..") fileList.push_back(VFSExplorer::Data::DirEntry(name, RTToVFSFileType(entry.Info.Attr.fMode), entry.Info.cbObject, entry.Info.Attr.fMode & (RTFS_UNIX_IRWXU | RTFS_UNIX_IRWXG | RTFS_UNIX_IRWXO))); } } if (aTask->progress) aTask->progress->SetCurrentOperationProgress(66); } catch(HRESULT aRC) { rc = aRC; } /* Clean up */ if (pszPath) RTStrFree(pszPath); if (pDir) RTDirClose(pDir); if (aTask->progress) aTask->progress->SetCurrentOperationProgress(99); /* Assign the result on success (this clears the old list) */ if (rc == S_OK) m->entryList.assign(fileList.begin(), fileList.end()); aTask->rc = rc; if (!aTask->progress.isNull()) aTask->progress->notifyComplete(rc); LogFlowFunc(("rc=%Rhrc\n", rc)); LogFlowFuncLeave(); return VINF_SUCCESS; } HRESULT VFSExplorer::deleteFS(TaskVFSExplorer *aTask) { LogFlowFuncEnter(); AutoCaller autoCaller(this); if (FAILED(autoCaller.rc())) return autoCaller.rc(); AutoWriteLock appLock(this COMMA_LOCKVAL_SRC_POS); HRESULT rc = S_OK; float fPercentStep = 100.0f / aTask->filenames.size(); try { char szPath[RTPATH_MAX]; std::list::const_iterator it; size_t i = 0; for (it = aTask->filenames.begin(); it != aTask->filenames.end(); ++it, ++i) { int vrc = RTPathJoin(szPath, sizeof(szPath), m->strPath.c_str(), (*it).c_str()); if (RT_FAILURE(vrc)) throw setError(E_FAIL, tr("Internal Error (%Rrc)"), vrc); vrc = RTFileDelete(szPath); if (RT_FAILURE(vrc)) throw setError(VBOX_E_FILE_ERROR, tr("Can't delete file '%s' (%Rrc)"), szPath, vrc); if (aTask->progress) aTask->progress->SetCurrentOperationProgress((ULONG)(fPercentStep * i)); } } catch(HRESULT aRC) { rc = aRC; } aTask->rc = rc; if (!aTask->progress.isNull()) aTask->progress->notifyComplete(rc); LogFlowFunc(("rc=%Rhrc\n", rc)); LogFlowFuncLeave(); return VINF_SUCCESS; } #ifdef VBOX_WITH_S3 HRESULT VFSExplorer::updateS3(TaskVFSExplorer *aTask) { LogFlowFuncEnter(); AutoCaller autoCaller(this); if (FAILED(autoCaller.rc())) return autoCaller.rc(); AutoWriteLock appLock(this COMMA_LOCKVAL_SRC_POS); HRESULT rc = S_OK; RTS3 hS3 = NULL; std::list fileList; try { int vrc = RTS3Create(&hS3, m->strUsername.c_str(), m->strPassword.c_str(), m->strHostname.c_str(), "virtualbox-agent/"VBOX_VERSION_STRING); if (RT_FAILURE(vrc)) throw setError(E_FAIL, tr ("Can't open S3 storage service (%Rrc)"), vrc); RTS3SetProgressCallback(hS3, VFSExplorer::TaskVFSExplorer::uploadProgress, &aTask); /* Do we need the list of buckets or keys? */ if (m->strBucket.isEmpty()) { PCRTS3BUCKETENTRY pBuckets = NULL; vrc = RTS3GetBuckets(hS3, &pBuckets); if (RT_FAILURE(vrc)) throw setError(E_FAIL, tr ("Can't get buckets (%Rrc)"), vrc); PCRTS3BUCKETENTRY pTmpBuckets = pBuckets; while (pBuckets) { /* Set always read/write permissions of the current logged in user. */ fileList.push_back(VFSExplorer::Data::DirEntry(pBuckets->pszName, VFSFileType_Directory, 0, RTFS_UNIX_IRUSR | RTFS_UNIX_IWUSR)); pBuckets = pBuckets->pNext; } RTS3BucketsDestroy(pTmpBuckets); } else { PCRTS3KEYENTRY pKeys = NULL; vrc = RTS3GetBucketKeys(hS3, m->strBucket.c_str(), &pKeys); if (RT_FAILURE(vrc)) throw setError(E_FAIL, tr ("Can't get keys for bucket (%Rrc)"), vrc); PCRTS3KEYENTRY pTmpKeys = pKeys; while (pKeys) { Utf8Str name(pKeys->pszName); /* Set always read/write permissions of the current logged in user. */ fileList.push_back(VFSExplorer::Data::DirEntry(pKeys->pszName, VFSFileType_File, pKeys->cbFile, RTFS_UNIX_IRUSR | RTFS_UNIX_IWUSR)); pKeys = pKeys->pNext; } RTS3KeysDestroy(pTmpKeys); } } catch(HRESULT aRC) { rc = aRC; } if (hS3 != NULL) RTS3Destroy(hS3); /* Assign the result on success (this clears the old list) */ if (rc == S_OK) m->entryList.assign(fileList.begin(), fileList.end()); aTask->rc = rc; if (!aTask->progress.isNull()) aTask->progress->notifyComplete(rc); LogFlowFunc(("rc=%Rhrc\n", rc)); LogFlowFuncLeave(); return VINF_SUCCESS; } HRESULT VFSExplorer::deleteS3(TaskVFSExplorer *aTask) { LogFlowFuncEnter(); AutoCaller autoCaller(this); if (FAILED(autoCaller.rc())) return autoCaller.rc(); AutoWriteLock appLock(this COMMA_LOCKVAL_SRC_POS); HRESULT rc = S_OK; RTS3 hS3 = NULL; float fPercentStep = 100.0f / aTask->filenames.size(); try { int vrc = RTS3Create(&hS3, m->strUsername.c_str(), m->strPassword.c_str(), m->strHostname.c_str(), "virtualbox-agent/"VBOX_VERSION_STRING); if (RT_FAILURE(vrc)) throw setError(E_FAIL, tr ("Can't open S3 storage service (%Rrc)"), vrc); RTS3SetProgressCallback(hS3, VFSExplorer::TaskVFSExplorer::uploadProgress, &aTask); std::list::const_iterator it; size_t i = 0; for (it = aTask->filenames.begin(); it != aTask->filenames.end(); ++it, ++i) { vrc = RTS3DeleteKey(hS3, m->strBucket.c_str(), (*it).c_str()); if (RT_FAILURE(vrc)) throw setError(VBOX_E_FILE_ERROR, tr ("Can't delete file '%s' (%Rrc)"), (*it).c_str(), vrc); if (aTask->progress) aTask->progress->SetCurrentOperationProgress((ULONG)(fPercentStep * i)); } } catch(HRESULT aRC) { rc = aRC; } aTask->rc = rc; if (hS3 != NULL) RTS3Destroy(hS3); if (!aTask->progress.isNull()) aTask->progress->notifyComplete(rc); LogFlowFunc(("rc=%Rhrc\n", rc)); LogFlowFuncLeave(); return VINF_SUCCESS; } #endif /* VBOX_WITH_S3 */ STDMETHODIMP VFSExplorer::Update(IProgress **aProgress) { CheckComArgOutPointerValid(aProgress); AutoCaller autoCaller(this); if (FAILED(autoCaller.rc())) return autoCaller.rc(); AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); HRESULT rc = S_OK; ComObjPtr progress; try { Bstr progressDesc = BstrFmt(tr("Update directory info for '%s'"), m->strPath.c_str()); /* Create the progress object */ progress.createObject(); rc = progress->init(mVirtualBox, static_cast(this), progressDesc.raw(), TRUE /* aCancelable */); if (FAILED(rc)) throw rc; /* Initialize our worker task */ std::auto_ptr task(new TaskVFSExplorer(TaskVFSExplorer::Update, this, progress)); rc = task->startThread(); if (FAILED(rc)) throw rc; /* Don't destruct on success */ task.release(); } catch (HRESULT aRC) { rc = aRC; } if (SUCCEEDED(rc)) /* Return progress to the caller */ progress.queryInterfaceTo(aProgress); return rc; } STDMETHODIMP VFSExplorer::Cd(IN_BSTR aDir, IProgress **aProgress) { CheckComArgStrNotEmptyOrNull(aDir); AutoCaller autoCaller(this); if (FAILED(autoCaller.rc())) return autoCaller.rc(); { AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); m->strPath = aDir; } return Update(aProgress); } STDMETHODIMP VFSExplorer::CdUp(IProgress **aProgress) { AutoCaller autoCaller(this); if (FAILED(autoCaller.rc())) return autoCaller.rc(); Utf8Str strUpPath; { AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); /* Remove lowest dir entry in a platform neutral way. */ char *pszNewPath = RTStrDup(m->strPath.c_str()); RTPathStripTrailingSlash(pszNewPath); RTPathStripFilename(pszNewPath); strUpPath = pszNewPath; RTStrFree(pszNewPath); } return Cd(Bstr(strUpPath).raw(), aProgress); } STDMETHODIMP VFSExplorer::EntryList(ComSafeArrayOut(BSTR, aNames), ComSafeArrayOut(VFSFileType_T, aTypes), ComSafeArrayOut(ULONG, aSizes), ComSafeArrayOut(ULONG, aModes)) { if (ComSafeArrayOutIsNull(aNames) || ComSafeArrayOutIsNull(aTypes)) return E_POINTER; AutoCaller autoCaller(this); if (FAILED(autoCaller.rc())) return autoCaller.rc(); AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); com::SafeArray sfaNames(m->entryList.size()); com::SafeArray sfaTypes(m->entryList.size()); com::SafeArray sfaSizes(m->entryList.size()); com::SafeArray sfaModes(m->entryList.size()); std::list::const_iterator it; size_t i = 0; for (it = m->entryList.begin(); it != m->entryList.end(); ++it, ++i) { const VFSExplorer::Data::DirEntry &entry = (*it); Bstr bstr(entry.name); bstr.cloneTo(&sfaNames[i]); sfaTypes[i] = entry.type; sfaSizes[i] = entry.size; sfaModes[i] = entry.mode; } sfaNames.detachTo(ComSafeArrayOutArg(aNames)); sfaTypes.detachTo(ComSafeArrayOutArg(aTypes)); sfaSizes.detachTo(ComSafeArrayOutArg(aSizes)); sfaModes.detachTo(ComSafeArrayOutArg(aModes)); return S_OK; } STDMETHODIMP VFSExplorer::Exists(ComSafeArrayIn(IN_BSTR, aNames), ComSafeArrayOut(BSTR, aExists)) { CheckComArgSafeArrayNotNull(aNames); AutoCaller autoCaller(this); if (FAILED(autoCaller.rc())) return autoCaller.rc(); AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); com::SafeArray sfaNames(ComSafeArrayInArg(aNames)); std::list listExists; for (size_t a=0; a < sfaNames.size(); ++a) { std::list::const_iterator it; for (it = m->entryList.begin(); it != m->entryList.end(); ++it) { const VFSExplorer::Data::DirEntry &entry = (*it); if (entry.name == RTPathFilename(Utf8Str(sfaNames[a]).c_str())) { BSTR name; Bstr tmp(sfaNames[a]); /* gcc-3.3 cruft */ tmp.cloneTo(&name); listExists.push_back(name); } } } com::SafeArray sfaExists(listExists); sfaExists.detachTo(ComSafeArrayOutArg(aExists)); return S_OK; } STDMETHODIMP VFSExplorer::Remove(ComSafeArrayIn(IN_BSTR, aNames), IProgress **aProgress) { CheckComArgSafeArrayNotNull(aNames); CheckComArgOutPointerValid(aProgress); AutoCaller autoCaller(this); if (FAILED(autoCaller.rc())) return autoCaller.rc(); AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); HRESULT rc = S_OK; com::SafeArray sfaNames(ComSafeArrayInArg(aNames)); ComObjPtr progress; try { /* Create the progress object */ progress.createObject(); rc = progress->init(mVirtualBox, static_cast(this), Bstr(tr("Delete files")).raw(), TRUE /* aCancelable */); if (FAILED(rc)) throw rc; /* Initialize our worker task */ std::auto_ptr task(new TaskVFSExplorer(TaskVFSExplorer::Delete, this, progress)); /* Add all filenames to delete as task data */ for (size_t a=0; a < sfaNames.size(); ++a) task->filenames.push_back(Utf8Str(sfaNames[a])); rc = task->startThread(); if (FAILED(rc)) throw rc; /* Don't destruct on success */ task.release(); } catch (HRESULT aRC) { rc = aRC; } if (SUCCEEDED(rc)) /* Return progress to the caller */ progress.queryInterfaceTo(aProgress); return rc; }