diff options
Diffstat (limited to 'src/VBox/Main/src-server/SnapshotImpl.cpp')
-rw-r--r-- | src/VBox/Main/src-server/SnapshotImpl.cpp | 499 |
1 files changed, 361 insertions, 138 deletions
diff --git a/src/VBox/Main/src-server/SnapshotImpl.cpp b/src/VBox/Main/src-server/SnapshotImpl.cpp index 4740b122..411cdd56 100644 --- a/src/VBox/Main/src-server/SnapshotImpl.cpp +++ b/src/VBox/Main/src-server/SnapshotImpl.cpp @@ -5,7 +5,7 @@ */ /* - * Copyright (C) 2006-2012 Oracle Corporation + * Copyright (C) 2006-2013 Oracle Corporation * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; @@ -29,6 +29,7 @@ // to remove them and put that code in shared code in MachineImplcpp #include "SharedFolderImpl.h" #include "USBControllerImpl.h" +#include "USBDeviceFiltersImpl.h" #include "VirtualBoxImpl.h" #include "AutoCaller.h" @@ -41,6 +42,7 @@ #include <VBox/settings.h> + //////////////////////////////////////////////////////////////////////////////// // // Snapshot private data definition @@ -113,7 +115,7 @@ HRESULT Snapshot::init(VirtualBox *aVirtualBox, { LogFlowThisFunc(("uuid=%s aParent->uuid=%s\n", aId.toString().c_str(), (aParent) ? aParent->m->uuid.toString().c_str() : "")); - ComAssertRet(!aId.isEmpty() && !aName.isEmpty() && aMachine, E_INVALIDARG); + ComAssertRet(!aId.isZero() && aId.isValid() && !aName.isEmpty() && aMachine, E_INVALIDARG); /* Enclose the state transition NotReady->InInit->Ready */ AutoInitSpan autoInitSpan(this); @@ -327,7 +329,8 @@ STDMETHODIMP Snapshot::COMSETTER(Name)(IN_BSTR aName) // prohibit setting a UUID only as the machine name, or else it can // never be found by findMachine() Guid test(aName); - if (test.isNotEmpty()) + + if (!test.isZero() && test.isValid()) return setError(E_INVALIDARG, tr("A machine cannot have a UUID as its name")); AutoCaller autoCaller(this); @@ -493,6 +496,31 @@ const Utf8Str& Snapshot::getStateFilePath() const } /** + * Returns the depth in the snapshot tree for this snapshot. + * + * @note takes the snapshot tree lock + */ + +uint32_t Snapshot::getDepth() +{ + AutoCaller autoCaller(this); + AssertComRC(autoCaller.rc()); + + // snapshots tree is protected by machine lock + AutoReadLock alock(m->pMachine COMMA_LOCKVAL_SRC_POS); + + uint32_t cDepth = 0; + ComObjPtr<Snapshot> pSnap(this); + while (!pSnap.isNull()) + { + pSnap = pSnap->m->pParent; + cDepth++; + } + + return cDepth; +} + +/** * Returns the number of direct child snapshots, without grandchildren. * Does not recurse. * @return @@ -788,11 +816,19 @@ HRESULT Snapshot::saveSnapshotImpl(settings::Snapshot &data, bool aAttrsOnly) it != m->llChildren.end(); ++it) { - settings::Snapshot snap; - rc = (*it)->saveSnapshotImpl(snap, aAttrsOnly); - if (FAILED(rc)) return rc; + // Use the heap to reduce the stack footprint. Each recursion needs + // over 1K, and there can be VMs with deeply nested snapshots. The + // stack can be quite small, especially with XPCOM. - data.llChildSnapshots.push_back(snap); + settings::Snapshot *snap = new settings::Snapshot(); + rc = (*it)->saveSnapshotImpl(*snap, aAttrsOnly); + if (FAILED(rc)) + { + delete snap; + return rc; + } + data.llChildSnapshots.push_back(*snap); + delete snap; } } @@ -954,7 +990,8 @@ HRESULT SnapshotMachine::init(SessionMachine *aSessionMachine, LogFlowThisFuncEnter(); LogFlowThisFunc(("mName={%s}\n", aSessionMachine->mUserData->s.strName.c_str())); - AssertReturn(aSessionMachine && !Guid(aSnapshotId).isEmpty(), E_INVALIDARG); + Guid l_guid(aSnapshotId); + AssertReturn(aSessionMachine && (!l_guid.isZero() && l_guid.isValid()), E_INVALIDARG); /* Enclose the state transition NotReady->InInit->Ready */ AutoInitSpan autoInitSpan(this); @@ -1040,8 +1077,22 @@ HRESULT SnapshotMachine::init(SessionMachine *aSessionMachine, unconst(mAudioAdapter).createObject(); mAudioAdapter->initCopy(this, pMachine->mAudioAdapter); - unconst(mUSBController).createObject(); - mUSBController->initCopy(this, pMachine->mUSBController); + /* create copies of all USB controllers (mUSBControllerData + * after attaching a copy contains just references to original objects) */ + mUSBControllers.allocate(); + for (USBControllerList::const_iterator + it = aSessionMachine->mUSBControllers->begin(); + it != aSessionMachine->mUSBControllers->end(); + ++it) + { + ComObjPtr<USBController> ctrl; + ctrl.createObject(); + ctrl->initCopy(this, *it); + mUSBControllers->push_back(ctrl); + } + + unconst(mUSBDeviceFilters).createObject(); + mUSBDeviceFilters->initCopy(this, pMachine->mUSBDeviceFilters); mNetworkAdapters.resize(pMachine->mNetworkAdapters.size()); for (ULONG slot = 0; slot < mNetworkAdapters.size(); slot++) @@ -1095,7 +1146,8 @@ HRESULT SnapshotMachine::initFromSettings(Machine *aMachine, LogFlowThisFuncEnter(); LogFlowThisFunc(("mName={%s}\n", aMachine->mUserData->s.strName.c_str())); - AssertReturn(aMachine && !Guid(aSnapshotId).isEmpty(), E_INVALIDARG); + Guid l_guid(aSnapshotId); + AssertReturn(aMachine && (!l_guid.isZero() && l_guid.isValid()), E_INVALIDARG); /* Enclose the state transition NotReady->InInit->Ready */ AutoInitSpan autoInitSpan(this); @@ -1122,6 +1174,7 @@ HRESULT SnapshotMachine::initFromSettings(Machine *aMachine, mHWData.allocate(); mMediaData.allocate(); mStorageControllers.allocate(); + mUSBControllers.allocate(); /* SSData is always unique for SnapshotMachine */ mSSData.allocate(); @@ -1138,8 +1191,8 @@ HRESULT SnapshotMachine::initFromSettings(Machine *aMachine, unconst(mAudioAdapter).createObject(); mAudioAdapter->init(this); - unconst(mUSBController).createObject(); - mUSBController->init(this); + unconst(mUSBDeviceFilters).createObject(); + mUSBDeviceFilters->init(this); mNetworkAdapters.resize(Global::getMaxNetworkAdapters(mHWData->mChipsetType)); for (ULONG slot = 0; slot < mNetworkAdapters.size(); slot++) @@ -1381,7 +1434,7 @@ struct SessionMachine::DeleteSnapshotTask * * When the console is done, it calls SessionMachine::EndTakingSnapshot(). * - * @note Locks mParent + this object for writing. + * @note Locks mParent + this object for writing. * * @param aInitiator in: The console on which Console::TakeSnapshot was called. * @param aName in: The name for the new snapshot. @@ -1416,6 +1469,14 @@ STDMETHODIMP SessionMachine::BeginTakingSnapshot(IConsole *aInitiator, AssertReturn(mConsoleTaskData.mLastState == MachineState_Null, E_FAIL); AssertReturn(mConsoleTaskData.mSnapshot.isNull(), E_FAIL); + if ( mData->mCurrentSnapshot + && mData->mCurrentSnapshot->getDepth() >= SETTINGS_SNAPSHOT_DEPTH_MAX) + { + return setError(VBOX_E_INVALID_OBJECT_STATE, + tr("Cannot take another snapshot for machine '%s', because it exceeds the maximum snapshot depth limit. Please delete some earlier snapshot which you no longer need"), + mUserData->s.strName.c_str()); + } + if ( !fTakingSnapshotOnline && mData->mMachineState != MachineState_Saved ) @@ -1435,8 +1496,16 @@ STDMETHODIMP SessionMachine::BeginTakingSnapshot(IConsole *aInitiator, Utf8Str strStateFilePath; /* stateFilePath is null when the machine is not online nor saved */ if (fTakingSnapshotOnline) - // creating a new online snapshot: then we need a fresh saved state file - composeSavedStateFilename(strStateFilePath); + { + Bstr value; + HRESULT rc = GetExtraData(Bstr("VBoxInternal2/ForceTakeSnapshotWithoutState").raw(), + value.asOutParam()); + if (FAILED(rc) || value != "1") + { + // creating a new online snapshot: we need a fresh saved state file + composeSavedStateFilename(strStateFilePath); + } + } else if (mData->mMachineState == MachineState_Saved) // taking an online snapshot from machine in "saved" state: then use existing state file strStateFilePath = mSSData->strStateFilePath; @@ -1499,9 +1568,9 @@ STDMETHODIMP SessionMachine::BeginTakingSnapshot(IConsole *aInitiator, if (FAILED(rc)) throw rc; - // if we got this far without an error, then save the media registries - // that got modified for the diff images - mParent->saveModifiedRegistries(); + // MUST NOT save the settings or the media registry here, because + // this causes trouble with rolling back settings if the user cancels + // taking the snapshot after the diff images have been created. } catch (HRESULT hrc) { @@ -1608,11 +1677,14 @@ STDMETHODIMP SessionMachine::EndTakingSnapshot(BOOL aSuccess) /* inform callbacks */ mParent->onSnapshotTaken(mData->mUuid, mConsoleTaskData.mSnapshot->getId()); + machineLock.release(); } else { /* delete all differencing hard disks created (this will also attach * their parents back by rolling back mMediaData) */ + machineLock.release(); + rollbackMedia(); mData->mFirstSnapshot = pOldFirstSnap; // might have been changed above @@ -1624,15 +1696,19 @@ STDMETHODIMP SessionMachine::EndTakingSnapshot(BOOL aSuccess) // snapshot means that a new saved state file was created, which we must // clean up now RTFileDelete(mConsoleTaskData.mSnapshot->getStateFilePath().c_str()); + machineLock.acquire(); + mConsoleTaskData.mSnapshot->uninit(); + machineLock.release(); + } /* clear out the snapshot data */ mConsoleTaskData.mLastState = MachineState_Null; mConsoleTaskData.mSnapshot.setNull(); - machineLock.release(); + /* machineLock has been released already */ mParent->saveModifiedRegistries(); @@ -2027,7 +2103,7 @@ void SessionMachine::restoreSnapshotHandler(RestoreSnapshotTask &aTask) //////////////////////////////////////////////////////////////////////////////// /** - * Implementation for IInternalMachineControl::deleteSnapshot(). + * Implementation for IInternalMachineControl::DeleteSnapshot(). * * Gets called from Console::DeleteSnapshot(), and that's basically the * only thing Console does initially. Deleting a snapshot happens entirely on @@ -2053,7 +2129,9 @@ STDMETHODIMP SessionMachine::DeleteSnapshot(IConsole *aInitiator, Guid startId(aStartId); Guid endId(aEndId); - AssertReturn(aInitiator && !startId.isEmpty() && !endId.isEmpty(), E_INVALIDARG); + + AssertReturn(aInitiator && !startId.isZero() && !endId.isZero() && startId.isValid() && endId.isValid(), E_INVALIDARG); + AssertReturn(aMachineState && aProgress, E_POINTER); /** @todo implement the "and all children" and "range" variants */ @@ -2086,10 +2164,15 @@ STDMETHODIMP SessionMachine::DeleteSnapshot(IConsole *aInitiator, size_t childrenCount = pSnapshot->getChildrenCount(); if (childrenCount > 1) return setError(VBOX_E_INVALID_OBJECT_STATE, - tr("Snapshot '%s' of the machine '%s' cannot be deleted. because it has %d child snapshots, which is more than the one snapshot allowed for deletion"), + tr("Snapshot '%s' of the machine '%s' cannot be deleted, because it has %d child snapshots, which is more than the one snapshot allowed for deletion"), pSnapshot->getName().c_str(), mUserData->s.strName.c_str(), childrenCount); + if (pSnapshot == mData->mCurrentSnapshot && childrenCount >= 1) + return setError(VBOX_E_INVALID_OBJECT_STATE, + tr("Snapshot '%s' of the machine '%s' cannot be deleted, because it is the current snapshot and has one child snapshot"), + pSnapshot->getName().c_str(), + mUserData->s.strName.c_str()); /* If the snapshot being deleted is the current one, ensure current * settings are committed and saved. @@ -2215,18 +2298,20 @@ struct MediumDeleteRec const ComObjPtr<MediumAttachment> &aOnlineMediumAttachment, bool fMergeForward, const ComObjPtr<Medium> &aParentForTarget, - const MediaList &aChildrenToReparent, + MediumLockList *aChildrenToReparent, bool fNeedsOnlineMerge, - MediumLockList *aMediumLockList) + MediumLockList *aMediumLockList, + const ComPtr<IToken> &aHDLockToken) : mpHD(aHd), mpSource(aSource), mpTarget(aTarget), mpOnlineMediumAttachment(aOnlineMediumAttachment), mfMergeForward(fMergeForward), mpParentForTarget(aParentForTarget), - mChildrenToReparent(aChildrenToReparent), + mpChildrenToReparent(aChildrenToReparent), mfNeedsOnlineMerge(fNeedsOnlineMerge), - mpMediumLockList(aMediumLockList) + mpMediumLockList(aMediumLockList), + mpHDLockToken(aHDLockToken) {} MediumDeleteRec(const ComObjPtr<Medium> &aHd, @@ -2235,9 +2320,10 @@ struct MediumDeleteRec const ComObjPtr<MediumAttachment> &aOnlineMediumAttachment, bool fMergeForward, const ComObjPtr<Medium> &aParentForTarget, - const MediaList &aChildrenToReparent, + MediumLockList *aChildrenToReparent, bool fNeedsOnlineMerge, MediumLockList *aMediumLockList, + const ComPtr<IToken> &aHDLockToken, const Guid &aMachineId, const Guid &aSnapshotId) : mpHD(aHd), @@ -2246,9 +2332,10 @@ struct MediumDeleteRec mpOnlineMediumAttachment(aOnlineMediumAttachment), mfMergeForward(fMergeForward), mpParentForTarget(aParentForTarget), - mChildrenToReparent(aChildrenToReparent), + mpChildrenToReparent(aChildrenToReparent), mfNeedsOnlineMerge(fNeedsOnlineMerge), mpMediumLockList(aMediumLockList), + mpHDLockToken(aHDLockToken), mMachineId(aMachineId), mSnapshotId(aSnapshotId) {} @@ -2259,9 +2346,11 @@ struct MediumDeleteRec ComObjPtr<MediumAttachment> mpOnlineMediumAttachment; bool mfMergeForward; ComObjPtr<Medium> mpParentForTarget; - MediaList mChildrenToReparent; + MediumLockList *mpChildrenToReparent; bool mfNeedsOnlineMerge; MediumLockList *mpMediumLockList; + /** optional lock token, used only in case mpHD is not merged/deleted */ + ComPtr<IToken> mpHDLockToken; /* these are for reattaching the hard disk in case of a failure: */ Guid mMachineId; Guid mSnapshotId; @@ -2371,11 +2460,12 @@ void SessionMachine::deleteSnapshotHandler(DeleteSnapshotTask &aTask) ComObjPtr<Medium> pSource; bool fMergeForward = false; ComObjPtr<Medium> pParentForTarget; - MediaList childrenToReparent; + MediumLockList *pChildrenToReparent = NULL; bool fNeedsOnlineMerge = false; bool fOnlineMergePossible = aTask.m_fDeleteOnline; MediumLockList *pMediumLockList = NULL; MediumLockList *pVMMALockList = NULL; + ComPtr<IToken> pHDLockToken; ComObjPtr<MediumAttachment> pOnlineMediumAttachment; if (fOnlineMergePossible) { @@ -2408,9 +2498,10 @@ void SessionMachine::deleteSnapshotHandler(DeleteSnapshotTask &aTask) fOnlineMergePossible, pVMMALockList, pSource, pTarget, fMergeForward, pParentForTarget, - childrenToReparent, + pChildrenToReparent, fNeedsOnlineMerge, - pMediumLockList); + pMediumLockList, + pHDLockToken); treeLock.acquire(); if (FAILED(rc)) throw rc; @@ -2433,7 +2524,6 @@ void SessionMachine::deleteSnapshotHandler(DeleteSnapshotTask &aTask) // then do a backward merge, i.e. merge its only child onto the // base disk. Here we need then to update the attachment that // refers to the child and have it point to the parent instead - Assert(pHD->getParent().isNull()); Assert(pHD->getChildren().size() == 1); ComObjPtr<Medium> pReplaceHD = pHD->getChildren().front(); @@ -2454,7 +2544,7 @@ void SessionMachine::deleteSnapshotHandler(DeleteSnapshotTask &aTask) if (pSnapshotId) replaceSnapshotId = *pSnapshotId; - if (!replaceMachineId.isEmpty()) + if (replaceMachineId.isValid() && !replaceMachineId.isZero()) { // Adjust the backreferences, otherwise merging will assert. // Note that the medium attachment object stays associated @@ -2467,9 +2557,10 @@ void SessionMachine::deleteSnapshotHandler(DeleteSnapshotTask &aTask) pOnlineMediumAttachment, fMergeForward, pParentForTarget, - childrenToReparent, + pChildrenToReparent, fNeedsOnlineMerge, pMediumLockList, + pHDLockToken, replaceMachineId, replaceSnapshotId)); } @@ -2478,9 +2569,116 @@ void SessionMachine::deleteSnapshotHandler(DeleteSnapshotTask &aTask) pOnlineMediumAttachment, fMergeForward, pParentForTarget, - childrenToReparent, + pChildrenToReparent, fNeedsOnlineMerge, - pMediumLockList)); + pMediumLockList, + pHDLockToken)); + } + + { + /*check available place on the storage*/ + RTFOFF pcbTotal = 0; + RTFOFF pcbFree = 0; + uint32_t pcbBlock = 0; + uint32_t pcbSector = 0; + std::multimap<uint32_t,uint64_t> neededStorageFreeSpace; + std::map<uint32_t,const char*> serialMapToStoragePath; + + MediumDeleteRecList::const_iterator it_md = toDelete.begin(); + + while (it_md != toDelete.end()) + { + uint64_t diskSize = 0; + uint32_t pu32Serial = 0; + ComObjPtr<Medium> pSource_local = it_md->mpSource; + ComObjPtr<Medium> pTarget_local = it_md->mpTarget; + ComPtr<IMediumFormat> pTargetFormat; + + { + if ( pSource_local.isNull() + || pSource_local == pTarget_local) + { + ++it_md; + continue; + } + } + + rc = pTarget_local->COMGETTER(MediumFormat)(pTargetFormat.asOutParam()); + if (FAILED(rc)) + throw rc; + + if(pTarget_local->isMediumFormatFile()) + { + int vrc = RTFsQuerySerial(pTarget_local->getLocationFull().c_str(), &pu32Serial); + if (RT_FAILURE(vrc)) + { + rc = setError(E_FAIL, + tr(" Unable to merge storage '%s'. Can't get storage UID "), + pTarget_local->getLocationFull().c_str()); + throw rc; + } + + pSource_local->COMGETTER(Size)((LONG64*)&diskSize); + + /* store needed free space in multimap */ + neededStorageFreeSpace.insert(std::make_pair(pu32Serial,diskSize)); + /* linking storage UID with snapshot path, it is a helper container (just for easy finding needed path) */ + serialMapToStoragePath.insert(std::make_pair(pu32Serial,pTarget_local->getLocationFull().c_str())); + } + + ++it_md; + } + + while (!neededStorageFreeSpace.empty()) + { + std::pair<std::multimap<uint32_t,uint64_t>::iterator,std::multimap<uint32_t,uint64_t>::iterator> ret; + uint64_t commonSourceStoragesSize = 0; + + /* find all records in multimap with identical storage UID*/ + ret = neededStorageFreeSpace.equal_range(neededStorageFreeSpace.begin()->first); + std::multimap<uint32_t,uint64_t>::const_iterator it_ns = ret.first; + + for (; it_ns != ret.second ; ++it_ns) + { + commonSourceStoragesSize += it_ns->second; + } + + /* find appropriate path by storage UID*/ + std::map<uint32_t,const char*>::const_iterator it_sm = serialMapToStoragePath.find(ret.first->first); + /* get info about a storage */ + if (it_sm == serialMapToStoragePath.end()) + { + LogFlowThisFunc((" Path to the storage wasn't found...\n ")); + + rc = setError(E_INVALIDARG, + tr(" Unable to merge storage '%s'. Path to the storage wasn't found. "), + it_sm->second); + throw rc; + } + + int vrc = RTFsQuerySizes(it_sm->second, &pcbTotal, &pcbFree,&pcbBlock, &pcbSector); + if (RT_FAILURE(vrc)) + { + rc = setError(E_FAIL, + tr(" Unable to merge storage '%s'. Can't get the storage size. "), + it_sm->second); + throw rc; + } + + if (commonSourceStoragesSize > (uint64_t)pcbFree) + { + LogFlowThisFunc((" Not enough free space to merge...\n ")); + + rc = setError(E_OUTOFMEMORY, + tr(" Unable to merge storage '%s' - not enough free storage space. "), + it_sm->second); + throw rc; + } + + neededStorageFreeSpace.erase(ret.first, ret.second); + } + + serialMapToStoragePath.clear(); } // we can release the locks now since the machine state is MachineState_DeletingSnapshot @@ -2572,16 +2770,22 @@ void SessionMachine::deleteSnapshotHandler(DeleteSnapshotTask &aTask) bool fNeedsSave = false; if (it->mfNeedsOnlineMerge) { + // Put the medium merge information (MediumDeleteRec) where + // SessionMachine::FinishOnlineMergeMedium can get at it. + // This callback will arrive while onlineMergeMedium is + // still executing, and there can't be two tasks. + mConsoleTaskData.mDeleteSnapshotInfo = (void *)&(*it); // online medium merge, in the direction decided earlier rc = onlineMergeMedium(it->mpOnlineMediumAttachment, it->mpSource, it->mpTarget, it->mfMergeForward, it->mpParentForTarget, - it->mChildrenToReparent, + it->mpChildrenToReparent, it->mpMediumLockList, aTask.pProgress, &fNeedsSave); + mConsoleTaskData.mDeleteSnapshotInfo = NULL; } else { @@ -2589,7 +2793,7 @@ void SessionMachine::deleteSnapshotHandler(DeleteSnapshotTask &aTask) rc = it->mpSource->mergeTo(it->mpTarget, it->mfMergeForward, it->mpParentForTarget, - it->mChildrenToReparent, + it->mpChildrenToReparent, it->mpMediumLockList, &aTask.pProgress, true /* aWait */); @@ -2708,7 +2912,9 @@ void SessionMachine::deleteSnapshotHandler(DeleteSnapshotTask &aTask) mParent->markRegistryModified(getId()); } } - catch (HRESULT aRC) { rc = aRC; } + catch (HRESULT aRC) { + rc = aRC; + } if (FAILED(rc)) { @@ -2726,10 +2932,10 @@ void SessionMachine::deleteSnapshotHandler(DeleteSnapshotTask &aTask) ++it) { cancelDeleteSnapshotMedium(it->mpHD, it->mpSource, - it->mChildrenToReparent, + it->mpChildrenToReparent, it->mfNeedsOnlineMerge, - it->mpMediumLockList, it->mMachineId, - it->mSnapshotId); + it->mpMediumLockList, it->mpHDLockToken, + it->mMachineId, it->mSnapshotId); } } @@ -2780,14 +2986,16 @@ void SessionMachine::deleteSnapshotHandler(DeleteSnapshotTask &aTask) * @param aTarget Target hard disk for merge (out). * @param aMergeForward Merge direction decision (out). * @param aParentForTarget New parent if target needs to be reparented (out). - * @param aChildrenToReparent Children which have to be reparented to the - * target (out). + * @param aChildrenToReparent MediumLockList with children which have to be + * reparented to the target (out). * @param fNeedsOnlineMerge Whether this merge needs to be done online (out). * If this is set to @a true then the @a aVMMALockList * parameter has been modified and is returned as * @a aMediumLockList. * @param aMediumLockList Where to store the created medium lock list (may * return NULL if no real merge is necessary). + * @param aHDLockToken Where to store the write lock token for aHD, in case + * it is not merged or deleted (out). * * @note Caller must hold media tree lock for writing. This locks this object * and every medium object on the merge chain for writing. @@ -2801,9 +3009,10 @@ HRESULT SessionMachine::prepareDeleteSnapshotMedium(const ComObjPtr<Medium> &aHD ComObjPtr<Medium> &aTarget, bool &aMergeForward, ComObjPtr<Medium> &aParentForTarget, - MediaList &aChildrenToReparent, + MediumLockList * &aChildrenToReparent, bool &fNeedsOnlineMerge, - MediumLockList * &aMediumLockList) + MediumLockList * &aMediumLockList, + ComPtr<IToken> &aHDLockToken) { Assert(!mParent->getMediaTreeLockHandle().isWriteLockOnCurrentThread()); Assert(!fOnlineMergePossible || VALID_PTR(aVMMALockList)); @@ -2816,6 +3025,7 @@ HRESULT SessionMachine::prepareDeleteSnapshotMedium(const ComObjPtr<Medium> &aHD && type != MediumType_Shareable && type != MediumType_Readonly, E_FAIL); + aChildrenToReparent = NULL; aMediumLockList = NULL; fNeedsOnlineMerge = false; @@ -2832,7 +3042,7 @@ HRESULT SessionMachine::prepareDeleteSnapshotMedium(const ComObjPtr<Medium> &aHD /* lock only, to prevent any usage until the snapshot deletion * is completed */ alock.release(); - return aHD->LockWrite(NULL); + return aHD->LockWrite(aHDLockToken.asOutParam()); } /* the differencing hard disk w/o children will be deleted, protect it @@ -2863,7 +3073,7 @@ HRESULT SessionMachine::prepareDeleteSnapshotMedium(const ComObjPtr<Medium> &aHD * deletion, so lock only, to prevent any usage */ childLock.release(); alock.release(); - return aHD->LockWrite(NULL); + return aHD->LockWrite(aHDLockToken.asOutParam()); } aSource = pChild; @@ -2871,9 +3081,30 @@ HRESULT SessionMachine::prepareDeleteSnapshotMedium(const ComObjPtr<Medium> &aHD } else { - /* forward merge */ - aSource = aHD; - aTarget = pChild; + /* Determine best merge direction. */ + bool fMergeForward = true; + + childLock.release(); + alock.release(); + HRESULT rc = aHD->queryPreferredMergeDirection(pChild, fMergeForward); + alock.acquire(); + childLock.acquire(); + + if (FAILED(rc) && rc != E_FAIL) + return rc; + + if (fMergeForward) + { + aSource = aHD; + aTarget = pChild; + LogFlowFunc(("Forward merging selected\n")); + } + else + { + aSource = pChild; + aTarget = aHD; + LogFlowFunc(("Backward merging selected\n")); + } } HRESULT rc; @@ -2939,39 +3170,37 @@ HRESULT SessionMachine::prepareDeleteSnapshotMedium(const ComObjPtr<Medium> &aHD if (fOnlineMergePossible) { /* we will lock the children of the source for reparenting */ - for (MediaList::const_iterator it = aChildrenToReparent.begin(); - it != aChildrenToReparent.end(); - ++it) + if (aChildrenToReparent && !aChildrenToReparent->IsEmpty()) { - ComObjPtr<Medium> pMedium = *it; - AutoReadLock mediumLock(pMedium COMMA_LOCKVAL_SRC_POS); - if (pMedium->getState() == MediumState_Created) - { - mediumLock.release(); - childLock.release(); - alock.release(); - rc = pMedium->LockWrite(NULL); - alock.acquire(); - childLock.acquire(); - mediumLock.acquire(); - if (FAILED(rc)) - throw rc; - } - else + /* Cannot just call aChildrenToReparent->Lock(), as one of + * the children is the one under which the current state of + * the VM is located, and this means it is already locked + * (for reading). Note that no special unlocking is needed, + * because cancelMergeTo will unlock everything locked in + * its context (using the unlock on destruction), and both + * cancelDeleteSnapshotMedium (in case something fails) and + * FinishOnlineMergeMedium re-define the read/write lock + * state of everything which the VM need, search for the + * UpdateLock method calls. */ + childLock.release(); + alock.release(); + rc = aChildrenToReparent->Lock(true /* fSkipOverLockedMedia */); + alock.acquire(); + childLock.acquire(); + MediumLockList::Base::iterator childrenToReparentBegin = aChildrenToReparent->GetBegin(); + MediumLockList::Base::iterator childrenToReparentEnd = aChildrenToReparent->GetEnd(); + for (MediumLockList::Base::iterator it = childrenToReparentBegin; + it != childrenToReparentEnd; + ++it) { - mediumLock.release(); - childLock.release(); - alock.release(); - rc = aVMMALockList->Update(pMedium, true); - alock.acquire(); - childLock.acquire(); - mediumLock.acquire(); - if (FAILED(rc)) + ComObjPtr<Medium> pMedium = it->GetMedium(); + AutoReadLock mediumLock(pMedium COMMA_LOCKVAL_SRC_POS); + if (!it->IsLocked()) { mediumLock.release(); childLock.release(); alock.release(); - rc = pMedium->LockWrite(NULL); + rc = aVMMALockList->Update(pMedium, true); alock.acquire(); childLock.acquire(); mediumLock.acquire(); @@ -3058,6 +3287,7 @@ HRESULT SessionMachine::prepareDeleteSnapshotMedium(const ComObjPtr<Medium> &aHD * @param aChildrenToReparent Children to unlock. * @param fNeedsOnlineMerge Whether this merge needs to be done online. * @param aMediumLockList Medium locks to cancel. + * @param aHDLockToken Optional write lock token for aHD. * @param aMachineId Machine id to attach the medium to. * @param aSnapshotId Snapshot id to attach the medium to. * @@ -3065,9 +3295,10 @@ HRESULT SessionMachine::prepareDeleteSnapshotMedium(const ComObjPtr<Medium> &aHD */ void SessionMachine::cancelDeleteSnapshotMedium(const ComObjPtr<Medium> &aHD, const ComObjPtr<Medium> &aSource, - const MediaList &aChildrenToReparent, + MediumLockList *aChildrenToReparent, bool fNeedsOnlineMerge, MediumLockList *aMediumLockList, + const ComPtr<IToken> &aHDLockToken, const Guid &aMachineId, const Guid &aSnapshotId) { @@ -3079,8 +3310,12 @@ void SessionMachine::cancelDeleteSnapshotMedium(const ComObjPtr<Medium> &aHD, if (aHD->getParent().isNull()) { - HRESULT rc = aHD->UnlockWrite(NULL); - AssertComRC(rc); + Assert(!aHDLockToken.isNull()); + if (!aHDLockToken.isNull()) + { + HRESULT rc = aHDLockToken->Abandon(); + AssertComRC(rc); + } } else { @@ -3128,7 +3363,7 @@ void SessionMachine::cancelDeleteSnapshotMedium(const ComObjPtr<Medium> &aHD, } } - if (!aMachineId.isEmpty()) + if (aMachineId.isValid() && !aMachineId.isZero()) { // reattach the source media to the snapshot HRESULT rc = aSource->addBackReference(aMachineId, aSnapshotId); @@ -3147,8 +3382,8 @@ void SessionMachine::cancelDeleteSnapshotMedium(const ComObjPtr<Medium> &aHD, * @param aTarget Target hard disk for merge. * @param aMergeForward Merge direction. * @param aParentForTarget New parent if target needs to be reparented. - * @param aChildrenToReparent Children which have to be reparented to the - * target. + * @param aChildrenToReparent Medium lock list with children which have to be + * reparented to the target. * @param aMediumLockList Where to store the created medium lock list (may * return NULL if no real merge is necessary). * @param aProgress Progress indicator. @@ -3159,7 +3394,7 @@ HRESULT SessionMachine::onlineMergeMedium(const ComObjPtr<MediumAttachment> &aMe const ComObjPtr<Medium> &aTarget, bool fMergeForward, const ComObjPtr<Medium> &aParentForTarget, - const MediaList &aChildrenToReparent, + MediumLockList *aChildrenToReparent, MediumLockList *aMediumLockList, ComObjPtr<Progress> &aProgress, bool *pfNeedsMachineSaveSettings) @@ -3168,6 +3403,9 @@ HRESULT SessionMachine::onlineMergeMedium(const ComObjPtr<MediumAttachment> &aMe AssertReturn(aTarget != NULL, E_FAIL); AssertReturn(aSource != aTarget, E_FAIL); AssertReturn(aMediumLockList != NULL, E_FAIL); + NOREF(fMergeForward); + NOREF(aParentForTarget); + NOREF(aChildrenToReparent); HRESULT rc = S_OK; @@ -3208,12 +3446,6 @@ HRESULT SessionMachine::onlineMergeMedium(const ComObjPtr<MediumAttachment> &aMe ComAssertThrow( uSourceIdx != (unsigned)-1 && uTargetIdx != (unsigned)-1, E_FAIL); - // For forward merges, tell the VM what images need to have their - // parent UUID updated. This cannot be done in VBoxSVC, as opening - // the required parent images is not safe while the VM is running. - // For backward merges this will be simply an array of size 0. - com::SafeIfaceArray<IMedium> childrenToReparent(aChildrenToReparent); - ComPtr<IInternalSessionControl> directControl; { AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); @@ -3229,9 +3461,6 @@ HRESULT SessionMachine::onlineMergeMedium(const ComObjPtr<MediumAttachment> &aMe // updating the medium attachment, chain linking and state. rc = directControl->OnlineMergeMedium(aMediumAttachment, uSourceIdx, uTargetIdx, - aSource, aTarget, - fMergeForward, aParentForTarget, - ComSafeArrayAsInParam(childrenToReparent), aProgress); if (FAILED(rc)) throw rc; @@ -3247,7 +3476,7 @@ HRESULT SessionMachine::onlineMergeMedium(const ComObjPtr<MediumAttachment> &aMe } /** - * Implementation for IInternalMachineControl::finishOnlineMergeMedium(). + * Implementation for IInternalMachineControl::FinishOnlineMergeMedium(). * * Gets called after the successful completion of an online merge from * Console::onlineMergeMedium(), which gets invoked indirectly above in @@ -3256,17 +3485,11 @@ HRESULT SessionMachine::onlineMergeMedium(const ComObjPtr<MediumAttachment> &aMe * This updates the medium information and medium state so that the VM * can continue with the updated state of the medium chain. */ -STDMETHODIMP SessionMachine::FinishOnlineMergeMedium(IMediumAttachment *aMediumAttachment, - IMedium *aSource, - IMedium *aTarget, - BOOL aMergeForward, - IMedium *aParentForTarget, - ComSafeArrayIn(IMedium *, aChildrenToReparent)) +STDMETHODIMP SessionMachine::FinishOnlineMergeMedium() { HRESULT rc = S_OK; - ComObjPtr<Medium> pSource(static_cast<Medium *>(aSource)); - ComObjPtr<Medium> pTarget(static_cast<Medium *>(aTarget)); - ComObjPtr<Medium> pParentForTarget(static_cast<Medium *>(aParentForTarget)); + MediumDeleteRec *pDeleteRec = (MediumDeleteRec *)mConsoleTaskData.mDeleteSnapshotInfo; + AssertReturn(pDeleteRec, E_FAIL); bool fSourceHasChildren = false; // all hard disks but the target were successfully deleted by @@ -3279,36 +3502,35 @@ STDMETHODIMP SessionMachine::FinishOnlineMergeMedium(IMediumAttachment *aMediumA // we delete the last reference to the no longer existing medium object. ComObjPtr<Medium> targetChild; - if (aMergeForward) + if (pDeleteRec->mfMergeForward) { // first, unregister the target since it may become a base // hard disk which needs re-registration - rc = mParent->unregisterMedium(pTarget); + rc = mParent->unregisterMedium(pDeleteRec->mpTarget); AssertComRC(rc); // then, reparent it and disconnect the deleted branch at // both ends (chain->parent() is source's parent) - pTarget->deparent(); - pTarget->setParent(pParentForTarget); - if (pParentForTarget) - pSource->deparent(); + pDeleteRec->mpTarget->deparent(); + pDeleteRec->mpTarget->setParent(pDeleteRec->mpParentForTarget); + if (pDeleteRec->mpParentForTarget) + pDeleteRec->mpSource->deparent(); // then, register again - rc = mParent->registerMedium(pTarget, &pTarget, DeviceType_HardDisk); + rc = mParent->registerMedium(pDeleteRec->mpTarget, &pDeleteRec->mpTarget, DeviceType_HardDisk); AssertComRC(rc); } else { - Assert(pTarget->getChildren().size() == 1); - targetChild = pTarget->getChildren().front(); + Assert(pDeleteRec->mpTarget->getChildren().size() == 1); + targetChild = pDeleteRec->mpTarget->getChildren().front(); // disconnect the deleted branch at the elder end targetChild->deparent(); // Update parent UUIDs of the source's children, reparent them and // disconnect the deleted branch at the younger end - com::SafeIfaceArray<IMedium> childrenToReparent(ComSafeArrayInArg(aChildrenToReparent)); - if (childrenToReparent.size() > 0) + if (pDeleteRec->mpChildrenToReparent && !pDeleteRec->mpChildrenToReparent->IsEmpty()) { fSourceHasChildren = true; // Fix the parent UUID of the images which needs to be moved to @@ -3316,35 +3538,35 @@ STDMETHODIMP SessionMachine::FinishOnlineMergeMedium(IMediumAttachment *aMediumA // but only for reading since the VM is paused. If anything fails // we must continue. The worst possible result is that the images // need manual fixing via VBoxManage to adjust the parent UUID. - MediaList toReparent; - for (size_t i = 0; i < childrenToReparent.size(); i++) - { - Medium *pMedium = static_cast<Medium *>(childrenToReparent[i]); - toReparent.push_back(pMedium); - } treeLock.release(); - pTarget->fixParentUuidOfChildren(toReparent); + pDeleteRec->mpTarget->fixParentUuidOfChildren(pDeleteRec->mpChildrenToReparent); + // The childen are still write locked, unlock them now and don't + // rely on the destructor doing it very late. + pDeleteRec->mpChildrenToReparent->Unlock(); treeLock.acquire(); // obey {parent,child} lock order - AutoWriteLock sourceLock(pSource COMMA_LOCKVAL_SRC_POS); + AutoWriteLock sourceLock(pDeleteRec->mpSource COMMA_LOCKVAL_SRC_POS); - for (size_t i = 0; i < childrenToReparent.size(); i++) + MediumLockList::Base::iterator childrenBegin = pDeleteRec->mpChildrenToReparent->GetBegin(); + MediumLockList::Base::iterator childrenEnd = pDeleteRec->mpChildrenToReparent->GetEnd(); + for (MediumLockList::Base::iterator it = childrenBegin; + it != childrenEnd; + ++it) { - Medium *pMedium = static_cast<Medium *>(childrenToReparent[i]); + Medium *pMedium = it->GetMedium(); AutoWriteLock childLock(pMedium COMMA_LOCKVAL_SRC_POS); pMedium->deparent(); // removes pMedium from source - pMedium->setParent(pTarget); + pMedium->setParent(pDeleteRec->mpTarget); } } } /* unregister and uninitialize all hard disks removed by the merge */ MediumLockList *pMediumLockList = NULL; - MediumAttachment *pMediumAttachment = static_cast<MediumAttachment *>(aMediumAttachment); - rc = mData->mSession.mLockedMedia.Get(pMediumAttachment, pMediumLockList); - const ComObjPtr<Medium> &pLast = aMergeForward ? pTarget : pSource; + rc = mData->mSession.mLockedMedia.Get(pDeleteRec->mpOnlineMediumAttachment, pMediumLockList); + const ComObjPtr<Medium> &pLast = pDeleteRec->mfMergeForward ? pDeleteRec->mpTarget : pDeleteRec->mpSource; AssertReturn(SUCCEEDED(rc) && pMediumLockList, E_FAIL); MediumLockList::Base::iterator lockListBegin = pMediumLockList->GetBegin(); @@ -3360,7 +3582,7 @@ STDMETHODIMP SessionMachine::FinishOnlineMergeMedium(IMediumAttachment *aMediumA const ComObjPtr<Medium> pMedium = mediumLock.GetMedium(); /* The target and all images not merged (readonly) are skipped */ - if ( pMedium == pTarget + if ( pMedium == pDeleteRec->mpTarget || pMedium->getState() == MediumState_LockedRead) { ++it; @@ -3382,10 +3604,10 @@ STDMETHODIMP SessionMachine::FinishOnlineMergeMedium(IMediumAttachment *aMediumA * caller may still hold an AutoCaller instance for it * and therefore we cannot uninit() it (it's therefore * the caller's responsibility) */ - if (pMedium == aSource) + if (pMedium == pDeleteRec->mpSource) { - Assert(pSource->getChildren().size() == 0); - Assert(pSource->getFirstMachineBackrefId() == NULL); + Assert(pDeleteRec->mpSource->getChildren().size() == 0); + Assert(pDeleteRec->mpSource->getFirstMachineBackrefId() == NULL); } /* Delete the medium lock list entry, which also releases the @@ -3421,11 +3643,12 @@ STDMETHODIMP SessionMachine::FinishOnlineMergeMedium(IMediumAttachment *aMediumA * source has no children) then update the medium associated with the * attachment, as the previously associated one (source) is now deleted. * Without the immediate update the VM could not continue running. */ - if (!aMergeForward && !fSourceHasChildren) + if (!pDeleteRec->mfMergeForward && !fSourceHasChildren) { - AutoWriteLock attLock(pMediumAttachment COMMA_LOCKVAL_SRC_POS); - pMediumAttachment->updateMedium(pTarget); + AutoWriteLock attLock(pDeleteRec->mpOnlineMediumAttachment COMMA_LOCKVAL_SRC_POS); + pDeleteRec->mpOnlineMediumAttachment->updateMedium(pDeleteRec->mpTarget); } return S_OK; } + |