diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:17:27 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:17:27 +0000 |
commit | f215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch) | |
tree | 6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/VBox/Main/src-server/MediumImpl.cpp | |
parent | Initial commit. (diff) | |
download | virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.tar.xz virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.zip |
Adding upstream version 7.0.14-dfsg.upstream/7.0.14-dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Main/src-server/MediumImpl.cpp')
-rw-r--r-- | src/VBox/Main/src-server/MediumImpl.cpp | 11182 |
1 files changed, 11182 insertions, 0 deletions
diff --git a/src/VBox/Main/src-server/MediumImpl.cpp b/src/VBox/Main/src-server/MediumImpl.cpp new file mode 100644 index 00000000..abcdd910 --- /dev/null +++ b/src/VBox/Main/src-server/MediumImpl.cpp @@ -0,0 +1,11182 @@ +/* $Id: MediumImpl.cpp $ */ +/** @file + * VirtualBox COM class implementation + */ + +/* + * Copyright (C) 2008-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 + */ + +#define LOG_GROUP LOG_GROUP_MAIN_MEDIUM +#include "MediumImpl.h" +#include "MediumIOImpl.h" +#include "TokenImpl.h" +#include "ProgressImpl.h" +#include "SystemPropertiesImpl.h" +#include "VirtualBoxImpl.h" +#include "ExtPackManagerImpl.h" + +#include "AutoCaller.h" +#include "Global.h" +#include "LoggingNew.h" +#include "ThreadTask.h" +#include "VBox/com/MultiResult.h" +#include "VBox/com/ErrorInfo.h" + +#include <VBox/err.h> +#include <VBox/settings.h> + +#include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/file.h> +#include <iprt/cpp/utils.h> +#include <iprt/memsafer.h> +#include <iprt/base64.h> +#include <iprt/vfs.h> +#include <iprt/fsvfs.h> + +#include <VBox/vd.h> + +#include <algorithm> +#include <list> +#include <set> +#include <map> + + +typedef std::list<Guid> GuidList; + + +#ifdef VBOX_WITH_EXTPACK +static const char g_szVDPlugin[] = "VDPluginCrypt"; +#endif + + +//////////////////////////////////////////////////////////////////////////////// +// +// Medium data definition +// +//////////////////////////////////////////////////////////////////////////////// +#if __cplusplus < 201700 && RT_GNUC_PREREQ(11,0) /* gcc/libstdc++ 12.1.1 errors out here because unary_function is deprecated */ +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +struct SnapshotRef +{ + /** Equality predicate for stdc++. */ + struct EqualsTo +#if __cplusplus < 201700 /* deprecated in C++11, removed in C++17. */ + : public std::unary_function <SnapshotRef, bool> +#endif + { + explicit EqualsTo(const Guid &aSnapshotId) : snapshotId(aSnapshotId) {} + +#if __cplusplus < 201700 /* deprecated in C++11, removed in C++17. */ + bool operator()(const argument_type &aThat) const +#else + bool operator()(const SnapshotRef &aThat) const +#endif + { + return aThat.snapshotId == snapshotId; + } + + const Guid snapshotId; + }; + + SnapshotRef(const Guid &aSnapshotId, + const int &aRefCnt = 1) + : snapshotId(aSnapshotId), + iRefCnt(aRefCnt) {} + + Guid snapshotId; + /* + * The number of attachments of the medium in the same snapshot. + * Used for MediumType_Readonly. It is always equal to 1 for other types. + * Usual int is used because any changes in the BackRef are guarded by + * AutoWriteLock. + */ + int iRefCnt; +}; + +/** Describes how a machine refers to this medium. */ +struct BackRef +{ + /** Equality predicate for stdc++. */ + struct EqualsTo +#if __cplusplus < 201700 /* deprecated in C++11, removed in C++17. */ + : public std::unary_function <BackRef, bool> +#endif + { + explicit EqualsTo(const Guid &aMachineId) : machineId(aMachineId) {} + +#if __cplusplus < 201700 /* deprecated in C++11, removed in C++17. */ + bool operator()(const argument_type &aThat) const +#else + bool operator()(const BackRef &aThat) const +#endif + { + return aThat.machineId == machineId; + } + + const Guid machineId; + }; + + BackRef(const Guid &aMachineId, + const Guid &aSnapshotId = Guid::Empty) + : machineId(aMachineId), + iRefCnt(1), + fInCurState(aSnapshotId.isZero()) + { + if (aSnapshotId.isValid() && !aSnapshotId.isZero()) + llSnapshotIds.push_back(SnapshotRef(aSnapshotId)); + } + + Guid machineId; + /* + * The number of attachments of the medium in the same machine. + * Used for MediumType_Readonly. It is always equal to 1 for other types. + * Usual int is used because any changes in the BackRef are guarded by + * AutoWriteLock. + */ + int iRefCnt; + bool fInCurState : 1; + std::list<SnapshotRef> llSnapshotIds; +}; + +typedef std::list<BackRef> BackRefList; + +#if __cplusplus < 201700 && RT_GNUC_PREREQ(11,0) +# pragma GCC diagnostic pop +#endif + + +struct Medium::Data +{ + Data() + : pVirtualBox(NULL), + state(MediumState_NotCreated), + variant(MediumVariant_Standard), + size(0), + readers(0), + preLockState(MediumState_NotCreated), + queryInfoSem(LOCKCLASS_MEDIUMQUERY), + queryInfoRunning(false), + type(MediumType_Normal), + devType(DeviceType_HardDisk), + logicalSize(0), + hddOpenMode(OpenReadWrite), + autoReset(false), + hostDrive(false), + implicit(false), + fClosing(false), + uOpenFlagsDef(VD_OPEN_FLAGS_IGNORE_FLUSH), + numCreateDiffTasks(0), + vdDiskIfaces(NULL), + vdImageIfaces(NULL), + fMoveThisMedium(false) + { } + + /** weak VirtualBox parent */ + VirtualBox * const pVirtualBox; + + // pParent and llChildren are protected by VirtualBox::i_getMediaTreeLockHandle() + ComObjPtr<Medium> pParent; + MediaList llChildren; // to add a child, just call push_back; to remove + // a child, call child->deparent() which does a lookup + + GuidList llRegistryIDs; // media registries in which this medium is listed + + const Guid id; + Utf8Str strDescription; + MediumState_T state; + MediumVariant_T variant; + Utf8Str strLocationFull; + uint64_t size; + Utf8Str strLastAccessError; + + BackRefList backRefs; + + size_t readers; + MediumState_T preLockState; + + /** Special synchronization for operations which must wait for + * Medium::i_queryInfo in another thread to complete. Using a SemRW is + * not quite ideal, but at least it is subject to the lock validator, + * unlike the SemEventMulti which we had here for many years. Catching + * possible deadlocks is more important than a tiny bit of efficiency. */ + RWLockHandle queryInfoSem; + bool queryInfoRunning : 1; + + const Utf8Str strFormat; + ComObjPtr<MediumFormat> formatObj; + + MediumType_T type; + DeviceType_T devType; + uint64_t logicalSize; + + HDDOpenMode hddOpenMode; + + bool autoReset : 1; + + /** New UUID to be set on the next Medium::i_queryInfo call. */ + const Guid uuidImage; + /** New parent UUID to be set on the next Medium::i_queryInfo call. */ + const Guid uuidParentImage; + +/** @todo r=bird: the boolean bitfields are pointless if they're not grouped! */ + bool hostDrive : 1; + + settings::StringsMap mapProperties; + + bool implicit : 1; + /** Flag whether the medium is in the process of being closed. */ + bool fClosing: 1; + + /** Default flags passed to VDOpen(). */ + unsigned uOpenFlagsDef; + + uint32_t numCreateDiffTasks; + + Utf8Str vdError; /*< Error remembered by the VD error callback. */ + + VDINTERFACEERROR vdIfError; + + VDINTERFACECONFIG vdIfConfig; + + /** The handle to the default VD TCP/IP interface. */ + VDIFINST hTcpNetInst; + + PVDINTERFACE vdDiskIfaces; + PVDINTERFACE vdImageIfaces; + + /** Flag if the medium is going to move to a new + * location. */ + bool fMoveThisMedium; + /** new location path */ + Utf8Str strNewLocationFull; +}; + +typedef struct VDSOCKETINT +{ + /** Socket handle. */ + RTSOCKET hSocket; +} VDSOCKETINT, *PVDSOCKETINT; + +//////////////////////////////////////////////////////////////////////////////// +// +// Globals +// +//////////////////////////////////////////////////////////////////////////////// + +/** + * Medium::Task class for asynchronous operations. + * + * @note Instances of this class must be created using new() because the + * task thread function will delete them when the task is complete. + * + * @note The constructor of this class adds a caller on the managed Medium + * object which is automatically released upon destruction. + */ +class Medium::Task : public ThreadTask +{ +public: + Task(Medium *aMedium, Progress *aProgress, bool fNotifyAboutChanges = true) + : ThreadTask("Medium::Task"), + mVDOperationIfaces(NULL), + mMedium(aMedium), + mMediumCaller(aMedium), + mProgress(aProgress), + mVirtualBoxCaller(NULL), + mNotifyAboutChanges(fNotifyAboutChanges) + { + AssertReturnVoidStmt(aMedium, mHrc = E_FAIL); + mHrc = mMediumCaller.hrc(); + if (FAILED(mHrc)) + return; + + /* Get strong VirtualBox reference, see below. */ + VirtualBox *pVirtualBox = aMedium->m->pVirtualBox; + mVirtualBox = pVirtualBox; + mVirtualBoxCaller.attach(pVirtualBox); + mHrc = mVirtualBoxCaller.hrc(); + if (FAILED(mHrc)) + return; + + /* Set up a per-operation progress interface, can be used freely (for + * binary operations you can use it either on the source or target). */ + if (mProgress) + { + mVDIfProgress.pfnProgress = aProgress->i_vdProgressCallback; + int vrc = VDInterfaceAdd(&mVDIfProgress.Core, + "Medium::Task::vdInterfaceProgress", + VDINTERFACETYPE_PROGRESS, + mProgress, + sizeof(mVDIfProgress), + &mVDOperationIfaces); + AssertRC(vrc); + if (RT_FAILURE(vrc)) + mHrc = E_FAIL; + } + } + + // Make all destructors virtual. Just in case. + virtual ~Task() + { + /* send the notification of completion.*/ + if ( isAsync() + && !mProgress.isNull()) + mProgress->i_notifyComplete(mHrc); + } + + HRESULT hrc() const { return mHrc; } + bool isOk() const { return SUCCEEDED(hrc()); } + bool NotifyAboutChanges() const { return mNotifyAboutChanges; } + + const ComPtr<Progress>& GetProgressObject() const {return mProgress;} + + /** + * Runs Medium::Task::executeTask() on the current thread + * instead of creating a new one. + */ + HRESULT runNow() + { + LogFlowFuncEnter(); + + mHrc = executeTask(); + + LogFlowFunc(("hrc=%Rhrc\n", mHrc)); + LogFlowFuncLeave(); + return mHrc; + } + + /** + * Implementation code for the "create base" task. + * Used as function for execution from a standalone thread. + */ + void handler() + { + LogFlowFuncEnter(); + try + { + mHrc = executeTask(); /* (destructor picks up mHrc, see above) */ + LogFlowFunc(("hrc=%Rhrc\n", mHrc)); + } + catch (...) + { + LogRel(("Some exception in the function Medium::Task:handler()\n")); + } + + LogFlowFuncLeave(); + } + + PVDINTERFACE mVDOperationIfaces; + + const ComObjPtr<Medium> mMedium; + AutoCaller mMediumCaller; + +protected: + HRESULT mHrc; + +private: + virtual HRESULT executeTask() = 0; + + const ComObjPtr<Progress> mProgress; + + VDINTERFACEPROGRESS mVDIfProgress; + + /* Must have a strong VirtualBox reference during a task otherwise the + * reference count might drop to 0 while a task is still running. This + * would result in weird behavior, including deadlocks due to uninit and + * locking order issues. The deadlock often is not detectable because the + * uninit uses event semaphores which sabotages deadlock detection. */ + ComObjPtr<VirtualBox> mVirtualBox; + AutoCaller mVirtualBoxCaller; + bool mNotifyAboutChanges; +}; + +class Medium::CreateBaseTask : public Medium::Task +{ +public: + CreateBaseTask(Medium *aMedium, + Progress *aProgress, + uint64_t aSize, + MediumVariant_T aVariant, + bool fNotifyAboutChanges = true) + : Medium::Task(aMedium, aProgress, fNotifyAboutChanges), + mSize(aSize), + mVariant(aVariant) + { + m_strTaskName = "createBase"; + } + + uint64_t mSize; + MediumVariant_T mVariant; + +private: + HRESULT executeTask() + { + return mMedium->i_taskCreateBaseHandler(*this); + } +}; + +class Medium::CreateDiffTask : public Medium::Task +{ +public: + CreateDiffTask(Medium *aMedium, + Progress *aProgress, + Medium *aTarget, + MediumVariant_T aVariant, + MediumLockList *aMediumLockList, + bool fKeepMediumLockList = false, + bool fNotifyAboutChanges = true) + : Medium::Task(aMedium, aProgress, fNotifyAboutChanges), + mpMediumLockList(aMediumLockList), + mTarget(aTarget), + mVariant(aVariant), + mTargetCaller(aTarget), + mfKeepMediumLockList(fKeepMediumLockList) + { + AssertReturnVoidStmt(aTarget != NULL, mHrc = E_FAIL); + mHrc = mTargetCaller.hrc(); + if (FAILED(mHrc)) + return; + m_strTaskName = "createDiff"; + } + + ~CreateDiffTask() + { + if (!mfKeepMediumLockList && mpMediumLockList) + delete mpMediumLockList; + } + + MediumLockList *mpMediumLockList; + + const ComObjPtr<Medium> mTarget; + MediumVariant_T mVariant; + +private: + HRESULT executeTask() + { + return mMedium->i_taskCreateDiffHandler(*this); + } + + AutoCaller mTargetCaller; + bool mfKeepMediumLockList; +}; + +class Medium::CloneTask : public Medium::Task +{ +public: + CloneTask(Medium *aMedium, + Progress *aProgress, + Medium *aTarget, + MediumVariant_T aVariant, + Medium *aParent, + uint32_t idxSrcImageSame, + uint32_t idxDstImageSame, + MediumLockList *aSourceMediumLockList, + MediumLockList *aTargetMediumLockList, + bool fKeepSourceMediumLockList = false, + bool fKeepTargetMediumLockList = false, + bool fNotifyAboutChanges = true, + uint64_t aTargetLogicalSize = 0) + : Medium::Task(aMedium, aProgress, fNotifyAboutChanges), + mTarget(aTarget), + mParent(aParent), + mTargetLogicalSize(aTargetLogicalSize), + mpSourceMediumLockList(aSourceMediumLockList), + mpTargetMediumLockList(aTargetMediumLockList), + mVariant(aVariant), + midxSrcImageSame(idxSrcImageSame), + midxDstImageSame(idxDstImageSame), + mTargetCaller(aTarget), + mParentCaller(aParent), + mfKeepSourceMediumLockList(fKeepSourceMediumLockList), + mfKeepTargetMediumLockList(fKeepTargetMediumLockList) + { + AssertReturnVoidStmt(aTarget != NULL, mHrc = E_FAIL); + mHrc = mTargetCaller.hrc(); + if (FAILED(mHrc)) + return; + /* aParent may be NULL */ + mHrc = mParentCaller.hrc(); + if (FAILED(mHrc)) + return; + AssertReturnVoidStmt(aSourceMediumLockList != NULL, mHrc = E_FAIL); + AssertReturnVoidStmt(aTargetMediumLockList != NULL, mHrc = E_FAIL); + m_strTaskName = "createClone"; + } + + ~CloneTask() + { + if (!mfKeepSourceMediumLockList && mpSourceMediumLockList) + delete mpSourceMediumLockList; + if (!mfKeepTargetMediumLockList && mpTargetMediumLockList) + delete mpTargetMediumLockList; + } + + const ComObjPtr<Medium> mTarget; + const ComObjPtr<Medium> mParent; + uint64_t mTargetLogicalSize; + MediumLockList *mpSourceMediumLockList; + MediumLockList *mpTargetMediumLockList; + MediumVariant_T mVariant; + uint32_t midxSrcImageSame; + uint32_t midxDstImageSame; + +private: + HRESULT executeTask() + { + return mMedium->i_taskCloneHandler(*this); + } + + AutoCaller mTargetCaller; + AutoCaller mParentCaller; + bool mfKeepSourceMediumLockList; + bool mfKeepTargetMediumLockList; +}; + +class Medium::MoveTask : public Medium::Task +{ +public: + MoveTask(Medium *aMedium, + Progress *aProgress, + MediumVariant_T aVariant, + MediumLockList *aMediumLockList, + bool fKeepMediumLockList = false, + bool fNotifyAboutChanges = true) + : Medium::Task(aMedium, aProgress, fNotifyAboutChanges), + mpMediumLockList(aMediumLockList), + mVariant(aVariant), + mfKeepMediumLockList(fKeepMediumLockList) + { + AssertReturnVoidStmt(aMediumLockList != NULL, mHrc = E_FAIL); + m_strTaskName = "createMove"; + } + + ~MoveTask() + { + if (!mfKeepMediumLockList && mpMediumLockList) + delete mpMediumLockList; + } + + MediumLockList *mpMediumLockList; + MediumVariant_T mVariant; + +private: + HRESULT executeTask() + { + return mMedium->i_taskMoveHandler(*this); + } + + bool mfKeepMediumLockList; +}; + +class Medium::CompactTask : public Medium::Task +{ +public: + CompactTask(Medium *aMedium, + Progress *aProgress, + MediumLockList *aMediumLockList, + bool fKeepMediumLockList = false, + bool fNotifyAboutChanges = true) + : Medium::Task(aMedium, aProgress, fNotifyAboutChanges), + mpMediumLockList(aMediumLockList), + mfKeepMediumLockList(fKeepMediumLockList) + { + AssertReturnVoidStmt(aMediumLockList != NULL, mHrc = E_FAIL); + m_strTaskName = "createCompact"; + } + + ~CompactTask() + { + if (!mfKeepMediumLockList && mpMediumLockList) + delete mpMediumLockList; + } + + MediumLockList *mpMediumLockList; + +private: + HRESULT executeTask() + { + return mMedium->i_taskCompactHandler(*this); + } + + bool mfKeepMediumLockList; +}; + +class Medium::ResizeTask : public Medium::Task +{ +public: + ResizeTask(Medium *aMedium, + uint64_t aSize, + Progress *aProgress, + MediumLockList *aMediumLockList, + bool fKeepMediumLockList = false, + bool fNotifyAboutChanges = true) + : Medium::Task(aMedium, aProgress, fNotifyAboutChanges), + mSize(aSize), + mpMediumLockList(aMediumLockList), + mfKeepMediumLockList(fKeepMediumLockList) + { + AssertReturnVoidStmt(aMediumLockList != NULL, mHrc = E_FAIL); + m_strTaskName = "createResize"; + } + + ~ResizeTask() + { + if (!mfKeepMediumLockList && mpMediumLockList) + delete mpMediumLockList; + } + + uint64_t mSize; + MediumLockList *mpMediumLockList; + +private: + HRESULT executeTask() + { + return mMedium->i_taskResizeHandler(*this); + } + + bool mfKeepMediumLockList; +}; + +class Medium::ResetTask : public Medium::Task +{ +public: + ResetTask(Medium *aMedium, + Progress *aProgress, + MediumLockList *aMediumLockList, + bool fKeepMediumLockList = false, + bool fNotifyAboutChanges = true) + : Medium::Task(aMedium, aProgress, fNotifyAboutChanges), + mpMediumLockList(aMediumLockList), + mfKeepMediumLockList(fKeepMediumLockList) + { + m_strTaskName = "createReset"; + } + + ~ResetTask() + { + if (!mfKeepMediumLockList && mpMediumLockList) + delete mpMediumLockList; + } + + MediumLockList *mpMediumLockList; + +private: + HRESULT executeTask() + { + return mMedium->i_taskResetHandler(*this); + } + + bool mfKeepMediumLockList; +}; + +class Medium::DeleteTask : public Medium::Task +{ +public: + DeleteTask(Medium *aMedium, + Progress *aProgress, + MediumLockList *aMediumLockList, + bool fKeepMediumLockList = false, + bool fNotifyAboutChanges = true) + : Medium::Task(aMedium, aProgress, fNotifyAboutChanges), + mpMediumLockList(aMediumLockList), + mfKeepMediumLockList(fKeepMediumLockList) + { + m_strTaskName = "createDelete"; + } + + ~DeleteTask() + { + if (!mfKeepMediumLockList && mpMediumLockList) + delete mpMediumLockList; + } + + MediumLockList *mpMediumLockList; + +private: + HRESULT executeTask() + { + return mMedium->i_taskDeleteHandler(*this); + } + + bool mfKeepMediumLockList; +}; + +class Medium::MergeTask : public Medium::Task +{ +public: + MergeTask(Medium *aMedium, + Medium *aTarget, + bool fMergeForward, + Medium *aParentForTarget, + MediumLockList *aChildrenToReparent, + Progress *aProgress, + MediumLockList *aMediumLockList, + bool fKeepMediumLockList = false, + bool fNotifyAboutChanges = true) + : Medium::Task(aMedium, aProgress, fNotifyAboutChanges), + mTarget(aTarget), + mfMergeForward(fMergeForward), + mParentForTarget(aParentForTarget), + mpChildrenToReparent(aChildrenToReparent), + mpMediumLockList(aMediumLockList), + mTargetCaller(aTarget), + mParentForTargetCaller(aParentForTarget), + mfKeepMediumLockList(fKeepMediumLockList) + { + AssertReturnVoidStmt(aMediumLockList != NULL, mHrc = E_FAIL); + m_strTaskName = "createMerge"; + } + + ~MergeTask() + { + if (!mfKeepMediumLockList && mpMediumLockList) + delete mpMediumLockList; + if (mpChildrenToReparent) + delete mpChildrenToReparent; + } + + const ComObjPtr<Medium> mTarget; + bool mfMergeForward; + /* When mpChildrenToReparent is null then mParentForTarget is non-null and + * vice versa. In other words: they are used in different cases. */ + const ComObjPtr<Medium> mParentForTarget; + MediumLockList *mpChildrenToReparent; + MediumLockList *mpMediumLockList; + +private: + HRESULT executeTask() + { + return mMedium->i_taskMergeHandler(*this); + } + + AutoCaller mTargetCaller; + AutoCaller mParentForTargetCaller; + bool mfKeepMediumLockList; +}; + +class Medium::ImportTask : public Medium::Task +{ +public: + ImportTask(Medium *aMedium, + Progress *aProgress, + const char *aFilename, + MediumFormat *aFormat, + MediumVariant_T aVariant, + RTVFSIOSTREAM aVfsIosSrc, + Medium *aParent, + MediumLockList *aTargetMediumLockList, + bool fKeepTargetMediumLockList = false, + bool fNotifyAboutChanges = true) + : Medium::Task(aMedium, aProgress, fNotifyAboutChanges), + mFilename(aFilename), + mFormat(aFormat), + mVariant(aVariant), + mParent(aParent), + mpTargetMediumLockList(aTargetMediumLockList), + mpVfsIoIf(NULL), + mParentCaller(aParent), + mfKeepTargetMediumLockList(fKeepTargetMediumLockList) + { + AssertReturnVoidStmt(aTargetMediumLockList != NULL, mHrc = E_FAIL); + /* aParent may be NULL */ + mHrc = mParentCaller.hrc(); + if (FAILED(mHrc)) + return; + + mVDImageIfaces = aMedium->m->vdImageIfaces; + + int vrc = VDIfCreateFromVfsStream(aVfsIosSrc, RTFILE_O_READ, &mpVfsIoIf); + AssertRCReturnVoidStmt(vrc, mHrc = E_FAIL); + + vrc = VDInterfaceAdd(&mpVfsIoIf->Core, "Medium::ImportTaskVfsIos", + VDINTERFACETYPE_IO, mpVfsIoIf, + sizeof(VDINTERFACEIO), &mVDImageIfaces); + AssertRCReturnVoidStmt(vrc, mHrc = E_FAIL); + m_strTaskName = "createImport"; + } + + ~ImportTask() + { + if (!mfKeepTargetMediumLockList && mpTargetMediumLockList) + delete mpTargetMediumLockList; + if (mpVfsIoIf) + { + VDIfDestroyFromVfsStream(mpVfsIoIf); + mpVfsIoIf = NULL; + } + } + + Utf8Str mFilename; + ComObjPtr<MediumFormat> mFormat; + MediumVariant_T mVariant; + const ComObjPtr<Medium> mParent; + MediumLockList *mpTargetMediumLockList; + PVDINTERFACE mVDImageIfaces; + PVDINTERFACEIO mpVfsIoIf; /**< Pointer to the VFS I/O stream to VD I/O interface wrapper. */ + +private: + HRESULT executeTask() + { + return mMedium->i_taskImportHandler(*this); + } + + AutoCaller mParentCaller; + bool mfKeepTargetMediumLockList; +}; + +class Medium::EncryptTask : public Medium::Task +{ +public: + EncryptTask(Medium *aMedium, + const com::Utf8Str &strNewPassword, + const com::Utf8Str &strCurrentPassword, + const com::Utf8Str &strCipher, + const com::Utf8Str &strNewPasswordId, + Progress *aProgress, + MediumLockList *aMediumLockList) + : Medium::Task(aMedium, aProgress, false), + mstrNewPassword(strNewPassword), + mstrCurrentPassword(strCurrentPassword), + mstrCipher(strCipher), + mstrNewPasswordId(strNewPasswordId), + mpMediumLockList(aMediumLockList) + { + AssertReturnVoidStmt(aMediumLockList != NULL, mHrc = E_FAIL); + /* aParent may be NULL */ + mHrc = mParentCaller.hrc(); + if (FAILED(mHrc)) + return; + + mVDImageIfaces = aMedium->m->vdImageIfaces; + m_strTaskName = "createEncrypt"; + } + + ~EncryptTask() + { + if (mstrNewPassword.length()) + RTMemWipeThoroughly(mstrNewPassword.mutableRaw(), mstrNewPassword.length(), 10 /* cPasses */); + if (mstrCurrentPassword.length()) + RTMemWipeThoroughly(mstrCurrentPassword.mutableRaw(), mstrCurrentPassword.length(), 10 /* cPasses */); + + /* Keep any errors which might be set when deleting the lock list. */ + ErrorInfoKeeper eik; + delete mpMediumLockList; + } + + Utf8Str mstrNewPassword; + Utf8Str mstrCurrentPassword; + Utf8Str mstrCipher; + Utf8Str mstrNewPasswordId; + MediumLockList *mpMediumLockList; + PVDINTERFACE mVDImageIfaces; + +private: + HRESULT executeTask() + { + return mMedium->i_taskEncryptHandler(*this); + } + + AutoCaller mParentCaller; +}; + + + +/** + * Converts the Medium device type to the VD type. + */ +static const char *getVDTypeName(VDTYPE enmType) +{ + switch (enmType) + { + case VDTYPE_HDD: return "HDD"; + case VDTYPE_OPTICAL_DISC: return "DVD"; + case VDTYPE_FLOPPY: return "floppy"; + case VDTYPE_INVALID: return "invalid"; + default: + AssertFailedReturn("unknown"); + } +} + +/** + * Converts the Medium device type to the VD type. + */ +static const char *getDeviceTypeName(DeviceType_T enmType) +{ + switch (enmType) + { + case DeviceType_HardDisk: return "HDD"; + case DeviceType_DVD: return "DVD"; + case DeviceType_Floppy: return "floppy"; + case DeviceType_Null: return "null"; + case DeviceType_Network: return "network"; + case DeviceType_USB: return "USB"; + case DeviceType_SharedFolder: return "shared folder"; + case DeviceType_Graphics3D: return "graphics 3d"; + default: + AssertFailedReturn("unknown"); + } +} + + + +//////////////////////////////////////////////////////////////////////////////// +// +// Medium constructor / destructor +// +//////////////////////////////////////////////////////////////////////////////// + +DEFINE_EMPTY_CTOR_DTOR(Medium) + +HRESULT Medium::FinalConstruct() +{ + m = new Data; + + /* Initialize the callbacks of the VD error interface */ + m->vdIfError.pfnError = i_vdErrorCall; + m->vdIfError.pfnMessage = NULL; + + /* Initialize the callbacks of the VD config interface */ + m->vdIfConfig.pfnAreKeysValid = i_vdConfigAreKeysValid; + m->vdIfConfig.pfnQuerySize = i_vdConfigQuerySize; + m->vdIfConfig.pfnQuery = i_vdConfigQuery; + m->vdIfConfig.pfnUpdate = i_vdConfigUpdate; + m->vdIfConfig.pfnQueryBytes = NULL; + + /* Initialize the per-disk interface chain (could be done more globally, + * but it's not wasting much time or space so it's not worth it). */ + int vrc; + vrc = VDInterfaceAdd(&m->vdIfError.Core, + "Medium::vdInterfaceError", + VDINTERFACETYPE_ERROR, this, + sizeof(VDINTERFACEERROR), &m->vdDiskIfaces); + AssertRCReturn(vrc, E_FAIL); + + /* Initialize the per-image interface chain */ + vrc = VDInterfaceAdd(&m->vdIfConfig.Core, + "Medium::vdInterfaceConfig", + VDINTERFACETYPE_CONFIG, this, + sizeof(VDINTERFACECONFIG), &m->vdImageIfaces); + AssertRCReturn(vrc, E_FAIL); + + /* Initialize the callbacks of the VD TCP interface (we always use the host + * IP stack for now) */ + vrc = VDIfTcpNetInstDefaultCreate(&m->hTcpNetInst, &m->vdImageIfaces); + AssertRCReturn(vrc, E_FAIL); + + return BaseFinalConstruct(); +} + +void Medium::FinalRelease() +{ + uninit(); + + VDIfTcpNetInstDefaultDestroy(m->hTcpNetInst); + delete m; + + BaseFinalRelease(); +} + +/** + * Initializes an empty hard disk object without creating or opening an associated + * storage unit. + * + * This gets called by VirtualBox::CreateMedium() in which case uuidMachineRegistry + * is empty since starting with VirtualBox 4.0, we no longer add opened media to a + * registry automatically (this is deferred until the medium is attached to a machine). + * + * This also gets called when VirtualBox creates diff images; in this case uuidMachineRegistry + * is set to the registry of the parent image to make sure they all end up in the same + * file. + * + * For hard disks that don't have the MediumFormatCapabilities_CreateFixed or + * MediumFormatCapabilities_CreateDynamic capability (and therefore cannot be created or deleted + * with the means of VirtualBox) the associated storage unit is assumed to be + * ready for use so the state of the hard disk object will be set to Created. + * + * @param aVirtualBox VirtualBox object. + * @param aFormat + * @param aLocation Storage unit location. + * @param uuidMachineRegistry The registry to which this medium should be added + * (global registry UUID or machine UUID or empty if none). + * @param aDeviceType Device Type. + */ +HRESULT Medium::init(VirtualBox *aVirtualBox, + const Utf8Str &aFormat, + const Utf8Str &aLocation, + const Guid &uuidMachineRegistry, + const DeviceType_T aDeviceType) +{ + AssertReturn(aVirtualBox != NULL, E_FAIL); + AssertReturn(!aFormat.isEmpty(), E_FAIL); + + /* Enclose the state transition NotReady->InInit->Ready */ + AutoInitSpan autoInitSpan(this); + AssertReturn(autoInitSpan.isOk(), E_FAIL); + + HRESULT hrc = S_OK; + + unconst(m->pVirtualBox) = aVirtualBox; + + if (uuidMachineRegistry.isValid() && !uuidMachineRegistry.isZero()) + m->llRegistryIDs.push_back(uuidMachineRegistry); + + /* no storage yet */ + m->state = MediumState_NotCreated; + + /* cannot be a host drive */ + m->hostDrive = false; + + m->devType = aDeviceType; + + /* No storage unit is created yet, no need to call Medium::i_queryInfo */ + + hrc = i_setFormat(aFormat); + if (FAILED(hrc)) return hrc; + + hrc = i_setLocation(aLocation); + if (FAILED(hrc)) return hrc; + + if (!(m->formatObj->i_getCapabilities() & ( MediumFormatCapabilities_CreateFixed + | MediumFormatCapabilities_CreateDynamic + | MediumFormatCapabilities_File)) + ) + { + /* Storage for media of this format can neither be explicitly + * created by VirtualBox nor deleted, so we place the medium to + * Inaccessible state here and also add it to the registry. The + * state means that one has to use RefreshState() to update the + * medium format specific fields. */ + m->state = MediumState_Inaccessible; + // create new UUID + unconst(m->id).create(); + + AutoWriteLock treeLock(m->pVirtualBox->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS); + ComObjPtr<Medium> pMedium; + + /* + * Check whether the UUID is taken already and create a new one + * if required. + * Try this only a limited amount of times in case the PRNG is broken + * in some way to prevent an endless loop. + */ + for (unsigned i = 0; i < 5; i++) + { + bool fInUse; + + fInUse = m->pVirtualBox->i_isMediaUuidInUse(m->id, aDeviceType); + if (fInUse) + { + // create new UUID + unconst(m->id).create(); + } + else + break; + } + + hrc = m->pVirtualBox->i_registerMedium(this, &pMedium, treeLock); + Assert(this == pMedium || FAILED(hrc)); + } + + /* Confirm a successful initialization when it's the case */ + if (SUCCEEDED(hrc)) + autoInitSpan.setSucceeded(); + + return hrc; +} + +/** + * Initializes the medium object by opening the storage unit at the specified + * location. The enOpenMode parameter defines whether the medium will be opened + * read/write or read-only. + * + * This gets called by VirtualBox::OpenMedium() and also by + * Machine::AttachDevice() and createImplicitDiffs() when new diff + * images are created. + * + * There is no registry for this case since starting with VirtualBox 4.0, we + * no longer add opened media to a registry automatically (this is deferred + * until the medium is attached to a machine). + * + * For hard disks, the UUID, format and the parent of this medium will be + * determined when reading the medium storage unit. For DVD and floppy images, + * which have no UUIDs in their storage units, new UUIDs are created. + * If the detected or set parent is not known to VirtualBox, then this method + * will fail. + * + * @param aVirtualBox VirtualBox object. + * @param aLocation Storage unit location. + * @param enOpenMode Whether to open the medium read/write or read-only. + * @param fForceNewUuid Whether a new UUID should be set to avoid duplicates. + * @param aDeviceType Device type of medium. + */ +HRESULT Medium::init(VirtualBox *aVirtualBox, + const Utf8Str &aLocation, + HDDOpenMode enOpenMode, + bool fForceNewUuid, + DeviceType_T aDeviceType) +{ + AssertReturn(aVirtualBox, E_INVALIDARG); + AssertReturn(!aLocation.isEmpty(), E_INVALIDARG); + + HRESULT hrc = S_OK; + + { + /* Enclose the state transition NotReady->InInit->Ready */ + AutoInitSpan autoInitSpan(this); + AssertReturn(autoInitSpan.isOk(), E_FAIL); + + unconst(m->pVirtualBox) = aVirtualBox; + + /* there must be a storage unit */ + m->state = MediumState_Created; + + /* remember device type for correct unregistering later */ + m->devType = aDeviceType; + + /* cannot be a host drive */ + m->hostDrive = false; + + /* remember the open mode (defaults to ReadWrite) */ + m->hddOpenMode = enOpenMode; + + if (aDeviceType == DeviceType_DVD) + m->type = MediumType_Readonly; + else if (aDeviceType == DeviceType_Floppy) + m->type = MediumType_Writethrough; + + hrc = i_setLocation(aLocation); + if (FAILED(hrc)) return hrc; + + /* get all the information about the medium from the storage unit */ + if (fForceNewUuid) + unconst(m->uuidImage).create(); + + m->state = MediumState_Inaccessible; + m->strLastAccessError = tr("Accessibility check was not yet performed"); + + /* Confirm a successful initialization before the call to i_queryInfo. + * Otherwise we can end up with a AutoCaller deadlock because the + * medium becomes visible but is not marked as initialized. Causes + * locking trouble (e.g. trying to save media registries) which is + * hard to solve. */ + autoInitSpan.setSucceeded(); + } + + /* we're normal code from now on, no longer init */ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.hrc())) + return autoCaller.hrc(); + + /* need to call i_queryInfo immediately to correctly place the medium in + * the respective media tree and update other information such as uuid */ + hrc = i_queryInfo(fForceNewUuid /* fSetImageId */, false /* fSetParentId */, autoCaller); + if (SUCCEEDED(hrc)) + { + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + /* if the storage unit is not accessible, it's not acceptable for the + * newly opened media so convert this into an error */ + if (m->state == MediumState_Inaccessible) + { + Assert(!m->strLastAccessError.isEmpty()); + hrc = setError(E_FAIL, "%s", m->strLastAccessError.c_str()); + alock.release(); + autoCaller.release(); + uninit(); + } + else + { + AssertStmt(!m->id.isZero(), + alock.release(); autoCaller.release(); uninit(); return E_FAIL); + + /* storage format must be detected by Medium::i_queryInfo if the + * medium is accessible */ + AssertStmt(!m->strFormat.isEmpty(), + alock.release(); autoCaller.release(); uninit(); return E_FAIL); + } + } + else + { + /* opening this image failed, mark the object as dead */ + autoCaller.release(); + uninit(); + } + + return hrc; +} + +/** + * Initializes the medium object by loading its data from the given settings + * node. The medium will always be opened read/write. + * + * In this case, since we're loading from a registry, uuidMachineRegistry is + * always set: it's either the global registry UUID or a machine UUID when + * loading from a per-machine registry. + * + * @param aParent Parent medium disk or NULL for a root (base) medium. + * @param aDeviceType Device type of the medium. + * @param uuidMachineRegistry The registry to which this medium should be + * added (global registry UUID or machine UUID). + * @param data Configuration settings. + * @param strMachineFolder The machine folder with which to resolve relative paths; + * if empty, then we use the VirtualBox home directory + * + * @note Locks the medium tree for writing. + */ +HRESULT Medium::initOne(Medium *aParent, + DeviceType_T aDeviceType, + const Guid &uuidMachineRegistry, + const Utf8Str &strMachineFolder, + const settings::Medium &data) +{ + HRESULT hrc; + + if (uuidMachineRegistry.isValid() && !uuidMachineRegistry.isZero()) + m->llRegistryIDs.push_back(uuidMachineRegistry); + + /* register with VirtualBox/parent early, since uninit() will + * unconditionally unregister on failure */ + if (aParent) + { + // differencing medium: add to parent + AutoWriteLock treeLock(m->pVirtualBox->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS); + // no need to check maximum depth as settings reading did it + i_setParent(aParent); + } + + /* see below why we don't call Medium::i_queryInfo (and therefore treat + * the medium as inaccessible for now */ + m->state = MediumState_Inaccessible; + m->strLastAccessError = tr("Accessibility check was not yet performed"); + + /* required */ + unconst(m->id) = data.uuid; + + /* assume not a host drive */ + m->hostDrive = false; + + /* optional */ + m->strDescription = data.strDescription; + + /* required */ + if (aDeviceType == DeviceType_HardDisk) + { + AssertReturn(!data.strFormat.isEmpty(), E_FAIL); + hrc = i_setFormat(data.strFormat); + if (FAILED(hrc)) return hrc; + } + else + { + /// @todo handle host drive settings here as well? + if (!data.strFormat.isEmpty()) + hrc = i_setFormat(data.strFormat); + else + hrc = i_setFormat("RAW"); + if (FAILED(hrc)) return hrc; + } + + /* optional, only for diffs, default is false; we can only auto-reset + * diff media so they must have a parent */ + if (aParent != NULL) + m->autoReset = data.fAutoReset; + else + m->autoReset = false; + + /* properties (after setting the format as it populates the map). Note that + * if some properties are not supported but present in the settings file, + * they will still be read and accessible (for possible backward + * compatibility; we can also clean them up from the XML upon next + * XML format version change if we wish) */ + for (settings::StringsMap::const_iterator it = data.properties.begin(); + it != data.properties.end(); + ++it) + { + const Utf8Str &name = it->first; + const Utf8Str &value = it->second; + m->mapProperties[name] = value; + } + + /* try to decrypt an optional iSCSI initiator secret */ + settings::StringsMap::const_iterator itCph = data.properties.find("InitiatorSecretEncrypted"); + if ( itCph != data.properties.end() + && !itCph->second.isEmpty()) + { + Utf8Str strPlaintext; + int vrc = m->pVirtualBox->i_decryptSetting(&strPlaintext, itCph->second); + if (RT_SUCCESS(vrc)) + m->mapProperties["InitiatorSecret"] = strPlaintext; + } + + Utf8Str strFull; + if (m->formatObj->i_getCapabilities() & MediumFormatCapabilities_File) + { + // compose full path of the medium, if it's not fully qualified... + // slightly convoluted logic here. If the caller has given us a + // machine folder, then a relative path will be relative to that: + if ( !strMachineFolder.isEmpty() + && !RTPathStartsWithRoot(data.strLocation.c_str()) + ) + { + strFull = strMachineFolder; + strFull += RTPATH_SLASH; + strFull += data.strLocation; + } + else + { + // Otherwise use the old VirtualBox "make absolute path" logic: + int vrc = m->pVirtualBox->i_calculateFullPath(data.strLocation, strFull); + if (RT_FAILURE(vrc)) + return Global::vboxStatusCodeToCOM(vrc); + } + } + else + strFull = data.strLocation; + + hrc = i_setLocation(strFull); + if (FAILED(hrc)) return hrc; + + if (aDeviceType == DeviceType_HardDisk) + { + /* type is only for base hard disks */ + if (m->pParent.isNull()) + m->type = data.hdType; + } + else if (aDeviceType == DeviceType_DVD) + m->type = MediumType_Readonly; + else + m->type = MediumType_Writethrough; + + /* remember device type for correct unregistering later */ + m->devType = aDeviceType; + + LogFlowThisFunc(("m->strLocationFull='%s', m->strFormat=%s, m->id={%RTuuid}\n", + m->strLocationFull.c_str(), m->strFormat.c_str(), m->id.raw())); + + return S_OK; +} + +/** + * Initializes and registers the medium object and its children by loading its + * data from the given settings node. The medium will always be opened + * read/write. + * + * @todo r=bird: What's that stuff about 'always be opened read/write'? + * + * In this case, since we're loading from a registry, uuidMachineRegistry is + * always set: it's either the global registry UUID or a machine UUID when + * loading from a per-machine registry. + * + * The only caller is currently VirtualBox::initMedia(). + * + * @param aVirtualBox VirtualBox object. + * @param aDeviceType Device type of the medium. + * @param uuidMachineRegistry The registry to which this medium should be added + * (global registry UUID or machine UUID). + * @param strMachineFolder The machine folder with which to resolve relative + * paths; if empty, then we use the VirtualBox home directory + * @param data Configuration settings. + * @param mediaTreeLock Autolock. + * @param uIdsForNotify List to be updated with newly registered media. + * + * @note Assumes that the medium tree lock is held for writing. May release + * and lock it again. At the end it is always held. + */ +/* static */ +HRESULT Medium::initFromSettings(VirtualBox *aVirtualBox, + DeviceType_T aDeviceType, + const Guid &uuidMachineRegistry, + const Utf8Str &strMachineFolder, + const settings::Medium &data, + AutoWriteLock &mediaTreeLock, + std::list<std::pair<Guid, DeviceType_T> > &uIdsForNotify) +{ + Assert(aVirtualBox->i_getMediaTreeLockHandle().isWriteLockOnCurrentThread()); + AssertReturn(aVirtualBox, E_INVALIDARG); + + HRESULT hrc = S_OK; + + MediaList llMediaTocleanup; + + std::list<const settings::Medium *> llSettingsTodo; + llSettingsTodo.push_back(&data); + MediaList llParentsTodo; + llParentsTodo.push_back(NULL); + + while (!llSettingsTodo.empty()) + { + const settings::Medium *current = llSettingsTodo.front(); + llSettingsTodo.pop_front(); + ComObjPtr<Medium> pParent = llParentsTodo.front(); + llParentsTodo.pop_front(); + + bool fReleasedMediaTreeLock = false; + ComObjPtr<Medium> pMedium; + hrc = pMedium.createObject(); + if (FAILED(hrc)) + break; + ComObjPtr<Medium> pActualMedium(pMedium); + + { + AutoInitSpan autoInitSpan(pMedium); + AssertBreakStmt(autoInitSpan.isOk(), hrc = E_FAIL); + + unconst(pMedium->m->pVirtualBox) = aVirtualBox; + hrc = pMedium->initOne(pParent, aDeviceType, uuidMachineRegistry, strMachineFolder, *current); + if (FAILED(hrc)) + break; + hrc = aVirtualBox->i_registerMedium(pActualMedium, &pActualMedium, mediaTreeLock, true /*fCalledFromMediumInit*/); + if (SUCCEEDED(hrc) && pActualMedium == pMedium) + { + /* It is a truly new medium, remember details for cleanup. */ + autoInitSpan.setSucceeded(); + llMediaTocleanup.push_front(pMedium); + } + else + { + /* Since the newly created medium was replaced by an already + * known one when merging medium trees, we can immediately mark + * it as failed. */ + autoInitSpan.setFailed(); + mediaTreeLock.release(); + fReleasedMediaTreeLock = true; + } + } + if (fReleasedMediaTreeLock) + { + /* With the InitSpan out of the way it's safe to let the refcount + * drop to 0 without causing uninit trouble. */ + pMedium.setNull(); + mediaTreeLock.acquire(); + + if (FAILED(hrc)) + break; + } + + /* create all children */ + std::list<settings::Medium>::const_iterator itBegin = current->llChildren.begin(); + std::list<settings::Medium>::const_iterator itEnd = current->llChildren.end(); + for (std::list<settings::Medium>::const_iterator it = itBegin; it != itEnd; ++it) + { + llSettingsTodo.push_back(&*it); + llParentsTodo.push_back(pActualMedium); + } + } + + if (SUCCEEDED(hrc)) + { + /* Check for consistency. */ + Assert(llSettingsTodo.size() == 0); + Assert(llParentsTodo.size() == 0); + /* Create the list of notifications, parent first. */ + for (MediaList::const_reverse_iterator it = llMediaTocleanup.rbegin(); it != llMediaTocleanup.rend(); ++it) + { + ComObjPtr<Medium> pMedium = *it; + AutoCaller mediumCaller(pMedium); + if (mediumCaller.isOk()) + { + const Guid &id = pMedium->i_getId(); + uIdsForNotify.push_back(std::pair<Guid, DeviceType_T>(id, aDeviceType)); + } + } + } + else + { + /* Forget state of the settings processing. */ + llSettingsTodo.clear(); + llParentsTodo.clear(); + /* Unregister all accumulated medium objects in the right order (last + * created to first created, avoiding config leftovers). */ + MediaList::const_iterator itBegin = llMediaTocleanup.begin(); + MediaList::const_iterator itEnd = llMediaTocleanup.end(); + for (MediaList::const_iterator it = itBegin; it != itEnd; ++it) + { + ComObjPtr<Medium> pMedium = *it; + pMedium->i_unregisterWithVirtualBox(); + } + /* Forget the only references to all newly created medium objects, + * triggering freeing (uninit happened in unregistering above). */ + mediaTreeLock.release(); + llMediaTocleanup.clear(); + mediaTreeLock.acquire(); + } + + return hrc; +} + +/** + * Initializes the medium object by providing the host drive information. + * Not used for anything but the host floppy/host DVD case. + * + * There is no registry for this case. + * + * @param aVirtualBox VirtualBox object. + * @param aDeviceType Device type of the medium. + * @param aLocation Location of the host drive. + * @param aDescription Comment for this host drive. + * + * @note Locks VirtualBox lock for writing. + */ +HRESULT Medium::init(VirtualBox *aVirtualBox, + DeviceType_T aDeviceType, + const Utf8Str &aLocation, + const Utf8Str &aDescription /* = Utf8Str::Empty */) +{ + ComAssertRet(aDeviceType == DeviceType_DVD || aDeviceType == DeviceType_Floppy, E_INVALIDARG); + ComAssertRet(!aLocation.isEmpty(), E_INVALIDARG); + + /* Enclose the state transition NotReady->InInit->Ready */ + AutoInitSpan autoInitSpan(this); + AssertReturn(autoInitSpan.isOk(), E_FAIL); + + unconst(m->pVirtualBox) = aVirtualBox; + + // We do not store host drives in VirtualBox.xml or anywhere else, so if we want + // host drives to be identifiable by UUID and not give the drive a different UUID + // every time VirtualBox starts, we need to fake a reproducible UUID here: + RTUUID uuid; + RTUuidClear(&uuid); + if (aDeviceType == DeviceType_DVD) + memcpy(&uuid.au8[0], "DVD", 3); + else + memcpy(&uuid.au8[0], "FD", 2); + /* use device name, adjusted to the end of uuid, shortened if necessary */ + size_t lenLocation = aLocation.length(); + if (lenLocation > 12) + memcpy(&uuid.au8[4], aLocation.c_str() + (lenLocation - 12), 12); + else + memcpy(&uuid.au8[4 + 12 - lenLocation], aLocation.c_str(), lenLocation); + unconst(m->id) = uuid; + + if (aDeviceType == DeviceType_DVD) + m->type = MediumType_Readonly; + else + m->type = MediumType_Writethrough; + m->devType = aDeviceType; + m->state = MediumState_Created; + m->hostDrive = true; + HRESULT hrc = i_setFormat("RAW"); + if (FAILED(hrc)) return hrc; + hrc = i_setLocation(aLocation); + if (FAILED(hrc)) return hrc; + m->strDescription = aDescription; + + autoInitSpan.setSucceeded(); + return S_OK; +} + +/** + * Uninitializes the instance. + * + * Called either from FinalRelease() or by the parent when it gets destroyed. + * + * @note All children of this medium get uninitialized, too, in a stack + * friendly manner. + */ +void Medium::uninit() +{ + /* It is possible that some previous/concurrent uninit has already cleared + * the pVirtualBox reference, and in this case we don't need to continue. + * Normally this would be handled through the AutoUninitSpan magic, however + * this cannot be done at this point as the media tree must be locked + * before reaching the AutoUninitSpan, otherwise deadlocks can happen. + * + * NOTE: The tree lock is higher priority than the medium caller and medium + * object locks, i.e. the medium caller may have to be released and be + * re-acquired in the right place later. See Medium::getParent() for sample + * code how to do this safely. */ + VirtualBox *pVirtualBox = m->pVirtualBox; + if (!pVirtualBox) + return; + + /* Caller must not hold the object (checked below) or media tree lock. */ + Assert(!pVirtualBox->i_getMediaTreeLockHandle().isWriteLockOnCurrentThread()); + + AutoWriteLock treeLock(pVirtualBox->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS); + + /* Must use a list without refcounting help since "this" might already have + * reached 0, and then the refcount must not be increased again since it + * would otherwise trigger a double free. For all other list entries this + * needs manual refcount updating, to make sure the refcount for children + * does not drop to 0 too early. */ + std::list<Medium *> llMediaTodo; + llMediaTodo.push_back(this); + + while (!llMediaTodo.empty()) + { + Medium *pMedium = llMediaTodo.front(); + llMediaTodo.pop_front(); + + /* Enclose the state transition Ready->InUninit->NotReady */ + AutoUninitSpan autoUninitSpan(pMedium); + if (autoUninitSpan.uninitDone()) + { + if (pMedium != this) + pMedium->Release(); + continue; + } + + Assert(!pMedium->isWriteLockOnCurrentThread()); +#ifdef DEBUG + if (!pMedium->m->backRefs.empty()) + pMedium->i_dumpBackRefs(); +#endif + Assert(pMedium->m->backRefs.empty()); + + pMedium->m->formatObj.setNull(); + + if (pMedium->m->state == MediumState_Deleting) + { + /* This medium has been already deleted (directly or as part of a + * merge). Reparenting has already been done. */ + Assert(pMedium->m->pParent.isNull()); + Assert(pMedium->m->llChildren.empty()); + if (pMedium != this) + pMedium->Release(); + continue; + } + + //Assert(!pMedium->m->pParent); + /** @todo r=klaus Should not be necessary, since the caller should be + * doing the deparenting. No time right now to test everything. */ + if (pMedium == this && pMedium->m->pParent) + pMedium->i_deparent(); + + /* Process all children */ + MediaList::const_iterator itBegin = pMedium->m->llChildren.begin(); + MediaList::const_iterator itEnd = pMedium->m->llChildren.end(); + for (MediaList::const_iterator it = itBegin; it != itEnd; ++it) + { + Medium *pChild = *it; + pChild->m->pParent.setNull(); + pChild->AddRef(); + llMediaTodo.push_back(pChild); + } + + /* Children information obsolete, will be processed anyway. */ + pMedium->m->llChildren.clear(); + + unconst(pMedium->m->pVirtualBox) = NULL; + + if (pMedium != this) + pMedium->Release(); + + autoUninitSpan.setSucceeded(); + } +} + +/** + * Internal helper that removes "this" from the list of children of its + * parent. Used in uninit() and other places when reparenting is necessary. + * + * The caller must hold the medium tree lock! + */ +void Medium::i_deparent() +{ + MediaList &llParent = m->pParent->m->llChildren; + for (MediaList::iterator it = llParent.begin(); + it != llParent.end(); + ++it) + { + Medium *pParentsChild = *it; + if (this == pParentsChild) + { + llParent.erase(it); + break; + } + } + m->pParent.setNull(); +} + +/** + * Internal helper that removes "this" from the list of children of its + * parent. Used in uninit() and other places when reparenting is necessary. + * + * The caller must hold the medium tree lock! + */ +void Medium::i_setParent(const ComObjPtr<Medium> &pParent) +{ + m->pParent = pParent; + if (pParent) + pParent->m->llChildren.push_back(this); +} + + +//////////////////////////////////////////////////////////////////////////////// +// +// IMedium public methods +// +//////////////////////////////////////////////////////////////////////////////// + +HRESULT Medium::getId(com::Guid &aId) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + aId = m->id; + + return S_OK; +} + +HRESULT Medium::getDescription(AutoCaller &autoCaller, com::Utf8Str &aDescription) +{ + NOREF(autoCaller); + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + aDescription = m->strDescription; + + return S_OK; +} + +HRESULT Medium::setDescription(AutoCaller &autoCaller, const com::Utf8Str &aDescription) +{ + /// @todo update m->strDescription and save the global registry (and local + /// registries of portable VMs referring to this medium), this will also + /// require to add the mRegistered flag to data + + HRESULT hrc = S_OK; + + MediumLockList *pMediumLockList(new MediumLockList()); + + try + { + autoCaller.release(); + + // to avoid redundant locking, which just takes a time, just call required functions. + // the error will be just stored and will be reported after locks will be acquired again + + const char *pszError = NULL; + + + /* Build the lock list. */ + hrc = i_createMediumLockList(true /* fFailIfInaccessible */, + this /* pToLockWrite */, + true /* fMediumLockWriteAll */, + NULL, + *pMediumLockList); + if (FAILED(hrc)) + pszError = tr("Failed to create medium lock list for '%s'"); + else + { + hrc = pMediumLockList->Lock(); + if (FAILED(hrc)) + pszError = tr("Failed to lock media '%s'"); + } + + // locking: we need the tree lock first because we access parent pointers + // and we need to write-lock the media involved + AutoWriteLock treeLock(m->pVirtualBox->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS); + + autoCaller.add(); + AssertComRCThrowRC(autoCaller.hrc()); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + if (FAILED(hrc)) + throw setError(hrc, pszError, i_getLocationFull().c_str()); + + /* Set a new description */ + m->strDescription = aDescription; + + // save the settings + alock.release(); + autoCaller.release(); + treeLock.release(); + i_markRegistriesModified(); + m->pVirtualBox->i_saveModifiedRegistries(); + m->pVirtualBox->i_onMediumConfigChanged(this); + } + catch (HRESULT hrcXcpt) { hrc = hrcXcpt; } + + delete pMediumLockList; + + return hrc; +} + +HRESULT Medium::getState(MediumState_T *aState) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + *aState = m->state; + + return S_OK; +} + +HRESULT Medium::getVariant(std::vector<MediumVariant_T> &aVariant) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + const size_t cBits = sizeof(MediumVariant_T) * 8; + aVariant.resize(cBits); + for (size_t i = 0; i < cBits; ++i) + aVariant[i] = (MediumVariant_T)(m->variant & RT_BIT(i)); + + return S_OK; +} + +HRESULT Medium::getLocation(com::Utf8Str &aLocation) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + aLocation = m->strLocationFull; + + return S_OK; +} + +HRESULT Medium::getName(com::Utf8Str &aName) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + aName = i_getName(); + + return S_OK; +} + +HRESULT Medium::getDeviceType(DeviceType_T *aDeviceType) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + *aDeviceType = m->devType; + + return S_OK; +} + +HRESULT Medium::getHostDrive(BOOL *aHostDrive) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + *aHostDrive = m->hostDrive; + + return S_OK; +} + +HRESULT Medium::getSize(LONG64 *aSize) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + *aSize = (LONG64)m->size; + + return S_OK; +} + +HRESULT Medium::getFormat(com::Utf8Str &aFormat) +{ + /* no need to lock, m->strFormat is const */ + + aFormat = m->strFormat; + return S_OK; +} + +HRESULT Medium::getMediumFormat(ComPtr<IMediumFormat> &aMediumFormat) +{ + /* no need to lock, m->formatObj is const */ + m->formatObj.queryInterfaceTo(aMediumFormat.asOutParam()); + + return S_OK; +} + +HRESULT Medium::getType(AutoCaller &autoCaller, MediumType_T *aType) +{ + NOREF(autoCaller); + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + *aType = m->type; + + return S_OK; +} + +HRESULT Medium::setType(AutoCaller &autoCaller, MediumType_T aType) +{ + autoCaller.release(); + + /* It is possible that some previous/concurrent uninit has already cleared + * the pVirtualBox reference, see #uninit(). */ + ComObjPtr<VirtualBox> pVirtualBox(m->pVirtualBox); + + // we access m->pParent + AutoReadLock treeLock(!pVirtualBox.isNull() ? &pVirtualBox->i_getMediaTreeLockHandle() : NULL COMMA_LOCKVAL_SRC_POS); + + autoCaller.add(); + if (FAILED(autoCaller.hrc())) return autoCaller.hrc(); + + AutoWriteLock mlock(this COMMA_LOCKVAL_SRC_POS); + + /* Wait for a concurrently running Medium::i_queryInfo to complete. */ + while (m->queryInfoRunning) + { + mlock.release(); + autoCaller.release(); + treeLock.release(); + /* Must not hold the media tree lock, as Medium::i_queryInfo needs + * this lock and thus we would run into a deadlock here. */ + Assert(!m->pVirtualBox->i_getMediaTreeLockHandle().isWriteLockOnCurrentThread()); + /* must not hold the object lock now */ + Assert(!isWriteLockOnCurrentThread()); + { + AutoReadLock qlock(m->queryInfoSem COMMA_LOCKVAL_SRC_POS); + } + treeLock.acquire(); + autoCaller.add(); + if (FAILED(autoCaller.hrc())) return autoCaller.hrc(); + mlock.acquire(); + } + + switch (m->state) + { + case MediumState_Created: + case MediumState_Inaccessible: + break; + default: + return i_setStateError(); + } + + if (m->type == aType) + { + /* Nothing to do */ + return S_OK; + } + + DeviceType_T devType = i_getDeviceType(); + // DVD media can only be readonly. + if (devType == DeviceType_DVD && aType != MediumType_Readonly) + return setError(VBOX_E_INVALID_OBJECT_STATE, + tr("Cannot change the type of DVD medium '%s'"), + m->strLocationFull.c_str()); + // Floppy media can only be writethrough or readonly. + if ( devType == DeviceType_Floppy + && aType != MediumType_Writethrough + && aType != MediumType_Readonly) + return setError(VBOX_E_INVALID_OBJECT_STATE, + tr("Cannot change the type of floppy medium '%s'"), + m->strLocationFull.c_str()); + + /* cannot change the type of a differencing medium */ + if (m->pParent) + return setError(VBOX_E_INVALID_OBJECT_STATE, + tr("Cannot change the type of medium '%s' because it is a differencing medium"), + m->strLocationFull.c_str()); + + /* Cannot change the type of a medium being in use by more than one VM. + * If the change is to Immutable or MultiAttach then it must not be + * directly attached to any VM, otherwise the assumptions about indirect + * attachment elsewhere are violated and the VM becomes inaccessible. + * Attaching an immutable medium triggers the diff creation, and this is + * vital for the correct operation. */ + if ( m->backRefs.size() > 1 + || ( ( aType == MediumType_Immutable + || aType == MediumType_MultiAttach) + && m->backRefs.size() > 0)) + return setError(VBOX_E_INVALID_OBJECT_STATE, + tr("Cannot change the type of medium '%s' because it is attached to %d virtual machines", + "", m->backRefs.size()), + m->strLocationFull.c_str(), m->backRefs.size()); + + switch (aType) + { + case MediumType_Normal: + case MediumType_Immutable: + case MediumType_MultiAttach: + { + /* normal can be easily converted to immutable and vice versa even + * if they have children as long as they are not attached to any + * machine themselves */ + break; + } + case MediumType_Writethrough: + case MediumType_Shareable: + case MediumType_Readonly: + { + /* cannot change to writethrough, shareable or readonly + * if there are children */ + if (i_getChildren().size() != 0) + return setError(VBOX_E_OBJECT_IN_USE, + tr("Cannot change type for medium '%s' since it has %d child media", "", i_getChildren().size()), + m->strLocationFull.c_str(), i_getChildren().size()); + if (aType == MediumType_Shareable) + { + MediumVariant_T variant = i_getVariant(); + if (!(variant & MediumVariant_Fixed)) + return setError(VBOX_E_INVALID_OBJECT_STATE, + tr("Cannot change type for medium '%s' to 'Shareable' since it is a dynamic medium storage unit"), + m->strLocationFull.c_str()); + } + else if (aType == MediumType_Readonly && devType == DeviceType_HardDisk) + { + // Readonly hard disks are not allowed, this medium type is reserved for + // DVDs and floppy images at the moment. Later we might allow readonly hard + // disks, but that's extremely unusual and many guest OSes will have trouble. + return setError(VBOX_E_INVALID_OBJECT_STATE, + tr("Cannot change type for medium '%s' to 'Readonly' since it is a hard disk"), + m->strLocationFull.c_str()); + } + break; + } + default: + AssertFailedReturn(E_FAIL); + } + + if (aType == MediumType_MultiAttach) + { + // This type is new with VirtualBox 4.0 and therefore requires settings + // version 1.11 in the settings backend. Unfortunately it is not enough to do + // the usual routine in MachineConfigFile::bumpSettingsVersionIfNeeded() for + // two reasons: The medium type is a property of the media registry tree, which + // can reside in the global config file (for pre-4.0 media); we would therefore + // possibly need to bump the global config version. We don't want to do that though + // because that might make downgrading to pre-4.0 impossible. + // As a result, we can only use these two new types if the medium is NOT in the + // global registry: + const Guid &uuidGlobalRegistry = m->pVirtualBox->i_getGlobalRegistryId(); + if (i_isInRegistry(uuidGlobalRegistry)) + return setError(VBOX_E_INVALID_OBJECT_STATE, + tr("Cannot change type for medium '%s': the media type 'MultiAttach' can only be used " + "on media registered with a machine that was created with VirtualBox 4.0 or later"), + m->strLocationFull.c_str()); + } + + m->type = aType; + + // save the settings + mlock.release(); + autoCaller.release(); + treeLock.release(); + i_markRegistriesModified(); + m->pVirtualBox->i_saveModifiedRegistries(); + m->pVirtualBox->i_onMediumConfigChanged(this); + + return S_OK; +} + +HRESULT Medium::getAllowedTypes(std::vector<MediumType_T> &aAllowedTypes) +{ + NOREF(aAllowedTypes); + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + ReturnComNotImplemented(); +} + +HRESULT Medium::getParent(AutoCaller &autoCaller, ComPtr<IMedium> &aParent) +{ + autoCaller.release(); + + /* It is possible that some previous/concurrent uninit has already cleared + * the pVirtualBox reference, see #uninit(). */ + ComObjPtr<VirtualBox> pVirtualBox(m->pVirtualBox); + + /* we access m->pParent */ + AutoReadLock treeLock(!pVirtualBox.isNull() ? &pVirtualBox->i_getMediaTreeLockHandle() : NULL COMMA_LOCKVAL_SRC_POS); + + autoCaller.add(); + if (FAILED(autoCaller.hrc())) return autoCaller.hrc(); + + m->pParent.queryInterfaceTo(aParent.asOutParam()); + + return S_OK; +} + +HRESULT Medium::getChildren(AutoCaller &autoCaller, std::vector<ComPtr<IMedium> > &aChildren) +{ + autoCaller.release(); + + /* It is possible that some previous/concurrent uninit has already cleared + * the pVirtualBox reference, see #uninit(). */ + ComObjPtr<VirtualBox> pVirtualBox(m->pVirtualBox); + + /* we access children */ + AutoReadLock treeLock(!pVirtualBox.isNull() ? &pVirtualBox->i_getMediaTreeLockHandle() : NULL COMMA_LOCKVAL_SRC_POS); + + autoCaller.add(); + if (FAILED(autoCaller.hrc())) return autoCaller.hrc(); + + MediaList children(this->i_getChildren()); + aChildren.resize(children.size()); + size_t i = 0; + for (MediaList::const_iterator it = children.begin(); it != children.end(); ++it, ++i) + (*it).queryInterfaceTo(aChildren[i].asOutParam()); + return S_OK; +} + +HRESULT Medium::getBase(AutoCaller &autoCaller, ComPtr<IMedium> &aBase) +{ + autoCaller.release(); + + /* i_getBase() will do callers/locking */ + i_getBase().queryInterfaceTo(aBase.asOutParam()); + + return S_OK; +} + +HRESULT Medium::getReadOnly(AutoCaller &autoCaller, BOOL *aReadOnly) +{ + autoCaller.release(); + + /* isReadOnly() will do locking */ + *aReadOnly = i_isReadOnly(); + + return S_OK; +} + +HRESULT Medium::getLogicalSize(LONG64 *aLogicalSize) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + *aLogicalSize = (LONG64)m->logicalSize; + + return S_OK; +} + +HRESULT Medium::getAutoReset(BOOL *aAutoReset) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + if (m->pParent.isNull()) + *aAutoReset = FALSE; + else + *aAutoReset = m->autoReset; + + return S_OK; +} + +HRESULT Medium::setAutoReset(BOOL aAutoReset) +{ + AutoWriteLock mlock(this COMMA_LOCKVAL_SRC_POS); + + if (m->pParent.isNull()) + return setError(VBOX_E_NOT_SUPPORTED, + tr("Medium '%s' is not differencing"), + m->strLocationFull.c_str()); + + if (m->autoReset != !!aAutoReset) + { + m->autoReset = !!aAutoReset; + + // save the settings + mlock.release(); + i_markRegistriesModified(); + m->pVirtualBox->i_saveModifiedRegistries(); + m->pVirtualBox->i_onMediumConfigChanged(this); + } + + return S_OK; +} + +HRESULT Medium::getLastAccessError(com::Utf8Str &aLastAccessError) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + aLastAccessError = m->strLastAccessError; + + return S_OK; +} + +HRESULT Medium::getMachineIds(std::vector<com::Guid> &aMachineIds) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + if (m->backRefs.size() != 0) + { + BackRefList brlist(m->backRefs); + aMachineIds.resize(brlist.size()); + size_t i = 0; + for (BackRefList::const_iterator it = brlist.begin(); it != brlist.end(); ++it, ++i) + aMachineIds[i] = it->machineId; + } + + return S_OK; +} + +HRESULT Medium::setIds(AutoCaller &autoCaller, + BOOL aSetImageId, + const com::Guid &aImageId, + BOOL aSetParentId, + const com::Guid &aParentId) +{ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + /* Wait for a concurrently running Medium::i_queryInfo to complete. */ + if (m->queryInfoRunning) + { + /* Must not hold the media tree lock, as Medium::i_queryInfo needs this + * lock and thus we would run into a deadlock here. */ + Assert(!m->pVirtualBox->i_getMediaTreeLockHandle().isWriteLockOnCurrentThread()); + while (m->queryInfoRunning) + { + alock.release(); + /* must not hold the object lock now */ + Assert(!isWriteLockOnCurrentThread()); + { + AutoReadLock qlock(m->queryInfoSem COMMA_LOCKVAL_SRC_POS); + } + alock.acquire(); + } + } + + switch (m->state) + { + case MediumState_Created: + break; + default: + return i_setStateError(); + } + + Guid imageId, parentId; + if (aSetImageId) + { + if (aImageId.isZero()) + imageId.create(); + else + { + imageId = aImageId; + if (!imageId.isValid()) + return setError(E_INVALIDARG, tr("Argument %s is invalid"), "aImageId"); + } + } + if (aSetParentId) + { + if (aParentId.isZero()) + parentId.create(); + else + parentId = aParentId; + } + + const Guid uPrevImage = m->uuidImage; + unconst(m->uuidImage) = imageId; + ComObjPtr<Medium> pPrevParent = i_getParent(); + unconst(m->uuidParentImage) = parentId; + + // must not hold any locks before calling Medium::i_queryInfo + alock.release(); + + HRESULT hrc = i_queryInfo(!!aSetImageId /* fSetImageId */, + !!aSetParentId /* fSetParentId */, + autoCaller); + + AutoReadLock arlock(this COMMA_LOCKVAL_SRC_POS); + const Guid uCurrImage = m->uuidImage; + ComObjPtr<Medium> pCurrParent = i_getParent(); + arlock.release(); + + if (SUCCEEDED(hrc)) + { + if (uCurrImage != uPrevImage) + m->pVirtualBox->i_onMediumConfigChanged(this); + if (pPrevParent != pCurrParent) + { + if (pPrevParent) + m->pVirtualBox->i_onMediumConfigChanged(pPrevParent); + if (pCurrParent) + m->pVirtualBox->i_onMediumConfigChanged(pCurrParent); + } + } + + return hrc; +} + +HRESULT Medium::refreshState(AutoCaller &autoCaller, MediumState_T *aState) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + HRESULT hrc = S_OK; + + switch (m->state) + { + case MediumState_Created: + case MediumState_Inaccessible: + case MediumState_LockedRead: + { + // must not hold any locks before calling Medium::i_queryInfo + alock.release(); + + hrc = i_queryInfo(false /* fSetImageId */, false /* fSetParentId */, autoCaller); + + alock.acquire(); + break; + } + default: + break; + } + + *aState = m->state; + + return hrc; +} + +HRESULT Medium::getSnapshotIds(const com::Guid &aMachineId, + std::vector<com::Guid> &aSnapshotIds) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + for (BackRefList::const_iterator it = m->backRefs.begin(); + it != m->backRefs.end(); ++it) + { + if (it->machineId == aMachineId) + { + size_t size = it->llSnapshotIds.size(); + + /* if the medium is attached to the machine in the current state, we + * return its ID as the first element of the array */ + if (it->fInCurState) + ++size; + + if (size > 0) + { + aSnapshotIds.resize(size); + + size_t j = 0; + if (it->fInCurState) + aSnapshotIds[j++] = it->machineId.toUtf16(); + + for(std::list<SnapshotRef>::const_iterator jt = it->llSnapshotIds.begin(); jt != it->llSnapshotIds.end(); ++jt, ++j) + aSnapshotIds[j] = jt->snapshotId; + } + + break; + } + } + + return S_OK; +} + +HRESULT Medium::lockRead(ComPtr<IToken> &aToken) +{ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + /* Wait for a concurrently running Medium::i_queryInfo to complete. */ + if (m->queryInfoRunning) + { + /* Must not hold the media tree lock, as Medium::i_queryInfo needs this + * lock and thus we would run into a deadlock here. */ + Assert(!m->pVirtualBox->i_getMediaTreeLockHandle().isWriteLockOnCurrentThread()); + while (m->queryInfoRunning) + { + alock.release(); + /* must not hold the object lock now */ + Assert(!isWriteLockOnCurrentThread()); + { + AutoReadLock qlock(m->queryInfoSem COMMA_LOCKVAL_SRC_POS); + } + alock.acquire(); + } + } + + HRESULT hrc = S_OK; + + switch (m->state) + { + case MediumState_Created: + case MediumState_Inaccessible: + case MediumState_LockedRead: + { + ++m->readers; + + ComAssertMsgBreak(m->readers != 0, (tr("Counter overflow")), hrc = E_FAIL); + + /* Remember pre-lock state */ + if (m->state != MediumState_LockedRead) + m->preLockState = m->state; + + LogFlowThisFunc(("Okay - prev state=%d readers=%d\n", m->state, m->readers)); + m->state = MediumState_LockedRead; + + ComObjPtr<MediumLockToken> pToken; + hrc = pToken.createObject(); + if (SUCCEEDED(hrc)) + hrc = pToken->init(this, false /* fWrite */); + if (FAILED(hrc)) + { + --m->readers; + if (m->readers == 0) + m->state = m->preLockState; + return hrc; + } + + pToken.queryInterfaceTo(aToken.asOutParam()); + break; + } + default: + { + LogFlowThisFunc(("Failing - state=%d\n", m->state)); + hrc = i_setStateError(); + break; + } + } + + return hrc; +} + +/** + * @note @a aState may be NULL if the state value is not needed (only for + * in-process calls). + */ +HRESULT Medium::i_unlockRead(MediumState_T *aState) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.hrc())) return autoCaller.hrc(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + HRESULT hrc = S_OK; + + switch (m->state) + { + case MediumState_LockedRead: + { + ComAssertMsgBreak(m->readers != 0, (tr("Counter underflow")), hrc = E_FAIL); + --m->readers; + + /* Reset the state after the last reader */ + if (m->readers == 0) + { + m->state = m->preLockState; + /* There are cases where we inject the deleting state into + * a medium locked for reading. Make sure #unmarkForDeletion() + * gets the right state afterwards. */ + if (m->preLockState == MediumState_Deleting) + m->preLockState = MediumState_Created; + } + + LogFlowThisFunc(("new state=%d\n", m->state)); + break; + } + default: + { + LogFlowThisFunc(("Failing - state=%d\n", m->state)); + hrc = setError(VBOX_E_INVALID_OBJECT_STATE, tr("Medium '%s' is not locked for reading"), m->strLocationFull.c_str()); + break; + } + } + + /* return the current state after */ + if (aState) + *aState = m->state; + + return hrc; +} +HRESULT Medium::lockWrite(ComPtr<IToken> &aToken) +{ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + /* Wait for a concurrently running Medium::i_queryInfo to complete. */ + if (m->queryInfoRunning) + { + /* Must not hold the media tree lock, as Medium::i_queryInfo needs this + * lock and thus we would run into a deadlock here. */ + Assert(!m->pVirtualBox->i_getMediaTreeLockHandle().isWriteLockOnCurrentThread()); + while (m->queryInfoRunning) + { + alock.release(); + /* must not hold the object lock now */ + Assert(!isWriteLockOnCurrentThread()); + { + AutoReadLock qlock(m->queryInfoSem COMMA_LOCKVAL_SRC_POS); + } + alock.acquire(); + } + } + + HRESULT hrc = S_OK; + + switch (m->state) + { + case MediumState_Created: + case MediumState_Inaccessible: + { + m->preLockState = m->state; + + LogFlowThisFunc(("Okay - prev state=%d locationFull=%s\n", m->state, i_getLocationFull().c_str())); + m->state = MediumState_LockedWrite; + + ComObjPtr<MediumLockToken> pToken; + hrc = pToken.createObject(); + if (SUCCEEDED(hrc)) + hrc = pToken->init(this, true /* fWrite */); + if (FAILED(hrc)) + { + m->state = m->preLockState; + return hrc; + } + + pToken.queryInterfaceTo(aToken.asOutParam()); + break; + } + default: + { + LogFlowThisFunc(("Failing - state=%d locationFull=%s\n", m->state, i_getLocationFull().c_str())); + hrc = i_setStateError(); + break; + } + } + + return hrc; +} + +/** + * @note @a aState may be NULL if the state value is not needed (only for + * in-process calls). + */ +HRESULT Medium::i_unlockWrite(MediumState_T *aState) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.hrc())) return autoCaller.hrc(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + HRESULT hrc = S_OK; + + switch (m->state) + { + case MediumState_LockedWrite: + { + m->state = m->preLockState; + /* There are cases where we inject the deleting state into + * a medium locked for writing. Make sure #unmarkForDeletion() + * gets the right state afterwards. */ + if (m->preLockState == MediumState_Deleting) + m->preLockState = MediumState_Created; + LogFlowThisFunc(("new state=%d locationFull=%s\n", m->state, i_getLocationFull().c_str())); + break; + } + default: + { + LogFlowThisFunc(("Failing - state=%d locationFull=%s\n", m->state, i_getLocationFull().c_str())); + hrc = setError(VBOX_E_INVALID_OBJECT_STATE, tr("Medium '%s' is not locked for writing"), m->strLocationFull.c_str()); + break; + } + } + + /* return the current state after */ + if (aState) + *aState = m->state; + + return hrc; +} + +HRESULT Medium::close(AutoCaller &aAutoCaller) +{ + // make a copy of VirtualBox pointer which gets nulled by uninit() + ComObjPtr<VirtualBox> pVirtualBox(m->pVirtualBox); + + Guid uId = i_getId(); + DeviceType_T devType = i_getDeviceType(); + MultiResult mrc = i_close(aAutoCaller); + + pVirtualBox->i_saveModifiedRegistries(); + + if (SUCCEEDED(mrc) && uId.isValid() && !uId.isZero()) + pVirtualBox->i_onMediumRegistered(uId, devType, FALSE); + + return mrc; +} + +HRESULT Medium::getProperty(const com::Utf8Str &aName, + com::Utf8Str &aValue) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + settings::StringsMap::const_iterator it = m->mapProperties.find(aName); + if (it == m->mapProperties.end()) + { + if (!aName.startsWith("Special/")) + return setError(VBOX_E_OBJECT_NOT_FOUND, + tr("Property '%s' does not exist"), aName.c_str()); + else + /* be more silent here */ + return VBOX_E_OBJECT_NOT_FOUND; + } + + aValue = it->second; + + return S_OK; +} + +HRESULT Medium::setProperty(const com::Utf8Str &aName, + const com::Utf8Str &aValue) +{ + AutoWriteLock mlock(this COMMA_LOCKVAL_SRC_POS); + + /* Wait for a concurrently running Medium::i_queryInfo to complete. */ + if (m->queryInfoRunning) + { + /* Must not hold the media tree lock, as Medium::i_queryInfo needs this + * lock and thus we would run into a deadlock here. */ + Assert(!m->pVirtualBox->i_getMediaTreeLockHandle().isWriteLockOnCurrentThread()); + while (m->queryInfoRunning) + { + mlock.release(); + /* must not hold the object lock now */ + Assert(!isWriteLockOnCurrentThread()); + { + AutoReadLock qlock(m->queryInfoSem COMMA_LOCKVAL_SRC_POS); + } + mlock.acquire(); + } + } + + switch (m->state) + { + case MediumState_NotCreated: + case MediumState_Created: + case MediumState_Inaccessible: + break; + default: + return i_setStateError(); + } + + settings::StringsMap::iterator it = m->mapProperties.find(aName); + if ( !aName.startsWith("Special/") + && !i_isPropertyForFilter(aName)) + { + if (it == m->mapProperties.end()) + return setError(VBOX_E_OBJECT_NOT_FOUND, + tr("Property '%s' does not exist"), + aName.c_str()); + it->second = aValue; + } + else + { + if (it == m->mapProperties.end()) + { + if (!aValue.isEmpty()) + m->mapProperties[aName] = aValue; + } + else + { + if (!aValue.isEmpty()) + it->second = aValue; + else + m->mapProperties.erase(it); + } + } + + // save the settings + mlock.release(); + i_markRegistriesModified(); + m->pVirtualBox->i_saveModifiedRegistries(); + m->pVirtualBox->i_onMediumConfigChanged(this); + + return S_OK; +} + +HRESULT Medium::getProperties(const com::Utf8Str &aNames, + std::vector<com::Utf8Str> &aReturnNames, + std::vector<com::Utf8Str> &aReturnValues) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + /// @todo make use of aNames according to the documentation + NOREF(aNames); + + aReturnNames.resize(m->mapProperties.size()); + aReturnValues.resize(m->mapProperties.size()); + size_t i = 0; + for (settings::StringsMap::const_iterator it = m->mapProperties.begin(); + it != m->mapProperties.end(); + ++it, ++i) + { + aReturnNames[i] = it->first; + aReturnValues[i] = it->second; + } + return S_OK; +} + +HRESULT Medium::setProperties(const std::vector<com::Utf8Str> &aNames, + const std::vector<com::Utf8Str> &aValues) +{ + AutoWriteLock mlock(this COMMA_LOCKVAL_SRC_POS); + + /* first pass: validate names */ + for (size_t i = 0; + i < aNames.size(); + ++i) + { + Utf8Str strName(aNames[i]); + if ( !strName.startsWith("Special/") + && !i_isPropertyForFilter(strName) + && m->mapProperties.find(strName) == m->mapProperties.end()) + return setError(VBOX_E_OBJECT_NOT_FOUND, + tr("Property '%s' does not exist"), strName.c_str()); + } + + /* second pass: assign */ + for (size_t i = 0; + i < aNames.size(); + ++i) + { + Utf8Str strName(aNames[i]); + Utf8Str strValue(aValues[i]); + settings::StringsMap::iterator it = m->mapProperties.find(strName); + if ( !strName.startsWith("Special/") + && !i_isPropertyForFilter(strName)) + { + AssertReturn(it != m->mapProperties.end(), E_FAIL); + it->second = strValue; + } + else + { + if (it == m->mapProperties.end()) + { + if (!strValue.isEmpty()) + m->mapProperties[strName] = strValue; + } + else + { + if (!strValue.isEmpty()) + it->second = strValue; + else + m->mapProperties.erase(it); + } + } + } + + // save the settings + mlock.release(); + i_markRegistriesModified(); + m->pVirtualBox->i_saveModifiedRegistries(); + m->pVirtualBox->i_onMediumConfigChanged(this); + + return S_OK; +} + +HRESULT Medium::createBaseStorage(LONG64 aLogicalSize, + const std::vector<MediumVariant_T> &aVariant, + ComPtr<IProgress> &aProgress) +{ + if (aLogicalSize < 0) + return setError(E_INVALIDARG, tr("The medium size argument (%lld) is negative"), aLogicalSize); + + HRESULT hrc = S_OK; + ComObjPtr<Progress> pProgress; + Medium::Task *pTask = NULL; + + try + { + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + ULONG mediumVariantFlags = 0; + + if (aVariant.size()) + { + for (size_t i = 0; i < aVariant.size(); i++) + mediumVariantFlags |= (ULONG)aVariant[i]; + } + + mediumVariantFlags &= ((unsigned)~MediumVariant_Diff); + + if ( !(mediumVariantFlags & MediumVariant_Fixed) + && !(m->formatObj->i_getCapabilities() & MediumFormatCapabilities_CreateDynamic)) + throw setError(VBOX_E_NOT_SUPPORTED, + tr("Medium format '%s' does not support dynamic storage creation"), + m->strFormat.c_str()); + + if ( (mediumVariantFlags & MediumVariant_Fixed) + && !(m->formatObj->i_getCapabilities() & MediumFormatCapabilities_CreateFixed)) + throw setError(VBOX_E_NOT_SUPPORTED, + tr("Medium format '%s' does not support fixed storage creation"), + m->strFormat.c_str()); + + if ( (mediumVariantFlags & MediumVariant_Formatted) + && i_getDeviceType() != DeviceType_Floppy) + throw setError(VBOX_E_NOT_SUPPORTED, + tr("Medium variant 'formatted' applies to floppy images only")); + + if (m->state != MediumState_NotCreated) + throw i_setStateError(); + + pProgress.createObject(); + hrc = pProgress->init(m->pVirtualBox, + static_cast<IMedium*>(this), + mediumVariantFlags & MediumVariant_Fixed + ? BstrFmt(tr("Creating fixed medium storage unit '%s'"), m->strLocationFull.c_str()).raw() + : BstrFmt(tr("Creating dynamic medium storage unit '%s'"), m->strLocationFull.c_str()).raw(), + TRUE /* aCancelable */); + if (FAILED(hrc)) + throw hrc; + + /* setup task object to carry out the operation asynchronously */ + pTask = new Medium::CreateBaseTask(this, pProgress, (uint64_t)aLogicalSize, + (MediumVariant_T)mediumVariantFlags); + hrc = pTask->hrc(); + AssertComRC(hrc); + if (FAILED(hrc)) + throw hrc; + + m->state = MediumState_Creating; + } + catch (HRESULT hrcXcpt) + { + hrc = hrcXcpt; + } + + if (SUCCEEDED(hrc)) + { + hrc = pTask->createThread(); + pTask = NULL; + + if (SUCCEEDED(hrc)) + pProgress.queryInterfaceTo(aProgress.asOutParam()); + } + else if (pTask != NULL) + delete pTask; + + return hrc; +} + +HRESULT Medium::deleteStorage(ComPtr<IProgress> &aProgress) +{ + ComObjPtr<Progress> pProgress; + + MultiResult mrc = i_deleteStorage(&pProgress, + false /* aWait */, + true /* aNotify */); + /* Must save the registries in any case, since an entry was removed. */ + m->pVirtualBox->i_saveModifiedRegistries(); + + if (SUCCEEDED(mrc)) + pProgress.queryInterfaceTo(aProgress.asOutParam()); + + return mrc; +} + +HRESULT Medium::createDiffStorage(AutoCaller &autoCaller, + const ComPtr<IMedium> &aTarget, + const std::vector<MediumVariant_T> &aVariant, + ComPtr<IProgress> &aProgress) +{ + IMedium *aT = aTarget; + ComObjPtr<Medium> diff = static_cast<Medium*>(aT); + + autoCaller.release(); + + /* It is possible that some previous/concurrent uninit has already cleared + * the pVirtualBox reference, see #uninit(). */ + ComObjPtr<VirtualBox> pVirtualBox(m->pVirtualBox); + + // we access m->pParent + AutoReadLock treeLock(!pVirtualBox.isNull() ? &pVirtualBox->i_getMediaTreeLockHandle() : NULL COMMA_LOCKVAL_SRC_POS); + + autoCaller.add(); + if (FAILED(autoCaller.hrc())) return autoCaller.hrc(); + + AutoMultiWriteLock2 alock(this, diff COMMA_LOCKVAL_SRC_POS); + + if (m->type == MediumType_Writethrough) + return setError(VBOX_E_INVALID_OBJECT_STATE, + tr("Medium type of '%s' is Writethrough"), + m->strLocationFull.c_str()); + else if (m->type == MediumType_Shareable) + return setError(VBOX_E_INVALID_OBJECT_STATE, + tr("Medium type of '%s' is Shareable"), + m->strLocationFull.c_str()); + else if (m->type == MediumType_Readonly) + return setError(VBOX_E_INVALID_OBJECT_STATE, + tr("Medium type of '%s' is Readonly"), + m->strLocationFull.c_str()); + + /* Apply the normal locking logic to the entire chain. */ + MediumLockList *pMediumLockList(new MediumLockList()); + alock.release(); + autoCaller.release(); + treeLock.release(); + HRESULT hrc = diff->i_createMediumLockList(true /* fFailIfInaccessible */, + diff /* pToLockWrite */, + false /* fMediumLockWriteAll */, + this, + *pMediumLockList); + treeLock.acquire(); + autoCaller.add(); + if (FAILED(autoCaller.hrc())) + hrc = autoCaller.hrc(); + alock.acquire(); + if (FAILED(hrc)) + { + delete pMediumLockList; + return hrc; + } + + alock.release(); + autoCaller.release(); + treeLock.release(); + hrc = pMediumLockList->Lock(); + treeLock.acquire(); + autoCaller.add(); + if (FAILED(autoCaller.hrc())) + hrc = autoCaller.hrc(); + alock.acquire(); + if (FAILED(hrc)) + { + delete pMediumLockList; + + return setError(hrc, tr("Could not lock medium when creating diff '%s'"), + diff->i_getLocationFull().c_str()); + } + + Guid parentMachineRegistry; + if (i_getFirstRegistryMachineId(parentMachineRegistry)) + { + /* since this medium has been just created it isn't associated yet */ + diff->m->llRegistryIDs.push_back(parentMachineRegistry); + alock.release(); + autoCaller.release(); + treeLock.release(); + diff->i_markRegistriesModified(); + treeLock.acquire(); + autoCaller.add(); + alock.acquire(); + } + + alock.release(); + autoCaller.release(); + treeLock.release(); + + ComObjPtr<Progress> pProgress; + + ULONG mediumVariantFlags = 0; + + if (aVariant.size()) + { + for (size_t i = 0; i < aVariant.size(); i++) + mediumVariantFlags |= (ULONG)aVariant[i]; + } + + if (mediumVariantFlags & MediumVariant_Formatted) + { + delete pMediumLockList; + return setError(VBOX_E_NOT_SUPPORTED, + tr("Medium variant 'formatted' applies to floppy images only")); + } + + hrc = i_createDiffStorage(diff, (MediumVariant_T)mediumVariantFlags, pMediumLockList, + &pProgress, false /* aWait */, true /* aNotify */); + if (FAILED(hrc)) + delete pMediumLockList; + else + pProgress.queryInterfaceTo(aProgress.asOutParam()); + + return hrc; +} + +HRESULT Medium::mergeTo(const ComPtr<IMedium> &aTarget, + ComPtr<IProgress> &aProgress) +{ + IMedium *aT = aTarget; + + ComAssertRet(aT != this, E_INVALIDARG); + + ComObjPtr<Medium> pTarget = static_cast<Medium*>(aT); + + bool fMergeForward = false; + ComObjPtr<Medium> pParentForTarget; + MediumLockList *pChildrenToReparent = NULL; + MediumLockList *pMediumLockList = NULL; + + HRESULT hrc = i_prepareMergeTo(pTarget, NULL, NULL, true, fMergeForward, + pParentForTarget, pChildrenToReparent, pMediumLockList); + if (FAILED(hrc)) return hrc; + + ComObjPtr<Progress> pProgress; + + hrc = i_mergeTo(pTarget, fMergeForward, pParentForTarget, pChildrenToReparent, + pMediumLockList, &pProgress, false /* aWait */, true /* aNotify */); + if (FAILED(hrc)) + i_cancelMergeTo(pChildrenToReparent, pMediumLockList); + else + pProgress.queryInterfaceTo(aProgress.asOutParam()); + + return hrc; +} + +HRESULT Medium::cloneToBase(const ComPtr<IMedium> &aTarget, + const std::vector<MediumVariant_T> &aVariant, + ComPtr<IProgress> &aProgress) +{ + return cloneTo(aTarget, aVariant, NULL, aProgress); +} + +HRESULT Medium::cloneTo(const ComPtr<IMedium> &aTarget, + const std::vector<MediumVariant_T> &aVariant, + const ComPtr<IMedium> &aParent, + ComPtr<IProgress> &aProgress) +{ + /** @todo r=jack: Remove redundancy. Call Medium::resizeAndCloneTo. */ + + /** @todo r=klaus The code below needs to be double checked with regard + * to lock order violations, it probably causes lock order issues related + * to the AutoCaller usage. */ + ComAssertRet(aTarget != this, E_INVALIDARG); + + IMedium *aT = aTarget; + ComObjPtr<Medium> pTarget = static_cast<Medium*>(aT); + ComObjPtr<Medium> pParent; + if (aParent) + { + IMedium *aP = aParent; + pParent = static_cast<Medium*>(aP); + } + + HRESULT hrc = S_OK; + ComObjPtr<Progress> pProgress; + Medium::Task *pTask = NULL; + + try + { + // locking: we need the tree lock first because we access parent pointers + // and we need to write-lock the media involved + uint32_t cHandles = 3; + LockHandle* pHandles[4] = { &m->pVirtualBox->i_getMediaTreeLockHandle(), + this->lockHandle(), + pTarget->lockHandle() }; + /* Only add parent to the lock if it is not null */ + if (!pParent.isNull()) + pHandles[cHandles++] = pParent->lockHandle(); + AutoWriteLock alock(cHandles, + pHandles + COMMA_LOCKVAL_SRC_POS); + + if ( pTarget->m->state != MediumState_NotCreated + && pTarget->m->state != MediumState_Created) + throw pTarget->i_setStateError(); + + /* Build the source lock list. */ + MediumLockList *pSourceMediumLockList(new MediumLockList()); + alock.release(); + hrc = i_createMediumLockList(true /* fFailIfInaccessible */, + NULL /* pToLockWrite */, + false /* fMediumLockWriteAll */, + NULL, + *pSourceMediumLockList); + alock.acquire(); + if (FAILED(hrc)) + { + delete pSourceMediumLockList; + throw hrc; + } + + /* Build the target lock list (including the to-be parent chain). */ + MediumLockList *pTargetMediumLockList(new MediumLockList()); + alock.release(); + hrc = pTarget->i_createMediumLockList(true /* fFailIfInaccessible */, + pTarget /* pToLockWrite */, + false /* fMediumLockWriteAll */, + pParent, + *pTargetMediumLockList); + alock.acquire(); + if (FAILED(hrc)) + { + delete pSourceMediumLockList; + delete pTargetMediumLockList; + throw hrc; + } + + alock.release(); + hrc = pSourceMediumLockList->Lock(); + alock.acquire(); + if (FAILED(hrc)) + { + delete pSourceMediumLockList; + delete pTargetMediumLockList; + throw setError(hrc, + tr("Failed to lock source media '%s'"), + i_getLocationFull().c_str()); + } + alock.release(); + hrc = pTargetMediumLockList->Lock(); + alock.acquire(); + if (FAILED(hrc)) + { + delete pSourceMediumLockList; + delete pTargetMediumLockList; + throw setError(hrc, + tr("Failed to lock target media '%s'"), + pTarget->i_getLocationFull().c_str()); + } + + pProgress.createObject(); + hrc = pProgress->init(m->pVirtualBox, + static_cast <IMedium *>(this), + BstrFmt(tr("Creating clone medium '%s'"), pTarget->m->strLocationFull.c_str()).raw(), + TRUE /* aCancelable */); + if (FAILED(hrc)) + { + delete pSourceMediumLockList; + delete pTargetMediumLockList; + throw hrc; + } + + ULONG mediumVariantFlags = 0; + + if (aVariant.size()) + { + for (size_t i = 0; i < aVariant.size(); i++) + mediumVariantFlags |= (ULONG)aVariant[i]; + } + + if (mediumVariantFlags & MediumVariant_Formatted) + { + delete pSourceMediumLockList; + delete pTargetMediumLockList; + throw setError(VBOX_E_NOT_SUPPORTED, + tr("Medium variant 'formatted' applies to floppy images only")); + } + + /* setup task object to carry out the operation asynchronously */ + pTask = new Medium::CloneTask(this, pProgress, pTarget, + (MediumVariant_T)mediumVariantFlags, + pParent, UINT32_MAX, UINT32_MAX, + pSourceMediumLockList, pTargetMediumLockList); + hrc = pTask->hrc(); + AssertComRC(hrc); + if (FAILED(hrc)) + throw hrc; + + if (pTarget->m->state == MediumState_NotCreated) + pTarget->m->state = MediumState_Creating; + } + catch (HRESULT hrcXcpt) + { + hrc = hrcXcpt; + } + + if (SUCCEEDED(hrc)) + { + hrc = pTask->createThread(); + pTask = NULL; + if (SUCCEEDED(hrc)) + pProgress.queryInterfaceTo(aProgress.asOutParam()); + } + else if (pTask != NULL) + delete pTask; + + return hrc; +} + +/** + * This is a helper function that combines the functionality of + * Medium::cloneTo() and Medium::resize(). The target medium will take the + * contents of the calling medium. + * + * @param aTarget Medium to resize and clone to + * @param aLogicalSize Desired size for targer medium + * @param aVariant + * @param aParent + * @param aProgress + * @return HRESULT + */ +HRESULT Medium::resizeAndCloneTo(const ComPtr<IMedium> &aTarget, + LONG64 aLogicalSize, + const std::vector<MediumVariant_T> &aVariant, + const ComPtr<IMedium> &aParent, + ComPtr<IProgress> &aProgress) +{ + /* Check for valid args */ + ComAssertRet(aTarget != this, E_INVALIDARG); + CheckComArgExpr(aLogicalSize, aLogicalSize >= 0); + + /* Convert args to usable/needed types */ + IMedium *aT = aTarget; + ComObjPtr<Medium> pTarget = static_cast<Medium*>(aT); + ComObjPtr<Medium> pParent; + if (aParent) + { + IMedium *aP = aParent; + pParent = static_cast<Medium*>(aP); + } + + /* Set up variables. Fetch needed data in lockable blocks */ + HRESULT hrc = S_OK; + Medium::Task *pTask = NULL; + + Utf8Str strSourceName; + { + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + strSourceName = i_getName(); + } + + uint64_t uTargetExistingSize = 0; + Utf8Str strTargetName; + { + AutoReadLock alock(pTarget COMMA_LOCKVAL_SRC_POS); + uTargetExistingSize = pTarget->i_getLogicalSize(); + strTargetName = pTarget->i_getName(); + } + + /* Set up internal multi-subprocess progress object */ + ComObjPtr<Progress> pProgress; + pProgress.createObject(); + hrc = pProgress->init(m->pVirtualBox, + static_cast<IMedium*>(this), + BstrFmt(tr("Resizing medium and cloning into it")).raw(), + TRUE, /* aCancelable */ + 2, /* Number of opearations */ + BstrFmt(tr("Resizing medium before clone")).raw() + ); + if (FAILED(hrc)) + return hrc; + + /* If target does not exist, handle resize. */ + if (pTarget->m->state != MediumState_NotCreated && aLogicalSize > 0) + { + if ((LONG64)uTargetExistingSize != aLogicalSize) { + if (!i_isMediumFormatFile()) + { + hrc = setError(VBOX_E_NOT_SUPPORTED, + tr("Sizes of '%s' and '%s' are different and medium format does not support resing"), + strSourceName.c_str(), strTargetName.c_str()); + return hrc; + } + + /** + * Need to lock the target medium as i_resize does do so + * automatically. + */ + + ComPtr<IToken> pToken; + hrc = pTarget->LockWrite(pToken.asOutParam()); + + if (FAILED(hrc)) return hrc; + + /** + * Have to make own lock list, because "resize" method resizes only + * last image in the lock chain. + */ + + MediumLockList* pMediumLockListForResize = new MediumLockList(); + pMediumLockListForResize->Append(pTarget, pTarget->m->state == MediumState_LockedWrite); + + hrc = pMediumLockListForResize->Lock(true /* fSkipOverLockedMedia */); + if (FAILED(hrc)) + { + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + hrc = setError(hrc, + tr("Failed to lock the medium '%s' to resize before merge"), + strTargetName.c_str()); + delete pMediumLockListForResize; + return hrc; + } + + + hrc = pTarget->i_resize((uint64_t)aLogicalSize, pMediumLockListForResize, &pProgress, true, false); + if (FAILED(hrc)) + { + /* No need to setError becasue i_resize and i_taskResizeHandler handle this automatically. */ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + delete pMediumLockListForResize; + return hrc; + } + + delete pMediumLockListForResize; + + pTarget->m->logicalSize = (uint64_t)aLogicalSize; + + pToken->Abandon(); + pToken.setNull(); + } + } + + /* Report progress to supplied progress argument */ + if (SUCCEEDED(hrc)) + { + pProgress.queryInterfaceTo(aProgress.asOutParam()); + } + + try + { + // locking: we need the tree lock first because we access parent pointers + // and we need to write-lock the media involved + uint32_t cHandles = 3; + LockHandle* pHandles[4] = { &m->pVirtualBox->i_getMediaTreeLockHandle(), + this->lockHandle(), + pTarget->lockHandle() }; + /* Only add parent to the lock if it is not null */ + if (!pParent.isNull()) + pHandles[cHandles++] = pParent->lockHandle(); + AutoWriteLock alock(cHandles, + pHandles + COMMA_LOCKVAL_SRC_POS); + + if ( pTarget->m->state != MediumState_NotCreated + && pTarget->m->state != MediumState_Created) + throw pTarget->i_setStateError(); + + /* Build the source lock list. */ + MediumLockList *pSourceMediumLockList(new MediumLockList()); + alock.release(); + hrc = i_createMediumLockList(true /* fFailIfInaccessible */, + NULL /* pToLockWrite */, + false /* fMediumLockWriteAll */, + NULL, + *pSourceMediumLockList); + alock.acquire(); + if (FAILED(hrc)) + { + delete pSourceMediumLockList; + throw hrc; + } + + /* Build the target lock list (including the to-be parent chain). */ + MediumLockList *pTargetMediumLockList(new MediumLockList()); + alock.release(); + hrc = pTarget->i_createMediumLockList(true /* fFailIfInaccessible */, + pTarget /* pToLockWrite */, + false /* fMediumLockWriteAll */, + pParent, + *pTargetMediumLockList); + alock.acquire(); + if (FAILED(hrc)) + { + delete pSourceMediumLockList; + delete pTargetMediumLockList; + throw hrc; + } + + alock.release(); + hrc = pSourceMediumLockList->Lock(); + alock.acquire(); + if (FAILED(hrc)) + { + delete pSourceMediumLockList; + delete pTargetMediumLockList; + throw setError(hrc, + tr("Failed to lock source media '%s'"), + i_getLocationFull().c_str()); + } + alock.release(); + hrc = pTargetMediumLockList->Lock(); + alock.acquire(); + if (FAILED(hrc)) + { + delete pSourceMediumLockList; + delete pTargetMediumLockList; + throw setError(hrc, + tr("Failed to lock target media '%s'"), + pTarget->i_getLocationFull().c_str()); + } + + ULONG mediumVariantFlags = 0; + + if (aVariant.size()) + { + for (size_t i = 0; i < aVariant.size(); i++) + mediumVariantFlags |= (ULONG)aVariant[i]; + } + + if (mediumVariantFlags & MediumVariant_Formatted) + { + delete pSourceMediumLockList; + delete pTargetMediumLockList; + throw setError(VBOX_E_NOT_SUPPORTED, + tr("Medium variant 'formatted' applies to floppy images only")); + } + + if (pTarget->m->state != MediumState_NotCreated || aLogicalSize == 0) + { + /* setup task object to carry out the operation asynchronously */ + pTask = new Medium::CloneTask(this, pProgress, pTarget, + (MediumVariant_T)mediumVariantFlags, + pParent, UINT32_MAX, UINT32_MAX, + pSourceMediumLockList, pTargetMediumLockList, + false, false, true, 0); + } + else + { + /* setup task object to carry out the operation asynchronously */ + pTask = new Medium::CloneTask(this, pProgress, pTarget, + (MediumVariant_T)mediumVariantFlags, + pParent, UINT32_MAX, UINT32_MAX, + pSourceMediumLockList, pTargetMediumLockList, + false, false, true, (uint64_t)aLogicalSize); + } + + hrc = pTask->hrc(); + AssertComRC(hrc); + if (FAILED(hrc)) + throw hrc; + + if (pTarget->m->state == MediumState_NotCreated) + pTarget->m->state = MediumState_Creating; + } + catch (HRESULT hrcXcpt) { hrc = hrcXcpt; } + + if (SUCCEEDED(hrc)) + { + hrc = pTask->createThread(); + pTask = NULL; + if (SUCCEEDED(hrc)) + pProgress.queryInterfaceTo(aProgress.asOutParam()); + } + else if (pTask != NULL) + delete pTask; + + return hrc; +} + +HRESULT Medium::moveTo(AutoCaller &autoCaller, const com::Utf8Str &aLocation, ComPtr<IProgress> &aProgress) +{ + ComObjPtr<Medium> pParent; + ComObjPtr<Progress> pProgress; + HRESULT hrc = S_OK; + Medium::Task *pTask = NULL; + + try + { + /// @todo NEWMEDIA for file names, add the default extension if no extension + /// is present (using the information from the VD backend which also implies + /// that one more parameter should be passed to moveTo() requesting + /// that functionality since it is only allowed when called from this method + + /// @todo NEWMEDIA rename the file and set m->location on success, then save + /// the global registry (and local registries of portable VMs referring to + /// this medium), this will also require to add the mRegistered flag to data + + autoCaller.release(); + + // locking: we need the tree lock first because we access parent pointers + // and we need to write-lock the media involved + AutoWriteLock treeLock(m->pVirtualBox->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS); + + autoCaller.add(); + AssertComRCThrowRC(autoCaller.hrc()); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + /* play with locations */ + { + /* get source path and filename */ + Utf8Str sourcePath = i_getLocationFull(); + Utf8Str sourceFName = i_getName(); + + if (aLocation.isEmpty()) + { + hrc = setErrorVrc(VERR_PATH_ZERO_LENGTH, + tr("Medium '%s' can't be moved. Destination path is empty."), + i_getLocationFull().c_str()); + throw hrc; + } + + /* extract destination path and filename */ + Utf8Str destPath(aLocation); + Utf8Str destFName(destPath); + destFName.stripPath(); + + if (destFName.isNotEmpty() && !RTPathHasSuffix(destFName.c_str())) + { + /* + * The target path has no filename: Either "/path/to/new/location" or + * just "newname" (no trailing backslash or there is no filename extension). + */ + if (destPath.equals(destFName)) + { + /* new path contains only "newname", no path, no extension */ + destFName.append(RTPathSuffix(sourceFName.c_str())); + destPath = destFName; + } + else + { + /* new path looks like "/path/to/new/location" */ + destFName.setNull(); + destPath.append(RTPATH_SLASH); + } + } + + if (destFName.isEmpty()) + { + /* No target name */ + destPath.append(sourceFName); + } + else + { + if (destPath.equals(destFName)) + { + /* + * The target path contains of only a filename without a directory. + * Move the medium within the source directory to the new name + * (actually rename operation). + * Scratches sourcePath! + */ + destPath = sourcePath.stripFilename().append(RTPATH_SLASH).append(destFName); + } + + const char *pszSuffix = RTPathSuffix(sourceFName.c_str()); + + /* Suffix is empty and one is deduced from the medium format */ + if (pszSuffix == NULL) + { + Utf8Str strExt = i_getFormat(); + if (strExt.compare("RAW", Utf8Str::CaseInsensitive) == 0) + { + DeviceType_T devType = i_getDeviceType(); + switch (devType) + { + case DeviceType_DVD: + strExt = "iso"; + break; + case DeviceType_Floppy: + strExt = "img"; + break; + default: + hrc = setErrorVrc(VERR_NOT_A_FILE, /** @todo r=bird: Mixing status codes again. */ + tr("Medium '%s' has RAW type. \"Move\" operation isn't supported for this type."), + i_getLocationFull().c_str()); + throw hrc; + } + } + else if (strExt.compare("Parallels", Utf8Str::CaseInsensitive) == 0) + { + strExt = "hdd"; + } + + /* Set the target extension like on the source. Any conversions are prohibited */ + strExt.toLower(); + destPath.stripSuffix().append('.').append(strExt); + } + else + destPath.stripSuffix().append(pszSuffix); + } + + /* Simple check for existence */ + if (RTFileExists(destPath.c_str())) + throw setError(VBOX_E_FILE_ERROR, + tr("The given path '%s' is an existing file. Delete or rename this file."), + destPath.c_str()); + + if (!i_isMediumFormatFile()) + throw setErrorVrc(VERR_NOT_A_FILE, + tr("Medium '%s' isn't a file object. \"Move\" operation isn't supported."), + i_getLocationFull().c_str()); + /* Path must be absolute */ + if (!RTPathStartsWithRoot(destPath.c_str())) + throw setError(VBOX_E_FILE_ERROR, + tr("The given path '%s' is not fully qualified"), + destPath.c_str()); + /* Check path for a new file object */ + hrc = VirtualBox::i_ensureFilePathExists(destPath, true); + if (FAILED(hrc)) + throw hrc; + + /* Set needed variables for "moving" procedure. It'll be used later in separate thread task */ + hrc = i_preparationForMoving(destPath); + if (FAILED(hrc)) + throw setErrorVrc(VERR_NO_CHANGE, + tr("Medium '%s' is already in the correct location"), + i_getLocationFull().c_str()); + } + + /* Check VMs which have this medium attached to*/ + std::vector<com::Guid> aMachineIds; + hrc = getMachineIds(aMachineIds); + std::vector<com::Guid>::const_iterator currMachineID = aMachineIds.begin(); + std::vector<com::Guid>::const_iterator lastMachineID = aMachineIds.end(); + + while (currMachineID != lastMachineID) + { + Guid id(*currMachineID); + ComObjPtr<Machine> aMachine; + + alock.release(); + autoCaller.release(); + treeLock.release(); + hrc = m->pVirtualBox->i_findMachine(id, false, true, &aMachine); + treeLock.acquire(); + autoCaller.add(); + AssertComRCThrowRC(autoCaller.hrc()); + alock.acquire(); + + if (SUCCEEDED(hrc)) + { + ComObjPtr<SessionMachine> sm; + ComPtr<IInternalSessionControl> ctl; + + alock.release(); + autoCaller.release(); + treeLock.release(); + bool ses = aMachine->i_isSessionOpenVM(sm, &ctl); + treeLock.acquire(); + autoCaller.add(); + AssertComRCThrowRC(autoCaller.hrc()); + alock.acquire(); + + if (ses) + throw setError(VBOX_E_INVALID_VM_STATE, + tr("At least the VM '%s' to whom this medium '%s' attached has currently an opened session. Stop all VMs before relocating this medium"), + id.toString().c_str(), i_getLocationFull().c_str()); + } + ++currMachineID; + } + + /* Build the source lock list. */ + MediumLockList *pMediumLockList(new MediumLockList()); + alock.release(); + autoCaller.release(); + treeLock.release(); + hrc = i_createMediumLockList(true /* fFailIfInaccessible */, + this /* pToLockWrite */, + true /* fMediumLockWriteAll */, + NULL, + *pMediumLockList); + treeLock.acquire(); + autoCaller.add(); + AssertComRCThrowRC(autoCaller.hrc()); + alock.acquire(); + if (FAILED(hrc)) + { + delete pMediumLockList; + throw setError(hrc, tr("Failed to create medium lock list for '%s'"), i_getLocationFull().c_str()); + } + alock.release(); + autoCaller.release(); + treeLock.release(); + hrc = pMediumLockList->Lock(); + treeLock.acquire(); + autoCaller.add(); + AssertComRCThrowRC(autoCaller.hrc()); + alock.acquire(); + if (FAILED(hrc)) + { + delete pMediumLockList; + throw setError(hrc, tr("Failed to lock media '%s'"), i_getLocationFull().c_str()); + } + + pProgress.createObject(); + hrc = pProgress->init(m->pVirtualBox, + static_cast <IMedium *>(this), + BstrFmt(tr("Moving medium '%s'"), m->strLocationFull.c_str()).raw(), + TRUE /* aCancelable */); + + /* Do the disk moving. */ + if (SUCCEEDED(hrc)) + { + ULONG mediumVariantFlags = i_getVariant(); + + /* setup task object to carry out the operation asynchronously */ + pTask = new Medium::MoveTask(this, pProgress, + (MediumVariant_T)mediumVariantFlags, + pMediumLockList); + hrc = pTask->hrc(); + AssertComRC(hrc); + if (FAILED(hrc)) + throw hrc; + } + + } + catch (HRESULT hrcXcpt) { hrc = hrcXcpt; } + + if (SUCCEEDED(hrc)) + { + hrc = pTask->createThread(); + pTask = NULL; + if (SUCCEEDED(hrc)) + pProgress.queryInterfaceTo(aProgress.asOutParam()); + } + else + { + if (pTask) + delete pTask; + } + + return hrc; +} + +HRESULT Medium::setLocation(const com::Utf8Str &aLocation) +{ + HRESULT hrc = S_OK; + + try + { + // locking: we need the tree lock first because we access parent pointers + // and we need to write-lock the media involved + AutoWriteLock treeLock(m->pVirtualBox->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS); + + AutoCaller autoCaller(this); + AssertComRCThrowRC(autoCaller.hrc()); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + Utf8Str destPath(aLocation); + + // some check for file based medium + if (i_isMediumFormatFile()) + { + /* Path must be absolute */ + if (!RTPathStartsWithRoot(destPath.c_str())) + throw setError(VBOX_E_FILE_ERROR, tr("The given path '%s' is not fully qualified"), destPath.c_str()); + + /* Simple check for existence */ + if (!RTFileExists(destPath.c_str())) + throw setError(VBOX_E_FILE_ERROR, + tr("The given path '%s' is not an existing file. New location is invalid."), + destPath.c_str()); + } + + /* Check VMs which have this medium attached to*/ + std::vector<com::Guid> aMachineIds; + hrc = getMachineIds(aMachineIds); + + // switch locks only if there are machines with this medium attached + if (!aMachineIds.empty()) + { + std::vector<com::Guid>::const_iterator currMachineID = aMachineIds.begin(); + std::vector<com::Guid>::const_iterator lastMachineID = aMachineIds.end(); + + alock.release(); + autoCaller.release(); + treeLock.release(); + + while (currMachineID != lastMachineID) + { + Guid id(*currMachineID); + ComObjPtr<Machine> aMachine; + hrc = m->pVirtualBox->i_findMachine(id, false, true, &aMachine); + if (SUCCEEDED(hrc)) + { + ComObjPtr<SessionMachine> sm; + ComPtr<IInternalSessionControl> ctl; + + bool ses = aMachine->i_isSessionOpenVM(sm, &ctl); + if (ses) + { + treeLock.acquire(); + autoCaller.add(); + AssertComRCThrowRC(autoCaller.hrc()); + alock.acquire(); + + throw setError(VBOX_E_INVALID_VM_STATE, + tr("At least the VM '%s' to whom this medium '%s' attached has currently an opened session. Stop all VMs before set location for this medium"), + id.toString().c_str(), + i_getLocationFull().c_str()); + } + } + ++currMachineID; + } + + treeLock.acquire(); + autoCaller.add(); + AssertComRCThrowRC(autoCaller.hrc()); + alock.acquire(); + } + + m->strLocationFull = destPath; + + // save the settings + alock.release(); + autoCaller.release(); + treeLock.release(); + + i_markRegistriesModified(); + m->pVirtualBox->i_saveModifiedRegistries(); + + MediumState_T mediumState; + refreshState(autoCaller, &mediumState); + m->pVirtualBox->i_onMediumConfigChanged(this); + } + catch (HRESULT hrcXcpt) { hrc = hrcXcpt; } + + return hrc; +} + +HRESULT Medium::compact(ComPtr<IProgress> &aProgress) +{ + HRESULT hrc = S_OK; + ComObjPtr<Progress> pProgress; + Medium::Task *pTask = NULL; + + try + { + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + /* Build the medium lock list. */ + MediumLockList *pMediumLockList(new MediumLockList()); + alock.release(); + hrc = i_createMediumLockList(true /* fFailIfInaccessible */ , + this /* pToLockWrite */, + false /* fMediumLockWriteAll */, + NULL, + *pMediumLockList); + alock.acquire(); + if (FAILED(hrc)) + { + delete pMediumLockList; + throw hrc; + } + + alock.release(); + hrc = pMediumLockList->Lock(); + alock.acquire(); + if (FAILED(hrc)) + { + delete pMediumLockList; + throw setError(hrc, + tr("Failed to lock media when compacting '%s'"), + i_getLocationFull().c_str()); + } + + pProgress.createObject(); + hrc = pProgress->init(m->pVirtualBox, + static_cast <IMedium *>(this), + BstrFmt(tr("Compacting medium '%s'"), m->strLocationFull.c_str()).raw(), + TRUE /* aCancelable */); + if (FAILED(hrc)) + { + delete pMediumLockList; + throw hrc; + } + + /* setup task object to carry out the operation asynchronously */ + pTask = new Medium::CompactTask(this, pProgress, pMediumLockList); + hrc = pTask->hrc(); + AssertComRC(hrc); + if (FAILED(hrc)) + throw hrc; + } + catch (HRESULT hrcXcpt) { hrc = hrcXcpt; } + + if (SUCCEEDED(hrc)) + { + hrc = pTask->createThread(); + pTask = NULL; + if (SUCCEEDED(hrc)) + pProgress.queryInterfaceTo(aProgress.asOutParam()); + } + else if (pTask != NULL) + delete pTask; + + return hrc; +} + +HRESULT Medium::resize(LONG64 aLogicalSize, + ComPtr<IProgress> &aProgress) +{ + CheckComArgExpr(aLogicalSize, aLogicalSize > 0); + HRESULT hrc = S_OK; + ComObjPtr<Progress> pProgress; + + /* Build the medium lock list. */ + MediumLockList *pMediumLockList(new MediumLockList()); + + try + { + const char *pszError = NULL; + + hrc = i_createMediumLockList(true /* fFailIfInaccessible */ , + this /* pToLockWrite */, + false /* fMediumLockWriteAll */, + NULL, + *pMediumLockList); + if (FAILED(hrc)) + pszError = tr("Failed to create medium lock list when resizing '%s'"); + else + { + hrc = pMediumLockList->Lock(); + if (FAILED(hrc)) + pszError = tr("Failed to lock media when resizing '%s'"); + } + + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + if (FAILED(hrc)) + { + throw setError(hrc, pszError, i_getLocationFull().c_str()); + } + + pProgress.createObject(); + hrc = pProgress->init(m->pVirtualBox, + static_cast <IMedium *>(this), + BstrFmt(tr("Resizing medium '%s'"), m->strLocationFull.c_str()).raw(), + TRUE /* aCancelable */); + if (FAILED(hrc)) + { + throw hrc; + } + } + catch (HRESULT hrcXcpt) { hrc = hrcXcpt; } + + if (SUCCEEDED(hrc)) + hrc = i_resize((uint64_t)aLogicalSize, pMediumLockList, &pProgress, false /* aWait */, true /* aNotify */); + + if (SUCCEEDED(hrc)) + pProgress.queryInterfaceTo(aProgress.asOutParam()); + else + delete pMediumLockList; + + return hrc; +} + +HRESULT Medium::reset(AutoCaller &autoCaller, ComPtr<IProgress> &aProgress) +{ + HRESULT hrc = S_OK; + ComObjPtr<Progress> pProgress; + Medium::Task *pTask = NULL; + + try + { + autoCaller.release(); + + /* It is possible that some previous/concurrent uninit has already + * cleared the pVirtualBox reference, see #uninit(). */ + ComObjPtr<VirtualBox> pVirtualBox(m->pVirtualBox); + + /* i_canClose() needs the tree lock */ + AutoMultiWriteLock2 multilock(!pVirtualBox.isNull() ? &pVirtualBox->i_getMediaTreeLockHandle() : NULL, + this->lockHandle() + COMMA_LOCKVAL_SRC_POS); + + autoCaller.add(); + if (FAILED(autoCaller.hrc())) return autoCaller.hrc(); + + LogFlowThisFunc(("ENTER for medium %s\n", m->strLocationFull.c_str())); + + if (m->pParent.isNull()) + throw setError(VBOX_E_NOT_SUPPORTED, + tr("Medium type of '%s' is not differencing"), + m->strLocationFull.c_str()); + + hrc = i_canClose(); + if (FAILED(hrc)) + throw hrc; + + /* Build the medium lock list. */ + MediumLockList *pMediumLockList(new MediumLockList()); + multilock.release(); + hrc = i_createMediumLockList(true /* fFailIfInaccessible */, + this /* pToLockWrite */, + false /* fMediumLockWriteAll */, + NULL, + *pMediumLockList); + multilock.acquire(); + if (FAILED(hrc)) + { + delete pMediumLockList; + throw hrc; + } + + multilock.release(); + hrc = pMediumLockList->Lock(); + multilock.acquire(); + if (FAILED(hrc)) + { + delete pMediumLockList; + throw setError(hrc, + tr("Failed to lock media when resetting '%s'"), + i_getLocationFull().c_str()); + } + + pProgress.createObject(); + hrc = pProgress->init(m->pVirtualBox, + static_cast<IMedium*>(this), + BstrFmt(tr("Resetting differencing medium '%s'"), m->strLocationFull.c_str()).raw(), + FALSE /* aCancelable */); + if (FAILED(hrc)) + throw hrc; + + /* setup task object to carry out the operation asynchronously */ + pTask = new Medium::ResetTask(this, pProgress, pMediumLockList); + hrc = pTask->hrc(); + AssertComRC(hrc); + if (FAILED(hrc)) + throw hrc; + } + catch (HRESULT hrcXcpt) { hrc = hrcXcpt; } + + if (SUCCEEDED(hrc)) + { + hrc = pTask->createThread(); + pTask = NULL; + if (SUCCEEDED(hrc)) + pProgress.queryInterfaceTo(aProgress.asOutParam()); + } + else if (pTask != NULL) + delete pTask; + + LogFlowThisFunc(("LEAVE, hrc=%Rhrc\n", hrc)); + + return hrc; +} + +HRESULT Medium::changeEncryption(const com::Utf8Str &aCurrentPassword, const com::Utf8Str &aCipher, + const com::Utf8Str &aNewPassword, const com::Utf8Str &aNewPasswordId, + ComPtr<IProgress> &aProgress) +{ + HRESULT hrc = S_OK; + ComObjPtr<Progress> pProgress; + Medium::Task *pTask = NULL; + + try + { + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + DeviceType_T devType = i_getDeviceType(); + /* Cannot encrypt DVD or floppy images so far. */ + if ( devType == DeviceType_DVD + || devType == DeviceType_Floppy) + return setError(VBOX_E_INVALID_OBJECT_STATE, + tr("Cannot encrypt DVD or Floppy medium '%s'"), + m->strLocationFull.c_str()); + + /* Cannot encrypt media which are attached to more than one virtual machine. */ + if (m->backRefs.size() > 1) + return setError(VBOX_E_INVALID_OBJECT_STATE, + tr("Cannot encrypt medium '%s' because it is attached to %d virtual machines", "", m->backRefs.size()), + m->strLocationFull.c_str(), m->backRefs.size()); + + if (i_getChildren().size() != 0) + return setError(VBOX_E_INVALID_OBJECT_STATE, + tr("Cannot encrypt medium '%s' because it has %d children", "", i_getChildren().size()), + m->strLocationFull.c_str(), i_getChildren().size()); + + /* Build the medium lock list. */ + MediumLockList *pMediumLockList(new MediumLockList()); + alock.release(); + hrc = i_createMediumLockList(true /* fFailIfInaccessible */ , + this /* pToLockWrite */, + true /* fMediumLockAllWrite */, + NULL, + *pMediumLockList); + alock.acquire(); + if (FAILED(hrc)) + { + delete pMediumLockList; + throw hrc; + } + + alock.release(); + hrc = pMediumLockList->Lock(); + alock.acquire(); + if (FAILED(hrc)) + { + delete pMediumLockList; + throw setError(hrc, + tr("Failed to lock media for encryption '%s'"), + i_getLocationFull().c_str()); + } + + /* + * Check all media in the chain to not contain any branches or references to + * other virtual machines, we support encrypting only a list of differencing media at the moment. + */ + MediumLockList::Base::const_iterator mediumListBegin = pMediumLockList->GetBegin(); + MediumLockList::Base::const_iterator mediumListEnd = pMediumLockList->GetEnd(); + for (MediumLockList::Base::const_iterator it = mediumListBegin; + it != mediumListEnd; + ++it) + { + const MediumLock &mediumLock = *it; + const ComObjPtr<Medium> &pMedium = mediumLock.GetMedium(); + AutoReadLock mediumReadLock(pMedium COMMA_LOCKVAL_SRC_POS); + + Assert(pMedium->m->state == MediumState_LockedWrite); + + if (pMedium->m->backRefs.size() > 1) + { + hrc = setError(VBOX_E_INVALID_OBJECT_STATE, + tr("Cannot encrypt medium '%s' because it is attached to %d virtual machines", "", + pMedium->m->backRefs.size()), + pMedium->m->strLocationFull.c_str(), pMedium->m->backRefs.size()); + break; + } + else if (pMedium->i_getChildren().size() > 1) + { + hrc = setError(VBOX_E_INVALID_OBJECT_STATE, + tr("Cannot encrypt medium '%s' because it has %d children", "", pMedium->i_getChildren().size()), + pMedium->m->strLocationFull.c_str(), pMedium->i_getChildren().size()); + break; + } + } + + if (FAILED(hrc)) + { + delete pMediumLockList; + throw hrc; + } + + const char *pszAction = tr("Encrypting medium"); + if ( aCurrentPassword.isNotEmpty() + && aCipher.isEmpty()) + pszAction = tr("Decrypting medium"); + + pProgress.createObject(); + hrc = pProgress->init(m->pVirtualBox, + static_cast <IMedium *>(this), + BstrFmt("%s '%s'", pszAction, m->strLocationFull.c_str()).raw(), + TRUE /* aCancelable */); + if (FAILED(hrc)) + { + delete pMediumLockList; + throw hrc; + } + + /* setup task object to carry out the operation asynchronously */ + pTask = new Medium::EncryptTask(this, aNewPassword, aCurrentPassword, + aCipher, aNewPasswordId, pProgress, pMediumLockList); + hrc = pTask->hrc(); + AssertComRC(hrc); + if (FAILED(hrc)) + throw hrc; + } + catch (HRESULT hrcXcpt) { hrc = hrcXcpt; } + + if (SUCCEEDED(hrc)) + { + hrc = pTask->createThread(); + pTask = NULL; + if (SUCCEEDED(hrc)) + pProgress.queryInterfaceTo(aProgress.asOutParam()); + } + else if (pTask != NULL) + delete pTask; + + return hrc; +} + +HRESULT Medium::getEncryptionSettings(AutoCaller &autoCaller, com::Utf8Str &aCipher, com::Utf8Str &aPasswordId) +{ +#ifndef VBOX_WITH_EXTPACK + RT_NOREF(aCipher, aPasswordId); +#endif + HRESULT hrc = S_OK; + + try + { + autoCaller.release(); + ComObjPtr<Medium> pBase = i_getBase(); + autoCaller.add(); + if (FAILED(autoCaller.hrc())) + throw hrc; + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + /* Check whether encryption is configured for this medium. */ + settings::StringsMap::iterator it = pBase->m->mapProperties.find("CRYPT/KeyStore"); + if (it == pBase->m->mapProperties.end()) + throw VBOX_E_NOT_SUPPORTED; + +# ifdef VBOX_WITH_EXTPACK + ExtPackManager *pExtPackManager = m->pVirtualBox->i_getExtPackManager(); + if (pExtPackManager->i_isExtPackUsable(ORACLE_PUEL_EXTPACK_NAME)) + { + /* Load the plugin */ + Utf8Str strPlugin; + hrc = pExtPackManager->i_getLibraryPathForExtPack(g_szVDPlugin, ORACLE_PUEL_EXTPACK_NAME, &strPlugin); + if (SUCCEEDED(hrc)) + { + int vrc = VDPluginLoadFromFilename(strPlugin.c_str()); + if (RT_FAILURE(vrc)) + throw setErrorBoth(VBOX_E_NOT_SUPPORTED, vrc, + tr("Retrieving encryption settings of the image failed because the encryption plugin could not be loaded (%s)"), + i_vdError(vrc).c_str()); + } + else + throw setError(VBOX_E_NOT_SUPPORTED, + tr("Encryption is not supported because the extension pack '%s' is missing the encryption plugin (old extension pack installed?)"), + ORACLE_PUEL_EXTPACK_NAME); + } + else + throw setError(VBOX_E_NOT_SUPPORTED, + tr("Encryption is not supported because the extension pack '%s' is missing"), + ORACLE_PUEL_EXTPACK_NAME); + + PVDISK pDisk = NULL; + int vrc = VDCreate(m->vdDiskIfaces, i_convertDeviceType(), &pDisk); + ComAssertRCThrow(vrc, E_FAIL); + + MediumCryptoFilterSettings CryptoSettings; + + i_taskEncryptSettingsSetup(&CryptoSettings, NULL, it->second.c_str(), NULL, false /* fCreateKeyStore */); + vrc = VDFilterAdd(pDisk, "CRYPT", VD_FILTER_FLAGS_READ | VD_FILTER_FLAGS_INFO, CryptoSettings.vdFilterIfaces); + if (RT_FAILURE(vrc)) + throw setErrorBoth(VBOX_E_INVALID_OBJECT_STATE, vrc, + tr("Failed to load the encryption filter: %s"), + i_vdError(vrc).c_str()); + + it = pBase->m->mapProperties.find("CRYPT/KeyId"); + if (it == pBase->m->mapProperties.end()) + throw setError(VBOX_E_INVALID_OBJECT_STATE, + tr("Image is configured for encryption but doesn't has a KeyId set")); + + aPasswordId = it->second.c_str(); + aCipher = CryptoSettings.pszCipherReturned; + RTStrFree(CryptoSettings.pszCipherReturned); + + VDDestroy(pDisk); +# else + throw setError(VBOX_E_NOT_SUPPORTED, + tr("Encryption is not supported because extension pack support is not built in")); +# endif + } + catch (HRESULT hrcXcpt) { hrc = hrcXcpt; } + + return hrc; +} + +HRESULT Medium::checkEncryptionPassword(const com::Utf8Str &aPassword) +{ + HRESULT hrc = S_OK; + + try + { + ComObjPtr<Medium> pBase = i_getBase(); + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + settings::StringsMap::iterator it = pBase->m->mapProperties.find("CRYPT/KeyStore"); + if (it == pBase->m->mapProperties.end()) + throw setError(VBOX_E_NOT_SUPPORTED, + tr("The image is not configured for encryption")); + + if (aPassword.isEmpty()) + throw setError(E_INVALIDARG, + tr("The given password must not be empty")); + +# ifdef VBOX_WITH_EXTPACK + ExtPackManager *pExtPackManager = m->pVirtualBox->i_getExtPackManager(); + if (pExtPackManager->i_isExtPackUsable(ORACLE_PUEL_EXTPACK_NAME)) + { + /* Load the plugin */ + Utf8Str strPlugin; + hrc = pExtPackManager->i_getLibraryPathForExtPack(g_szVDPlugin, ORACLE_PUEL_EXTPACK_NAME, &strPlugin); + if (SUCCEEDED(hrc)) + { + int vrc = VDPluginLoadFromFilename(strPlugin.c_str()); + if (RT_FAILURE(vrc)) + throw setErrorBoth(VBOX_E_NOT_SUPPORTED, vrc, + tr("Retrieving encryption settings of the image failed because the encryption plugin could not be loaded (%s)"), + i_vdError(vrc).c_str()); + } + else + throw setError(VBOX_E_NOT_SUPPORTED, + tr("Encryption is not supported because the extension pack '%s' is missing the encryption plugin (old extension pack installed?)"), + ORACLE_PUEL_EXTPACK_NAME); + } + else + throw setError(VBOX_E_NOT_SUPPORTED, + tr("Encryption is not supported because the extension pack '%s' is missing"), + ORACLE_PUEL_EXTPACK_NAME); + + PVDISK pDisk = NULL; + int vrc = VDCreate(m->vdDiskIfaces, i_convertDeviceType(), &pDisk); + ComAssertRCThrow(vrc, E_FAIL); + + MediumCryptoFilterSettings CryptoSettings; + + i_taskEncryptSettingsSetup(&CryptoSettings, NULL, it->second.c_str(), aPassword.c_str(), + false /* fCreateKeyStore */); + vrc = VDFilterAdd(pDisk, "CRYPT", VD_FILTER_FLAGS_READ, CryptoSettings.vdFilterIfaces); + if (vrc == VERR_VD_PASSWORD_INCORRECT) + throw setError(VBOX_E_PASSWORD_INCORRECT, + tr("The given password is incorrect")); + else if (RT_FAILURE(vrc)) + throw setErrorBoth(VBOX_E_INVALID_OBJECT_STATE, vrc, + tr("Failed to load the encryption filter: %s"), + i_vdError(vrc).c_str()); + + VDDestroy(pDisk); +# else + throw setError(VBOX_E_NOT_SUPPORTED, + tr("Encryption is not supported because extension pack support is not built in")); +# endif + } + catch (HRESULT hrcXcpt) { hrc = hrcXcpt; } + + return hrc; +} + +HRESULT Medium::openForIO(BOOL aWritable, com::Utf8Str const &aPassword, ComPtr<IMediumIO> &aMediumIO) +{ + /* + * Input validation. + */ + if (aWritable && i_isReadOnly()) + return setError(E_ACCESSDENIED, tr("Write access denied: read-only")); + + com::Utf8Str const strKeyId = i_getKeyId(); + if (strKeyId.isEmpty() && aPassword.isNotEmpty()) + return setError(E_INVALIDARG, tr("Password given for unencrypted medium")); + if (strKeyId.isNotEmpty() && aPassword.isEmpty()) + return setError(E_INVALIDARG, tr("Password needed for encrypted medium")); + + /* + * Create IO object and return it. + */ + ComObjPtr<MediumIO> ptrIO; + HRESULT hrc = ptrIO.createObject(); + if (SUCCEEDED(hrc)) + { + hrc = ptrIO->initForMedium(this, m->pVirtualBox, aWritable != FALSE, strKeyId, aPassword); + if (SUCCEEDED(hrc)) + ptrIO.queryInterfaceTo(aMediumIO.asOutParam()); + } + return hrc; +} + + +//////////////////////////////////////////////////////////////////////////////// +// +// Medium public internal methods +// +//////////////////////////////////////////////////////////////////////////////// + +/** + * Internal method to return the medium's parent medium. Must have caller + locking! + * @return + */ +const ComObjPtr<Medium>& Medium::i_getParent() const +{ + return m->pParent; +} + +/** + * Internal method to return the medium's list of child media. Must have caller + locking! + * @return + */ +const MediaList& Medium::i_getChildren() const +{ + return m->llChildren; +} + +/** + * Internal method to return the medium's GUID. Must have caller + locking! + * @return + */ +const Guid& Medium::i_getId() const +{ + return m->id; +} + +/** + * Internal method to return the medium's state. Must have caller + locking! + * @return + */ +MediumState_T Medium::i_getState() const +{ + return m->state; +} + +/** + * Internal method to return the medium's variant. Must have caller + locking! + * @return + */ +MediumVariant_T Medium::i_getVariant() const +{ + return m->variant; +} + +/** + * Internal method which returns true if this medium represents a host drive. + * @return + */ +bool Medium::i_isHostDrive() const +{ + return m->hostDrive; +} + +/** + * Internal method which returns true if this medium is in the process of being closed. + * @return + */ +bool Medium::i_isClosing() const +{ + return m->fClosing; +} + +/** + * Internal method to return the medium's full location. Must have caller + locking! + * @return + */ +const Utf8Str& Medium::i_getLocationFull() const +{ + return m->strLocationFull; +} + +/** + * Internal method to return the medium's format string. Must have caller + locking! + * @return + */ +const Utf8Str& Medium::i_getFormat() const +{ + return m->strFormat; +} + +/** + * Internal method to return the medium's format object. Must have caller + locking! + * @return + */ +const ComObjPtr<MediumFormat>& Medium::i_getMediumFormat() const +{ + return m->formatObj; +} + +/** + * Internal method that returns true if the medium is represented by a file on the host disk + * (and not iSCSI or something). + * @return + */ +bool Medium::i_isMediumFormatFile() const +{ + if ( m->formatObj + && (m->formatObj->i_getCapabilities() & MediumFormatCapabilities_File) + ) + return true; + return false; +} + +/** + * Internal method to return the medium's size. Must have caller + locking! + * @return + */ +uint64_t Medium::i_getSize() const +{ + return m->size; +} + +/** + * Internal method to return the medium's size. Must have caller + locking! + * @return + */ +uint64_t Medium::i_getLogicalSize() const +{ + return m->logicalSize; +} + +/** + * Returns the medium device type. Must have caller + locking! + * @return + */ +DeviceType_T Medium::i_getDeviceType() const +{ + return m->devType; +} + +/** + * Returns the medium type. Must have caller + locking! + * @return + */ +MediumType_T Medium::i_getType() const +{ + return m->type; +} + +/** + * Returns a short version of the location attribute. + * + * @note Must be called from under this object's read or write lock. + */ +Utf8Str Medium::i_getName() +{ + Utf8Str name = RTPathFilename(m->strLocationFull.c_str()); + return name; +} + +/** + * Same as i_addRegistry() except that we don't check the object state, making + * it safe to call with initFromSettings() on the call stack. + */ +bool Medium::i_addRegistryNoCallerCheck(const Guid &id) +{ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + bool fAdd = true; + + // hard disks cannot be in more than one registry + if ( m->devType == DeviceType_HardDisk + && m->llRegistryIDs.size() > 0) + fAdd = false; + + // no need to add the UUID twice + if (fAdd) + { + for (GuidList::const_iterator it = m->llRegistryIDs.begin(); + it != m->llRegistryIDs.end(); + ++it) + { + if ((*it) == id) + { + fAdd = false; + break; + } + } + } + + if (fAdd) + m->llRegistryIDs.push_back(id); + + return fAdd; +} + +/** + * This adds the given UUID to the list of media registries in which this + * medium should be registered. The UUID can either be a machine UUID, + * to add a machine registry, or the global registry UUID as returned by + * VirtualBox::getGlobalRegistryId(). + * + * Note that for hard disks, this method does nothing if the medium is + * already in another registry to avoid having hard disks in more than + * one registry, which causes trouble with keeping diff images in sync. + * See getFirstRegistryMachineId() for details. + * + * @param id + * @return true if the registry was added; false if the given id was already on the list. + */ +bool Medium::i_addRegistry(const Guid &id) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.hrc())) + return false; + return i_addRegistryNoCallerCheck(id); +} + +/** + * This adds the given UUID to the list of media registries in which this + * medium should be registered. The UUID can either be a machine UUID, + * to add a machine registry, or the global registry UUID as returned by + * VirtualBox::getGlobalRegistryId(). Thisis applied to all children. + * + * Note that for hard disks, this method does nothing if the medium is + * already in another registry to avoid having hard disks in more than + * one registry, which causes trouble with keeping diff images in sync. + * See getFirstRegistryMachineId() for details. + * + * @note the caller must hold the media tree lock for reading. + * + * @param id + * @return true if the registry was added; false if the given id was already on the list. + */ +bool Medium::i_addRegistryAll(const Guid &id) +{ + MediaList llMediaTodo; + llMediaTodo.push_back(this); + + bool fAdd = false; + + while (!llMediaTodo.empty()) + { + ComObjPtr<Medium> pMedium = llMediaTodo.front(); + llMediaTodo.pop_front(); + + AutoCaller mediumCaller(pMedium); + if (FAILED(mediumCaller.hrc())) continue; + + fAdd |= pMedium->i_addRegistryNoCallerCheck(id); + + // protected by the medium tree lock held by our original caller + MediaList::const_iterator itBegin = pMedium->i_getChildren().begin(); + MediaList::const_iterator itEnd = pMedium->i_getChildren().end(); + for (MediaList::const_iterator it = itBegin; it != itEnd; ++it) + llMediaTodo.push_back(*it); + } + + return fAdd; +} + +/** + * Removes the given UUID from the list of media registry UUIDs of this medium. + * + * @param id + * @return true if the UUID was found or false if not. + */ +bool Medium::i_removeRegistry(const Guid &id) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.hrc())) + return false; + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + bool fRemove = false; + + /// @todo r=klaus eliminate this code, replace it by using find. + for (GuidList::iterator it = m->llRegistryIDs.begin(); + it != m->llRegistryIDs.end(); + ++it) + { + if ((*it) == id) + { + // getting away with this as the iterator isn't used after + m->llRegistryIDs.erase(it); + fRemove = true; + break; + } + } + + return fRemove; +} + +/** + * Removes the given UUID from the list of media registry UUIDs, for this + * medium and all its children. + * + * @note the caller must hold the media tree lock for reading. + * + * @param id + * @return true if the UUID was found or false if not. + */ +bool Medium::i_removeRegistryAll(const Guid &id) +{ + MediaList llMediaTodo; + llMediaTodo.push_back(this); + + bool fRemove = false; + + while (!llMediaTodo.empty()) + { + ComObjPtr<Medium> pMedium = llMediaTodo.front(); + llMediaTodo.pop_front(); + + AutoCaller mediumCaller(pMedium); + if (FAILED(mediumCaller.hrc())) continue; + + fRemove |= pMedium->i_removeRegistry(id); + + // protected by the medium tree lock held by our original caller + MediaList::const_iterator itBegin = pMedium->i_getChildren().begin(); + MediaList::const_iterator itEnd = pMedium->i_getChildren().end(); + for (MediaList::const_iterator it = itBegin; it != itEnd; ++it) + llMediaTodo.push_back(*it); + } + + return fRemove; +} + +/** + * Returns true if id is in the list of media registries for this medium. + * + * Must have caller + read locking! + * + * @param id + * @return + */ +bool Medium::i_isInRegistry(const Guid &id) +{ + /// @todo r=klaus eliminate this code, replace it by using find. + for (GuidList::const_iterator it = m->llRegistryIDs.begin(); + it != m->llRegistryIDs.end(); + ++it) + { + if (*it == id) + return true; + } + + return false; +} + +/** + * Internal method to return the medium's first registry machine (i.e. the machine in whose + * machine XML this medium is listed). + * + * Every attached medium must now (4.0) reside in at least one media registry, which is identified + * by a UUID. This is either a machine UUID if the machine is from 4.0 or newer, in which case + * machines have their own media registries, or it is the pseudo-UUID of the VirtualBox + * object if the machine is old and still needs the global registry in VirtualBox.xml. + * + * By definition, hard disks may only be in one media registry, in which all its children + * will be stored as well. Otherwise we run into problems with having keep multiple registries + * in sync. (This is the "cloned VM" case in which VM1 may link to the disks of VM2; in this + * case, only VM2's registry is used for the disk in question.) + * + * If there is no medium registry, particularly if the medium has not been attached yet, this + * does not modify uuid and returns false. + * + * ISOs and RAWs, by contrast, can be in more than one repository to make things easier for + * the user. + * + * Must have caller + locking! + * + * @param uuid Receives first registry machine UUID, if available. + * @return true if uuid was set. + */ +bool Medium::i_getFirstRegistryMachineId(Guid &uuid) const +{ + if (m->llRegistryIDs.size()) + { + uuid = m->llRegistryIDs.front(); + return true; + } + return false; +} + +/** + * Marks all the registries in which this medium is registered as modified. + */ +void Medium::i_markRegistriesModified() +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.hrc())) return; + + // Get local copy, as keeping the lock over VirtualBox::markRegistryModified + // causes trouble with the lock order + GuidList llRegistryIDs; + { + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + llRegistryIDs = m->llRegistryIDs; + } + + autoCaller.release(); + + /* Save the error information now, the implicit restore when this goes + * out of scope will throw away spurious additional errors created below. */ + ErrorInfoKeeper eik; + for (GuidList::const_iterator it = llRegistryIDs.begin(); + it != llRegistryIDs.end(); + ++it) + { + m->pVirtualBox->i_markRegistryModified(*it); + } +} + +/** + * Adds the given machine and optionally the snapshot to the list of the objects + * this medium is attached to. + * + * @param aMachineId Machine ID. + * @param aSnapshotId Snapshot ID; when non-empty, adds a snapshot attachment. + */ +HRESULT Medium::i_addBackReference(const Guid &aMachineId, + const Guid &aSnapshotId /*= Guid::Empty*/) +{ + AssertReturn(aMachineId.isValid(), E_FAIL); + + LogFlowThisFunc(("ENTER, aMachineId: {%RTuuid}, aSnapshotId: {%RTuuid}\n", aMachineId.raw(), aSnapshotId.raw())); + + AutoCaller autoCaller(this); + AssertComRCReturnRC(autoCaller.hrc()); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + switch (m->state) + { + case MediumState_Created: + case MediumState_Inaccessible: + case MediumState_LockedRead: + case MediumState_LockedWrite: + break; + + default: + return i_setStateError(); + } + + if (m->numCreateDiffTasks > 0) + return setError(VBOX_E_OBJECT_IN_USE, + tr("Cannot attach medium '%s' {%RTuuid}: %u differencing child media are being created", "", + m->numCreateDiffTasks), + m->strLocationFull.c_str(), + m->id.raw(), + m->numCreateDiffTasks); + + BackRefList::iterator it = std::find_if(m->backRefs.begin(), + m->backRefs.end(), + BackRef::EqualsTo(aMachineId)); + if (it == m->backRefs.end()) + { + BackRef ref(aMachineId, aSnapshotId); + m->backRefs.push_back(ref); + + return S_OK; + } + bool fDvd = false; + { + AutoReadLock arlock(this COMMA_LOCKVAL_SRC_POS); + /* + * Check the medium is DVD and readonly. It's for the case if DVD + * will be able to be writable sometime in the future. + */ + fDvd = m->type == MediumType_Readonly && m->devType == DeviceType_DVD; + } + + // if the caller has not supplied a snapshot ID, then we're attaching + // to a machine a medium which represents the machine's current state, + // so set the flag + + if (aSnapshotId.isZero()) + { + // Allow DVD having MediumType_Readonly to be attached twice. + // (the medium already had been added to back reference) + if (fDvd) + { + it->iRefCnt++; + return S_OK; + } + + /* sanity: no duplicate attachments */ + if (it->fInCurState) + return setError(VBOX_E_OBJECT_IN_USE, + tr("Cannot attach medium '%s' {%RTuuid}: medium is already associated with the current state of machine uuid {%RTuuid}!"), + m->strLocationFull.c_str(), + m->id.raw(), + aMachineId.raw()); + it->fInCurState = true; + + return S_OK; + } + + // otherwise: a snapshot medium is being attached + + /* sanity: no duplicate attachments */ + for (std::list<SnapshotRef>::iterator jt = it->llSnapshotIds.begin(); + jt != it->llSnapshotIds.end(); + ++jt) + { + const Guid &idOldSnapshot = jt->snapshotId; + + if (idOldSnapshot == aSnapshotId) + { + if (fDvd) + { + jt->iRefCnt++; + return S_OK; + } +#ifdef DEBUG + i_dumpBackRefs(); +#endif + return setError(VBOX_E_OBJECT_IN_USE, + tr("Cannot attach medium '%s' {%RTuuid} from snapshot '%RTuuid': medium is already in use by this snapshot!"), + m->strLocationFull.c_str(), + m->id.raw(), + aSnapshotId.raw()); + } + } + + it->llSnapshotIds.push_back(SnapshotRef(aSnapshotId)); + // Do not touch fInCurState, as the image may be attached to the current + // state *and* a snapshot, otherwise we lose the current state association! + + LogFlowThisFuncLeave(); + + return S_OK; +} + +/** + * Removes the given machine and optionally the snapshot from the list of the + * objects this medium is attached to. + * + * @param aMachineId Machine ID. + * @param aSnapshotId Snapshot ID; when non-empty, removes the snapshot + * attachment. + */ +HRESULT Medium::i_removeBackReference(const Guid &aMachineId, + const Guid &aSnapshotId /*= Guid::Empty*/) +{ + AssertReturn(aMachineId.isValid(), E_FAIL); + + AutoCaller autoCaller(this); + AssertComRCReturnRC(autoCaller.hrc()); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + BackRefList::iterator it = + std::find_if(m->backRefs.begin(), m->backRefs.end(), + BackRef::EqualsTo(aMachineId)); + AssertReturn(it != m->backRefs.end(), E_FAIL); + + if (aSnapshotId.isZero()) + { + it->iRefCnt--; + if (it->iRefCnt > 0) + return S_OK; + + /* remove the current state attachment */ + it->fInCurState = false; + } + else + { + /* remove the snapshot attachment */ + std::list<SnapshotRef>::iterator jt = + std::find_if(it->llSnapshotIds.begin(), + it->llSnapshotIds.end(), + SnapshotRef::EqualsTo(aSnapshotId)); + + AssertReturn(jt != it->llSnapshotIds.end(), E_FAIL); + + jt->iRefCnt--; + if (jt->iRefCnt > 0) + return S_OK; + + it->llSnapshotIds.erase(jt); + } + + /* if the backref becomes empty, remove it */ + if (it->fInCurState == false && it->llSnapshotIds.size() == 0) + m->backRefs.erase(it); + + return S_OK; +} + +/** + * Internal method to return the medium's list of backrefs. Must have caller + locking! + * @return + */ +const Guid* Medium::i_getFirstMachineBackrefId() const +{ + if (!m->backRefs.size()) + return NULL; + + return &m->backRefs.front().machineId; +} + +/** + * Internal method which returns a machine that either this medium or one of its children + * is attached to. This is used for finding a replacement media registry when an existing + * media registry is about to be deleted in VirtualBox::unregisterMachine(). + * + * Must have caller + locking, *and* caller must hold the media tree lock! + * @param aId Id to ignore when looking for backrefs. + * @return + */ +const Guid* Medium::i_getAnyMachineBackref(const Guid &aId) const +{ + std::list<const Medium *> llMediaTodo; + llMediaTodo.push_back(this); + + while (!llMediaTodo.empty()) + { + const Medium *pMedium = llMediaTodo.front(); + llMediaTodo.pop_front(); + + if (pMedium->m->backRefs.size()) + { + if (pMedium->m->backRefs.front().machineId != aId) + return &pMedium->m->backRefs.front().machineId; + if (pMedium->m->backRefs.size() > 1) + { + BackRefList::const_iterator it = pMedium->m->backRefs.begin(); + ++it; + return &it->machineId; + } + } + + MediaList::const_iterator itBegin = pMedium->i_getChildren().begin(); + MediaList::const_iterator itEnd = pMedium->i_getChildren().end(); + for (MediaList::const_iterator it = itBegin; it != itEnd; ++it) + llMediaTodo.push_back(*it); + } + + return NULL; +} + +const Guid* Medium::i_getFirstMachineBackrefSnapshotId() const +{ + if (!m->backRefs.size()) + return NULL; + + const BackRef &ref = m->backRefs.front(); + if (ref.llSnapshotIds.empty()) + return NULL; + + return &ref.llSnapshotIds.front().snapshotId; +} + +size_t Medium::i_getMachineBackRefCount() const +{ + return m->backRefs.size(); +} + +#ifdef DEBUG +/** + * Debugging helper that gets called after VirtualBox initialization that writes all + * machine backreferences to the debug log. + */ +void Medium::i_dumpBackRefs() +{ + AutoCaller autoCaller(this); + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + LogFlowThisFunc(("Dumping backrefs for medium '%s':\n", m->strLocationFull.c_str())); + + for (BackRefList::iterator it2 = m->backRefs.begin(); + it2 != m->backRefs.end(); + ++it2) + { + const BackRef &ref = *it2; + LogFlowThisFunc((" Backref from machine {%RTuuid} (fInCurState: %d, iRefCnt: %d)\n", ref.machineId.raw(), ref.fInCurState, ref.iRefCnt)); + + for (std::list<SnapshotRef>::const_iterator jt2 = it2->llSnapshotIds.begin(); + jt2 != it2->llSnapshotIds.end(); + ++jt2) + { + const Guid &id = jt2->snapshotId; + LogFlowThisFunc((" Backref from snapshot {%RTuuid} (iRefCnt = %d)\n", id.raw(), jt2->iRefCnt)); + } + } +} +#endif + +/** + * Checks if the given change of \a aOldPath to \a aNewPath affects the location + * of this media and updates it if necessary to reflect the new location. + * + * @param strOldPath Old path (full). + * @param strNewPath New path (full). + * + * @note Locks this object for writing. + */ +HRESULT Medium::i_updatePath(const Utf8Str &strOldPath, const Utf8Str &strNewPath) +{ + AssertReturn(!strOldPath.isEmpty(), E_FAIL); + AssertReturn(!strNewPath.isEmpty(), E_FAIL); + + AutoCaller autoCaller(this); + if (FAILED(autoCaller.hrc())) return autoCaller.hrc(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + LogFlowThisFunc(("locationFull.before='%s'\n", m->strLocationFull.c_str())); + + const char *pcszMediumPath = m->strLocationFull.c_str(); + + if (RTPathStartsWith(pcszMediumPath, strOldPath.c_str())) + { + Utf8Str newPath(strNewPath); + newPath.append(pcszMediumPath + strOldPath.length()); + unconst(m->strLocationFull) = newPath; + + m->pVirtualBox->i_onMediumConfigChanged(this); + + LogFlowThisFunc(("locationFull.after='%s'\n", m->strLocationFull.c_str())); + // we changed something + return S_OK; + } + + // no change was necessary, signal error which the caller needs to interpret + return VBOX_E_FILE_ERROR; +} + +/** + * Returns the base medium of the media chain this medium is part of. + * + * The base medium is found by walking up the parent-child relationship axis. + * If the medium doesn't have a parent (i.e. it's a base medium), it + * returns itself in response to this method. + * + * @param aLevel Where to store the number of ancestors of this medium + * (zero for the base), may be @c NULL. + * + * @note Locks medium tree for reading. + */ +ComObjPtr<Medium> Medium::i_getBase(uint32_t *aLevel /*= NULL*/) +{ + ComObjPtr<Medium> pBase; + + /* it is possible that some previous/concurrent uninit has already cleared + * the pVirtualBox reference, and in this case we don't need to continue */ + ComObjPtr<VirtualBox> pVirtualBox(m->pVirtualBox); + if (!pVirtualBox) + return pBase; + + /* we access m->pParent */ + AutoReadLock treeLock(pVirtualBox->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS); + + AutoCaller autoCaller(this); + AssertReturn(autoCaller.isOk(), pBase); + + pBase = this; + uint32_t level = 0; + + if (m->pParent) + { + for (;;) + { + AutoCaller baseCaller(pBase); + AssertReturn(baseCaller.isOk(), pBase); + + if (pBase->m->pParent.isNull()) + break; + + pBase = pBase->m->pParent; + ++level; + } + } + + if (aLevel != NULL) + *aLevel = level; + + return pBase; +} + +/** + * Returns the depth of this medium in the media chain. + * + * @note Locks medium tree for reading. + */ +uint32_t Medium::i_getDepth() +{ + /* it is possible that some previous/concurrent uninit has already cleared + * the pVirtualBox reference, and in this case we don't need to continue */ + ComObjPtr<VirtualBox> pVirtualBox(m->pVirtualBox); + if (!pVirtualBox) + return 1; + + /* we access m->pParent */ + AutoReadLock treeLock(pVirtualBox->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS); + + uint32_t cDepth = 0; + ComObjPtr<Medium> pMedium(this); + while (!pMedium.isNull()) + { + AutoCaller autoCaller(this); + AssertReturn(autoCaller.isOk(), cDepth + 1); + + pMedium = pMedium->m->pParent; + cDepth++; + } + + return cDepth; +} + +/** + * Returns @c true if this medium cannot be modified because it has + * dependents (children) or is part of the snapshot. Related to the medium + * type and posterity, not to the current media state. + * + * @note Locks this object and medium tree for reading. + */ +bool Medium::i_isReadOnly() +{ + /* it is possible that some previous/concurrent uninit has already cleared + * the pVirtualBox reference, and in this case we don't need to continue */ + ComObjPtr<VirtualBox> pVirtualBox(m->pVirtualBox); + if (!pVirtualBox) + return false; + + /* we access children */ + AutoReadLock treeLock(m->pVirtualBox->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS); + + AutoCaller autoCaller(this); + AssertComRCReturn(autoCaller.hrc(), false); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + switch (m->type) + { + case MediumType_Normal: + { + if (i_getChildren().size() != 0) + return true; + + for (BackRefList::const_iterator it = m->backRefs.begin(); + it != m->backRefs.end(); ++it) + if (it->llSnapshotIds.size() != 0) + return true; + + if (m->variant & MediumVariant_VmdkStreamOptimized) + return true; + + return false; + } + case MediumType_Immutable: + case MediumType_MultiAttach: + return true; + case MediumType_Writethrough: + case MediumType_Shareable: + case MediumType_Readonly: /* explicit readonly media has no diffs */ + return false; + default: + break; + } + + AssertFailedReturn(false); +} + +/** + * Internal method to update the medium's id. Must have caller + locking! + */ +void Medium::i_updateId(const Guid &id) +{ + unconst(m->id) = id; +} + +/** + * Saves the settings of one medium. + * + * @note Caller MUST take care of the medium tree lock and caller. + * + * @param data Settings struct to be updated. + * @param strHardDiskFolder Folder for which paths should be relative. + */ +void Medium::i_saveSettingsOne(settings::Medium &data, const Utf8Str &strHardDiskFolder) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + data.uuid = m->id; + + // make path relative if needed + if ( !strHardDiskFolder.isEmpty() + && RTPathStartsWith(m->strLocationFull.c_str(), strHardDiskFolder.c_str()) + ) + data.strLocation = m->strLocationFull.substr(strHardDiskFolder.length() + 1); + else + data.strLocation = m->strLocationFull; + data.strFormat = m->strFormat; + + /* optional, only for diffs, default is false */ + if (m->pParent) + data.fAutoReset = m->autoReset; + else + data.fAutoReset = false; + + /* optional */ + data.strDescription = m->strDescription; + + /* optional properties */ + data.properties.clear(); + + /* handle iSCSI initiator secrets transparently */ + bool fHaveInitiatorSecretEncrypted = false; + Utf8Str strCiphertext; + settings::StringsMap::const_iterator itPln = m->mapProperties.find("InitiatorSecret"); + if ( itPln != m->mapProperties.end() + && !itPln->second.isEmpty()) + { + /* Encrypt the plain secret. If that does not work (i.e. no or wrong settings key + * specified), just use the encrypted secret (if there is any). */ + int vrc = m->pVirtualBox->i_encryptSetting(itPln->second, &strCiphertext); + if (RT_SUCCESS(vrc)) + fHaveInitiatorSecretEncrypted = true; + } + for (settings::StringsMap::const_iterator it = m->mapProperties.begin(); + it != m->mapProperties.end(); + ++it) + { + /* only save properties that have non-default values */ + if (!it->second.isEmpty()) + { + const Utf8Str &name = it->first; + const Utf8Str &value = it->second; + bool fCreateOnly = false; + for (MediumFormat::PropertyArray::const_iterator itf = m->formatObj->i_getProperties().begin(); + itf != m->formatObj->i_getProperties().end(); + ++itf) + { + if ( itf->strName.equals(name) + && (itf->flags & VD_CFGKEY_CREATEONLY)) + { + fCreateOnly = true; + break; + } + } + if (!fCreateOnly) + /* do NOT store the plain InitiatorSecret */ + if ( !fHaveInitiatorSecretEncrypted + || !name.equals("InitiatorSecret")) + data.properties[name] = value; + } + } + if (fHaveInitiatorSecretEncrypted) + data.properties["InitiatorSecretEncrypted"] = strCiphertext; + + /* only for base media */ + if (m->pParent.isNull()) + data.hdType = m->type; +} + +/** + * Saves medium data by putting it into the provided data structure. + * The settings of all children is saved, too. + * + * @param data Settings struct to be updated. + * @param strHardDiskFolder Folder for which paths should be relative. + * + * @note Locks this object, medium tree and children for reading. + */ +HRESULT Medium::i_saveSettings(settings::Medium &data, + const Utf8Str &strHardDiskFolder) +{ + /* we access m->pParent */ + AutoReadLock treeLock(m->pVirtualBox->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS); + + AutoCaller autoCaller(this); + if (FAILED(autoCaller.hrc())) return autoCaller.hrc(); + + MediaList llMediaTodo; + llMediaTodo.push_back(this); + std::list<settings::Medium *> llSettingsTodo; + llSettingsTodo.push_back(&data); + + while (!llMediaTodo.empty()) + { + ComObjPtr<Medium> pMedium = llMediaTodo.front(); + llMediaTodo.pop_front(); + settings::Medium *current = llSettingsTodo.front(); + llSettingsTodo.pop_front(); + + AutoCaller mediumCaller(pMedium); + if (FAILED(mediumCaller.hrc())) return mediumCaller.hrc(); + + pMedium->i_saveSettingsOne(*current, strHardDiskFolder); + + /* save all children */ + MediaList::const_iterator itBegin = pMedium->i_getChildren().begin(); + MediaList::const_iterator itEnd = pMedium->i_getChildren().end(); + for (MediaList::const_iterator it = itBegin; it != itEnd; ++it) + { + llMediaTodo.push_back(*it); + current->llChildren.push_back(settings::Medium::Empty); + llSettingsTodo.push_back(¤t->llChildren.back()); + } + } + + return S_OK; +} + +/** + * Constructs a medium lock list for this medium. The lock is not taken. + * + * @note Caller MUST NOT hold the media tree or medium lock. + * + * @param fFailIfInaccessible If true, this fails with an error if a medium is inaccessible. If false, + * inaccessible media are silently skipped and not locked (i.e. their state remains "Inaccessible"); + * this is necessary for a VM's removable media VM startup for which we do not want to fail. + * @param pToLockWrite If not NULL, associate a write lock with this medium object. + * @param fMediumLockWriteAll Whether to associate a write lock to all other media too. + * @param pToBeParent Medium which will become the parent of this medium. + * @param mediumLockList Where to store the resulting list. + */ +HRESULT Medium::i_createMediumLockList(bool fFailIfInaccessible, + Medium *pToLockWrite, + bool fMediumLockWriteAll, + Medium *pToBeParent, + MediumLockList &mediumLockList) +{ + /** @todo r=klaus this needs to be reworked, as the code below uses + * i_getParent without holding the tree lock, and changing this is + * a significant amount of effort. */ + Assert(!m->pVirtualBox->i_getMediaTreeLockHandle().isWriteLockOnCurrentThread()); + Assert(!isWriteLockOnCurrentThread()); + + AutoCaller autoCaller(this); + if (FAILED(autoCaller.hrc())) return autoCaller.hrc(); + + HRESULT hrc = S_OK; + + /* paranoid sanity checking if the medium has a to-be parent medium */ + if (pToBeParent) + { + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + ComAssertRet(i_getParent().isNull(), E_FAIL); + ComAssertRet(i_getChildren().size() == 0, E_FAIL); + } + + ErrorInfoKeeper eik; + MultiResult mrc(S_OK); + + ComObjPtr<Medium> pMedium = this; + while (!pMedium.isNull()) + { + AutoReadLock alock(pMedium COMMA_LOCKVAL_SRC_POS); + + /* Accessibility check must be first, otherwise locking interferes + * with getting the medium state. Lock lists are not created for + * fun, and thus getting the medium status is no luxury. */ + MediumState_T mediumState = pMedium->i_getState(); + if (mediumState == MediumState_Inaccessible) + { + alock.release(); + hrc = pMedium->i_queryInfo(false /* fSetImageId */, false /* fSetParentId */, autoCaller); + alock.acquire(); + if (FAILED(hrc)) return hrc; + + mediumState = pMedium->i_getState(); + if (mediumState == MediumState_Inaccessible) + { + // ignore inaccessible ISO media and silently return S_OK, + // otherwise VM startup (esp. restore) may fail without good reason + if (!fFailIfInaccessible) + return S_OK; + + // otherwise report an error + Bstr error; + hrc = pMedium->COMGETTER(LastAccessError)(error.asOutParam()); + if (FAILED(hrc)) return hrc; + + /* collect multiple errors */ + eik.restore(); + Assert(!error.isEmpty()); + mrc = setError(E_FAIL, + "%ls", + error.raw()); + // error message will be something like + // "Could not open the medium ... VD: error VERR_FILE_NOT_FOUND opening image file ... (VERR_FILE_NOT_FOUND). + eik.fetch(); + } + } + + if (pMedium == pToLockWrite) + mediumLockList.Prepend(pMedium, true); + else + mediumLockList.Prepend(pMedium, fMediumLockWriteAll); + + pMedium = pMedium->i_getParent(); + if (pMedium.isNull() && pToBeParent) + { + pMedium = pToBeParent; + pToBeParent = NULL; + } + } + + return mrc; +} + +/** + * Creates a new differencing storage unit using the format of the given target + * medium and the location. Note that @c aTarget must be NotCreated. + * + * The @a aMediumLockList parameter contains the associated medium lock list, + * which must be in locked state. If @a aWait is @c true then the caller is + * responsible for unlocking. + * + * If @a aProgress is not NULL but the object it points to is @c null then a + * new progress object will be created and assigned to @a *aProgress on + * success, otherwise the existing progress object is used. If @a aProgress is + * NULL, then no progress object is created/used at all. + * + * When @a aWait is @c false, this method will create a thread to perform the + * create operation asynchronously and will return immediately. Otherwise, it + * will perform the operation on the calling thread and will not return to the + * caller until the operation is completed. Note that @a aProgress cannot be + * NULL when @a aWait is @c false (this method will assert in this case). + * + * @param aTarget Target medium. + * @param aVariant Precise medium variant to create. + * @param aMediumLockList List of media which should be locked. + * @param aProgress Where to find/store a Progress object to track + * operation completion. + * @param aWait @c true if this method should block instead of + * creating an asynchronous thread. + * @param aNotify Notify about media for which metadata is changed + * during execution of the function. + * + * @note Locks this object and @a aTarget for writing. + */ +HRESULT Medium::i_createDiffStorage(ComObjPtr<Medium> &aTarget, + MediumVariant_T aVariant, + MediumLockList *aMediumLockList, + ComObjPtr<Progress> *aProgress, + bool aWait, + bool aNotify) +{ + AssertReturn(!aTarget.isNull(), E_FAIL); + AssertReturn(aMediumLockList, E_FAIL); + AssertReturn(aProgress != NULL || aWait == true, E_FAIL); + + AutoCaller autoCaller(this); + if (FAILED(autoCaller.hrc())) return autoCaller.hrc(); + + AutoCaller targetCaller(aTarget); + if (FAILED(targetCaller.hrc())) return targetCaller.hrc(); + + HRESULT hrc = S_OK; + ComObjPtr<Progress> pProgress; + Medium::Task *pTask = NULL; + + try + { + AutoMultiWriteLock2 alock(this, aTarget COMMA_LOCKVAL_SRC_POS); + + ComAssertThrow( m->type != MediumType_Writethrough + && m->type != MediumType_Shareable + && m->type != MediumType_Readonly, E_FAIL); + ComAssertThrow(m->state == MediumState_LockedRead, E_FAIL); + + if (aTarget->m->state != MediumState_NotCreated) + throw aTarget->i_setStateError(); + + /* Check that the medium is not attached to the current state of + * any VM referring to it. */ + for (BackRefList::const_iterator it = m->backRefs.begin(); + it != m->backRefs.end(); + ++it) + { + if (it->fInCurState) + { + /* Note: when a VM snapshot is being taken, all normal media + * attached to the VM in the current state will be, as an + * exception, also associated with the snapshot which is about + * to create (see SnapshotMachine::init()) before deassociating + * them from the current state (which takes place only on + * success in Machine::fixupHardDisks()), so that the size of + * snapshotIds will be 1 in this case. The extra condition is + * used to filter out this legal situation. */ + if (it->llSnapshotIds.size() == 0) + throw setError(VBOX_E_INVALID_OBJECT_STATE, + tr("Medium '%s' is attached to a virtual machine with UUID {%RTuuid}. No differencing media based on it may be created until it is detached"), + m->strLocationFull.c_str(), it->machineId.raw()); + + Assert(it->llSnapshotIds.size() == 1); + } + } + + if (aProgress != NULL) + { + /* use the existing progress object... */ + pProgress = *aProgress; + + /* ...but create a new one if it is null */ + if (pProgress.isNull()) + { + pProgress.createObject(); + hrc = pProgress->init(m->pVirtualBox, + static_cast<IMedium*>(this), + BstrFmt(tr("Creating differencing medium storage unit '%s'"), + aTarget->m->strLocationFull.c_str()).raw(), + TRUE /* aCancelable */); + if (FAILED(hrc)) + throw hrc; + } + } + + /* setup task object to carry out the operation sync/async */ + pTask = new Medium::CreateDiffTask(this, pProgress, aTarget, aVariant, + aMediumLockList, + aWait /* fKeepMediumLockList */, + aNotify); + hrc = pTask->hrc(); + AssertComRC(hrc); + if (FAILED(hrc)) + throw hrc; + + /* register a task (it will deregister itself when done) */ + ++m->numCreateDiffTasks; + Assert(m->numCreateDiffTasks != 0); /* overflow? */ + + aTarget->m->state = MediumState_Creating; + } + catch (HRESULT hrcXcpt) { hrc = hrcXcpt; } + + if (SUCCEEDED(hrc)) + { + if (aWait) + { + hrc = pTask->runNow(); + delete pTask; + } + else + hrc = pTask->createThread(); + pTask = NULL; + if (SUCCEEDED(hrc) && aProgress != NULL) + *aProgress = pProgress; + } + else if (pTask != NULL) + delete pTask; + + return hrc; +} + +/** + * Returns a preferred format for differencing media. + */ +Utf8Str Medium::i_getPreferredDiffFormat() +{ + AutoCaller autoCaller(this); + AssertComRCReturn(autoCaller.hrc(), Utf8Str::Empty); + + /* check that our own format supports diffs */ + if (!(m->formatObj->i_getCapabilities() & MediumFormatCapabilities_Differencing)) + { + /* use the default format if not */ + Utf8Str tmp; + m->pVirtualBox->i_getDefaultHardDiskFormat(tmp); + return tmp; + } + + /* m->strFormat is const, no need to lock */ + return m->strFormat; +} + +/** + * Returns a preferred variant for differencing media. + */ +MediumVariant_T Medium::i_getPreferredDiffVariant() +{ + AutoCaller autoCaller(this); + AssertComRCReturn(autoCaller.hrc(), MediumVariant_Standard); + + /* check that our own format supports diffs */ + if (!(m->formatObj->i_getCapabilities() & MediumFormatCapabilities_Differencing)) + return MediumVariant_Standard; + + /* m->variant is const, no need to lock */ + ULONG mediumVariantFlags = (ULONG)m->variant; + mediumVariantFlags &= ~(ULONG)(MediumVariant_Fixed | MediumVariant_VmdkStreamOptimized | MediumVariant_VmdkESX | MediumVariant_VmdkRawDisk); + mediumVariantFlags |= MediumVariant_Diff; + return (MediumVariant_T)mediumVariantFlags; +} + +/** + * Implementation for the public Medium::Close() with the exception of calling + * VirtualBox::saveRegistries(), in case someone wants to call this for several + * media. + * + * After this returns with success, uninit() has been called on the medium, and + * the object is no longer usable ("not ready" state). + * + * @param autoCaller AutoCaller instance which must have been created on the caller's + * stack for this medium. This gets released hereupon + * which the Medium instance gets uninitialized. + * @return + */ +HRESULT Medium::i_close(AutoCaller &autoCaller) +{ + // must temporarily drop the caller, need the tree lock first + autoCaller.release(); + + // we're accessing parent/child and backrefs, so lock the tree first, then ourselves + AutoMultiWriteLock2 multilock(&m->pVirtualBox->i_getMediaTreeLockHandle(), + this->lockHandle() + COMMA_LOCKVAL_SRC_POS); + + autoCaller.add(); + if (FAILED(autoCaller.hrc())) return autoCaller.hrc(); + + /* Wait for a concurrently running Medium::i_queryInfo to complete. */ + while (m->queryInfoRunning) + { + autoCaller.release(); + multilock.release(); + /* Must not hold the media tree lock, as Medium::i_queryInfo needs + * this lock and thus we would run into a deadlock here. */ + Assert(!m->pVirtualBox->i_getMediaTreeLockHandle().isWriteLockOnCurrentThread()); + /* must not hold the object lock now */ + Assert(!isWriteLockOnCurrentThread()); + { + AutoReadLock qlock(m->queryInfoSem COMMA_LOCKVAL_SRC_POS); + } + multilock.acquire(); + autoCaller.add(); + if (FAILED(autoCaller.hrc())) return autoCaller.hrc(); + } + + LogFlowFunc(("ENTER for %s\n", i_getLocationFull().c_str())); + + bool wasCreated = true; + + switch (m->state) + { + case MediumState_NotCreated: + wasCreated = false; + break; + case MediumState_Created: + case MediumState_Inaccessible: + break; + default: + return i_setStateError(); + } + + if (m->fClosing) + return setError(VBOX_E_OBJECT_IN_USE, + tr("Medium '%s' cannot be closed because it is already in the process of being closed", ""), + m->strLocationFull.c_str()); + + if (m->backRefs.size() != 0) + return setError(VBOX_E_OBJECT_IN_USE, + tr("Medium '%s' cannot be closed because it is still attached to %d virtual machines", "", + m->backRefs.size()), + m->strLocationFull.c_str(), m->backRefs.size()); + + // perform extra media-dependent close checks + HRESULT hrc = i_canClose(); + if (FAILED(hrc)) return hrc; + + m->fClosing = true; + + if (wasCreated) + { + // remove from the list of known media before performing actual + // uninitialization (to keep the media registry consistent on + // failure to do so) + hrc = i_unregisterWithVirtualBox(); + if (FAILED(hrc)) return hrc; + + multilock.release(); + // Release the AutoCaller now, as otherwise uninit() will simply hang. + // Needs to be done before mark the registries as modified and saving + // the registry, as otherwise there may be a deadlock with someone else + // closing this object while we're in i_saveModifiedRegistries(), which + // needs the media tree lock, which the other thread holds until after + // uninit() below. + autoCaller.release(); + i_markRegistriesModified(); + m->pVirtualBox->i_saveModifiedRegistries(); + } + else + { + multilock.release(); + // release the AutoCaller, as otherwise uninit() will simply hang + autoCaller.release(); + } + + uninit(); + + LogFlowFuncLeave(); + + return hrc; +} + +/** + * Deletes the medium storage unit. + * + * If @a aProgress is not NULL but the object it points to is @c null then a new + * progress object will be created and assigned to @a *aProgress on success, + * otherwise the existing progress object is used. If Progress is NULL, then no + * progress object is created/used at all. + * + * When @a aWait is @c false, this method will create a thread to perform the + * delete operation asynchronously and will return immediately. Otherwise, it + * will perform the operation on the calling thread and will not return to the + * caller until the operation is completed. Note that @a aProgress cannot be + * NULL when @a aWait is @c false (this method will assert in this case). + * + * @param aProgress Where to find/store a Progress object to track operation + * completion. + * @param aWait @c true if this method should block instead of creating + * an asynchronous thread. + * @param aNotify Notify about media for which metadata is changed + * during execution of the function. + * + * @note Locks mVirtualBox and this object for writing. Locks medium tree for + * writing. + */ +HRESULT Medium::i_deleteStorage(ComObjPtr<Progress> *aProgress, + bool aWait, bool aNotify) +{ + AssertReturn(aProgress != NULL || aWait == true, E_FAIL); + + HRESULT hrc = S_OK; + ComObjPtr<Progress> pProgress; + Medium::Task *pTask = NULL; + + try + { + /* we're accessing the media tree, and i_canClose() needs it too */ + AutoWriteLock treelock(m->pVirtualBox->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS); + + AutoCaller autoCaller(this); + AssertComRCThrowRC(autoCaller.hrc()); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + LogFlowThisFunc(("aWait=%RTbool locationFull=%s\n", aWait, i_getLocationFull().c_str() )); + + if ( !(m->formatObj->i_getCapabilities() & ( MediumFormatCapabilities_CreateDynamic + | MediumFormatCapabilities_CreateFixed))) + throw setError(VBOX_E_NOT_SUPPORTED, + tr("Medium format '%s' does not support storage deletion"), + m->strFormat.c_str()); + + /* Wait for a concurrently running Medium::i_queryInfo to complete. */ + /** @todo r=klaus would be great if this could be moved to the async + * part of the operation as it can take quite a while */ + while (m->queryInfoRunning) + { + alock.release(); + autoCaller.release(); + treelock.release(); + /* Must not hold the media tree lock or the object lock, as + * Medium::i_queryInfo needs this lock and thus we would run + * into a deadlock here. */ + Assert(!m->pVirtualBox->i_getMediaTreeLockHandle().isWriteLockOnCurrentThread()); + Assert(!isWriteLockOnCurrentThread()); + { + AutoReadLock qlock(m->queryInfoSem COMMA_LOCKVAL_SRC_POS); + } + treelock.acquire(); + autoCaller.add(); + AssertComRCThrowRC(autoCaller.hrc()); + alock.acquire(); + } + + /* Note that we are fine with Inaccessible state too: a) for symmetry + * with create calls and b) because it doesn't really harm to try, if + * it is really inaccessible, the delete operation will fail anyway. + * Accepting Inaccessible state is especially important because all + * registered media are initially Inaccessible upon VBoxSVC startup + * until COMGETTER(RefreshState) is called. Accept Deleting state + * because some callers need to put the medium in this state early + * to prevent races. */ + switch (m->state) + { + case MediumState_Created: + case MediumState_Deleting: + case MediumState_Inaccessible: + break; + default: + throw i_setStateError(); + } + + if (m->backRefs.size() != 0) + { + Utf8Str strMachines; + for (BackRefList::const_iterator it = m->backRefs.begin(); + it != m->backRefs.end(); + ++it) + { + const BackRef &b = *it; + if (strMachines.length()) + strMachines.append(", "); + strMachines.append(b.machineId.toString().c_str()); + } +#ifdef DEBUG + i_dumpBackRefs(); +#endif + throw setError(VBOX_E_OBJECT_IN_USE, + tr("Cannot delete storage: medium '%s' is still attached to the following %d virtual machine(s): %s", + "", m->backRefs.size()), + m->strLocationFull.c_str(), + m->backRefs.size(), + strMachines.c_str()); + } + + hrc = i_canClose(); + if (FAILED(hrc)) + throw hrc; + + /* go to Deleting state, so that the medium is not actually locked */ + if (m->state != MediumState_Deleting) + { + hrc = i_markForDeletion(); + if (FAILED(hrc)) + throw hrc; + } + + /* Build the medium lock list. */ + MediumLockList *pMediumLockList(new MediumLockList()); + alock.release(); + autoCaller.release(); + treelock.release(); + hrc = i_createMediumLockList(true /* fFailIfInaccessible */, + this /* pToLockWrite */, + false /* fMediumLockWriteAll */, + NULL, + *pMediumLockList); + treelock.acquire(); + autoCaller.add(); + AssertComRCThrowRC(autoCaller.hrc()); + alock.acquire(); + if (FAILED(hrc)) + { + delete pMediumLockList; + throw hrc; + } + + alock.release(); + autoCaller.release(); + treelock.release(); + hrc = pMediumLockList->Lock(); + treelock.acquire(); + autoCaller.add(); + AssertComRCThrowRC(autoCaller.hrc()); + alock.acquire(); + if (FAILED(hrc)) + { + delete pMediumLockList; + throw setError(hrc, + tr("Failed to lock media when deleting '%s'"), + i_getLocationFull().c_str()); + } + + /* try to remove from the list of known media before performing + * actual deletion (we favor the consistency of the media registry + * which would have been broken if unregisterWithVirtualBox() failed + * after we successfully deleted the storage) */ + hrc = i_unregisterWithVirtualBox(); + if (FAILED(hrc)) + throw hrc; + // no longer need lock + alock.release(); + autoCaller.release(); + treelock.release(); + i_markRegistriesModified(); + + if (aProgress != NULL) + { + /* use the existing progress object... */ + pProgress = *aProgress; + + /* ...but create a new one if it is null */ + if (pProgress.isNull()) + { + pProgress.createObject(); + hrc = pProgress->init(m->pVirtualBox, + static_cast<IMedium*>(this), + BstrFmt(tr("Deleting medium storage unit '%s'"), m->strLocationFull.c_str()).raw(), + FALSE /* aCancelable */); + if (FAILED(hrc)) + throw hrc; + } + } + + /* setup task object to carry out the operation sync/async */ + pTask = new Medium::DeleteTask(this, pProgress, pMediumLockList, false, aNotify); + hrc = pTask->hrc(); + AssertComRC(hrc); + if (FAILED(hrc)) + throw hrc; + } + catch (HRESULT hrcXcpt) { hrc = hrcXcpt; } + + if (SUCCEEDED(hrc)) + { + if (aWait) + { + hrc = pTask->runNow(); + delete pTask; + } + else + hrc = pTask->createThread(); + pTask = NULL; + if (SUCCEEDED(hrc) && aProgress != NULL) + *aProgress = pProgress; + } + else + { + if (pTask) + delete pTask; + + /* Undo deleting state if necessary. */ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + /* Make sure that any error signalled by unmarkForDeletion() is not + * ending up in the error list (if the caller uses MultiResult). It + * usually is spurious, as in most cases the medium hasn't been marked + * for deletion when the error was thrown above. */ + ErrorInfoKeeper eik; + i_unmarkForDeletion(); + } + + return hrc; +} + +/** + * Mark a medium for deletion. + * + * @note Caller must hold the write lock on this medium! + */ +HRESULT Medium::i_markForDeletion() +{ + ComAssertRet(isWriteLockOnCurrentThread(), E_FAIL); + switch (m->state) + { + case MediumState_Created: + case MediumState_Inaccessible: + m->preLockState = m->state; + m->state = MediumState_Deleting; + return S_OK; + default: + return i_setStateError(); + } +} + +/** + * Removes the "mark for deletion". + * + * @note Caller must hold the write lock on this medium! + */ +HRESULT Medium::i_unmarkForDeletion() +{ + ComAssertRet(isWriteLockOnCurrentThread(), E_FAIL); + switch (m->state) + { + case MediumState_Deleting: + m->state = m->preLockState; + return S_OK; + default: + return i_setStateError(); + } +} + +/** + * Mark a medium for deletion which is in locked state. + * + * @note Caller must hold the write lock on this medium! + */ +HRESULT Medium::i_markLockedForDeletion() +{ + ComAssertRet(isWriteLockOnCurrentThread(), E_FAIL); + if ( ( m->state == MediumState_LockedRead + || m->state == MediumState_LockedWrite) + && m->preLockState == MediumState_Created) + { + m->preLockState = MediumState_Deleting; + return S_OK; + } + else + return i_setStateError(); +} + +/** + * Removes the "mark for deletion" for a medium in locked state. + * + * @note Caller must hold the write lock on this medium! + */ +HRESULT Medium::i_unmarkLockedForDeletion() +{ + ComAssertRet(isWriteLockOnCurrentThread(), E_FAIL); + if ( ( m->state == MediumState_LockedRead + || m->state == MediumState_LockedWrite) + && m->preLockState == MediumState_Deleting) + { + m->preLockState = MediumState_Created; + return S_OK; + } + else + return i_setStateError(); +} + +/** + * Queries the preferred merge direction from this to the other medium, i.e. + * the one which requires the least amount of I/O and therefore time and + * disk consumption. + * + * @returns Status code. + * @retval E_FAIL in case determining the merge direction fails for some reason, + * for example if getting the size of the media fails. There is no + * error set though and the caller is free to continue to find out + * what was going wrong later. Leaves fMergeForward unset. + * @retval VBOX_E_INVALID_OBJECT_STATE if both media are not related to each other + * An error is set. + * @param pOther The other medium to merge with. + * @param fMergeForward Resulting preferred merge direction (out). + */ +HRESULT Medium::i_queryPreferredMergeDirection(const ComObjPtr<Medium> &pOther, + bool &fMergeForward) +{ + AssertReturn(pOther != NULL, E_FAIL); + AssertReturn(pOther != this, E_FAIL); + + HRESULT hrc = S_OK; + bool fThisParent = false; /**<< Flag whether this medium is the parent of pOther. */ + + try + { + // locking: we need the tree lock first because we access parent pointers + AutoWriteLock treeLock(m->pVirtualBox->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS); + + AutoCaller autoCaller(this); + AssertComRCThrowRC(autoCaller.hrc()); + + AutoCaller otherCaller(pOther); + AssertComRCThrowRC(otherCaller.hrc()); + + /* more sanity checking and figuring out the current merge direction */ + ComObjPtr<Medium> pMedium = i_getParent(); + while (!pMedium.isNull() && pMedium != pOther) + pMedium = pMedium->i_getParent(); + if (pMedium == pOther) + fThisParent = false; + else + { + pMedium = pOther->i_getParent(); + while (!pMedium.isNull() && pMedium != this) + pMedium = pMedium->i_getParent(); + if (pMedium == this) + fThisParent = true; + else + { + Utf8Str tgtLoc; + { + AutoReadLock alock(pOther COMMA_LOCKVAL_SRC_POS); + tgtLoc = pOther->i_getLocationFull(); + } + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + throw setError(VBOX_E_INVALID_OBJECT_STATE, + tr("Media '%s' and '%s' are unrelated"), + m->strLocationFull.c_str(), tgtLoc.c_str()); + } + } + + /* + * Figure out the preferred merge direction. The current way is to + * get the current sizes of file based images and select the merge + * direction depending on the size. + * + * Can't use the VD API to get current size here as the media might + * be write locked by a running VM. Resort to RTFileQuerySize(). + */ + int vrc = VINF_SUCCESS; + uint64_t cbMediumThis = 0; + uint64_t cbMediumOther = 0; + + if (i_isMediumFormatFile() && pOther->i_isMediumFormatFile()) + { + vrc = RTFileQuerySizeByPath(this->i_getLocationFull().c_str(), &cbMediumThis); + if (RT_SUCCESS(vrc)) + { + vrc = RTFileQuerySizeByPath(pOther->i_getLocationFull().c_str(), + &cbMediumOther); + } + + if (RT_FAILURE(vrc)) + hrc = E_FAIL; + else + { + /* + * Check which merge direction might be more optimal. + * This method is not bullet proof of course as there might + * be overlapping blocks in the images so the file size is + * not the best indicator but it is good enough for our purpose + * and everything else is too complicated, especially when the + * media are used by a running VM. + */ + + uint32_t mediumVariants = MediumVariant_Fixed | MediumVariant_VmdkStreamOptimized; + uint32_t mediumCaps = MediumFormatCapabilities_CreateDynamic | MediumFormatCapabilities_File; + + bool fDynamicOther = pOther->i_getMediumFormat()->i_getCapabilities() & mediumCaps + && pOther->i_getVariant() & ~mediumVariants; + bool fDynamicThis = i_getMediumFormat()->i_getCapabilities() & mediumCaps + && i_getVariant() & ~mediumVariants; + bool fMergeIntoThis = (fDynamicThis && !fDynamicOther) + || (fDynamicThis == fDynamicOther && cbMediumThis > cbMediumOther); + fMergeForward = fMergeIntoThis != fThisParent; + } + } + } + catch (HRESULT hrcXcpt) { hrc = hrcXcpt; } + + return hrc; +} + +/** + * Prepares this (source) medium, target medium and all intermediate media + * for the merge operation. + * + * This method is to be called prior to calling the #mergeTo() to perform + * necessary consistency checks and place involved media to appropriate + * states. If #mergeTo() is not called or fails, the state modifications + * performed by this method must be undone by #i_cancelMergeTo(). + * + * See #mergeTo() for more information about merging. + * + * @param pTarget Target medium. + * @param aMachineId Allowed machine attachment. NULL means do not check. + * @param aSnapshotId Allowed snapshot attachment. NULL or empty UUID means + * do not check. + * @param fLockMedia Flag whether to lock the medium lock list or not. + * If set to false and the medium lock list locking fails + * later you must call #i_cancelMergeTo(). + * @param fMergeForward Resulting merge direction (out). + * @param pParentForTarget New parent for target medium after merge (out). + * @param aChildrenToReparent Medium lock list containing all children of the + * source which will have to be reparented to the target + * after merge (out). + * @param aMediumLockList Medium locking information (out). + * + * @note Locks medium tree for reading. Locks this object, aTarget and all + * intermediate media for writing. + */ +HRESULT Medium::i_prepareMergeTo(const ComObjPtr<Medium> &pTarget, + const Guid *aMachineId, + const Guid *aSnapshotId, + bool fLockMedia, + bool &fMergeForward, + ComObjPtr<Medium> &pParentForTarget, + MediumLockList * &aChildrenToReparent, + MediumLockList * &aMediumLockList) +{ + AssertReturn(pTarget != NULL, E_FAIL); + AssertReturn(pTarget != this, E_FAIL); + + HRESULT hrc = S_OK; + fMergeForward = false; + pParentForTarget.setNull(); + Assert(aChildrenToReparent == NULL); + aChildrenToReparent = NULL; + Assert(aMediumLockList == NULL); + aMediumLockList = NULL; + + try + { + // locking: we need the tree lock first because we access parent pointers + AutoWriteLock treeLock(m->pVirtualBox->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS); + + AutoCaller autoCaller(this); + AssertComRCThrowRC(autoCaller.hrc()); + + AutoCaller targetCaller(pTarget); + AssertComRCThrowRC(targetCaller.hrc()); + + /* more sanity checking and figuring out the merge direction */ + ComObjPtr<Medium> pMedium = i_getParent(); + while (!pMedium.isNull() && pMedium != pTarget) + pMedium = pMedium->i_getParent(); + if (pMedium == pTarget) + fMergeForward = false; + else + { + pMedium = pTarget->i_getParent(); + while (!pMedium.isNull() && pMedium != this) + pMedium = pMedium->i_getParent(); + if (pMedium == this) + fMergeForward = true; + else + { + Utf8Str tgtLoc; + { + AutoReadLock alock(pTarget COMMA_LOCKVAL_SRC_POS); + tgtLoc = pTarget->i_getLocationFull(); + } + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + throw setError(VBOX_E_INVALID_OBJECT_STATE, + tr("Media '%s' and '%s' are unrelated"), + m->strLocationFull.c_str(), tgtLoc.c_str()); + } + } + + /* Build the lock list. */ + aMediumLockList = new MediumLockList(); + targetCaller.release(); + autoCaller.release(); + treeLock.release(); + if (fMergeForward) + hrc = pTarget->i_createMediumLockList(true /* fFailIfInaccessible */, + pTarget /* pToLockWrite */, + false /* fMediumLockWriteAll */, + NULL, + *aMediumLockList); + else + hrc = i_createMediumLockList(true /* fFailIfInaccessible */, + pTarget /* pToLockWrite */, + false /* fMediumLockWriteAll */, + NULL, + *aMediumLockList); + treeLock.acquire(); + autoCaller.add(); + AssertComRCThrowRC(autoCaller.hrc()); + targetCaller.add(); + AssertComRCThrowRC(targetCaller.hrc()); + if (FAILED(hrc)) + throw hrc; + + /* Sanity checking, must be after lock list creation as it depends on + * valid medium states. The medium objects must be accessible. Only + * do this if immediate locking is requested, otherwise it fails when + * we construct a medium lock list for an already running VM. Snapshot + * deletion uses this to simplify its life. */ + if (fLockMedia) + { + { + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + if (m->state != MediumState_Created) + throw i_setStateError(); + } + { + AutoReadLock alock(pTarget COMMA_LOCKVAL_SRC_POS); + if (pTarget->m->state != MediumState_Created) + throw pTarget->i_setStateError(); + } + } + + /* check medium attachment and other sanity conditions */ + if (fMergeForward) + { + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + if (i_getChildren().size() > 1) + { + throw setError(VBOX_E_INVALID_OBJECT_STATE, + tr("Medium '%s' involved in the merge operation has more than one child medium (%d)"), + m->strLocationFull.c_str(), i_getChildren().size()); + } + /* One backreference is only allowed if the machine ID is not empty + * and it matches the machine the medium is attached to (including + * the snapshot ID if not empty). */ + if ( m->backRefs.size() != 0 + && ( !aMachineId + || m->backRefs.size() != 1 + || aMachineId->isZero() + || *i_getFirstMachineBackrefId() != *aMachineId + || ( (!aSnapshotId || !aSnapshotId->isZero()) + && *i_getFirstMachineBackrefSnapshotId() != *aSnapshotId))) + throw setError(VBOX_E_OBJECT_IN_USE, + tr("Medium '%s' is attached to %d virtual machines", "", m->backRefs.size()), + m->strLocationFull.c_str(), m->backRefs.size()); + if (m->type == MediumType_Immutable) + throw setError(VBOX_E_INVALID_OBJECT_STATE, + tr("Medium '%s' is immutable"), + m->strLocationFull.c_str()); + if (m->type == MediumType_MultiAttach) + throw setError(VBOX_E_INVALID_OBJECT_STATE, + tr("Medium '%s' is multi-attach"), + m->strLocationFull.c_str()); + } + else + { + AutoReadLock alock(pTarget COMMA_LOCKVAL_SRC_POS); + if (pTarget->i_getChildren().size() > 1) + { + throw setError(VBOX_E_OBJECT_IN_USE, + tr("Medium '%s' involved in the merge operation has more than one child medium (%d)"), + pTarget->m->strLocationFull.c_str(), + pTarget->i_getChildren().size()); + } + if (pTarget->m->type == MediumType_Immutable) + throw setError(VBOX_E_INVALID_OBJECT_STATE, + tr("Medium '%s' is immutable"), + pTarget->m->strLocationFull.c_str()); + if (pTarget->m->type == MediumType_MultiAttach) + throw setError(VBOX_E_INVALID_OBJECT_STATE, + tr("Medium '%s' is multi-attach"), + pTarget->m->strLocationFull.c_str()); + } + ComObjPtr<Medium> pLast(fMergeForward ? (Medium *)pTarget : this); + ComObjPtr<Medium> pLastIntermediate = pLast->i_getParent(); + for (pLast = pLastIntermediate; + !pLast.isNull() && pLast != pTarget && pLast != this; + pLast = pLast->i_getParent()) + { + AutoReadLock alock(pLast COMMA_LOCKVAL_SRC_POS); + if (pLast->i_getChildren().size() > 1) + { + throw setError(VBOX_E_OBJECT_IN_USE, + tr("Medium '%s' involved in the merge operation has more than one child medium (%d)"), + pLast->m->strLocationFull.c_str(), + pLast->i_getChildren().size()); + } + if (pLast->m->backRefs.size() != 0) + throw setError(VBOX_E_OBJECT_IN_USE, + tr("Medium '%s' is attached to %d virtual machines", "", pLast->m->backRefs.size()), + pLast->m->strLocationFull.c_str(), + pLast->m->backRefs.size()); + + } + + /* Update medium states appropriately */ + { + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + if (m->state == MediumState_Created) + { + hrc = i_markForDeletion(); + if (FAILED(hrc)) + throw hrc; + } + else + { + if (fLockMedia) + throw i_setStateError(); + else if ( m->state == MediumState_LockedWrite + || m->state == MediumState_LockedRead) + { + /* Either mark it for deletion in locked state or allow + * others to have done so. */ + if (m->preLockState == MediumState_Created) + i_markLockedForDeletion(); + else if (m->preLockState != MediumState_Deleting) + throw i_setStateError(); + } + else + throw i_setStateError(); + } + } + + if (fMergeForward) + { + /* we will need parent to reparent target */ + pParentForTarget = i_getParent(); + } + else + { + /* we will need to reparent children of the source */ + aChildrenToReparent = new MediumLockList(); + for (MediaList::const_iterator it = i_getChildren().begin(); + it != i_getChildren().end(); + ++it) + { + pMedium = *it; + aChildrenToReparent->Append(pMedium, true /* fLockWrite */); + } + if (fLockMedia && aChildrenToReparent) + { + targetCaller.release(); + autoCaller.release(); + treeLock.release(); + hrc = aChildrenToReparent->Lock(); + treeLock.acquire(); + autoCaller.add(); + AssertComRCThrowRC(autoCaller.hrc()); + targetCaller.add(); + AssertComRCThrowRC(targetCaller.hrc()); + if (FAILED(hrc)) + throw hrc; + } + } + for (pLast = pLastIntermediate; + !pLast.isNull() && pLast != pTarget && pLast != this; + pLast = pLast->i_getParent()) + { + AutoWriteLock alock(pLast COMMA_LOCKVAL_SRC_POS); + if (pLast->m->state == MediumState_Created) + { + hrc = pLast->i_markForDeletion(); + if (FAILED(hrc)) + throw hrc; + } + else + throw pLast->i_setStateError(); + } + + /* Tweak the lock list in the backward merge case, as the target + * isn't marked to be locked for writing yet. */ + if (!fMergeForward) + { + MediumLockList::Base::iterator lockListBegin = + aMediumLockList->GetBegin(); + MediumLockList::Base::iterator lockListEnd = + aMediumLockList->GetEnd(); + ++lockListEnd; + for (MediumLockList::Base::iterator it = lockListBegin; + it != lockListEnd; + ++it) + { + MediumLock &mediumLock = *it; + if (mediumLock.GetMedium() == pTarget) + { + HRESULT hrc2 = mediumLock.UpdateLock(true); + AssertComRC(hrc2); + break; + } + } + } + + if (fLockMedia) + { + targetCaller.release(); + autoCaller.release(); + treeLock.release(); + hrc = aMediumLockList->Lock(); + treeLock.acquire(); + autoCaller.add(); + AssertComRCThrowRC(autoCaller.hrc()); + targetCaller.add(); + AssertComRCThrowRC(targetCaller.hrc()); + if (FAILED(hrc)) + { + AutoReadLock alock(pTarget COMMA_LOCKVAL_SRC_POS); + throw setError(hrc, + tr("Failed to lock media when merging to '%s'"), + pTarget->i_getLocationFull().c_str()); + } + } + } + catch (HRESULT hrcXcpt) { hrc = hrcXcpt; } + + if (FAILED(hrc)) + { + if (aMediumLockList) + { + delete aMediumLockList; + aMediumLockList = NULL; + } + if (aChildrenToReparent) + { + delete aChildrenToReparent; + aChildrenToReparent = NULL; + } + } + + return hrc; +} + +/** + * Merges this medium to the specified medium which must be either its + * direct ancestor or descendant. + * + * Given this medium is SOURCE and the specified medium is TARGET, we will + * get two variants of the merge operation: + * + * forward merge + * -------------------------> + * [Extra] <- SOURCE <- Intermediate <- TARGET + * Any Del Del LockWr + * + * + * backward merge + * <------------------------- + * TARGET <- Intermediate <- SOURCE <- [Extra] + * LockWr Del Del LockWr + * + * Each diagram shows the involved media on the media chain where + * SOURCE and TARGET belong. Under each medium there is a state value which + * the medium must have at a time of the mergeTo() call. + * + * The media in the square braces may be absent (e.g. when the forward + * operation takes place and SOURCE is the base medium, or when the backward + * merge operation takes place and TARGET is the last child in the chain) but if + * they present they are involved too as shown. + * + * Neither the source medium nor intermediate media may be attached to + * any VM directly or in the snapshot, otherwise this method will assert. + * + * The #i_prepareMergeTo() method must be called prior to this method to place + * all involved to necessary states and perform other consistency checks. + * + * If @a aWait is @c true then this method will perform the operation on the + * calling thread and will not return to the caller until the operation is + * completed. When this method succeeds, all intermediate medium objects in + * the chain will be uninitialized, the state of the target medium (and all + * involved extra media) will be restored. @a aMediumLockList will not be + * deleted, whether the operation is successful or not. The caller has to do + * this if appropriate. Note that this (source) medium is not uninitialized + * because of possible AutoCaller instances held by the caller of this method + * on the current thread. It's therefore the responsibility of the caller to + * call Medium::uninit() after releasing all callers. + * + * If @a aWait is @c false then this method will create a thread to perform the + * operation asynchronously and will return immediately. If the operation + * succeeds, the thread will uninitialize the source medium object and all + * intermediate medium objects in the chain, reset the state of the target + * medium (and all involved extra media) and delete @a aMediumLockList. + * If the operation fails, the thread will only reset the states of all + * involved media and delete @a aMediumLockList. + * + * When this method fails (regardless of the @a aWait mode), it is a caller's + * responsibility to undo state changes and delete @a aMediumLockList using + * #i_cancelMergeTo(). + * + * If @a aProgress is not NULL but the object it points to is @c null then a new + * progress object will be created and assigned to @a *aProgress on success, + * otherwise the existing progress object is used. If Progress is NULL, then no + * progress object is created/used at all. Note that @a aProgress cannot be + * NULL when @a aWait is @c false (this method will assert in this case). + * + * @param pTarget Target medium. + * @param fMergeForward Merge direction. + * @param pParentForTarget New parent for target medium after merge. + * @param aChildrenToReparent List of children of the source which will have + * to be reparented to the target after merge. + * @param aMediumLockList Medium locking information. + * @param aProgress Where to find/store a Progress object to track operation + * completion. + * @param aWait @c true if this method should block instead of creating + * an asynchronous thread. + * @param aNotify Notify about media for which metadata is changed + * during execution of the function. + * + * @note Locks the tree lock for writing. Locks the media from the chain + * for writing. + */ +HRESULT Medium::i_mergeTo(const ComObjPtr<Medium> &pTarget, + bool fMergeForward, + const ComObjPtr<Medium> &pParentForTarget, + MediumLockList *aChildrenToReparent, + MediumLockList *aMediumLockList, + ComObjPtr<Progress> *aProgress, + bool aWait, bool aNotify) +{ + AssertReturn(pTarget != NULL, E_FAIL); + AssertReturn(pTarget != this, E_FAIL); + AssertReturn(aMediumLockList != NULL, E_FAIL); + AssertReturn(aProgress != NULL || aWait == true, E_FAIL); + + AutoCaller autoCaller(this); + AssertComRCReturnRC(autoCaller.hrc()); + + AutoCaller targetCaller(pTarget); + AssertComRCReturnRC(targetCaller.hrc()); + + HRESULT hrc = S_OK; + ComObjPtr<Progress> pProgress; + Medium::Task *pTask = NULL; + + try + { + if (aProgress != NULL) + { + /* use the existing progress object... */ + pProgress = *aProgress; + + /* ...but create a new one if it is null */ + if (pProgress.isNull()) + { + Utf8Str tgtName; + { + AutoReadLock alock(pTarget COMMA_LOCKVAL_SRC_POS); + tgtName = pTarget->i_getName(); + } + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + pProgress.createObject(); + hrc = pProgress->init(m->pVirtualBox, + static_cast<IMedium*>(this), + BstrFmt(tr("Merging medium '%s' to '%s'"), + i_getName().c_str(), + tgtName.c_str()).raw(), + TRUE, /* aCancelable */ + 2, /* Number of opearations */ + BstrFmt(tr("Resizing medium '%s' before merge"), + tgtName.c_str()).raw() + ); + if (FAILED(hrc)) + throw hrc; + } + } + + /* setup task object to carry out the operation sync/async */ + pTask = new Medium::MergeTask(this, pTarget, fMergeForward, + pParentForTarget, aChildrenToReparent, + pProgress, aMediumLockList, + aWait /* fKeepMediumLockList */, + aNotify); + hrc = pTask->hrc(); + AssertComRC(hrc); + if (FAILED(hrc)) + throw hrc; + } + catch (HRESULT hrcXcpt) { hrc = hrcXcpt; } + + if (SUCCEEDED(hrc)) + { + if (aWait) + { + hrc = pTask->runNow(); + delete pTask; + } + else + hrc = pTask->createThread(); + pTask = NULL; + if (SUCCEEDED(hrc) && aProgress != NULL) + *aProgress = pProgress; + } + else if (pTask != NULL) + delete pTask; + + return hrc; +} + +/** + * Undoes what #i_prepareMergeTo() did. Must be called if #mergeTo() is not + * called or fails. Frees memory occupied by @a aMediumLockList and unlocks + * the medium objects in @a aChildrenToReparent. + * + * @param aChildrenToReparent List of children of the source which will have + * to be reparented to the target after merge. + * @param aMediumLockList Medium locking information. + * + * @note Locks the tree lock for writing. Locks the media from the chain + * for writing. + */ +void Medium::i_cancelMergeTo(MediumLockList *aChildrenToReparent, + MediumLockList *aMediumLockList) +{ + AutoCaller autoCaller(this); + AssertComRCReturnVoid(autoCaller.hrc()); + + AssertReturnVoid(aMediumLockList != NULL); + + /* Revert media marked for deletion to previous state. */ + MediumLockList::Base::const_iterator mediumListBegin = + aMediumLockList->GetBegin(); + MediumLockList::Base::const_iterator mediumListEnd = + aMediumLockList->GetEnd(); + for (MediumLockList::Base::const_iterator it = mediumListBegin; + it != mediumListEnd; + ++it) + { + const MediumLock &mediumLock = *it; + const ComObjPtr<Medium> &pMedium = mediumLock.GetMedium(); + AutoWriteLock alock(pMedium COMMA_LOCKVAL_SRC_POS); + + if (pMedium->m->state == MediumState_Deleting) + { + HRESULT hrc = pMedium->i_unmarkForDeletion(); + AssertComRC(hrc); + } + else if ( ( pMedium->m->state == MediumState_LockedWrite + || pMedium->m->state == MediumState_LockedRead) + && pMedium->m->preLockState == MediumState_Deleting) + { + HRESULT hrc = pMedium->i_unmarkLockedForDeletion(); + AssertComRC(hrc); + } + } + + /* the destructor will do the work */ + delete aMediumLockList; + + /* unlock the children which had to be reparented, the destructor will do + * the work */ + if (aChildrenToReparent) + delete aChildrenToReparent; +} + +/** + * Resizes the media. + * + * If @a aWait is @c true then this method will perform the operation on the + * calling thread and will not return to the caller until the operation is + * completed. When this method succeeds, the state of the target medium (and all + * involved extra media) will be restored. @a aMediumLockList will not be + * deleted, whether the operation is successful or not. The caller has to do + * this if appropriate. + * + * If @a aWait is @c false then this method will create a thread to perform the + * operation asynchronously and will return immediately. The thread will reset + * the state of the target medium (and all involved extra media) and delete + * @a aMediumLockList. + * + * When this method fails (regardless of the @a aWait mode), it is a caller's + * responsibility to undo state changes and delete @a aMediumLockList. + * + * If @a aProgress is not NULL but the object it points to is @c null then a new + * progress object will be created and assigned to @a *aProgress on success, + * otherwise the existing progress object is used. If Progress is NULL, then no + * progress object is created/used at all. Note that @a aProgress cannot be + * NULL when @a aWait is @c false (this method will assert in this case). + * + * @param aLogicalSize New nominal capacity of the medium in bytes. + * @param aMediumLockList Medium locking information. + * @param aProgress Where to find/store a Progress object to track operation + * completion. + * @param aWait @c true if this method should block instead of creating + * an asynchronous thread. + * @param aNotify Notify about media for which metadata is changed + * during execution of the function. + * + * @note Locks the media from the chain for writing. + */ + +HRESULT Medium::i_resize(uint64_t aLogicalSize, + MediumLockList *aMediumLockList, + ComObjPtr<Progress> *aProgress, + bool aWait, + bool aNotify) +{ + AssertReturn(aMediumLockList != NULL, E_FAIL); + AssertReturn(aProgress != NULL || aWait == true, E_FAIL); + + AutoCaller autoCaller(this); + AssertComRCReturnRC(autoCaller.hrc()); + + HRESULT hrc = S_OK; + ComObjPtr<Progress> pProgress; + Medium::Task *pTask = NULL; + + try + { + if (aProgress != NULL) + { + /* use the existing progress object... */ + pProgress = *aProgress; + + /* ...but create a new one if it is null */ + if (pProgress.isNull()) + { + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + pProgress.createObject(); + hrc = pProgress->init(m->pVirtualBox, + static_cast <IMedium *>(this), + BstrFmt(tr("Resizing medium '%s'"), m->strLocationFull.c_str()).raw(), + TRUE /* aCancelable */); + if (FAILED(hrc)) + throw hrc; + } + } + + /* setup task object to carry out the operation asynchronously */ + pTask = new Medium::ResizeTask(this, + aLogicalSize, + pProgress, + aMediumLockList, + aWait /* fKeepMediumLockList */, + aNotify); + hrc = pTask->hrc(); + AssertComRC(hrc); + if (FAILED(hrc)) + throw hrc; + } + catch (HRESULT hrcXcpt) { hrc = hrcXcpt; } + + if (SUCCEEDED(hrc)) + { + if (aWait) + { + hrc = pTask->runNow(); + delete pTask; + } + else + hrc = pTask->createThread(); + pTask = NULL; + if (SUCCEEDED(hrc) && aProgress != NULL) + *aProgress = pProgress; + } + else if (pTask != NULL) + delete pTask; + + return hrc; +} + +/** + * Fix the parent UUID of all children to point to this medium as their + * parent. + */ +HRESULT Medium::i_fixParentUuidOfChildren(MediumLockList *pChildrenToReparent) +{ + /** @todo r=klaus The code below needs to be double checked with regard + * to lock order violations, it probably causes lock order issues related + * to the AutoCaller usage. Likewise the code using this method seems + * problematic. */ + Assert(!isWriteLockOnCurrentThread()); + Assert(!m->pVirtualBox->i_getMediaTreeLockHandle().isWriteLockOnCurrentThread()); + MediumLockList mediumLockList; + HRESULT hrc = i_createMediumLockList(true /* fFailIfInaccessible */, + NULL /* pToLockWrite */, + false /* fMediumLockWriteAll */, + this, + mediumLockList); + AssertComRCReturnRC(hrc); + + try + { + PVDISK hdd; + int vrc = VDCreate(m->vdDiskIfaces, i_convertDeviceType(), &hdd); + ComAssertRCThrow(vrc, E_FAIL); + + try + { + MediumLockList::Base::iterator lockListBegin = + mediumLockList.GetBegin(); + MediumLockList::Base::iterator lockListEnd = + mediumLockList.GetEnd(); + for (MediumLockList::Base::iterator it = lockListBegin; + it != lockListEnd; + ++it) + { + MediumLock &mediumLock = *it; + const ComObjPtr<Medium> &pMedium = mediumLock.GetMedium(); + AutoReadLock alock(pMedium COMMA_LOCKVAL_SRC_POS); + + // open the medium + vrc = VDOpen(hdd, + pMedium->m->strFormat.c_str(), + pMedium->m->strLocationFull.c_str(), + VD_OPEN_FLAGS_READONLY | m->uOpenFlagsDef, + pMedium->m->vdImageIfaces); + if (RT_FAILURE(vrc)) + throw vrc; + } + + MediumLockList::Base::iterator childrenBegin = pChildrenToReparent->GetBegin(); + MediumLockList::Base::iterator childrenEnd = pChildrenToReparent->GetEnd(); + for (MediumLockList::Base::iterator it = childrenBegin; + it != childrenEnd; + ++it) + { + Medium *pMedium = it->GetMedium(); + /* VD_OPEN_FLAGS_INFO since UUID is wrong yet */ + vrc = VDOpen(hdd, + pMedium->m->strFormat.c_str(), + pMedium->m->strLocationFull.c_str(), + VD_OPEN_FLAGS_INFO | m->uOpenFlagsDef, + pMedium->m->vdImageIfaces); + if (RT_FAILURE(vrc)) + throw vrc; + + vrc = VDSetParentUuid(hdd, VD_LAST_IMAGE, m->id.raw()); + if (RT_FAILURE(vrc)) + throw vrc; + + vrc = VDClose(hdd, false /* fDelete */); + if (RT_FAILURE(vrc)) + throw vrc; + } + } + catch (HRESULT hrcXcpt) { hrc = hrcXcpt; } + catch (int vrcXcpt) + { + hrc = setErrorBoth(E_FAIL, vrcXcpt, + tr("Could not update medium UUID references to parent '%s' (%s)"), + m->strLocationFull.c_str(), + i_vdError(vrcXcpt).c_str()); + } + + VDDestroy(hdd); + } + catch (HRESULT hrcXcpt) { hrc = hrcXcpt; } + + return hrc; +} + +/** + * + * @note Similar code exists in i_taskExportHandler. + */ +HRESULT Medium::i_addRawToFss(const char *aFilename, SecretKeyStore *pKeyStore, RTVFSFSSTREAM hVfsFssDst, + const ComObjPtr<Progress> &aProgress, bool fSparse) +{ + AutoCaller autoCaller(this); + HRESULT hrc = autoCaller.hrc(); + if (SUCCEEDED(hrc)) + { + /* + * Get a readonly hdd for this medium. + */ + MediumCryptoFilterSettings CryptoSettingsRead; + MediumLockList SourceMediumLockList; + PVDISK pHdd; + hrc = i_openForIO(false /*fWritable*/, pKeyStore, &pHdd, &SourceMediumLockList, &CryptoSettingsRead); + if (SUCCEEDED(hrc)) + { + /* + * Create a VFS file interface to the HDD and attach a progress wrapper + * that monitors the progress reading of the raw image. The image will + * be read twice if hVfsFssDst does sparse processing. + */ + RTVFSFILE hVfsFileDisk = NIL_RTVFSFILE; + int vrc = VDCreateVfsFileFromDisk(pHdd, 0 /*fFlags*/, &hVfsFileDisk); + if (RT_SUCCESS(vrc)) + { + RTVFSFILE hVfsFileProgress = NIL_RTVFSFILE; + vrc = RTVfsCreateProgressForFile(hVfsFileDisk, aProgress->i_iprtProgressCallback, &*aProgress, + RTVFSPROGRESS_F_CANCELABLE | RTVFSPROGRESS_F_FORWARD_SEEK_AS_READ, + VDGetSize(pHdd, VD_LAST_IMAGE) * (fSparse ? 2 : 1) /*cbExpectedRead*/, + 0 /*cbExpectedWritten*/, &hVfsFileProgress); + RTVfsFileRelease(hVfsFileDisk); + if (RT_SUCCESS(vrc)) + { + RTVFSOBJ hVfsObj = RTVfsObjFromFile(hVfsFileProgress); + RTVfsFileRelease(hVfsFileProgress); + + vrc = RTVfsFsStrmAdd(hVfsFssDst, aFilename, hVfsObj, 0 /*fFlags*/); + RTVfsObjRelease(hVfsObj); + if (RT_FAILURE(vrc)) + hrc = setErrorBoth(VBOX_E_FILE_ERROR, vrc, tr("Failed to add '%s' to output (%Rrc)"), aFilename, vrc); + } + else + hrc = setErrorBoth(VBOX_E_FILE_ERROR, vrc, + tr("RTVfsCreateProgressForFile failed when processing '%s' (%Rrc)"), aFilename, vrc); + } + else + hrc = setErrorBoth(VBOX_E_FILE_ERROR, vrc, tr("VDCreateVfsFileFromDisk failed for '%s' (%Rrc)"), aFilename, vrc); + VDDestroy(pHdd); + } + } + return hrc; +} + +/** + * Used by IAppliance to export disk images. + * + * @param aFilename Filename to create (UTF8). + * @param aFormat Medium format for creating @a aFilename. + * @param aVariant Which exact image format variant to use for the + * destination image. + * @param pKeyStore The optional key store for decrypting the data for + * encrypted media during the export. + * @param hVfsIosDst The destination I/O stream object. + * @param aProgress Progress object to use. + * @return + * + * @note The source format is defined by the Medium instance. + */ +HRESULT Medium::i_exportFile(const char *aFilename, + const ComObjPtr<MediumFormat> &aFormat, + MediumVariant_T aVariant, + SecretKeyStore *pKeyStore, + RTVFSIOSTREAM hVfsIosDst, + const ComObjPtr<Progress> &aProgress) +{ + AssertPtrReturn(aFilename, E_INVALIDARG); + AssertReturn(aFormat.isNotNull(), E_INVALIDARG); + AssertReturn(aProgress.isNotNull(), E_INVALIDARG); + + AutoCaller autoCaller(this); + HRESULT hrc = autoCaller.hrc(); + if (SUCCEEDED(hrc)) + { + /* + * Setup VD interfaces. + */ + PVDINTERFACE pVDImageIfaces = m->vdImageIfaces; + PVDINTERFACEIO pVfsIoIf; + int vrc = VDIfCreateFromVfsStream(hVfsIosDst, RTFILE_O_WRITE, &pVfsIoIf); + if (RT_SUCCESS(vrc)) + { + vrc = VDInterfaceAdd(&pVfsIoIf->Core, "Medium::ExportTaskVfsIos", VDINTERFACETYPE_IO, + pVfsIoIf, sizeof(VDINTERFACEIO), &pVDImageIfaces); + if (RT_SUCCESS(vrc)) + { + /* + * Get a readonly hdd for this medium (source). + */ + MediumCryptoFilterSettings CryptoSettingsRead; + MediumLockList SourceMediumLockList; + PVDISK pSrcHdd; + hrc = i_openForIO(false /*fWritable*/, pKeyStore, &pSrcHdd, &SourceMediumLockList, &CryptoSettingsRead); + if (SUCCEEDED(hrc)) + { + /* + * Create the target medium. + */ + Utf8Str strDstFormat(aFormat->i_getId()); + + /* ensure the target directory exists */ + uint64_t fDstCapabilities = aFormat->i_getCapabilities(); + if (fDstCapabilities & MediumFormatCapabilities_File) + { + Utf8Str strDstLocation(aFilename); + hrc = VirtualBox::i_ensureFilePathExists(strDstLocation.c_str(), + !(aVariant & MediumVariant_NoCreateDir) /* fCreate */); + } + if (SUCCEEDED(hrc)) + { + PVDISK pDstHdd; + vrc = VDCreate(m->vdDiskIfaces, i_convertDeviceType(), &pDstHdd); + if (RT_SUCCESS(vrc)) + { + /* + * Create an interface for getting progress callbacks. + */ + VDINTERFACEPROGRESS ProgressIf = VDINTERFACEPROGRESS_INITALIZER(aProgress->i_vdProgressCallback); + PVDINTERFACE pProgress = NULL; + vrc = VDInterfaceAdd(&ProgressIf.Core, "export-progress", VDINTERFACETYPE_PROGRESS, + &*aProgress, sizeof(ProgressIf), &pProgress); + AssertRC(vrc); + + /* + * Do the exporting. + */ + vrc = VDCopy(pSrcHdd, + VD_LAST_IMAGE, + pDstHdd, + strDstFormat.c_str(), + aFilename, + false /* fMoveByRename */, + 0 /* cbSize */, + aVariant & ~(MediumVariant_NoCreateDir | MediumVariant_Formatted | MediumVariant_VmdkESX | MediumVariant_VmdkRawDisk), + NULL /* pDstUuid */, + VD_OPEN_FLAGS_NORMAL | VD_OPEN_FLAGS_SEQUENTIAL, + pProgress, + pVDImageIfaces, + NULL); + if (RT_SUCCESS(vrc)) + hrc = S_OK; + else + hrc = setErrorBoth(VBOX_E_FILE_ERROR, vrc, tr("Could not create the exported medium '%s'%s"), + aFilename, i_vdError(vrc).c_str()); + VDDestroy(pDstHdd); + } + else + hrc = setErrorVrc(vrc); + } + } + VDDestroy(pSrcHdd); + } + else + hrc = setErrorVrc(vrc, "VDInterfaceAdd -> %Rrc", vrc); + VDIfDestroyFromVfsStream(pVfsIoIf); + } + else + hrc = setErrorVrc(vrc, "VDIfCreateFromVfsStream -> %Rrc", vrc); + } + return hrc; +} + +/** + * Used by IAppliance to import disk images. + * + * @param aFilename Filename to read (UTF8). + * @param aFormat Medium format for reading @a aFilename. + * @param aVariant Which exact image format variant to use + * for the destination image. + * @param aVfsIosSrc Handle to the source I/O stream. + * @param aParent Parent medium. May be NULL. + * @param aProgress Progress object to use. + * @param aNotify Notify about media for which metadata is changed + * during execution of the function. + * @return + * @note The destination format is defined by the Medium instance. + * + * @todo The only consumer of this method (Appliance::i_importOneDiskImage) is + * already on a worker thread, so perhaps consider bypassing the thread + * here and run in the task synchronously? VBoxSVC has enough threads as + * it is... + */ +HRESULT Medium::i_importFile(const char *aFilename, + const ComObjPtr<MediumFormat> &aFormat, + MediumVariant_T aVariant, + RTVFSIOSTREAM aVfsIosSrc, + const ComObjPtr<Medium> &aParent, + const ComObjPtr<Progress> &aProgress, + bool aNotify) +{ + /** @todo r=klaus The code below needs to be double checked with regard + * to lock order violations, it probably causes lock order issues related + * to the AutoCaller usage. */ + AssertPtrReturn(aFilename, E_INVALIDARG); + AssertReturn(!aFormat.isNull(), E_INVALIDARG); + AssertReturn(!aProgress.isNull(), E_INVALIDARG); + + AutoCaller autoCaller(this); + if (FAILED(autoCaller.hrc())) return autoCaller.hrc(); + + HRESULT hrc = S_OK; + Medium::Task *pTask = NULL; + + try + { + // locking: we need the tree lock first because we access parent pointers + // and we need to write-lock the media involved + uint32_t cHandles = 2; + LockHandle* pHandles[3] = { &m->pVirtualBox->i_getMediaTreeLockHandle(), + this->lockHandle() }; + /* Only add parent to the lock if it is not null */ + if (!aParent.isNull()) + pHandles[cHandles++] = aParent->lockHandle(); + AutoWriteLock alock(cHandles, + pHandles + COMMA_LOCKVAL_SRC_POS); + + if ( m->state != MediumState_NotCreated + && m->state != MediumState_Created) + throw i_setStateError(); + + /* Build the target lock list. */ + MediumLockList *pTargetMediumLockList(new MediumLockList()); + alock.release(); + hrc = i_createMediumLockList(true /* fFailIfInaccessible */, + this /* pToLockWrite */, + false /* fMediumLockWriteAll */, + aParent, + *pTargetMediumLockList); + alock.acquire(); + if (FAILED(hrc)) + { + delete pTargetMediumLockList; + throw hrc; + } + + alock.release(); + hrc = pTargetMediumLockList->Lock(); + alock.acquire(); + if (FAILED(hrc)) + { + delete pTargetMediumLockList; + throw setError(hrc, + tr("Failed to lock target media '%s'"), + i_getLocationFull().c_str()); + } + + /* setup task object to carry out the operation asynchronously */ + pTask = new Medium::ImportTask(this, aProgress, aFilename, aFormat, aVariant, + aVfsIosSrc, aParent, pTargetMediumLockList, false, aNotify); + hrc = pTask->hrc(); + AssertComRC(hrc); + if (FAILED(hrc)) + throw hrc; + + if (m->state == MediumState_NotCreated) + m->state = MediumState_Creating; + } + catch (HRESULT hrcXcpt) { hrc = hrcXcpt; } + + if (SUCCEEDED(hrc)) + { + hrc = pTask->createThread(); + pTask = NULL; + } + else if (pTask != NULL) + delete pTask; + + return hrc; +} + +/** + * Internal version of the public CloneTo API which allows to enable certain + * optimizations to improve speed during VM cloning. + * + * @param aTarget Target medium + * @param aVariant Which exact image format variant to use + * for the destination image. + * @param aParent Parent medium. May be NULL. + * @param aProgress Progress object to use. + * @param idxSrcImageSame The last image in the source chain which has the + * same content as the given image in the destination + * chain. Use UINT32_MAX to disable this optimization. + * @param idxDstImageSame The last image in the destination chain which has the + * same content as the given image in the source chain. + * Use UINT32_MAX to disable this optimization. + * @param aNotify Notify about media for which metadata is changed + * during execution of the function. + * @return + */ +HRESULT Medium::i_cloneToEx(const ComObjPtr<Medium> &aTarget, MediumVariant_T aVariant, + const ComObjPtr<Medium> &aParent, IProgress **aProgress, + uint32_t idxSrcImageSame, uint32_t idxDstImageSame, bool aNotify) +{ + /** @todo r=klaus The code below needs to be double checked with regard + * to lock order violations, it probably causes lock order issues related + * to the AutoCaller usage. */ + CheckComArgNotNull(aTarget); + CheckComArgOutPointerValid(aProgress); + ComAssertRet(aTarget != this, E_INVALIDARG); + + AutoCaller autoCaller(this); + if (FAILED(autoCaller.hrc())) return autoCaller.hrc(); + + HRESULT hrc = S_OK; + ComObjPtr<Progress> pProgress; + Medium::Task *pTask = NULL; + + try + { + // locking: we need the tree lock first because we access parent pointers + // and we need to write-lock the media involved + uint32_t cHandles = 3; + LockHandle* pHandles[4] = { &m->pVirtualBox->i_getMediaTreeLockHandle(), + this->lockHandle(), + aTarget->lockHandle() }; + /* Only add parent to the lock if it is not null */ + if (!aParent.isNull()) + pHandles[cHandles++] = aParent->lockHandle(); + AutoWriteLock alock(cHandles, + pHandles + COMMA_LOCKVAL_SRC_POS); + + if ( aTarget->m->state != MediumState_NotCreated + && aTarget->m->state != MediumState_Created) + throw aTarget->i_setStateError(); + + /* Build the source lock list. */ + MediumLockList *pSourceMediumLockList(new MediumLockList()); + alock.release(); + hrc = i_createMediumLockList(true /* fFailIfInaccessible */, + NULL /* pToLockWrite */, + false /* fMediumLockWriteAll */, + NULL, + *pSourceMediumLockList); + alock.acquire(); + if (FAILED(hrc)) + { + delete pSourceMediumLockList; + throw hrc; + } + + /* Build the target lock list (including the to-be parent chain). */ + MediumLockList *pTargetMediumLockList(new MediumLockList()); + alock.release(); + hrc = aTarget->i_createMediumLockList(true /* fFailIfInaccessible */, + aTarget /* pToLockWrite */, + false /* fMediumLockWriteAll */, + aParent, + *pTargetMediumLockList); + alock.acquire(); + if (FAILED(hrc)) + { + delete pSourceMediumLockList; + delete pTargetMediumLockList; + throw hrc; + } + + alock.release(); + hrc = pSourceMediumLockList->Lock(); + alock.acquire(); + if (FAILED(hrc)) + { + delete pSourceMediumLockList; + delete pTargetMediumLockList; + throw setError(hrc, + tr("Failed to lock source media '%s'"), + i_getLocationFull().c_str()); + } + alock.release(); + hrc = pTargetMediumLockList->Lock(); + alock.acquire(); + if (FAILED(hrc)) + { + delete pSourceMediumLockList; + delete pTargetMediumLockList; + throw setError(hrc, + tr("Failed to lock target media '%s'"), + aTarget->i_getLocationFull().c_str()); + } + + pProgress.createObject(); + hrc = pProgress->init(m->pVirtualBox, + static_cast <IMedium *>(this), + BstrFmt(tr("Creating clone medium '%s'"), aTarget->m->strLocationFull.c_str()).raw(), + TRUE /* aCancelable */); + if (FAILED(hrc)) + { + delete pSourceMediumLockList; + delete pTargetMediumLockList; + throw hrc; + } + + /* setup task object to carry out the operation asynchronously */ + pTask = new Medium::CloneTask(this, pProgress, aTarget, aVariant, + aParent, idxSrcImageSame, + idxDstImageSame, pSourceMediumLockList, + pTargetMediumLockList, false, false, aNotify); + hrc = pTask->hrc(); + AssertComRC(hrc); + if (FAILED(hrc)) + throw hrc; + + if (aTarget->m->state == MediumState_NotCreated) + aTarget->m->state = MediumState_Creating; + } + catch (HRESULT hrcXcpt) { hrc = hrcXcpt; } + + if (SUCCEEDED(hrc)) + { + hrc = pTask->createThread(); + pTask = NULL; + if (SUCCEEDED(hrc)) + pProgress.queryInterfaceTo(aProgress); + } + else if (pTask != NULL) + delete pTask; + + return hrc; +} + +/** + * Returns the key identifier for this medium if encryption is configured. + * + * @returns Key identifier or empty string if no encryption is configured. + */ +const Utf8Str& Medium::i_getKeyId() +{ + ComObjPtr<Medium> pBase = i_getBase(); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + settings::StringsMap::const_iterator it = pBase->m->mapProperties.find("CRYPT/KeyId"); + if (it == pBase->m->mapProperties.end()) + return Utf8Str::Empty; + + return it->second; +} + + +/** + * Returns all filter related properties. + * + * @returns COM status code. + * @param aReturnNames Where to store the properties names on success. + * @param aReturnValues Where to store the properties values on success. + */ +HRESULT Medium::i_getFilterProperties(std::vector<com::Utf8Str> &aReturnNames, + std::vector<com::Utf8Str> &aReturnValues) +{ + std::vector<com::Utf8Str> aPropNames; + std::vector<com::Utf8Str> aPropValues; + HRESULT hrc = getProperties(Utf8Str(""), aPropNames, aPropValues); + + if (SUCCEEDED(hrc)) + { + unsigned cReturnSize = 0; + aReturnNames.resize(0); + aReturnValues.resize(0); + for (unsigned idx = 0; idx < aPropNames.size(); idx++) + { + if (i_isPropertyForFilter(aPropNames[idx])) + { + aReturnNames.resize(cReturnSize + 1); + aReturnValues.resize(cReturnSize + 1); + aReturnNames[cReturnSize] = aPropNames[idx]; + aReturnValues[cReturnSize] = aPropValues[idx]; + cReturnSize++; + } + } + } + + return hrc; +} + +/** + * Preparation to move this medium to a new location + * + * @param aLocation Location of the storage unit. If the location is a FS-path, + * then it can be relative to the VirtualBox home directory. + * + * @note Must be called from under this object's write lock. + */ +HRESULT Medium::i_preparationForMoving(const Utf8Str &aLocation) +{ + HRESULT hrc = E_FAIL; + + if (i_getLocationFull() != aLocation) + { + m->strNewLocationFull = aLocation; + m->fMoveThisMedium = true; + hrc = S_OK; + } + + return hrc; +} + +/** + * Checking whether current operation "moving" or not + */ +bool Medium::i_isMoveOperation(const ComObjPtr<Medium> &aTarget) const +{ + RT_NOREF(aTarget); + return m->fMoveThisMedium; +} + +bool Medium::i_resetMoveOperationData() +{ + m->strNewLocationFull.setNull(); + m->fMoveThisMedium = false; + return true; +} + +Utf8Str Medium::i_getNewLocationForMoving() const +{ + if (m->fMoveThisMedium == true) + return m->strNewLocationFull; + else + return Utf8Str(); +} +//////////////////////////////////////////////////////////////////////////////// +// +// Private methods +// +//////////////////////////////////////////////////////////////////////////////// + +/** + * Queries information from the medium. + * + * As a result of this call, the accessibility state and data members such as + * size and description will be updated with the current information. + * + * @note This method may block during a system I/O call that checks storage + * accessibility. + * + * @note Caller MUST NOT hold the media tree or medium lock. + * + * @note Locks m->pParent for reading. Locks this object for writing. + * + * @param fSetImageId Whether to reset the UUID contained in the image file + * to the UUID in the medium instance data (see SetIDs()) + * @param fSetParentId Whether to reset the parent UUID contained in the image + * file to the parent UUID in the medium instance data (see + * SetIDs()) + * @param autoCaller + * @return + */ +HRESULT Medium::i_queryInfo(bool fSetImageId, bool fSetParentId, AutoCaller &autoCaller) +{ + Assert(!isWriteLockOnCurrentThread()); + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + if ( ( m->state != MediumState_Created + && m->state != MediumState_Inaccessible + && m->state != MediumState_LockedRead) + || m->fClosing) + return E_FAIL; + + HRESULT hrc = S_OK; + + int vrc = VINF_SUCCESS; + + /* check if a blocking i_queryInfo() call is in progress on some other thread, + * and wait for it to finish if so instead of querying data ourselves */ + if (m->queryInfoRunning) + { + Assert( m->state == MediumState_LockedRead + || m->state == MediumState_LockedWrite); + + while (m->queryInfoRunning) + { + alock.release(); + /* must not hold the object lock now */ + Assert(!isWriteLockOnCurrentThread()); + { + AutoReadLock qlock(m->queryInfoSem COMMA_LOCKVAL_SRC_POS); + } + alock.acquire(); + } + + return S_OK; + } + + bool success = false; + Utf8Str lastAccessError; + + /* are we dealing with a new medium constructed using the existing + * location? */ + bool isImport = m->id.isZero(); + unsigned uOpenFlags = VD_OPEN_FLAGS_INFO; + + /* Note that we don't use VD_OPEN_FLAGS_READONLY when opening new + * media because that would prevent necessary modifications + * when opening media of some third-party formats for the first + * time in VirtualBox (such as VMDK for which VDOpen() needs to + * generate an UUID if it is missing) */ + if ( m->hddOpenMode == OpenReadOnly + || m->type == MediumType_Readonly + || (!isImport && !fSetImageId && !fSetParentId) + ) + uOpenFlags |= VD_OPEN_FLAGS_READONLY; + + /* Open shareable medium with the appropriate flags */ + if (m->type == MediumType_Shareable) + uOpenFlags |= VD_OPEN_FLAGS_SHAREABLE; + + /* Lock the medium, which makes the behavior much more consistent, must be + * done before dropping the object lock and setting queryInfoRunning. */ + ComPtr<IToken> pToken; + if (uOpenFlags & (VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_SHAREABLE)) + hrc = LockRead(pToken.asOutParam()); + else + hrc = LockWrite(pToken.asOutParam()); + if (FAILED(hrc)) return hrc; + + /* Copies of the input state fields which are not read-only, + * as we're dropping the lock. CAUTION: be extremely careful what + * you do with the contents of this medium object, as you will + * create races if there are concurrent changes. */ + Utf8Str format(m->strFormat); + Utf8Str location(m->strLocationFull); + ComObjPtr<MediumFormat> formatObj = m->formatObj; + + /* "Output" values which can't be set because the lock isn't held + * at the time the values are determined. */ + Guid mediumId = m->id; + uint64_t mediumSize = 0; + uint64_t mediumLogicalSize = 0; + + /* Flag whether a base image has a non-zero parent UUID and thus + * need repairing after it was closed again. */ + bool fRepairImageZeroParentUuid = false; + + ComObjPtr<VirtualBox> pVirtualBox = m->pVirtualBox; + + /* must be set before leaving the object lock the first time */ + m->queryInfoRunning = true; + + /* must leave object lock now, because a lock from a higher lock class + * is needed and also a lengthy operation is coming */ + alock.release(); + autoCaller.release(); + + /* Note that taking the queryInfoSem after leaving the object lock above + * can lead to short spinning of the loops waiting for i_queryInfo() to + * complete. This is unavoidable since the other order causes a lock order + * violation: here it would be requesting the object lock (at the beginning + * of the method), then queryInfoSem, and below the other way round. */ + AutoWriteLock qlock(m->queryInfoSem COMMA_LOCKVAL_SRC_POS); + + /* take the opportunity to have a media tree lock, released initially */ + Assert(!isWriteLockOnCurrentThread()); + Assert(!pVirtualBox->i_getMediaTreeLockHandle().isWriteLockOnCurrentThread()); + AutoWriteLock treeLock(pVirtualBox->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS); + treeLock.release(); + + /* re-take the caller, but not the object lock, to keep uninit away */ + autoCaller.add(); + if (FAILED(autoCaller.hrc())) + { + m->queryInfoRunning = false; + return autoCaller.hrc(); + } + + try + { + /* skip accessibility checks for host drives */ + if (m->hostDrive) + { + success = true; + throw S_OK; + } + + PVDISK hdd; + vrc = VDCreate(m->vdDiskIfaces, i_convertDeviceType(), &hdd); + ComAssertRCThrow(vrc, E_FAIL); + + try + { + /** @todo This kind of opening of media is assuming that diff + * media can be opened as base media. Should be documented that + * it must work for all medium format backends. */ + vrc = VDOpen(hdd, + format.c_str(), + location.c_str(), + uOpenFlags | m->uOpenFlagsDef, + m->vdImageIfaces); + if (RT_FAILURE(vrc)) + { + lastAccessError = Utf8StrFmt(tr("Could not open the medium '%s'%s"), + location.c_str(), i_vdError(vrc).c_str()); + throw S_OK; + } + + if (formatObj->i_getCapabilities() & MediumFormatCapabilities_Uuid) + { + /* Modify the UUIDs if necessary. The associated fields are + * not modified by other code, so no need to copy. */ + if (fSetImageId) + { + alock.acquire(); + vrc = VDSetUuid(hdd, 0, m->uuidImage.raw()); + alock.release(); + if (RT_FAILURE(vrc)) + { + lastAccessError = Utf8StrFmt(tr("Could not update the UUID of medium '%s'%s"), + location.c_str(), i_vdError(vrc).c_str()); + throw S_OK; + } + mediumId = m->uuidImage; + } + if (fSetParentId) + { + alock.acquire(); + vrc = VDSetParentUuid(hdd, 0, m->uuidParentImage.raw()); + alock.release(); + if (RT_FAILURE(vrc)) + { + lastAccessError = Utf8StrFmt(tr("Could not update the parent UUID of medium '%s'%s"), + location.c_str(), i_vdError(vrc).c_str()); + throw S_OK; + } + } + /* zap the information, these are no long-term members */ + alock.acquire(); + unconst(m->uuidImage).clear(); + unconst(m->uuidParentImage).clear(); + alock.release(); + + /* check the UUID */ + RTUUID uuid; + vrc = VDGetUuid(hdd, 0, &uuid); + ComAssertRCThrow(vrc, E_FAIL); + + if (isImport) + { + mediumId = uuid; + + if (mediumId.isZero() && (m->hddOpenMode == OpenReadOnly)) + // only when importing a VDMK that has no UUID, create one in memory + mediumId.create(); + } + else + { + Assert(!mediumId.isZero()); + + if (mediumId != uuid) + { + /** @todo r=klaus this always refers to VirtualBox.xml as the medium registry, even for new VMs */ + lastAccessError = Utf8StrFmt( + tr("UUID {%RTuuid} of the medium '%s' does not match the value {%RTuuid} stored in the media registry ('%s')"), + &uuid, + location.c_str(), + mediumId.raw(), + pVirtualBox->i_settingsFilePath().c_str()); + throw S_OK; + } + } + } + else + { + /* the backend does not support storing UUIDs within the + * underlying storage so use what we store in XML */ + + if (fSetImageId) + { + /* set the UUID if an API client wants to change it */ + alock.acquire(); + mediumId = m->uuidImage; + alock.release(); + } + else if (isImport) + { + /* generate an UUID for an imported UUID-less medium */ + mediumId.create(); + } + } + + /* set the image uuid before the below parent uuid handling code + * might place it somewhere in the media tree, so that the medium + * UUID is valid at this point */ + alock.acquire(); + if (isImport || fSetImageId) + unconst(m->id) = mediumId; + alock.release(); + + /* get the medium variant */ + unsigned uImageFlags; + vrc = VDGetImageFlags(hdd, 0, &uImageFlags); + ComAssertRCThrow(vrc, E_FAIL); + alock.acquire(); + m->variant = (MediumVariant_T)uImageFlags; + alock.release(); + + /* check/get the parent uuid and update corresponding state */ + if (uImageFlags & VD_IMAGE_FLAGS_DIFF) + { + RTUUID parentId; + vrc = VDGetParentUuid(hdd, 0, &parentId); + ComAssertRCThrow(vrc, E_FAIL); + + /* streamOptimized VMDK images are only accepted as base + * images, as this allows automatic repair of OVF appliances. + * Since such images don't support random writes they will not + * be created for diff images. Only an overly smart user might + * manually create this case. Too bad for him. */ + if ( (isImport || fSetParentId) + && !(uImageFlags & VD_VMDK_IMAGE_FLAGS_STREAM_OPTIMIZED)) + { + /* the parent must be known to us. Note that we freely + * call locking methods of mVirtualBox and parent, as all + * relevant locks must be already held. There may be no + * concurrent access to the just opened medium on other + * threads yet (and init() will fail if this method reports + * MediumState_Inaccessible) */ + + ComObjPtr<Medium> pParent; + if (RTUuidIsNull(&parentId)) + hrc = VBOX_E_OBJECT_NOT_FOUND; + else + hrc = pVirtualBox->i_findHardDiskById(Guid(parentId), false /* aSetError */, &pParent); + if (FAILED(hrc)) + { + if (fSetImageId && !fSetParentId) + { + /* If the image UUID gets changed for an existing + * image then the parent UUID can be stale. In such + * cases clear the parent information. The parent + * information may/will be re-set later if the + * API client wants to adjust a complete medium + * hierarchy one by one. */ + hrc = S_OK; + alock.acquire(); + RTUuidClear(&parentId); + vrc = VDSetParentUuid(hdd, 0, &parentId); + alock.release(); + ComAssertRCThrow(vrc, E_FAIL); + } + else + { + lastAccessError = Utf8StrFmt(tr("Parent medium with UUID {%RTuuid} of the medium '%s' is not found in the media registry ('%s')"), + &parentId, location.c_str(), + pVirtualBox->i_settingsFilePath().c_str()); + throw S_OK; + } + } + + /* must drop the caller before taking the tree lock */ + autoCaller.release(); + /* we set m->pParent & children() */ + treeLock.acquire(); + autoCaller.add(); + if (FAILED(autoCaller.hrc())) + throw autoCaller.hrc(); + + if (m->pParent) + i_deparent(); + + if (!pParent.isNull()) + if (pParent->i_getDepth() >= SETTINGS_MEDIUM_DEPTH_MAX) + { + AutoReadLock plock(pParent COMMA_LOCKVAL_SRC_POS); + throw setError(VBOX_E_INVALID_OBJECT_STATE, + tr("Cannot open differencing image for medium '%s', because it exceeds the medium tree depth limit. Please merge some images which you no longer need"), + pParent->m->strLocationFull.c_str()); + } + i_setParent(pParent); + + treeLock.release(); + } + else + { + /* must drop the caller before taking the tree lock */ + autoCaller.release(); + /* we access m->pParent */ + treeLock.acquire(); + autoCaller.add(); + if (FAILED(autoCaller.hrc())) + throw autoCaller.hrc(); + + /* check that parent UUIDs match. Note that there's no need + * for the parent's AutoCaller (our lifetime is bound to + * it) */ + + if (m->pParent.isNull()) + { + /* Due to a bug in VDCopy() in VirtualBox 3.0.0-3.0.14 + * and 3.1.0-3.1.8 there are base images out there + * which have a non-zero parent UUID. No point in + * complaining about them, instead automatically + * repair the problem. Later we can bring back the + * error message, but we should wait until really + * most users have repaired their images, either with + * VBoxFixHdd or this way. */ +#if 1 + fRepairImageZeroParentUuid = true; +#else /* 0 */ + lastAccessError = Utf8StrFmt( + tr("Medium type of '%s' is differencing but it is not associated with any parent medium in the media registry ('%s')"), + location.c_str(), + pVirtualBox->settingsFilePath().c_str()); + treeLock.release(); + throw S_OK; +#endif /* 0 */ + } + + { + autoCaller.release(); + AutoReadLock parentLock(m->pParent COMMA_LOCKVAL_SRC_POS); + autoCaller.add(); + if (FAILED(autoCaller.hrc())) + throw autoCaller.hrc(); + + if ( !fRepairImageZeroParentUuid + && m->pParent->i_getState() != MediumState_Inaccessible + && m->pParent->i_getId() != parentId) + { + /** @todo r=klaus this always refers to VirtualBox.xml as the medium registry, even for new VMs */ + lastAccessError = Utf8StrFmt( + tr("Parent UUID {%RTuuid} of the medium '%s' does not match UUID {%RTuuid} of its parent medium stored in the media registry ('%s')"), + &parentId, location.c_str(), + m->pParent->i_getId().raw(), + pVirtualBox->i_settingsFilePath().c_str()); + parentLock.release(); + treeLock.release(); + throw S_OK; + } + } + + /// @todo NEWMEDIA what to do if the parent is not + /// accessible while the diff is? Probably nothing. The + /// real code will detect the mismatch anyway. + + treeLock.release(); + } + } + + mediumSize = VDGetFileSize(hdd, 0); + mediumLogicalSize = VDGetSize(hdd, 0); + + success = true; + } + catch (HRESULT hrcXcpt) + { + hrc = hrcXcpt; + } + + vrc = VDDestroy(hdd); + if (RT_FAILURE(vrc)) + { + lastAccessError.printf(tr("Could not update and close the medium '%s'%s"), + location.c_str(), i_vdError(vrc).c_str()); + success = false; + throw S_OK; + } + } + catch (HRESULT hrcXcpt) + { + hrc = hrcXcpt; + } + + autoCaller.release(); + treeLock.acquire(); + autoCaller.add(); + if (FAILED(autoCaller.hrc())) + { + m->queryInfoRunning = false; + return autoCaller.hrc(); + } + alock.acquire(); + + if (success) + { + m->size = mediumSize; + m->logicalSize = mediumLogicalSize; + m->strLastAccessError.setNull(); + } + else + { + m->strLastAccessError = lastAccessError; + Log1WarningFunc(("'%s' is not accessible (error='%s', hrc=%Rhrc, vrc=%Rrc)\n", + location.c_str(), m->strLastAccessError.c_str(), hrc, vrc)); + } + + /* Set the proper state according to the result of the check */ + if (success) + m->preLockState = MediumState_Created; + else + m->preLockState = MediumState_Inaccessible; + + /* unblock anyone waiting for the i_queryInfo results */ + qlock.release(); + m->queryInfoRunning = false; + + pToken->Abandon(); + pToken.setNull(); + + if (FAILED(hrc)) + return hrc; + + /* If this is a base image which incorrectly has a parent UUID set, + * repair the image now by zeroing the parent UUID. This is only done + * when we have structural information from a config file, on import + * this is not possible. If someone would accidentally call openMedium + * with a diff image before the base is registered this would destroy + * the diff. Not acceptable. */ + do + { + if (fRepairImageZeroParentUuid) + { + hrc = LockWrite(pToken.asOutParam()); + if (FAILED(hrc)) + break; + + alock.release(); + + try + { + PVDISK hdd; + vrc = VDCreate(m->vdDiskIfaces, i_convertDeviceType(), &hdd); + ComAssertRCThrow(vrc, E_FAIL); + + try + { + vrc = VDOpen(hdd, + format.c_str(), + location.c_str(), + (uOpenFlags & ~VD_OPEN_FLAGS_READONLY) | m->uOpenFlagsDef, + m->vdImageIfaces); + if (RT_FAILURE(vrc)) + throw S_OK; + + RTUUID zeroParentUuid; + RTUuidClear(&zeroParentUuid); + vrc = VDSetParentUuid(hdd, 0, &zeroParentUuid); + ComAssertRCThrow(vrc, E_FAIL); + } + catch (HRESULT hrcXcpt) + { + hrc = hrcXcpt; + } + + VDDestroy(hdd); + } + catch (HRESULT hrcXcpt) + { + hrc = hrcXcpt; + } + + pToken->Abandon(); + pToken.setNull(); + if (FAILED(hrc)) + break; + } + } while(0); + + return hrc; +} + +/** + * Performs extra checks if the medium can be closed and returns S_OK in + * this case. Otherwise, returns a respective error message. Called by + * Close() under the medium tree lock and the medium lock. + * + * @note Also reused by Medium::Reset(). + * + * @note Caller must hold the media tree write lock! + */ +HRESULT Medium::i_canClose() +{ + Assert(m->pVirtualBox->i_getMediaTreeLockHandle().isWriteLockOnCurrentThread()); + + if (i_getChildren().size() != 0) + return setError(VBOX_E_OBJECT_IN_USE, + tr("Cannot close medium '%s' because it has %d child media", "", i_getChildren().size()), + m->strLocationFull.c_str(), i_getChildren().size()); + + return S_OK; +} + +/** + * Unregisters this medium with mVirtualBox. Called by close() under the medium tree lock. + * + * @note Caller must have locked the media tree lock for writing! + */ +HRESULT Medium::i_unregisterWithVirtualBox() +{ + /* Note that we need to de-associate ourselves from the parent to let + * VirtualBox::i_unregisterMedium() properly save the registry */ + + /* we modify m->pParent and access children */ + Assert(m->pVirtualBox->i_getMediaTreeLockHandle().isWriteLockOnCurrentThread()); + + Medium *pParentBackup = m->pParent; + AssertReturn(i_getChildren().size() == 0, E_FAIL); + if (m->pParent) + i_deparent(); + + HRESULT hrc = m->pVirtualBox->i_unregisterMedium(this); + if (FAILED(hrc)) + { + if (pParentBackup) + { + // re-associate with the parent as we are still relatives in the registry + i_setParent(pParentBackup); + } + } + + return hrc; +} + +/** + * Like SetProperty but do not trigger a settings store. Only for internal use! + */ +HRESULT Medium::i_setPropertyDirect(const Utf8Str &aName, const Utf8Str &aValue) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.hrc())) return autoCaller.hrc(); + + AutoWriteLock mlock(this COMMA_LOCKVAL_SRC_POS); + + switch (m->state) + { + case MediumState_Created: + case MediumState_Inaccessible: + break; + default: + return i_setStateError(); + } + + m->mapProperties[aName] = aValue; + + return S_OK; +} + +/** + * Sets the extended error info according to the current media state. + * + * @note Must be called from under this object's write or read lock. + */ +HRESULT Medium::i_setStateError() +{ + HRESULT hrc; + + switch (m->state) + { + case MediumState_NotCreated: + hrc = setError(VBOX_E_INVALID_OBJECT_STATE, + tr("Storage for the medium '%s' is not created"), + m->strLocationFull.c_str()); + break; + case MediumState_Created: + hrc = setError(VBOX_E_INVALID_OBJECT_STATE, + tr("Storage for the medium '%s' is already created"), + m->strLocationFull.c_str()); + break; + case MediumState_LockedRead: + hrc = setError(VBOX_E_INVALID_OBJECT_STATE, + tr("Medium '%s' is locked for reading by another task"), + m->strLocationFull.c_str()); + break; + case MediumState_LockedWrite: + hrc = setError(VBOX_E_INVALID_OBJECT_STATE, + tr("Medium '%s' is locked for writing by another task"), + m->strLocationFull.c_str()); + break; + case MediumState_Inaccessible: + /* be in sync with Console::powerUpThread() */ + if (!m->strLastAccessError.isEmpty()) + hrc = setError(VBOX_E_INVALID_OBJECT_STATE, + tr("Medium '%s' is not accessible. %s"), + m->strLocationFull.c_str(), m->strLastAccessError.c_str()); + else + hrc = setError(VBOX_E_INVALID_OBJECT_STATE, + tr("Medium '%s' is not accessible"), + m->strLocationFull.c_str()); + break; + case MediumState_Creating: + hrc = setError(VBOX_E_INVALID_OBJECT_STATE, + tr("Storage for the medium '%s' is being created"), + m->strLocationFull.c_str()); + break; + case MediumState_Deleting: + hrc = setError(VBOX_E_INVALID_OBJECT_STATE, + tr("Storage for the medium '%s' is being deleted"), + m->strLocationFull.c_str()); + break; + default: + AssertFailed(); + hrc = E_FAIL; + break; + } + + return hrc; +} + +/** + * Sets the value of m->strLocationFull. The given location must be a fully + * qualified path; relative paths are not supported here. + * + * As a special exception, if the specified location is a file path that ends with '/' + * then the file name part will be generated by this method automatically in the format + * '{\<uuid\>}.\<ext\>' where \<uuid\> is a fresh UUID that this method will generate + * and assign to this medium, and \<ext\> is the default extension for this + * medium's storage format. Note that this procedure requires the media state to + * be NotCreated and will return a failure otherwise. + * + * @param aLocation Location of the storage unit. If the location is a FS-path, + * then it can be relative to the VirtualBox home directory. + * @param aFormat Optional fallback format if it is an import and the format + * cannot be determined. + * + * @note Must be called from under this object's write lock. + */ +HRESULT Medium::i_setLocation(const Utf8Str &aLocation, + const Utf8Str &aFormat /* = Utf8Str::Empty */) +{ + AssertReturn(!aLocation.isEmpty(), E_FAIL); + + AutoCaller autoCaller(this); + AssertComRCReturnRC(autoCaller.hrc()); + + /* formatObj may be null only when initializing from an existing path and + * no format is known yet */ + AssertReturn( (!m->strFormat.isEmpty() && !m->formatObj.isNull()) + || ( getObjectState().getState() == ObjectState::InInit + && m->state != MediumState_NotCreated + && m->id.isZero() + && m->strFormat.isEmpty() + && m->formatObj.isNull()), + E_FAIL); + + /* are we dealing with a new medium constructed using the existing + * location? */ + bool isImport = m->strFormat.isEmpty(); + + if ( isImport + || ( (m->formatObj->i_getCapabilities() & MediumFormatCapabilities_File) + && !m->hostDrive)) + { + Guid id; + + Utf8Str locationFull(aLocation); + + if (m->state == MediumState_NotCreated) + { + /* must be a file (formatObj must be already known) */ + Assert(m->formatObj->i_getCapabilities() & MediumFormatCapabilities_File); + + if (RTPathFilename(aLocation.c_str()) == NULL) + { + /* no file name is given (either an empty string or ends with a + * slash), generate a new UUID + file name if the state allows + * this */ + + ComAssertMsgRet(!m->formatObj->i_getFileExtensions().empty(), + (tr("Must be at least one extension if it is MediumFormatCapabilities_File\n")), + E_FAIL); + + Utf8Str strExt = m->formatObj->i_getFileExtensions().front(); + ComAssertMsgRet(!strExt.isEmpty(), + (tr("Default extension must not be empty\n")), + E_FAIL); + + id.create(); + + locationFull = Utf8StrFmt("%s{%RTuuid}.%s", + aLocation.c_str(), id.raw(), strExt.c_str()); + } + } + + // we must always have full paths now (if it refers to a file) + if ( ( m->formatObj.isNull() + || m->formatObj->i_getCapabilities() & MediumFormatCapabilities_File) + && !RTPathStartsWithRoot(locationFull.c_str())) + return setError(VBOX_E_FILE_ERROR, + tr("The given path '%s' is not fully qualified"), + locationFull.c_str()); + + /* detect the backend from the storage unit if importing */ + if (isImport) + { + VDTYPE const enmDesiredType = i_convertDeviceType(); + VDTYPE enmType = VDTYPE_INVALID; + char *backendName = NULL; + + /* is it a file? */ + RTFILE hFile; + int vrc = RTFileOpen(&hFile, locationFull.c_str(), RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE); + if (RT_SUCCESS(vrc)) + { + RTFileClose(hFile); + vrc = VDGetFormat(NULL /* pVDIfsDisk */, NULL /* pVDIfsImage */, + locationFull.c_str(), enmDesiredType, &backendName, &enmType); + } + else if ( vrc != VERR_FILE_NOT_FOUND + && vrc != VERR_PATH_NOT_FOUND + && vrc != VERR_ACCESS_DENIED + && locationFull != aLocation) + { + /* assume it's not a file, restore the original location */ + locationFull = aLocation; + vrc = VDGetFormat(NULL /* pVDIfsDisk */, NULL /* pVDIfsImage */, + locationFull.c_str(), enmDesiredType, &backendName, &enmType); + } + + if (RT_FAILURE(vrc)) + { + if (vrc == VERR_ACCESS_DENIED) + return setErrorBoth(VBOX_E_FILE_ERROR, vrc, + tr("Permission problem accessing the file for the medium '%s' (%Rrc)"), + locationFull.c_str(), vrc); + if (vrc == VERR_FILE_NOT_FOUND || vrc == VERR_PATH_NOT_FOUND) + return setErrorBoth(VBOX_E_FILE_ERROR, vrc, + tr("Could not find file for the medium '%s' (%Rrc)"), + locationFull.c_str(), vrc); + if (aFormat.isEmpty()) + return setErrorBoth(VBOX_E_IPRT_ERROR, vrc, + tr("Could not get the storage format of the medium '%s' (%Rrc)"), + locationFull.c_str(), vrc); + HRESULT hrc = i_setFormat(aFormat); + /* setFormat() must not fail since we've just used the backend so + * the format object must be there */ + AssertComRCReturnRC(hrc); + } + else if ( enmType == VDTYPE_INVALID + || m->devType != i_convertToDeviceType(enmType)) + { + /* + * The user tried to use a image as a device which is not supported + * by the backend. + */ + RTStrFree(backendName); + return setError(E_FAIL, + tr("The medium '%s' can't be used as the requested device type (%s, detected %s)"), + locationFull.c_str(), getDeviceTypeName(m->devType), getVDTypeName(enmType)); + } + else + { + ComAssertRet(backendName != NULL && *backendName != '\0', E_FAIL); + + HRESULT hrc = i_setFormat(backendName); + RTStrFree(backendName); + + /* setFormat() must not fail since we've just used the backend so + * the format object must be there */ + AssertComRCReturnRC(hrc); + } + } + + m->strLocationFull = locationFull; + + /* is it still a file? */ + if ( (m->formatObj->i_getCapabilities() & MediumFormatCapabilities_File) + && (m->state == MediumState_NotCreated) + ) + /* assign a new UUID (this UUID will be used when calling + * VDCreateBase/VDCreateDiff as a wanted UUID). Note that we + * also do that if we didn't generate it to make sure it is + * either generated by us or reset to null */ + unconst(m->id) = id; + } + else + m->strLocationFull = aLocation; + + return S_OK; +} + +/** + * Checks that the format ID is valid and sets it on success. + * + * Note that this method will caller-reference the format object on success! + * This reference must be released somewhere to let the MediumFormat object be + * uninitialized. + * + * @note Must be called from under this object's write lock. + */ +HRESULT Medium::i_setFormat(const Utf8Str &aFormat) +{ + /* get the format object first */ + { + SystemProperties *pSysProps = m->pVirtualBox->i_getSystemProperties(); + AutoReadLock propsLock(pSysProps COMMA_LOCKVAL_SRC_POS); + + unconst(m->formatObj) = pSysProps->i_mediumFormat(aFormat); + if (m->formatObj.isNull()) + return setError(E_INVALIDARG, + tr("Invalid medium storage format '%s'"), + aFormat.c_str()); + + /* get properties (preinsert them as keys in the map). Note that the + * map doesn't grow over the object life time since the set of + * properties is meant to be constant. */ + + Assert(m->mapProperties.empty()); + + for (MediumFormat::PropertyArray::const_iterator it = m->formatObj->i_getProperties().begin(); + it != m->formatObj->i_getProperties().end(); + ++it) + { + m->mapProperties.insert(std::make_pair(it->strName, Utf8Str::Empty)); + } + } + + unconst(m->strFormat) = aFormat; + + return S_OK; +} + +/** + * Converts the Medium device type to the VD type. + */ +VDTYPE Medium::i_convertDeviceType() +{ + VDTYPE enmType; + + switch (m->devType) + { + case DeviceType_HardDisk: + enmType = VDTYPE_HDD; + break; + case DeviceType_DVD: + enmType = VDTYPE_OPTICAL_DISC; + break; + case DeviceType_Floppy: + enmType = VDTYPE_FLOPPY; + break; + default: + ComAssertFailedRet(VDTYPE_INVALID); + } + + return enmType; +} + +/** + * Converts from the VD type to the medium type. + */ +DeviceType_T Medium::i_convertToDeviceType(VDTYPE enmType) +{ + DeviceType_T devType; + + switch (enmType) + { + case VDTYPE_HDD: + devType = DeviceType_HardDisk; + break; + case VDTYPE_OPTICAL_DISC: + devType = DeviceType_DVD; + break; + case VDTYPE_FLOPPY: + devType = DeviceType_Floppy; + break; + default: + ComAssertFailedRet(DeviceType_Null); + } + + return devType; +} + +/** + * Internal method which checks whether a property name is for a filter plugin. + */ +bool Medium::i_isPropertyForFilter(const com::Utf8Str &aName) +{ + /* If the name contains "/" use the part before as a filter name and lookup the filter. */ + size_t offSlash; + if ((offSlash = aName.find("/", 0)) != aName.npos) + { + com::Utf8Str strFilter; + com::Utf8Str strKey; + + HRESULT hrc = strFilter.assignEx(aName, 0, offSlash); + if (FAILED(hrc)) + return false; + + hrc = strKey.assignEx(aName, offSlash + 1, aName.length() - offSlash - 1); /* Skip slash */ + if (FAILED(hrc)) + return false; + + VDFILTERINFO FilterInfo; + int vrc = VDFilterInfoOne(strFilter.c_str(), &FilterInfo); + if (RT_SUCCESS(vrc)) + { + /* Check that the property exists. */ + PCVDCONFIGINFO paConfig = FilterInfo.paConfigInfo; + while (paConfig->pszKey) + { + if (strKey.equals(paConfig->pszKey)) + return true; + paConfig++; + } + } + } + + return false; +} + +/** + * Returns the last error message collected by the i_vdErrorCall callback and + * resets it. + * + * The error message is returned prepended with a dot and a space, like this: + * <code> + * ". <error_text> (%Rrc)" + * </code> + * to make it easily appendable to a more general error message. The @c %Rrc + * format string is given @a aVRC as an argument. + * + * If there is no last error message collected by i_vdErrorCall or if it is a + * null or empty string, then this function returns the following text: + * <code> + * " (%Rrc)" + * </code> + * + * @note Doesn't do any object locking; it is assumed that the caller makes sure + * the callback isn't called by more than one thread at a time. + * + * @param aVRC VBox error code to use when no error message is provided. + */ +Utf8Str Medium::i_vdError(int aVRC) +{ + Utf8Str error; + + if (m->vdError.isEmpty()) + error.printf(" (%Rrc)", aVRC); + else + error.printf(".\n%s", m->vdError.c_str()); + + m->vdError.setNull(); + + return error; +} + +/** + * Error message callback. + * + * Puts the reported error message to the m->vdError field. + * + * @note Doesn't do any object locking; it is assumed that the caller makes sure + * the callback isn't called by more than one thread at a time. + * + * @param pvUser The opaque data passed on container creation. + * @param vrc The VBox error code. + * @param SRC_POS Use RT_SRC_POS. + * @param pszFormat Error message format string. + * @param va Error message arguments. + */ +/*static*/ +DECLCALLBACK(void) Medium::i_vdErrorCall(void *pvUser, int vrc, RT_SRC_POS_DECL, + const char *pszFormat, va_list va) +{ + NOREF(pszFile); NOREF(iLine); NOREF(pszFunction); /* RT_SRC_POS_DECL */ + + Medium *that = static_cast<Medium*>(pvUser); + AssertReturnVoid(that != NULL); + + va_list vaCopy; /* For gcc */ + va_copy(vaCopy, va); + if (that->m->vdError.isEmpty()) + that->m->vdError.printf("%N (%Rrc)", pszFormat, &vaCopy, vrc); + else + that->m->vdError.appendPrintf(".\n%N (%Rrc)", pszFormat, &vaCopy, vrc); + va_end(vaCopy); +} + +/* static */ +DECLCALLBACK(bool) Medium::i_vdConfigAreKeysValid(void *pvUser, + const char * /* pszzValid */) +{ + Medium *that = static_cast<Medium*>(pvUser); + AssertReturn(that != NULL, false); + + /* we always return true since the only keys we have are those found in + * VDBACKENDINFO */ + return true; +} + +/* static */ +DECLCALLBACK(int) Medium::i_vdConfigQuerySize(void *pvUser, + const char *pszName, + size_t *pcbValue) +{ + AssertPtrReturn(pcbValue, VERR_INVALID_POINTER); + + Medium *that = static_cast<Medium*>(pvUser); + AssertReturn(that != NULL, VERR_GENERAL_FAILURE); + + settings::StringsMap::const_iterator it = that->m->mapProperties.find(Utf8Str(pszName)); + if (it == that->m->mapProperties.end()) + return VERR_CFGM_VALUE_NOT_FOUND; + + /* we interpret null values as "no value" in Medium */ + if (it->second.isEmpty()) + return VERR_CFGM_VALUE_NOT_FOUND; + + *pcbValue = it->second.length() + 1 /* include terminator */; + + return VINF_SUCCESS; +} + +/* static */ +DECLCALLBACK(int) Medium::i_vdConfigQuery(void *pvUser, + const char *pszName, + char *pszValue, + size_t cchValue) +{ + AssertPtrReturn(pszValue, VERR_INVALID_POINTER); + + Medium *that = static_cast<Medium*>(pvUser); + AssertReturn(that != NULL, VERR_GENERAL_FAILURE); + + settings::StringsMap::const_iterator it = that->m->mapProperties.find(Utf8Str(pszName)); + if (it == that->m->mapProperties.end()) + return VERR_CFGM_VALUE_NOT_FOUND; + + /* we interpret null values as "no value" in Medium */ + if (it->second.isEmpty()) + return VERR_CFGM_VALUE_NOT_FOUND; + + const Utf8Str &value = it->second; + if (value.length() >= cchValue) + return VERR_CFGM_NOT_ENOUGH_SPACE; + + memcpy(pszValue, value.c_str(), value.length() + 1); + + return VINF_SUCCESS; +} + +DECLCALLBACK(bool) Medium::i_vdCryptoConfigAreKeysValid(void *pvUser, const char *pszzValid) +{ + /* Just return always true here. */ + NOREF(pvUser); + NOREF(pszzValid); + return true; +} + +DECLCALLBACK(int) Medium::i_vdCryptoConfigQuerySize(void *pvUser, const char *pszName, size_t *pcbValue) +{ + MediumCryptoFilterSettings *pSettings = (MediumCryptoFilterSettings *)pvUser; + AssertPtrReturn(pSettings, VERR_GENERAL_FAILURE); + AssertPtrReturn(pcbValue, VERR_INVALID_POINTER); + + size_t cbValue = 0; + if (!strcmp(pszName, "Algorithm")) + cbValue = strlen(pSettings->pszCipher) + 1; + else if (!strcmp(pszName, "KeyId")) + cbValue = sizeof("irrelevant"); + else if (!strcmp(pszName, "KeyStore")) + { + if (!pSettings->pszKeyStoreLoad) + return VERR_CFGM_VALUE_NOT_FOUND; + cbValue = strlen(pSettings->pszKeyStoreLoad) + 1; + } + else if (!strcmp(pszName, "CreateKeyStore")) + cbValue = 2; /* Single digit + terminator. */ + else + return VERR_CFGM_VALUE_NOT_FOUND; + + *pcbValue = cbValue + 1 /* include terminator */; + + return VINF_SUCCESS; +} + +DECLCALLBACK(int) Medium::i_vdCryptoConfigQuery(void *pvUser, const char *pszName, + char *pszValue, size_t cchValue) +{ + MediumCryptoFilterSettings *pSettings = (MediumCryptoFilterSettings *)pvUser; + AssertPtrReturn(pSettings, VERR_GENERAL_FAILURE); + AssertPtrReturn(pszValue, VERR_INVALID_POINTER); + + const char *psz = NULL; + if (!strcmp(pszName, "Algorithm")) + psz = pSettings->pszCipher; + else if (!strcmp(pszName, "KeyId")) + psz = "irrelevant"; + else if (!strcmp(pszName, "KeyStore")) + psz = pSettings->pszKeyStoreLoad; + else if (!strcmp(pszName, "CreateKeyStore")) + { + if (pSettings->fCreateKeyStore) + psz = "1"; + else + psz = "0"; + } + else + return VERR_CFGM_VALUE_NOT_FOUND; + + size_t cch = strlen(psz); + if (cch >= cchValue) + return VERR_CFGM_NOT_ENOUGH_SPACE; + + memcpy(pszValue, psz, cch + 1); + return VINF_SUCCESS; +} + +DECLCALLBACK(int) Medium::i_vdConfigUpdate(void *pvUser, + bool fCreate, + const char *pszName, + const char *pszValue) +{ + Medium *that = (Medium *)pvUser; + + // Detect if this runs inside i_queryInfo() on the current thread. + // Skip if not. Check does not need synchronization. + if (!that->m || !that->m->queryInfoRunning || !that->m->queryInfoSem.isWriteLockOnCurrentThread()) + return VINF_SUCCESS; + + // It's guaranteed that this code is executing inside Medium::i_queryInfo, + // can assume it took care of synchronization. + int rv = VINF_SUCCESS; + Utf8Str strName(pszName); + settings::StringsMap::const_iterator it = that->m->mapProperties.find(strName); + if (it == that->m->mapProperties.end() && !fCreate) + rv = VERR_CFGM_VALUE_NOT_FOUND; + else + that->m->mapProperties[strName] = Utf8Str(pszValue); + return rv; +} + +DECLCALLBACK(int) Medium::i_vdCryptoKeyRetain(void *pvUser, const char *pszId, + const uint8_t **ppbKey, size_t *pcbKey) +{ + MediumCryptoFilterSettings *pSettings = (MediumCryptoFilterSettings *)pvUser; + NOREF(pszId); + NOREF(ppbKey); + NOREF(pcbKey); + AssertPtrReturn(pSettings, VERR_GENERAL_FAILURE); + AssertMsgFailedReturn(("This method should not be called here!\n"), VERR_INVALID_STATE); +} + +DECLCALLBACK(int) Medium::i_vdCryptoKeyRelease(void *pvUser, const char *pszId) +{ + MediumCryptoFilterSettings *pSettings = (MediumCryptoFilterSettings *)pvUser; + NOREF(pszId); + AssertPtrReturn(pSettings, VERR_GENERAL_FAILURE); + AssertMsgFailedReturn(("This method should not be called here!\n"), VERR_INVALID_STATE); +} + +DECLCALLBACK(int) Medium::i_vdCryptoKeyStorePasswordRetain(void *pvUser, const char *pszId, const char **ppszPassword) +{ + MediumCryptoFilterSettings *pSettings = (MediumCryptoFilterSettings *)pvUser; + AssertPtrReturn(pSettings, VERR_GENERAL_FAILURE); + + NOREF(pszId); + *ppszPassword = pSettings->pszPassword; + return VINF_SUCCESS; +} + +DECLCALLBACK(int) Medium::i_vdCryptoKeyStorePasswordRelease(void *pvUser, const char *pszId) +{ + MediumCryptoFilterSettings *pSettings = (MediumCryptoFilterSettings *)pvUser; + AssertPtrReturn(pSettings, VERR_GENERAL_FAILURE); + NOREF(pszId); + return VINF_SUCCESS; +} + +DECLCALLBACK(int) Medium::i_vdCryptoKeyStoreSave(void *pvUser, const void *pvKeyStore, size_t cbKeyStore) +{ + MediumCryptoFilterSettings *pSettings = (MediumCryptoFilterSettings *)pvUser; + AssertPtrReturn(pSettings, VERR_GENERAL_FAILURE); + + pSettings->pszKeyStore = (char *)RTMemAllocZ(cbKeyStore); + if (!pSettings->pszKeyStore) + return VERR_NO_MEMORY; + + memcpy(pSettings->pszKeyStore, pvKeyStore, cbKeyStore); + return VINF_SUCCESS; +} + +DECLCALLBACK(int) Medium::i_vdCryptoKeyStoreReturnParameters(void *pvUser, const char *pszCipher, + const uint8_t *pbDek, size_t cbDek) +{ + MediumCryptoFilterSettings *pSettings = (MediumCryptoFilterSettings *)pvUser; + AssertPtrReturn(pSettings, VERR_GENERAL_FAILURE); + + pSettings->pszCipherReturned = RTStrDup(pszCipher); + pSettings->pbDek = pbDek; + pSettings->cbDek = cbDek; + + return pSettings->pszCipherReturned ? VINF_SUCCESS : VERR_NO_MEMORY; +} + +/** + * Creates a VDISK instance for this medium. + * + * @note Caller should not hold any medium related locks as this method will + * acquire the medium lock for writing and others (VirtualBox). + * + * @returns COM status code. + * @param fWritable Whether to return a writable VDISK instance + * (true) or a read-only one (false). + * @param pKeyStore The key store. + * @param ppHdd Where to return the pointer to the VDISK on + * success. + * @param pMediumLockList The lock list to populate and lock. Caller + * is responsible for calling the destructor or + * MediumLockList::Clear() after destroying + * @a *ppHdd + * @param pCryptoSettings The crypto settings to use for setting up + * decryption/encryption of the VDISK. This object + * must be alive until the VDISK is destroyed! + */ +HRESULT Medium::i_openForIO(bool fWritable, SecretKeyStore *pKeyStore, PVDISK *ppHdd, MediumLockList *pMediumLockList, + MediumCryptoFilterSettings *pCryptoSettings) +{ + /* + * Create the media lock list and lock the media. + */ + HRESULT hrc = i_createMediumLockList(true /* fFailIfInaccessible */, + fWritable ? this : NULL /* pToLockWrite */, + false /* fMediumLockWriteAll */, + NULL, + *pMediumLockList); + if (SUCCEEDED(hrc)) + hrc = pMediumLockList->Lock(); + if (FAILED(hrc)) + return hrc; + + /* + * Get the base medium before write locking this medium. + */ + ComObjPtr<Medium> pBase = i_getBase(); + AutoWriteLock thisLock(this COMMA_LOCKVAL_SRC_POS); + + /* + * Create the VDISK instance. + */ + PVDISK pHdd; + int vrc = VDCreate(m->vdDiskIfaces, i_convertDeviceType(), &pHdd); + AssertRCReturn(vrc, E_FAIL); + + /* + * Goto avoidance using try/catch/throw(HRESULT). + */ + try + { + settings::StringsMap::iterator itKeyStore = pBase->m->mapProperties.find("CRYPT/KeyStore"); + if (itKeyStore != pBase->m->mapProperties.end()) + { +#ifdef VBOX_WITH_EXTPACK + settings::StringsMap::iterator itKeyId = pBase->m->mapProperties.find("CRYPT/KeyId"); + + ExtPackManager *pExtPackManager = m->pVirtualBox->i_getExtPackManager(); + if (pExtPackManager->i_isExtPackUsable(ORACLE_PUEL_EXTPACK_NAME)) + { + /* Load the plugin */ + Utf8Str strPlugin; + hrc = pExtPackManager->i_getLibraryPathForExtPack(g_szVDPlugin, ORACLE_PUEL_EXTPACK_NAME, &strPlugin); + if (SUCCEEDED(hrc)) + { + vrc = VDPluginLoadFromFilename(strPlugin.c_str()); + if (RT_FAILURE(vrc)) + throw setErrorBoth(VBOX_E_NOT_SUPPORTED, vrc, + tr("Retrieving encryption settings of the image failed because the encryption plugin could not be loaded (%s)"), + i_vdError(vrc).c_str()); + } + else + throw setError(VBOX_E_NOT_SUPPORTED, + tr("Encryption is not supported because the extension pack '%s' is missing the encryption plugin (old extension pack installed?)"), + ORACLE_PUEL_EXTPACK_NAME); + } + else + throw setError(VBOX_E_NOT_SUPPORTED, + tr("Encryption is not supported because the extension pack '%s' is missing"), + ORACLE_PUEL_EXTPACK_NAME); + + if (itKeyId == pBase->m->mapProperties.end()) + throw setError(VBOX_E_INVALID_OBJECT_STATE, + tr("Image '%s' is configured for encryption but doesn't has a key identifier set"), + pBase->m->strLocationFull.c_str()); + + /* Find the proper secret key in the key store. */ + if (!pKeyStore) + throw setError(VBOX_E_INVALID_OBJECT_STATE, + tr("Image '%s' is configured for encryption but there is no key store to retrieve the password from"), + pBase->m->strLocationFull.c_str()); + + SecretKey *pKey = NULL; + vrc = pKeyStore->retainSecretKey(itKeyId->second, &pKey); + if (RT_FAILURE(vrc)) + throw setErrorBoth(VBOX_E_INVALID_OBJECT_STATE, vrc, + tr("Failed to retrieve the secret key with ID \"%s\" from the store (%Rrc)"), + itKeyId->second.c_str(), vrc); + + i_taskEncryptSettingsSetup(pCryptoSettings, NULL, itKeyStore->second.c_str(), (const char *)pKey->getKeyBuffer(), + false /* fCreateKeyStore */); + vrc = VDFilterAdd(pHdd, "CRYPT", VD_FILTER_FLAGS_DEFAULT, pCryptoSettings->vdFilterIfaces); + pKeyStore->releaseSecretKey(itKeyId->second); + if (vrc == VERR_VD_PASSWORD_INCORRECT) + throw setErrorBoth(VBOX_E_PASSWORD_INCORRECT, vrc, tr("The password to decrypt the image is incorrect")); + if (RT_FAILURE(vrc)) + throw setErrorBoth(VBOX_E_INVALID_OBJECT_STATE, vrc, tr("Failed to load the decryption filter: %s"), + i_vdError(vrc).c_str()); +#else + RT_NOREF(pKeyStore, pCryptoSettings); + throw setError(VBOX_E_NOT_SUPPORTED, + tr("Encryption is not supported because extension pack support is not built in")); +#endif /* VBOX_WITH_EXTPACK */ + } + + /* + * Open all media in the source chain. + */ + MediumLockList::Base::const_iterator sourceListBegin = pMediumLockList->GetBegin(); + MediumLockList::Base::const_iterator sourceListEnd = pMediumLockList->GetEnd(); + MediumLockList::Base::const_iterator mediumListLast = sourceListEnd; + --mediumListLast; + + for (MediumLockList::Base::const_iterator it = sourceListBegin; it != sourceListEnd; ++it) + { + const MediumLock &mediumLock = *it; + const ComObjPtr<Medium> &pMedium = mediumLock.GetMedium(); + AutoReadLock alock(pMedium COMMA_LOCKVAL_SRC_POS); + + /* sanity check */ + Assert(pMedium->m->state == (fWritable && it == mediumListLast ? MediumState_LockedWrite : MediumState_LockedRead)); + + /* Open all media in read-only mode. */ + vrc = VDOpen(pHdd, + pMedium->m->strFormat.c_str(), + pMedium->m->strLocationFull.c_str(), + m->uOpenFlagsDef | (fWritable && it == mediumListLast ? VD_OPEN_FLAGS_NORMAL : VD_OPEN_FLAGS_READONLY), + pMedium->m->vdImageIfaces); + if (RT_FAILURE(vrc)) + throw setErrorBoth(VBOX_E_FILE_ERROR, vrc, + tr("Could not open the medium storage unit '%s'%s"), + pMedium->m->strLocationFull.c_str(), + i_vdError(vrc).c_str()); + } + + Assert(m->state == (fWritable ? MediumState_LockedWrite : MediumState_LockedRead)); + + /* + * Done! + */ + *ppHdd = pHdd; + return S_OK; + } + catch (HRESULT hrc2) + { + hrc = hrc2; + } + + VDDestroy(pHdd); + return hrc; + +} + +/** + * Implementation code for the "create base" task. + * + * This only gets started from Medium::CreateBaseStorage() and always runs + * asynchronously. As a result, we always save the VirtualBox.xml file when + * we're done here. + * + * @param task + * @return + */ +HRESULT Medium::i_taskCreateBaseHandler(Medium::CreateBaseTask &task) +{ + /** @todo r=klaus The code below needs to be double checked with regard + * to lock order violations, it probably causes lock order issues related + * to the AutoCaller usage. */ + HRESULT hrc = S_OK; + + /* these parameters we need after creation */ + uint64_t size = 0, logicalSize = 0; + MediumVariant_T variant = MediumVariant_Standard; + bool fGenerateUuid = false; + + try + { + AutoWriteLock thisLock(this COMMA_LOCKVAL_SRC_POS); + + /* The object may request a specific UUID (through a special form of + * the moveTo() argument). Otherwise we have to generate it */ + Guid id = m->id; + + fGenerateUuid = id.isZero(); + if (fGenerateUuid) + { + id.create(); + /* VirtualBox::i_registerMedium() will need UUID */ + unconst(m->id) = id; + } + + Utf8Str format(m->strFormat); + Utf8Str location(m->strLocationFull); + uint64_t capabilities = m->formatObj->i_getCapabilities(); + ComAssertThrow(capabilities & ( MediumFormatCapabilities_CreateFixed + | MediumFormatCapabilities_CreateDynamic), E_FAIL); + Assert(m->state == MediumState_Creating); + + PVDISK hdd; + int vrc = VDCreate(m->vdDiskIfaces, i_convertDeviceType(), &hdd); + ComAssertRCThrow(vrc, E_FAIL); + + /* unlock before the potentially lengthy operation */ + thisLock.release(); + + try + { + /* ensure the directory exists */ + if (capabilities & MediumFormatCapabilities_File) + { + hrc = VirtualBox::i_ensureFilePathExists(location, !(task.mVariant & MediumVariant_NoCreateDir) /* fCreate */); + if (FAILED(hrc)) + throw hrc; + } + + VDGEOMETRY geo = { 0, 0, 0 }; /* auto-detect */ + + vrc = VDCreateBase(hdd, + format.c_str(), + location.c_str(), + task.mSize, + task.mVariant & ~(MediumVariant_NoCreateDir | MediumVariant_Formatted), + NULL, + &geo, + &geo, + id.raw(), + VD_OPEN_FLAGS_NORMAL | m->uOpenFlagsDef, + m->vdImageIfaces, + task.mVDOperationIfaces); + if (RT_FAILURE(vrc)) + { + if (vrc == VERR_VD_INVALID_TYPE) + throw setErrorBoth(VBOX_E_FILE_ERROR, vrc, + tr("Parameters for creating the medium storage unit '%s' are invalid%s"), + location.c_str(), i_vdError(vrc).c_str()); + else + throw setErrorBoth(VBOX_E_FILE_ERROR, vrc, + tr("Could not create the medium storage unit '%s'%s"), + location.c_str(), i_vdError(vrc).c_str()); + } + + if (task.mVariant & MediumVariant_Formatted) + { + RTVFSFILE hVfsFile; + vrc = VDCreateVfsFileFromDisk(hdd, 0 /*fFlags*/, &hVfsFile); + if (RT_FAILURE(vrc)) + throw setErrorBoth(VBOX_E_FILE_ERROR, vrc, tr("Opening medium storage unit '%s' failed%s"), + location.c_str(), i_vdError(vrc).c_str()); + RTERRINFOSTATIC ErrInfo; + vrc = RTFsFatVolFormat(hVfsFile, 0 /* offVol */, 0 /* cbVol */, RTFSFATVOL_FMT_F_FULL, + 0 /* cbSector */, 0 /* cbSectorPerCluster */, RTFSFATTYPE_INVALID, + 0 /* cHeads */, 0 /* cSectorsPerTrack*/, 0 /* bMedia */, + 0 /* cRootDirEntries */, 0 /* cHiddenSectors */, + RTErrInfoInitStatic(&ErrInfo)); + RTVfsFileRelease(hVfsFile); + if (RT_FAILURE(vrc) && RTErrInfoIsSet(&ErrInfo.Core)) + throw setErrorBoth(VBOX_E_FILE_ERROR, vrc, tr("Formatting medium storage unit '%s' failed: %s"), + location.c_str(), ErrInfo.Core.pszMsg); + if (RT_FAILURE(vrc)) + throw setErrorBoth(VBOX_E_FILE_ERROR, vrc, tr("Formatting medium storage unit '%s' failed%s"), + location.c_str(), i_vdError(vrc).c_str()); + } + + size = VDGetFileSize(hdd, 0); + logicalSize = VDGetSize(hdd, 0); + unsigned uImageFlags; + vrc = VDGetImageFlags(hdd, 0, &uImageFlags); + if (RT_SUCCESS(vrc)) + variant = (MediumVariant_T)uImageFlags; + } + catch (HRESULT hrcXcpt) { hrc = hrcXcpt; } + + VDDestroy(hdd); + } + catch (HRESULT hrcXcpt) { hrc = hrcXcpt; } + + if (SUCCEEDED(hrc)) + { + /* register with mVirtualBox as the last step and move to + * Created state only on success (leaving an orphan file is + * better than breaking media registry consistency) */ + AutoWriteLock treeLock(m->pVirtualBox->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS); + ComObjPtr<Medium> pMedium; + hrc = m->pVirtualBox->i_registerMedium(this, &pMedium, treeLock); + Assert(pMedium == NULL || this == pMedium); + } + + // re-acquire the lock before changing state + AutoWriteLock thisLock(this COMMA_LOCKVAL_SRC_POS); + + if (SUCCEEDED(hrc)) + { + m->state = MediumState_Created; + + m->size = size; + m->logicalSize = logicalSize; + m->variant = variant; + + thisLock.release(); + i_markRegistriesModified(); + if (task.isAsync()) + { + // in asynchronous mode, save settings now + m->pVirtualBox->i_saveModifiedRegistries(); + } + } + else + { + /* back to NotCreated on failure */ + m->state = MediumState_NotCreated; + + /* reset UUID to prevent it from being reused next time */ + if (fGenerateUuid) + unconst(m->id).clear(); + } + + if (task.NotifyAboutChanges() && SUCCEEDED(hrc)) + { + m->pVirtualBox->i_onMediumConfigChanged(this); + m->pVirtualBox->i_onMediumRegistered(m->id, m->devType, TRUE); + } + + return hrc; +} + +/** + * Implementation code for the "create diff" task. + * + * This task always gets started from Medium::createDiffStorage() and can run + * synchronously or asynchronously depending on the "wait" parameter passed to + * that function. If we run synchronously, the caller expects the medium + * registry modification to be set before returning; otherwise (in asynchronous + * mode), we save the settings ourselves. + * + * @param task + * @return + */ +HRESULT Medium::i_taskCreateDiffHandler(Medium::CreateDiffTask &task) +{ + /** @todo r=klaus The code below needs to be double checked with regard + * to lock order violations, it probably causes lock order issues related + * to the AutoCaller usage. */ + HRESULT hrcTmp = S_OK; + + const ComObjPtr<Medium> &pTarget = task.mTarget; + + uint64_t size = 0, logicalSize = 0; + MediumVariant_T variant = MediumVariant_Standard; + bool fGenerateUuid = false; + + try + { + if (i_getDepth() >= SETTINGS_MEDIUM_DEPTH_MAX) + { + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + throw setError(VBOX_E_INVALID_OBJECT_STATE, + tr("Cannot create differencing image for medium '%s', because it exceeds the medium tree depth limit. Please merge some images which you no longer need"), + m->strLocationFull.c_str()); + } + + /* Lock both in {parent,child} order. */ + AutoMultiWriteLock2 mediaLock(this, pTarget COMMA_LOCKVAL_SRC_POS); + + /* The object may request a specific UUID (through a special form of + * the moveTo() argument). Otherwise we have to generate it */ + Guid targetId = pTarget->m->id; + + fGenerateUuid = targetId.isZero(); + if (fGenerateUuid) + { + targetId.create(); + /* VirtualBox::i_registerMedium() will need UUID */ + unconst(pTarget->m->id) = targetId; + } + + Guid id = m->id; + + Utf8Str targetFormat(pTarget->m->strFormat); + Utf8Str targetLocation(pTarget->m->strLocationFull); + uint64_t capabilities = pTarget->m->formatObj->i_getCapabilities(); + ComAssertThrow(capabilities & MediumFormatCapabilities_CreateDynamic, E_FAIL); + + Assert(pTarget->m->state == MediumState_Creating); + Assert(m->state == MediumState_LockedRead); + + PVDISK hdd; + int vrc = VDCreate(m->vdDiskIfaces, i_convertDeviceType(), &hdd); + ComAssertRCThrow(vrc, E_FAIL); + + /* the two media are now protected by their non-default states; + * unlock the media before the potentially lengthy operation */ + mediaLock.release(); + + try + { + /* Open all media in the target chain but the last. */ + MediumLockList::Base::const_iterator targetListBegin = + task.mpMediumLockList->GetBegin(); + MediumLockList::Base::const_iterator targetListEnd = + task.mpMediumLockList->GetEnd(); + for (MediumLockList::Base::const_iterator it = targetListBegin; + it != targetListEnd; + ++it) + { + const MediumLock &mediumLock = *it; + const ComObjPtr<Medium> &pMedium = mediumLock.GetMedium(); + + AutoReadLock alock(pMedium COMMA_LOCKVAL_SRC_POS); + + /* Skip over the target diff medium */ + if (pMedium->m->state == MediumState_Creating) + continue; + + /* sanity check */ + Assert(pMedium->m->state == MediumState_LockedRead); + + /* Open all media in appropriate mode. */ + vrc = VDOpen(hdd, + pMedium->m->strFormat.c_str(), + pMedium->m->strLocationFull.c_str(), + VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO | m->uOpenFlagsDef, + pMedium->m->vdImageIfaces); + if (RT_FAILURE(vrc)) + throw setErrorBoth(VBOX_E_FILE_ERROR, vrc, + tr("Could not open the medium storage unit '%s'%s"), + pMedium->m->strLocationFull.c_str(), + i_vdError(vrc).c_str()); + } + + /* ensure the target directory exists */ + if (capabilities & MediumFormatCapabilities_File) + { + HRESULT hrc = VirtualBox::i_ensureFilePathExists(targetLocation, + !(task.mVariant & MediumVariant_NoCreateDir) /* fCreate */); + if (FAILED(hrc)) + throw hrc; + } + + vrc = VDCreateDiff(hdd, + targetFormat.c_str(), + targetLocation.c_str(), + (task.mVariant & ~(MediumVariant_NoCreateDir | MediumVariant_Formatted | MediumVariant_VmdkESX | MediumVariant_VmdkRawDisk)) + | VD_IMAGE_FLAGS_DIFF, + NULL, + targetId.raw(), + id.raw(), + VD_OPEN_FLAGS_NORMAL | m->uOpenFlagsDef, + pTarget->m->vdImageIfaces, + task.mVDOperationIfaces); + if (RT_FAILURE(vrc)) + { + if (vrc == VERR_VD_INVALID_TYPE) + throw setErrorBoth(VBOX_E_FILE_ERROR, vrc, + tr("Parameters for creating the differencing medium storage unit '%s' are invalid%s"), + targetLocation.c_str(), i_vdError(vrc).c_str()); + else + throw setErrorBoth(VBOX_E_FILE_ERROR, vrc, + tr("Could not create the differencing medium storage unit '%s'%s"), + targetLocation.c_str(), i_vdError(vrc).c_str()); + } + + size = VDGetFileSize(hdd, VD_LAST_IMAGE); + logicalSize = VDGetSize(hdd, VD_LAST_IMAGE); + unsigned uImageFlags; + vrc = VDGetImageFlags(hdd, 0, &uImageFlags); + if (RT_SUCCESS(vrc)) + variant = (MediumVariant_T)uImageFlags; + } + catch (HRESULT hrcXcpt) { hrcTmp = hrcXcpt; } + + VDDestroy(hdd); + } + catch (HRESULT hrcXcpt) { hrcTmp = hrcXcpt; } + + MultiResult mrc(hrcTmp); + + if (SUCCEEDED(mrc)) + { + AutoWriteLock treeLock(m->pVirtualBox->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS); + + Assert(pTarget->m->pParent.isNull()); + + /* associate child with the parent, maximum depth was checked above */ + pTarget->i_setParent(this); + + /* diffs for immutable media are auto-reset by default */ + bool fAutoReset; + { + ComObjPtr<Medium> pBase = i_getBase(); + AutoReadLock block(pBase COMMA_LOCKVAL_SRC_POS); + fAutoReset = (pBase->m->type == MediumType_Immutable); + } + { + AutoWriteLock tlock(pTarget COMMA_LOCKVAL_SRC_POS); + pTarget->m->autoReset = fAutoReset; + } + + /* register with mVirtualBox as the last step and move to + * Created state only on success (leaving an orphan file is + * better than breaking media registry consistency) */ + ComObjPtr<Medium> pMedium; + mrc = m->pVirtualBox->i_registerMedium(pTarget, &pMedium, treeLock); + Assert(pTarget == pMedium); + + if (FAILED(mrc)) + /* break the parent association on failure to register */ + i_deparent(); + } + + AutoMultiWriteLock2 mediaLock(this, pTarget COMMA_LOCKVAL_SRC_POS); + + if (SUCCEEDED(mrc)) + { + pTarget->m->state = MediumState_Created; + + pTarget->m->size = size; + pTarget->m->logicalSize = logicalSize; + pTarget->m->variant = variant; + } + else + { + /* back to NotCreated on failure */ + pTarget->m->state = MediumState_NotCreated; + + pTarget->m->autoReset = false; + + /* reset UUID to prevent it from being reused next time */ + if (fGenerateUuid) + unconst(pTarget->m->id).clear(); + } + + // deregister the task registered in createDiffStorage() + Assert(m->numCreateDiffTasks != 0); + --m->numCreateDiffTasks; + + mediaLock.release(); + i_markRegistriesModified(); + if (task.isAsync()) + { + // in asynchronous mode, save settings now + m->pVirtualBox->i_saveModifiedRegistries(); + } + + /* Note that in sync mode, it's the caller's responsibility to + * unlock the medium. */ + + if (task.NotifyAboutChanges() && SUCCEEDED(mrc)) + { + m->pVirtualBox->i_onMediumConfigChanged(this); + m->pVirtualBox->i_onMediumRegistered(m->id, m->devType, TRUE); + } + + return mrc; +} + +/** + * Implementation code for the "merge" task. + * + * This task always gets started from Medium::mergeTo() and can run + * synchronously or asynchronously depending on the "wait" parameter passed to + * that function. If we run synchronously, the caller expects the medium + * registry modification to be set before returning; otherwise (in asynchronous + * mode), we save the settings ourselves. + * + * @param task + * @return + */ +HRESULT Medium::i_taskMergeHandler(Medium::MergeTask &task) +{ + /** @todo r=klaus The code below needs to be double checked with regard + * to lock order violations, it probably causes lock order issues related + * to the AutoCaller usage. */ + HRESULT hrcTmp = S_OK; + + const ComObjPtr<Medium> &pTarget = task.mTarget; + + try + { + if (!task.mParentForTarget.isNull()) + if (task.mParentForTarget->i_getDepth() >= SETTINGS_MEDIUM_DEPTH_MAX) + { + AutoReadLock plock(task.mParentForTarget COMMA_LOCKVAL_SRC_POS); + throw setError(VBOX_E_INVALID_OBJECT_STATE, + tr("Cannot merge image for medium '%s', because it exceeds the medium tree depth limit. Please merge some images which you no longer need"), + task.mParentForTarget->m->strLocationFull.c_str()); + } + + // Resize target to source size, if possible. Otherwise throw an error. + // It's offline resizing. Online resizing will be called in the + // SessionMachine::onlineMergeMedium. + + uint64_t sourceSize = 0; + Utf8Str sourceName; + { + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + sourceSize = i_getLogicalSize(); + sourceName = i_getName(); + } + uint64_t targetSize = 0; + Utf8Str targetName; + { + AutoReadLock alock(pTarget COMMA_LOCKVAL_SRC_POS); + targetSize = pTarget->i_getLogicalSize(); + targetName = pTarget->i_getName(); + } + + //reducing vm disks are not implemented yet + if (sourceSize > targetSize) + { + if (i_isMediumFormatFile()) + { + /// @todo r=klaus Can this use the standard code for creating a medium lock list? + // Have to make own lock list, because "resize" method resizes the last image + // in the lock chain only. The lock chain is already in the task.mpMediumLockList, + // so just make new lock list based on it, with the right last medium. The own + // lock list skips double locking and therefore does not affect the general lock + // state after the "resize" method. + MediumLockList* pMediumLockListForResize = new MediumLockList(); + + for (MediumLockList::Base::iterator it = task.mpMediumLockList->GetBegin(); + it != task.mpMediumLockList->GetEnd(); + ++it) + { + ComObjPtr<Medium> pMedium = it->GetMedium(); + pMediumLockListForResize->Append(pMedium, pMedium->m->state == MediumState_LockedWrite); + if (pMedium == pTarget) + break; + } + + // just to switch internal state of the lock list to avoid errors during list deletion, + // because all media in the list already locked by task.mpMediumLockList + HRESULT hrc = pMediumLockListForResize->Lock(true /* fSkipOverLockedMedia */); + if (FAILED(hrc)) + { + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + delete pMediumLockListForResize; + throw setError(hrc, + tr("Failed to lock the medium '%s' to resize before merge"), + targetName.c_str()); + } + + ComObjPtr<Progress> pProgress(task.GetProgressObject()); + hrc = pTarget->i_resize(sourceSize, pMediumLockListForResize, &pProgress, true, false); + if (FAILED(hrc)) + { + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + delete pMediumLockListForResize; + throw setError(hrc, + tr("Failed to set size of '%s' to size of '%s'"), + targetName.c_str(), sourceName.c_str()); + } + delete pMediumLockListForResize; + } + else + { + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + throw setError(VBOX_E_NOT_SUPPORTED, + tr("Sizes of '%s' and '%s' are different and medium format does not support resing"), + sourceName.c_str(), targetName.c_str()); + } + } + + task.GetProgressObject()->SetNextOperation(BstrFmt(tr("Merging medium '%s' to '%s'"), + i_getName().c_str(), + targetName.c_str()).raw(), + 1); + + PVDISK hdd; + int vrc = VDCreate(m->vdDiskIfaces, i_convertDeviceType(), &hdd); + ComAssertRCThrow(vrc, E_FAIL); + + try + { + // Similar code appears in SessionMachine::onlineMergeMedium, so + // if you make any changes below check whether they are applicable + // in that context as well. + + unsigned uTargetIdx = VD_LAST_IMAGE; + unsigned uSourceIdx = VD_LAST_IMAGE; + /* Open all media in the chain. */ + MediumLockList::Base::iterator lockListBegin = + task.mpMediumLockList->GetBegin(); + MediumLockList::Base::iterator lockListEnd = + task.mpMediumLockList->GetEnd(); + unsigned i = 0; + for (MediumLockList::Base::iterator it = lockListBegin; + it != lockListEnd; + ++it) + { + MediumLock &mediumLock = *it; + const ComObjPtr<Medium> &pMedium = mediumLock.GetMedium(); + + if (pMedium == this) + uSourceIdx = i; + else if (pMedium == pTarget) + uTargetIdx = i; + + AutoReadLock alock(pMedium COMMA_LOCKVAL_SRC_POS); + + /* + * complex sanity (sane complexity) + * + * The current medium must be in the Deleting (medium is merged) + * or LockedRead (parent medium) state if it is not the target. + * If it is the target it must be in the LockedWrite state. + */ + Assert( ( pMedium != pTarget + && ( pMedium->m->state == MediumState_Deleting + || pMedium->m->state == MediumState_LockedRead)) + || ( pMedium == pTarget + && pMedium->m->state == MediumState_LockedWrite)); + /* + * Medium must be the target, in the LockedRead state + * or Deleting state where it is not allowed to be attached + * to a virtual machine. + */ + Assert( pMedium == pTarget + || pMedium->m->state == MediumState_LockedRead + || ( pMedium->m->backRefs.size() == 0 + && pMedium->m->state == MediumState_Deleting)); + /* The source medium must be in Deleting state. */ + Assert( pMedium != this + || pMedium->m->state == MediumState_Deleting); + + unsigned uOpenFlags = VD_OPEN_FLAGS_NORMAL; + + if ( pMedium->m->state == MediumState_LockedRead + || pMedium->m->state == MediumState_Deleting) + uOpenFlags = VD_OPEN_FLAGS_READONLY; + if (pMedium->m->type == MediumType_Shareable) + uOpenFlags |= VD_OPEN_FLAGS_SHAREABLE; + + /* Open the medium */ + vrc = VDOpen(hdd, + pMedium->m->strFormat.c_str(), + pMedium->m->strLocationFull.c_str(), + uOpenFlags | m->uOpenFlagsDef, + pMedium->m->vdImageIfaces); + if (RT_FAILURE(vrc)) + throw vrc; + + i++; + } + + ComAssertThrow( uSourceIdx != VD_LAST_IMAGE + && uTargetIdx != VD_LAST_IMAGE, E_FAIL); + + vrc = VDMerge(hdd, uSourceIdx, uTargetIdx, + task.mVDOperationIfaces); + if (RT_FAILURE(vrc)) + throw vrc; + + /* update parent UUIDs */ + if (!task.mfMergeForward) + { + /* we need to update UUIDs of all source's children + * which cannot be part of the container at once so + * add each one in there individually */ + if (task.mpChildrenToReparent) + { + MediumLockList::Base::iterator childrenBegin = task.mpChildrenToReparent->GetBegin(); + MediumLockList::Base::iterator childrenEnd = task.mpChildrenToReparent->GetEnd(); + for (MediumLockList::Base::iterator it = childrenBegin; + it != childrenEnd; + ++it) + { + Medium *pMedium = it->GetMedium(); + /* VD_OPEN_FLAGS_INFO since UUID is wrong yet */ + vrc = VDOpen(hdd, + pMedium->m->strFormat.c_str(), + pMedium->m->strLocationFull.c_str(), + VD_OPEN_FLAGS_INFO | m->uOpenFlagsDef, + pMedium->m->vdImageIfaces); + if (RT_FAILURE(vrc)) + throw vrc; + + vrc = VDSetParentUuid(hdd, VD_LAST_IMAGE, + pTarget->m->id.raw()); + if (RT_FAILURE(vrc)) + throw vrc; + + vrc = VDClose(hdd, false /* fDelete */); + if (RT_FAILURE(vrc)) + throw vrc; + } + } + } + } + catch (HRESULT hrcXcpt) { hrcTmp = hrcXcpt; } + catch (int aVRC) + { + hrcTmp = setErrorBoth(VBOX_E_FILE_ERROR, aVRC, + tr("Could not merge the medium '%s' to '%s'%s"), + m->strLocationFull.c_str(), + pTarget->m->strLocationFull.c_str(), + i_vdError(aVRC).c_str()); + } + + VDDestroy(hdd); + } + catch (HRESULT hrcXcpt) { hrcTmp = hrcXcpt; } + + ErrorInfoKeeper eik; + MultiResult mrc(hrcTmp); + HRESULT hrc2; + + std::set<ComObjPtr<Medium> > pMediaForNotify; + std::map<Guid, DeviceType_T> uIdsForNotify; + + if (SUCCEEDED(mrc)) + { + /* all media but the target were successfully deleted by + * VDMerge; reparent the last one and uninitialize deleted media. */ + + AutoWriteLock treeLock(m->pVirtualBox->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS); + + if (task.mfMergeForward) + { + /* first, unregister the target since it may become a base + * medium which needs re-registration */ + hrc2 = m->pVirtualBox->i_unregisterMedium(pTarget); + AssertComRC(hrc2); + + /* then, reparent it and disconnect the deleted branch at both ends + * (chain->parent() is source's parent). Depth check above. */ + pTarget->i_deparent(); + pTarget->i_setParent(task.mParentForTarget); + if (task.mParentForTarget) + { + i_deparent(); + if (task.NotifyAboutChanges()) + pMediaForNotify.insert(task.mParentForTarget); + } + + /* then, register again */ + ComObjPtr<Medium> pMedium; + hrc2 = m->pVirtualBox->i_registerMedium(pTarget, &pMedium, treeLock); + AssertComRC(hrc2); + } + else + { + Assert(pTarget->i_getChildren().size() == 1); + Medium *targetChild = pTarget->i_getChildren().front(); + + /* disconnect the deleted branch at the elder end */ + targetChild->i_deparent(); + + /* reparent source's children and disconnect the deleted + * branch at the younger end */ + if (task.mpChildrenToReparent) + { + /* obey {parent,child} lock order */ + AutoWriteLock sourceLock(this COMMA_LOCKVAL_SRC_POS); + + MediumLockList::Base::iterator childrenBegin = task.mpChildrenToReparent->GetBegin(); + MediumLockList::Base::iterator childrenEnd = task.mpChildrenToReparent->GetEnd(); + for (MediumLockList::Base::iterator it = childrenBegin; + it != childrenEnd; + ++it) + { + Medium *pMedium = it->GetMedium(); + AutoWriteLock childLock(pMedium COMMA_LOCKVAL_SRC_POS); + + pMedium->i_deparent(); // removes pMedium from source + // no depth check, reduces depth + pMedium->i_setParent(pTarget); + + if (task.NotifyAboutChanges()) + pMediaForNotify.insert(pMedium); + } + } + pMediaForNotify.insert(pTarget); + } + + /* unregister and uninitialize all media removed by the merge */ + MediumLockList::Base::iterator lockListBegin = + task.mpMediumLockList->GetBegin(); + MediumLockList::Base::iterator lockListEnd = + task.mpMediumLockList->GetEnd(); + for (MediumLockList::Base::iterator it = lockListBegin; + it != lockListEnd; + ) + { + MediumLock &mediumLock = *it; + /* Create a real copy of the medium pointer, as the medium + * lock deletion below would invalidate the referenced object. */ + const ComObjPtr<Medium> pMedium = mediumLock.GetMedium(); + + /* The target and all media not merged (readonly) are skipped */ + if ( pMedium == pTarget + || pMedium->m->state == MediumState_LockedRead) + { + ++it; + continue; + } + + uIdsForNotify[pMedium->i_getId()] = pMedium->i_getDeviceType(); + hrc2 = pMedium->m->pVirtualBox->i_unregisterMedium(pMedium); + AssertComRC(hrc2); + + /* now, uninitialize the deleted medium (note that + * due to the Deleting state, uninit() will not touch + * the parent-child relationship so we need to + * uninitialize each disk individually) */ + + /* note that the operation initiator medium (which is + * normally also the source medium) is a special case + * -- there is one more caller added by Task to it which + * we must release. Also, if we are in sync mode, the + * caller may still hold an AutoCaller instance for it + * and therefore we cannot uninit() it (it's therefore + * the caller's responsibility) */ + if (pMedium == this) + { + Assert(i_getChildren().size() == 0); + Assert(m->backRefs.size() == 0); + task.mMediumCaller.release(); + } + + /* Delete the medium lock list entry, which also releases the + * caller added by MergeChain before uninit() and updates the + * iterator to point to the right place. */ + hrc2 = task.mpMediumLockList->RemoveByIterator(it); + AssertComRC(hrc2); + + if (task.isAsync() || pMedium != this) + { + treeLock.release(); + pMedium->uninit(); + treeLock.acquire(); + } + } + } + + i_markRegistriesModified(); + if (task.isAsync()) + { + // in asynchronous mode, save settings now + eik.restore(); + m->pVirtualBox->i_saveModifiedRegistries(); + eik.fetch(); + } + + if (FAILED(mrc)) + { + /* Here we come if either VDMerge() failed (in which case we + * assume that it tried to do everything to make a further + * retry possible -- e.g. not deleted intermediate media + * and so on) or VirtualBox::saveRegistries() failed (where we + * should have the original tree but with intermediate storage + * units deleted by VDMerge()). We have to only restore states + * (through the MergeChain dtor) unless we are run synchronously + * in which case it's the responsibility of the caller as stated + * in the mergeTo() docs. The latter also implies that we + * don't own the merge chain, so release it in this case. */ + if (task.isAsync()) + i_cancelMergeTo(task.mpChildrenToReparent, task.mpMediumLockList); + } + else if (task.NotifyAboutChanges()) + { + for (std::set<ComObjPtr<Medium> >::const_iterator it = pMediaForNotify.begin(); + it != pMediaForNotify.end(); + ++it) + { + if (it->isNotNull()) + m->pVirtualBox->i_onMediumConfigChanged(*it); + } + for (std::map<Guid, DeviceType_T>::const_iterator it = uIdsForNotify.begin(); + it != uIdsForNotify.end(); + ++it) + { + m->pVirtualBox->i_onMediumRegistered(it->first, it->second, FALSE); + } + } + + return mrc; +} + +/** + * Implementation code for the "clone" task. + * + * This only gets started from Medium::CloneTo() and always runs asynchronously. + * As a result, we always save the VirtualBox.xml file when we're done here. + * + * @param task + * @return + */ +HRESULT Medium::i_taskCloneHandler(Medium::CloneTask &task) +{ + /** @todo r=klaus The code below needs to be double checked with regard + * to lock order violations, it probably causes lock order issues related + * to the AutoCaller usage. */ + HRESULT hrcTmp = S_OK; + + const ComObjPtr<Medium> &pTarget = task.mTarget; + const ComObjPtr<Medium> &pParent = task.mParent; + + bool fCreatingTarget = false; + + uint64_t size = 0, logicalSize = 0; + MediumVariant_T variant = MediumVariant_Standard; + bool fGenerateUuid = false; + + try + { + if (!pParent.isNull()) + { + + if (pParent->i_getDepth() >= SETTINGS_MEDIUM_DEPTH_MAX) + { + AutoReadLock plock(pParent COMMA_LOCKVAL_SRC_POS); + throw setError(VBOX_E_INVALID_OBJECT_STATE, + tr("Cannot clone image for medium '%s', because it exceeds the medium tree depth limit. Please merge some images which you no longer need"), + pParent->m->strLocationFull.c_str()); + } + } + + /* Lock all in {parent,child} order. The lock is also used as a + * signal from the task initiator (which releases it only after + * RTThreadCreate()) that we can start the job. */ + AutoMultiWriteLock3 thisLock(this, pTarget, pParent COMMA_LOCKVAL_SRC_POS); + + fCreatingTarget = pTarget->m->state == MediumState_Creating; + + /* The object may request a specific UUID (through a special form of + * the moveTo() argument). Otherwise we have to generate it */ + Guid targetId = pTarget->m->id; + + fGenerateUuid = targetId.isZero(); + if (fGenerateUuid) + { + targetId.create(); + /* VirtualBox::registerMedium() will need UUID */ + unconst(pTarget->m->id) = targetId; + } + + PVDISK hdd; + int vrc = VDCreate(m->vdDiskIfaces, i_convertDeviceType(), &hdd); + ComAssertRCThrow(vrc, E_FAIL); + + try + { + /* Open all media in the source chain. */ + MediumLockList::Base::const_iterator sourceListBegin = + task.mpSourceMediumLockList->GetBegin(); + MediumLockList::Base::const_iterator sourceListEnd = + task.mpSourceMediumLockList->GetEnd(); + for (MediumLockList::Base::const_iterator it = sourceListBegin; + it != sourceListEnd; + ++it) + { + const MediumLock &mediumLock = *it; + const ComObjPtr<Medium> &pMedium = mediumLock.GetMedium(); + AutoReadLock alock(pMedium COMMA_LOCKVAL_SRC_POS); + + /* sanity check */ + Assert(pMedium->m->state == MediumState_LockedRead); + + /** Open all media in read-only mode. */ + vrc = VDOpen(hdd, + pMedium->m->strFormat.c_str(), + pMedium->m->strLocationFull.c_str(), + VD_OPEN_FLAGS_READONLY | m->uOpenFlagsDef, + pMedium->m->vdImageIfaces); + if (RT_FAILURE(vrc)) + throw setErrorBoth(VBOX_E_FILE_ERROR, vrc, + tr("Could not open the medium storage unit '%s'%s"), + pMedium->m->strLocationFull.c_str(), + i_vdError(vrc).c_str()); + } + + Utf8Str targetFormat(pTarget->m->strFormat); + Utf8Str targetLocation(pTarget->m->strLocationFull); + uint64_t capabilities = pTarget->m->formatObj->i_getCapabilities(); + + Assert( pTarget->m->state == MediumState_Creating + || pTarget->m->state == MediumState_LockedWrite); + Assert(m->state == MediumState_LockedRead); + Assert( pParent.isNull() + || pParent->m->state == MediumState_LockedRead); + + /* unlock before the potentially lengthy operation */ + thisLock.release(); + + /* ensure the target directory exists */ + if (capabilities & MediumFormatCapabilities_File) + { + HRESULT hrc = VirtualBox::i_ensureFilePathExists(targetLocation, + !(task.mVariant & MediumVariant_NoCreateDir) /* fCreate */); + if (FAILED(hrc)) + throw hrc; + } + + PVDISK targetHdd; + vrc = VDCreate(m->vdDiskIfaces, i_convertDeviceType(), &targetHdd); + ComAssertRCThrow(vrc, E_FAIL); + + try + { + /* Open all media in the target chain. */ + MediumLockList::Base::const_iterator targetListBegin = + task.mpTargetMediumLockList->GetBegin(); + MediumLockList::Base::const_iterator targetListEnd = + task.mpTargetMediumLockList->GetEnd(); + for (MediumLockList::Base::const_iterator it = targetListBegin; + it != targetListEnd; + ++it) + { + const MediumLock &mediumLock = *it; + const ComObjPtr<Medium> &pMedium = mediumLock.GetMedium(); + + /* If the target medium is not created yet there's no + * reason to open it. */ + if (pMedium == pTarget && fCreatingTarget) + continue; + + AutoReadLock alock(pMedium COMMA_LOCKVAL_SRC_POS); + + /* sanity check */ + Assert( pMedium->m->state == MediumState_LockedRead + || pMedium->m->state == MediumState_LockedWrite); + + unsigned uOpenFlags = VD_OPEN_FLAGS_NORMAL; + if (pMedium->m->state != MediumState_LockedWrite) + uOpenFlags = VD_OPEN_FLAGS_READONLY; + if (pMedium->m->type == MediumType_Shareable) + uOpenFlags |= VD_OPEN_FLAGS_SHAREABLE; + + /* Open all media in appropriate mode. */ + vrc = VDOpen(targetHdd, + pMedium->m->strFormat.c_str(), + pMedium->m->strLocationFull.c_str(), + uOpenFlags | m->uOpenFlagsDef, + pMedium->m->vdImageIfaces); + if (RT_FAILURE(vrc)) + throw setErrorBoth(VBOX_E_FILE_ERROR, vrc, + tr("Could not open the medium storage unit '%s'%s"), + pMedium->m->strLocationFull.c_str(), + i_vdError(vrc).c_str()); + } + + /* target isn't locked, but no changing data is accessed */ + if (task.midxSrcImageSame == UINT32_MAX) + { + vrc = VDCopy(hdd, + VD_LAST_IMAGE, + targetHdd, + targetFormat.c_str(), + (fCreatingTarget) ? targetLocation.c_str() : (char *)NULL, + false /* fMoveByRename */, + task.mTargetLogicalSize /* cbSize */, + task.mVariant & ~(MediumVariant_NoCreateDir | MediumVariant_Formatted | MediumVariant_VmdkESX | MediumVariant_VmdkRawDisk), + targetId.raw(), + VD_OPEN_FLAGS_NORMAL | m->uOpenFlagsDef, + NULL /* pVDIfsOperation */, + pTarget->m->vdImageIfaces, + task.mVDOperationIfaces); + } + else + { + vrc = VDCopyEx(hdd, + VD_LAST_IMAGE, + targetHdd, + targetFormat.c_str(), + (fCreatingTarget) ? targetLocation.c_str() : (char *)NULL, + false /* fMoveByRename */, + task.mTargetLogicalSize /* cbSize */, + task.midxSrcImageSame, + task.midxDstImageSame, + task.mVariant & ~(MediumVariant_NoCreateDir | MediumVariant_Formatted | MediumVariant_VmdkESX | MediumVariant_VmdkRawDisk), + targetId.raw(), + VD_OPEN_FLAGS_NORMAL | m->uOpenFlagsDef, + NULL /* pVDIfsOperation */, + pTarget->m->vdImageIfaces, + task.mVDOperationIfaces); + } + if (RT_FAILURE(vrc)) + throw setErrorBoth(VBOX_E_FILE_ERROR, vrc, + tr("Could not create the clone medium '%s'%s"), + targetLocation.c_str(), i_vdError(vrc).c_str()); + + size = VDGetFileSize(targetHdd, VD_LAST_IMAGE); + logicalSize = VDGetSize(targetHdd, VD_LAST_IMAGE); + unsigned uImageFlags; + vrc = VDGetImageFlags(targetHdd, 0, &uImageFlags); + if (RT_SUCCESS(vrc)) + variant = (MediumVariant_T)uImageFlags; + } + catch (HRESULT hrcXcpt) { hrcTmp = hrcXcpt; } + + VDDestroy(targetHdd); + } + catch (HRESULT hrcXcpt) { hrcTmp = hrcXcpt; } + + VDDestroy(hdd); + } + catch (HRESULT hrcXcpt) { hrcTmp = hrcXcpt; } + + ErrorInfoKeeper eik; + MultiResult mrc(hrcTmp); + + /* Only do the parent changes for newly created media. */ + if (SUCCEEDED(mrc) && fCreatingTarget) + { + /* we set m->pParent & children() */ + AutoWriteLock treeLock(m->pVirtualBox->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS); + + Assert(pTarget->m->pParent.isNull()); + + if (pParent) + { + /* Associate the clone with the parent and deassociate + * from VirtualBox. Depth check above. */ + pTarget->i_setParent(pParent); + + /* register with mVirtualBox as the last step and move to + * Created state only on success (leaving an orphan file is + * better than breaking media registry consistency) */ + eik.restore(); + ComObjPtr<Medium> pMedium; + mrc = pParent->m->pVirtualBox->i_registerMedium(pTarget, &pMedium, + treeLock); + Assert( FAILED(mrc) + || pTarget == pMedium); + eik.fetch(); + + if (FAILED(mrc)) + /* break parent association on failure to register */ + pTarget->i_deparent(); // removes target from parent + } + else + { + /* just register */ + eik.restore(); + ComObjPtr<Medium> pMedium; + mrc = m->pVirtualBox->i_registerMedium(pTarget, &pMedium, + treeLock); + Assert( FAILED(mrc) + || pTarget == pMedium); + eik.fetch(); + } + } + + if (fCreatingTarget) + { + AutoWriteLock mLock(pTarget COMMA_LOCKVAL_SRC_POS); + + if (SUCCEEDED(mrc)) + { + pTarget->m->state = MediumState_Created; + + pTarget->m->size = size; + pTarget->m->logicalSize = logicalSize; + pTarget->m->variant = variant; + } + else + { + /* back to NotCreated on failure */ + pTarget->m->state = MediumState_NotCreated; + + /* reset UUID to prevent it from being reused next time */ + if (fGenerateUuid) + unconst(pTarget->m->id).clear(); + } + } + + /* Copy any filter related settings over to the target. */ + if (SUCCEEDED(mrc)) + { + /* Copy any filter related settings over. */ + ComObjPtr<Medium> pBase = i_getBase(); + ComObjPtr<Medium> pTargetBase = pTarget->i_getBase(); + std::vector<com::Utf8Str> aFilterPropNames; + std::vector<com::Utf8Str> aFilterPropValues; + mrc = pBase->i_getFilterProperties(aFilterPropNames, aFilterPropValues); + if (SUCCEEDED(mrc)) + { + /* Go through the properties and add them to the target medium. */ + for (unsigned idx = 0; idx < aFilterPropNames.size(); idx++) + { + mrc = pTargetBase->i_setPropertyDirect(aFilterPropNames[idx], aFilterPropValues[idx]); + if (FAILED(mrc)) break; + } + + // now, at the end of this task (always asynchronous), save the settings + if (SUCCEEDED(mrc)) + { + // save the settings + i_markRegistriesModified(); + /* collect multiple errors */ + eik.restore(); + m->pVirtualBox->i_saveModifiedRegistries(); + eik.fetch(); + + if (task.NotifyAboutChanges()) + { + if (!fCreatingTarget) + { + if (!aFilterPropNames.empty()) + m->pVirtualBox->i_onMediumConfigChanged(pTargetBase); + if (pParent) + m->pVirtualBox->i_onMediumConfigChanged(pParent); + } + else + { + m->pVirtualBox->i_onMediumRegistered(pTarget->i_getId(), pTarget->i_getDeviceType(), TRUE); + } + } + } + } + } + + /* Everything is explicitly unlocked when the task exits, + * as the task destruction also destroys the source chain. */ + + /* Make sure the source chain is released early. It could happen + * that we get a deadlock in Appliance::Import when Medium::Close + * is called & the source chain is released at the same time. */ + task.mpSourceMediumLockList->Clear(); + + return mrc; +} + +/** + * Implementation code for the "move" task. + * + * This only gets started from Medium::MoveTo() and always + * runs asynchronously. + * + * @param task + * @return + */ +HRESULT Medium::i_taskMoveHandler(Medium::MoveTask &task) +{ + LogFlowFuncEnter(); + HRESULT hrcOut = S_OK; + + /* pTarget is equal "this" in our case */ + const ComObjPtr<Medium> &pTarget = task.mMedium; + + uint64_t size = 0; NOREF(size); + uint64_t logicalSize = 0; NOREF(logicalSize); + MediumVariant_T variant = MediumVariant_Standard; NOREF(variant); + + /* + * it's exactly moving, not cloning + */ + if (!i_isMoveOperation(pTarget)) + { + LogFlowFunc(("LEAVE: hrc=VBOX_E_FILE_ERROR (early)\n")); + return setError(VBOX_E_FILE_ERROR, + tr("Wrong preconditions for moving the medium %s"), + pTarget->m->strLocationFull.c_str()); + } + + try + { + /* Lock all in {parent,child} order. The lock is also used as a + * signal from the task initiator (which releases it only after + * RTThreadCreate()) that we can start the job. */ + + AutoWriteLock thisLock(this COMMA_LOCKVAL_SRC_POS); + + PVDISK hdd; + int vrc = VDCreate(m->vdDiskIfaces, i_convertDeviceType(), &hdd); + ComAssertRCThrow(vrc, E_FAIL); + + try + { + /* Open all media in the source chain. */ + MediumLockList::Base::const_iterator sourceListBegin = + task.mpMediumLockList->GetBegin(); + MediumLockList::Base::const_iterator sourceListEnd = + task.mpMediumLockList->GetEnd(); + for (MediumLockList::Base::const_iterator it = sourceListBegin; + it != sourceListEnd; + ++it) + { + const MediumLock &mediumLock = *it; + const ComObjPtr<Medium> &pMedium = mediumLock.GetMedium(); + AutoWriteLock alock(pMedium COMMA_LOCKVAL_SRC_POS); + + /* sanity check */ + Assert(pMedium->m->state == MediumState_LockedWrite); + + vrc = VDOpen(hdd, + pMedium->m->strFormat.c_str(), + pMedium->m->strLocationFull.c_str(), + VD_OPEN_FLAGS_NORMAL, + pMedium->m->vdImageIfaces); + if (RT_FAILURE(vrc)) + throw setErrorBoth(VBOX_E_FILE_ERROR, vrc, + tr("Could not open the medium storage unit '%s'%s"), + pMedium->m->strLocationFull.c_str(), + i_vdError(vrc).c_str()); + } + + /* we can directly use pTarget->m->"variables" but for better reading we use local copies */ + Guid targetId = pTarget->m->id; + Utf8Str targetFormat(pTarget->m->strFormat); + uint64_t targetCapabilities = pTarget->m->formatObj->i_getCapabilities(); + + /* + * change target location + * m->strNewLocationFull has been set already together with m->fMoveThisMedium in + * i_preparationForMoving() + */ + Utf8Str targetLocation = i_getNewLocationForMoving(); + + /* unlock before the potentially lengthy operation */ + thisLock.release(); + + /* ensure the target directory exists */ + if (targetCapabilities & MediumFormatCapabilities_File) + { + HRESULT hrc = VirtualBox::i_ensureFilePathExists(targetLocation, + !(task.mVariant & MediumVariant_NoCreateDir) /* fCreate */); + if (FAILED(hrc)) + throw hrc; + } + + try + { + vrc = VDCopy(hdd, + VD_LAST_IMAGE, + hdd, + targetFormat.c_str(), + targetLocation.c_str(), + true /* fMoveByRename */, + 0 /* cbSize */, + VD_IMAGE_FLAGS_NONE, + targetId.raw(), + VD_OPEN_FLAGS_NORMAL, + NULL /* pVDIfsOperation */, + pTarget->m->vdImageIfaces, + task.mVDOperationIfaces); + if (RT_FAILURE(vrc)) + throw setErrorBoth(VBOX_E_FILE_ERROR, vrc, + tr("Could not move medium '%s'%s"), + targetLocation.c_str(), i_vdError(vrc).c_str()); + size = VDGetFileSize(hdd, VD_LAST_IMAGE); + logicalSize = VDGetSize(hdd, VD_LAST_IMAGE); + unsigned uImageFlags; + vrc = VDGetImageFlags(hdd, 0, &uImageFlags); + if (RT_SUCCESS(vrc)) + variant = (MediumVariant_T)uImageFlags; + + /* + * set current location, because VDCopy\VDCopyEx doesn't do it. + * also reset moving flag + */ + i_resetMoveOperationData(); + m->strLocationFull = targetLocation; + + } + catch (HRESULT hrcXcpt) { hrcOut = hrcXcpt; } + + } + catch (HRESULT hrcXcpt) { hrcOut = hrcXcpt; } + + VDDestroy(hdd); + } + catch (HRESULT hrcXcpt) { hrcOut = hrcXcpt; } + + ErrorInfoKeeper eik; + MultiResult mrc(hrcOut); + + // now, at the end of this task (always asynchronous), save the settings + if (SUCCEEDED(mrc)) + { + // save the settings + i_markRegistriesModified(); + /* collect multiple errors */ + eik.restore(); + m->pVirtualBox->i_saveModifiedRegistries(); + eik.fetch(); + } + + /* Everything is explicitly unlocked when the task exits, + * as the task destruction also destroys the source chain. */ + + task.mpMediumLockList->Clear(); + + if (task.NotifyAboutChanges() && SUCCEEDED(mrc)) + m->pVirtualBox->i_onMediumConfigChanged(this); + + LogFlowFunc(("LEAVE: mrc=%Rhrc\n", (HRESULT)mrc)); + return mrc; +} + +/** + * Implementation code for the "delete" task. + * + * This task always gets started from Medium::deleteStorage() and can run + * synchronously or asynchronously depending on the "wait" parameter passed to + * that function. + * + * @param task + * @return + */ +HRESULT Medium::i_taskDeleteHandler(Medium::DeleteTask &task) +{ + NOREF(task); + HRESULT hrc = S_OK; + + try + { + /* The lock is also used as a signal from the task initiator (which + * releases it only after RTThreadCreate()) that we can start the job */ + AutoWriteLock thisLock(this COMMA_LOCKVAL_SRC_POS); + + PVDISK hdd; + int vrc = VDCreate(m->vdDiskIfaces, i_convertDeviceType(), &hdd); + ComAssertRCThrow(vrc, E_FAIL); + + Utf8Str format(m->strFormat); + Utf8Str location(m->strLocationFull); + + /* unlock before the potentially lengthy operation */ + Assert(m->state == MediumState_Deleting); + thisLock.release(); + + try + { + vrc = VDOpen(hdd, + format.c_str(), + location.c_str(), + VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO | m->uOpenFlagsDef, + m->vdImageIfaces); + if (RT_SUCCESS(vrc)) + vrc = VDClose(hdd, true /* fDelete */); + + if (RT_FAILURE(vrc) && vrc != VERR_FILE_NOT_FOUND) + throw setErrorBoth(VBOX_E_FILE_ERROR, vrc, + tr("Could not delete the medium storage unit '%s'%s"), + location.c_str(), i_vdError(vrc).c_str()); + + } + catch (HRESULT hrcXcpt) { hrc = hrcXcpt; } + + VDDestroy(hdd); + } + catch (HRESULT hrcXcpt) { hrc = hrcXcpt; } + + AutoWriteLock thisLock(this COMMA_LOCKVAL_SRC_POS); + + /* go to the NotCreated state even on failure since the storage + * may have been already partially deleted and cannot be used any + * more. One will be able to manually re-open the storage if really + * needed to re-register it. */ + m->state = MediumState_NotCreated; + + /* Reset UUID to prevent Create* from reusing it again */ + com::Guid uOldId = m->id; + unconst(m->id).clear(); + + if (task.NotifyAboutChanges() && SUCCEEDED(hrc)) + { + if (m->pParent.isNotNull()) + m->pVirtualBox->i_onMediumConfigChanged(m->pParent); + m->pVirtualBox->i_onMediumRegistered(uOldId, m->devType, FALSE); + } + + return hrc; +} + +/** + * Implementation code for the "reset" task. + * + * This always gets started asynchronously from Medium::Reset(). + * + * @param task + * @return + */ +HRESULT Medium::i_taskResetHandler(Medium::ResetTask &task) +{ + HRESULT hrc = S_OK; + + uint64_t size = 0, logicalSize = 0; + MediumVariant_T variant = MediumVariant_Standard; + + try + { + /* The lock is also used as a signal from the task initiator (which + * releases it only after RTThreadCreate()) that we can start the job */ + AutoWriteLock thisLock(this COMMA_LOCKVAL_SRC_POS); + + /// @todo Below we use a pair of delete/create operations to reset + /// the diff contents but the most efficient way will of course be + /// to add a VDResetDiff() API call + + PVDISK hdd; + int vrc = VDCreate(m->vdDiskIfaces, i_convertDeviceType(), &hdd); + ComAssertRCThrow(vrc, E_FAIL); + + Guid id = m->id; + Utf8Str format(m->strFormat); + Utf8Str location(m->strLocationFull); + + Medium *pParent = m->pParent; + Guid parentId = pParent->m->id; + Utf8Str parentFormat(pParent->m->strFormat); + Utf8Str parentLocation(pParent->m->strLocationFull); + + Assert(m->state == MediumState_LockedWrite); + + /* unlock before the potentially lengthy operation */ + thisLock.release(); + + try + { + /* Open all media in the target chain but the last. */ + MediumLockList::Base::const_iterator targetListBegin = + task.mpMediumLockList->GetBegin(); + MediumLockList::Base::const_iterator targetListEnd = + task.mpMediumLockList->GetEnd(); + for (MediumLockList::Base::const_iterator it = targetListBegin; + it != targetListEnd; + ++it) + { + const MediumLock &mediumLock = *it; + const ComObjPtr<Medium> &pMedium = mediumLock.GetMedium(); + + AutoReadLock alock(pMedium COMMA_LOCKVAL_SRC_POS); + + /* sanity check, "this" is checked above */ + Assert( pMedium == this + || pMedium->m->state == MediumState_LockedRead); + + /* Open all media in appropriate mode. */ + vrc = VDOpen(hdd, + pMedium->m->strFormat.c_str(), + pMedium->m->strLocationFull.c_str(), + VD_OPEN_FLAGS_READONLY | m->uOpenFlagsDef, + pMedium->m->vdImageIfaces); + if (RT_FAILURE(vrc)) + throw setErrorBoth(VBOX_E_FILE_ERROR, vrc, + tr("Could not open the medium storage unit '%s'%s"), + pMedium->m->strLocationFull.c_str(), + i_vdError(vrc).c_str()); + + /* Done when we hit the media which should be reset */ + if (pMedium == this) + break; + } + + /* first, delete the storage unit */ + vrc = VDClose(hdd, true /* fDelete */); + if (RT_FAILURE(vrc)) + throw setErrorBoth(VBOX_E_FILE_ERROR, vrc, + tr("Could not delete the medium storage unit '%s'%s"), + location.c_str(), i_vdError(vrc).c_str()); + + /* next, create it again */ + vrc = VDOpen(hdd, + parentFormat.c_str(), + parentLocation.c_str(), + VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO | m->uOpenFlagsDef, + m->vdImageIfaces); + if (RT_FAILURE(vrc)) + throw setErrorBoth(VBOX_E_FILE_ERROR, vrc, + tr("Could not open the medium storage unit '%s'%s"), + parentLocation.c_str(), i_vdError(vrc).c_str()); + + vrc = VDCreateDiff(hdd, + format.c_str(), + location.c_str(), + /// @todo use the same medium variant as before + VD_IMAGE_FLAGS_NONE, + NULL, + id.raw(), + parentId.raw(), + VD_OPEN_FLAGS_NORMAL, + m->vdImageIfaces, + task.mVDOperationIfaces); + if (RT_FAILURE(vrc)) + { + if (vrc == VERR_VD_INVALID_TYPE) + throw setErrorBoth(VBOX_E_FILE_ERROR, vrc, + tr("Parameters for creating the differencing medium storage unit '%s' are invalid%s"), + location.c_str(), i_vdError(vrc).c_str()); + else + throw setErrorBoth(VBOX_E_FILE_ERROR, vrc, + tr("Could not create the differencing medium storage unit '%s'%s"), + location.c_str(), i_vdError(vrc).c_str()); + } + + size = VDGetFileSize(hdd, VD_LAST_IMAGE); + logicalSize = VDGetSize(hdd, VD_LAST_IMAGE); + unsigned uImageFlags; + vrc = VDGetImageFlags(hdd, 0, &uImageFlags); + if (RT_SUCCESS(vrc)) + variant = (MediumVariant_T)uImageFlags; + } + catch (HRESULT hrcXcpt) { hrc = hrcXcpt; } + + VDDestroy(hdd); + } + catch (HRESULT hrcXcpt) { hrc = hrcXcpt; } + + AutoWriteLock thisLock(this COMMA_LOCKVAL_SRC_POS); + + m->size = size; + m->logicalSize = logicalSize; + m->variant = variant; + + if (task.NotifyAboutChanges() && SUCCEEDED(hrc)) + m->pVirtualBox->i_onMediumConfigChanged(this); + + /* Everything is explicitly unlocked when the task exits, + * as the task destruction also destroys the media chain. */ + + return hrc; +} + +/** + * Implementation code for the "compact" task. + * + * @param task + * @return + */ +HRESULT Medium::i_taskCompactHandler(Medium::CompactTask &task) +{ + HRESULT hrc = S_OK; + + /* Lock all in {parent,child} order. The lock is also used as a + * signal from the task initiator (which releases it only after + * RTThreadCreate()) that we can start the job. */ + AutoWriteLock thisLock(this COMMA_LOCKVAL_SRC_POS); + + try + { + PVDISK hdd; + int vrc = VDCreate(m->vdDiskIfaces, i_convertDeviceType(), &hdd); + ComAssertRCThrow(vrc, E_FAIL); + + try + { + /* Open all media in the chain. */ + MediumLockList::Base::const_iterator mediumListBegin = + task.mpMediumLockList->GetBegin(); + MediumLockList::Base::const_iterator mediumListEnd = + task.mpMediumLockList->GetEnd(); + MediumLockList::Base::const_iterator mediumListLast = + mediumListEnd; + --mediumListLast; + for (MediumLockList::Base::const_iterator it = mediumListBegin; + it != mediumListEnd; + ++it) + { + const MediumLock &mediumLock = *it; + const ComObjPtr<Medium> &pMedium = mediumLock.GetMedium(); + AutoReadLock alock(pMedium COMMA_LOCKVAL_SRC_POS); + + /* sanity check */ + if (it == mediumListLast) + Assert(pMedium->m->state == MediumState_LockedWrite); + else + Assert(pMedium->m->state == MediumState_LockedRead); + + /* Open all media but last in read-only mode. Do not handle + * shareable media, as compaction and sharing are mutually + * exclusive. */ + vrc = VDOpen(hdd, + pMedium->m->strFormat.c_str(), + pMedium->m->strLocationFull.c_str(), + m->uOpenFlagsDef | (it == mediumListLast ? VD_OPEN_FLAGS_NORMAL : VD_OPEN_FLAGS_READONLY), + pMedium->m->vdImageIfaces); + if (RT_FAILURE(vrc)) + throw setErrorBoth(VBOX_E_FILE_ERROR, vrc, + tr("Could not open the medium storage unit '%s'%s"), + pMedium->m->strLocationFull.c_str(), + i_vdError(vrc).c_str()); + } + + Assert(m->state == MediumState_LockedWrite); + + Utf8Str location(m->strLocationFull); + + /* unlock before the potentially lengthy operation */ + thisLock.release(); + + vrc = VDCompact(hdd, VD_LAST_IMAGE, task.mVDOperationIfaces); + if (RT_FAILURE(vrc)) + { + if (vrc == VERR_NOT_SUPPORTED) + throw setErrorBoth(VBOX_E_NOT_SUPPORTED, vrc, + tr("Compacting is not yet supported for medium '%s'"), + location.c_str()); + else if (vrc == VERR_NOT_IMPLEMENTED) + throw setErrorBoth(E_NOTIMPL, vrc, + tr("Compacting is not implemented, medium '%s'"), + location.c_str()); + else + throw setErrorBoth(VBOX_E_FILE_ERROR, vrc, + tr("Could not compact medium '%s'%s"), + location.c_str(), + i_vdError(vrc).c_str()); + } + } + catch (HRESULT hrcXcpt) { hrc = hrcXcpt; } + + VDDestroy(hdd); + } + catch (HRESULT hrcXcpt) { hrc = hrcXcpt; } + + if (task.NotifyAboutChanges() && SUCCEEDED(hrc)) + m->pVirtualBox->i_onMediumConfigChanged(this); + + /* Everything is explicitly unlocked when the task exits, + * as the task destruction also destroys the media chain. */ + + return hrc; +} + +/** + * Implementation code for the "resize" task. + * + * @param task + * @return + */ +HRESULT Medium::i_taskResizeHandler(Medium::ResizeTask &task) +{ + HRESULT hrc = S_OK; + + uint64_t size = 0, logicalSize = 0; + + try + { + /* The lock is also used as a signal from the task initiator (which + * releases it only after RTThreadCreate()) that we can start the job */ + AutoWriteLock thisLock(this COMMA_LOCKVAL_SRC_POS); + + PVDISK hdd; + int vrc = VDCreate(m->vdDiskIfaces, i_convertDeviceType(), &hdd); + ComAssertRCThrow(vrc, E_FAIL); + + try + { + /* Open all media in the chain. */ + MediumLockList::Base::const_iterator mediumListBegin = + task.mpMediumLockList->GetBegin(); + MediumLockList::Base::const_iterator mediumListEnd = + task.mpMediumLockList->GetEnd(); + MediumLockList::Base::const_iterator mediumListLast = + mediumListEnd; + --mediumListLast; + for (MediumLockList::Base::const_iterator it = mediumListBegin; + it != mediumListEnd; + ++it) + { + const MediumLock &mediumLock = *it; + const ComObjPtr<Medium> &pMedium = mediumLock.GetMedium(); + AutoReadLock alock(pMedium COMMA_LOCKVAL_SRC_POS); + + /* sanity check */ + if (it == mediumListLast) + Assert(pMedium->m->state == MediumState_LockedWrite); + else + Assert(pMedium->m->state == MediumState_LockedRead || + // Allow resize the target image during mergeTo in case + // of direction from parent to child because all intermediate + // images are marked to MediumState_Deleting and will be + // destroyed after successful merge + pMedium->m->state == MediumState_Deleting); + + /* Open all media but last in read-only mode. Do not handle + * shareable media, as compaction and sharing are mutually + * exclusive. */ + vrc = VDOpen(hdd, + pMedium->m->strFormat.c_str(), + pMedium->m->strLocationFull.c_str(), + m->uOpenFlagsDef | (it == mediumListLast ? VD_OPEN_FLAGS_NORMAL : VD_OPEN_FLAGS_READONLY), + pMedium->m->vdImageIfaces); + if (RT_FAILURE(vrc)) + throw setErrorBoth(VBOX_E_FILE_ERROR, vrc, + tr("Could not open the medium storage unit '%s'%s"), + pMedium->m->strLocationFull.c_str(), + i_vdError(vrc).c_str()); + } + + Assert(m->state == MediumState_LockedWrite); + + Utf8Str location(m->strLocationFull); + + /* unlock before the potentially lengthy operation */ + thisLock.release(); + + VDGEOMETRY geo = {0, 0, 0}; /* auto */ + vrc = VDResize(hdd, task.mSize, &geo, &geo, task.mVDOperationIfaces); + if (RT_FAILURE(vrc)) + { + if (vrc == VERR_VD_SHRINK_NOT_SUPPORTED) + throw setErrorBoth(VBOX_E_NOT_SUPPORTED, vrc, + tr("Shrinking is not yet supported for medium '%s'"), + location.c_str()); + if (vrc == VERR_NOT_SUPPORTED) + throw setErrorBoth(VBOX_E_NOT_SUPPORTED, vrc, + tr("Resizing to new size %llu is not yet supported for medium '%s'"), + task.mSize, location.c_str()); + else if (vrc == VERR_NOT_IMPLEMENTED) + throw setErrorBoth(E_NOTIMPL, vrc, + tr("Resizing is not implemented, medium '%s'"), + location.c_str()); + else + throw setErrorBoth(VBOX_E_FILE_ERROR, vrc, + tr("Could not resize medium '%s'%s"), + location.c_str(), + i_vdError(vrc).c_str()); + } + size = VDGetFileSize(hdd, VD_LAST_IMAGE); + logicalSize = VDGetSize(hdd, VD_LAST_IMAGE); + } + catch (HRESULT hrcXcpt) { hrc = hrcXcpt; } + + VDDestroy(hdd); + } + catch (HRESULT hrcXcpt) { hrc = hrcXcpt; } + + if (SUCCEEDED(hrc)) + { + AutoWriteLock thisLock(this COMMA_LOCKVAL_SRC_POS); + m->size = size; + m->logicalSize = logicalSize; + + if (task.NotifyAboutChanges()) + m->pVirtualBox->i_onMediumConfigChanged(this); + } + + /* Everything is explicitly unlocked when the task exits, + * as the task destruction also destroys the media chain. */ + + return hrc; +} + +/** + * Implementation code for the "import" task. + * + * This only gets started from Medium::importFile() and always runs + * asynchronously. It potentially touches the media registry, so we + * always save the VirtualBox.xml file when we're done here. + * + * @param task + * @return + */ +HRESULT Medium::i_taskImportHandler(Medium::ImportTask &task) +{ + /** @todo r=klaus The code below needs to be double checked with regard + * to lock order violations, it probably causes lock order issues related + * to the AutoCaller usage. */ + HRESULT hrcTmp = S_OK; + + const ComObjPtr<Medium> &pParent = task.mParent; + + bool fCreatingTarget = false; + + uint64_t size = 0, logicalSize = 0; + MediumVariant_T variant = MediumVariant_Standard; + bool fGenerateUuid = false; + + try + { + if (!pParent.isNull()) + if (pParent->i_getDepth() >= SETTINGS_MEDIUM_DEPTH_MAX) + { + AutoReadLock plock(pParent COMMA_LOCKVAL_SRC_POS); + throw setError(VBOX_E_INVALID_OBJECT_STATE, + tr("Cannot import image for medium '%s', because it exceeds the medium tree depth limit. Please merge some images which you no longer need"), + pParent->m->strLocationFull.c_str()); + } + + /* Lock all in {parent,child} order. The lock is also used as a + * signal from the task initiator (which releases it only after + * RTThreadCreate()) that we can start the job. */ + AutoMultiWriteLock2 thisLock(this, pParent COMMA_LOCKVAL_SRC_POS); + + fCreatingTarget = m->state == MediumState_Creating; + + /* The object may request a specific UUID (through a special form of + * the moveTo() argument). Otherwise we have to generate it */ + Guid targetId = m->id; + + fGenerateUuid = targetId.isZero(); + if (fGenerateUuid) + { + targetId.create(); + /* VirtualBox::i_registerMedium() will need UUID */ + unconst(m->id) = targetId; + } + + + PVDISK hdd; + int vrc = VDCreate(m->vdDiskIfaces, i_convertDeviceType(), &hdd); + ComAssertRCThrow(vrc, E_FAIL); + + try + { + /* Open source medium. */ + vrc = VDOpen(hdd, + task.mFormat->i_getId().c_str(), + task.mFilename.c_str(), + VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_SEQUENTIAL | m->uOpenFlagsDef, + task.mVDImageIfaces); + if (RT_FAILURE(vrc)) + throw setErrorBoth(VBOX_E_FILE_ERROR, vrc, + tr("Could not open the medium storage unit '%s'%s"), + task.mFilename.c_str(), + i_vdError(vrc).c_str()); + + Utf8Str targetFormat(m->strFormat); + Utf8Str targetLocation(m->strLocationFull); + uint64_t capabilities = task.mFormat->i_getCapabilities(); + + Assert( m->state == MediumState_Creating + || m->state == MediumState_LockedWrite); + Assert( pParent.isNull() + || pParent->m->state == MediumState_LockedRead); + + /* unlock before the potentially lengthy operation */ + thisLock.release(); + + /* ensure the target directory exists */ + if (capabilities & MediumFormatCapabilities_File) + { + HRESULT hrc = VirtualBox::i_ensureFilePathExists(targetLocation, + !(task.mVariant & MediumVariant_NoCreateDir) /* fCreate */); + if (FAILED(hrc)) + throw hrc; + } + + PVDISK targetHdd; + vrc = VDCreate(m->vdDiskIfaces, i_convertDeviceType(), &targetHdd); + ComAssertRCThrow(vrc, E_FAIL); + + try + { + /* Open all media in the target chain. */ + MediumLockList::Base::const_iterator targetListBegin = + task.mpTargetMediumLockList->GetBegin(); + MediumLockList::Base::const_iterator targetListEnd = + task.mpTargetMediumLockList->GetEnd(); + for (MediumLockList::Base::const_iterator it = targetListBegin; + it != targetListEnd; + ++it) + { + const MediumLock &mediumLock = *it; + const ComObjPtr<Medium> &pMedium = mediumLock.GetMedium(); + + /* If the target medium is not created yet there's no + * reason to open it. */ + if (pMedium == this && fCreatingTarget) + continue; + + AutoReadLock alock(pMedium COMMA_LOCKVAL_SRC_POS); + + /* sanity check */ + Assert( pMedium->m->state == MediumState_LockedRead + || pMedium->m->state == MediumState_LockedWrite); + + unsigned uOpenFlags = VD_OPEN_FLAGS_NORMAL; + if (pMedium->m->state != MediumState_LockedWrite) + uOpenFlags = VD_OPEN_FLAGS_READONLY; + if (pMedium->m->type == MediumType_Shareable) + uOpenFlags |= VD_OPEN_FLAGS_SHAREABLE; + + /* Open all media in appropriate mode. */ + vrc = VDOpen(targetHdd, + pMedium->m->strFormat.c_str(), + pMedium->m->strLocationFull.c_str(), + uOpenFlags | m->uOpenFlagsDef, + pMedium->m->vdImageIfaces); + if (RT_FAILURE(vrc)) + throw setErrorBoth(VBOX_E_FILE_ERROR, vrc, + tr("Could not open the medium storage unit '%s'%s"), + pMedium->m->strLocationFull.c_str(), + i_vdError(vrc).c_str()); + } + + vrc = VDCopy(hdd, + VD_LAST_IMAGE, + targetHdd, + targetFormat.c_str(), + (fCreatingTarget) ? targetLocation.c_str() : (char *)NULL, + false /* fMoveByRename */, + 0 /* cbSize */, + task.mVariant & ~(MediumVariant_NoCreateDir | MediumVariant_Formatted | MediumVariant_VmdkESX | MediumVariant_VmdkRawDisk), + targetId.raw(), + VD_OPEN_FLAGS_NORMAL, + NULL /* pVDIfsOperation */, + m->vdImageIfaces, + task.mVDOperationIfaces); + if (RT_FAILURE(vrc)) + throw setErrorBoth(VBOX_E_FILE_ERROR, vrc, + tr("Could not create the imported medium '%s'%s"), + targetLocation.c_str(), i_vdError(vrc).c_str()); + + size = VDGetFileSize(targetHdd, VD_LAST_IMAGE); + logicalSize = VDGetSize(targetHdd, VD_LAST_IMAGE); + unsigned uImageFlags; + vrc = VDGetImageFlags(targetHdd, 0, &uImageFlags); + if (RT_SUCCESS(vrc)) + variant = (MediumVariant_T)uImageFlags; + } + catch (HRESULT hrcXcpt) { hrcTmp = hrcXcpt; } + + VDDestroy(targetHdd); + } + catch (HRESULT hrcXcpt) { hrcTmp = hrcXcpt; } + + VDDestroy(hdd); + } + catch (HRESULT hrcXcpt) { hrcTmp = hrcXcpt; } + + ErrorInfoKeeper eik; + MultiResult mrc(hrcTmp); + + /* Only do the parent changes for newly created media. */ + if (SUCCEEDED(mrc) && fCreatingTarget) + { + /* we set m->pParent & children() */ + AutoWriteLock treeLock(m->pVirtualBox->i_getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS); + + Assert(m->pParent.isNull()); + + if (pParent) + { + /* Associate the imported medium with the parent and deassociate + * from VirtualBox. Depth check above. */ + i_setParent(pParent); + + /* register with mVirtualBox as the last step and move to + * Created state only on success (leaving an orphan file is + * better than breaking media registry consistency) */ + eik.restore(); + ComObjPtr<Medium> pMedium; + mrc = pParent->m->pVirtualBox->i_registerMedium(this, &pMedium, + treeLock); + Assert(this == pMedium); + eik.fetch(); + + if (FAILED(mrc)) + /* break parent association on failure to register */ + this->i_deparent(); // removes target from parent + } + else + { + /* just register */ + eik.restore(); + ComObjPtr<Medium> pMedium; + mrc = m->pVirtualBox->i_registerMedium(this, &pMedium, treeLock); + Assert(this == pMedium); + eik.fetch(); + } + } + + if (fCreatingTarget) + { + AutoWriteLock mLock(this COMMA_LOCKVAL_SRC_POS); + + if (SUCCEEDED(mrc)) + { + m->state = MediumState_Created; + + m->size = size; + m->logicalSize = logicalSize; + m->variant = variant; + } + else + { + /* back to NotCreated on failure */ + m->state = MediumState_NotCreated; + + /* reset UUID to prevent it from being reused next time */ + if (fGenerateUuid) + unconst(m->id).clear(); + } + } + + // now, at the end of this task (always asynchronous), save the settings + { + // save the settings + i_markRegistriesModified(); + /* collect multiple errors */ + eik.restore(); + m->pVirtualBox->i_saveModifiedRegistries(); + eik.fetch(); + } + + /* Everything is explicitly unlocked when the task exits, + * as the task destruction also destroys the target chain. */ + + /* Make sure the target chain is released early, otherwise it can + * lead to deadlocks with concurrent IAppliance activities. */ + task.mpTargetMediumLockList->Clear(); + + if (task.NotifyAboutChanges() && SUCCEEDED(mrc)) + { + if (pParent) + m->pVirtualBox->i_onMediumConfigChanged(pParent); + if (fCreatingTarget) + m->pVirtualBox->i_onMediumConfigChanged(this); + else + m->pVirtualBox->i_onMediumRegistered(m->id, m->devType, TRUE); + } + + return mrc; +} + +/** + * Sets up the encryption settings for a filter. + */ +void Medium::i_taskEncryptSettingsSetup(MediumCryptoFilterSettings *pSettings, const char *pszCipher, + const char *pszKeyStore, const char *pszPassword, + bool fCreateKeyStore) +{ + pSettings->pszCipher = pszCipher; + pSettings->pszPassword = pszPassword; + pSettings->pszKeyStoreLoad = pszKeyStore; + pSettings->fCreateKeyStore = fCreateKeyStore; + pSettings->pbDek = NULL; + pSettings->cbDek = 0; + pSettings->vdFilterIfaces = NULL; + + pSettings->vdIfCfg.pfnAreKeysValid = i_vdCryptoConfigAreKeysValid; + pSettings->vdIfCfg.pfnQuerySize = i_vdCryptoConfigQuerySize; + pSettings->vdIfCfg.pfnQuery = i_vdCryptoConfigQuery; + pSettings->vdIfCfg.pfnQueryBytes = NULL; + + pSettings->vdIfCrypto.pfnKeyRetain = i_vdCryptoKeyRetain; + pSettings->vdIfCrypto.pfnKeyRelease = i_vdCryptoKeyRelease; + pSettings->vdIfCrypto.pfnKeyStorePasswordRetain = i_vdCryptoKeyStorePasswordRetain; + pSettings->vdIfCrypto.pfnKeyStorePasswordRelease = i_vdCryptoKeyStorePasswordRelease; + pSettings->vdIfCrypto.pfnKeyStoreSave = i_vdCryptoKeyStoreSave; + pSettings->vdIfCrypto.pfnKeyStoreReturnParameters = i_vdCryptoKeyStoreReturnParameters; + + int vrc = VDInterfaceAdd(&pSettings->vdIfCfg.Core, + "Medium::vdInterfaceCfgCrypto", + VDINTERFACETYPE_CONFIG, pSettings, + sizeof(VDINTERFACECONFIG), &pSettings->vdFilterIfaces); + AssertRC(vrc); + + vrc = VDInterfaceAdd(&pSettings->vdIfCrypto.Core, + "Medium::vdInterfaceCrypto", + VDINTERFACETYPE_CRYPTO, pSettings, + sizeof(VDINTERFACECRYPTO), &pSettings->vdFilterIfaces); + AssertRC(vrc); +} + +/** + * Implementation code for the "encrypt" task. + * + * @param task + * @return + */ +HRESULT Medium::i_taskEncryptHandler(Medium::EncryptTask &task) +{ +# ifndef VBOX_WITH_EXTPACK + RT_NOREF(task); +# endif + HRESULT hrc = S_OK; + + /* Lock all in {parent,child} order. The lock is also used as a + * signal from the task initiator (which releases it only after + * RTThreadCreate()) that we can start the job. */ + ComObjPtr<Medium> pBase = i_getBase(); + AutoWriteLock thisLock(this COMMA_LOCKVAL_SRC_POS); + + try + { +# ifdef VBOX_WITH_EXTPACK + ExtPackManager *pExtPackManager = m->pVirtualBox->i_getExtPackManager(); + if (pExtPackManager->i_isExtPackUsable(ORACLE_PUEL_EXTPACK_NAME)) + { + /* Load the plugin */ + Utf8Str strPlugin; + hrc = pExtPackManager->i_getLibraryPathForExtPack(g_szVDPlugin, ORACLE_PUEL_EXTPACK_NAME, &strPlugin); + if (SUCCEEDED(hrc)) + { + int vrc = VDPluginLoadFromFilename(strPlugin.c_str()); + if (RT_FAILURE(vrc)) + throw setErrorBoth(VBOX_E_NOT_SUPPORTED, vrc, + tr("Encrypting the image failed because the encryption plugin could not be loaded (%s)"), + i_vdError(vrc).c_str()); + } + else + throw setError(VBOX_E_NOT_SUPPORTED, + tr("Encryption is not supported because the extension pack '%s' is missing the encryption plugin (old extension pack installed?)"), + ORACLE_PUEL_EXTPACK_NAME); + } + else + throw setError(VBOX_E_NOT_SUPPORTED, + tr("Encryption is not supported because the extension pack '%s' is missing"), + ORACLE_PUEL_EXTPACK_NAME); + + PVDISK pDisk = NULL; + int vrc = VDCreate(m->vdDiskIfaces, i_convertDeviceType(), &pDisk); + ComAssertRCThrow(vrc, E_FAIL); + + MediumCryptoFilterSettings CryptoSettingsRead; + MediumCryptoFilterSettings CryptoSettingsWrite; + + void *pvBuf = NULL; + const char *pszPasswordNew = NULL; + try + { + /* Set up disk encryption filters. */ + if (task.mstrCurrentPassword.isEmpty()) + { + /* + * Query whether the medium property indicating that encryption is + * configured is existing. + */ + settings::StringsMap::iterator it = pBase->m->mapProperties.find("CRYPT/KeyStore"); + if (it != pBase->m->mapProperties.end()) + throw setError(VBOX_E_PASSWORD_INCORRECT, + tr("The password given for the encrypted image is incorrect")); + } + else + { + settings::StringsMap::iterator it = pBase->m->mapProperties.find("CRYPT/KeyStore"); + if (it == pBase->m->mapProperties.end()) + throw setError(VBOX_E_INVALID_OBJECT_STATE, + tr("The image is not configured for encryption")); + + i_taskEncryptSettingsSetup(&CryptoSettingsRead, NULL, it->second.c_str(), task.mstrCurrentPassword.c_str(), + false /* fCreateKeyStore */); + vrc = VDFilterAdd(pDisk, "CRYPT", VD_FILTER_FLAGS_READ, CryptoSettingsRead.vdFilterIfaces); + if (vrc == VERR_VD_PASSWORD_INCORRECT) + throw setError(VBOX_E_PASSWORD_INCORRECT, + tr("The password to decrypt the image is incorrect")); + else if (RT_FAILURE(vrc)) + throw setError(VBOX_E_INVALID_OBJECT_STATE, + tr("Failed to load the decryption filter: %s"), + i_vdError(vrc).c_str()); + } + + if (task.mstrCipher.isNotEmpty()) + { + if ( task.mstrNewPassword.isEmpty() + && task.mstrNewPasswordId.isEmpty() + && task.mstrCurrentPassword.isNotEmpty()) + { + /* An empty password and password ID will default to the current password. */ + pszPasswordNew = task.mstrCurrentPassword.c_str(); + } + else if (task.mstrNewPassword.isEmpty()) + throw setError(VBOX_E_OBJECT_NOT_FOUND, + tr("A password must be given for the image encryption")); + else if (task.mstrNewPasswordId.isEmpty()) + throw setError(VBOX_E_INVALID_OBJECT_STATE, + tr("A valid identifier for the password must be given")); + else + pszPasswordNew = task.mstrNewPassword.c_str(); + + i_taskEncryptSettingsSetup(&CryptoSettingsWrite, task.mstrCipher.c_str(), NULL, + pszPasswordNew, true /* fCreateKeyStore */); + vrc = VDFilterAdd(pDisk, "CRYPT", VD_FILTER_FLAGS_WRITE, CryptoSettingsWrite.vdFilterIfaces); + if (RT_FAILURE(vrc)) + throw setErrorBoth(VBOX_E_INVALID_OBJECT_STATE, vrc, + tr("Failed to load the encryption filter: %s"), + i_vdError(vrc).c_str()); + } + else if (task.mstrNewPasswordId.isNotEmpty() || task.mstrNewPassword.isNotEmpty()) + throw setError(VBOX_E_INVALID_OBJECT_STATE, + tr("The password and password identifier must be empty if the output should be unencrypted")); + + /* Open all media in the chain. */ + MediumLockList::Base::const_iterator mediumListBegin = + task.mpMediumLockList->GetBegin(); + MediumLockList::Base::const_iterator mediumListEnd = + task.mpMediumLockList->GetEnd(); + MediumLockList::Base::const_iterator mediumListLast = + mediumListEnd; + --mediumListLast; + for (MediumLockList::Base::const_iterator it = mediumListBegin; + it != mediumListEnd; + ++it) + { + const MediumLock &mediumLock = *it; + const ComObjPtr<Medium> &pMedium = mediumLock.GetMedium(); + AutoReadLock alock(pMedium COMMA_LOCKVAL_SRC_POS); + + Assert(pMedium->m->state == MediumState_LockedWrite); + + /* Open all media but last in read-only mode. Do not handle + * shareable media, as compaction and sharing are mutually + * exclusive. */ + vrc = VDOpen(pDisk, + pMedium->m->strFormat.c_str(), + pMedium->m->strLocationFull.c_str(), + m->uOpenFlagsDef | (it == mediumListLast ? VD_OPEN_FLAGS_NORMAL : VD_OPEN_FLAGS_READONLY), + pMedium->m->vdImageIfaces); + if (RT_FAILURE(vrc)) + throw setErrorBoth(VBOX_E_FILE_ERROR, vrc, + tr("Could not open the medium storage unit '%s'%s"), + pMedium->m->strLocationFull.c_str(), + i_vdError(vrc).c_str()); + } + + Assert(m->state == MediumState_LockedWrite); + + Utf8Str location(m->strLocationFull); + + /* unlock before the potentially lengthy operation */ + thisLock.release(); + + vrc = VDPrepareWithFilters(pDisk, task.mVDOperationIfaces); + if (RT_FAILURE(vrc)) + throw setErrorBoth(VBOX_E_FILE_ERROR, vrc, + tr("Could not prepare disk images for encryption (%Rrc): %s"), + vrc, i_vdError(vrc).c_str()); + + thisLock.acquire(); + /* If everything went well set the new key store. */ + settings::StringsMap::iterator it = pBase->m->mapProperties.find("CRYPT/KeyStore"); + if (it != pBase->m->mapProperties.end()) + pBase->m->mapProperties.erase(it); + + /* Delete KeyId if encryption is removed or the password did change. */ + if ( task.mstrNewPasswordId.isNotEmpty() + || task.mstrCipher.isEmpty()) + { + it = pBase->m->mapProperties.find("CRYPT/KeyId"); + if (it != pBase->m->mapProperties.end()) + pBase->m->mapProperties.erase(it); + } + + if (CryptoSettingsWrite.pszKeyStore) + { + pBase->m->mapProperties["CRYPT/KeyStore"] = Utf8Str(CryptoSettingsWrite.pszKeyStore); + if (task.mstrNewPasswordId.isNotEmpty()) + pBase->m->mapProperties["CRYPT/KeyId"] = task.mstrNewPasswordId; + } + + if (CryptoSettingsRead.pszCipherReturned) + RTStrFree(CryptoSettingsRead.pszCipherReturned); + + if (CryptoSettingsWrite.pszCipherReturned) + RTStrFree(CryptoSettingsWrite.pszCipherReturned); + + thisLock.release(); + pBase->i_markRegistriesModified(); + m->pVirtualBox->i_saveModifiedRegistries(); + } + catch (HRESULT hrcXcpt) { hrc = hrcXcpt; } + + if (pvBuf) + RTMemFree(pvBuf); + + VDDestroy(pDisk); +# else + throw setError(VBOX_E_NOT_SUPPORTED, + tr("Encryption is not supported because extension pack support is not built in")); +# endif + } + catch (HRESULT hrcXcpt) { hrc = hrcXcpt; } + + /* Everything is explicitly unlocked when the task exits, + * as the task destruction also destroys the media chain. */ + + return hrc; +} + +/* vi: set tabstop=4 shiftwidth=4 expandtab: */ |