diff options
author | Felix Geyer <debfx-pkg@fobos.de> | 2011-07-29 17:55:18 +0200 |
---|---|---|
committer | Felix Geyer <debfx-pkg@fobos.de> | 2011-07-29 17:55:18 +0200 |
commit | cba113ca2826bc4814be2f69a7704c865a37d4ea (patch) | |
tree | 511123b10dd1e58e56958520534f5c50e6f570fc /src/VBox/Main/src-server/MachineImpl.cpp | |
parent | 6a16f6900dd884e07125b51c9625f6be0a1f9b70 (diff) | |
download | virtualbox-cba113ca2826bc4814be2f69a7704c865a37d4ea.tar.gz |
Imported Upstream version 4.1.0-dfsgupstream/4.1.0-dfsg
Diffstat (limited to 'src/VBox/Main/src-server/MachineImpl.cpp')
-rw-r--r-- | src/VBox/Main/src-server/MachineImpl.cpp | 1109 |
1 files changed, 881 insertions, 228 deletions
diff --git a/src/VBox/Main/src-server/MachineImpl.cpp b/src/VBox/Main/src-server/MachineImpl.cpp index ae2b4c76e..8a6f3b139 100644 --- a/src/VBox/Main/src-server/MachineImpl.cpp +++ b/src/VBox/Main/src-server/MachineImpl.cpp @@ -1,4 +1,4 @@ -/* $Id: MachineImpl.cpp $ */ +/* $Id: MachineImpl.cpp 38056 2011-07-19 09:03:26Z vboxsync $ */ /** @file * Implementation of IMachine in VBoxSVC. */ @@ -49,6 +49,10 @@ #include "DisplayImpl.h" #include "DisplayUtils.h" #include "BandwidthControlImpl.h" +#include "MachineImplCloneVM.h" + +// generated header +#include "VBoxEvents.h" #ifdef VBOX_WITH_USB # include "USBProxyService.h" @@ -68,6 +72,7 @@ #include <iprt/string.h> #include <VBox/com/array.h> +#include <VBox/com/list.h> #include <VBox/err.h> #include <VBox/param.h> @@ -153,7 +158,7 @@ Machine::HWData::HWData() mMonitorCount = 1; mHWVirtExEnabled = true; mHWVirtExNestedPagingEnabled = true; -#if HC_ARCH_BITS == 64 && !defined(RT_OS_LINUX) && !defined(RT_OS_SOLARIS) +#if HC_ARCH_BITS == 64 && !defined(RT_OS_LINUX) mHWVirtExLargePagesEnabled = true; #else /* Not supported on 32 bits hosts. */ @@ -223,7 +228,7 @@ Machine::MediaData::~MediaData() ///////////////////////////////////////////////////////////////////////////// Machine::Machine() - : mGuestHAL(NULL), + : mCollectorGuest(NULL), mPeer(NULL), mParent(NULL) {} @@ -234,13 +239,14 @@ Machine::~Machine() HRESULT Machine::FinalConstruct() { LogFlowThisFunc(("\n")); - return S_OK; + return BaseFinalConstruct(); } void Machine::FinalRelease() { LogFlowThisFunc(("\n")); uninit(); + BaseFinalRelease(); } /** @@ -787,6 +793,11 @@ void Machine::uninit() // uninit media from this machine's media registry, if they're still there Guid uuidMachine(getId()); + + /* XXX This will fail with + * "cannot be closed because it is still attached to 1 virtual machines" + * because at this point we did not call uninitDataAndChildObjects() yet + * and therefore also removeBackReference() for all these mediums was not called! */ if (!uuidMachine.isEmpty()) // can be empty if we're called from a failure of Machine::init mParent->unregisterMachineMedia(uuidMachine); @@ -933,6 +944,12 @@ STDMETHODIMP Machine::COMSETTER(Name)(IN_BSTR aName) AutoCaller autoCaller(this); if (FAILED(autoCaller.rc())) return autoCaller.rc(); + // prohibit setting a UUID only as the machine name, or else it can + // never be found by findMachine() + Guid test(aName); + if (test.isNotEmpty()) + return setError(E_INVALIDARG, tr("A machine cannot have a UUID as its name")); + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); HRESULT rc = checkStateDependency(MutableStateDep); @@ -1305,14 +1322,15 @@ STDMETHODIMP Machine::COMSETTER(CPUCount)(ULONG CPUCount) AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); - /* We cant go below the current number of CPUs if hotplug is enabled*/ + /* We cant go below the current number of CPUs attached if hotplug is enabled*/ if (mHWData->mCPUHotPlugEnabled) { for (unsigned idx = CPUCount; idx < SchemaDefs::MaxCPUCount; idx++) { if (mHWData->mCPUAttached[idx]) return setError(E_INVALIDARG, - tr(": %lu (must be higher than or equal to %lu)"), + tr("There is still a CPU attached to socket %lu." + "Detach the CPU before removing the socket"), CPUCount, idx+1); } } @@ -1437,7 +1455,7 @@ STDMETHODIMP Machine::COMSETTER(CPUHotPlugEnabled)(BOOL enabled) if ( (cCpusAttached != mHWData->mCPUCount) || (iHighestId >= mHWData->mCPUCount)) return setError(E_INVALIDARG, - tr("CPU hotplugging can't be disabled because the maximum number of CPUs is not equal to the amount of CPUs attached\n")); + tr("CPU hotplugging can't be disabled because the maximum number of CPUs is not equal to the amount of CPUs attached")); setModified(IsModified_MachineData); mHWData.backup(); @@ -1449,6 +1467,30 @@ STDMETHODIMP Machine::COMSETTER(CPUHotPlugEnabled)(BOOL enabled) return rc; } +STDMETHODIMP Machine::COMGETTER(EmulatedUSBCardReaderEnabled)(BOOL *enabled) +{ + NOREF(enabled); + return E_NOTIMPL; +} + +STDMETHODIMP Machine::COMSETTER(EmulatedUSBCardReaderEnabled)(BOOL enabled) +{ + NOREF(enabled); + return E_NOTIMPL; +} + +STDMETHODIMP Machine::COMGETTER(EmulatedUSBWebcameraEnabled)(BOOL *enabled) +{ + NOREF(enabled); + return E_NOTIMPL; +} + +STDMETHODIMP Machine::COMSETTER(EmulatedUSBWebcameraEnabled)(BOOL enabled) +{ + NOREF(enabled); + return E_NOTIMPL; +} + STDMETHODIMP Machine::COMGETTER(HpetEnabled)(BOOL *enabled) { CheckComArgOutPointerValid(enabled); @@ -1804,7 +1846,7 @@ STDMETHODIMP Machine::GetCPUIDLeaf(ULONG aId, ULONG *aValEax, ULONG *aValEbx, UL case 0x9: case 0xA: if (mHWData->mCpuIdStdLeafs[aId].ulId != aId) - return setError(E_INVALIDARG, tr("CpuId override leaf %#x is not set"), aId); + return E_INVALIDARG; *aValEax = mHWData->mCpuIdStdLeafs[aId].ulEax; *aValEbx = mHWData->mCpuIdStdLeafs[aId].ulEbx; @@ -1824,7 +1866,7 @@ STDMETHODIMP Machine::GetCPUIDLeaf(ULONG aId, ULONG *aValEax, ULONG *aValEbx, UL case 0x80000009: case 0x8000000A: if (mHWData->mCpuIdExtLeafs[aId - 0x80000000].ulId != aId) - return setError(E_INVALIDARG, tr("CpuId override leaf %#x is not set"), aId); + return E_INVALIDARG; *aValEax = mHWData->mCpuIdExtLeafs[aId - 0x80000000].ulEax; *aValEbx = mHWData->mCpuIdExtLeafs[aId - 0x80000000].ulEbx; @@ -2010,6 +2052,9 @@ STDMETHODIMP Machine::GetHWVirtExProperty(HWVirtExPropertyType_T property, BOOL case HWVirtExPropertyType_LargePages: *aVal = mHWData->mHWVirtExLargePagesEnabled; +#if defined(DEBUG_bird) && defined(RT_OS_LINUX) /* This feature is deadly here */ + *aVal = FALSE; +#endif break; case HWVirtExPropertyType_Force: @@ -2323,7 +2368,7 @@ STDMETHODIMP Machine::COMGETTER(StateFilePath)(BSTR *aStateFilePath) AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - mSSData->mStateFilePath.cloneTo(aStateFilePath); + mSSData->strStateFilePath.cloneTo(aStateFilePath); return S_OK; } @@ -3389,16 +3434,32 @@ STDMETHODIMP Machine::AttachDevice(IN_BSTR aControllerName, AssertReturn(mData->mMachineState != MachineState_Saved, E_FAIL); - if (Global::IsOnlineOrTransient(mData->mMachineState)) - return setError(VBOX_E_INVALID_VM_STATE, - tr("Invalid machine state: %s"), - Global::stringifyMachineState(mData->mMachineState)); - /* Check for an existing controller. */ ComObjPtr<StorageController> ctl; rc = getStorageControllerByName(aControllerName, ctl, true /* aSetError */); if (FAILED(rc)) return rc; + StorageControllerType_T ctrlType; + rc = ctl->COMGETTER(ControllerType)(&ctrlType); + if (FAILED(rc)) + return setError(E_FAIL, + tr("Could not get type of controller '%ls'"), + aControllerName); + + /* Check that the controller can do hotplugging if we detach the device while the VM is running. */ + bool fHotplug = false; + if (Global::IsOnlineOrTransient(mData->mMachineState)) + fHotplug = true; + + if (fHotplug && !isControllerHotplugCapable(ctrlType)) + return setError(VBOX_E_INVALID_VM_STATE, + tr("Controller '%ls' does not support hotplugging"), + aControllerName); + + if (fHotplug && aType == DeviceType_DVD) + return setError(VBOX_E_INVALID_VM_STATE, + tr("Attaching a DVD drive while the VM is running is not supported")); + // check that the port and device are not out of range rc = ctl->checkPortAndDeviceValid(aControllerPort, aDevice); if (FAILED(rc)) return rc; @@ -3745,6 +3806,9 @@ STDMETHODIMP Machine::AttachDevice(IN_BSTR aControllerName, aDevice, aType, fIndirect, + false /* fPassthrough */, + false /* fTempEject */, + false /* fNonRotational */, Utf8Str::Empty); if (FAILED(rc)) return rc; @@ -3769,6 +3833,9 @@ STDMETHODIMP Machine::AttachDevice(IN_BSTR aControllerName, treeLock.leave(); alock.release(); + if (fHotplug) + rc = onStorageDeviceChange(attachment, FALSE /* aRemove */); + mParent->saveRegistries(llRegistriesThatNeedSaving); return rc; @@ -3779,7 +3846,7 @@ STDMETHODIMP Machine::DetachDevice(IN_BSTR aControllerName, LONG aControllerPort { CheckComArgStrNotEmptyOrNull(aControllerName); - LogFlowThisFunc(("aControllerName=\"%ls\" aControllerPort=%ld aDevice=%ld\n", + LogFlowThisFunc(("aControllerName=\"%ls\" aControllerPort=%d aDevice=%d\n", aControllerName, aControllerPort, aDevice)); AutoCaller autoCaller(this); @@ -3794,10 +3861,27 @@ STDMETHODIMP Machine::DetachDevice(IN_BSTR aControllerName, LONG aControllerPort AssertReturn(mData->mMachineState != MachineState_Saved, E_FAIL); + /* Check for an existing controller. */ + ComObjPtr<StorageController> ctl; + rc = getStorageControllerByName(aControllerName, ctl, true /* aSetError */); + if (FAILED(rc)) return rc; + + StorageControllerType_T ctrlType; + rc = ctl->COMGETTER(ControllerType)(&ctrlType); + if (FAILED(rc)) + return setError(E_FAIL, + tr("Could not get type of controller '%ls'"), + aControllerName); + + /* Check that the controller can do hotplugging if we detach the device while the VM is running. */ + bool fHotplug = false; if (Global::IsOnlineOrTransient(mData->mMachineState)) + fHotplug = true; + + if (fHotplug && !isControllerHotplugCapable(ctrlType)) return setError(VBOX_E_INVALID_VM_STATE, - tr("Invalid machine state: %s"), - Global::stringifyMachineState(mData->mMachineState)); + tr("Controller '%ls' does not support hotplugging"), + aControllerName); MediumAttachment *pAttach = findAttachment(mMediaData->mAttachments, aControllerName, @@ -3808,6 +3892,23 @@ STDMETHODIMP Machine::DetachDevice(IN_BSTR aControllerName, LONG aControllerPort tr("No storage device attached to device slot %d on port %d of controller '%ls'"), aDevice, aControllerPort, aControllerName); + if (fHotplug && pAttach->getType() == DeviceType_DVD) + return setError(VBOX_E_INVALID_VM_STATE, + tr("Detaching a DVD drive while the VM is running is not supported")); + + /* + * The VM has to detach the device before we delete any implicit diffs. + * If this fails we can roll back without loosing data. + */ + if (fHotplug) + { + alock.leave(); + rc = onStorageDeviceChange(pAttach, TRUE /* aRemove */); + alock.enter(); + } + if (FAILED(rc)) return rc; + + /* If we are here everything went well and we can delete the implicit now. */ rc = detachDevice(pAttach, alock, NULL /* pSnapshot */, &llRegistriesThatNeedSaving); alock.release(); @@ -3823,7 +3924,7 @@ STDMETHODIMP Machine::PassthroughDevice(IN_BSTR aControllerName, LONG aControlle { CheckComArgStrNotEmptyOrNull(aControllerName); - LogFlowThisFunc(("aControllerName=\"%ls\" aControllerPort=%ld aDevice=%ld aPassthrough=%d\n", + LogFlowThisFunc(("aControllerName=\"%ls\" aControllerPort=%d aDevice=%d aPassthrough=%d\n", aControllerName, aControllerPort, aDevice, aPassthrough)); AutoCaller autoCaller(this); @@ -3865,12 +3966,99 @@ STDMETHODIMP Machine::PassthroughDevice(IN_BSTR aControllerName, LONG aControlle return S_OK; } +STDMETHODIMP Machine::TemporaryEjectDevice(IN_BSTR aControllerName, LONG aControllerPort, + LONG aDevice, BOOL aTemporaryEject) +{ + CheckComArgStrNotEmptyOrNull(aControllerName); + + LogFlowThisFunc(("aControllerName=\"%ls\" aControllerPort=%d aDevice=%d aTemporaryEject=%d\n", + aControllerName, aControllerPort, aDevice, aTemporaryEject)); + + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + HRESULT rc = checkStateDependency(MutableStateDep); + if (FAILED(rc)) return rc; + + MediumAttachment *pAttach = findAttachment(mMediaData->mAttachments, + aControllerName, + aControllerPort, + aDevice); + if (!pAttach) + return setError(VBOX_E_OBJECT_NOT_FOUND, + tr("No storage device attached to device slot %d on port %d of controller '%ls'"), + aDevice, aControllerPort, aControllerName); + + + setModified(IsModified_Storage); + mMediaData.backup(); + + AutoWriteLock attLock(pAttach COMMA_LOCKVAL_SRC_POS); + + if (pAttach->getType() != DeviceType_DVD) + return setError(E_INVALIDARG, + tr("Setting temporary eject flag rejected as the device attached to device slot %d on port %d of controller '%ls' is not a DVD"), + aDevice, aControllerPort, aControllerName); + pAttach->updateTempEject(!!aTemporaryEject); + + return S_OK; +} + +STDMETHODIMP Machine::NonRotationalDevice(IN_BSTR aControllerName, LONG aControllerPort, + LONG aDevice, BOOL aNonRotational) +{ + CheckComArgStrNotEmptyOrNull(aControllerName); + + LogFlowThisFunc(("aControllerName=\"%ls\" aControllerPort=%d aDevice=%d aNonRotational=%d\n", + aControllerName, aControllerPort, aDevice, aNonRotational)); + + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + HRESULT rc = checkStateDependency(MutableStateDep); + if (FAILED(rc)) return rc; + + AssertReturn(mData->mMachineState != MachineState_Saved, E_FAIL); + + if (Global::IsOnlineOrTransient(mData->mMachineState)) + return setError(VBOX_E_INVALID_VM_STATE, + tr("Invalid machine state: %s"), + Global::stringifyMachineState(mData->mMachineState)); + + MediumAttachment *pAttach = findAttachment(mMediaData->mAttachments, + aControllerName, + aControllerPort, + aDevice); + if (!pAttach) + return setError(VBOX_E_OBJECT_NOT_FOUND, + tr("No storage device attached to device slot %d on port %d of controller '%ls'"), + aDevice, aControllerPort, aControllerName); + + + setModified(IsModified_Storage); + mMediaData.backup(); + + AutoWriteLock attLock(pAttach COMMA_LOCKVAL_SRC_POS); + + if (pAttach->getType() != DeviceType_HardDisk) + return setError(E_INVALIDARG, + tr("Setting the non-rotational medium flag rejected as the device attached to device slot %d on port %d of controller '%ls' is not a hard disk"), + aDevice, aControllerPort, aControllerName); + pAttach->updateNonRotational(!!aNonRotational); + + return S_OK; +} + STDMETHODIMP Machine::SetBandwidthGroupForDevice(IN_BSTR aControllerName, LONG aControllerPort, LONG aDevice, IBandwidthGroup *aBandwidthGroup) { CheckComArgStrNotEmptyOrNull(aControllerName); - LogFlowThisFunc(("aControllerName=\"%ls\" aControllerPort=%ld aDevice=%ld\n", + LogFlowThisFunc(("aControllerName=\"%ls\" aControllerPort=%d aDevice=%d\n", aControllerName, aControllerPort, aDevice)); AutoCaller autoCaller(this); @@ -3936,7 +4124,7 @@ STDMETHODIMP Machine::MountMedium(IN_BSTR aControllerName, BOOL aForce) { int rc = S_OK; - LogFlowThisFunc(("aControllerName=\"%ls\" aControllerPort=%ld aDevice=%ld aForce=%d\n", + LogFlowThisFunc(("aControllerName=\"%ls\" aControllerPort=%d aDevice=%d aForce=%d\n", aControllerName, aControllerPort, aDevice, aForce)); CheckComArgStrNotEmptyOrNull(aControllerName); @@ -4054,7 +4242,7 @@ STDMETHODIMP Machine::GetMedium(IN_BSTR aControllerName, LONG aDevice, IMedium **aMedium) { - LogFlowThisFunc(("aControllerName=\"%ls\" aControllerPort=%ld aDevice=%ld\n", + LogFlowThisFunc(("aControllerName=\"%ls\" aControllerPort=%d aDevice=%d\n", aControllerName, aControllerPort, aDevice)); CheckComArgStrNotEmptyOrNull(aControllerName); @@ -4356,10 +4544,10 @@ STDMETHODIMP Machine::Unregister(CleanupMode_T cleanupMode, if (mData->mMachineState == MachineState_Saved) { // add the saved state file to the list of files the caller should delete - Assert(!mSSData->mStateFilePath.isEmpty()); - mData->llFilesToDelete.push_back(mSSData->mStateFilePath); + Assert(!mSSData->strStateFilePath.isEmpty()); + mData->llFilesToDelete.push_back(mSSData->strStateFilePath); - mSSData->mStateFilePath.setNull(); + mSSData->strStateFilePath.setNull(); // unconditionally set the machine state to powered off, we now // know no session has locked the machine @@ -4450,6 +4638,7 @@ STDMETHODIMP Machine::Unregister(CleanupMode_T cleanupMode, struct Machine::DeleteTask { ComObjPtr<Machine> pMachine; + RTCList< ComPtr<IMedium> > llMediums; std::list<Utf8Str> llFilesToDelete; ComObjPtr<Progress> pProgress; GuidList llRegistriesThatNeedSaving; @@ -4481,20 +4670,17 @@ STDMETHODIMP Machine::Delete(ComSafeArrayIn(IMedium*, aMedia), IProgress **aProg for (size_t i = 0; i < sfaMedia.size(); ++i) { IMedium *pIMedium(sfaMedia[i]); - Medium *pMedium = static_cast<Medium*>(pIMedium); - AutoCaller mediumAutoCaller(pMedium); - if (FAILED(mediumAutoCaller.rc())) return mediumAutoCaller.rc(); - - Utf8Str bstrLocation = pMedium->getLocationFull(); - - bool fDoesMediumNeedFileDeletion = pMedium->isMediumFormatFile(); - - // close the medium now; if that succeeds, then that means the medium is no longer - // in use and we can add it to the list of files to delete - rc = pMedium->close(&pTask->llRegistriesThatNeedSaving, - mediumAutoCaller); - if (SUCCEEDED(rc) && fDoesMediumNeedFileDeletion) - pTask->llFilesToDelete.push_back(bstrLocation); + ComObjPtr<Medium> pMedium = static_cast<Medium*>(pIMedium); + if (pMedium.isNull()) + return setError(E_INVALIDARG, "The given medium pointer with index %d is invalid", i); + SafeArray<BSTR> ids; + rc = pMedium->COMGETTER(MachineIds)(ComSafeArrayAsOutParam(ids)); + if (FAILED(rc)) return rc; + /* At this point the medium should not have any back references + * anymore. If it has it is attached to another VM and *must* not + * deleted. */ + if (ids.size() < 1) + pTask->llMediums.append(pMedium); } if (mData->pMachineConfigFile->fileExists()) pTask->llFilesToDelete.push_back(mData->m_strConfigFileFull); @@ -4504,7 +4690,7 @@ STDMETHODIMP Machine::Delete(ComSafeArrayIn(IMedium*, aMedia), IProgress **aProg static_cast<IMachine*>(this) /* aInitiator */, Bstr(tr("Deleting files")).raw(), true /* fCancellable */, - pTask->llFilesToDelete.size() + 1, // cOperations + pTask->llFilesToDelete.size() + pTask->llMediums.size() + 1, // cOperations BstrFmt(tr("Deleting '%s'"), pTask->llFilesToDelete.front().c_str()).raw()); int vrc = RTThreadCreate(NULL, @@ -4569,96 +4755,147 @@ HRESULT Machine::deleteTaskWorker(DeleteTask &task) AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); - ULONG uLogHistoryCount = 3; - ComPtr<ISystemProperties> systemProperties; - mParent->COMGETTER(SystemProperties)(systemProperties.asOutParam()); - if (!systemProperties.isNull()) - systemProperties->COMGETTER(LogHistoryCount)(&uLogHistoryCount); + HRESULT rc = S_OK; - // delete the files pushed on the task list by Machine::Delete() - // (this includes saved states of the machine and snapshots and - // medium storage files from the IMedium list passed in, and the - // machine XML file) - std::list<Utf8Str>::const_iterator it = task.llFilesToDelete.begin(); - while (it != task.llFilesToDelete.end()) + try { - const Utf8Str &strFile = *it; - LogFunc(("Deleting file %s\n", strFile.c_str())); - RTFileDelete(strFile.c_str()); + ULONG uLogHistoryCount = 3; + ComPtr<ISystemProperties> systemProperties; + rc = mParent->COMGETTER(SystemProperties)(systemProperties.asOutParam()); + if (FAILED(rc)) throw rc; - ++it; - if (it == task.llFilesToDelete.end()) + if (!systemProperties.isNull()) { - task.pProgress->SetNextOperation(Bstr(tr("Cleaning up machine directory")).raw(), 1); - break; + rc = systemProperties->COMGETTER(LogHistoryCount)(&uLogHistoryCount); + if (FAILED(rc)) throw rc; } - task.pProgress->SetNextOperation(BstrFmt(tr("Deleting '%s'"), it->c_str()).raw(), 1); - } + MachineState_T oldState = mData->mMachineState; + setMachineState(MachineState_SettingUp); + alock.release(); + for (size_t i = 0; i < task.llMediums.size(); ++i) + { + ComObjPtr<Medium> pMedium = (Medium*)(IMedium*)task.llMediums.at(i); + { + AutoCaller mac(pMedium); + if (FAILED(mac.rc())) throw mac.rc(); + Utf8Str strLocation = pMedium->getLocationFull(); + rc = task.pProgress->SetNextOperation(BstrFmt(tr("Deleting '%s'"), strLocation.c_str()).raw(), 1); + if (FAILED(rc)) throw rc; + LogFunc(("Deleting file %s\n", strLocation.c_str())); + } + ComPtr<IProgress> pProgress2; + rc = pMedium->DeleteStorage(pProgress2.asOutParam()); + if (FAILED(rc)) throw rc; + rc = task.pProgress->WaitForAsyncProgressCompletion(pProgress2); + if (FAILED(rc)) throw rc; + /* Check the result of the asynchrony process. */ + LONG iRc; + rc = pProgress2->COMGETTER(ResultCode)(&iRc); + if (FAILED(rc)) throw rc; + if (FAILED(iRc)) + { + /* If the thread of the progress object has an error, then + * retrieve the error info from there, or it'll be lost. */ + ProgressErrorInfo info(pProgress2); + throw setError(iRc, Utf8Str(info.getText()).c_str()); + } + } + setMachineState(oldState); + alock.acquire(); - /* delete the settings only when the file actually exists */ - if (mData->pMachineConfigFile->fileExists()) - { - /* Delete any backup or uncommitted XML files. Ignore failures. - See the fSafe parameter of xml::XmlFileWriter::write for details. */ - /** @todo Find a way to avoid referring directly to iprt/xml.h here. */ - Utf8Str otherXml = Utf8StrFmt("%s%s", mData->m_strConfigFileFull.c_str(), xml::XmlFileWriter::s_pszTmpSuff); - RTFileDelete(otherXml.c_str()); - otherXml = Utf8StrFmt("%s%s", mData->m_strConfigFileFull.c_str(), xml::XmlFileWriter::s_pszPrevSuff); - RTFileDelete(otherXml.c_str()); - - /* delete the Logs folder, nothing important should be left - * there (we don't check for errors because the user might have - * some private files there that we don't want to delete) */ - Utf8Str logFolder; - getLogFolder(logFolder); - Assert(logFolder.length()); - if (RTDirExists(logFolder.c_str())) - { - /* Delete all VBox.log[.N] files from the Logs folder - * (this must be in sync with the rotation logic in - * Console::powerUpThread()). Also, delete the VBox.png[.N] - * files that may have been created by the GUI. */ - Utf8Str log = Utf8StrFmt("%s%cVBox.log", - logFolder.c_str(), RTPATH_DELIMITER); - RTFileDelete(log.c_str()); - log = Utf8StrFmt("%s%cVBox.png", - logFolder.c_str(), RTPATH_DELIMITER); - RTFileDelete(log.c_str()); - for (int i = uLogHistoryCount; i > 0; i--) + // delete the files pushed on the task list by Machine::Delete() + // (this includes saved states of the machine and snapshots and + // medium storage files from the IMedium list passed in, and the + // machine XML file) + std::list<Utf8Str>::const_iterator it = task.llFilesToDelete.begin(); + while (it != task.llFilesToDelete.end()) + { + const Utf8Str &strFile = *it; + LogFunc(("Deleting file %s\n", strFile.c_str())); + int vrc = RTFileDelete(strFile.c_str()); + if (RT_FAILURE(vrc)) + throw setError(VBOX_E_IPRT_ERROR, + tr("Could not delete file '%s' (%Rrc)"), strFile.c_str(), vrc); + + ++it; + if (it == task.llFilesToDelete.end()) { - log = Utf8StrFmt("%s%cVBox.log.%d", - logFolder.c_str(), RTPATH_DELIMITER, i); + rc = task.pProgress->SetNextOperation(Bstr(tr("Cleaning up machine directory")).raw(), 1); + if (FAILED(rc)) throw rc; + break; + } + + rc = task.pProgress->SetNextOperation(BstrFmt(tr("Deleting '%s'"), it->c_str()).raw(), 1); + if (FAILED(rc)) throw rc; + } + + /* delete the settings only when the file actually exists */ + if (mData->pMachineConfigFile->fileExists()) + { + /* Delete any backup or uncommitted XML files. Ignore failures. + See the fSafe parameter of xml::XmlFileWriter::write for details. */ + /** @todo Find a way to avoid referring directly to iprt/xml.h here. */ + Utf8Str otherXml = Utf8StrFmt("%s%s", mData->m_strConfigFileFull.c_str(), xml::XmlFileWriter::s_pszTmpSuff); + RTFileDelete(otherXml.c_str()); + otherXml = Utf8StrFmt("%s%s", mData->m_strConfigFileFull.c_str(), xml::XmlFileWriter::s_pszPrevSuff); + RTFileDelete(otherXml.c_str()); + + /* delete the Logs folder, nothing important should be left + * there (we don't check for errors because the user might have + * some private files there that we don't want to delete) */ + Utf8Str logFolder; + getLogFolder(logFolder); + Assert(logFolder.length()); + if (RTDirExists(logFolder.c_str())) + { + /* Delete all VBox.log[.N] files from the Logs folder + * (this must be in sync with the rotation logic in + * Console::powerUpThread()). Also, delete the VBox.png[.N] + * files that may have been created by the GUI. */ + Utf8Str log = Utf8StrFmt("%s%cVBox.log", + logFolder.c_str(), RTPATH_DELIMITER); RTFileDelete(log.c_str()); - log = Utf8StrFmt("%s%cVBox.png.%d", - logFolder.c_str(), RTPATH_DELIMITER, i); + log = Utf8StrFmt("%s%cVBox.png", + logFolder.c_str(), RTPATH_DELIMITER); RTFileDelete(log.c_str()); + for (int i = uLogHistoryCount; i > 0; i--) + { + log = Utf8StrFmt("%s%cVBox.log.%d", + logFolder.c_str(), RTPATH_DELIMITER, i); + RTFileDelete(log.c_str()); + log = Utf8StrFmt("%s%cVBox.png.%d", + logFolder.c_str(), RTPATH_DELIMITER, i); + RTFileDelete(log.c_str()); + } + + RTDirRemove(logFolder.c_str()); } - RTDirRemove(logFolder.c_str()); + /* delete the Snapshots folder, nothing important should be left + * there (we don't check for errors because the user might have + * some private files there that we don't want to delete) */ + Utf8Str strFullSnapshotFolder; + calculateFullPath(mUserData->s.strSnapshotFolder, strFullSnapshotFolder); + Assert(!strFullSnapshotFolder.isEmpty()); + if (RTDirExists(strFullSnapshotFolder.c_str())) + RTDirRemove(strFullSnapshotFolder.c_str()); + + // delete the directory that contains the settings file, but only + // if it matches the VM name + Utf8Str settingsDir; + if (isInOwnDir(&settingsDir)) + RTDirRemove(settingsDir.c_str()); } - /* delete the Snapshots folder, nothing important should be left - * there (we don't check for errors because the user might have - * some private files there that we don't want to delete) */ - Utf8Str strFullSnapshotFolder; - calculateFullPath(mUserData->s.strSnapshotFolder, strFullSnapshotFolder); - Assert(!strFullSnapshotFolder.isEmpty()); - if (RTDirExists(strFullSnapshotFolder.c_str())) - RTDirRemove(strFullSnapshotFolder.c_str()); + alock.release(); - // delete the directory that contains the settings file, but only - // if it matches the VM name - Utf8Str settingsDir; - if (isInOwnDir(&settingsDir)) - RTDirRemove(settingsDir.c_str()); + rc = mParent->saveRegistries(task.llRegistriesThatNeedSaving); + if (FAILED(rc)) throw rc; } + catch (HRESULT aRC) { rc = aRC; } - alock.release(); - - mParent->saveRegistries(task.llRegistriesThatNeedSaving); - - return S_OK; + return rc; } STDMETHODIMP Machine::FindSnapshot(IN_BSTR aNameOrId, ISnapshot **aSnapshot) @@ -5025,30 +5262,22 @@ HRESULT Machine::setGuestPropertyToService(IN_BSTR aName, IN_BSTR aValue, HRESULT Machine::setGuestPropertyToVM(IN_BSTR aName, IN_BSTR aValue, IN_BSTR aFlags) { - CheckComArgStrNotEmptyOrNull(aName); - if ((aValue != NULL) && !VALID_PTR(aValue)) - return E_INVALIDARG; - if ((aFlags != NULL) && !VALID_PTR(aFlags)) - return E_INVALIDARG; - HRESULT rc; - try { - ComPtr<IInternalSessionControl> directControl = - mData->mSession.mDirectControl; + try + { + ComPtr<IInternalSessionControl> directControl = mData->mSession.mDirectControl; BSTR dummy = NULL; /* will not be changed (setter) */ LONG64 dummy64; if (!directControl) rc = E_ACCESSDENIED; else - rc = directControl->AccessGuestProperty - (aName, - /** @todo Fix when adding DeleteGuestProperty(), - see defect. */ - aValue, aFlags, - true /* isSetter */, - &dummy, &dummy64, &dummy); + /** @todo Fix when adding DeleteGuestProperty(), + see defect. */ + rc = directControl->AccessGuestProperty(aName, aValue, aFlags, + true /* isSetter */, + &dummy, &dummy64, &dummy); } catch (std::bad_alloc &) { @@ -5066,10 +5295,12 @@ STDMETHODIMP Machine::SetGuestProperty(IN_BSTR aName, IN_BSTR aValue, ReturnComNotImplemented(); #else // VBOX_WITH_GUEST_PROPS CheckComArgStrNotEmptyOrNull(aName); - if ((aFlags != NULL) && !VALID_PTR(aFlags)) - return E_INVALIDARG; + CheckComArgMaybeNull(aFlags); + CheckComArgMaybeNull(aValue); + AutoCaller autoCaller(this); - if (FAILED(autoCaller.rc())) return autoCaller.rc(); + if (FAILED(autoCaller.rc())) + return autoCaller.rc(); HRESULT rc = setGuestPropertyToVM(aName, aValue, aFlags); if (rc == E_ACCESSDENIED) @@ -5180,9 +5411,7 @@ STDMETHODIMP Machine::EnumerateGuestProperties(IN_BSTR aPatterns, #ifndef VBOX_WITH_GUEST_PROPS ReturnComNotImplemented(); #else // VBOX_WITH_GUEST_PROPS - if (!VALID_PTR(aPatterns) && (aPatterns != NULL)) - return E_POINTER; - + CheckComArgMaybeNull(aPatterns); CheckComArgOutSafeArrayPointerValid(aNames); CheckComArgOutSafeArrayPointerValid(aValues); CheckComArgOutSafeArrayPointerValid(aTimestamps); @@ -5468,7 +5697,7 @@ STDMETHODIMP Machine::QuerySavedGuestSize(ULONG uScreenId, ULONG *puWidth, ULONG uint32_t u32Width = 0; uint32_t u32Height = 0; - int vrc = readSavedGuestSize(mSSData->mStateFilePath, uScreenId, &u32Width, &u32Height); + int vrc = readSavedGuestSize(mSSData->strStateFilePath, uScreenId, &u32Width, &u32Height); if (RT_FAILURE(vrc)) return setError(VBOX_E_IPRT_ERROR, tr("Saved guest size is not available (%Rrc)"), @@ -5501,7 +5730,7 @@ STDMETHODIMP Machine::QuerySavedThumbnailSize(ULONG aScreenId, ULONG *aSize, ULO uint32_t u32Width = 0; uint32_t u32Height = 0; - int vrc = readSavedDisplayScreenshot(mSSData->mStateFilePath, 0 /* u32Type */, &pu8Data, &cbData, &u32Width, &u32Height); + int vrc = readSavedDisplayScreenshot(mSSData->strStateFilePath, 0 /* u32Type */, &pu8Data, &cbData, &u32Width, &u32Height); if (RT_FAILURE(vrc)) return setError(VBOX_E_IPRT_ERROR, @@ -5538,7 +5767,7 @@ STDMETHODIMP Machine::ReadSavedThumbnailToArray(ULONG aScreenId, BOOL aBGR, ULON uint32_t u32Width = 0; uint32_t u32Height = 0; - int vrc = readSavedDisplayScreenshot(mSSData->mStateFilePath, 0 /* u32Type */, &pu8Data, &cbData, &u32Width, &u32Height); + int vrc = readSavedDisplayScreenshot(mSSData->strStateFilePath, 0 /* u32Type */, &pu8Data, &cbData, &u32Width, &u32Height); if (RT_FAILURE(vrc)) return setError(VBOX_E_IPRT_ERROR, @@ -5601,7 +5830,7 @@ STDMETHODIMP Machine::ReadSavedThumbnailPNGToArray(ULONG aScreenId, ULONG *aWidt uint32_t u32Width = 0; uint32_t u32Height = 0; - int vrc = readSavedDisplayScreenshot(mSSData->mStateFilePath, 0 /* u32Type */, &pu8Data, &cbData, &u32Width, &u32Height); + int vrc = readSavedDisplayScreenshot(mSSData->strStateFilePath, 0 /* u32Type */, &pu8Data, &cbData, &u32Width, &u32Height); if (RT_FAILURE(vrc)) return setError(VBOX_E_IPRT_ERROR, @@ -5650,7 +5879,7 @@ STDMETHODIMP Machine::QuerySavedScreenshotPNGSize(ULONG aScreenId, ULONG *aSize, uint32_t u32Width = 0; uint32_t u32Height = 0; - int vrc = readSavedDisplayScreenshot(mSSData->mStateFilePath, 1 /* u32Type */, &pu8Data, &cbData, &u32Width, &u32Height); + int vrc = readSavedDisplayScreenshot(mSSData->strStateFilePath, 1 /* u32Type */, &pu8Data, &cbData, &u32Width, &u32Height); if (RT_FAILURE(vrc)) return setError(VBOX_E_IPRT_ERROR, @@ -5687,7 +5916,7 @@ STDMETHODIMP Machine::ReadSavedScreenshotPNGToArray(ULONG aScreenId, ULONG *aWid uint32_t u32Width = 0; uint32_t u32Height = 0; - int vrc = readSavedDisplayScreenshot(mSSData->mStateFilePath, 1 /* u32Type */, &pu8Data, &cbData, &u32Width, &u32Height); + int vrc = readSavedDisplayScreenshot(mSSData->strStateFilePath, 1 /* u32Type */, &pu8Data, &cbData, &u32Width, &u32Height); if (RT_FAILURE(vrc)) return setError(VBOX_E_IPRT_ERROR, @@ -5879,32 +6108,116 @@ STDMETHODIMP Machine::ReadLog(ULONG aIdx, LONG64 aOffset, LONG64 aSize, ComSafeA } -STDMETHODIMP Machine::AttachHostPciDevice(LONG hostAddress, LONG desiredGuestAddress, IEventContext * /*eventContext*/, BOOL /*tryToUnbind*/) +/** + * Currently this method doesn't attach device to the running VM, + * just makes sure it's plugged on next VM start. + */ +STDMETHODIMP Machine::AttachHostPciDevice(LONG hostAddress, LONG desiredGuestAddress, BOOL /*tryToUnbind*/) { AutoCaller autoCaller(this); if (FAILED(autoCaller.rc())) return autoCaller.rc(); - AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); - ComObjPtr<PciDeviceAttachment> pda; - char name[32]; + // lock scope + { + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + HRESULT rc = checkStateDependency(MutableStateDep); + if (FAILED(rc)) return rc; - pda.createObject(); - RTStrPrintf(name, sizeof(name), "host%02x:%02x.%x", (hostAddress>>8) & 0xff, (hostAddress & 0xf8) >> 3, hostAddress & 7); - Bstr bname(name); - pda.createObject(); - pda->init(this, bname, hostAddress, desiredGuestAddress, TRUE); + ChipsetType_T aChipset = ChipsetType_PIIX3; + COMGETTER(ChipsetType)(&aChipset); + + if (aChipset != ChipsetType_ICH9) + { + return setError(E_INVALIDARG, + tr("Host PCI attachment only supported with ICH9 chipset")); + } + + // check if device with this host PCI address already attached + for (HWData::PciDeviceAssignmentList::iterator it = mHWData->mPciDeviceAssignments.begin(); + it != mHWData->mPciDeviceAssignments.end(); + ++it) + { + LONG iHostAddress = -1; + ComPtr<PciDeviceAttachment> pAttach; + pAttach = *it; + pAttach->COMGETTER(HostAddress)(&iHostAddress); + if (iHostAddress == hostAddress) + return setError(E_INVALIDARG, + tr("Device with host PCI address already attached to this VM")); + } + + ComObjPtr<PciDeviceAttachment> pda; + char name[32]; + + RTStrPrintf(name, sizeof(name), "host%02x:%02x.%x", (hostAddress>>8) & 0xff, (hostAddress & 0xf8) >> 3, hostAddress & 7); + Bstr bname(name); + pda.createObject(); + pda->init(this, bname, hostAddress, desiredGuestAddress, TRUE); + setModified(IsModified_MachineData); + mHWData.backup(); + mHWData->mPciDeviceAssignments.push_back(pda); + } - mPciDeviceAssignments.push_back(pda); return S_OK; } -STDMETHODIMP Machine::DetachHostPciDevice(LONG /*hostAddress*/) +/** + * Currently this method doesn't detach device from the running VM, + * just makes sure it's not plugged on next VM start. + */ +STDMETHODIMP Machine::DetachHostPciDevice(LONG hostAddress) { AutoCaller autoCaller(this); if (FAILED(autoCaller.rc())) return autoCaller.rc(); - AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); - return E_NOTIMPL; + ComObjPtr<PciDeviceAttachment> pAttach; + bool fRemoved = false; + HRESULT rc; + + // lock scope + { + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + rc = checkStateDependency(MutableStateDep); + if (FAILED(rc)) return rc; + + for (HWData::PciDeviceAssignmentList::iterator it = mHWData->mPciDeviceAssignments.begin(); + it != mHWData->mPciDeviceAssignments.end(); + ++it) + { + LONG iHostAddress = -1; + pAttach = *it; + pAttach->COMGETTER(HostAddress)(&iHostAddress); + if (iHostAddress != -1 && iHostAddress == hostAddress) + { + setModified(IsModified_MachineData); + mHWData.backup(); + mHWData->mPciDeviceAssignments.remove(pAttach); + fRemoved = true; + break; + } + } + } + + + /* Fire event outside of the lock */ + if (fRemoved) + { + Assert(!pAttach.isNull()); + ComPtr<IEventSource> es; + rc = mParent->COMGETTER(EventSource)(es.asOutParam()); + Assert(SUCCEEDED(rc)); + Bstr mid; + rc = this->COMGETTER(Id)(mid.asOutParam()); + Assert(SUCCEEDED(rc)); + fireHostPciDevicePlugEvent(es, mid.raw(), false /* unplugged */, true /* success */, pAttach, NULL); + } + + return fRemoved ? S_OK : setError(VBOX_E_OBJECT_NOT_FOUND, + tr("No host PCI device %08x attached"), + hostAddress + ); } STDMETHODIMP Machine::COMGETTER(PciDeviceAssignments)(ComSafeArrayOut(IPciDeviceAttachment *, aAssignments)) @@ -5916,7 +6229,7 @@ STDMETHODIMP Machine::COMGETTER(PciDeviceAssignments)(ComSafeArrayOut(IPciDevice AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - SafeIfaceArray<IPciDeviceAttachment> assignments(mPciDeviceAssignments); + SafeIfaceArray<IPciDeviceAttachment> assignments(mHWData->mPciDeviceAssignments); assignments.detachTo(ComSafeArrayOutArg(aAssignments)); return S_OK; @@ -5934,6 +6247,48 @@ STDMETHODIMP Machine::COMGETTER(BandwidthControl)(IBandwidthControl **aBandwidth return S_OK; } +STDMETHODIMP Machine::CloneTo(IMachine *pTarget, CloneMode_T mode, ComSafeArrayIn(CloneOptions_T, options), IProgress **pProgress) +{ + LogFlowFuncEnter(); + + CheckComArgNotNull(pTarget); + CheckComArgOutPointerValid(pProgress); + + /** @todo r=klaus disabled as there are apparently still cases which are + * not handled correctly */ + if (mode == CloneMode_MachineAndChildStates) + return setError(VBOX_E_NOT_SUPPORTED, + tr("The clone mode \"Machine and child states\" is not yet supported. Please try again in the next VirtualBox maintenance update")); + + /* Convert the options. */ + RTCList<CloneOptions_T> optList; + if (options != NULL) + optList = com::SafeArray<CloneOptions_T>(ComSafeArrayInArg(options)).toList(); + + if (optList.contains(CloneOptions_Link)) + { + if (!isSnapshotMachine()) + return setError(E_INVALIDARG, + tr("Linked clone can only be created from a snapshot")); + if (mode != CloneMode_MachineState) + return setError(E_INVALIDARG, + tr("Linked clone can only be created for a single machine state")); + } + AssertReturn(!(optList.contains(CloneOptions_KeepAllMACs) && optList.contains(CloneOptions_KeepNATMACs)), E_INVALIDARG); + + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + + MachineCloneVM *pWorker = new MachineCloneVM(this, static_cast<Machine*>(pTarget), mode, optList); + + HRESULT rc = pWorker->start(pProgress); + + LogFlowFuncLeave(); + + return rc; +} + // public methods for internal purposes ///////////////////////////////////////////////////////////////////////////// @@ -5948,6 +6303,18 @@ void Machine::setModified(uint32_t fl) } /** + * Adds the given IsModified_* flag to the dirty flags of the machine, taking + * care of the write locking. + * + * @param fModifications The flag to add. + */ +void Machine::setModifiedLock(uint32_t fModification) +{ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + mData->flModifications |= fModification; +} + +/** * Saves the registry entry of this machine to the given configuration node. * * @param aEntryNode Node to save the registry entry to. @@ -6069,6 +6436,44 @@ Utf8Str Machine::queryLogFilename(ULONG idx) } /** + * Composes a unique saved state filename based on the current system time. The filename is + * granular to the second so this will work so long as no more than one snapshot is taken on + * a machine per second. + * + * Before version 4.1, we used this formula for saved state files: + * Utf8StrFmt("%s%c{%RTuuid}.sav", strFullSnapshotFolder.c_str(), RTPATH_DELIMITER, mData->mUuid.raw()) + * which no longer works because saved state files can now be shared between the saved state of the + * "saved" machine and an online snapshot, and the following would cause problems: + * 1) save machine + * 2) create online snapshot from that machine state --> reusing saved state file + * 3) save machine again --> filename would be reused, breaking the online snapshot + * + * So instead we now use a timestamp. + * + * @param str + */ +void Machine::composeSavedStateFilename(Utf8Str &strStateFilePath) +{ + AutoCaller autoCaller(this); + AssertComRCReturnVoid(autoCaller.rc()); + + { + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + calculateFullPath(mUserData->s.strSnapshotFolder, strStateFilePath); + } + + RTTIMESPEC ts; + RTTimeNow(&ts); + RTTIME time; + RTTimeExplode(&time, &ts); + + strStateFilePath += RTPATH_DELIMITER; + strStateFilePath += Utf8StrFmt("%04d-%02u-%02uT%02u-%02u-%02u-%09uZ.sav", + time.i32Year, time.u8Month, time.u8MonthDay, + time.u8Hour, time.u8Minute, time.u8Second, time.u32Nanosecond); +} + +/** * @note Locks this object for writing, calls the client process * (inside the lock). */ @@ -6199,6 +6604,7 @@ HRESULT Machine::launchVMProcess(IInternalSessionControl *aControl, Utf8Str idStr = mData->mUuid.toString(); const char * args[] = {szPath, "--comment", mUserData->s.strName.c_str(), "--startvm", idStr.c_str(), 0 }; + fprintf(stderr, "SDL=%s\n", szPath); vrc = RTProcCreate(szPath, args, env, 0, &pid); } #else /* !VBOX_WITH_VBOXSDL */ @@ -7078,6 +7484,8 @@ HRESULT Machine::findSharedFolder(const Utf8Str &aName, * ensure that the media listed as attachments in the config (which have * been imported from the OVF) receive the correct registry ID. * + * -- During VM cloning. + * * @param config Machine settings from XML. * @param puuidRegistry If != NULL, Medium::setRegistryIdIfFirst() gets called with this registry ID for each attached medium in the config. * @return @@ -7096,7 +7504,7 @@ HRESULT Machine::loadMachineDataFromSettings(const settings::MachineConfigFile & // stateFile (optional) if (config.strStateFile.isEmpty()) - mSSData->mStateFilePath.setNull(); + mSSData->strStateFilePath.setNull(); else { Utf8Str stateFilePathFull(config.strStateFile); @@ -7106,13 +7514,18 @@ HRESULT Machine::loadMachineDataFromSettings(const settings::MachineConfigFile & tr("Invalid saved state file path '%s' (%Rrc)"), config.strStateFile.c_str(), vrc); - mSSData->mStateFilePath = stateFilePathFull; + mSSData->strStateFilePath = stateFilePathFull; } // snapshot folder needs special processing so set it again rc = COMSETTER(SnapshotFolder)(Bstr(config.machineUserData.strSnapshotFolder).raw()); if (FAILED(rc)) return rc; + /* Copy the extra data items (Not in any case config is already the same as + * mData->pMachineConfigFile, like when the xml files are read from disk. So + * make sure the extra data map is copied). */ + mData->pMachineConfigFile->mapExtraDataItems = config.mapExtraDataItems; + /* currentStateModified (optional, default is true) */ mData->mCurrentStateModified = config.fCurrentStateModified; @@ -7172,13 +7585,12 @@ HRESULT Machine::loadMachineDataFromSettings(const settings::MachineConfigFile & /* set the machine state to Aborted or Saved when appropriate */ if (config.fAborted) { - Assert(!mSSData->mStateFilePath.isEmpty()); - mSSData->mStateFilePath.setNull(); + mSSData->strStateFilePath.setNull(); /* no need to use setMachineState() during init() */ mData->mMachineState = MachineState_Aborted; } - else if (!mSSData->mStateFilePath.isEmpty()) + else if (!mSSData->strStateFilePath.isEmpty()) { /* no need to use setMachineState() during init() */ mData->mMachineState = MachineState_Saved; @@ -7385,6 +7797,10 @@ HRESULT Machine::loadHardware(const settings::Hardware &data) rc = mBIOSSettings->loadSettings(data.biosSettings); if (FAILED(rc)) return rc; + // Bandwidth control (must come before network adapters) + rc = mBandwidthControl->loadSettings(data.ioSettings); + if (FAILED(rc)) return rc; + /* USB Controller */ rc = mUSBController->loadSettings(data.usbController); if (FAILED(rc)) return rc; @@ -7398,7 +7814,7 @@ HRESULT Machine::loadHardware(const settings::Hardware &data) /* slot unicity is guaranteed by XML Schema */ AssertBreak(nic.ulSlot < RT_ELEMENTS(mNetworkAdapters)); - rc = mNetworkAdapters[nic.ulSlot]->loadSettings(nic); + rc = mNetworkAdapters[nic.ulSlot]->loadSettings(mBandwidthControl, nic); if (FAILED(rc)) return rc; } @@ -7451,9 +7867,18 @@ HRESULT Machine::loadHardware(const settings::Hardware &data) mHWData->mIoCacheEnabled = data.ioSettings.fIoCacheEnabled; mHWData->mIoCacheSize = data.ioSettings.ulIoCacheSize; - // Bandwidth control - rc = mBandwidthControl->loadSettings(data.ioSettings); - if (FAILED(rc)) return rc; + // Host PCI devices + for (settings::HostPciDeviceAttachmentList::const_iterator it = data.pciAttachments.begin(); + it != data.pciAttachments.end(); + ++it) + { + const settings::HostPciDeviceAttachment &hpda = *it; + ComObjPtr<PciDeviceAttachment> pda; + + pda.createObject(); + pda->loadSettings(this, hpda); + mHWData->mPciDeviceAssignments.push_back(pda); + } #ifdef VBOX_WITH_GUEST_PROPS /* Guest properties (optional) */ @@ -7742,7 +8167,10 @@ HRESULT Machine::loadStorageDevices(StorageController *aStorageController, dev.lPort, dev.lDevice, dev.deviceType, + false, dev.fPassThrough, + dev.fTempEject, + dev.fNonRotational, pBwGroup.isNull() ? Utf8Str::Empty : pBwGroup->getName()); if (FAILED(rc)) break; @@ -8030,16 +8458,11 @@ HRESULT Machine::prepareSaveSettings(bool *pfNeedsGlobalSaveSettings) *pfNeedsGlobalSaveSettings = true; } - /* update the saved state file path */ - Utf8Str path = mSSData->mStateFilePath; - if (RTPathStartsWith(path.c_str(), configDir.c_str())) - mSSData->mStateFilePath = Utf8StrFmt("%s%s", - newConfigDir.c_str(), - path.c_str() + configDir.length()); + // in the saved state file path, replace the old directory with the new directory + if (RTPathStartsWith(mSSData->strStateFilePath.c_str(), configDir.c_str())) + mSSData->strStateFilePath = newConfigDir.append(mSSData->strStateFilePath.c_str() + configDir.length()); - /* Update saved state file paths of all online snapshots. - * Note that saveSettings() will recognize name change - * and will save all snapshots in this case. */ + // and do the same thing for the saved state file paths of all the online snapshots if (mData->mFirstSnapshot) mData->mFirstSnapshot->updateSavedStatePaths(configDir.c_str(), newConfigDir.c_str()); @@ -8275,17 +8698,17 @@ void Machine::copyMachineDataToSettings(settings::MachineConfigFile &config) || ( ( mData->mMachineState == MachineState_DeletingSnapshot || mData->mMachineState == MachineState_DeletingSnapshotOnline || mData->mMachineState == MachineState_DeletingSnapshotPaused) - && (!mSSData->mStateFilePath.isEmpty()) + && (!mSSData->strStateFilePath.isEmpty()) ) ) { - Assert(!mSSData->mStateFilePath.isEmpty()); + Assert(!mSSData->strStateFilePath.isEmpty()); /* try to make the file name relative to the settings file dir */ - copyPathRelativeToMachine(mSSData->mStateFilePath, config.strStateFile); + copyPathRelativeToMachine(mSSData->strStateFilePath, config.strStateFile); } else { - Assert(mSSData->mStateFilePath.isEmpty()); + Assert(mSSData->strStateFilePath.isEmpty() || mData->mMachineState == MachineState_Saving); config.strStateFile.setNull(); } @@ -8382,7 +8805,7 @@ HRESULT Machine::saveHardware(settings::Hardware &data) /* The hardware version attribute (optional). Automatically upgrade from 1 to 2 when there is no saved state. (ugly!) */ if ( mHWData->mHWVersion == "1" - && mSSData->mStateFilePath.isEmpty() + && mSSData->strStateFilePath.isEmpty() ) mHWData->mHWVersion = "2"; /** @todo Is this safe, to update mHWVersion here? If not some other point needs to be found where this can be done. */ @@ -8550,6 +8973,21 @@ HRESULT Machine::saveHardware(settings::Hardware &data) rc = mBandwidthControl->saveSettings(data.ioSettings); if (FAILED(rc)) throw rc; + /* Host PCI devices */ + for (HWData::PciDeviceAssignmentList::const_iterator it = mHWData->mPciDeviceAssignments.begin(); + it != mHWData->mPciDeviceAssignments.end(); + ++it) + { + ComObjPtr<PciDeviceAttachment> pda = *it; + settings::HostPciDeviceAttachment hpda; + + rc = pda->saveSettings(hpda); + if (FAILED(rc)) throw rc; + + data.pciAttachments.push_back(hpda); + } + + // guest properties data.llGuestProperties.clear(); #ifdef VBOX_WITH_GUEST_PROPS @@ -8679,6 +9117,8 @@ HRESULT Machine::saveStorageDevices(ComObjPtr<StorageController> aStorageControl else dev.uuid = pMedium->getId(); dev.fPassThrough = pAttach->getPassthrough(); + dev.fTempEject = pAttach->getTempEject(); + dev.fNonRotational = pAttach->getNonRotational(); } dev.strBwGroup = pAttach->getBandwidthGroup(); @@ -8720,9 +9160,9 @@ HRESULT Machine::saveStateSettings(int aFlags) if (aFlags & SaveSTS_StateFilePath) { - if (!mSSData->mStateFilePath.isEmpty()) + if (!mSSData->strStateFilePath.isEmpty()) /* try to make the file name relative to the settings file dir */ - copyPathRelativeToMachine(mSSData->mStateFilePath, mData->pMachineConfigFile->strStateFile); + copyPathRelativeToMachine(mSSData->strStateFilePath, mData->pMachineConfigFile->strStateFile); else mData->pMachineConfigFile->strStateFile.setNull(); } @@ -8730,7 +9170,7 @@ HRESULT Machine::saveStateSettings(int aFlags) if (aFlags & SaveSTS_StateTimeStamp) { Assert( mData->mMachineState != MachineState_Aborted - || mSSData->mStateFilePath.isEmpty()); + || mSSData->strStateFilePath.isEmpty()); mData->pMachineConfigFile->timeLastStateChange = mData->mLastStateChange; @@ -8780,7 +9220,7 @@ void Machine::addMediumToRegistry(ComObjPtr<Medium> &pMedium, if (pMedium->addRegistry(uuid, false /* fRecurse */)) // registry actually changed: - mParent->addGuidToListUniquely(llRegistriesThatNeedSaving, uuid); + VirtualBox::addGuidToListUniquely(llRegistriesThatNeedSaving, uuid); if (puuid) *puuid = uuid; @@ -9000,6 +9440,9 @@ HRESULT Machine::createImplicitDiffs(IProgress *aProgress, pAtt->getDevice(), DeviceType_HardDisk, true /* aImplicit */, + false /* aPassthrough */, + false /* aTempEject */, + false /* aNonRotational */, pAtt->getBandwidthGroup()); if (FAILED(rc)) throw rc; @@ -9031,9 +9474,9 @@ HRESULT Machine::createImplicitDiffs(IProgress *aProgress, /** * Deletes implicit differencing hard disks created either by - * #createImplicitDiffs() or by #AttachMedium() and rolls back mMediaData. + * #createImplicitDiffs() or by #AttachDevice() and rolls back mMediaData. * - * Note that to delete hard disks created by #AttachMedium() this method is + * Note that to delete hard disks created by #AttachDevice() this method is * called from #fixupMedia() when the changes are rolled back. * * @param pllRegistriesThatNeedSaving Optional pointer to a list of UUIDs to receive the registry IDs that need saving @@ -9329,12 +9772,41 @@ HRESULT Machine::detachAllMedia(AutoWriteLock &writeLock, if (!pMedium.isNull()) { + AutoCaller mac(pMedium); + if (FAILED(mac.rc())) return mac.rc(); + AutoReadLock lock(pMedium COMMA_LOCKVAL_SRC_POS); DeviceType_T devType = pMedium->getDeviceType(); if ( ( cleanupMode == CleanupMode_DetachAllReturnHardDisksOnly && devType == DeviceType_HardDisk) || (cleanupMode == CleanupMode_Full) ) + { llMedia.push_back(pMedium); + ComObjPtr<Medium> pParent = pMedium->getParent(); + /* + * Search for medias which are not attached to any machine, but + * in the chain to an attached disk. Mediums are only consided + * if they are: + * - have only one child + * - no references to any machines + * - are of normal medium type + */ + while (!pParent.isNull()) + { + AutoCaller mac1(pParent); + if (FAILED(mac1.rc())) return mac1.rc(); + AutoReadLock lock1(pParent COMMA_LOCKVAL_SRC_POS); + if (pParent->getChildren().size() == 1) + { + if ( pParent->getMachineBackRefCount() == 0 + && pParent->getType() == MediumType_Normal + && find(llMedia.begin(), llMedia.end(), pParent) == llMedia.end()) + llMedia.push_back(pParent); + }else + break; + pParent = pParent->getParent(); + } + } } // real machine: then we need to use the proper method @@ -9508,10 +9980,11 @@ void Machine::commitMedia(bool aOnline /*= false*/) * This is necessary because the stored parent will point to the * session machine otherwise and cause crashes or errors later * when the session machine gets invalid. - * - * @todo: Change the MediumAttachment class to behave like any other class - * in this regard by creating peer MediumAttachment objects for - * session machines and share the data with the peer machine. + */ + /** @todo Change the MediumAttachment class to behave like any other + * class in this regard by creating peer MediumAttachment + * objects for session machines and share the data with the peer + * machine. */ for (MediaData::AttachmentList::const_iterator it = mMediaData->mAttachments.begin(); it != mMediaData->mAttachments.end(); @@ -9537,6 +10010,8 @@ void Machine::commitMedia(bool aOnline /*= false*/) * by this function if the caller should invoke VirtualBox::saveSettings() because the global settings have changed. * * @note Locks this object for writing! + * + * @todo r=dj this needs a pllRegistriesThatNeedSaving as well */ void Machine::rollbackMedia() { @@ -9945,6 +10420,31 @@ void Machine::copyFrom(Machine *aThat) mParallelPorts[slot]->copyFrom(aThat->mParallelPorts[slot]); } +/** + * Returns whether the given storage controller is hotplug capable. + * + * @returns true if the controller supports hotplugging + * false otherwise. + * @param enmCtrlType The controller type to check for. + */ +bool Machine::isControllerHotplugCapable(StorageControllerType_T enmCtrlType) +{ + switch (enmCtrlType) + { + case StorageControllerType_IntelAhci: + return true; + case StorageControllerType_LsiLogic: + case StorageControllerType_LsiLogicSas: + case StorageControllerType_BusLogic: + case StorageControllerType_PIIX3: + case StorageControllerType_PIIX4: + case StorageControllerType_ICH6: + case StorageControllerType_I82078: + default: + return false; + } +} + #ifdef VBOX_WITH_RESOURCE_USAGE_API void Machine::registerMetrics(PerformanceCollector *aCollector, Machine *aMachine, RTPROCESS pid) @@ -9992,8 +10492,11 @@ void Machine::registerMetrics(PerformanceCollector *aCollector, Machine *aMachin new pm::AggregateMax())); - /* Guest metrics */ - mGuestHAL = new pm::CollectorGuestHAL(this, hal); + /* Guest metrics collector */ + mCollectorGuest = new pm::CollectorGuest(aMachine, pid); + aCollector->registerGuest(mCollectorGuest); + LogAleksey(("{%p} " LOG_FN_FMT ": mCollectorGuest=%p\n", + this, __PRETTY_FUNCTION__, mCollectorGuest)); /* Create sub metrics */ pm::SubMetric *guestLoadUser = new pm::SubMetric("Guest/CPU/Load/User", @@ -10013,10 +10516,13 @@ void Machine::registerMetrics(PerformanceCollector *aCollector, Machine *aMachin pm::SubMetric *guestPagedTotal = new pm::SubMetric("Guest/Pagefile/Usage/Total", "Total amount of space in the page file."); /* Create and register base metrics */ - pm::BaseMetric *guestCpuLoad = new pm::GuestCpuLoad(mGuestHAL, aMachine, guestLoadUser, guestLoadKernel, guestLoadIdle); + pm::BaseMetric *guestCpuLoad = new pm::GuestCpuLoad(mCollectorGuest, aMachine, + guestLoadUser, guestLoadKernel, guestLoadIdle); aCollector->registerBaseMetric(guestCpuLoad); - pm::BaseMetric *guestCpuMem = new pm::GuestRamUsage(mGuestHAL, aMachine, guestMemTotal, guestMemFree, guestMemBalloon, guestMemShared, + pm::BaseMetric *guestCpuMem = new pm::GuestRamUsage(mCollectorGuest, aMachine, + guestMemTotal, guestMemFree, + guestMemBalloon, guestMemShared, guestMemCache, guestPagedTotal); aCollector->registerBaseMetric(guestCpuMem); @@ -10075,12 +10581,6 @@ void Machine::unregisterMetrics(PerformanceCollector *aCollector, Machine *aMach aCollector->unregisterMetricsFor(aMachine); aCollector->unregisterBaseMetricsFor(aMachine); } - - if (mGuestHAL) - { - delete mGuestHAL; - mGuestHAL = NULL; - } } #endif /* VBOX_WITH_RESOURCE_USAGE_API */ @@ -10104,7 +10604,7 @@ HRESULT SessionMachine::FinalConstruct() # error "Port me!" #endif - return S_OK; + return BaseFinalConstruct(); } void SessionMachine::FinalRelease() @@ -10112,6 +10612,8 @@ void SessionMachine::FinalRelease() LogFlowThisFunc(("\n")); uninit(Uninit::Unexpected); + + BaseFinalRelease(); } /** @@ -10388,10 +10890,27 @@ void SessionMachine::uninit(Uninit::Reason aReason) // and others need mParent lock, and USB needs host lock. AutoMultiWriteLock3 multilock(mParent, mParent->host(), this COMMA_LOCKVAL_SRC_POS); + LogAleksey(("{%p} " LOG_FN_FMT ": mCollectorGuest=%p\n", + this, __PRETTY_FUNCTION__, mCollectorGuest)); + if (mCollectorGuest) + { + mParent->performanceCollector()->unregisterGuest(mCollectorGuest); + // delete mCollectorGuest; => CollectorGuestManager::destroyUnregistered() + mCollectorGuest = NULL; + } +#if 0 // Trigger async cleanup tasks, avoid doing things here which are not // vital to be done immediately and maybe need more locks. This calls // Machine::unregisterMetrics(). mParent->onMachineUninit(mPeer); +#else + /* + * It is safe to call Machine::unregisterMetrics() here because + * PerformanceCollector::samplerCallback no longer accesses guest methods + * holding the lock. + */ + unregisterMetrics(mParent->performanceCollector(), mPeer); +#endif if (aReason == Uninit::Abnormal) { @@ -10410,8 +10929,9 @@ void SessionMachine::uninit(Uninit::Reason aReason) rollback(false /* aNotify */); } - Assert(mConsoleTaskData.mStateFilePath.isEmpty() || !mConsoleTaskData.mSnapshot); - if (!mConsoleTaskData.mStateFilePath.isEmpty()) + Assert( mConsoleTaskData.strStateFilePath.isEmpty() + || !mConsoleTaskData.mSnapshot); + if (!mConsoleTaskData.strStateFilePath.isEmpty()) { LogWarningThisFunc(("canceling failed save state request!\n")); endSavingState(E_FAIL, tr("Machine terminated with pending save state!")); @@ -10423,11 +10943,13 @@ void SessionMachine::uninit(Uninit::Reason aReason) /* delete all differencing hard disks created (this will also attach * their parents back by rolling back mMediaData) */ rollbackMedia(); - /* delete the saved state file (it might have been already created) */ - if (mConsoleTaskData.mSnapshot->stateFilePath().length()) - RTFileDelete(mConsoleTaskData.mSnapshot->stateFilePath().c_str()); + // delete the saved state file (it might have been already created) + // AFTER killing the snapshot so that releaseSavedStateFile() won't + // think it's still in use + Utf8Str strStateFile = mConsoleTaskData.mSnapshot->getStateFilePath(); mConsoleTaskData.mSnapshot->uninit(); + releaseSavedStateFile(strStateFile, NULL /* pSnapshotToIgnore */ ); } if (!mData->mSession.mType.isEmpty()) @@ -11009,7 +11531,7 @@ STDMETHODIMP SessionMachine::BeginSavingState(IProgress **aProgress, BSTR *aStat AssertReturn( mData->mMachineState == MachineState_Paused && mConsoleTaskData.mLastState == MachineState_Null - && mConsoleTaskData.mStateFilePath.isEmpty(), + && mConsoleTaskData.strStateFilePath.isEmpty(), E_FAIL); /* create a progress object to track operation completion */ @@ -11020,27 +11542,20 @@ STDMETHODIMP SessionMachine::BeginSavingState(IProgress **aProgress, BSTR *aStat Bstr(tr("Saving the execution state of the virtual machine")).raw(), FALSE /* aCancelable */); - Bstr stateFilePath; + Utf8Str strStateFilePath; /* stateFilePath is null when the machine is not running */ if (mData->mMachineState == MachineState_Paused) - { - Utf8Str strFullSnapshotFolder; - calculateFullPath(mUserData->s.strSnapshotFolder, strFullSnapshotFolder); - stateFilePath = Utf8StrFmt("%s%c{%RTuuid}.sav", - strFullSnapshotFolder.c_str(), - RTPATH_DELIMITER, - mData->mUuid.raw()); - } + composeSavedStateFilename(strStateFilePath); /* fill in the console task data */ mConsoleTaskData.mLastState = mData->mMachineState; - mConsoleTaskData.mStateFilePath = stateFilePath; + mConsoleTaskData.strStateFilePath = strStateFilePath; mConsoleTaskData.mProgress = pProgress; /* set the state to Saving (this is expected by Console::SaveState()) */ setMachineState(MachineState_Saving); - stateFilePath.cloneTo(aStateFilePath); + strStateFilePath.cloneTo(aStateFilePath); pProgress.queryInterfaceTo(aProgress); return S_OK; @@ -11062,7 +11577,7 @@ STDMETHODIMP SessionMachine::EndSavingState(LONG iResult, IN_BSTR aErrMsg) AssertReturn( ( (SUCCEEDED(iResult) && mData->mMachineState == MachineState_Saved) || (FAILED(iResult) && mData->mMachineState == MachineState_Saving)) && mConsoleTaskData.mLastState != MachineState_Null - && !mConsoleTaskData.mStateFilePath.isEmpty(), + && !mConsoleTaskData.strStateFilePath.isEmpty(), E_FAIL); /* @@ -11104,7 +11619,7 @@ STDMETHODIMP SessionMachine::AdoptSavedState(IN_BSTR aSavedStateFile) aSavedStateFile, vrc); - mSSData->mStateFilePath = stateFilePathFull; + mSSData->strStateFilePath = stateFilePathFull; /* The below setMachineState() will detect the state transition and will * update the settings file */ @@ -11177,8 +11692,8 @@ STDMETHODIMP SessionMachine::PushGuestProperty(IN_BSTR aName, using namespace guestProp; CheckComArgStrNotEmptyOrNull(aName); - if (aValue != NULL && (!VALID_PTR(aValue) || !VALID_PTR(aFlags))) - return E_POINTER; /* aValue can be NULL to indicate deletion */ + CheckComArgMaybeNull(aValue); + CheckComArgMaybeNull(aFlags); try { @@ -11280,6 +11795,85 @@ STDMETHODIMP SessionMachine::PushGuestProperty(IN_BSTR aName, #endif } +STDMETHODIMP SessionMachine::EjectMedium(IMediumAttachment *aAttachment, + IMediumAttachment **aNewAttachment) +{ + CheckComArgNotNull(aAttachment); + CheckComArgOutPointerValid(aNewAttachment); + + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + // request the host lock first, since might be calling Host methods for getting host drives; + // next, protect the media tree all the while we're in here, as well as our member variables + AutoMultiWriteLock3 multiLock(mParent->host()->lockHandle(), + this->lockHandle(), + &mParent->getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS); + + ComObjPtr<MediumAttachment> pAttach = static_cast<MediumAttachment *>(aAttachment); + + Bstr ctrlName; + LONG lPort; + LONG lDevice; + bool fTempEject; + { + AutoCaller autoAttachCaller(this); + if (FAILED(autoAttachCaller.rc())) return autoAttachCaller.rc(); + + AutoReadLock attLock(pAttach COMMA_LOCKVAL_SRC_POS); + + /* Need to query the details first, as the IMediumAttachment reference + * might be to the original settings, which we are going to change. */ + ctrlName = pAttach->getControllerName(); + lPort = pAttach->getPort(); + lDevice = pAttach->getDevice(); + fTempEject = pAttach->getTempEject(); + } + + if (!fTempEject) + { + /* Remember previously mounted medium. The medium before taking the + * backup is not necessarily the same thing. */ + ComObjPtr<Medium> oldmedium; + oldmedium = pAttach->getMedium(); + + setModified(IsModified_Storage); + mMediaData.backup(); + + // The backup operation makes the pAttach reference point to the + // old settings. Re-get the correct reference. + pAttach = findAttachment(mMediaData->mAttachments, + ctrlName.raw(), + lPort, + lDevice); + + { + AutoCaller autoAttachCaller(this); + if (FAILED(autoAttachCaller.rc())) return autoAttachCaller.rc(); + + AutoWriteLock attLock(pAttach COMMA_LOCKVAL_SRC_POS); + if (!oldmedium.isNull()) + oldmedium->removeBackReference(mData->mUuid); + + pAttach->updateMedium(NULL); + pAttach->updateEjected(); + } + + setModified(IsModified_Storage); + } + else + { + { + AutoWriteLock attLock(pAttach COMMA_LOCKVAL_SRC_POS); + pAttach->updateEjected(); + } + } + + pAttach.queryInterfaceTo(aNewAttachment); + + return S_OK; +} + // public methods only for internal purposes ///////////////////////////////////////////////////////////////////////////// @@ -11648,6 +12242,29 @@ HRESULT SessionMachine::onBandwidthGroupChange(IBandwidthGroup *aBandwidthGroup) } /** + * @note Locks this object for reading. + */ +HRESULT SessionMachine::onStorageDeviceChange(IMediumAttachment *aAttachment, BOOL aRemove) +{ + LogFlowThisFunc(("\n")); + + AutoCaller autoCaller(this); + AssertComRCReturn(autoCaller.rc(), autoCaller.rc()); + + ComPtr<IInternalSessionControl> directControl; + { + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + directControl = mData->mSession.mDirectControl; + } + + /* ignore notifications sent after #OnSessionEnd() is called */ + if (!directControl) + return S_OK; + + return directControl->OnStorageDeviceChange(aAttachment, aRemove); +} + +/** * Returns @c true if this machine's USB controller reports it has a matching * filter for the given USB device and @c false otherwise. * @@ -11773,7 +12390,7 @@ HRESULT SessionMachine::endSavingState(HRESULT aRc, const Utf8Str &aErrMsg) if (SUCCEEDED(aRc)) { - mSSData->mStateFilePath = mConsoleTaskData.mStateFilePath; + mSSData->strStateFilePath = mConsoleTaskData.strStateFilePath; /* save all VM settings */ rc = saveSettings(NULL); @@ -11782,8 +12399,10 @@ HRESULT SessionMachine::endSavingState(HRESULT aRc, const Utf8Str &aErrMsg) } else { - /* delete the saved state file (it might have been already created) */ - RTFileDelete(mConsoleTaskData.mStateFilePath.c_str()); + // delete the saved state file (it might have been already created); + // we need not check whether this is shared with a snapshot here because + // we certainly created this saved state file here anew + RTFileDelete(mConsoleTaskData.strStateFilePath.c_str()); } /* notify the progress object about operation completion */ @@ -11803,7 +12422,7 @@ HRESULT SessionMachine::endSavingState(HRESULT aRc, const Utf8Str &aErrMsg) /* clear out the temporary saved state data */ mConsoleTaskData.mLastState = MachineState_Null; - mConsoleTaskData.mStateFilePath.setNull(); + mConsoleTaskData.strStateFilePath.setNull(); mConsoleTaskData.mProgress.setNull(); LogFlowThisFuncLeave(); @@ -11811,6 +12430,33 @@ HRESULT SessionMachine::endSavingState(HRESULT aRc, const Utf8Str &aErrMsg) } /** + * Deletes the given file if it is no longer in use by either the current machine state + * (if the machine is "saved") or any of the machine's snapshots. + * + * Note: This checks mSSData->strStateFilePath, which is shared by the Machine and SessionMachine + * but is different for each SnapshotMachine. When calling this, the order of calling this + * function on the one hand and changing that variable OR the snapshots tree on the other hand + * is therefore critical. I know, it's all rather messy. + * + * @param strStateFile + * @param pSnapshotToIgnore Passed to Snapshot::sharesSavedStateFile(); this snapshot is ignored in the test for whether the saved state file is in use. + */ +void SessionMachine::releaseSavedStateFile(const Utf8Str &strStateFile, + Snapshot *pSnapshotToIgnore) +{ + // it is safe to delete this saved state file if it is not currently in use by the machine ... + if ( (strStateFile.isNotEmpty()) + && (strStateFile != mSSData->strStateFilePath) // session machine's saved state + ) + // ... and it must also not be shared with other snapshots + if ( !mData->mFirstSnapshot + || !mData->mFirstSnapshot->sharesSavedStateFile(strStateFile, pSnapshotToIgnore) + // this checks the SnapshotMachine's state file paths + ) + RTFileDelete(strStateFile.c_str()); +} + +/** * Locks the attached media. * * All attached hard disks are locked for writing and DVD/floppy are locked for @@ -12062,10 +12708,17 @@ HRESULT SessionMachine::setMachineState(MachineState_T aMachineState) { if (mRemoveSavedState) { - Assert(!mSSData->mStateFilePath.isEmpty()); - RTFileDelete(mSSData->mStateFilePath.c_str()); + Assert(!mSSData->strStateFilePath.isEmpty()); + + // it is safe to delete the saved state file if ... + if ( !mData->mFirstSnapshot // ... we have no snapshots or + || !mData->mFirstSnapshot->sharesSavedStateFile(mSSData->strStateFilePath, NULL /* pSnapshotToIgnore */) + // ... none of the snapshots share the saved state file + ) + RTFileDelete(mSSData->strStateFilePath.c_str()); } - mSSData->mStateFilePath.setNull(); + + mSSData->strStateFilePath.setNull(); stsFlags |= SaveSTS_StateFilePath; } @@ -12089,7 +12742,7 @@ HRESULT SessionMachine::setMachineState(MachineState_T aMachineState) && aMachineState == MachineState_Saved) { /* the saved state file was adopted */ - Assert(!mSSData->mStateFilePath.isEmpty()); + Assert(!mSSData->strStateFilePath.isEmpty()); stsFlags |= SaveSTS_StateFilePath; } |