diff options
Diffstat (limited to 'src/VBox/Main/src-server/MachineImpl.cpp')
-rw-r--r-- | src/VBox/Main/src-server/MachineImpl.cpp | 2706 |
1 files changed, 1968 insertions, 738 deletions
diff --git a/src/VBox/Main/src-server/MachineImpl.cpp b/src/VBox/Main/src-server/MachineImpl.cpp index 2ffa9b51..d7905917 100644 --- a/src/VBox/Main/src-server/MachineImpl.cpp +++ b/src/VBox/Main/src-server/MachineImpl.cpp @@ -4,7 +4,7 @@ */ /* - * Copyright (C) 2004-2012 Oracle Corporation + * Copyright (C) 2004-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; @@ -23,23 +23,17 @@ # define __STDC_CONSTANT_MACROS #endif -#ifdef VBOX_WITH_SYS_V_IPC_SESSION_WATCHER -# include <errno.h> -# include <sys/types.h> -# include <sys/stat.h> -# include <sys/ipc.h> -# include <sys/sem.h> -#endif - #include "Logging.h" #include "VirtualBoxImpl.h" #include "MachineImpl.h" +#include "ClientToken.h" #include "ProgressImpl.h" #include "ProgressProxyImpl.h" #include "MediumAttachmentImpl.h" #include "MediumImpl.h" #include "MediumLock.h" #include "USBControllerImpl.h" +#include "USBDeviceFiltersImpl.h" #include "HostImpl.h" #include "SharedFolderImpl.h" #include "GuestOSTypeImpl.h" @@ -48,9 +42,9 @@ #include "StorageControllerImpl.h" #include "DisplayImpl.h" #include "DisplayUtils.h" -#include "BandwidthControlImpl.h" #include "MachineImplCloneVM.h" #include "AutostartDb.h" +#include "SystemPropertiesImpl.h" // generated header #include "VBoxEvents.h" @@ -73,6 +67,7 @@ #include <iprt/cpp/xml.h> /* xml::XmlFileWriter::s_psz*Suff. */ #include <iprt/sha.h> #include <iprt/string.h> +#include <iprt/base64.h> #include <VBox/com/array.h> #include <VBox/com/list.h> @@ -161,14 +156,18 @@ Machine::HWData::HWData() mCPUHotPlugEnabled = false; mMemoryBalloonSize = 0; mPageFusionEnabled = false; + mGraphicsControllerType = GraphicsControllerType_VBoxVGA; mVRAMSize = 8; mAccelerate3DEnabled = false; mAccelerate2DVideoEnabled = false; mMonitorCount = 1; - mVideoCaptureFile = "Test.webm"; - mVideoCaptureWidth = 640; - mVideoCaptureHeight = 480; - mVideoCaptureEnabled = true; + mVideoCaptureWidth = 1024; + mVideoCaptureHeight = 768; + mVideoCaptureRate = 512; + mVideoCaptureFPS = 25; + mVideoCaptureEnabled = false; + for (unsigned i = 0; i < RT_ELEMENTS(maVideoCaptureScreens); i++) + maVideoCaptureScreens[i] = true; mHWVirtExEnabled = true; mHWVirtExNestedPagingEnabled = true; @@ -179,18 +178,16 @@ Machine::HWData::HWData() mHWVirtExLargePagesEnabled = false; #endif mHWVirtExVPIDEnabled = true; + mHWVirtExUXEnabled = true; mHWVirtExForceEnabled = false; -#if defined(RT_OS_DARWIN) || defined(RT_OS_WINDOWS) - mHWVirtExExclusive = false; -#else - mHWVirtExExclusive = true; -#endif #if HC_ARCH_BITS == 64 || defined(RT_OS_WINDOWS) || defined(RT_OS_DARWIN) mPAEEnabled = true; #else mPAEEnabled = false; #endif + mLongMode = HC_ARCH_BITS == 64 ? settings::Hardware::LongMode_Enabled : settings::Hardware::LongMode_Disabled; mSyntheticCpu = false; + mTripleFaultReset = false; mHPETEnabled = false; /* default boot order: floppy - DVD - HDD */ @@ -243,13 +240,15 @@ Machine::MediaData::~MediaData() // constructor / destructor ///////////////////////////////////////////////////////////////////////////// -Machine::Machine() - : mCollectorGuest(NULL), - mPeer(NULL), - mParent(NULL), - mSerialPorts(), - mParallelPorts(), - uRegistryNeedsSaving(0) +Machine::Machine() : +#ifdef VBOX_WITH_RESOURCE_USAGE_API + mCollectorGuest(NULL), +#endif + mPeer(NULL), + mParent(NULL), + mSerialPorts(), + mParallelPorts(), + uRegistryNeedsSaving(0) {} Machine::~Machine() @@ -349,6 +348,10 @@ HRESULT Machine::init(VirtualBox *aParent, /* Apply serial port defaults */ for (ULONG slot = 0; slot < RT_ELEMENTS(mSerialPorts); ++slot) mSerialPorts[slot]->applyDefaults(aOsType); + + /* Let the OS type select 64-bit ness. */ + mHWData->mLongMode = aOsType->is64Bit() + ? settings::Hardware::LongMode_Enabled : settings::Hardware::LongMode_Disabled; } /* At this point the changing of the current state modification @@ -568,6 +571,10 @@ HRESULT Machine::init(VirtualBox *aParent, autoInitSpan.setSucceeded(); else { + /* Ignore all errors from unregistering, they would destroy + * the more interesting error information we already have, + * pinpointing the issue with the VM config. */ + ErrorInfoKeeper eik; autoInitSpan.setLimited(); // uninit media from this machine's media registry, or else @@ -684,7 +691,7 @@ HRESULT Machine::registeredInit() { AssertReturn(!isSessionMachine(), E_FAIL); AssertReturn(!isSnapshotMachine(), E_FAIL); - AssertReturn(!mData->mUuid.isEmpty(), E_FAIL); + AssertReturn(mData->mUuid.isValid(), E_FAIL); AssertReturn(!mData->mAccessible, E_FAIL); HRESULT rc = initDataAndChildObjects(); @@ -853,7 +860,8 @@ void Machine::uninit() * "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 + + if (uuidMachine.isValid() && !uuidMachine.isZero()) // can be empty if we're called from a failure of Machine::init mParent->unregisterMachineMedia(uuidMachine); // has machine been modified? @@ -898,6 +906,10 @@ STDMETHODIMP Machine::COMGETTER(Accessible)(BOOL *aAccessible) LogFlowThisFunc(("ENTER\n")); + /* In some cases (medium registry related), it is necessary to be able to + * go through the list of all machines. Happens when an inaccessible VM + * has a sensible medium registry. */ + AutoReadLock mllock(mParent->getMachinesListLockHandle() COMMA_LOCKVAL_SRC_POS); AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); HRESULT rc = S_OK; @@ -999,8 +1011,9 @@ STDMETHODIMP Machine::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()) - return setError(E_INVALIDARG, tr("A machine cannot have a UUID as its name")); + + if (test.isValid()) + return setError(E_INVALIDARG, tr("A machine cannot have a UUID as its name")); AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); @@ -1274,8 +1287,8 @@ STDMETHODIMP Machine::COMSETTER(ChipsetType)(ChipsetType_T aChipsetType) // Resize network adapter array, to be finalized on commit/rollback. // We must not throw away entries yet, otherwise settings are lost // without a way to roll back. - uint32_t newCount = Global::getMaxNetworkAdapters(aChipsetType); - uint32_t oldCount = mNetworkAdapters.size(); + size_t newCount = Global::getMaxNetworkAdapters(aChipsetType); + size_t oldCount = mNetworkAdapters.size(); if (newCount > oldCount) { mNetworkAdapters.resize(newCount); @@ -1337,7 +1350,7 @@ STDMETHODIMP Machine::COMGETTER(HardwareUUID)(BSTR *aUUID) AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - if (!mHWData->mHardwareUUID.isEmpty()) + if (!mHWData->mHardwareUUID.isZero()) mHWData->mHardwareUUID.toUtf16().cloneTo(aUUID); else mData->mUuid.toUtf16().cloneTo(aUUID); @@ -1348,7 +1361,7 @@ STDMETHODIMP Machine::COMGETTER(HardwareUUID)(BSTR *aUUID) STDMETHODIMP Machine::COMSETTER(HardwareUUID)(IN_BSTR aUUID) { Guid hardwareUUID(aUUID); - if (hardwareUUID.isEmpty()) + if (!hardwareUUID.isValid()) return E_INVALIDARG; AutoCaller autoCaller(this); @@ -1500,7 +1513,7 @@ STDMETHODIMP Machine::COMSETTER(CPUExecutionCap)(ULONG aExecutionCap) mHWData.backup(); mHWData->mCpuExecutionCap = aExecutionCap; - /* Save settings if online - todo why is this required?? */ + /** Save settings if online - @todo why is this required? -- @bugref{6818} */ if (Global::IsOnline(mData->mMachineState)) saveSettings(NULL); @@ -1508,21 +1521,21 @@ STDMETHODIMP Machine::COMSETTER(CPUExecutionCap)(ULONG aExecutionCap) } -STDMETHODIMP Machine::COMGETTER(CPUHotPlugEnabled)(BOOL *enabled) +STDMETHODIMP Machine::COMGETTER(CPUHotPlugEnabled)(BOOL *aEnabled) { - CheckComArgOutPointerValid(enabled); + CheckComArgOutPointerValid(aEnabled); AutoCaller autoCaller(this); if (FAILED(autoCaller.rc())) return autoCaller.rc(); AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - *enabled = mHWData->mCPUHotPlugEnabled; + *aEnabled = mHWData->mCPUHotPlugEnabled; return S_OK; } -STDMETHODIMP Machine::COMSETTER(CPUHotPlugEnabled)(BOOL enabled) +STDMETHODIMP Machine::COMSETTER(CPUHotPlugEnabled)(BOOL aEnabled) { HRESULT rc = S_OK; @@ -1534,9 +1547,9 @@ STDMETHODIMP Machine::COMSETTER(CPUHotPlugEnabled)(BOOL enabled) rc = checkStateDependency(MutableStateDep); if (FAILED(rc)) return rc; - if (mHWData->mCPUHotPlugEnabled != enabled) + if (mHWData->mCPUHotPlugEnabled != aEnabled) { - if (enabled) + if (aEnabled) { setModified(IsModified_MachineData); mHWData.backup(); @@ -1575,31 +1588,31 @@ STDMETHODIMP Machine::COMSETTER(CPUHotPlugEnabled)(BOOL enabled) } } - mHWData->mCPUHotPlugEnabled = enabled; + mHWData->mCPUHotPlugEnabled = aEnabled; return rc; } -STDMETHODIMP Machine::COMGETTER(EmulatedUSBCardReaderEnabled)(BOOL *enabled) +STDMETHODIMP Machine::COMGETTER(EmulatedUSBCardReaderEnabled)(BOOL *aEnabled) { #ifdef VBOX_WITH_USB_CARDREADER - CheckComArgOutPointerValid(enabled); + CheckComArgOutPointerValid(aEnabled); AutoCaller autoCaller(this); if (FAILED(autoCaller.rc())) return autoCaller.rc(); AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - *enabled = mHWData->mEmulatedUSBCardReaderEnabled; + *aEnabled = mHWData->mEmulatedUSBCardReaderEnabled; return S_OK; #else - NOREF(enabled); + NOREF(aEnabled); return E_NOTIMPL; #endif } -STDMETHODIMP Machine::COMSETTER(EmulatedUSBCardReaderEnabled)(BOOL enabled) +STDMETHODIMP Machine::COMSETTER(EmulatedUSBCardReaderEnabled)(BOOL aEnabled) { #ifdef VBOX_WITH_USB_CARDREADER AutoCaller autoCaller(this); @@ -1611,41 +1624,29 @@ STDMETHODIMP Machine::COMSETTER(EmulatedUSBCardReaderEnabled)(BOOL enabled) setModified(IsModified_MachineData); mHWData.backup(); - mHWData->mEmulatedUSBCardReaderEnabled = enabled; + mHWData->mEmulatedUSBCardReaderEnabled = aEnabled; return S_OK; #else - NOREF(enabled); + NOREF(aEnabled); return E_NOTIMPL; #endif } -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) +STDMETHODIMP Machine::COMGETTER(HPETEnabled)(BOOL *aEnabled) { - CheckComArgOutPointerValid(enabled); + CheckComArgOutPointerValid(aEnabled); AutoCaller autoCaller(this); if (FAILED(autoCaller.rc())) return autoCaller.rc(); AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - *enabled = mHWData->mHPETEnabled; + *aEnabled = mHWData->mHPETEnabled; return S_OK; } -STDMETHODIMP Machine::COMSETTER(HPETEnabled)(BOOL enabled) +STDMETHODIMP Machine::COMSETTER(HPETEnabled)(BOOL aEnabled) { HRESULT rc = S_OK; @@ -1659,12 +1660,12 @@ STDMETHODIMP Machine::COMSETTER(HPETEnabled)(BOOL enabled) setModified(IsModified_MachineData); mHWData.backup(); - mHWData->mHPETEnabled = enabled; + mHWData->mHPETEnabled = aEnabled; return rc; } -STDMETHODIMP Machine::COMGETTER(VideoCaptureEnabled)(BOOL * fEnabled) +STDMETHODIMP Machine::COMGETTER(VideoCaptureEnabled)(BOOL *fEnabled) { AutoCaller autoCaller(this); if (FAILED(autoCaller.rc())) return autoCaller.rc(); @@ -1675,23 +1676,102 @@ STDMETHODIMP Machine::COMGETTER(VideoCaptureEnabled)(BOOL * fEnabled) return S_OK; } -STDMETHODIMP Machine::COMSETTER(VideoCaptureEnabled)(BOOL fEnabled) +STDMETHODIMP Machine::COMSETTER(VideoCaptureEnabled)(BOOL fEnabled) { + HRESULT rc = S_OK; + AutoCaller autoCaller(this); if (FAILED(autoCaller.rc())) return autoCaller.rc(); - AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + setModified(IsModified_MachineData); + mHWData.backup(); mHWData->mVideoCaptureEnabled = fEnabled; + + alock.release(); + rc = onVideoCaptureChange(); + alock.acquire(); + if (FAILED(rc)) + { + /* + * Normally we would do the actual change _after_ onVideoCaptureChange() succeeded. + * We cannot do this because that function uses Machine::GetVideoCaptureEnabled to + * determine if it should start or stop capturing. Therefore we need to manually + * undo change. + */ + mHWData->mVideoCaptureEnabled = mHWData.backedUpData()->mVideoCaptureEnabled; + return rc; + } + + /** Save settings if online - @todo why is this required? -- @bugref{6818} */ + if (Global::IsOnline(mData->mMachineState)) + saveSettings(NULL); + + return rc; +} + +STDMETHODIMP Machine::COMGETTER(VideoCaptureScreens)(ComSafeArrayOut(BOOL, aScreens)) +{ + CheckComArgOutSafeArrayPointerValid(aScreens); + + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + SafeArray<BOOL> screens(mHWData->mMonitorCount); + for (unsigned i = 0; i < screens.size(); i++) + screens[i] = mHWData->maVideoCaptureScreens[i]; + screens.detachTo(ComSafeArrayOutArg(aScreens)); + return S_OK; +} + +STDMETHODIMP Machine::COMSETTER(VideoCaptureScreens)(ComSafeArrayIn(BOOL, aScreens)) +{ + SafeArray<BOOL> screens(ComSafeArrayInArg(aScreens)); + AssertReturn(screens.size() <= RT_ELEMENTS(mHWData->maVideoCaptureScreens), E_INVALIDARG); + bool fChanged = false; + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + for (unsigned i = 0; i < screens.size(); i++) + { + if (mHWData->maVideoCaptureScreens[i] != RT_BOOL(screens[i])) + { + mHWData->maVideoCaptureScreens[i] = RT_BOOL(screens[i]); + fChanged = true; + } + } + if (fChanged) + { + alock.release(); + HRESULT rc = onVideoCaptureChange(); + alock.acquire(); + if (FAILED(rc)) return rc; + setModified(IsModified_MachineData); + + /** Save settings if online - @todo why is this required? -- @bugref{6818} */ + if (Global::IsOnline(mData->mMachineState)) + saveSettings(NULL); + } + return S_OK; } -STDMETHODIMP Machine::COMGETTER(VideoCaptureFile)(BSTR * apFile) +STDMETHODIMP Machine::COMGETTER(VideoCaptureFile)(BSTR *apFile) { AutoCaller autoCaller(this); if (FAILED(autoCaller.rc())) return autoCaller.rc(); AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - mHWData->mVideoCaptureFile.cloneTo(apFile); + if (mHWData->mVideoCaptureFile.isEmpty()) + { + Utf8Str defaultFile; + getDefaultVideoCaptureFile(defaultFile); + defaultFile.cloneTo(apFile); + } + else + mHWData->mVideoCaptureFile.cloneTo(apFile); return S_OK; } @@ -1702,54 +1782,181 @@ STDMETHODIMP Machine::COMSETTER(VideoCaptureFile)(IN_BSTR aFile) if (FAILED(autoCaller.rc())) return autoCaller.rc(); AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); - if(strFile.isEmpty()) - strFile = "VideoCap.webm"; + + if ( Global::IsOnline(mData->mMachineState) + && mHWData->mVideoCaptureEnabled) + return setError(E_INVALIDARG, tr("Cannot change parameters while capturing is enabled")); + + if (!RTPathStartsWithRoot(strFile.c_str())) + return setError(E_INVALIDARG, tr("Video capture file name '%s' is not absolute"), strFile.c_str()); + + if (!strFile.isEmpty()) + { + Utf8Str defaultFile; + getDefaultVideoCaptureFile(defaultFile); + if (!RTPathCompare(strFile.c_str(), defaultFile.c_str())) + strFile.setNull(); + } + + setModified(IsModified_MachineData); + mHWData.backup(); mHWData->mVideoCaptureFile = strFile; + return S_OK; } +STDMETHODIMP Machine::COMGETTER(VideoCaptureWidth)(ULONG *aHorzRes) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); -STDMETHODIMP Machine::COMGETTER(VideoCaptureWidth)(ULONG *ulHorzRes) + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + *aHorzRes = mHWData->mVideoCaptureWidth; + return S_OK; +} + +STDMETHODIMP Machine::COMSETTER(VideoCaptureWidth)(ULONG aHorzRes) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + if ( Global::IsOnline(mData->mMachineState) + && mHWData->mVideoCaptureEnabled) + return setError(E_INVALIDARG, tr("Cannot change parameters while capturing is enabled")); + + setModified(IsModified_MachineData); + mHWData.backup(); + mHWData->mVideoCaptureWidth = aHorzRes; + + return S_OK; +} + +STDMETHODIMP Machine::COMGETTER(VideoCaptureHeight)(ULONG *aVertRes) { AutoCaller autoCaller(this); if (FAILED(autoCaller.rc())) return autoCaller.rc(); AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - *ulHorzRes = mHWData->mVideoCaptureWidth; + *aVertRes = mHWData->mVideoCaptureHeight; return S_OK; } -STDMETHODIMP Machine::COMSETTER(VideoCaptureWidth)(ULONG ulHorzRes) +STDMETHODIMP Machine::COMSETTER(VideoCaptureHeight)(ULONG aVertRes) { AutoCaller autoCaller(this); - if (FAILED(autoCaller.rc())) - { - LogFlow(("Autolocked failed\n")); - return autoCaller.rc(); - } + if (FAILED(autoCaller.rc())) return autoCaller.rc(); AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); - mHWData->mVideoCaptureWidth = ulHorzRes; + + if ( Global::IsOnline(mData->mMachineState) + && mHWData->mVideoCaptureEnabled) + return setError(E_INVALIDARG, tr("Cannot change parameters while capturing is enabled")); + + setModified(IsModified_MachineData); + mHWData.backup(); + mHWData->mVideoCaptureHeight = aVertRes; + return S_OK; } -STDMETHODIMP Machine::COMGETTER(VideoCaptureHeight)(ULONG *ulVertRes) +STDMETHODIMP Machine::COMGETTER(VideoCaptureRate)(ULONG *aRate) { AutoCaller autoCaller(this); if (FAILED(autoCaller.rc())) return autoCaller.rc(); AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - *ulVertRes = mHWData->mVideoCaptureHeight; + *aRate = mHWData->mVideoCaptureRate; + return S_OK; +} + +STDMETHODIMP Machine::COMSETTER(VideoCaptureRate)(ULONG aRate) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + if ( Global::IsOnline(mData->mMachineState) + && mHWData->mVideoCaptureEnabled) + return setError(E_INVALIDARG, tr("Cannot change parameters while capturing is enabled")); + + setModified(IsModified_MachineData); + mHWData.backup(); + mHWData->mVideoCaptureRate = aRate; + + return S_OK; +} + +STDMETHODIMP Machine::COMGETTER(VideoCaptureFPS)(ULONG *aFPS) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + *aFPS = mHWData->mVideoCaptureFPS; + return S_OK; +} + +STDMETHODIMP Machine::COMSETTER(VideoCaptureFPS)(ULONG aFPS) +{ + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + if ( Global::IsOnline(mData->mMachineState) + && mHWData->mVideoCaptureEnabled) + return setError(E_INVALIDARG, tr("Cannot change parameters while capturing is enabled")); + + setModified(IsModified_MachineData); + mHWData.backup(); + mHWData->mVideoCaptureFPS = aFPS; + return S_OK; } -STDMETHODIMP Machine::COMSETTER(VideoCaptureHeight)(ULONG ulVertRes) +STDMETHODIMP Machine::COMGETTER(GraphicsControllerType)(GraphicsControllerType_T *aGraphicsControllerType) { + CheckComArgOutPointerValid(aGraphicsControllerType); + AutoCaller autoCaller(this); if (FAILED(autoCaller.rc())) return autoCaller.rc(); AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - mHWData->mVideoCaptureHeight = ulVertRes; + + *aGraphicsControllerType = mHWData->mGraphicsControllerType; + + return S_OK; +} + +STDMETHODIMP Machine::COMSETTER(GraphicsControllerType)(GraphicsControllerType_T aGraphicsControllerType) +{ + switch (aGraphicsControllerType) + { + case GraphicsControllerType_Null: + case GraphicsControllerType_VBoxVGA: +#ifdef VBOX_WITH_VMSVGA + case GraphicsControllerType_VMSVGA: +#endif + break; + default: + return setError(E_INVALIDARG, tr("The graphics controller type (%d) is invalid"), aGraphicsControllerType); + } + + 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; + + setModified(IsModified_MachineData); + mHWData.backup(); + mHWData->mGraphicsControllerType = aGraphicsControllerType; + return S_OK; } @@ -1838,20 +2045,20 @@ STDMETHODIMP Machine::COMSETTER(MemoryBalloonSize)(ULONG memoryBalloonSize) #endif } -STDMETHODIMP Machine::COMGETTER(PageFusionEnabled) (BOOL *enabled) +STDMETHODIMP Machine::COMGETTER(PageFusionEnabled) (BOOL *aEnabled) { - CheckComArgOutPointerValid(enabled); + CheckComArgOutPointerValid(aEnabled); AutoCaller autoCaller(this); if (FAILED(autoCaller.rc())) return autoCaller.rc(); AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - *enabled = mHWData->mPageFusionEnabled; + *aEnabled = mHWData->mPageFusionEnabled; return S_OK; } -STDMETHODIMP Machine::COMSETTER(PageFusionEnabled) (BOOL enabled) +STDMETHODIMP Machine::COMSETTER(PageFusionEnabled) (BOOL aEnabled) { #ifdef VBOX_WITH_PAGE_SHARING AutoCaller autoCaller(this); @@ -1862,24 +2069,24 @@ STDMETHODIMP Machine::COMSETTER(PageFusionEnabled) (BOOL enabled) /** @todo must support changes for running vms and keep this in sync with IGuest. */ setModified(IsModified_MachineData); mHWData.backup(); - mHWData->mPageFusionEnabled = enabled; + mHWData->mPageFusionEnabled = aEnabled; return S_OK; #else - NOREF(enabled); + NOREF(aEnabled); return setError(E_NOTIMPL, tr("Page fusion is only supported on 64-bit hosts")); #endif } -STDMETHODIMP Machine::COMGETTER(Accelerate3DEnabled)(BOOL *enabled) +STDMETHODIMP Machine::COMGETTER(Accelerate3DEnabled)(BOOL *aEnabled) { - CheckComArgOutPointerValid(enabled); + CheckComArgOutPointerValid(aEnabled); AutoCaller autoCaller(this); if (FAILED(autoCaller.rc())) return autoCaller.rc(); AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - *enabled = mHWData->mAccelerate3DEnabled; + *aEnabled = mHWData->mAccelerate3DEnabled; return S_OK; } @@ -1904,16 +2111,16 @@ STDMETHODIMP Machine::COMSETTER(Accelerate3DEnabled)(BOOL enable) } -STDMETHODIMP Machine::COMGETTER(Accelerate2DVideoEnabled)(BOOL *enabled) +STDMETHODIMP Machine::COMGETTER(Accelerate2DVideoEnabled)(BOOL *aEnabled) { - CheckComArgOutPointerValid(enabled); + CheckComArgOutPointerValid(aEnabled); AutoCaller autoCaller(this); if (FAILED(autoCaller.rc())) return autoCaller.rc(); AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - *enabled = mHWData->mAccelerate2DVideoEnabled; + *aEnabled = mHWData->mAccelerate2DVideoEnabled; return S_OK; } @@ -1996,18 +2203,55 @@ STDMETHODIMP Machine::GetCPUProperty(CPUPropertyType_T property, BOOL *aVal) AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - switch(property) + switch (property) { - case CPUPropertyType_PAE: - *aVal = mHWData->mPAEEnabled; - break; + case CPUPropertyType_PAE: + *aVal = mHWData->mPAEEnabled; + break; - case CPUPropertyType_Synthetic: - *aVal = mHWData->mSyntheticCpu; - break; + case CPUPropertyType_Synthetic: + *aVal = mHWData->mSyntheticCpu; + break; - default: - return E_INVALIDARG; + case CPUPropertyType_LongMode: + if (mHWData->mLongMode == settings::Hardware::LongMode_Enabled) + *aVal = TRUE; + else if (mHWData->mLongMode == settings::Hardware::LongMode_Disabled) + *aVal = FALSE; +#if HC_ARCH_BITS == 64 + else + *aVal = TRUE; +#else + else + { + *aVal = FALSE; + + ComPtr<IGuestOSType> ptrGuestOSType; + HRESULT hrc2 = mParent->GetGuestOSType(Bstr(mUserData->s.strOsType).raw(), ptrGuestOSType.asOutParam()); + if (SUCCEEDED(hrc2)) + { + BOOL fIs64Bit = FALSE; + hrc2 = ptrGuestOSType->COMGETTER(Is64Bit)(&fIs64Bit); AssertComRC(hrc2); + if (SUCCEEDED(hrc2) && fIs64Bit) + { + ComObjPtr<Host> ptrHost = mParent->host(); + alock.release(); + + hrc2 = ptrHost->GetProcessorFeature(ProcessorFeature_LongMode, aVal); AssertComRC(hrc2); + if (FAILED(hrc2)) + *aVal = FALSE; + } + } + } +#endif + break; + + case CPUPropertyType_TripleFaultReset: + *aVal = mHWData->mTripleFaultReset; + break; + + default: + return E_INVALIDARG; } return S_OK; } @@ -2022,22 +2266,34 @@ STDMETHODIMP Machine::SetCPUProperty(CPUPropertyType_T property, BOOL aVal) HRESULT rc = checkStateDependency(MutableStateDep); if (FAILED(rc)) return rc; - switch(property) + switch (property) { - case CPUPropertyType_PAE: - setModified(IsModified_MachineData); - mHWData.backup(); - mHWData->mPAEEnabled = !!aVal; - break; + case CPUPropertyType_PAE: + setModified(IsModified_MachineData); + mHWData.backup(); + mHWData->mPAEEnabled = !!aVal; + break; - case CPUPropertyType_Synthetic: - setModified(IsModified_MachineData); - mHWData.backup(); - mHWData->mSyntheticCpu = !!aVal; - break; + case CPUPropertyType_Synthetic: + setModified(IsModified_MachineData); + mHWData.backup(); + mHWData->mSyntheticCpu = !!aVal; + break; - default: - return E_INVALIDARG; + case CPUPropertyType_LongMode: + setModified(IsModified_MachineData); + mHWData.backup(); + mHWData->mLongMode = !aVal ? settings::Hardware::LongMode_Disabled : settings::Hardware::LongMode_Enabled; + break; + + case CPUPropertyType_TripleFaultReset: + setModified(IsModified_MachineData); + mHWData.backup(); + mHWData->mTripleFaultReset = !!aVal; + break; + + default: + return E_INVALIDARG; } return S_OK; } @@ -2259,10 +2515,6 @@ STDMETHODIMP Machine::GetHWVirtExProperty(HWVirtExPropertyType_T property, BOOL *aVal = mHWData->mHWVirtExEnabled; break; - case HWVirtExPropertyType_Exclusive: - *aVal = mHWData->mHWVirtExExclusive; - break; - case HWVirtExPropertyType_VPID: *aVal = mHWData->mHWVirtExVPIDEnabled; break; @@ -2271,6 +2523,10 @@ STDMETHODIMP Machine::GetHWVirtExProperty(HWVirtExPropertyType_T property, BOOL *aVal = mHWData->mHWVirtExNestedPagingEnabled; break; + case HWVirtExPropertyType_UnrestrictedExecution: + *aVal = mHWData->mHWVirtExUXEnabled; + break; + case HWVirtExPropertyType_LargePages: *aVal = mHWData->mHWVirtExLargePagesEnabled; #if defined(DEBUG_bird) && defined(RT_OS_LINUX) /* This feature is deadly here */ @@ -2306,12 +2562,6 @@ STDMETHODIMP Machine::SetHWVirtExProperty(HWVirtExPropertyType_T property, BOOL mHWData->mHWVirtExEnabled = !!aVal; break; - case HWVirtExPropertyType_Exclusive: - setModified(IsModified_MachineData); - mHWData.backup(); - mHWData->mHWVirtExExclusive = !!aVal; - break; - case HWVirtExPropertyType_VPID: setModified(IsModified_MachineData); mHWData.backup(); @@ -2324,6 +2574,12 @@ STDMETHODIMP Machine::SetHWVirtExProperty(HWVirtExPropertyType_T property, BOOL mHWData->mHWVirtExNestedPagingEnabled = !!aVal; break; + case HWVirtExPropertyType_UnrestrictedExecution: + setModified(IsModified_MachineData); + mHWData.backup(); + mHWData->mHWVirtExUXEnabled = !!aVal; + break; + case HWVirtExPropertyType_LargePages: setModified(IsModified_MachineData); mHWData.backup(); @@ -2444,10 +2700,40 @@ STDMETHODIMP Machine::COMGETTER(AudioAdapter)(IAudioAdapter **audioAdapter) return S_OK; } -STDMETHODIMP Machine::COMGETTER(USBController)(IUSBController **aUSBController) +STDMETHODIMP Machine::COMGETTER(USBControllers)(ComSafeArrayOut(IUSBController *, aUSBControllers)) +{ +#ifdef VBOX_WITH_VUSB + CheckComArgOutPointerValid(aUSBControllers); + + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + clearError(); + MultiResult rc(S_OK); + +# ifdef VBOX_WITH_USB + rc = mParent->host()->checkUSBProxyService(); + if (FAILED(rc)) return rc; +# endif + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + SafeIfaceArray<IUSBController> ctrls(*mUSBControllers.data()); + ctrls.detachTo(ComSafeArrayOutArg(aUSBControllers)); + return S_OK; +#else + /* Note: The GUI depends on this method returning E_NOTIMPL with no + * extended error info to indicate that USB is simply not available + * (w/o treating it as a failure), for example, as in OSE */ + NOREF(aUSBControllers); + ReturnComNotImplemented(); +#endif /* VBOX_WITH_VUSB */ +} + +STDMETHODIMP Machine::COMGETTER(USBDeviceFilters)(IUSBDeviceFilters **aUSBDeviceFilters) { #ifdef VBOX_WITH_VUSB - CheckComArgOutPointerValid(aUSBController); + CheckComArgOutPointerValid(aUSBDeviceFilters); AutoCaller autoCaller(this); if (FAILED(autoCaller.rc())) return autoCaller.rc(); @@ -2462,12 +2748,12 @@ STDMETHODIMP Machine::COMGETTER(USBController)(IUSBController **aUSBController) AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - return rc = mUSBController.queryInterfaceTo(aUSBController); + return rc = mUSBDeviceFilters.queryInterfaceTo(aUSBDeviceFilters); #else /* Note: The GUI depends on this method returning E_NOTIMPL with no * extended error info to indicate that USB is simply not available * (w/o treating it as a failure), for example, as in OSE */ - NOREF(aUSBController); + NOREF(aUSBDeviceFilters); ReturnComNotImplemented(); #endif /* VBOX_WITH_VUSB */ } @@ -2685,8 +2971,7 @@ STDMETHODIMP Machine::COMGETTER(ClipboardMode)(ClipboardMode_T *aClipboardMode) return S_OK; } -STDMETHODIMP -Machine::COMSETTER(ClipboardMode)(ClipboardMode_T aClipboardMode) +STDMETHODIMP Machine::COMSETTER(ClipboardMode)(ClipboardMode_T aClipboardMode) { HRESULT rc = S_OK; @@ -2704,7 +2989,7 @@ Machine::COMSETTER(ClipboardMode)(ClipboardMode_T aClipboardMode) mHWData.backup(); mHWData->mClipboardMode = aClipboardMode; - /* Save settings if online - todo why is this required?? */ + /** Save settings if online - @todo why is this required? -- @bugref{6818} */ if (Global::IsOnline(mData->mMachineState)) saveSettings(NULL); @@ -2725,8 +3010,7 @@ STDMETHODIMP Machine::COMGETTER(DragAndDropMode)(DragAndDropMode_T *aDragAndDrop return S_OK; } -STDMETHODIMP -Machine::COMSETTER(DragAndDropMode)(DragAndDropMode_T aDragAndDropMode) +STDMETHODIMP Machine::COMSETTER(DragAndDropMode)(DragAndDropMode_T aDragAndDropMode) { HRESULT rc = S_OK; @@ -2744,15 +3028,14 @@ Machine::COMSETTER(DragAndDropMode)(DragAndDropMode_T aDragAndDropMode) mHWData.backup(); mHWData->mDragAndDropMode = aDragAndDropMode; - /* Save settings if online - todo why is this required?? */ + /** Save settings if online - @todo why is this required? -- @bugref{6818} */ if (Global::IsOnline(mData->mMachineState)) saveSettings(NULL); return S_OK; } -STDMETHODIMP -Machine::COMGETTER(GuestPropertyNotificationPatterns)(BSTR *aPatterns) +STDMETHODIMP Machine::COMGETTER(GuestPropertyNotificationPatterns)(BSTR *aPatterns) { CheckComArgOutPointerValid(aPatterns); @@ -2773,8 +3056,7 @@ Machine::COMGETTER(GuestPropertyNotificationPatterns)(BSTR *aPatterns) return S_OK; } -STDMETHODIMP -Machine::COMSETTER(GuestPropertyNotificationPatterns)(IN_BSTR aPatterns) +STDMETHODIMP Machine::COMSETTER(GuestPropertyNotificationPatterns)(IN_BSTR aPatterns) { AutoCaller autoCaller(this); if (FAILED(autoCaller.rc())) return autoCaller.rc(); @@ -2790,8 +3072,7 @@ Machine::COMSETTER(GuestPropertyNotificationPatterns)(IN_BSTR aPatterns) return rc; } -STDMETHODIMP -Machine::COMGETTER(StorageControllers)(ComSafeArrayOut(IStorageController *, aStorageControllers)) +STDMETHODIMP Machine::COMGETTER(StorageControllers)(ComSafeArrayOut(IStorageController *, aStorageControllers)) { CheckComArgOutSafeArrayPointerValid(aStorageControllers); @@ -2806,8 +3087,7 @@ Machine::COMGETTER(StorageControllers)(ComSafeArrayOut(IStorageController *, aSt return S_OK; } -STDMETHODIMP -Machine::COMGETTER(TeleporterEnabled)(BOOL *aEnabled) +STDMETHODIMP Machine::COMGETTER(TeleporterEnabled)(BOOL *aEnabled) { CheckComArgOutPointerValid(aEnabled); @@ -3301,7 +3581,7 @@ STDMETHODIMP Machine::LockMachine(ISession *aSession, if (FAILED(rc)) // the failure may occur w/o any error info (from RPC), so provide one return setError(VBOX_E_VM_ERROR, - tr("Failed to get a console object from the direct session (%Rrc)"), rc); + tr("Failed to get a console object from the direct session (%Rhrc)"), rc); ComAssertRet(!pConsoleW.isNull(), E_FAIL); @@ -3313,7 +3593,7 @@ STDMETHODIMP Machine::LockMachine(ISession *aSession, if (FAILED(rc)) // the failure may occur w/o any error info (from RPC), so provide one return setError(VBOX_E_VM_ERROR, - tr("Failed to assign the machine to the session (%Rrc)"), rc); + tr("Failed to assign the machine to the session (%Rhrc)"), rc); alock.acquire(); // need to revalidate the state after acquiring the lock again @@ -3354,6 +3634,17 @@ STDMETHODIMP Machine::LockMachine(ISession *aSession, if (fLaunchingVMProcess) { + if (mData->mSession.mPID == NIL_RTPROCESS) + { + // two or more clients racing for a lock, the one which set the + // session state to Spawning will win, the others will get an + // error as we can't decide here if waiting a little would help + // (only for shared locks this would avoid an error) + return setError(VBOX_E_INVALID_OBJECT_STATE, + tr("The machine '%s' already has a lock request pending"), + mUserData->s.strName.c_str()); + } + // this machine is awaiting for a spawning session to be opened: // then the calling process must be the one that got started by // LaunchVMProcess() @@ -3390,6 +3681,19 @@ STDMETHODIMP Machine::LockMachine(ISession *aSession, SessionState_T origState = mData->mSession.mState; mData->mSession.mState = SessionState_Spawning; +#ifndef VBOX_WITH_GENERIC_SESSION_WATCHER + /* Get the client token ID to be passed to the client process */ + Utf8Str strTokenId; + sessionMachine->getTokenId(strTokenId); + Assert(!strTokenId.isEmpty()); +#else /* VBOX_WITH_GENERIC_SESSION_WATCHER */ + /* Get the client token to be passed to the client process */ + ComPtr<IToken> pToken(sessionMachine->getToken()); + /* The token is now "owned" by pToken, fix refcount */ + if (!pToken.isNull()) + pToken->Release(); +#endif /* VBOX_WITH_GENERIC_SESSION_WATCHER */ + /* * Release the lock before calling the client process -- it will call * Machine/SessionMachine methods. Releasing the lock here is quite safe @@ -3403,13 +3707,19 @@ STDMETHODIMP Machine::LockMachine(ISession *aSession, alock.release(); LogFlowThisFunc(("Calling AssignMachine()...\n")); - rc = pSessionControl->AssignMachine(sessionMachine, lockType); +#ifndef VBOX_WITH_GENERIC_SESSION_WATCHER + rc = pSessionControl->AssignMachine(sessionMachine, lockType, Bstr(strTokenId).raw()); +#else /* VBOX_WITH_GENERIC_SESSION_WATCHER */ + rc = pSessionControl->AssignMachine(sessionMachine, lockType, pToken); + /* Now the token is owned by the client process. */ + pToken.setNull(); +#endif /* VBOX_WITH_GENERIC_SESSION_WATCHER */ LogFlowThisFunc(("AssignMachine() returned %08X\n", rc)); /* The failure may occur w/o any error info (from RPC), so provide one */ if (FAILED(rc)) setError(VBOX_E_VM_ERROR, - tr("Failed to assign the machine to the session (%Rrc)"), rc); + tr("Failed to assign the machine to the session (%Rhrc)"), rc); if ( SUCCEEDED(rc) && fLaunchingVMProcess @@ -3442,7 +3752,7 @@ STDMETHODIMP Machine::LockMachine(ISession *aSession, /* The failure may occur w/o any error info (from RPC), so provide one */ if (FAILED(rc)) setError(VBOX_E_VM_ERROR, - tr("Failed to assign the machine to the remote session (%Rrc)"), rc); + tr("Failed to assign the machine to the remote session (%Rhrc)"), rc); } if (FAILED(rc)) @@ -3470,7 +3780,7 @@ STDMETHODIMP Machine::LockMachine(ISession *aSession, { /* Close the remote session, remove the remote control from the list * and reset session state to Closed (@note keep the code in sync - * with the relevant part in openSession()). */ + * with the relevant part in checkForSpawnFailure()). */ Assert(mData->mSession.mRemoteControls.size() == 1); if (mData->mSession.mRemoteControls.size() == 1) @@ -3535,28 +3845,52 @@ STDMETHODIMP Machine::LockMachine(ISession *aSession, * @note Locks objects! */ STDMETHODIMP Machine::LaunchVMProcess(ISession *aSession, - IN_BSTR aType, + IN_BSTR aFrontend, IN_BSTR aEnvironment, IProgress **aProgress) { - CheckComArgStrNotEmptyOrNull(aType); - Utf8Str strType(aType); + CheckComArgStr(aFrontend); + Utf8Str strFrontend(aFrontend); Utf8Str strEnvironment(aEnvironment); /* "emergencystop" doesn't need the session, so skip the checks/interface * retrieval. This code doesn't quite fit in here, but introducing a * special API method would be even more effort, and would require explicit * support by every API client. It's better to hide the feature a bit. */ - if (strType != "emergencystop") + if (strFrontend != "emergencystop") CheckComArgNotNull(aSession); CheckComArgOutPointerValid(aProgress); AutoCaller autoCaller(this); if (FAILED(autoCaller.rc())) return autoCaller.rc(); - ComPtr<IInternalSessionControl> control; HRESULT rc = S_OK; + if (strFrontend.isEmpty()) + { + Bstr bstrFrontend; + rc = COMGETTER(DefaultFrontend)(bstrFrontend.asOutParam()); + if (FAILED(rc)) + return rc; + strFrontend = bstrFrontend; + if (strFrontend.isEmpty()) + { + ComPtr<ISystemProperties> systemProperties; + rc = mParent->COMGETTER(SystemProperties)(systemProperties.asOutParam()); + if (FAILED(rc)) + return rc; + rc = systemProperties->COMGETTER(DefaultFrontend)(bstrFrontend.asOutParam()); + if (FAILED(rc)) + return rc; + strFrontend = bstrFrontend; + } + /* paranoia - emergencystop is not a valid default */ + if (strFrontend == "emergencystop") + strFrontend = Utf8Str::Empty; + } + /* default frontend: Qt GUI */ + if (strFrontend.isEmpty()) + strFrontend = "GUI/Qt"; - if (strType != "emergencystop") + if (strFrontend != "emergencystop") { /* check the session state */ SessionState_T state; @@ -3569,21 +3903,18 @@ STDMETHODIMP Machine::LaunchVMProcess(ISession *aSession, tr("The given session is busy")); /* get the IInternalSessionControl interface */ - control = aSession; + ComPtr<IInternalSessionControl> control(aSession); ComAssertMsgRet(!control.isNull(), ("No IInternalSessionControl interface"), E_INVALIDARG); - } - /* get the teleporter enable state for the progress object init. */ - BOOL fTeleporterEnabled; - rc = COMGETTER(TeleporterEnabled)(&fTeleporterEnabled); - if (FAILED(rc)) - return rc; + /* get the teleporter enable state for the progress object init. */ + BOOL fTeleporterEnabled; + rc = COMGETTER(TeleporterEnabled)(&fTeleporterEnabled); + if (FAILED(rc)) + return rc; - /* create a progress object */ - if (strType != "emergencystop") - { + /* create a progress object */ ComObjPtr<ProgressProxy> progress; progress.createObject(); rc = progress->init(mParent, @@ -3591,13 +3922,13 @@ STDMETHODIMP Machine::LaunchVMProcess(ISession *aSession, Bstr(tr("Starting VM")).raw(), TRUE /* aCancelable */, fTeleporterEnabled ? 20 : 10 /* uTotalOperationsWeight */, - BstrFmt(tr("Creating process for virtual machine \"%s\" (%s)"), mUserData->s.strName.c_str(), strType.c_str()).raw(), + BstrFmt(tr("Creating process for virtual machine \"%s\" (%s)"), mUserData->s.strName.c_str(), strFrontend.c_str()).raw(), 2 /* uFirstOperationWeight */, fTeleporterEnabled ? 3 : 1 /* cOtherProgressObjectOperations */); if (SUCCEEDED(rc)) { - rc = launchVMProcess(control, strType, strEnvironment, progress); + rc = launchVMProcess(control, strFrontend, strEnvironment, progress); if (SUCCEEDED(rc)) { progress.queryInterfaceTo(aProgress); @@ -3725,9 +4056,18 @@ STDMETHODIMP Machine::AttachDevice(IN_BSTR aControllerName, tr("Could not get type of controller '%ls'"), aControllerName); + bool fSilent = false; + Utf8Str strReconfig; + + /* Check whether the flag to allow silent storage attachment reconfiguration is set. */ + strReconfig = getExtraData(Utf8Str("VBoxInternal2/SilentReconfigureWhilePaused")); + if ( mData->mMachineState == MachineState_Paused + && strReconfig == "1") + fSilent = true; + /* 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)) + if (!fSilent && Global::IsOnlineOrTransient(mData->mMachineState)) fHotplug = true; if (fHotplug && !isControllerHotplugCapable(ctrlType)) @@ -3834,6 +4174,46 @@ STDMETHODIMP Machine::AttachDevice(IN_BSTR aControllerName, /* the simplest case: restore the whole attachment * and return, nothing else to do */ mMediaData->mAttachments.push_back(pAttachTemp); + + /* Reattach the medium to the VM. */ + if (fHotplug || fSilent) + { + mediumLock.release(); + treeLock.release(); + alock.release(); + + MediumLockList *pMediumLockList(new MediumLockList()); + + rc = medium->createMediumLockList(true /* fFailIfInaccessible */, + true /* fMediumLockWrite */, + NULL, + *pMediumLockList); + alock.acquire(); + if (FAILED(rc)) + delete pMediumLockList; + else + { + mData->mSession.mLockedMedia.Unlock(); + alock.release(); + rc = mData->mSession.mLockedMedia.Insert(pAttachTemp, pMediumLockList); + mData->mSession.mLockedMedia.Lock(); + alock.acquire(); + } + alock.release(); + + if (SUCCEEDED(rc)) + { + rc = onStorageDeviceChange(pAttachTemp, FALSE /* aRemove */, fSilent); + /* Remove lock list in case of error. */ + if (FAILED(rc)) + { + mData->mSession.mLockedMedia.Unlock(); + mData->mSession.mLockedMedia.Remove(pAttachTemp); + mData->mSession.mLockedMedia.Lock(); + } + } + } + return S_OK; } @@ -3893,6 +4273,46 @@ STDMETHODIMP Machine::AttachDevice(IN_BSTR aControllerName, /* the simplest case: restore the whole attachment * and return, nothing else to do */ mMediaData->mAttachments.push_back(*it); + + /* Reattach the medium to the VM. */ + if (fHotplug || fSilent) + { + mediumLock.release(); + treeLock.release(); + alock.release(); + + MediumLockList *pMediumLockList(new MediumLockList()); + + rc = medium->createMediumLockList(true /* fFailIfInaccessible */, + true /* fMediumLockWrite */, + NULL, + *pMediumLockList); + alock.acquire(); + if (FAILED(rc)) + delete pMediumLockList; + else + { + mData->mSession.mLockedMedia.Unlock(); + alock.release(); + rc = mData->mSession.mLockedMedia.Insert(pAttachTemp, pMediumLockList); + mData->mSession.mLockedMedia.Lock(); + alock.acquire(); + } + alock.release(); + + if (SUCCEEDED(rc)) + { + rc = onStorageDeviceChange(pAttachTemp, FALSE /* aRemove */, fSilent); + /* Remove lock list in case of error. */ + if (FAILED(rc)) + { + mData->mSession.mLockedMedia.Unlock(); + mData->mSession.mLockedMedia.Remove(pAttachTemp); + mData->mSession.mLockedMedia.Lock(); + } + } + } + return S_OK; } else if ( foundIt == oldAtts.end() @@ -4098,6 +4518,7 @@ STDMETHODIMP Machine::AttachDevice(IN_BSTR aControllerName, false /* fTempEject */, false /* fNonRotational */, false /* fDiscard */, + false /* fHotPluggable */, Utf8Str::Empty); if (FAILED(rc)) return rc; @@ -4126,8 +4547,42 @@ STDMETHODIMP Machine::AttachDevice(IN_BSTR aControllerName, treeLock.release(); alock.release(); - if (fHotplug) - rc = onStorageDeviceChange(attachment, FALSE /* aRemove */); + if (fHotplug || fSilent) + { + if (!medium.isNull()) + { + MediumLockList *pMediumLockList(new MediumLockList()); + + rc = medium->createMediumLockList(true /* fFailIfInaccessible */, + true /* fMediumLockWrite */, + NULL, + *pMediumLockList); + alock.acquire(); + if (FAILED(rc)) + delete pMediumLockList; + else + { + mData->mSession.mLockedMedia.Unlock(); + alock.release(); + rc = mData->mSession.mLockedMedia.Insert(attachment, pMediumLockList); + mData->mSession.mLockedMedia.Lock(); + alock.acquire(); + } + alock.release(); + } + + if (SUCCEEDED(rc)) + { + rc = onStorageDeviceChange(attachment, FALSE /* aRemove */, fSilent); + /* Remove lock list in case of error. */ + if (FAILED(rc)) + { + mData->mSession.mLockedMedia.Unlock(); + mData->mSession.mLockedMedia.Remove(attachment); + mData->mSession.mLockedMedia.Lock(); + } + } + } mParent->saveModifiedRegistries(); @@ -4164,9 +4619,18 @@ STDMETHODIMP Machine::DetachDevice(IN_BSTR aControllerName, LONG aControllerPort tr("Could not get type of controller '%ls'"), aControllerName); + bool fSilent = false; + Utf8Str strReconfig; + + /* Check whether the flag to allow silent storage attachment reconfiguration is set. */ + strReconfig = getExtraData(Utf8Str("VBoxInternal2/SilentReconfigureWhilePaused")); + if ( mData->mMachineState == MachineState_Paused + && strReconfig == "1") + fSilent = true; + /* 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)) + if (!fSilent && Global::IsOnlineOrTransient(mData->mMachineState)) fHotplug = true; if (fHotplug && !isControllerHotplugCapable(ctrlType)) @@ -4187,10 +4651,10 @@ STDMETHODIMP Machine::DetachDevice(IN_BSTR aControllerName, LONG aControllerPort * 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) + if (fHotplug || fSilent) { alock.release(); - rc = onStorageDeviceChange(pAttach, TRUE /* aRemove */); + rc = onStorageDeviceChange(pAttach, TRUE /* aRemove */, fSilent); alock.acquire(); } if (FAILED(rc)) return rc; @@ -4386,6 +4850,58 @@ STDMETHODIMP Machine::SetAutoDiscardForDevice(IN_BSTR aControllerName, LONG aCon return S_OK; } +STDMETHODIMP Machine::SetHotPluggableForDevice(IN_BSTR aControllerName, LONG aControllerPort, + LONG aDevice, BOOL aHotPluggable) +{ + CheckComArgStrNotEmptyOrNull(aControllerName); + + LogFlowThisFunc(("aControllerName=\"%ls\" aControllerPort=%d aDevice=%d aHotPluggable=%d\n", + aControllerName, aControllerPort, aDevice, aHotPluggable)); + + 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); + + /** @todo remove this blocker and add the missing code to support this + * flag properly in all code areas, with proper support checks below. */ + return setError(VBOX_E_NOT_SUPPORTED, + tr("Controller '%ls' does not support changing the hot-pluggable device flag"), + aControllerName); + + setModified(IsModified_Storage); + mMediaData.backup(); + + AutoWriteLock attLock(pAttach COMMA_LOCKVAL_SRC_POS); + + if (pAttach->getType() == DeviceType_Floppy) + return setError(E_INVALIDARG, + tr("Setting the hot-pluggable device flag rejected as the device attached to device slot %d on port %d of controller '%ls' is a floppy drive"), + aDevice, aControllerPort, aControllerName); + pAttach->updateHotPluggable(!!aHotPluggable); + + return S_OK; +} + STDMETHODIMP Machine::SetNoBandwidthGroupForDevice(IN_BSTR aControllerName, LONG aControllerPort, LONG aDevice) { @@ -4833,6 +5349,18 @@ STDMETHODIMP Machine::SetExtraData(IN_BSTR aKey, IN_BSTR aValue) return S_OK; } +STDMETHODIMP Machine::SetSettingsFilePath(IN_BSTR aFilePath, IProgress **aProgress) +{ + CheckComArgStrNotEmptyOrNull(aFilePath); + CheckComArgOutPointerValid(aProgress); + + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + *aProgress = NULL; + ReturnComNotImplemented(); +} + STDMETHODIMP Machine::SaveSettings() { AutoCaller autoCaller(this); @@ -5024,7 +5552,7 @@ struct Machine::DeleteTask ComObjPtr<Progress> pProgress; }; -STDMETHODIMP Machine::Delete(ComSafeArrayIn(IMedium*, aMedia), IProgress **aProgress) +STDMETHODIMP Machine::DeleteConfig(ComSafeArrayIn(IMedium*, aMedia), IProgress **aProgress) { LogFlowFuncEnter(); @@ -5169,7 +5697,7 @@ HRESULT Machine::deleteTaskWorker(DeleteTask &task) if (FAILED(rc)) throw rc; rc = task.pProgress->WaitForAsyncProgressCompletion(pProgress2); if (FAILED(rc)) throw rc; - /* Check the result of the asynchrony process. */ + /* Check the result of the asynchronous process. */ LONG iRc; rc = pProgress2->COMGETTER(ResultCode)(&iRc); if (FAILED(rc)) throw rc; @@ -5177,6 +5705,15 @@ HRESULT Machine::deleteTaskWorker(DeleteTask &task) * retrieve the error info from there, or it'll be lost. */ if (FAILED(iRc)) throw setError(ProgressErrorInfo(pProgress2)); + + /* Close the medium, deliberately without checking the return + * code, and without leaving any trace in the error info, as + * a failure here is a very minor issue, which shouldn't happen + * as above we even managed to delete the medium. */ + { + ErrorInfoKeeper eik; + pMedium->Close(); + } } setMachineState(oldState); alock.acquire(); @@ -5292,7 +5829,7 @@ STDMETHODIMP Machine::FindSnapshot(IN_BSTR aNameOrId, ISnapshot **aSnapshot) else { Guid uuid(aNameOrId); - if (!uuid.isEmpty()) + if (uuid.isValid()) rc = findSnapshotById(uuid, pSnapshot, true /* aSetError */); else rc = findSnapshotByName(Utf8Str(aNameOrId), pSnapshot, true /* aSetError */); @@ -5441,21 +5978,17 @@ HRESULT Machine::getGuestPropertyFromService(IN_BSTR aName, AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); Utf8Str strName(aName); - HWData::GuestPropertyList::const_iterator it; + HWData::GuestPropertyMap::const_iterator it = mHWData->mGuestProperties.find(strName); - for (it = mHWData->mGuestProperties.begin(); - it != mHWData->mGuestProperties.end(); ++it) + if (it != mHWData->mGuestProperties.end()) { - if (it->strName == strName) - { - char szFlags[MAX_FLAGS_LEN + 1]; - it->strValue.cloneTo(aValue); - *aTimestamp = it->mTimestamp; - writeFlags(it->mFlags, szFlags); - Bstr(szFlags).cloneTo(aFlags); - break; - } + char szFlags[MAX_FLAGS_LEN + 1]; + it->second.strValue.cloneTo(aValue); + *aTimestamp = it->second.mTimestamp; + writeFlags(it->second.mFlags, szFlags); + Bstr(szFlags).cloneTo(aFlags); } + return S_OK; } @@ -5477,6 +6010,9 @@ HRESULT Machine::getGuestPropertyFromVM(IN_BSTR aName, /* fail if we were called after #OnSessionEnd() is called. This is a * silly race condition. */ + /** @todo This code is bothering API clients (like python script clients) with + * the AccessGuestProperty call, creating unncessary IPC. Need to + * have a way of figuring out which kind of direct session it is... */ if (!directControl) rc = E_ACCESSDENIED; else @@ -5536,9 +6072,6 @@ HRESULT Machine::setGuestPropertyToService(IN_BSTR aName, IN_BSTR aValue, AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); HRESULT rc = S_OK; - HWData::GuestProperty property; - property.mFlags = NILFLAG; - bool found = false; rc = checkStateDependency(MutableStateDep); if (FAILED(rc)) return rc; @@ -5548,64 +6081,59 @@ HRESULT Machine::setGuestPropertyToService(IN_BSTR aName, IN_BSTR aValue, Utf8Str utf8Name(aName); Utf8Str utf8Flags(aFlags); uint32_t fFlags = NILFLAG; - if ( (aFlags != NULL) - && RT_FAILURE(validateFlags(utf8Flags.c_str(), &fFlags)) - ) + if ( aFlags != NULL + && RT_FAILURE(validateFlags(utf8Flags.c_str(), &fFlags))) return setError(E_INVALIDARG, - tr("Invalid flag values: '%ls'"), + tr("Invalid guest property flag values: '%ls'"), aFlags); - /** @todo r=bird: see efficiency rant in PushGuestProperty. (Yeah, I - * know, this is simple and do an OK job atm.) */ - HWData::GuestPropertyList::iterator it; - for (it = mHWData->mGuestProperties.begin(); - it != mHWData->mGuestProperties.end(); ++it) - if (it->strName == utf8Name) - { - property = *it; - if (it->mFlags & (RDONLYHOST)) - rc = setError(E_ACCESSDENIED, - tr("The property '%ls' cannot be changed by the host"), - aName); - else - { - setModified(IsModified_MachineData); - mHWData.backup(); // @todo r=dj backup in a loop?!? - - /* The backup() operation invalidates our iterator, so - * get a new one. */ - for (it = mHWData->mGuestProperties.begin(); - it->strName != utf8Name; - ++it) - ; - mHWData->mGuestProperties.erase(it); - } - found = true; - break; - } - if (found && SUCCEEDED(rc)) + bool fDelete = !RT_VALID_PTR(aValue) || *(aValue) == '\0'; + HWData::GuestPropertyMap::iterator it = mHWData->mGuestProperties.find(utf8Name); + if (it == mHWData->mGuestProperties.end()) { - if (aValue) + if (!fDelete) { + setModified(IsModified_MachineData); + mHWData.backupEx(); + RTTIMESPEC time; - property.strValue = aValue; - property.mTimestamp = RTTimeSpecGetNano(RTTimeNow(&time)); - if (aFlags != NULL) - property.mFlags = fFlags; - mHWData->mGuestProperties.push_back(property); + HWData::GuestProperty prop; + prop.strValue = aValue; + prop.mTimestamp = RTTimeSpecGetNano(RTTimeNow(&time)); + prop.mFlags = fFlags; + mHWData->mGuestProperties[Utf8Str(aName)] = prop; } } - else if (SUCCEEDED(rc) && aValue) + else { - RTTIMESPEC time; - setModified(IsModified_MachineData); - mHWData.backup(); - property.strName = aName; - property.strValue = aValue; - property.mTimestamp = RTTimeSpecGetNano(RTTimeNow(&time)); - property.mFlags = fFlags; - mHWData->mGuestProperties.push_back(property); + if (it->second.mFlags & (RDONLYHOST)) + { + rc = setError(E_ACCESSDENIED, + tr("The property '%ls' cannot be changed by the host"), + aName); + } + else + { + setModified(IsModified_MachineData); + mHWData.backupEx(); + + /* The backupEx() operation invalidates our iterator, + * so get a new one. */ + it = mHWData->mGuestProperties.find(utf8Name); + Assert(it != mHWData->mGuestProperties.end()); + + if (!fDelete) + { + RTTIMESPEC time; + it->second.strValue = aValue; + it->second.mTimestamp = RTTimeSpecGetNano(RTTimeNow(&time)); + it->second.mFlags = fFlags; + } + else + mHWData->mGuestProperties.erase(it); + } } + if ( SUCCEEDED(rc) && ( mHWData->mGuestPropertyNotificationPatterns.isEmpty() || RTStrSimplePatternMultiMatch(mHWData->mGuestPropertyNotificationPatterns.c_str(), @@ -5616,8 +6144,8 @@ HRESULT Machine::setGuestPropertyToService(IN_BSTR aName, IN_BSTR aValue, ) ) { - /** @todo r=bird: Why aren't we leaving the lock here? The - * same code in PushGuestProperty does... */ + alock.release(); + mParent->onGuestPropertyChange(mData->mUuid, aName, aValue ? aValue : Bstr("").raw(), aFlags ? aFlags : Bstr("").raw()); @@ -5651,8 +6179,7 @@ HRESULT Machine::setGuestPropertyToVM(IN_BSTR aName, IN_BSTR aValue, if (!directControl) rc = E_ACCESSDENIED; else - /** @todo Fix when adding DeleteGuestProperty(), - see defect. */ + /** @todo Fix when adding DeleteGuestProperty(), see defect. */ rc = directControl->AccessGuestProperty(aName, aValue, aFlags, true /* isSetter */, &dummy, &dummy64, &dummy); @@ -5713,42 +6240,50 @@ HRESULT Machine::enumerateGuestPropertiesInService AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); Utf8Str strPatterns(aPatterns); + HWData::GuestPropertyMap propMap; + /* * Look for matching patterns and build up a list. */ - HWData::GuestPropertyList propList; - for (HWData::GuestPropertyList::iterator it = mHWData->mGuestProperties.begin(); - it != mHWData->mGuestProperties.end(); - ++it) + HWData::GuestPropertyMap::const_iterator it = mHWData->mGuestProperties.begin(); + while (it != mHWData->mGuestProperties.end()) + { if ( strPatterns.isEmpty() || RTStrSimplePatternMultiMatch(strPatterns.c_str(), RTSTR_MAX, - it->strName.c_str(), + it->first.c_str(), RTSTR_MAX, NULL) ) - propList.push_back(*it); + { + propMap.insert(*it); + } + + it++; + } + + alock.release(); /* * And build up the arrays for returning the property information. */ - size_t cEntries = propList.size(); + size_t cEntries = propMap.size(); SafeArray<BSTR> names(cEntries); SafeArray<BSTR> values(cEntries); SafeArray<LONG64> timestamps(cEntries); SafeArray<BSTR> flags(cEntries); size_t iProp = 0; - for (HWData::GuestPropertyList::iterator it = propList.begin(); - it != propList.end(); - ++it) + + it = propMap.begin(); + while (it != propMap.end()) { char szFlags[MAX_FLAGS_LEN + 1]; - it->strName.cloneTo(&names[iProp]); - it->strValue.cloneTo(&values[iProp]); - timestamps[iProp] = it->mTimestamp; - writeFlags(it->mFlags, szFlags); - Bstr(szFlags).cloneTo(&flags[iProp]); - ++iProp; + it->first.cloneTo(&names[iProp]); + it->second.strValue.cloneTo(&values[iProp]); + timestamps[iProp] = it->second.mTimestamp; + writeFlags(it->second.mFlags, szFlags); + Bstr(szFlags).cloneTo(&flags[iProp++]); + it++; } names.detachTo(ComSafeArrayOutArg(aNames)); values.detachTo(ComSafeArrayOutArg(aValues)); @@ -6083,6 +6618,132 @@ STDMETHODIMP Machine::RemoveStorageController(IN_BSTR aName) return S_OK; } +STDMETHODIMP Machine::AddUSBController(IN_BSTR aName, USBControllerType_T aType, + IUSBController **controller) +{ + if ( (aType <= USBControllerType_Null) + || (aType >= USBControllerType_Last)) + return setError(E_INVALIDARG, + tr("Invalid USB controller type: %d"), + aType); + + 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; + + /* try to find one with the same type first. */ + ComObjPtr<USBController> ctrl; + + rc = getUSBControllerByName(aName, ctrl, false /* aSetError */); + if (SUCCEEDED(rc)) + return setError(VBOX_E_OBJECT_IN_USE, + tr("USB controller named '%ls' already exists"), + aName); + + /* Check that we don't exceed the maximum number of USB controllers for the given type. */ + ULONG maxInstances; + rc = mParent->getSystemProperties()->GetMaxInstancesOfUSBControllerType(mHWData->mChipsetType, aType, &maxInstances); + if (FAILED(rc)) + return rc; + + ULONG cInstances = getUSBControllerCountByType(aType); + if (cInstances >= maxInstances) + return setError(E_INVALIDARG, + tr("Too many USB controllers of this type")); + + ctrl.createObject(); + + rc = ctrl->init(this, aName, aType); + if (FAILED(rc)) return rc; + + setModified(IsModified_USB); + mUSBControllers.backup(); + mUSBControllers->push_back(ctrl); + + ctrl.queryInterfaceTo(controller); + + /* inform the direct session if any */ + alock.release(); + onUSBControllerChange(); + + return S_OK; +} + +STDMETHODIMP Machine::GetUSBControllerByName(IN_BSTR aName, IUSBController **aUSBController) +{ + CheckComArgStrNotEmptyOrNull(aName); + + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + ComObjPtr<USBController> ctrl; + + HRESULT rc = getUSBControllerByName(aName, ctrl, true /* aSetError */); + if (SUCCEEDED(rc)) + ctrl.queryInterfaceTo(aUSBController); + + return rc; +} + +STDMETHODIMP Machine::GetUSBControllerCountByType(USBControllerType_T aType, + ULONG *aControllers) +{ + CheckComArgOutPointerValid(aControllers); + + if ( (aType <= USBControllerType_Null) + || (aType >= USBControllerType_Last)) + return setError(E_INVALIDARG, + tr("Invalid USB controller type: %d"), + aType); + + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + ComObjPtr<USBController> ctrl; + + *aControllers = getUSBControllerCountByType(aType); + + return S_OK; +} + +STDMETHODIMP Machine::RemoveUSBController(IN_BSTR aName) +{ + CheckComArgStrNotEmptyOrNull(aName); + + 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; + + ComObjPtr<USBController> ctrl; + rc = getUSBControllerByName(aName, ctrl, true /* aSetError */); + if (FAILED(rc)) return rc; + + setModified(IsModified_USB); + mUSBControllers.backup(); + + ctrl->unshare(); + + mUSBControllers->remove(ctrl); + + /* inform the direct session if any */ + alock.release(); + onUSBControllerChange(); + + return S_OK; +} + STDMETHODIMP Machine::QuerySavedGuestScreenInfo(ULONG uScreenId, ULONG *puOriginX, ULONG *puOriginY, @@ -6397,7 +7058,7 @@ STDMETHODIMP Machine::HotPlugCPU(ULONG aCpu) mHWData.backup(); mHWData->mCPUAttached[aCpu] = true; - /* Save settings if online */ + /** Save settings if online - @todo why is this required? -- @bugref{6818} */ if (Global::IsOnline(mData->mMachineState)) saveSettings(NULL); @@ -6438,7 +7099,7 @@ STDMETHODIMP Machine::HotUnplugCPU(ULONG aCpu) mHWData.backup(); mHWData->mCPUAttached[aCpu] = false; - /* Save settings if online */ + /** Save settings if online - @todo why is this required? -- @bugref{6818} */ if (Global::IsOnline(mData->mMachineState)) saveSettings(NULL); @@ -6934,6 +7595,92 @@ STDMETHODIMP Machine::COMSETTER(AutostopType)(AutostopType_T enmAutostopType) return hrc; } +STDMETHODIMP Machine::COMGETTER(DefaultFrontend)(BSTR *aDefaultFrontend) +{ + CheckComArgOutPointerValid(aDefaultFrontend); + AutoCaller autoCaller(this); + HRESULT hrc = autoCaller.rc(); + if (SUCCEEDED(hrc)) + { + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + mHWData->mDefaultFrontend.cloneTo(aDefaultFrontend); + } + return hrc; +} + +STDMETHODIMP Machine::COMSETTER(DefaultFrontend)(IN_BSTR aDefaultFrontend) +{ + CheckComArgStr(aDefaultFrontend); + AutoCaller autoCaller(this); + HRESULT hrc = autoCaller.rc(); + if (SUCCEEDED(hrc)) + { + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + hrc = checkStateDependency(MutableOrSavedStateDep); + if (SUCCEEDED(hrc)) + { + hrc = mHWData.backupEx(); + if (SUCCEEDED(hrc)) + { + setModified(IsModified_MachineData); + mHWData->mDefaultFrontend = aDefaultFrontend; + } + } + } + return hrc; +} + +STDMETHODIMP Machine::COMGETTER(Icon)(ComSafeArrayOut(BYTE, aIcon)) +{ + CheckComArgSafeArrayNotNull(aIcon); + CheckComArgOutSafeArrayPointerValid(aIcon); + AutoCaller autoCaller(this); + HRESULT hrc = autoCaller.rc(); + if (SUCCEEDED(hrc)) + { + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + com::SafeArray<BYTE> icon(mUserData->mIcon.size()); + memcpy(icon.raw(), &mUserData->mIcon[0], mUserData->mIcon.size()); + icon.detachTo(ComSafeArrayOutArg(aIcon)); + } + return hrc; +} + +STDMETHODIMP Machine::COMSETTER(Icon)(ComSafeArrayIn(BYTE, aIcon)) +{ + CheckComArgSafeArrayNotNull(aIcon); + AutoCaller autoCaller(this); + HRESULT hrc = autoCaller.rc(); + if (SUCCEEDED(hrc)) + { + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + hrc = checkStateDependency(MutableOrSavedStateDep); + if (SUCCEEDED(hrc)) + { + setModified(IsModified_MachineData); + mUserData.backup(); + com::SafeArray<BYTE> icon(ComSafeArrayInArg(aIcon)); + mUserData->mIcon.resize(icon.size()); + memcpy(&mUserData->mIcon[0], icon.raw(), mUserData->mIcon.size()); + } + } + return hrc; +} + +STDMETHODIMP Machine::COMGETTER(USBProxyAvailable)(BOOL *aAvailable) +{ + CheckComArgOutPointerValid(aAvailable); + + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + +#ifdef VBOX_WITH_USB + *aAvailable = true; +#else + *aAvailable = false; +#endif + return S_OK; +} STDMETHODIMP Machine::CloneTo(IMachine *pTarget, CloneMode_T mode, ComSafeArrayIn(CloneOptions_T, options), IProgress **pProgress) { @@ -7180,11 +7927,39 @@ void Machine::composeSavedStateFilename(Utf8Str &strStateFilePath) } /** + * Returns the full path to the default video capture file. + */ +void Machine::getDefaultVideoCaptureFile(Utf8Str &strFile) +{ + AutoCaller autoCaller(this); + AssertComRCReturnVoid(autoCaller.rc()); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + strFile = mData->m_strConfigFileFull; // path/to/machinesfolder/vmname/vmname.vbox + strFile.stripExt(); // path/to/machinesfolder/vmname/vmname + strFile.append(".webm"); // path/to/machinesfolder/vmname/vmname.webm +} + +/** + * Returns whether at least one USB controller is present for the VM. + */ +bool Machine::isUSBControllerPresent() +{ + AutoCaller autoCaller(this); + AssertComRCReturn(autoCaller.rc(), false); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + return (mUSBControllers->size() > 0); +} + +/** * @note Locks this object for writing, calls the client process * (inside the lock). */ HRESULT Machine::launchVMProcess(IInternalSessionControl *aControl, - const Utf8Str &strType, + const Utf8Str &strFrontend, const Utf8Str &strEnvironment, ProgressProxy *aProgress) { @@ -7192,6 +7967,7 @@ HRESULT Machine::launchVMProcess(IInternalSessionControl *aControl, AssertReturn(aControl, E_FAIL); AssertReturn(aProgress, E_FAIL); + AssertReturn(!strFrontend.isEmpty(), E_FAIL); AutoCaller autoCaller(this); if (FAILED(autoCaller.rc())) return autoCaller.rc(); @@ -7222,7 +7998,7 @@ HRESULT Machine::launchVMProcess(IInternalSessionControl *aControl, szPath[sz++] = RTPATH_DELIMITER; szPath[sz] = 0; char *cmd = szPath + sz; - sz = RTPATH_MAX - sz; + sz = sizeof(szPath) - sz; int vrc = VINF_SUCCESS; RTPROCESS pid = NIL_RTPROCESS; @@ -7278,16 +8054,44 @@ HRESULT Machine::launchVMProcess(IInternalSessionControl *aControl, RTStrFree(newEnvStr); } - /* Qt is default */ #ifdef VBOX_WITH_QTGUI - if (strType == "gui" || strType == "GUI/Qt") + if (strFrontend == "gui" || strFrontend == "GUI/Qt") { # ifdef RT_OS_DARWIN /* Avoid Launch Services confusing this with the selector by using a helper app. */ - const char VirtualBox_exe[] = "../Resources/VirtualBoxVM.app/Contents/MacOS/VirtualBoxVM"; + /* Modify the base path so that we don't need to use ".." below. */ + RTPathStripTrailingSlash(szPath); + RTPathStripFilename(szPath); + sz = strlen(szPath); + cmd = szPath + sz; + sz = sizeof(szPath) - sz; + +#define OSX_APP_NAME "VirtualBoxVM" +#define OSX_APP_PATH_FMT "/Resources/%s.app/Contents/MacOS/VirtualBoxVM" + + Utf8Str strAppOverride = getExtraData(Utf8Str("VBoxInternal2/VirtualBoxVMAppOverride")); + if ( strAppOverride.contains(".") + || strAppOverride.contains("/") + || strAppOverride.contains("\\") + || strAppOverride.contains(":")) + strAppOverride.setNull(); + Utf8Str strAppPath; + if (!strAppOverride.isEmpty()) + { + strAppPath = Utf8StrFmt(OSX_APP_PATH_FMT, strAppOverride.c_str()); + Utf8Str strFullPath(szPath); + strFullPath.append(strAppPath); + /* there is a race, but people using this deserve the failure */ + if (!RTFileExists(strFullPath.c_str())) + strAppOverride.setNull(); + } + if (strAppOverride.isEmpty()) + strAppPath = Utf8StrFmt(OSX_APP_PATH_FMT, OSX_APP_NAME); + const char *VirtualBox_exe = strAppPath.c_str(); + AssertReturn(sz >= strlen(VirtualBox_exe), E_UNEXPECTED); # else const char VirtualBox_exe[] = "VirtualBox" HOSTSUFF_EXE; -# endif Assert(sz >= sizeof(VirtualBox_exe)); +# endif strcpy(cmd, VirtualBox_exe); Utf8Str idStr = mData->mUuid.toString(); @@ -7302,7 +8106,7 @@ HRESULT Machine::launchVMProcess(IInternalSessionControl *aControl, else #ifdef VBOX_WITH_VBOXSDL - if (strType == "sdl" || strType == "GUI/SDL") + if (strFrontend == "sdl" || strFrontend == "GUI/SDL") { const char VBoxSDL_exe[] = "VBoxSDL" HOSTSUFF_EXE; Assert(sz >= sizeof(VBoxSDL_exe)); @@ -7320,9 +8124,9 @@ HRESULT Machine::launchVMProcess(IInternalSessionControl *aControl, else #ifdef VBOX_WITH_HEADLESS - if ( strType == "headless" - || strType == "capture" - || strType == "vrdp" /* Deprecated. Same as headless. */ + if ( strFrontend == "headless" + || strFrontend == "capture" + || strFrontend == "vrdp" /* Deprecated. Same as headless. */ ) { /* On pre-4.0 the "headless" type was used for passing "--vrdp off" to VBoxHeadless to let it work in OSE, @@ -7343,7 +8147,7 @@ HRESULT Machine::launchVMProcess(IInternalSessionControl *aControl, "--vrde", "config", 0, /* For "--capture". */ 0 }; - if (strType == "capture") + if (strFrontend == "capture") { unsigned pos = RT_ELEMENTS(args) - 2; args[pos] = "--capture"; @@ -7364,8 +8168,8 @@ HRESULT Machine::launchVMProcess(IInternalSessionControl *aControl, { RTEnvDestroy(env); return setError(E_INVALIDARG, - tr("Invalid session type: '%s'"), - strType.c_str()); + tr("Invalid frontend name: '%s'"), + strFrontend.c_str()); } RTEnvDestroy(env); @@ -7382,22 +8186,28 @@ HRESULT Machine::launchVMProcess(IInternalSessionControl *aControl, * because it doesn't need to call us back if called with a NULL argument. * Releasing the lock here is dangerous because we didn't prepare the * launch data yet, but the client we've just started may happen to be - * too fast and call openSession() that will fail (because of PID, etc.), + * too fast and call LockMachine() that will fail (because of PID, etc.), * so that the Machine will never get out of the Spawning session state. */ /* inform the session that it will be a remote one */ LogFlowThisFunc(("Calling AssignMachine (NULL)...\n")); - HRESULT rc = aControl->AssignMachine(NULL, LockType_Write); +#ifndef VBOX_WITH_GENERIC_SESSION_WATCHER + HRESULT rc = aControl->AssignMachine(NULL, LockType_Write, Bstr::Empty.raw()); +#else /* VBOX_WITH_GENERIC_SESSION_WATCHER */ + HRESULT rc = aControl->AssignMachine(NULL, LockType_Write, NULL); +#endif /* VBOX_WITH_GENERIC_SESSION_WATCHER */ LogFlowThisFunc(("AssignMachine (NULL) returned %08X\n", rc)); if (FAILED(rc)) { /* restore the session state */ mData->mSession.mState = SessionState_Unlocked; + alock.release(); + mParent->addProcessToReap(pid); /* The failure may occur w/o any error info (from RPC), so provide one */ return setError(VBOX_E_VM_ERROR, - tr("Failed to assign the machine to the session (%Rrc)"), rc); + tr("Failed to assign the machine to the session (%Rhrc)"), rc); } /* attach launch data to the machine */ @@ -7406,40 +8216,32 @@ HRESULT Machine::launchVMProcess(IInternalSessionControl *aControl, mData->mSession.mProgress = aProgress; mData->mSession.mPID = pid; mData->mSession.mState = SessionState_Spawning; - mData->mSession.mType = strType; + mData->mSession.mType = strFrontend; + + alock.release(); + mParent->addProcessToReap(pid); LogFlowThisFuncLeave(); return S_OK; } /** - * Returns @c true if the given machine has an open direct session and returns - * the session machine instance and additional session data (on some platforms) - * if so. + * Returns @c true if the given session machine instance has an open direct + * session (and optionally also for direct sessions which are closing) and + * returns the session control machine instance if so. * * Note that when the method returns @c false, the arguments remain unchanged. * - * @param aMachine Session machine object. - * @param aControl Direct session control object (optional). - * @param aIPCSem Mutex IPC semaphore handle for this machine (optional). + * @param aMachine Session machine object. + * @param aControl Direct session control object (optional). + * @param aAllowClosing If true then additionally a session which is currently + * being closed will also be allowed. * * @note locks this object for reading. */ -#if defined(RT_OS_WINDOWS) -bool Machine::isSessionOpen(ComObjPtr<SessionMachine> &aMachine, - ComPtr<IInternalSessionControl> *aControl /*= NULL*/, - HANDLE *aIPCSem /*= NULL*/, - bool aAllowClosing /*= false*/) -#elif defined(RT_OS_OS2) -bool Machine::isSessionOpen(ComObjPtr<SessionMachine> &aMachine, - ComPtr<IInternalSessionControl> *aControl /*= NULL*/, - HMTX *aIPCSem /*= NULL*/, - bool aAllowClosing /*= false*/) -#else bool Machine::isSessionOpen(ComObjPtr<SessionMachine> &aMachine, ComPtr<IInternalSessionControl> *aControl /*= NULL*/, bool aAllowClosing /*= false*/) -#endif { AutoLimitedCaller autoCaller(this); AssertComRCReturn(autoCaller.rc(), false); @@ -7461,11 +8263,6 @@ bool Machine::isSessionOpen(ComObjPtr<SessionMachine> &aMachine, if (aControl != NULL) *aControl = mData->mSession.mDirectControl; -#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) - /* Additional session data */ - if (aIPCSem != NULL) - *aIPCSem = aMachine->mIPCSem; -#endif return true; } @@ -7473,20 +8270,11 @@ bool Machine::isSessionOpen(ComObjPtr<SessionMachine> &aMachine, } /** - * Returns @c true if the given machine has an spawning direct session and - * returns and additional session data (on some platforms) if so. - * - * Note that when the method returns @c false, the arguments remain unchanged. - * - * @param aPID PID of the spawned direct session process. + * Returns @c true if the given machine has an spawning direct session. * * @note locks this object for reading. */ -#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) -bool Machine::isSessionSpawning(RTPROCESS *aPID /*= NULL*/) -#else bool Machine::isSessionSpawning() -#endif { AutoLimitedCaller autoCaller(this); AssertComRCReturn(autoCaller.rc(), false); @@ -7498,17 +8286,7 @@ bool Machine::isSessionSpawning() AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); if (mData->mSession.mState == SessionState_Spawning) - { -#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) - /* Additional session data */ - if (aPID != NULL) - { - AssertReturn(mData->mSession.mPID != NIL_RTPROCESS, false); - *aPID = mData->mSession.mPID; - } -#endif return true; - } return false; } @@ -7537,8 +8315,7 @@ bool Machine::checkForSpawnFailure() return true; } - /* VirtualBox::addProcessToReap() needs a write lock */ - AutoMultiWriteLock2 alock(mParent, this COMMA_LOCKVAL_SRC_POS); + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); if (mData->mSession.mState != SessionState_Spawning) { @@ -7549,22 +8326,12 @@ bool Machine::checkForSpawnFailure() HRESULT rc = S_OK; -#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) - - /* the process was already unexpectedly terminated, we just need to set an - * error and finalize session spawning */ - rc = setError(E_FAIL, - tr("The virtual machine '%s' has terminated unexpectedly during startup"), - getName().c_str()); -#else - /* PID not yet initialized, skip check. */ if (mData->mSession.mPID == NIL_RTPROCESS) return false; RTPROCSTATUS status; - int vrc = ::RTProcWait(mData->mSession.mPID, RTPROCWAIT_FLAGS_NOBLOCK, - &status); + int vrc = RTProcWait(mData->mSession.mPID, RTPROCWAIT_FLAGS_NOBLOCK, &status); if (vrc != VERR_PROCESS_RUNNING) { @@ -7583,16 +8350,14 @@ bool Machine::checkForSpawnFailure() else rc = setError(E_FAIL, tr("The virtual machine '%s' has terminated unexpectedly during startup (%Rrc)"), - getName().c_str(), rc); + getName().c_str(), vrc); } -#endif - if (FAILED(rc)) { /* Close the remote session, remove the remote control from the list * and reset session state to Closed (@note keep the code in sync with - * the relevant part in checkForSpawnFailure()). */ + * the relevant part in LockMachine()). */ Assert(mData->mSession.mRemoteControls.size() == 1); if (mData->mSession.mRemoteControls.size() == 1) @@ -7611,7 +8376,6 @@ bool Machine::checkForSpawnFailure() mData->mSession.mProgress.setNull(); } - mParent->addProcessToReap(mData->mSession.mPID); mData->mSession.mPID = NIL_RTPROCESS; mParent->onSessionStateChange(mData->mUuid, SessionState_Unlocked); @@ -7771,6 +8535,21 @@ void Machine::releaseStateDependency() } } +Utf8Str Machine::getExtraData(const Utf8Str &strKey) +{ + /* start with nothing found */ + Utf8Str strResult(""); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + settings::StringsMap::const_iterator it = mData->pMachineConfigFile->mapExtraDataItems.find(strKey); + if (it != mData->pMachineConfigFile->mapExtraDataItems.end()) + // found: + strResult = it->second; // source is a Utf8Str + + return strResult; +} + // protected methods ///////////////////////////////////////////////////////////////////////////// @@ -7894,6 +8673,7 @@ HRESULT Machine::initDataAndChildObjects() mHWData.allocate(); mMediaData.allocate(); mStorageControllers.allocate(); + mUSBControllers.allocate(); /* initialize mOSTypeId */ mUserData->s.strOsType = mParent->getUnknownOSType()->id(); @@ -7924,9 +8704,9 @@ HRESULT Machine::initDataAndChildObjects() unconst(mAudioAdapter).createObject(); mAudioAdapter->init(this); - /* create the USB controller object (always present, default is disabled) */ - unconst(mUSBController).createObject(); - mUSBController->init(this); + /* create the USB device filters object (always present) */ + unconst(mUSBDeviceFilters).createObject(); + mUSBDeviceFilters->init(this); /* create associated network adapter objects */ mNetworkAdapters.resize(Global::getMaxNetworkAdapters(mHWData->mChipsetType)); @@ -7975,10 +8755,10 @@ void Machine::uninitDataAndChildObjects() } } - if (mUSBController) + if (mUSBDeviceFilters) { - mUSBController->uninit(); - unconst(mUSBController).setNull(); + mUSBDeviceFilters->uninit(); + unconst(mUSBDeviceFilters).setNull(); } if (mAudioAdapter) @@ -8017,13 +8797,13 @@ void Machine::uninitDataAndChildObjects() unconst(mBIOSSettings).setNull(); } - /* Deassociate hard disks (only when a real Machine or a SnapshotMachine + /* Deassociate media (only when a real Machine or a SnapshotMachine * instance is uninitialized; SessionMachine instances refer to real - * Machine hard disks). This is necessary for a clean re-initialization of + * Machine media). This is necessary for a clean re-initialization of * the VM after successfully re-checking the accessibility state. Note * that in case of normal Machine or SnapshotMachine uninitialization (as - * a result of unregistering or deleting the snapshot), outdated hard - * disk attachments will already be uninitialized and deleted, so this + * a result of unregistering or deleting the snapshot), outdated media + * attachments will already be uninitialized and deleted, so this * code will not affect them. */ if ( !!mMediaData && (!isSessionMachine()) @@ -8033,10 +8813,10 @@ void Machine::uninitDataAndChildObjects() it != mMediaData->mAttachments.end(); ++it) { - ComObjPtr<Medium> hd = (*it)->getMedium(); - if (hd.isNull()) + ComObjPtr<Medium> pMedium = (*it)->getMedium(); + if (pMedium.isNull()) continue; - HRESULT rc = hd->removeBackReference(mData->mUuid, getSnapshotId()); + HRESULT rc = pMedium->removeBackReference(mData->mUuid, getSnapshotId()); AssertComRC(rc); } } @@ -8061,6 +8841,7 @@ void Machine::uninitDataAndChildObjects() * since it may be still in use) */ mMediaData.free(); mStorageControllers.free(); + mUSBControllers.free(); mHWData.free(); mUserData.free(); mSSData.free(); @@ -8228,9 +9009,28 @@ HRESULT Machine::loadMachineDataFromSettings(const settings::MachineConfigFile & // copy name, description, OS type, teleporter, UTC etc. mUserData->s = config.machineUserData; + // Decode the Icon overide data from config userdata and set onto Machine. + #define DECODE_STR_MAX _1M + const char* pszStr = config.machineUserData.ovIcon.c_str(); + ssize_t cbOut = RTBase64DecodedSize(pszStr, NULL); + if (cbOut > DECODE_STR_MAX) + return setError(E_FAIL, + tr("Icon Data too long.'%d' > '%d'"), + cbOut, + DECODE_STR_MAX); + com::SafeArray<BYTE> iconByte(cbOut); + HRESULT rc = RTBase64Decode(pszStr, iconByte.raw(), cbOut, NULL, NULL); + if (FAILED(rc)) + return setError(E_FAIL, + tr("Failure to Decode Icon Data. '%s' (%d)"), + pszStr, + rc); + mUserData->mIcon.resize(iconByte.size()); + memcpy(&mUserData->mIcon[0], iconByte.raw(), mUserData->mIcon.size()); + // look up the object by Id to check it is valid ComPtr<IGuestOSType> guestOSType; - HRESULT rc = mParent->GetGuestOSType(Bstr(mUserData->s.strOsType).raw(), + rc = mParent->GetGuestOSType(Bstr(mUserData->s.strOsType).raw(), guestOSType.asOutParam()); if (FAILED(rc)) return rc; @@ -8435,14 +9235,15 @@ HRESULT Machine::loadHardware(const settings::Hardware &data, const settings::De mHWData->mHardwareUUID = data.uuid; mHWData->mHWVirtExEnabled = data.fHardwareVirt; - mHWData->mHWVirtExExclusive = data.fHardwareVirtExclusive; mHWData->mHWVirtExNestedPagingEnabled = data.fNestedPaging; mHWData->mHWVirtExLargePagesEnabled = data.fLargePages; mHWData->mHWVirtExVPIDEnabled = data.fVPID; + mHWData->mHWVirtExUXEnabled = data.fUnrestrictedExecution; mHWData->mHWVirtExForceEnabled = data.fHardwareVirtForce; mHWData->mPAEEnabled = data.fPAE; mHWData->mSyntheticCpu = data.fSyntheticCpu; - + mHWData->mLongMode = data.enmLongMode; + mHWData->mTripleFaultReset = data.fTripleFaultReset; mHWData->mCPUCount = data.cCPUs; mHWData->mCPUHotPlugEnabled = data.fCpuHotPlug; mHWData->mCpuExecutionCap = data.ulCpuExecutionCap; @@ -8518,6 +9319,7 @@ HRESULT Machine::loadHardware(const settings::Hardware &data, const settings::De mHWData->mBootOrder[i] = it->second; } + mHWData->mGraphicsControllerType = data.graphicsControllerType; mHWData->mVRAMSize = data.ulVRAMSizeMB; mHWData->mMonitorCount = data.cMonitors; mHWData->mAccelerate3DEnabled = data.fAccelerate3D; @@ -8525,7 +9327,15 @@ HRESULT Machine::loadHardware(const settings::Hardware &data, const settings::De mHWData->mVideoCaptureWidth = data.ulVideoCaptureHorzRes; mHWData->mVideoCaptureHeight = data.ulVideoCaptureVertRes; mHWData->mVideoCaptureEnabled = data.fVideoCaptureEnabled; - mHWData->mVideoCaptureFile = data.strVideoCaptureFile; + for (unsigned i = 0; i < RT_ELEMENTS(mHWData->maVideoCaptureScreens); i++) + mHWData->maVideoCaptureScreens[i] = ASMBitTest(&data.u64VideoCaptureScreens, i); + AssertCompile(RT_ELEMENTS(mHWData->maVideoCaptureScreens) == sizeof(data.u64VideoCaptureScreens) * 8); + mHWData->mVideoCaptureRate = data.ulVideoCaptureRate; + mHWData->mVideoCaptureFPS = data.ulVideoCaptureFPS; + if (!data.strVideoCaptureFile.isEmpty()) + calculateFullPath(data.strVideoCaptureFile, mHWData->mVideoCaptureFile); + else + mHWData->mVideoCaptureFile.setNull(); mHWData->mFirmwareType = data.firmwareType; mHWData->mPointingHIDType = data.pointingHIDType; mHWData->mKeyboardHIDType = data.keyboardHIDType; @@ -8545,8 +9355,21 @@ HRESULT Machine::loadHardware(const settings::Hardware &data, const settings::De rc = mBandwidthControl->loadSettings(data.ioSettings); if (FAILED(rc)) return rc; - /* USB Controller */ - rc = mUSBController->loadSettings(data.usbController); + /* Shared folders */ + for (settings::USBControllerList::const_iterator it = data.usbSettings.llUSBControllers.begin(); + it != data.usbSettings.llUSBControllers.end(); + ++it) + { + const settings::USBController &settingsCtrl = *it; + ComObjPtr<USBController> newCtrl; + + newCtrl.createObject(); + newCtrl->init(this, settingsCtrl.strName, settingsCtrl.enmType); + mUSBControllers->push_back(newCtrl); + } + + /* USB device filters */ + rc = mUSBDeviceFilters->loadSettings(data.usbSettings); if (FAILED(rc)) return rc; // network adapters @@ -8671,8 +9494,8 @@ HRESULT Machine::loadHardware(const settings::Hardware &data, const settings::De const settings::GuestProperty &prop = *it; uint32_t fFlags = guestProp::NILFLAG; guestProp::validateFlags(prop.strFlags.c_str(), &fFlags); - HWData::GuestProperty property = { prop.strName, prop.strValue, (LONG64) prop.timestamp, fFlags }; - mHWData->mGuestProperties.push_back(property); + HWData::GuestProperty property = { prop.strValue, (LONG64) prop.timestamp, fFlags }; + mHWData->mGuestProperties[prop.strName] = property; } mHWData->mGuestPropertyNotificationPatterns = data.strNotificationPatterns; @@ -8683,6 +9506,9 @@ HRESULT Machine::loadHardware(const settings::Hardware &data, const settings::De return rc; mHWData->mAutostart = *pAutostart; + + /* default frontend */ + mHWData->mDefaultFrontend = data.strDefaultFrontend; } catch(std::bad_alloc &) { @@ -8973,6 +9799,8 @@ HRESULT Machine::loadStorageDevices(StorageController *aStorageController, dev.fTempEject, dev.fNonRotational, dev.fDiscard, + /// @todo load setting once the hot-pluggable flag works + false /*dev.fHotPluggable*/, pBwGroup.isNull() ? Utf8Str::Empty : pBwGroup->getName()); if (FAILED(rc)) break; @@ -9028,7 +9856,7 @@ HRESULT Machine::findSnapshotById(const Guid &aId, return E_FAIL; } - if (aId.isEmpty()) + if (aId.isZero()) aSnapshot = mData->mFirstSnapshot; else aSnapshot = mData->mFirstSnapshot->findChildOrSelf(aId.ref()); @@ -9112,6 +9940,57 @@ HRESULT Machine::getStorageControllerByName(const Utf8Str &aName, return VBOX_E_OBJECT_NOT_FOUND; } +/** + * Returns a USB controller object with the given name. + * + * @param aName USB controller name to find + * @param aUSBController where to return the found USB controller + * @param aSetError true to set extended error info on failure + */ +HRESULT Machine::getUSBControllerByName(const Utf8Str &aName, + ComObjPtr<USBController> &aUSBController, + bool aSetError /* = false */) +{ + AssertReturn(!aName.isEmpty(), E_INVALIDARG); + + for (USBControllerList::const_iterator it = mUSBControllers->begin(); + it != mUSBControllers->end(); + ++it) + { + if ((*it)->getName() == aName) + { + aUSBController = (*it); + return S_OK; + } + } + + if (aSetError) + return setError(VBOX_E_OBJECT_NOT_FOUND, + tr("Could not find a storage controller named '%s'"), + aName.c_str()); + return VBOX_E_OBJECT_NOT_FOUND; +} + +/** + * Returns the number of USB controller instance of the given type. + * + * @param enmType USB controller type. + */ +ULONG Machine::getUSBControllerCountByType(USBControllerType_T enmType) +{ + ULONG cCtrls = 0; + + for (USBControllerList::const_iterator it = mUSBControllers->begin(); + it != mUSBControllers->end(); + ++it) + { + if ((*it)->getControllerType() == enmType) + cCtrls++; + } + + return cCtrls; +} + HRESULT Machine::getMediumAttachmentsOfController(CBSTR aName, MediaData::AttachmentList &atts) { @@ -9534,6 +10413,26 @@ void Machine::copyMachineDataToSettings(settings::MachineConfigFile &config) // copy name, description, OS type, teleport, UTC etc. config.machineUserData = mUserData->s; + // Encode the Icon Override data from Machine and store on config userdata. + com::SafeArray<BYTE> iconByte; + COMGETTER(Icon)(ComSafeArrayAsOutParam(iconByte)); + ssize_t cbData = iconByte.size(); + if (cbData > 0) + { + ssize_t cchOut = RTBase64EncodedLength(cbData); + Utf8Str strIconData; + strIconData.reserve(cchOut+1); + int vrc = RTBase64Encode(iconByte.raw(), cbData, + strIconData.mutableRaw(), strIconData.capacity(), + NULL); + if (RT_FAILURE(vrc)) + throw setError(E_FAIL, tr("Failure to Encode Icon Data. '%s' (%Rrc)"), strIconData.mutableRaw(), vrc); + strIconData.jolt(); + config.machineUserData.ovIcon = strIconData; + } + else + config.machineUserData.ovIcon.setNull(); + if ( mData->mMachineState == MachineState_Saved || mData->mMachineState == MachineState_Restoring // when deleting a snapshot we may or may not have a saved state in the current state, @@ -9662,13 +10561,15 @@ HRESULT Machine::saveHardware(settings::Hardware &data, settings::Debugging *pDb // CPU data.fHardwareVirt = !!mHWData->mHWVirtExEnabled; - data.fHardwareVirtExclusive = !!mHWData->mHWVirtExExclusive; data.fNestedPaging = !!mHWData->mHWVirtExNestedPagingEnabled; data.fLargePages = !!mHWData->mHWVirtExLargePagesEnabled; data.fVPID = !!mHWData->mHWVirtExVPIDEnabled; + data.fUnrestrictedExecution = !!mHWData->mHWVirtExUXEnabled; data.fHardwareVirtForce = !!mHWData->mHWVirtExForceEnabled; data.fPAE = !!mHWData->mPAEEnabled; + data.enmLongMode = mHWData->mLongMode; data.fSyntheticCpu = !!mHWData->mSyntheticCpu; + data.fTripleFaultReset = !!mHWData->mTripleFaultReset; /* Standard and Extended CPUID leafs. */ data.llCpuIdLeafs.clear(); @@ -9728,14 +10629,25 @@ HRESULT Machine::saveHardware(settings::Hardware &data, settings::Debugging *pDb data.mapBootOrder[i] = mHWData->mBootOrder[i]; // display + data.graphicsControllerType = mHWData->mGraphicsControllerType; data.ulVRAMSizeMB = mHWData->mVRAMSize; data.cMonitors = mHWData->mMonitorCount; data.fAccelerate3D = !!mHWData->mAccelerate3DEnabled; data.fAccelerate2DVideo = !!mHWData->mAccelerate2DVideoEnabled; data.ulVideoCaptureHorzRes = mHWData->mVideoCaptureWidth; data.ulVideoCaptureVertRes = mHWData->mVideoCaptureHeight; - data.fVideoCaptureEnabled = !! mHWData->mVideoCaptureEnabled; - data.strVideoCaptureFile = mHWData->mVideoCaptureFile; + data.ulVideoCaptureRate = mHWData->mVideoCaptureRate; + data.ulVideoCaptureFPS = mHWData->mVideoCaptureFPS; + data.fVideoCaptureEnabled = !!mHWData->mVideoCaptureEnabled; + for (unsigned i = 0; i < sizeof(data.u64VideoCaptureScreens) * 8; i++) + { + if (mHWData->maVideoCaptureScreens[i]) + ASMBitSet(&data.u64VideoCaptureScreens, i); + else + ASMBitClear(&data.u64VideoCaptureScreens, i); + } + /* store relative video capture file if possible */ + copyPathRelativeToMachine(mHWData->mVideoCaptureFile, data.strVideoCaptureFile); /* VRDEServer settings (optional) */ rc = mVRDEServer->saveSettings(data.vrdeSettings); @@ -9746,7 +10658,21 @@ HRESULT Machine::saveHardware(settings::Hardware &data, settings::Debugging *pDb if (FAILED(rc)) throw rc; /* USB Controller (required) */ - rc = mUSBController->saveSettings(data.usbController); + for (USBControllerList::const_iterator it = mUSBControllers->begin(); + it != mUSBControllers->end(); + ++it) + { + ComObjPtr<USBController> ctrl = *it; + settings::USBController settingsCtrl; + + settingsCtrl.strName = ctrl->getName(); + settingsCtrl.enmType = ctrl->getControllerType(); + + data.usbSettings.llUSBControllers.push_back(settingsCtrl); + } + + /* USB device filters (required) */ + rc = mUSBDeviceFilters->saveSettings(data.usbSettings); if (FAILED(rc)) throw rc; /* Network adapters (required) */ @@ -9854,11 +10780,11 @@ HRESULT Machine::saveHardware(settings::Hardware &data, settings::Debugging *pDb // guest properties data.llGuestProperties.clear(); #ifdef VBOX_WITH_GUEST_PROPS - for (HWData::GuestPropertyList::const_iterator it = mHWData->mGuestProperties.begin(); + for (HWData::GuestPropertyMap::const_iterator it = mHWData->mGuestProperties.begin(); it != mHWData->mGuestProperties.end(); ++it) { - HWData::GuestProperty property = *it; + HWData::GuestProperty property = it->second; /* Remove transient guest properties at shutdown unless we * are saving state */ @@ -9869,7 +10795,7 @@ HRESULT Machine::saveHardware(settings::Hardware &data, settings::Debugging *pDb || property.mFlags & guestProp::TRANSRESET)) continue; settings::GuestProperty prop; - prop.strName = property.strName; + prop.strName = it->first; prop.strValue = property.strValue; prop.timestamp = property.mTimestamp; char szFlags[guestProp::MAX_FLAGS_LEN + 1]; @@ -9886,6 +10812,8 @@ HRESULT Machine::saveHardware(settings::Hardware &data, settings::Debugging *pDb *pDbg = mHWData->mDebugging; *pAutostart = mHWData->mAutostart; + + data.strDefaultFrontend = mHWData->mDefaultFrontend; } catch(std::bad_alloc &) { @@ -9976,14 +10904,17 @@ HRESULT Machine::saveStorageDevices(ComObjPtr<StorageController> aStorageControl dev.deviceType = pAttach->getType(); dev.lPort = pAttach->getPort(); dev.lDevice = pAttach->getDevice(); + dev.fPassThrough = pAttach->getPassthrough(); + /// @todo save setting once the hot-pluggable flag works + dev.fHotPluggable = false /* pAttach->getHotPluggable()*/; if (pMedium) { if (pMedium->isHostDrive()) dev.strHostDriveSrc = pMedium->getLocationFull(); else dev.uuid = pMedium->getId(); - dev.fPassThrough = pAttach->getPassthrough(); dev.fTempEject = pAttach->getTempEject(); + dev.fNonRotational = pAttach->getNonRotational(); dev.fDiscard = pAttach->getDiscard(); } @@ -10128,7 +11059,7 @@ void Machine::addMediumToRegistry(ComObjPtr<Medium> &pMedium) * @note The progress object is not marked as completed, neither on success nor * on failure. This is a responsibility of the caller. * - * @note Locks this object for writing. + * @note Locks this object and the media tree for writing. */ HRESULT Machine::createImplicitDiffs(IProgress *aProgress, ULONG aWeight, @@ -10151,6 +11082,7 @@ HRESULT Machine::createImplicitDiffs(IProgress *aProgress, HRESULT rc = S_OK; + // use appropriate locked media map (online or offline) MediumLockListMap lockedMediaOffline; MediumLockListMap *lockedMediaMap; if (aOnline) @@ -10282,6 +11214,8 @@ HRESULT Machine::createImplicitDiffs(IProgress *aProgress, if (aOnline) { alock.release(); + /* The currently attached medium will be read-only, change + * the lock type to read. */ rc = pMediumLockList->Update(pMedium, false); alock.acquire(); AssertComRCThrowRC(rc); @@ -10296,16 +11230,7 @@ HRESULT Machine::createImplicitDiffs(IProgress *aProgress, alock.acquire(); if (FAILED(rc)) throw rc; - rc = lockedMediaMap->Unlock(); - AssertComRCThrowRC(rc); - alock.release(); - rc = pMediumLockList->Append(diff, true); - alock.acquire(); - AssertComRCThrowRC(rc); - alock.release(); - rc = lockedMediaMap->Lock(); - alock.acquire(); - AssertComRCThrowRC(rc); + /* actual lock list update is done in Medium::commitMedia */ rc = diff->addBackReference(mData->mUuid); AssertComRCThrowRC(rc); @@ -10324,6 +11249,7 @@ HRESULT Machine::createImplicitDiffs(IProgress *aProgress, false /* aTempEject */, pAtt->getNonRotational(), pAtt->getDiscard(), + pAtt->getHotPluggable(), pAtt->getBandwidthGroup()); if (FAILED(rc)) throw rc; @@ -10334,7 +11260,7 @@ HRESULT Machine::createImplicitDiffs(IProgress *aProgress, } catch (HRESULT aRC) { rc = aRC; } - /* unlock all hard disks we locked */ + /* unlock all hard disks we locked when there is no VM */ if (!aOnline) { ErrorInfoKeeper eik; @@ -10343,14 +11269,6 @@ HRESULT Machine::createImplicitDiffs(IProgress *aProgress, AssertComRC(rc1); } - if (FAILED(rc)) - { - MultiResult mrc = rc; - - alock.release(); - mrc = deleteImplicitDiffs(); - } - return rc; } @@ -10361,99 +11279,225 @@ HRESULT Machine::createImplicitDiffs(IProgress *aProgress, * Note that to delete hard disks created by #AttachDevice() this method is * called from #fixupMedia() when the changes are rolled back. * - * @note Locks this object for writing. + * @note Locks this object and the media tree for writing. */ -HRESULT Machine::deleteImplicitDiffs() +HRESULT Machine::deleteImplicitDiffs(bool aOnline) { + LogFlowThisFunc(("aOnline=%d\n", aOnline)); + AutoCaller autoCaller(this); AssertComRCReturn(autoCaller.rc(), autoCaller.rc()); - AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); - LogFlowThisFuncEnter(); + AutoMultiWriteLock2 alock(this->lockHandle(), + &mParent->getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS); + /* We absolutely must have backed up state. */ AssertReturn(mMediaData.isBackedUp(), E_FAIL); - HRESULT rc = S_OK; - - MediaData::AttachmentList implicitAtts; - - const MediaData::AttachmentList &oldAtts = mMediaData.backedUpData()->mAttachments; - - /* enumerate new attachments */ + /* Check if there are any implicitly created diff images. */ + bool fImplicitDiffs = false; for (MediaData::AttachmentList::const_iterator it = mMediaData->mAttachments.begin(); it != mMediaData->mAttachments.end(); ++it) { - ComObjPtr<Medium> hd = (*it)->getMedium(); - if (hd.isNull()) - continue; - - if ((*it)->isImplicit()) - { - /* deassociate and mark for deletion */ - LogFlowThisFunc(("Detaching '%s', pending deletion\n", (*it)->getLogName())); - rc = hd->removeBackReference(mData->mUuid); - AssertComRC(rc); - implicitAtts.push_back(*it); - continue; - } - - /* was this hard disk attached before? */ - if (!findAttachment(oldAtts, hd)) + const ComObjPtr<MediumAttachment> &pAtt = *it; + if (pAtt->isImplicit()) { - /* no: de-associate */ - LogFlowThisFunc(("Detaching '%s', no deletion\n", (*it)->getLogName())); - rc = hd->removeBackReference(mData->mUuid); - AssertComRC(rc); - continue; + fImplicitDiffs = true; + break; } - LogFlowThisFunc(("Not detaching '%s'\n", (*it)->getLogName())); } + /* If there is nothing to do, leave early. This saves lots of image locking + * effort. It also avoids a MachineStateChanged event without real reason. + * This is important e.g. when loading a VM config, because there should be + * no events. Otherwise API clients can become thoroughly confused for + * inaccessible VMs (the code for loading VM configs uses this method for + * cleanup if the config makes no sense), as they take such events as an + * indication that the VM is alive, and they would force the VM config to + * be reread, leading to an endless loop. */ + if (!fImplicitDiffs) + return S_OK; - /* rollback hard disk changes */ - mMediaData.rollback(); + HRESULT rc = S_OK; + MachineState_T oldState = mData->mMachineState; + + /* will release the lock before the potentially lengthy operation, + * so protect with the special state (unless already protected) */ + if ( oldState != MachineState_Saving + && oldState != MachineState_LiveSnapshotting + && oldState != MachineState_RestoringSnapshot + && oldState != MachineState_DeletingSnapshot + && oldState != MachineState_DeletingSnapshotOnline + && oldState != MachineState_DeletingSnapshotPaused + ) + setMachineState(MachineState_SettingUp); - MultiResult mrc(S_OK); + // use appropriate locked media map (online or offline) + MediumLockListMap lockedMediaOffline; + MediumLockListMap *lockedMediaMap; + if (aOnline) + lockedMediaMap = &mData->mSession.mLockedMedia; + else + lockedMediaMap = &lockedMediaOffline; - /* delete unused implicit diffs */ - if (implicitAtts.size() != 0) + try { - /* will release the lock before the potentially lengthy - * operation, so protect with the special state (unless already - * protected) */ - MachineState_T oldState = mData->mMachineState; - if ( oldState != MachineState_Saving - && oldState != MachineState_LiveSnapshotting - && oldState != MachineState_RestoringSnapshot - && oldState != MachineState_DeletingSnapshot - && oldState != MachineState_DeletingSnapshotOnline - && oldState != MachineState_DeletingSnapshotPaused - ) - setMachineState(MachineState_SettingUp); + if (!aOnline) + { + /* lock all attached hard disks early to detect "in use" + * situations before deleting actual diffs */ + for (MediaData::AttachmentList::const_iterator it = mMediaData->mAttachments.begin(); + it != mMediaData->mAttachments.end(); + ++it) + { + MediumAttachment* pAtt = *it; + if (pAtt->getType() == DeviceType_HardDisk) + { + Medium* pMedium = pAtt->getMedium(); + Assert(pMedium); - alock.release(); + MediumLockList *pMediumLockList(new MediumLockList()); + alock.release(); + rc = pMedium->createMediumLockList(true /* fFailIfInaccessible */, + false /* fMediumLockWrite */, + NULL, + *pMediumLockList); + alock.acquire(); - for (MediaData::AttachmentList::const_iterator it = implicitAtts.begin(); - it != implicitAtts.end(); + if (FAILED(rc)) + { + delete pMediumLockList; + throw rc; + } + + rc = lockedMediaMap->Insert(pAtt, pMediumLockList); + if (FAILED(rc)) + throw rc; + } + } + + if (FAILED(rc)) + throw rc; + } // end of offline + + /* Lock lists are now up to date and include implicitly created media */ + + /* Go through remembered attachments and delete all implicitly created + * diffs and fix up the attachment information */ + const MediaData::AttachmentList &oldAtts = mMediaData.backedUpData()->mAttachments; + MediaData::AttachmentList implicitAtts; + for (MediaData::AttachmentList::const_iterator it = mMediaData->mAttachments.begin(); + it != mMediaData->mAttachments.end(); ++it) { - LogFlowThisFunc(("Deleting '%s'\n", (*it)->getLogName())); - ComObjPtr<Medium> hd = (*it)->getMedium(); + ComObjPtr<MediumAttachment> pAtt = *it; + ComObjPtr<Medium> pMedium = pAtt->getMedium(); + if (pMedium.isNull()) + continue; - rc = hd->deleteStorage(NULL /*aProgress*/, true /*aWait*/); - AssertMsg(SUCCEEDED(rc), ("rc=%Rhrc it=%s hd=%s\n", rc, (*it)->getLogName(), hd->getLocationFull().c_str() )); - mrc = rc; + // Implicit attachments go on the list for deletion and back references are removed. + if (pAtt->isImplicit()) + { + /* Deassociate and mark for deletion */ + LogFlowThisFunc(("Detaching '%s', pending deletion\n", pAtt->getLogName())); + rc = pMedium->removeBackReference(mData->mUuid); + if (FAILED(rc)) + throw rc; + implicitAtts.push_back(pAtt); + continue; + } + + /* Was this medium attached before? */ + if (!findAttachment(oldAtts, pMedium)) + { + /* no: de-associate */ + LogFlowThisFunc(("Detaching '%s', no deletion\n", pAtt->getLogName())); + rc = pMedium->removeBackReference(mData->mUuid); + if (FAILED(rc)) + throw rc; + continue; + } + LogFlowThisFunc(("Not detaching '%s'\n", pAtt->getLogName())); } - alock.acquire(); + /* If there are implicit attachments to delete, throw away the lock + * map contents (which will unlock all media) since the medium + * attachments will be rolled back. Below we need to completely + * recreate the lock map anyway since it is infinitely complex to + * do this incrementally (would need reconstructing each attachment + * change, which would be extremely hairy). */ + if (implicitAtts.size() != 0) + { + ErrorInfoKeeper eik; + + HRESULT rc1 = lockedMediaMap->Clear(); + AssertComRC(rc1); + } + + /* rollback hard disk changes */ + mMediaData.rollback(); + + MultiResult mrc(S_OK); + + // Delete unused implicit diffs. + if (implicitAtts.size() != 0) + { + alock.release(); + + for (MediaData::AttachmentList::const_iterator it = implicitAtts.begin(); + it != implicitAtts.end(); + ++it) + { + // Remove medium associated with this attachment. + ComObjPtr<MediumAttachment> pAtt = *it; + Assert(pAtt); + LogFlowThisFunc(("Deleting '%s'\n", pAtt->getLogName())); + ComObjPtr<Medium> pMedium = pAtt->getMedium(); + Assert(pMedium); + + rc = pMedium->deleteStorage(NULL /*aProgress*/, true /*aWait*/); + // continue on delete failure, just collect error messages + AssertMsg(SUCCEEDED(rc), ("rc=%Rhrc it=%s hd=%s\n", rc, pAtt->getLogName(), pMedium->getLocationFull().c_str() )); + mrc = rc; + } + + alock.acquire(); + + /* if there is a VM recreate media lock map as mentioned above, + * otherwise it is a waste of time and we leave things unlocked */ + if (aOnline) + { + const ComObjPtr<SessionMachine> pMachine = mData->mSession.mMachine; + /* must never be NULL, but better safe than sorry */ + if (!pMachine.isNull()) + { + alock.release(); + rc = mData->mSession.mMachine->lockMedia(); + alock.acquire(); + if (FAILED(rc)) + throw rc; + } + } + } + } + catch (HRESULT aRC) {rc = aRC;} + + if (mData->mMachineState == MachineState_SettingUp) + setMachineState(oldState); + + /* unlock all hard disks we locked when there is no VM */ + if (!aOnline) + { + ErrorInfoKeeper eik; - if (mData->mMachineState == MachineState_SettingUp) - setMachineState(oldState); + HRESULT rc1 = lockedMediaMap->Clear(); + AssertComRC(rc1); } - return mrc; + return rc; } + /** * Looks through the given list of media attachments for one with the given parameters * and returns it, or NULL if not found. The list is a parameter so that backup lists @@ -10822,11 +11866,21 @@ void Machine::commitMedia(bool aOnline /*= false*/) /* unlock since medium is not used anymore */ MediumLockList *pMediumLockList; rc = mData->mSession.mLockedMedia.Get(pAttach, pMediumLockList); - AssertComRC(rc); - if (pMediumLockList) + if (RT_UNLIKELY(rc == VBOX_E_INVALID_OBJECT_STATE)) + { + /* this happens for online snapshots, there the attachment + * is changing, but only to a diff image created under + * the old one, so there is no separate lock list */ + Assert(!pMediumLockList); + } + else { - rc = mData->mSession.mLockedMedia.Remove(pAttach); AssertComRC(rc); + if (pMediumLockList) + { + rc = mData->mSession.mLockedMedia.Remove(pAttach); + AssertComRC(rc); + } } } } @@ -10881,11 +11935,10 @@ void Machine::commitMedia(bool aOnline /*= false*/) void Machine::rollbackMedia() { AutoCaller autoCaller(this); - AssertComRCReturnVoid (autoCaller.rc()); - - AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertComRCReturnVoid(autoCaller.rc()); - LogFlowThisFunc(("Entering\n")); + // AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + LogFlowThisFunc(("Entering rollbackMedia\n")); HRESULT rc = S_OK; @@ -10927,7 +11980,7 @@ void Machine::rollbackMedia() /** @todo convert all this Machine-based voodoo to MediumAttachment * based rollback logic. */ - deleteImplicitDiffs(); + deleteImplicitDiffs(Global::IsOnline(mData->mMachineState)); return; } @@ -11011,6 +12064,40 @@ void Machine::rollback(bool aNotify) } } + if (!mUSBControllers.isNull()) + { + if (mUSBControllers.isBackedUp()) + { + /* unitialize all new devices (absent in the backed up list). */ + USBControllerList::const_iterator it = mUSBControllers->begin(); + USBControllerList *backedList = mUSBControllers.backedUpData(); + while (it != mUSBControllers->end()) + { + if ( std::find(backedList->begin(), backedList->end(), *it) + == backedList->end() + ) + { + (*it)->uninit(); + } + ++it; + } + + /* restore the list */ + mUSBControllers.rollback(); + } + + /* rollback any changes to devices after restoring the list */ + if (mData->flModifications & IsModified_USB) + { + USBControllerList::const_iterator it = mUSBControllers->begin(); + while (it != mUSBControllers->end()) + { + (*it)->rollback(); + ++it; + } + } + } + mUserData.rollback(); mHWData.rollback(); @@ -11027,8 +12114,8 @@ void Machine::rollback(bool aNotify) if (mAudioAdapter) mAudioAdapter->rollback(); - if (mUSBController && (mData->flModifications & IsModified_USB)) - mUSBController->rollback(); + if (mUSBDeviceFilters && (mData->flModifications & IsModified_USB)) + mUSBDeviceFilters->rollback(); if (mBandwidthControl && (mData->flModifications & IsModified_BandwidthControl)) mBandwidthControl->rollback(); @@ -11128,12 +12215,12 @@ void Machine::commit() mHWData.commit(); if (mMediaData.isBackedUp()) - commitMedia(); + commitMedia(Global::IsOnline(mData->mMachineState)); mBIOSSettings->commit(); mVRDEServer->commit(); mAudioAdapter->commit(); - mUSBController->commit(); + mUSBDeviceFilters->commit(); mBandwidthControl->commit(); /* Since mNetworkAdapters is a list which might have been changed (resized) @@ -11263,6 +12350,77 @@ void Machine::commit() } } + bool commitUSBControllers = false; + + if (mUSBControllers.isBackedUp()) + { + mUSBControllers.commit(); + + if (mPeer) + { + /* Commit all changes to new controllers (this will reshare data with + * peers for those who have peers) */ + USBControllerList *newList = new USBControllerList(); + USBControllerList::const_iterator it = mUSBControllers->begin(); + while (it != mUSBControllers->end()) + { + (*it)->commit(); + + /* look if this controller has a peer device */ + ComObjPtr<USBController> peer = (*it)->getPeer(); + if (!peer) + { + /* no peer means the device is a newly created one; + * create a peer owning data this device share it with */ + peer.createObject(); + peer->init(mPeer, *it, true /* aReshare */); + } + else + { + /* remove peer from the old list */ + mPeer->mUSBControllers->remove(peer); + } + /* and add it to the new list */ + newList->push_back(peer); + + ++it; + } + + /* uninit old peer's controllers that are left */ + it = mPeer->mUSBControllers->begin(); + while (it != mPeer->mUSBControllers->end()) + { + (*it)->uninit(); + ++it; + } + + /* attach new list of controllers to our peer */ + mPeer->mUSBControllers.attach(newList); + } + else + { + /* we have no peer (our parent is the newly created machine); + * just commit changes to devices */ + commitUSBControllers = true; + } + } + else + { + /* the list of controllers itself is not changed, + * just commit changes to controllers themselves */ + commitUSBControllers = true; + } + + if (commitUSBControllers) + { + USBControllerList::const_iterator it = mUSBControllers->begin(); + while (it != mUSBControllers->end()) + { + (*it)->commit(); + ++it; + } + } + if (isSessionMachine()) { /* attach new data to the primary machine and reshare it */ @@ -11311,7 +12469,7 @@ void Machine::copyFrom(Machine *aThat) mBIOSSettings->copyFrom(aThat->mBIOSSettings); mVRDEServer->copyFrom(aThat->mVRDEServer); mAudioAdapter->copyFrom(aThat->mAudioAdapter); - mUSBController->copyFrom(aThat->mUSBController); + mUSBDeviceFilters->copyFrom(aThat->mUSBDeviceFilters); mBandwidthControl->copyFrom(aThat->mBandwidthControl); /* create private copies of all controllers */ @@ -11327,6 +12485,19 @@ void Machine::copyFrom(Machine *aThat) mStorageControllers->push_back(ctrl); } + /* create private copies of all USB controllers */ + mUSBControllers.backup(); + mUSBControllers->clear(); + for (USBControllerList::iterator it = aThat->mUSBControllers->begin(); + it != aThat->mUSBControllers->end(); + ++it) + { + ComObjPtr<USBController> ctrl; + ctrl.createObject(); + ctrl->initCopy(this, *it); + mUSBControllers->push_back(ctrl); + } + mNetworkAdapters.resize(aThat->mNetworkAdapters.size()); for (ULONG slot = 0; slot < mNetworkAdapters.size(); slot++) mNetworkAdapters[slot]->copyFrom(aThat->mNetworkAdapters[slot]); @@ -11363,6 +12534,26 @@ bool Machine::isControllerHotplugCapable(StorageControllerType_T enmCtrlType) #ifdef VBOX_WITH_RESOURCE_USAGE_API +void Machine::getDiskList(MediaList &list) +{ + for (MediaData::AttachmentList::const_iterator it = mMediaData->mAttachments.begin(); + it != mMediaData->mAttachments.end(); + ++it) + { + MediumAttachment* pAttach = *it; + /* just in case */ + AssertStmt(pAttach, continue); + + AutoCaller localAutoCallerA(pAttach); + if (FAILED(localAutoCallerA.rc())) continue; + + AutoReadLock local_alockA(pAttach COMMA_LOCKVAL_SRC_POS); + + if (pAttach->getType() == DeviceType_HardDisk) + list.push_back(pAttach->getMedium()); + } +} + void Machine::registerMetrics(PerformanceCollector *aCollector, Machine *aMachine, RTPROCESS pid) { AssertReturnVoid(isWriteLockOnCurrentThread()); @@ -11376,6 +12567,12 @@ void Machine::registerMetrics(PerformanceCollector *aCollector, Machine *aMachin "Percentage of processor time spent in kernel mode by the VM process."); pm::SubMetric *ramUsageUsed = new pm::SubMetric("RAM/Usage/Used", "Size of resident portion of VM process in memory."); + pm::SubMetric *diskUsageUsed = new pm::SubMetric("Disk/Usage/Used", + "Actual size of all VM disks combined."); + pm::SubMetric *machineNetRx = new pm::SubMetric("Net/Rate/Rx", + "Network receive rate."); + pm::SubMetric *machineNetTx = new pm::SubMetric("Net/Rate/Tx", + "Network transmit rate."); /* Create and register base metrics */ pm::BaseMetric *cpuLoad = new pm::MachineCpuLoadRaw(hal, aMachine, pid, cpuLoadUser, cpuLoadKernel); @@ -11383,6 +12580,11 @@ void Machine::registerMetrics(PerformanceCollector *aCollector, Machine *aMachin pm::BaseMetric *ramUsage = new pm::MachineRamUsage(hal, aMachine, pid, ramUsageUsed); aCollector->registerBaseMetric(ramUsage); + MediaList disks; + getDiskList(disks); + pm::BaseMetric *diskUsage = new pm::MachineDiskUsage(hal, aMachine, disks, + diskUsageUsed); + aCollector->registerBaseMetric(diskUsage); aCollector->registerMetric(new pm::Metric(cpuLoad, cpuLoadUser, 0)); aCollector->registerMetric(new pm::Metric(cpuLoad, cpuLoadUser, @@ -11407,6 +12609,14 @@ void Machine::registerMetrics(PerformanceCollector *aCollector, Machine *aMachin aCollector->registerMetric(new pm::Metric(ramUsage, ramUsageUsed, new pm::AggregateMax())); + aCollector->registerMetric(new pm::Metric(diskUsage, diskUsageUsed, 0)); + aCollector->registerMetric(new pm::Metric(diskUsage, diskUsageUsed, + new pm::AggregateAvg())); + aCollector->registerMetric(new pm::Metric(diskUsage, diskUsageUsed, + new pm::AggregateMin())); + aCollector->registerMetric(new pm::Metric(diskUsage, diskUsageUsed, + new pm::AggregateMax())); + /* Guest metrics collector */ mCollectorGuest = new pm::CollectorGuest(aMachine, pid); @@ -11432,6 +12642,10 @@ 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 *machineNetRate = new pm::MachineNetRate(mCollectorGuest, aMachine, + machineNetRx, machineNetTx); + aCollector->registerBaseMetric(machineNetRate); + pm::BaseMetric *guestCpuLoad = new pm::GuestCpuLoad(mCollectorGuest, aMachine, guestLoadUser, guestLoadKernel, guestLoadIdle); aCollector->registerBaseMetric(guestCpuLoad); @@ -11442,6 +12656,16 @@ void Machine::registerMetrics(PerformanceCollector *aCollector, Machine *aMachin guestMemCache, guestPagedTotal); aCollector->registerBaseMetric(guestCpuMem); + aCollector->registerMetric(new pm::Metric(machineNetRate, machineNetRx, 0)); + aCollector->registerMetric(new pm::Metric(machineNetRate, machineNetRx, new pm::AggregateAvg())); + aCollector->registerMetric(new pm::Metric(machineNetRate, machineNetRx, new pm::AggregateMin())); + aCollector->registerMetric(new pm::Metric(machineNetRate, machineNetRx, new pm::AggregateMax())); + + aCollector->registerMetric(new pm::Metric(machineNetRate, machineNetTx, 0)); + aCollector->registerMetric(new pm::Metric(machineNetRate, machineNetTx, new pm::AggregateAvg())); + aCollector->registerMetric(new pm::Metric(machineNetRate, machineNetTx, new pm::AggregateMin())); + aCollector->registerMetric(new pm::Metric(machineNetRate, machineNetTx, new pm::AggregateMax())); + aCollector->registerMetric(new pm::Metric(guestCpuLoad, guestLoadUser, 0)); aCollector->registerMetric(new pm::Metric(guestCpuLoad, guestLoadUser, new pm::AggregateAvg())); aCollector->registerMetric(new pm::Metric(guestCpuLoad, guestLoadUser, new pm::AggregateMin())); @@ -11510,15 +12734,7 @@ HRESULT SessionMachine::FinalConstruct() { LogFlowThisFunc(("\n")); -#if defined(RT_OS_WINDOWS) - mIPCSem = NULL; -#elif defined(RT_OS_OS2) - mIPCSem = NULLHANDLE; -#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER) - mIPCSem = -1; -#else -# error "Port me!" -#endif + mClientToken = NULL; return BaseFinalConstruct(); } @@ -11527,13 +12743,21 @@ void SessionMachine::FinalRelease() { LogFlowThisFunc(("\n")); + Assert(!mClientToken); + /* paranoia, should not hang around any more */ + if (mClientToken) + { + delete mClientToken; + mClientToken = NULL; + } + uninit(Uninit::Unexpected); BaseFinalRelease(); } /** - * @note Must be called only by Machine::openSession() from its own write lock. + * @note Must be called only by Machine::LockMachine() from its own write lock. */ HRESULT SessionMachine::init(Machine *aMachine) { @@ -11548,96 +12772,25 @@ HRESULT SessionMachine::init(Machine *aMachine) AutoInitSpan autoInitSpan(this); AssertReturn(autoInitSpan.isOk(), E_FAIL); - /* create the interprocess semaphore */ -#if defined(RT_OS_WINDOWS) - mIPCSemName = aMachine->mData->m_strConfigFileFull; - for (size_t i = 0; i < mIPCSemName.length(); i++) - if (mIPCSemName.raw()[i] == '\\') - mIPCSemName.raw()[i] = '/'; - mIPCSem = ::CreateMutex(NULL, FALSE, mIPCSemName.raw()); - ComAssertMsgRet(mIPCSem, - ("Cannot create IPC mutex '%ls', err=%d", - mIPCSemName.raw(), ::GetLastError()), - E_FAIL); -#elif defined(RT_OS_OS2) - Utf8Str ipcSem = Utf8StrFmt("\\SEM32\\VBOX\\VM\\{%RTuuid}", - aMachine->mData->mUuid.raw()); - mIPCSemName = ipcSem; - APIRET arc = ::DosCreateMutexSem((PSZ)ipcSem.c_str(), &mIPCSem, 0, FALSE); - ComAssertMsgRet(arc == NO_ERROR, - ("Cannot create IPC mutex '%s', arc=%ld", - ipcSem.c_str(), arc), - E_FAIL); -#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER) -# ifdef VBOX_WITH_NEW_SYS_V_KEYGEN -# if defined(RT_OS_FREEBSD) && (HC_ARCH_BITS == 64) - /** @todo Check that this still works correctly. */ - AssertCompileSize(key_t, 8); -# else - AssertCompileSize(key_t, 4); -# endif - key_t key; - mIPCSem = -1; - mIPCKey = "0"; - for (uint32_t i = 0; i < 1 << 24; i++) - { - key = ((uint32_t)'V' << 24) | i; - int sem = ::semget(key, 1, S_IRUSR | S_IWUSR | IPC_CREAT | IPC_EXCL); - if (sem >= 0 || (errno != EEXIST && errno != EACCES)) - { - mIPCSem = sem; - if (sem >= 0) - mIPCKey = BstrFmt("%u", key); - break; - } - } -# else /* !VBOX_WITH_NEW_SYS_V_KEYGEN */ - Utf8Str semName = aMachine->mData->m_strConfigFileFull; - char *pszSemName = NULL; - RTStrUtf8ToCurrentCP(&pszSemName, semName); - key_t key = ::ftok(pszSemName, 'V'); - RTStrFree(pszSemName); - - mIPCSem = ::semget(key, 1, S_IRWXU | S_IRWXG | S_IRWXO | IPC_CREAT); -# endif /* !VBOX_WITH_NEW_SYS_V_KEYGEN */ + HRESULT rc = S_OK; - int errnoSave = errno; - if (mIPCSem < 0 && errnoSave == ENOSYS) + /* create the machine client token */ + try { - setError(E_FAIL, - tr("Cannot create IPC semaphore. Most likely your host kernel lacks " - "support for SysV IPC. Check the host kernel configuration for " - "CONFIG_SYSVIPC=y")); - return E_FAIL; + mClientToken = new ClientToken(aMachine, this); + if (!mClientToken->isReady()) + { + delete mClientToken; + mClientToken = NULL; + rc = E_FAIL; + } } - /* ENOSPC can also be the result of VBoxSVC crashes without properly freeing - * the IPC semaphores */ - if (mIPCSem < 0 && errnoSave == ENOSPC) - { -#ifdef RT_OS_LINUX - setError(E_FAIL, - tr("Cannot create IPC semaphore because the system limit for the " - "maximum number of semaphore sets (SEMMNI), or the system wide " - "maximum number of semaphores (SEMMNS) would be exceeded. The " - "current set of SysV IPC semaphores can be determined from " - "the file /proc/sysvipc/sem")); -#else - setError(E_FAIL, - tr("Cannot create IPC semaphore because the system-imposed limit " - "on the maximum number of allowed semaphores or semaphore " - "identifiers system-wide would be exceeded")); -#endif - return E_FAIL; + catch (std::bad_alloc &) + { + rc = E_OUTOFMEMORY; } - ComAssertMsgRet(mIPCSem >= 0, ("Cannot create IPC semaphore, errno=%d", errnoSave), - E_FAIL); - /* set the initial value to 1 */ - int rv = ::semctl(mIPCSem, 0, SETVAL, 1); - ComAssertMsgRet(rv == 0, ("Cannot init IPC semaphore, errno=%d", errno), - E_FAIL); -#else -# error "Port me!" -#endif + if (FAILED(rc)) + return rc; /* memorize the peer Machine */ unconst(mPeer) = aMachine; @@ -11663,6 +12816,17 @@ HRESULT SessionMachine::init(Machine *aMachine) mStorageControllers->push_back(ctl); } + mUSBControllers.allocate(); + for (USBControllerList::const_iterator it = aMachine->mUSBControllers->begin(); + it != aMachine->mUSBControllers->end(); + ++it) + { + ComObjPtr<USBController> ctl; + ctl.createObject(); + ctl->init(this, *it); + mUSBControllers->push_back(ctl); + } + unconst(mBIOSSettings).createObject(); mBIOSSettings->init(this, aMachine->mBIOSSettings); /* create another VRDEServer object that will be mutable */ @@ -11683,9 +12847,10 @@ HRESULT SessionMachine::init(Machine *aMachine) unconst(mParallelPorts[slot]).createObject(); mParallelPorts[slot]->init(this, aMachine->mParallelPorts[slot]); } - /* create another USB controller object that will be mutable */ - unconst(mUSBController).createObject(); - mUSBController->init(this, aMachine->mUSBController); + + /* create another USB device filters object that will be mutable */ + unconst(mUSBDeviceFilters).createObject(); + mUSBDeviceFilters->init(this, aMachine->mUSBDeviceFilters); /* create a list of network adapters that will be mutable */ mNetworkAdapters.resize(aMachine->mNetworkAdapters.size()); @@ -11705,13 +12870,16 @@ HRESULT SessionMachine::init(Machine *aMachine) /* Confirm a successful initialization when it's the case */ autoInitSpan.setSucceeded(); + miNATNetworksStarted = 0; + LogFlowThisFuncLeave(); - return S_OK; + return rc; } /** * Uninitializes this session object. If the reason is other than - * Uninit::Unexpected, then this method MUST be called from #checkForDeath(). + * Uninit::Unexpected, then this method MUST be called from #checkForDeath() + * or the client watcher code. * * @param aReason uninitialization reason * @@ -11747,24 +12915,12 @@ void SessionMachine::uninit(Uninit::Reason aReason) * below, the following is enough. */ LogFlowThisFunc(("Initialization failed.\n")); -#if defined(RT_OS_WINDOWS) - if (mIPCSem) - ::CloseHandle(mIPCSem); - mIPCSem = NULL; -#elif defined(RT_OS_OS2) - if (mIPCSem != NULLHANDLE) - ::DosCloseMutexSem(mIPCSem); - mIPCSem = NULLHANDLE; -#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER) - if (mIPCSem >= 0) - ::semctl(mIPCSem, 0, IPC_RMID); - mIPCSem = -1; -# ifdef VBOX_WITH_NEW_SYS_V_KEYGEN - mIPCKey = "0"; -# endif /* VBOX_WITH_NEW_SYS_V_KEYGEN */ -#else -# error "Port me!" -#endif + /* destroy the machine client token */ + if (mClientToken) + { + delete mClientToken; + mClientToken = NULL; + } uninitDataAndChildObjects(); mData.free(); unconst(mParent) = NULL; @@ -11792,7 +12948,7 @@ void SessionMachine::uninit(Uninit::Reason aReason) * * This is identical to SessionMachine::DetachAllUSBDevices except * for the aAbnormal argument. */ - HRESULT rc = mUSBController->notifyProxy(false /* aInsertFilters */); + HRESULT rc = mUSBDeviceFilters->notifyProxy(false /* aInsertFilters */); AssertComRC(rc); NOREF(rc); @@ -11803,23 +12959,17 @@ void SessionMachine::uninit(Uninit::Reason aReason) #endif /* VBOX_WITH_USB */ // we need to lock this object in uninit() because the lock is shared - // with mPeer (as well as data we modify below). mParent->addProcessToReap() - // and others need mParent lock, and USB needs host lock. + // with mPeer (as well as data we modify below). mParent lock is needed + // by several calls to it, and USB needs host lock. AutoMultiWriteLock3 multilock(mParent, mParent->host(), this COMMA_LOCKVAL_SRC_POS); -#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 +#ifdef VBOX_WITH_RESOURCE_USAGE_API /* * It is safe to call Machine::unregisterMetrics() here because * PerformanceCollector::samplerCallback no longer accesses guest methods * holding the lock. */ unregisterMetrics(mParent->performanceCollector(), mPeer); -#endif /* The guest must be unregistered after its metrics (@bugref{5949}). */ LogAleksey(("{%p} " LOG_FN_FMT ": mCollectorGuest=%p\n", this, __PRETTY_FUNCTION__, mCollectorGuest)); @@ -11829,6 +12979,7 @@ void SessionMachine::uninit(Uninit::Reason aReason) // delete mCollectorGuest; => CollectorGuestManager::destroyUnregistered() mCollectorGuest = NULL; } +#endif if (aReason == Uninit::Abnormal) { @@ -11870,16 +13021,6 @@ void SessionMachine::uninit(Uninit::Reason aReason) releaseSavedStateFile(strStateFile, NULL /* pSnapshotToIgnore */ ); } - if (!mData->mSession.mType.isEmpty()) - { - /* mType is not null when this machine's process has been started by - * Machine::LaunchVMProcess(), therefore it is our child. We - * need to queue the PID to reap the process (and avoid zombies on - * Linux). */ - Assert(mData->mSession.mPID != NIL_RTPROCESS); - mParent->addProcessToReap(mData->mSession.mPID); - } - mData->mSession.mPID = NIL_RTPROCESS; if (aReason == Uninit::Unexpected) @@ -11910,6 +13051,33 @@ void SessionMachine::uninit(Uninit::Reason aReason) mData->mSession.mRemoteControls.clear(); } + /* Remove all references to the NAT network service. The service will stop + * if all references (also from other VMs) are removed. */ + for (; miNATNetworksStarted > 0; miNATNetworksStarted--) + { + for (ULONG slot = 0; slot < mNetworkAdapters.size(); slot++) + { + NetworkAttachmentType_T type; + HRESULT hrc; + + hrc = mNetworkAdapters[slot]->COMGETTER(AttachmentType)(&type); + if ( SUCCEEDED(hrc) + && type == NetworkAttachmentType_NATNetwork) + { + Bstr name; + hrc = mNetworkAdapters[slot]->COMGETTER(NATNetwork)(name.asOutParam()); + if (SUCCEEDED(hrc)) + { + multilock.release(); + LogRel(("VM '%s' stops using NAT network '%ls'\n", + mUserData->s.strName.c_str(), name.raw())); + mParent->natNetworkRefDec(name.raw()); + multilock.acquire(); + } + } + } + } + /* * An expected uninitialization can come only from #checkForDeath(). * Otherwise it means that something's gone really wrong (for example, @@ -11957,25 +13125,12 @@ void SessionMachine::uninit(Uninit::Reason aReason) mData->mSession.mState = SessionState_Unlocked; mData->mSession.mType.setNull(); - /* close the interprocess semaphore before leaving the exclusive lock */ -#if defined(RT_OS_WINDOWS) - if (mIPCSem) - ::CloseHandle(mIPCSem); - mIPCSem = NULL; -#elif defined(RT_OS_OS2) - if (mIPCSem != NULLHANDLE) - ::DosCloseMutexSem(mIPCSem); - mIPCSem = NULLHANDLE; -#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER) - if (mIPCSem >= 0) - ::semctl(mIPCSem, 0, IPC_RMID); - mIPCSem = -1; -# ifdef VBOX_WITH_NEW_SYS_V_KEYGEN - mIPCKey = "0"; -# endif /* VBOX_WITH_NEW_SYS_V_KEYGEN */ -#else -# error "Port me!" -#endif + /* destroy the machine client token before leaving the exclusive lock */ + if (mClientToken) + { + delete mClientToken; + mClientToken = NULL; + } /* fire an event */ mParent->onSessionStateChange(mData->mUuid, SessionState_Unlocked); @@ -12013,21 +13168,42 @@ RWLockHandle *SessionMachine::lockHandle() const /** * Passes collected guest statistics to performance collector object */ -STDMETHODIMP SessionMachine::ReportGuestStatistics(ULONG aValidStats, ULONG aCpuUser, - ULONG aCpuKernel, ULONG aCpuIdle, - ULONG aMemTotal, ULONG aMemFree, - ULONG aMemBalloon, ULONG aMemShared, - ULONG aMemCache, ULONG aPageTotal, - ULONG aAllocVMM, ULONG aFreeVMM, - ULONG aBalloonedVMM, ULONG aSharedVMM) +STDMETHODIMP SessionMachine::ReportVmStatistics(ULONG aValidStats, ULONG aCpuUser, + ULONG aCpuKernel, ULONG aCpuIdle, + ULONG aMemTotal, ULONG aMemFree, + ULONG aMemBalloon, ULONG aMemShared, + ULONG aMemCache, ULONG aPageTotal, + ULONG aAllocVMM, ULONG aFreeVMM, + ULONG aBalloonedVMM, ULONG aSharedVMM, + ULONG aVmNetRx, ULONG aVmNetTx) { +#ifdef VBOX_WITH_RESOURCE_USAGE_API if (mCollectorGuest) mCollectorGuest->updateStats(aValidStats, aCpuUser, aCpuKernel, aCpuIdle, aMemTotal, aMemFree, aMemBalloon, aMemShared, aMemCache, aPageTotal, aAllocVMM, aFreeVMM, - aBalloonedVMM, aSharedVMM); + aBalloonedVMM, aSharedVMM, aVmNetRx, aVmNetTx); return S_OK; +#else + NOREF(aValidStats); + NOREF(aCpuUser); + NOREF(aCpuKernel); + NOREF(aCpuIdle); + NOREF(aMemTotal); + NOREF(aMemFree); + NOREF(aMemBalloon); + NOREF(aMemShared); + NOREF(aMemCache); + NOREF(aPageTotal); + NOREF(aAllocVMM); + NOREF(aFreeVMM); + NOREF(aBalloonedVMM); + NOREF(aSharedVMM); + NOREF(aVmNetRx); + NOREF(aVmNetTx); + return E_NOTIMPL; +#endif } /** @@ -12054,31 +13230,6 @@ STDMETHODIMP SessionMachine::UpdateState(MachineState_T aMachineState) } /** - * @note Locks this object for reading. - */ -STDMETHODIMP SessionMachine::GetIPCId(BSTR *aId) -{ - AutoCaller autoCaller(this); - AssertComRCReturn(autoCaller.rc(), autoCaller.rc()); - - AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - -#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) - mIPCSemName.cloneTo(aId); - return S_OK; -#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER) -# ifdef VBOX_WITH_NEW_SYS_V_KEYGEN - mIPCKey.cloneTo(aId); -# else /* !VBOX_WITH_NEW_SYS_V_KEYGEN */ - mData->m_strConfigFileFull.cloneTo(aId); -# endif /* !VBOX_WITH_NEW_SYS_V_KEYGEN */ - return S_OK; -#else -# error "Port me!" -#endif -} - -/** * @note Locks this object for writing. */ STDMETHODIMP SessionMachine::BeginPowerUp(IProgress *aProgress) @@ -12095,6 +13246,37 @@ STDMETHODIMP SessionMachine::BeginPowerUp(IProgress *aProgress) if (!mData->mSession.mProgress.isNull()) mData->mSession.mProgress->setOtherProgressObject(aProgress); + /* If we didn't reference the NAT network service yet, add a reference to + * force a start */ + if (miNATNetworksStarted < 1) + { + for (ULONG slot = 0; slot < mNetworkAdapters.size(); slot++) + { + NetworkAttachmentType_T type; + HRESULT hrc; + hrc = mNetworkAdapters[slot]->COMGETTER(AttachmentType)(&type); + if ( SUCCEEDED(hrc) + && type == NetworkAttachmentType_NATNetwork) + { + Bstr name; + hrc = mNetworkAdapters[slot]->COMGETTER(NATNetwork)(name.asOutParam()); + if (SUCCEEDED(hrc)) + { + LogRel(("VM '%s' starts using NAT network '%ls'\n", + mUserData->s.strName.c_str(), name.raw())); + mPeer->lockHandle()->unlockWrite(); + mParent->natNetworkRefInc(name.raw()); +#ifdef RT_LOCK_STRICT + mPeer->lockHandle()->lockWrite(RT_SRC_POS); +#else + mPeer->lockHandle()->lockWrite(); +#endif + } + } + } + miNATNetworksStarted++; + } + LogFlowThisFunc(("returns S_OK.\n")); return S_OK; } @@ -12240,7 +13422,7 @@ STDMETHODIMP SessionMachine::RunUSBDeviceFilters(IUSBDevice *aUSBDevice, AssertComRCReturn(autoCaller.rc(), autoCaller.rc()); #ifdef VBOX_WITH_USB - *aMatched = mUSBController->hasMatchingFilter(aUSBDevice, aMaskedIfs); + *aMatched = mUSBDeviceFilters->hasMatchingFilter(aUSBDevice, aMaskedIfs); #else NOREF(aUSBDevice); NOREF(aMaskedIfs); @@ -12312,7 +13494,7 @@ STDMETHODIMP SessionMachine::AutoCaptureUSBDevices() AssertComRCReturn(autoCaller.rc(), autoCaller.rc()); #ifdef VBOX_WITH_USB - HRESULT rc = mUSBController->notifyProxy(true /* aInsertFilters */); + HRESULT rc = mUSBDeviceFilters->notifyProxy(true /* aInsertFilters */); AssertComRC(rc); NOREF(rc); @@ -12342,7 +13524,7 @@ STDMETHODIMP SessionMachine::DetachAllUSBDevices(BOOL aDone) AssertComRCReturn(autoCaller.rc(), autoCaller.rc()); #ifdef VBOX_WITH_USB - HRESULT rc = mUSBController->notifyProxy(false /* aInsertFilters */); + HRESULT rc = mUSBDeviceFilters->notifyProxy(false /* aInsertFilters */); AssertComRC(rc); NOREF(rc); @@ -12591,18 +13773,18 @@ STDMETHODIMP SessionMachine::PullGuestProperties(ComSafeArrayOut(BSTR, aNames), com::SafeArray<LONG64> timestamps(cEntries); com::SafeArray<BSTR> flags(cEntries); unsigned i = 0; - for (HWData::GuestPropertyList::iterator it = mHWData->mGuestProperties.begin(); + for (HWData::GuestPropertyMap::iterator it = mHWData->mGuestProperties.begin(); it != mHWData->mGuestProperties.end(); ++it) { char szFlags[MAX_FLAGS_LEN + 1]; - it->strName.cloneTo(&names[i]); - it->strValue.cloneTo(&values[i]); - timestamps[i] = it->mTimestamp; + it->first.cloneTo(&names[i]); + it->second.strValue.cloneTo(&values[i]); + timestamps[i] = it->second.mTimestamp; /* If it is NULL, keep it NULL. */ - if (it->mFlags) + if (it->second.mFlags) { - writeFlags(it->mFlags, szFlags); + writeFlags(it->second.mFlags, szFlags); Bstr(szFlags).cloneTo(&flags[i]); } else @@ -12638,8 +13820,8 @@ STDMETHODIMP SessionMachine::PushGuestProperty(IN_BSTR aName, /* * Convert input up front. */ - Utf8Str utf8Name(aName); - uint32_t fFlags = NILFLAG; + Utf8Str utf8Name(aName); + uint32_t fFlags = NILFLAG; if (aFlags) { Utf8Str utf8Flags(aFlags); @@ -12665,43 +13847,40 @@ STDMETHODIMP SessionMachine::PushGuestProperty(IN_BSTR aName, case MachineState_DeletingSnapshotOnline: case MachineState_DeletingSnapshotPaused: case MachineState_Saving: + case MachineState_Stopping: break; default: -#ifndef DEBUG_sunlover AssertMsgFailedReturn(("%s\n", Global::stringifyMachineState(mData->mMachineState)), VBOX_E_INVALID_VM_STATE); -#else - return VBOX_E_INVALID_VM_STATE; -#endif } setModified(IsModified_MachineData); mHWData.backup(); - /** @todo r=bird: The careful memory handling doesn't work out here because - * the catch block won't undo any damage we've done. So, if push_back throws - * bad_alloc then you've lost the value. - * - * Another thing. Doing a linear search here isn't extremely efficient, esp. - * since values that changes actually bubbles to the end of the list. Using - * something that has an efficient lookup and can tolerate a bit of updates - * would be nice. RTStrSpace is one suggestion (it's not perfect). Some - * combination of RTStrCache (for sharing names and getting uniqueness into - * the bargain) and hash/tree is another. */ - for (HWData::GuestPropertyList::iterator iter = mHWData->mGuestProperties.begin(); - iter != mHWData->mGuestProperties.end(); - ++iter) - if (utf8Name == iter->strName) + bool fDelete = !RT_VALID_PTR(aValue) || *(aValue) == '\0'; + HWData::GuestPropertyMap::iterator it = mHWData->mGuestProperties.find(utf8Name); + if (it != mHWData->mGuestProperties.end()) + { + if (!fDelete) { - mHWData->mGuestProperties.erase(iter); - mData->mGuestPropertiesModified = TRUE; - break; + it->second.strValue = aValue; + it->second.mTimestamp = aTimestamp; + it->second.mFlags = fFlags; } - if (aValue != NULL) + else + mHWData->mGuestProperties.erase(it); + + mData->mGuestPropertiesModified = TRUE; + } + else if (!fDelete) { - HWData::GuestProperty property = { aName, aValue, aTimestamp, fFlags }; - mHWData->mGuestProperties.push_back(property); + HWData::GuestProperty prop; + prop.strValue = aValue; + prop.mTimestamp = aTimestamp; + prop.mFlags = fFlags; + + mHWData->mGuestProperties[utf8Name] = prop; mData->mGuestPropertiesModified = TRUE; } @@ -12733,6 +13912,29 @@ STDMETHODIMP SessionMachine::PushGuestProperty(IN_BSTR aName, #endif } +STDMETHODIMP SessionMachine::LockMedia() +{ + AutoCaller autoCaller(this); + AssertComRCReturn(autoCaller.rc(), autoCaller.rc()); + + AutoMultiWriteLock2 alock(this->lockHandle(), + &mParent->getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS); + + AssertReturn( mData->mMachineState == MachineState_Starting + || mData->mMachineState == MachineState_Restoring + || mData->mMachineState == MachineState_TeleportingIn, E_FAIL); + + clearError(); + alock.release(); + return lockMedia(); +} + +STDMETHODIMP SessionMachine::UnlockMedia() +{ + unlockMedia(); + return S_OK; +} + STDMETHODIMP SessionMachine::EjectMedium(IMediumAttachment *aAttachment, IMediumAttachment **aNewAttachment) { @@ -12815,6 +14017,7 @@ STDMETHODIMP SessionMachine::EjectMedium(IMediumAttachment *aAttachment, // public methods only for internal purposes ///////////////////////////////////////////////////////////////////////////// +#ifndef VBOX_WITH_GENERIC_SESSION_WATCHER /** * Called from the client watcher thread to check for expected or unexpected * death of the client process that has a direct session to this machine. @@ -12860,47 +14063,56 @@ bool SessionMachine::checkForDeath() Uninit::Normal : Uninit::Abnormal; -#if defined(RT_OS_WINDOWS) - - AssertMsg(mIPCSem, ("semaphore must be created")); - - /* release the IPC mutex */ - ::ReleaseMutex(mIPCSem); - - terminated = true; + if (mClientToken) + terminated = mClientToken->release(); + } /* AutoCaller block */ -#elif defined(RT_OS_OS2) + if (terminated) + uninit(reason); - AssertMsg(mIPCSem, ("semaphore must be created")); + return terminated; +} - /* release the IPC mutex */ - ::DosReleaseMutexSem(mIPCSem); +void SessionMachine::getTokenId(Utf8Str &strTokenId) +{ + LogFlowThisFunc(("\n")); - terminated = true; + strTokenId.setNull(); -#elif defined(VBOX_WITH_SYS_V_IPC_SESSION_WATCHER) + AutoCaller autoCaller(this); + AssertComRCReturnVoid(autoCaller.rc()); - AssertMsg(mIPCSem >= 0, ("semaphore must be created")); + Assert(mClientToken); + if (mClientToken) + mClientToken->getId(strTokenId); +} +#else /* VBOX_WITH_GENERIC_SESSION_WATCHER */ +IToken *SessionMachine::getToken() +{ + LogFlowThisFunc(("\n")); - int val = ::semctl(mIPCSem, 0, GETVAL); - if (val > 0) - { - /* the semaphore is signaled, meaning the session is terminated */ - terminated = true; - } + AutoCaller autoCaller(this); + AssertComRCReturn(autoCaller.rc(), NULL); -#else -# error "Port me!" -#endif + Assert(mClientToken); + if (mClientToken) + return mClientToken->getToken(); + else + return NULL; +} +#endif /* VBOX_WITH_GENERIC_SESSION_WATCHER */ - } /* AutoCaller block */ +Machine::ClientToken *SessionMachine::getClientToken() +{ + LogFlowThisFunc(("\n")); - if (terminated) - uninit(reason); + AutoCaller autoCaller(this); + AssertComRCReturn(autoCaller.rc(), NULL); - return terminated; + return mClientToken; } + /** * @note Locks this object for reading. */ @@ -13052,7 +14264,7 @@ HRESULT SessionMachine::onCPUChange(ULONG aCPU, BOOL aRemove) LogFlowThisFunc(("\n")); AutoCaller autoCaller(this); - AssertComRCReturn (autoCaller.rc(), autoCaller.rc()); + AssertComRCReturn(autoCaller.rc(), autoCaller.rc()); ComPtr<IInternalSessionControl> directControl; { @@ -13072,7 +14284,7 @@ HRESULT SessionMachine::onCPUExecutionCapChange(ULONG aExecutionCap) LogFlowThisFunc(("\n")); AutoCaller autoCaller(this); - AssertComRCReturn (autoCaller.rc(), autoCaller.rc()); + AssertComRCReturn(autoCaller.rc(), autoCaller.rc()); ComPtr<IInternalSessionControl> directControl; { @@ -13111,6 +14323,29 @@ HRESULT SessionMachine::onVRDEServerChange(BOOL aRestart) } /** + * @note Locks this object for reading. + */ +HRESULT SessionMachine::onVideoCaptureChange() +{ + 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->OnVideoCaptureChange(); +} + +/** * @note Locks this object for reading. */ HRESULT SessionMachine::onUSBControllerChange() @@ -13210,7 +14445,7 @@ HRESULT SessionMachine::onBandwidthGroupChange(IBandwidthGroup *aBandwidthGroup) LogFlowThisFunc(("\n")); AutoCaller autoCaller(this); - AssertComRCReturn (autoCaller.rc(), autoCaller.rc()); + AssertComRCReturn(autoCaller.rc(), autoCaller.rc()); ComPtr<IInternalSessionControl> directControl; { @@ -13228,7 +14463,7 @@ HRESULT SessionMachine::onBandwidthGroupChange(IBandwidthGroup *aBandwidthGroup) /** * @note Locks this object for reading. */ -HRESULT SessionMachine::onStorageDeviceChange(IMediumAttachment *aAttachment, BOOL aRemove) +HRESULT SessionMachine::onStorageDeviceChange(IMediumAttachment *aAttachment, BOOL aRemove, BOOL aSilent) { LogFlowThisFunc(("\n")); @@ -13245,7 +14480,7 @@ HRESULT SessionMachine::onStorageDeviceChange(IMediumAttachment *aAttachment, BO if (!directControl) return S_OK; - return directControl->OnStorageDeviceChange(aAttachment, aRemove); + return directControl->OnStorageDeviceChange(aAttachment, aRemove, aSilent); } /** @@ -13275,7 +14510,7 @@ bool SessionMachine::hasMatchingUSBFilter(const ComObjPtr<HostUSBDevice> &aDevic /** @todo Live Migration: snapshoting & teleporting. Need to fend things of * elsewhere... */ alock.release(); - return mUSBController->hasMatchingFilter(aDevice, aMaskedIfs); + return mUSBDeviceFilters->hasMatchingFilter(aDevice, aMaskedIfs); default: break; } #else @@ -13456,8 +14691,7 @@ void SessionMachine::releaseSavedStateFile(const Utf8Str &strStateFile, * locked as described above; on failure no media is locked at all (all * succeeded individual locks will be undone). * - * This method is intended to be called when the machine is in Starting or - * Restoring state and asserts otherwise. + * The caller is responsible for doing the necessary state sanity checks. * * The locks made by this method must be undone by calling #unlockMedia() when * no more needed. @@ -13470,13 +14704,9 @@ HRESULT SessionMachine::lockMedia() AutoMultiWriteLock2 alock(this->lockHandle(), &mParent->getMediaTreeLockHandle() COMMA_LOCKVAL_SRC_POS); - AssertReturn( mData->mMachineState == MachineState_Starting - || mData->mMachineState == MachineState_Restoring - || mData->mMachineState == MachineState_TeleportingIn, E_FAIL); /* bail out if trying to lock things with already set up locking */ AssertReturn(mData->mSession.mLockedMedia.IsEmpty(), E_FAIL); - clearError(); MultiResult mrc(S_OK); /* Collect locking information for all medium objects attached to the VM. */ @@ -13749,13 +14979,13 @@ HRESULT SessionMachine::setMachineState(MachineState_T aMachineState) /* Make sure any transient guest properties get removed from the * property store on shutdown. */ - HWData::GuestPropertyList::iterator it; + HWData::GuestPropertyMap::const_iterator it; BOOL fNeedsSaving = mData->mGuestPropertiesModified; if (!fNeedsSaving) for (it = mHWData->mGuestProperties.begin(); it != mHWData->mGuestProperties.end(); ++it) - if ( (it->mFlags & guestProp::TRANSIENT) - || (it->mFlags & guestProp::TRANSRESET)) + if ( (it->second.mFlags & guestProp::TRANSIENT) + || (it->second.mFlags & guestProp::TRANSRESET)) { fNeedsSaving = true; break; |