/** @file * * VirtualBox COM class implementation */ /* * Copyright (C) 2006 InnoTek Systemberatung GmbH * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; * you can redistribute it and/or modify it under the terms of the GNU * General Public License as published by the Free Software Foundation, * in version 2 as it comes in the "COPYING" file of the VirtualBox OSE * distribution. VirtualBox OSE is distributed in the hope that it will * be useful, but WITHOUT ANY WARRANTY of any kind. * * If you received this file as part of a commercial VirtualBox * distribution, then only the terms of your commercial VirtualBox * license agreement apply instead of the previous paragraph. */ #include "VirtualBoxImpl.h" #include "MachineImpl.h" #include "HardDiskImpl.h" #include "DVDImageImpl.h" #include "FloppyImageImpl.h" #include "SharedFolderImpl.h" #include "ProgressImpl.h" #include "HostImpl.h" #include "USBControllerImpl.h" #include "SystemPropertiesImpl.h" #include "Logging.h" #include "GuestOSTypeImpl.h" #ifdef __WIN__ #include "win32/svchlp.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // for auto_ptr // defines ///////////////////////////////////////////////////////////////////////////// #ifdef __DARWIN__ #define VBOXCONFIGDIR "Library/VirtualBox" #else #define VBOXCONFIGDIR ".VirtualBox" #endif #define VBOXCONFIGGLOBALFILE "VirtualBox.xml" // globals ///////////////////////////////////////////////////////////////////////////// static const char DefaultGlobalConfig [] = { "" RTFILE_LINEFEED "" RTFILE_LINEFEED "" RTFILE_LINEFEED " "RTFILE_LINEFEED " "RTFILE_LINEFEED " "RTFILE_LINEFEED " "RTFILE_LINEFEED " "RTFILE_LINEFEED " "RTFILE_LINEFEED ""RTFILE_LINEFEED }; // static Bstr VirtualBox::sVersion; // constructor / destructor ///////////////////////////////////////////////////////////////////////////// VirtualBox::VirtualBox() : mAsyncEventThread (NIL_RTTHREAD) , mAsyncEventQ (NULL) {} VirtualBox::~VirtualBox() {} HRESULT VirtualBox::FinalConstruct() { LogFlowThisFunc (("\n")); return init(); } void VirtualBox::FinalRelease() { LogFlowThisFunc (("\n")); uninit(); } VirtualBox::Data::Data() { } // public initializer/uninitializer for internal purposes only ///////////////////////////////////////////////////////////////////////////// /** * Initializes the VirtualBox object. * * @return COM result code */ HRESULT VirtualBox::init() { /* Enclose the state transition NotReady->InInit->Ready */ AutoInitSpan autoInitSpan (this); AssertReturn (autoInitSpan.isOk(), E_UNEXPECTED); LogFlow (("===========================================================\n")); LogFlowThisFuncEnter(); if (sVersion.isNull()) sVersion = VBOX_VERSION_STRING; LogFlowThisFunc (("Version: %ls\n", sVersion.raw())); int vrc = VINF_SUCCESS; char buf [RTPATH_MAX]; /* * Detect the VirtualBox home directory. Note that the code below must * be in sync with the appropriate code in Logging.cpp * (MainReleaseLoggerInit()). */ { /* check if an alternative VBox Home directory is set */ const char *vboxUserHome = getenv ("VBOX_USER_HOME"); if (vboxUserHome) { /* get the full path name */ vrc = RTPathAbs (vboxUserHome, buf, sizeof (buf)); if (VBOX_FAILURE (vrc)) return setError (E_FAIL, tr ("Invalid home directory file path '%s' (%Vrc)"), vboxUserHome, vrc); unconst (mData.mHomeDir) = buf; } else { /* compose the config directory (full path) */ RTPathUserHome (buf, sizeof (buf)); unconst (mData.mHomeDir) = Utf8StrFmt ("%s%c%s", buf, RTPATH_DELIMITER, VBOXCONFIGDIR); } /* ensure the home directory exists */ if (!RTDirExists (mData.mHomeDir)) { vrc = RTDirCreateFullPath (mData.mHomeDir, 0777); if (VBOX_FAILURE (vrc)) { return setError (E_FAIL, tr ("Could not create the VirtualBox home directory '%s'" "(%Vrc)"), mData.mHomeDir.raw(), vrc); } } } /* compose the global config file name (always full path) */ Utf8StrFmt vboxConfigFile ("%s%c%s", mData.mHomeDir.raw(), RTPATH_DELIMITER, VBOXCONFIGGLOBALFILE); /* store the config file name */ unconst (mData.mCfgFile.mName) = vboxConfigFile; /* lock the config file */ HRESULT rc = lockConfig(); if (SUCCEEDED (rc)) { if (!isConfigLocked()) { /* * This means the config file not found. This is not fatal -- * we just create an empty one. */ RTFILE handle = NIL_RTFILE; int vrc = RTFileOpen (&handle, vboxConfigFile, RTFILE_O_READWRITE | RTFILE_O_CREATE | RTFILE_O_DENY_WRITE | RTFILE_O_WRITE_THROUGH); if (VBOX_SUCCESS (vrc)) vrc = RTFileWrite (handle, (void *) DefaultGlobalConfig, sizeof (DefaultGlobalConfig), NULL); if (VBOX_FAILURE (vrc)) { rc = setError (E_FAIL, tr ("Could not create the default settings file " "'%s' (%Vrc)"), vboxConfigFile.raw(), vrc); } else { mData.mCfgFile.mHandle = handle; /* we do not close the file to simulate lockConfig() */ } } } /* initialize our Xerces XML subsystem */ if (SUCCEEDED (rc)) { int vrc = CFGLDRInitialize(); if (VBOX_FAILURE (vrc)) rc = setError (E_FAIL, tr ("Could not initialize the XML parser (%Vrc)"), vrc); } if (SUCCEEDED (rc)) { CFGHANDLE configLoader = NULL; char *loaderError = NULL; /* load the config file */ int vrc = CFGLDRLoad (&configLoader, vboxConfigFile, mData.mCfgFile.mHandle, XmlSchemaNS, true, cfgLdrEntityResolver, &loaderError); if (VBOX_SUCCESS (vrc)) { CFGNODE global = NULL; CFGLDRGetNode (configLoader, "VirtualBox/Global", 0, &global); Assert (global); do { /* create the host object early, machines will need it */ unconst (mData.mHost).createObject(); rc = mData.mHost->init (this); ComAssertComRCBreak (rc, rc = rc); unconst (mData.mSystemProperties).createObject(); rc = mData.mSystemProperties->init (this); ComAssertComRCBreak (rc, rc = rc); rc = loadDisks (global); CheckComRCBreakRC ((rc)); /* guest OS type objects, needed by the machines */ rc = registerGuestOSTypes(); ComAssertComRCBreak (rc, rc = rc); /* machines */ rc = loadMachines (global); CheckComRCBreakRC ((rc)); /* host data (USB filters) */ rc = mData.mHost->loadSettings (global); CheckComRCBreakRC ((rc)); rc = mData.mSystemProperties->loadSettings (global); CheckComRCBreakRC ((rc)); /* check hard disk consistency */ /// @todo (r=dmik) add IVirtualBox::cleanupHardDisks() instead or similar // for (HardDiskList::const_iterator it = mData.mHardDisks.begin(); // it != mData.mHardDisks.end() && SUCCEEDED (rc); // ++ it) // { // rc = (*it)->checkConsistency(); // } // CheckComRCBreakRC ((rc)); } while (0); /// @todo (dmik) if successful, check for orphan (unused) diffs // that might be left because of the server crash, and remove them. CFGLDRReleaseNode (global); CFGLDRFree(configLoader); } else { rc = setError (E_FAIL, tr ("Could not load the settings file '%ls' (%Vrc)%s%s"), mData.mCfgFile.mName.raw(), vrc, loaderError ? ".\n" : "", loaderError ? loaderError : ""); } if (loaderError) RTMemTmpFree (loaderError); } if (SUCCEEDED (rc)) { /* start the client watcher thread */ #if defined(__WIN__) unconst (mWatcherData.mUpdateReq) = ::CreateEvent (NULL, FALSE, FALSE, NULL); #else RTSemEventCreate (&unconst (mWatcherData.mUpdateReq)); #endif int vrc = RTThreadCreate (&unconst (mWatcherData.mThread), clientWatcher, (void *) this, 0, RTTHREADTYPE_MAIN_WORKER, RTTHREADFLAGS_WAITABLE, "Watcher"); ComAssertRC (vrc); if (VBOX_FAILURE (vrc)) rc = E_FAIL; } if (SUCCEEDED (rc)) do { /* start the async event handler thread */ int vrc = RTThreadCreate (&unconst (mAsyncEventThread), asyncEventHandler, &unconst (mAsyncEventQ), 0, RTTHREADTYPE_MAIN_WORKER, RTTHREADFLAGS_WAITABLE, "EventHandler"); ComAssertRCBreak (vrc, rc = E_FAIL); /* wait until the thread sets mAsyncEventQ */ RTThreadUserWait (mAsyncEventThread, RT_INDEFINITE_WAIT); ComAssertBreak (mAsyncEventQ, rc = E_FAIL); } while (0); /* Confirm a successful initialization when it's the case */ if (SUCCEEDED (rc)) autoInitSpan.setSucceeded(); LogFlowThisFunc (("rc=%08X\n", rc)); LogFlowThisFuncLeave(); LogFlow (("===========================================================\n")); return rc; } void VirtualBox::uninit() { /* Enclose the state transition Ready->InUninit->NotReady */ AutoUninitSpan autoUninitSpan (this); if (autoUninitSpan.uninitDone()) return; LogFlow (("===========================================================\n")); LogFlowThisFuncEnter(); LogFlowThisFunc (("initFailed()=%d\n", autoUninitSpan.initFailed())); /* tell all our child objects we've been uninitialized */ LogFlowThisFunc (("Uninitializing machines (%d)...\n", mData.mMachines.size())); if (mData.mMachines.size()) { MachineList::iterator it = mData.mMachines.begin(); while (it != mData.mMachines.end()) (*it++)->uninit(); mData.mMachines.clear(); } if (mData.mSystemProperties) { mData.mSystemProperties->uninit(); unconst (mData.mSystemProperties).setNull(); } if (mData.mHost) { mData.mHost->uninit(); unconst (mData.mHost).setNull(); } /* * Uninit all other children still referenced by clients * (unregistered machines, hard disks, DVD/floppy images, * server-side progress operations). */ uninitDependentChildren(); mData.mFloppyImages.clear(); mData.mDVDImages.clear(); mData.mHardDisks.clear(); mData.mHardDiskMap.clear(); mData.mProgressOperations.clear(); mData.mGuestOSTypes.clear(); /* unlock the config file */ unlockConfig(); LogFlowThisFunc (("Releasing callbacks...\n")); if (mData.mCallbacks.size()) { /* release all callbacks */ LogWarningFunc (("%d unregistered callbacks!\n", mData.mCallbacks.size())); mData.mCallbacks.clear(); } LogFlowThisFunc (("Terminating the async event handler...\n")); if (mAsyncEventThread != NIL_RTTHREAD) { /* signal to exit the event loop */ if (mAsyncEventQ->postEvent (NULL)) { /* * Wait for thread termination (only if we've successfully posted * a NULL event!) */ int vrc = RTThreadWait (mAsyncEventThread, 60000, NULL); if (VBOX_FAILURE (vrc)) LogWarningFunc (("RTThreadWait(%RTthrd) -> %Vrc\n", mAsyncEventThread, vrc)); } else { AssertMsgFailed (("postEvent(NULL) failed\n")); RTThreadWait (mAsyncEventThread, 0, NULL); } unconst (mAsyncEventThread) = NIL_RTTHREAD; unconst (mAsyncEventQ) = NULL; } LogFlowThisFunc (("Terminating the client watcher...\n")); if (mWatcherData.mThread != NIL_RTTHREAD) { /* signal the client watcher thread */ updateClientWatcher(); /* wait for the termination */ RTThreadWait (mWatcherData.mThread, RT_INDEFINITE_WAIT, NULL); unconst (mWatcherData.mThread) = NIL_RTTHREAD; } mWatcherData.mProcesses.clear(); #if defined(__WIN__) if (mWatcherData.mUpdateReq != NULL) { ::CloseHandle (mWatcherData.mUpdateReq); unconst (mWatcherData.mUpdateReq) = NULL; } #else if (mWatcherData.mUpdateReq != NIL_RTSEMEVENT) { RTSemEventDestroy (mWatcherData.mUpdateReq); unconst (mWatcherData.mUpdateReq) = NIL_RTSEMEVENT; } #endif /* uninitialize the Xerces XML subsystem */ CFGLDRShutdown(); LogFlowThisFuncLeave(); LogFlow (("===========================================================\n")); } // IVirtualBox properties ///////////////////////////////////////////////////////////////////////////// STDMETHODIMP VirtualBox::COMGETTER(Version) (BSTR *aVersion) { if (!aVersion) return E_INVALIDARG; AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); sVersion.cloneTo (aVersion); return S_OK; } STDMETHODIMP VirtualBox::COMGETTER(HomeFolder) (BSTR *aHomeFolder) { if (!aHomeFolder) return E_POINTER; AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); mData.mHomeDir.cloneTo (aHomeFolder); return S_OK; } STDMETHODIMP VirtualBox::COMGETTER(Host) (IHost **aHost) { if (!aHost) return E_POINTER; AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); mData.mHost.queryInterfaceTo (aHost); return S_OK; } STDMETHODIMP VirtualBox::COMGETTER(SystemProperties) (ISystemProperties **aSystemProperties) { if (!aSystemProperties) return E_POINTER; AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); mData.mSystemProperties.queryInterfaceTo (aSystemProperties); return S_OK; } /** @note Locks this object for reading. */ STDMETHODIMP VirtualBox::COMGETTER(Machines) (IMachineCollection **aMachines) { if (!aMachines) return E_POINTER; AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); ComObjPtr collection; collection.createObject(); AutoReaderLock alock (this); collection->init (mData.mMachines); collection.queryInterfaceTo (aMachines); return S_OK; } /** @note Locks this object for reading. */ STDMETHODIMP VirtualBox::COMGETTER(HardDisks) (IHardDiskCollection **aHardDisks) { if (!aHardDisks) return E_POINTER; AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); ComObjPtr collection; collection.createObject(); AutoReaderLock alock (this); collection->init (mData.mHardDisks); collection.queryInterfaceTo (aHardDisks); return S_OK; } /** @note Locks this object for reading. */ STDMETHODIMP VirtualBox::COMGETTER(DVDImages) (IDVDImageCollection **aDVDImages) { if (!aDVDImages) return E_POINTER; AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); ComObjPtr collection; collection.createObject(); AutoReaderLock alock (this); collection->init (mData.mDVDImages); collection.queryInterfaceTo (aDVDImages); return S_OK; } /** @note Locks this object for reading. */ STDMETHODIMP VirtualBox::COMGETTER(FloppyImages) (IFloppyImageCollection **aFloppyImages) { if (!aFloppyImages) return E_POINTER; AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); ComObjPtr collection; collection.createObject(); AutoReaderLock alock (this); collection->init (mData.mFloppyImages); collection.queryInterfaceTo (aFloppyImages); return S_OK; } /** @note Locks this object for reading. */ STDMETHODIMP VirtualBox::COMGETTER(ProgressOperations) (IProgressCollection **aOperations) { if (!aOperations) return E_POINTER; AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); ComObjPtr collection; collection.createObject(); AutoReaderLock alock (this); collection->init (mData.mProgressOperations); collection.queryInterfaceTo (aOperations); return S_OK; } /** @note Locks this object for reading. */ STDMETHODIMP VirtualBox::COMGETTER(GuestOSTypes) (IGuestOSTypeCollection **aGuestOSTypes) { if (!aGuestOSTypes) return E_POINTER; AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); ComObjPtr collection; collection.createObject(); AutoReaderLock alock (this); collection->init (mData.mGuestOSTypes); collection.queryInterfaceTo (aGuestOSTypes); return S_OK; } STDMETHODIMP VirtualBox::COMGETTER(SharedFolders) (ISharedFolderCollection **aSharedFolders) { if (!aSharedFolders) return E_POINTER; AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); return setError (E_NOTIMPL, "Not yet implemented"); } // IVirtualBox methods ///////////////////////////////////////////////////////////////////////////// /** @note Locks mSystemProperties object for reading. */ STDMETHODIMP VirtualBox::CreateMachine (INPTR BSTR aBaseFolder, INPTR BSTR aName, IMachine **aMachine) { LogFlowThisFuncEnter(); LogFlowThisFunc (("aBaseFolder='%ls', aName='%ls' aMachine={%p}\n", aBaseFolder, aName, aMachine)); if (!aName) return E_INVALIDARG; if (!aMachine) return E_POINTER; if (!*aName) return setError (E_INVALIDARG, tr ("Machine name cannot be empty")); AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); /* Compose the settings file name using the following scheme: * * //.xml * * If a non-null and non-empty base folder is specified, the default * machine folder will be used as a base folder. */ Bstr settingsFile = aBaseFolder; if (settingsFile.isEmpty()) { AutoReaderLock propsLock (systemProperties()); /* we use the non-full folder value below to keep the path relative */ settingsFile = systemProperties()->defaultMachineFolder(); } settingsFile = Utf8StrFmt ("%ls%c%ls%c%ls.xml", settingsFile.raw(), RTPATH_DELIMITER, aName, RTPATH_DELIMITER, aName); HRESULT rc = E_FAIL; /* create a new object */ ComObjPtr machine; rc = machine.createObject(); if (SUCCEEDED (rc)) { /* initialize the machine object */ rc = machine->init (this, settingsFile, Machine::Init_New, aName); if (SUCCEEDED (rc)) { /* set the return value */ rc = machine.queryInterfaceTo (aMachine); ComAssertComRC (rc); } } LogFlowThisFunc (("rc=%08X\n", rc)); LogFlowThisFuncLeave(); return rc; } STDMETHODIMP VirtualBox::CreateLegacyMachine (INPTR BSTR aSettingsFile, INPTR BSTR aName, IMachine **aMachine) { /* null and empty strings are not allowed as path names */ if (!aSettingsFile || !(*aSettingsFile)) return E_INVALIDARG; if (!aName) return E_INVALIDARG; if (!aMachine) return E_POINTER; if (!*aName) return setError (E_INVALIDARG, tr ("Machine name cannot be empty")); AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); HRESULT rc = E_FAIL; Utf8Str settingsFile = aSettingsFile; /* append the default extension if none */ if (!RTPathHaveExt (settingsFile)) settingsFile = Utf8StrFmt ("%s.xml", settingsFile.raw()); /* create a new object */ ComObjPtr machine; rc = machine.createObject(); if (SUCCEEDED (rc)) { /* initialize the machine object */ rc = machine->init (this, Bstr (settingsFile), Machine::Init_New, aName, FALSE /* aNameSync */); if (SUCCEEDED (rc)) { /* set the return value */ rc = machine.queryInterfaceTo (aMachine); ComAssertComRC (rc); } } return rc; } STDMETHODIMP VirtualBox::OpenMachine (INPTR BSTR aSettingsFile, IMachine **aMachine) { /* null and empty strings are not allowed as path names */ if (!aSettingsFile || !(*aSettingsFile)) return E_INVALIDARG; if (!aMachine) return E_POINTER; AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); HRESULT rc = E_FAIL; /* create a new object */ ComObjPtr machine; rc = machine.createObject(); if (SUCCEEDED (rc)) { /* initialize the machine object */ rc = machine->init (this, aSettingsFile, Machine::Init_Existing); if (SUCCEEDED (rc)) { /* set the return value */ rc = machine.queryInterfaceTo (aMachine); ComAssertComRC (rc); } } return rc; } /** @note Locks objects! */ STDMETHODIMP VirtualBox::RegisterMachine (IMachine *aMachine) { if (!aMachine) return E_INVALIDARG; AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); HRESULT rc; Bstr name; rc = aMachine->COMGETTER(Name) (name.asOutParam()); CheckComRCReturnRC (rc); /* * we can safely cast child to Machine * here because only Machine * implementations of IMachine can be among our children */ Machine *machine = static_cast (getDependentChild (aMachine)); if (!machine) { /* * this machine was not created by CreateMachine() * or opened by OpenMachine() or loaded during startup */ return setError (E_FAIL, tr ("The machine named '%ls' is not created within this " "VirtualBox instance"), name.raw()); } /* * Machine::trySetRegistered() will commit and save machine settings, and * will also call #registerMachine() on success. */ rc = registerMachine (machine); /* fire an event */ if (SUCCEEDED (rc)) onMachineRegistered (machine->data()->mUuid, TRUE); return rc; } /** @note Locks objects! */ STDMETHODIMP VirtualBox::GetMachine (INPTR GUIDPARAM aId, IMachine **aMachine) { if (!aMachine) return E_POINTER; AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); ComObjPtr machine; HRESULT rc = findMachine (Guid (aId), true /* setError */, &machine); /* the below will set *aMachine to NULL if machine is null */ machine.queryInterfaceTo (aMachine); return rc; } /** @note Locks this object for reading, then some machine objects for reading. */ STDMETHODIMP VirtualBox::FindMachine (INPTR BSTR aName, IMachine **aMachine) { LogFlowThisFuncEnter(); LogFlowThisFunc (("aName=\"%ls\", aMachine={%p}\n", aName, aMachine)); if (!aName) return E_INVALIDARG; if (!aMachine) return E_POINTER; AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); /* start with not found */ ComObjPtr machine; MachineList machines; { /* take a copy for safe iteration outside the lock */ AutoReaderLock alock (this); machines = mData.mMachines; } for (MachineList::iterator it = machines.begin(); !machine && it != machines.end(); ++ it) { AutoReaderLock machineLock (*it); if ((*it)->userData()->mName == aName) machine = *it; } /* this will set (*machine) to NULL if machineObj is null */ machine.queryInterfaceTo (aMachine); HRESULT rc = machine ? S_OK : setError (E_INVALIDARG, tr ("Could not find a registered machine named '%ls'"), aName); LogFlowThisFunc (("rc=%08X\n", rc)); LogFlowThisFuncLeave(); return rc; } /** @note Locks objects! */ STDMETHODIMP VirtualBox::UnregisterMachine (INPTR GUIDPARAM aId, IMachine **aMachine) { Guid id = aId; if (id.isEmpty()) return E_INVALIDARG; AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); AutoLock alock (this); ComObjPtr machine; HRESULT rc = findMachine (id, true /* setError */, &machine); CheckComRCReturnRC (rc); rc = machine->trySetRegistered (FALSE); CheckComRCReturnRC (rc); /* remove from the collection of registered machines */ mData.mMachines.remove (machine); /* save the global registry */ rc = saveConfig(); /* return the unregistered machine to the caller */ machine.queryInterfaceTo (aMachine); /* fire an event */ onMachineRegistered (id, FALSE); return rc; } STDMETHODIMP VirtualBox::CreateHardDisk (HardDiskStorageType_T aStorageType, IHardDisk **aHardDisk) { if (!aHardDisk) return E_POINTER; AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); HRESULT rc = E_FAIL; ComObjPtr hardDisk; switch (aStorageType) { case HardDiskStorageType_VirtualDiskImage: { ComObjPtr vdi; vdi.createObject(); rc = vdi->init (this, NULL, NULL); hardDisk = vdi; break; } case HardDiskStorageType_ISCSIHardDisk: { ComObjPtr iscsi; iscsi.createObject(); rc = iscsi->init (this); hardDisk = iscsi; break; } case HardDiskStorageType_VMDKImage: { ComObjPtr vmdk; vmdk.createObject(); rc = vmdk->init (this, NULL, NULL); hardDisk = vmdk; break; } default: AssertFailed(); }; if (SUCCEEDED (rc)) hardDisk.queryInterfaceTo (aHardDisk); return rc; } /** @note Locks mSystemProperties object for reading. */ STDMETHODIMP VirtualBox::OpenHardDisk (INPTR BSTR aLocation, IHardDisk **aHardDisk) { /* null and empty strings are not allowed locations */ if (!aLocation || !(*aLocation)) return E_INVALIDARG; if (!aHardDisk) return E_POINTER; AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); /* Currently, the location is always a path. So, append the * default path if only a name is given. */ Bstr location = aLocation; { Utf8Str loc = aLocation; if (!RTPathHavePath (loc)) { AutoLock propsLock (mData.mSystemProperties); location = Utf8StrFmt ("%ls%c%s", mData.mSystemProperties->defaultVDIFolder().raw(), RTPATH_DELIMITER, loc.raw()); } } ComObjPtr hardDisk; HRESULT rc = HardDisk::openHardDisk (this, location, hardDisk); if (SUCCEEDED (rc)) hardDisk.queryInterfaceTo (aHardDisk); return rc; } /** @note Locks mSystemProperties object for reading. */ STDMETHODIMP VirtualBox::OpenVirtualDiskImage (INPTR BSTR aFilePath, IVirtualDiskImage **aImage) { /* null and empty strings are not allowed as path names here */ if (!aFilePath || !(*aFilePath)) return E_INVALIDARG; if (!aImage) return E_POINTER; AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); /* append the default path if only a name is given */ Bstr path = aFilePath; { Utf8Str fp = aFilePath; if (!RTPathHavePath (fp)) { AutoLock propsLock (mData.mSystemProperties); path = Utf8StrFmt ("%ls%c%s", mData.mSystemProperties->defaultVDIFolder().raw(), RTPATH_DELIMITER, fp.raw()); } } ComObjPtr vdi; vdi.createObject(); HRESULT rc = vdi->init (this, NULL, path); if (SUCCEEDED (rc)) vdi.queryInterfaceTo (aImage); return rc; } /** @note Locks objects! */ STDMETHODIMP VirtualBox::RegisterHardDisk (IHardDisk *aHardDisk) { if (!aHardDisk) return E_POINTER; AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); VirtualBoxBase *child = getDependentChild (aHardDisk); if (!child) return setError (E_FAIL, tr ("The given hard disk is not created within " "this VirtualBox instance")); /* * we can safely cast child to HardDisk * here because only HardDisk * implementations of IHardDisk can be among our children */ return registerHardDisk (static_cast (child), RHD_External); } /** @note Locks objects! */ STDMETHODIMP VirtualBox::GetHardDisk (INPTR GUIDPARAM aId, IHardDisk **aHardDisk) { if (!aHardDisk) return E_POINTER; AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); Guid id = aId; ComObjPtr hd; HRESULT rc = findHardDisk (&id, NULL, true /* setError */, &hd); /* the below will set *aHardDisk to NULL if hd is null */ hd.queryInterfaceTo (aHardDisk); return rc; } /** @note Locks objects! */ STDMETHODIMP VirtualBox::FindHardDisk (INPTR BSTR aLocation, IHardDisk **aHardDisk) { if (!aLocation) return E_INVALIDARG; if (!aHardDisk) return E_POINTER; AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); Utf8Str location = aLocation; if (strncmp (location, "iscsi:", 6) == 0) { /* nothing special */ } else { /* For locations represented by file paths, append the default path if * only a name is given, and then get the full path. */ if (!RTPathHavePath (location)) { AutoLock propsLock (mData.mSystemProperties); location = Utf8StrFmt ("%ls%c%s", mData.mSystemProperties->defaultVDIFolder().raw(), RTPATH_DELIMITER, location.raw()); } /* get the full file name */ char buf [RTPATH_MAX]; int vrc = RTPathAbsEx (mData.mHomeDir, location, buf, sizeof (buf)); if (VBOX_FAILURE (vrc)) return setError (E_FAIL, tr ("Invalid hard disk location '%ls' (%Vrc)"), aLocation, vrc); location = buf; } ComObjPtr hardDisk; HRESULT rc = findHardDisk (NULL, Bstr (location), true /* setError */, &hardDisk); /* the below will set *aHardDisk to NULL if hardDisk is null */ hardDisk.queryInterfaceTo (aHardDisk); return rc; } /** @note Locks objects! */ STDMETHODIMP VirtualBox::FindVirtualDiskImage (INPTR BSTR aFilePath, IVirtualDiskImage **aImage) { if (!aFilePath) return E_INVALIDARG; if (!aImage) return E_POINTER; AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); /* append the default path if only a name is given */ Utf8Str path = aFilePath; { Utf8Str fp = path; if (!RTPathHavePath (fp)) { AutoLock propsLock (mData.mSystemProperties); path = Utf8StrFmt ("%ls%c%s", mData.mSystemProperties->defaultVDIFolder().raw(), RTPATH_DELIMITER, fp.raw()); } } /* get the full file name */ char buf [RTPATH_MAX]; int vrc = RTPathAbsEx (mData.mHomeDir, path, buf, sizeof (buf)); if (VBOX_FAILURE (vrc)) return setError (E_FAIL, tr ("Invalid image file path '%ls' (%Vrc)"), aFilePath, vrc); ComObjPtr vdi; HRESULT rc = findVirtualDiskImage (NULL, Bstr (buf), true /* setError */, &vdi); /* the below will set *aImage to NULL if vdi is null */ vdi.queryInterfaceTo (aImage); return rc; } /** @note Locks objects! */ STDMETHODIMP VirtualBox::UnregisterHardDisk (INPTR GUIDPARAM aId, IHardDisk **aHardDisk) { if (!aHardDisk) return E_POINTER; AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); *aHardDisk = NULL; Guid id = aId; ComObjPtr hd; HRESULT rc = findHardDisk (&id, NULL, true /* setError */, &hd); CheckComRCReturnRC (rc); rc = unregisterHardDisk (hd); if (SUCCEEDED (rc)) hd.queryInterfaceTo (aHardDisk); return rc; } /** @note Doesn't lock anything. */ STDMETHODIMP VirtualBox::OpenDVDImage (INPTR BSTR aFilePath, INPTR GUIDPARAM aId, IDVDImage **aDVDImage) { /* null and empty strings are not allowed as path names */ if (!aFilePath || !(*aFilePath)) return E_INVALIDARG; if (!aDVDImage) return E_POINTER; AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); HRESULT rc = E_FAIL; Guid uuid = aId; /* generate an UUID if not specified */ if (uuid.isEmpty()) uuid.create(); ComObjPtr dvdImage; dvdImage.createObject(); rc = dvdImage->init (this, aFilePath, FALSE /* !isRegistered */, uuid); if (SUCCEEDED (rc)) dvdImage.queryInterfaceTo (aDVDImage); return rc; } /** @note Locks objects! */ STDMETHODIMP VirtualBox::RegisterDVDImage (IDVDImage *aDVDImage) { if (!aDVDImage) return E_POINTER; AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); VirtualBoxBase *child = getDependentChild (aDVDImage); if (!child) return setError (E_FAIL, tr ("The given CD/DVD image is not created within " "this VirtualBox instance")); /* * we can safely cast child to DVDImage * here because only DVDImage * implementations of IDVDImage can be among our children */ return registerDVDImage (static_cast (child), FALSE /* aOnStartUp */); } /** @note Locks objects! */ STDMETHODIMP VirtualBox::GetDVDImage (INPTR GUIDPARAM aId, IDVDImage **aDVDImage) { if (!aDVDImage) return E_POINTER; AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); Guid uuid = aId; ComObjPtr dvd; HRESULT rc = findDVDImage (&uuid, NULL, true /* setError */, &dvd); /* the below will set *aDVDImage to NULL if dvd is null */ dvd.queryInterfaceTo (aDVDImage); return rc; } /** @note Locks objects! */ STDMETHODIMP VirtualBox::FindDVDImage (INPTR BSTR aFilePath, IDVDImage **aDVDImage) { if (!aFilePath) return E_INVALIDARG; if (!aDVDImage) return E_POINTER; AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); /* get the full file name */ char buf [RTPATH_MAX]; int vrc = RTPathAbsEx (mData.mHomeDir, Utf8Str (aFilePath), buf, sizeof (buf)); if (VBOX_FAILURE (vrc)) return setError (E_FAIL, tr ("Invalid image file path: '%ls' (%Vrc)"), aFilePath, vrc); ComObjPtr dvd; HRESULT rc = findDVDImage (NULL, Bstr (buf), true /* setError */, &dvd); /* the below will set *dvdImage to NULL if dvd is null */ dvd.queryInterfaceTo (aDVDImage); return rc; } /** @note Locks objects! */ STDMETHODIMP VirtualBox::GetDVDImageUsage (INPTR GUIDPARAM aId, ResourceUsage_T aUsage, BSTR *aMachineIDs) { if (!aMachineIDs) return E_POINTER; if (aUsage == ResourceUsage_InvalidUsage) return E_INVALIDARG; AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); AutoReaderLock alock (this); Guid uuid = Guid (aId); HRESULT rc = findDVDImage (&uuid, NULL, true /* setError */, NULL); if (FAILED (rc)) return rc; Bstr ids; getDVDImageUsage (uuid, aUsage, &ids); ids.cloneTo (aMachineIDs); return S_OK; } /** @note Locks objects! */ STDMETHODIMP VirtualBox::UnregisterDVDImage (INPTR GUIDPARAM aId, IDVDImage **aDVDImage) { if (!aDVDImage) return E_POINTER; AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); AutoLock alock (this); *aDVDImage = NULL; Guid uuid = aId; ComObjPtr dvd; HRESULT rc = findDVDImage (&uuid, NULL, true /* setError */, &dvd); CheckComRCReturnRC (rc); if (!getDVDImageUsage (aId, ResourceUsage_AllUsage)) { /* remove from the collection */ mData.mDVDImages.remove (dvd); /* save the global config file */ rc = saveConfig(); if (SUCCEEDED (rc)) { rc = dvd.queryInterfaceTo (aDVDImage); ComAssertComRC (rc); } } else rc = setError(E_FAIL, tr ("The CD/DVD image with the UUID {%s} is currently in use"), uuid.toString().raw()); return rc; } /** @note Doesn't lock anything. */ STDMETHODIMP VirtualBox::OpenFloppyImage (INPTR BSTR aFilePath, INPTR GUIDPARAM aId, IFloppyImage **aFloppyImage) { /* null and empty strings are not allowed as path names */ if (!aFilePath || !(*aFilePath)) return E_INVALIDARG; if (!aFloppyImage) return E_POINTER; AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); HRESULT rc = E_FAIL; Guid uuid = aId; /* generate an UUID if not specified */ if (Guid::isEmpty (aId)) uuid.create(); ComObjPtr floppyImage; floppyImage.createObject(); rc = floppyImage->init (this, aFilePath, FALSE /* !isRegistered */, uuid); if (SUCCEEDED (rc)) floppyImage.queryInterfaceTo (aFloppyImage); return rc; } /** @note Locks objects! */ STDMETHODIMP VirtualBox::RegisterFloppyImage (IFloppyImage *aFloppyImage) { if (!aFloppyImage) return E_POINTER; AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); VirtualBoxBase *child = getDependentChild (aFloppyImage); if (!child) return setError (E_FAIL, tr ("The given floppy image is not created within " "this VirtualBox instance")); /* * we can safely cast child to FloppyImage * here because only FloppyImage * implementations of IFloppyImage can be among our children */ return registerFloppyImage (static_cast (child), FALSE /* aOnStartUp */); } /** @note Locks objects! */ STDMETHODIMP VirtualBox::GetFloppyImage (INPTR GUIDPARAM aId, IFloppyImage **aFloppyImage) { if (!aFloppyImage) return E_POINTER; AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); Guid uuid = aId; ComObjPtr floppy; HRESULT rc = findFloppyImage (&uuid, NULL, true /* setError */, &floppy); /* the below will set *aFloppyImage to NULL if dvd is null */ floppy.queryInterfaceTo (aFloppyImage); return rc; } /** @note Locks objects! */ STDMETHODIMP VirtualBox::FindFloppyImage (INPTR BSTR aFilePath, IFloppyImage **aFloppyImage) { if (!aFilePath) return E_INVALIDARG; if (!aFloppyImage) return E_POINTER; AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); /* get the full file name */ char buf [RTPATH_MAX]; int vrc = RTPathAbsEx (mData.mHomeDir, Utf8Str (aFilePath), buf, sizeof (buf)); if (VBOX_FAILURE (vrc)) return setError (E_FAIL, tr ("Invalid image file path: '%ls' (%Vrc)"), aFilePath, vrc); ComObjPtr floppy; HRESULT rc = findFloppyImage (NULL, Bstr (buf), true /* setError */, &floppy); /* the below will set *image to NULL if img is null */ floppy.queryInterfaceTo (aFloppyImage); return rc; } /** @note Locks objects! */ STDMETHODIMP VirtualBox::GetFloppyImageUsage (INPTR GUIDPARAM aId, ResourceUsage_T aUsage, BSTR *aMachineIDs) { if (!aMachineIDs) return E_POINTER; if (aUsage == ResourceUsage_InvalidUsage) return E_INVALIDARG; AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); AutoReaderLock alock (this); Guid uuid = Guid (aId); HRESULT rc = findFloppyImage (&uuid, NULL, true /* setError */, NULL); if (FAILED (rc)) return rc; Bstr ids; getFloppyImageUsage (uuid, aUsage, &ids); ids.cloneTo (aMachineIDs); return S_OK; } /** @note Locks objects! */ STDMETHODIMP VirtualBox::UnregisterFloppyImage (INPTR GUIDPARAM aId, IFloppyImage **aFloppyImage) { if (!aFloppyImage) return E_INVALIDARG; AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); AutoLock alock (this); *aFloppyImage = NULL; Guid uuid = aId; ComObjPtr floppy; HRESULT rc = findFloppyImage (&uuid, NULL, true /* setError */, &floppy); CheckComRCReturnRC (rc); if (!getFloppyImageUsage (aId, ResourceUsage_AllUsage)) { /* remove from the collection */ mData.mFloppyImages.remove (floppy); /* save the global config file */ rc = saveConfig(); if (SUCCEEDED (rc)) { rc = floppy.queryInterfaceTo (aFloppyImage); ComAssertComRC (rc); } } else rc = setError(E_FAIL, tr ("A floppy image with UUID {%s} is currently in use"), uuid.toString().raw()); return rc; } /** @note Locks this object for reading. */ STDMETHODIMP VirtualBox::FindGuestOSType (INPTR BSTR aId, IGuestOSType **aType) { if (!aType) return E_INVALIDARG; AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); *aType = NULL; AutoReaderLock alock (this); for (GuestOSTypeList::iterator it = mData.mGuestOSTypes.begin(); it != mData.mGuestOSTypes.end(); ++ it) { const Bstr &typeId = (*it)->id(); AssertMsg (!!typeId, ("ID must not be NULL")); if (typeId == aId) { (*it).queryInterfaceTo (aType); break; } } return (*aType) ? S_OK : E_INVALIDARG; } STDMETHODIMP VirtualBox::CreateSharedFolder (INPTR BSTR aName, INPTR BSTR aHostPath) { if (!aName || !aHostPath) return E_INVALIDARG; AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); return setError (E_NOTIMPL, "Not yet implemented"); } STDMETHODIMP VirtualBox::RemoveSharedFolder (INPTR BSTR aName) { if (!aName) return E_INVALIDARG; AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); return setError (E_NOTIMPL, "Not yet implemented"); } /** @note Locks this object for reading. */ STDMETHODIMP VirtualBox:: GetNextExtraDataKey (INPTR BSTR aKey, BSTR *aNextKey, BSTR *aNextValue) { if (!aNextKey) return E_POINTER; AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); /* start with nothing found */ *aNextKey = NULL; HRESULT rc = S_OK; /* serialize file access */ AutoReaderLock alock (this); CFGHANDLE configLoader; /* load the config file */ int vrc = CFGLDRLoad (&configLoader, Utf8Str (mData.mCfgFile.mName), mData.mCfgFile.mHandle, XmlSchemaNS, true, cfgLdrEntityResolver, NULL); ComAssertRCRet (vrc, E_FAIL); CFGNODE extraDataNode; /* navigate to the right position */ if (VBOX_SUCCESS (CFGLDRGetNode (configLoader, "VirtualBox/Global/ExtraData", 0, &extraDataNode))) { /* check if it exists */ bool found = false; unsigned count; CFGNODE extraDataItemNode; CFGLDRCountChildren (extraDataNode, "ExtraDataItem", &count); for (unsigned i = 0; (i < count) && (found == false); i++) { Bstr name; CFGLDRGetChildNode (extraDataNode, "ExtraDataItem", i, &extraDataItemNode); CFGLDRQueryBSTR (extraDataItemNode, "name", name.asOutParam()); /* if we're supposed to return the first one */ if (aKey == NULL) { name.cloneTo (aNextKey); if (aNextValue) CFGLDRQueryBSTR (extraDataItemNode, "value", aNextValue); found = true; } /* did we find the key we're looking for? */ else if (name == aKey) { found = true; /* is there another item? */ if (i + 1 < count) { CFGLDRGetChildNode (extraDataNode, "ExtraDataItem", i + 1, &extraDataItemNode); CFGLDRQueryBSTR (extraDataItemNode, "name", name.asOutParam()); name.cloneTo (aNextKey); if (aNextValue) CFGLDRQueryBSTR (extraDataItemNode, "value", aNextValue); found = true; } else { /* it's the last one */ *aNextKey = NULL; } } CFGLDRReleaseNode (extraDataItemNode); } /* if we haven't found the key, it's an error */ if (!found) rc = setError (E_FAIL, tr ("Could not find extra data key '%ls'"), aKey); CFGLDRReleaseNode (extraDataNode); } CFGLDRFree (configLoader); return rc; } /** @note Locks this object for reading. */ STDMETHODIMP VirtualBox::GetExtraData (INPTR BSTR aKey, BSTR *aValue) { if (!aKey || !(*aKey)) return E_INVALIDARG; if (!aValue) return E_POINTER; AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); /* start with nothing found */ *aValue = NULL; HRESULT rc = S_OK; /* serialize file access */ AutoReaderLock alock (this); CFGHANDLE configLoader; /* load the config file */ int vrc = CFGLDRLoad (&configLoader, Utf8Str (mData.mCfgFile.mName), mData.mCfgFile.mHandle, XmlSchemaNS, true, cfgLdrEntityResolver, NULL); ComAssertRCRet (vrc, E_FAIL); CFGNODE extraDataNode; /* navigate to the right position */ if (VBOX_SUCCESS (CFGLDRGetNode (configLoader, "VirtualBox/Global/ExtraData", 0, &extraDataNode))) { /* check if it exists */ bool found = false; unsigned count; CFGNODE extraDataItemNode; CFGLDRCountChildren (extraDataNode, "ExtraDataItem", &count); for (unsigned i = 0; (i < count) && (found == false); i++) { Bstr name; CFGLDRGetChildNode (extraDataNode, "ExtraDataItem", i, &extraDataItemNode); CFGLDRQueryBSTR (extraDataItemNode, "name", name.asOutParam()); if (name == aKey) { found = true; CFGLDRQueryBSTR (extraDataItemNode, "value", aValue); } CFGLDRReleaseNode (extraDataItemNode); } CFGLDRReleaseNode (extraDataNode); } CFGLDRFree (configLoader); return rc; } /** @note Locks this object for writing. */ STDMETHODIMP VirtualBox::SetExtraData(INPTR BSTR aKey, INPTR BSTR aValue) { if (!aKey) return E_POINTER; AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); Guid emptyGuid; bool changed = false; HRESULT rc = S_OK; /* serialize file access */ AutoLock alock (this); CFGHANDLE configLoader; /* load the config file */ int vrc = CFGLDRLoad (&configLoader, Utf8Str (mData.mCfgFile.mName), mData.mCfgFile.mHandle, XmlSchemaNS, true, cfgLdrEntityResolver, NULL); ComAssertRCRet (vrc, E_FAIL); CFGNODE extraDataNode = 0; vrc = CFGLDRGetNode (configLoader, "VirtualBox/Global/ExtraData", 0, &extraDataNode); if (VBOX_FAILURE (vrc) && aValue) vrc = CFGLDRCreateNode (configLoader, "VirtualBox/Global/ExtraData", &extraDataNode); if (extraDataNode) { CFGNODE extraDataItemNode = 0; Bstr oldVal; unsigned count; CFGLDRCountChildren (extraDataNode, "ExtraDataItem", &count); for (unsigned i = 0; i < count; i++) { CFGLDRGetChildNode (extraDataNode, "ExtraDataItem", i, &extraDataItemNode); Bstr name; CFGLDRQueryBSTR (extraDataItemNode, "name", name.asOutParam()); if (name == aKey) { CFGLDRQueryBSTR (extraDataItemNode, "value", oldVal.asOutParam()); break; } CFGLDRReleaseNode (extraDataItemNode); extraDataItemNode = 0; } /* * When no key is found, oldVal is null. Note: * 1. when oldVal is null, |oldVal == (BSTR) NULL| is true * 2. we cannot do |oldVal != value| because it will compare * BSTR pointers instead of strings (due to type conversion ops) */ changed = !(oldVal == aValue); if (changed) { /* ask for permission from all listeners */ if (!onExtraDataCanChange (emptyGuid, aKey, aValue)) { Log (("VirtualBox::SetExtraData(): Someone vetoed! Change refused\n")); rc = setError (E_ACCESSDENIED, tr ("Could not set extra data because someone refused " "the requested change of '%ls' to '%ls'"), aKey, aValue); } else { if (aValue) { if (!extraDataItemNode) { /* create a new item */ CFGLDRAppendChildNode (extraDataNode, "ExtraDataItem", &extraDataItemNode); CFGLDRSetBSTR (extraDataItemNode, "name", aKey); } CFGLDRSetBSTR (extraDataItemNode, "value", aValue); } else { /* an old value does for sure exist here */ CFGLDRDeleteNode (extraDataItemNode); extraDataItemNode = 0; } } } if (extraDataItemNode) CFGLDRReleaseNode (extraDataItemNode); CFGLDRReleaseNode (extraDataNode); if (SUCCEEDED (rc) && changed) { char *loaderError = NULL; vrc = CFGLDRSave (configLoader, &loaderError); if (VBOX_FAILURE (vrc)) { rc = setError (E_FAIL, tr ("Could not save the settings file '%ls' (%Vrc)%s%s"), mData.mCfgFile.mName.raw(), vrc, loaderError ? ".\n" : "", loaderError ? loaderError : ""); if (loaderError) RTMemTmpFree (loaderError); } } } CFGLDRFree (configLoader); /* notification handling */ if (SUCCEEDED (rc) && changed) onExtraDataChange (emptyGuid, aKey, aValue); return rc; } /** * @note Locks objects! */ STDMETHODIMP VirtualBox::OpenSession (ISession *aSession, INPTR GUIDPARAM aMachineId) { if (!aSession) return E_INVALIDARG; AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); Guid id = aMachineId; ComObjPtr machine; HRESULT rc = findMachine (id, true /* setError */, &machine); CheckComRCReturnRC (rc); /* check the session state */ SessionState_T state; rc = aSession->COMGETTER(State) (&state); CheckComRCReturnRC (rc); if (state != SessionState_SessionClosed) return setError (E_INVALIDARG, tr ("The given session is already open or being opened")); /* get the IInternalSessionControl interface */ ComPtr control = aSession; ComAssertMsgRet (!!control, ("No IInternalSessionControl interface"), E_INVALIDARG); rc = machine->openSession (control); if (SUCCEEDED (rc)) { /* * tell the client watcher thread to update the set of * machines that have open sessions */ updateClientWatcher(); /* fire an event */ onSessionStateChange (aMachineId, SessionState_SessionOpen); } return rc; } /** * @note Locks objects! */ STDMETHODIMP VirtualBox::OpenRemoteSession (ISession *aSession, INPTR GUIDPARAM aMachineId, INPTR BSTR aType, IProgress **aProgress) { if (!aSession || !aType) return E_INVALIDARG; if (!aProgress) return E_POINTER; AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); Guid id = aMachineId; ComObjPtr machine; HRESULT rc = findMachine (id, true /* setError */, &machine); CheckComRCReturnRC (rc); /* check the session state */ SessionState_T state; rc = aSession->COMGETTER(State) (&state); CheckComRCReturnRC (rc); if (state != SessionState_SessionClosed) return setError (E_INVALIDARG, tr ("The given session is already open or being opened")); /* get the IInternalSessionControl interface */ ComPtr control = aSession; ComAssertMsgRet (!!control, ("No IInternalSessionControl interface"), E_INVALIDARG); /* create a progress object */ ComObjPtr progress; progress.createObject(); progress->init (this, (IMachine *) machine, Bstr (tr ("Spawning session")), FALSE /* aCancelable */); rc = machine->openRemoteSession (control, aType, progress); if (SUCCEEDED (rc)) { progress.queryInterfaceTo (aProgress); /* fire an event */ onSessionStateChange (aMachineId, SessionState_SessionSpawning); } return rc; } /** * @note Locks objects! */ STDMETHODIMP VirtualBox::OpenExistingSession (ISession *aSession, INPTR GUIDPARAM aMachineId) { if (!aSession) return E_POINTER; AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); Guid id = aMachineId; ComObjPtr machine; HRESULT rc = findMachine (id, true /* setError */, &machine); CheckComRCReturnRC (rc); /* check the session state */ SessionState_T state; rc = aSession->COMGETTER(State) (&state); CheckComRCReturnRC (rc); if (state != SessionState_SessionClosed) return setError (E_INVALIDARG, tr ("The given session is already open or being opened")); /* get the IInternalSessionControl interface */ ComPtr control = aSession; ComAssertMsgRet (!!control, ("No IInternalSessionControl interface"), E_INVALIDARG); rc = machine->openExistingSession (control); return rc; } /** * Registers a new client callback on this instance. The methods of the * callback interface will be called by this instance when the appropriate * event occurs. * * @note Locks this object for writing. */ STDMETHODIMP VirtualBox::RegisterCallback (IVirtualBoxCallback *callback) { LogFlowMember (("VirtualBox::RegisterCallback(): callback=%p\n", callback)); if (!callback) return E_INVALIDARG; AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); AutoLock alock (this); mData.mCallbacks.push_back (CallbackList::value_type (callback)); return S_OK; } /** * Unregisters the previously registered client callback. * * @note Locks this object for writing. */ STDMETHODIMP VirtualBox::UnregisterCallback (IVirtualBoxCallback *callback) { if (!callback) return E_INVALIDARG; AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); HRESULT rc = S_OK; AutoLock alock (this); CallbackList::iterator it; it = std::find (mData.mCallbacks.begin(), mData.mCallbacks.end(), CallbackList::value_type (callback)); if (it == mData.mCallbacks.end()) rc = E_INVALIDARG; else mData.mCallbacks.erase (it); LogFlowMember (("VirtualBox::UnregisterCallback(): callback=%p, rc=%08X\n", callback, rc)); return rc; } // public methods only for internal purposes ///////////////////////////////////////////////////////////////////////////// /** * Posts an event to the event queue that is processed asynchronously * on a dedicated thread. * * Posting events to the dedicated event queue is useful to perform secondary * actions outside any object locks -- for example, to iterate over a list * of callbacks and inform them about some change caused by some object's * method call. * * @param event event to post * (must be allocated using |new|, will be deleted automatically * by the event thread after processing) * * @note Doesn't lock any object. */ HRESULT VirtualBox::postEvent (Event *event) { AutoCaller autoCaller (this); AssertComRCReturn (autoCaller.rc(), autoCaller.rc()); if (autoCaller.state() != Ready) { LogWarningFunc (("VirtualBox has been uninitialized (state=%d), " "the event is discarded!\n", autoCaller.state())); return S_OK; } AssertReturn (event, E_FAIL); AssertReturn (mAsyncEventQ, E_FAIL); AutoLock alock (mAsyncEventQLock); if (mAsyncEventQ->postEvent (event)) return S_OK; return E_FAIL; } /** * Helper method to add a progress to the global collection of pending * operations. * * @param aProgress operation to add to the collection * @return COM status code * * @note Locks this object for writing. */ HRESULT VirtualBox::addProgress (IProgress *aProgress) { if (!aProgress) return E_INVALIDARG; AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); AutoLock alock (this); mData.mProgressOperations.push_back (aProgress); return S_OK; } /** * Helper method to remove the progress from the global collection of pending * operations. Usualy gets called upon progress completion. * * @param aId UUID of the progress operation to remove * @return COM status code * * @note Locks this object for writing. */ HRESULT VirtualBox::removeProgress (INPTR GUIDPARAM aId) { AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); ComPtr progress; AutoLock alock (this); for (ProgressList::iterator it = mData.mProgressOperations.begin(); it != mData.mProgressOperations.end(); ++ it) { Guid id; (*it)->COMGETTER(Id) (id.asOutParam()); if (id == aId) { mData.mProgressOperations.erase (it); return S_OK; } } AssertFailed(); /* should never happen */ return E_FAIL; } #ifdef __WIN__ struct StartSVCHelperClientData { ComObjPtr that; ComObjPtr progress; bool privileged; VirtualBox::SVCHelperClientFunc func; void *user; }; /** * Helper method to that starts a worker thread that: * - creates a pipe communication channel using SVCHlpClient; * - starts a SVC Helper process that will inherit this channel; * - executes the supplied function by passing it the created SVCHlpClient * and opened instance to communicate to the Helper process and the given * Progress object. * * The user function is supposed to communicate to the helper process * using the \a aClient argument to do the requested job and optionally expose * the prgress through the \a aProgress object. The user function should never * call notifyComplete() on it: this will be done authomatically using the * result code returned by the function. * * Before the user function is stared, the communication channel passed to in * the \a aClient argument, is fully set up, the function should start using * it's write() and read() methods directly. * * The \a aVrc parameter of the user function may be used to return an error * code if it is related to communication errors (for example, returned by * the SVCHlpClient members when they fail). In this case, the correct error * message using this value will be reported to the caller. Note that the * value of \a aVrc is inspected only if the user function itself returns * a success. * * If a failure happens anywhere before the user function would be normally * called, it will be called anyway in special "cleanup only" mode indicated * by \a aClient, \a aProgress and \aVrc arguments set to NULL. In this mode, * all the function is supposed to do is to cleanup its aUser argument if * necessary (it's assumed that the ownership of this argument is passed to * the user function once #startSVCHelperClient() returns a success, thus * making it responsible for the cleanup). * * After the user function returns, the thread will send the SVCHlpMsg::Null * message to indicate a process termination. * * @param aPrivileged |true| to start the SVC Hepler process as a privlieged * user that can perform administrative tasks * @param aFunc user function to run * @param aUser argument to the user function * @param aProgress progress object that will track operation completion * * @note aPrivileged is currently ignored (due to some unsolved problems in * Vista) and the process will be started as a normal (unprivileged) * process. * * @note Doesn't lock anything. */ HRESULT VirtualBox::startSVCHelperClient (bool aPrivileged, SVCHelperClientFunc aFunc, void *aUser, Progress *aProgress) { AssertReturn (aFunc, E_POINTER); AssertReturn (aProgress, E_POINTER); AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); /* create the SVCHelperClientThread() argument */ std::auto_ptr d (new StartSVCHelperClientData()); AssertReturn (d.get(), E_OUTOFMEMORY); d->that = this; d->progress = aProgress; d->privileged = aPrivileged; d->func = aFunc; d->user = aUser; RTTHREAD tid = NIL_RTTHREAD; int vrc = RTThreadCreate (&tid, SVCHelperClientThread, static_cast (d.get()), 0, RTTHREADTYPE_MAIN_WORKER, RTTHREADFLAGS_WAITABLE, "SVCHelper"); ComAssertMsgRCRet (vrc, ("Could not create SVCHelper thread (%Vrc)\n", vrc), E_FAIL); /* d is now owned by SVCHelperClientThread(), so release it */ d.release(); return S_OK; } /** * Worker thread for startSVCHelperClient(). */ /* static */ DECLCALLBACK(int) VirtualBox::SVCHelperClientThread (RTTHREAD aThread, void *aUser) { LogFlowFuncEnter(); std::auto_ptr d (static_cast (aUser)); HRESULT rc = S_OK; bool userFuncCalled = false; do { AssertBreak (d.get(), rc = E_POINTER); AssertReturn (!d->progress.isNull(), E_POINTER); /* protect VirtualBox from uninitialization */ AutoCaller autoCaller (d->that); if (!autoCaller.isOk()) { /* it's too late */ rc = autoCaller.rc(); break; } int vrc = VINF_SUCCESS; Guid id; id.create(); SVCHlpClient client; vrc = client.create (Utf8StrFmt ("VirtualBox\\SVCHelper\\{%Vuuid}", id.raw())); if (VBOX_FAILURE (vrc)) { rc = setError (E_FAIL, tr ("Could not create the communication channel (%Vrc)"), vrc); break; } /* get the path to the executable */ char exePathBuf [RTPATH_MAX]; char *exePath = RTProcGetExecutableName (exePathBuf, RTPATH_MAX); ComAssertBreak (exePath, E_FAIL); Utf8Str argsStr = Utf8StrFmt ("/Helper %s", client.name().raw()); LogFlowFunc (("Starting '\"%s\" %s'...\n", exePath, argsStr.raw())); RTPROCESS pid = NIL_RTPROCESS; if (d->privileged) { /* Attempt to start a privileged process using the Run As dialog */ Bstr file = exePath; Bstr parameters = argsStr; SHELLEXECUTEINFO shExecInfo; shExecInfo.cbSize = sizeof (SHELLEXECUTEINFO); shExecInfo.fMask = NULL; shExecInfo.hwnd = NULL; shExecInfo.lpVerb = L"runas"; shExecInfo.lpFile = file; shExecInfo.lpParameters = parameters; shExecInfo.lpDirectory = NULL; shExecInfo.nShow = SW_NORMAL; shExecInfo.hInstApp = NULL; if (!ShellExecuteEx (&shExecInfo)) { int vrc2 = RTErrConvertFromWin32 (GetLastError()); /* hide excessive details in case of a frequent error * (pressing the Cancel button to close the Run As dialog) */ if (vrc2 == VERR_CANCELLED) rc = setError (E_FAIL, tr ("Operatiion cancelled by the user")); else rc = setError (E_FAIL, tr ("Could not launch a privileged process '%s' (%Vrc)"), exePath, vrc2); break; } } else { const char *args[] = { exePath, "/Helper", client.name(), 0 }; vrc = RTProcCreate (exePath, args, NULL, 0, &pid); if (VBOX_FAILURE (vrc)) { rc = setError (E_FAIL, tr ("Could not launch a process '%s' (%Vrc)"), exePath, vrc); break; } } /* wait for the client to connect */ vrc = client.connect(); if (VBOX_SUCCESS (vrc)) { /* start the user supplied function */ rc = d->func (&client, d->progress, d->user, &vrc); userFuncCalled = true; } /* send the termination signal to the process anyway */ { int vrc2 = client.write (SVCHlpMsg::Null); if (VBOX_SUCCESS (vrc)) vrc = vrc2; } if (SUCCEEDED (rc) && VBOX_FAILURE (vrc)) { rc = setError (E_FAIL, tr ("Could not operate the communication channel (%Vrc)"), vrc); break; } } while (0); if (FAILED (rc) && !userFuncCalled) { /* call the user function in the "cleanup only" mode * to let it free resources passed to in aUser */ d->func (NULL, NULL, d->user, NULL); } d->progress->notifyComplete (rc); LogFlowFuncLeave(); return 0; } #endif /* __WIN__ */ /** * Sends a signal to the client watcher thread to rescan the set of machines * that have open sessions. * * @note Doesn't lock anything. */ void VirtualBox::updateClientWatcher() { AutoCaller autoCaller (this); AssertComRCReturn (autoCaller.rc(), (void) 0); AssertReturn (mWatcherData.mThread != NIL_RTTHREAD, (void) 0); /* sent an update request */ #if defined(__WIN__) ::SetEvent (mWatcherData.mUpdateReq); #else RTSemEventSignal (mWatcherData.mUpdateReq); #endif } /** * Adds the given child process ID to the list of processes to be reaped. * This call should be followed by #updateClientWatcher() to take the effect. */ void VirtualBox::addProcessToReap (RTPROCESS pid) { AutoCaller autoCaller (this); AssertComRCReturn (autoCaller.rc(), (void) 0); /// @todo (dmik) Win32? #ifndef __WIN__ AutoLock alock (this); mWatcherData.mProcesses.push_back (pid); #endif } /** Event for onMachineStateChange(), onMachineDataChange(), onMachineRegistered() */ struct MachineEvent : public VirtualBox::CallbackEvent { enum What { DataChanged, StateChanged, Registered }; MachineEvent (VirtualBox *aVB, const Guid &aId) : CallbackEvent (aVB), what (DataChanged), id (aId) {} MachineEvent (VirtualBox *aVB, const Guid &aId, MachineState_T aState) : CallbackEvent (aVB), what (StateChanged), id (aId) , state (aState) {} MachineEvent (VirtualBox *aVB, const Guid &aId, BOOL aRegistered) : CallbackEvent (aVB), what (Registered), id (aId) , registered (aRegistered) {} void handleCallback (const ComPtr &aCallback) { switch (what) { case DataChanged: LogFlow (("OnMachineDataChange: id={%Vuuid}\n", id.ptr())); aCallback->OnMachineDataChange (id); break; case StateChanged: LogFlow (("OnMachineStateChange: id={%Vuuid}, state=%d\n", id.ptr(), state)); aCallback->OnMachineStateChange (id, state); break; case Registered: LogFlow (("OnMachineRegistered: id={%Vuuid}, registered=%d\n", id.ptr(), registered)); aCallback->OnMachineRegistered (id, registered); break; } } const What what; Guid id; MachineState_T state; BOOL registered; }; /** * @note Doesn't lock any object. */ void VirtualBox::onMachineStateChange (const Guid &id, MachineState_T state) { postEvent (new MachineEvent (this, id, state)); } /** * @note Doesn't lock any object. */ void VirtualBox::onMachineDataChange (const Guid &id) { postEvent (new MachineEvent (this, id)); } /** * @note Locks this object for reading. */ BOOL VirtualBox::onExtraDataCanChange(const Guid &id, INPTR BSTR key, INPTR BSTR value) { LogFlowThisFunc(("machine={%s} key={%ls} val={%ls}\n", id.toString().raw(), key, value)); CallbackList list; { AutoReaderLock alock (this); list = mData.mCallbacks; } BOOL allowChange = true; CallbackList::iterator it = list.begin(); while ((it != list.end()) && allowChange) { HRESULT rc = (*it++)->OnExtraDataCanChange(id, key, value, &allowChange); if (FAILED (rc)) { /* * if a call to this method fails for some reason (for ex., because * the other side is dead), we ensure allowChange stays true * (MS COM RPC implementation seems to zero all output vars before * issuing an IPC call or after a failure, so it's essential there) */ allowChange = true; } } LogFlowThisFunc (("allowChange=%d\n", allowChange)); return allowChange; } /** Event for onExtraDataChange() */ struct ExtraDataEvent : public VirtualBox::CallbackEvent { ExtraDataEvent (VirtualBox *aVB, const Guid &aMachineId, INPTR BSTR aKey, INPTR BSTR aVal) : CallbackEvent (aVB), machineId (aMachineId) , key (aKey), val (aVal) {} void handleCallback (const ComPtr &aCallback) { LogFlow (("OnExtraDataChange: machineId={%Vuuid}, key='%ls', val='%ls'\n", machineId.ptr(), key.raw(), val.raw())); aCallback->OnExtraDataChange (machineId, key, val); } Guid machineId; Bstr key, val; }; /** * @note Doesn't lock any object. */ void VirtualBox::onExtraDataChange (const Guid &id, INPTR BSTR key, INPTR BSTR value) { postEvent (new ExtraDataEvent (this, id, key, value)); } /** * @note Doesn't lock any object. */ void VirtualBox::onMachineRegistered (const Guid &aId, BOOL aRegistered) { postEvent (new MachineEvent (this, aId, aRegistered)); } /** Event for onSessionStateChange() */ struct SessionEvent : public VirtualBox::CallbackEvent { SessionEvent (VirtualBox *aVB, const Guid &aMachineId, SessionState_T aState) : CallbackEvent (aVB), machineId (aMachineId), sessionState (aState) {} void handleCallback (const ComPtr &aCallback) { LogFlow (("OnSessionStateChange: machineId={%Vuuid}, sessionState=%d\n", machineId.ptr(), sessionState)); aCallback->OnSessionStateChange (machineId, sessionState); } Guid machineId; SessionState_T sessionState; }; /** * @note Doesn't lock any object. */ void VirtualBox::onSessionStateChange (const Guid &id, SessionState_T state) { postEvent (new SessionEvent (this, id, state)); } /** Event for onSnapshotTaken(), onSnapshotRemoved() and onSnapshotChange() */ struct SnapshotEvent : public VirtualBox::CallbackEvent { enum What { Taken, Discarded, Changed }; SnapshotEvent (VirtualBox *aVB, const Guid &aMachineId, const Guid &aSnapshotId, What aWhat) : CallbackEvent (aVB) , what (aWhat) , machineId (aMachineId), snapshotId (aSnapshotId) {} void handleCallback (const ComPtr &aCallback) { switch (what) { case Taken: LogFlow (("OnSnapshotTaken: machineId={%Vuuid}, snapshotId={%Vuuid}\n", machineId.ptr(), snapshotId.ptr())); aCallback->OnSnapshotTaken (machineId, snapshotId); break; case Discarded: LogFlow (("OnSnapshotDiscarded: machineId={%Vuuid}, snapshotId={%Vuuid}\n", machineId.ptr(), snapshotId.ptr())); aCallback->OnSnapshotDiscarded (machineId, snapshotId); break; case Changed: LogFlow (("OnSnapshotChange: machineId={%Vuuid}, snapshotId={%Vuuid}\n", machineId.ptr(), snapshotId.ptr())); aCallback->OnSnapshotChange (machineId, snapshotId); break; } } const What what; Guid machineId; Guid snapshotId; }; /** * @note Doesn't lock any object. */ void VirtualBox::onSnapshotTaken (const Guid &aMachineId, const Guid &aSnapshotId) { postEvent (new SnapshotEvent (this, aMachineId, aSnapshotId, SnapshotEvent::Taken)); } /** * @note Doesn't lock any object. */ void VirtualBox::onSnapshotDiscarded (const Guid &aMachineId, const Guid &aSnapshotId) { postEvent (new SnapshotEvent (this, aMachineId, aSnapshotId, SnapshotEvent::Discarded)); } /** * @note Doesn't lock any object. */ void VirtualBox::onSnapshotChange (const Guid &aMachineId, const Guid &aSnapshotId) { postEvent (new SnapshotEvent (this, aMachineId, aSnapshotId, SnapshotEvent::Changed)); } /** * @note Locks this object for reading. */ ComPtr VirtualBox::getUnknownOSType() { ComPtr type; AutoCaller autoCaller (this); AssertComRCReturn (autoCaller.rc(), type); AutoReaderLock alock (this); ComAssertRet (mData.mGuestOSTypes.size() > 0, type); type = mData.mGuestOSTypes.front(); return type; } /** * Returns the list of opened machines (i.e. machines having direct sessions * opened by client processes). * * @note the returned list contains smart pointers. So, clear it as soon as * it becomes no more necessary to release instances. * @note it can be possible that a session machine from the list has been * already uninitialized, so a) lock the instance and b) chheck for * instance->isReady() return value before manipulating the object directly * (i.e. not through COM methods). * * @note Locks objects for reading. */ void VirtualBox::getOpenedMachines (SessionMachineVector &aVector) { AutoCaller autoCaller (this); AssertComRCReturn (autoCaller.rc(), (void) 0); std::list > list; { AutoReaderLock alock (this); for (MachineList::iterator it = mData.mMachines.begin(); it != mData.mMachines.end(); ++ it) { ComObjPtr sm = (*it)->sessionMachine(); /* SessionMachine is null when there are no open sessions */ if (!sm.isNull()) list.push_back (sm); } } aVector = SessionMachineVector (list.begin(), list.end()); return; } /** * Helper to find machines that use the given DVD image. * * @param machineIDs string where to store the list (can be NULL) * @return TRUE if at least one machine found and false otherwise * * @note For now, we just scan all the machines. We can optimize this later * if required by adding the corresponding field to DVDImage and requiring all * IDVDImage instances to be DVDImage objects. * * @note Locks objects for reading. */ BOOL VirtualBox::getDVDImageUsage (const Guid &id, ResourceUsage_T usage, Bstr *machineIDs) { AutoCaller autoCaller (this); AssertComRCReturn (autoCaller.rc(), FALSE); typedef std::set Set; Set idSet; { AutoReaderLock alock (this); for (MachineList::const_iterator mit = mData.mMachines.begin(); mit != mData.mMachines.end(); ++ mit) { /// @todo (dmik) move this part to Machine for better incapsulation ComObjPtr m = *mit; AutoReaderLock malock (m); /* take the session machine when appropriate */ if (!m->data()->mSession.mMachine.isNull()) m = m->data()->mSession.mMachine; const ComObjPtr &dvd = m->dvdDrive(); AutoReaderLock dalock (dvd); /* loop over the backed up (permanent) and current (temporary) dvd data */ DVDDrive::Data *dvdData [2]; if (dvd->data().isBackedUp()) { dvdData [0] = dvd->data().backedUpData(); dvdData [1] = dvd->data().data(); } else { dvdData [0] = dvd->data().data(); dvdData [1] = NULL; } if (!(usage & ResourceUsage_PermanentUsage)) dvdData [0] = NULL; if (!(usage & ResourceUsage_TemporaryUsage)) dvdData [1] = NULL; for (unsigned i = 0; i < ELEMENTS (dvdData); i++) { if (dvdData [i]) { if (dvdData [i]->mDriveState == DriveState_ImageMounted) { Guid iid; dvdData [i]->mDVDImage->COMGETTER(Id) (iid.asOutParam()); if (iid == id) idSet.insert (m->data()->mUuid); } } } } } if (machineIDs) { if (!idSet.empty()) { /* convert to a string of UUIDs */ char *idList = (char *) RTMemTmpAllocZ (RTUUID_STR_LENGTH * idSet.size()); char *idListPtr = idList; for (Set::iterator it = idSet.begin(); it != idSet.end(); ++ it) { RTUuidToStr (*it, idListPtr, RTUUID_STR_LENGTH); idListPtr += RTUUID_STR_LENGTH - 1; /* replace EOS with a space char */ *(idListPtr ++) = ' '; } Assert (int (idListPtr - idList) == int (RTUUID_STR_LENGTH * idSet.size())); /* remove the trailing space */ *(-- idListPtr) = 0; /* copy the string */ *machineIDs = idList; RTMemTmpFree (idList); } else { (*machineIDs).setNull(); } } return !idSet.empty(); } /** * Helper to find machines that use the given floppy image. * * @param machineIDs string where to store the list (can be NULL) * @return TRUE if at least one machine found and false otherwise * * @note For now, we just scan all the machines. We can optimize this later * if required by adding the corresponding field to FloppyImage and requiring all * IFloppyImage instances to be FloppyImage objects. * * @note Locks objects for reading. */ BOOL VirtualBox::getFloppyImageUsage (const Guid &id, ResourceUsage_T usage, Bstr *machineIDs) { AutoCaller autoCaller (this); AssertComRCReturn (autoCaller.rc(), FALSE); typedef std::set Set; Set idSet; { AutoReaderLock alock (this); for (MachineList::const_iterator mit = mData.mMachines.begin(); mit != mData.mMachines.end(); ++ mit) { /// @todo (dmik) move this part to Machine for better incapsulation ComObjPtr m = *mit; AutoReaderLock malock (m); /* take the session machine when appropriate */ if (!m->data()->mSession.mMachine.isNull()) m = m->data()->mSession.mMachine; const ComObjPtr &drv = m->floppyDrive(); AutoReaderLock dalock (drv); /* loop over the backed up (permanent) and current (temporary) floppy data */ FloppyDrive::Data *data [2]; if (drv->data().isBackedUp()) { data [0] = drv->data().backedUpData(); data [1] = drv->data().data(); } else { data [0] = drv->data().data(); data [1] = NULL; } if (!(usage & ResourceUsage_PermanentUsage)) data [0] = NULL; if (!(usage & ResourceUsage_TemporaryUsage)) data [1] = NULL; for (unsigned i = 0; i < ELEMENTS (data); i++) { if (data [i]) { if (data [i]->mDriveState == DriveState_ImageMounted) { Guid iid; data [i]->mFloppyImage->COMGETTER(Id) (iid.asOutParam()); if (iid == id) idSet.insert (m->data()->mUuid); } } } } } if (machineIDs) { if (!idSet.empty()) { /* convert to a string of UUIDs */ char *idList = (char *) RTMemTmpAllocZ (RTUUID_STR_LENGTH * idSet.size()); char *idListPtr = idList; for (Set::iterator it = idSet.begin(); it != idSet.end(); ++ it) { RTUuidToStr (*it, idListPtr, RTUUID_STR_LENGTH); idListPtr += RTUUID_STR_LENGTH - 1; /* replace EOS with a space char */ *(idListPtr ++) = ' '; } Assert (int (idListPtr - idList) == int (RTUUID_STR_LENGTH * idSet.size())); /* remove the trailing space */ *(-- idListPtr) = 0; /* copy the string */ *machineIDs = idList; RTMemTmpFree (idList); } else { (*machineIDs).setNull(); } } return !idSet.empty(); } /** * Tries to calculate the relative path of the given absolute path using the * directory of the VirtualBox settings file as the base directory. * * @param aPath absolute path to calculate the relative path for * @param aResult where to put the result (used only when it's possible to * make a relative path from the given absolute path; * otherwise left untouched) * * @note Doesn't lock any object. */ void VirtualBox::calculateRelativePath (const char *aPath, Utf8Str &aResult) { AutoCaller autoCaller (this); AssertComRCReturn (autoCaller.rc(), (void) 0); /* no need to lock since mHomeDir is const */ Utf8Str settingsDir = mData.mHomeDir; if (RTPathStartsWith (aPath, settingsDir)) { /* when assigning, we create a separate Utf8Str instance because both * aPath and aResult can point to the same memory location when this * func is called (if we just do aResult = aPath, aResult will be freed * first, and since its the same as aPath, an attempt to copy garbage * will be made. */ aResult = Utf8Str (aPath + settingsDir.length() + 1); } } // private methods ///////////////////////////////////////////////////////////////////////////// /** * Searches for a Machine object with the given ID in the collection * of registered machines. * * @param id * ID of the machine * @param doSetError * if TRUE, the appropriate error info is set in case when the machine * is not found * @param machine * where to store the found machine object (can be NULL) * * @return * S_OK when found or E_INVALIDARG when not found * * @note Locks this object for reading. */ HRESULT VirtualBox::findMachine (const Guid &aId, bool aSetError, ComObjPtr *aMachine /* = NULL */) { AutoCaller autoCaller (this); AssertComRCReturn (autoCaller.rc(), autoCaller.rc()); bool found = false; { AutoReaderLock alock (this); for (MachineList::iterator it = mData.mMachines.begin(); !found && it != mData.mMachines.end(); ++ it) { /* mUuid is constant, no need to lock */ found = (*it)->data()->mUuid == aId; if (found && aMachine) *aMachine = *it; } } HRESULT rc = found ? S_OK : E_INVALIDARG; if (aSetError && !found) { setError (E_INVALIDARG, tr ("Could not find a registered machine with UUID {%Vuuid}"), aId.raw()); } return rc; } /** * Searches for a HardDisk object with the given ID or location specification * in the collection of registered hard disks. If both ID and location are * specified, the first object that matches either of them (not necessarily * both) is returned. * * @param aId ID of the hard disk (NULL when unused) * @param aLocation full location specification (NULL when unused) * @param aSetError if TRUE, the appropriate error info is set in case when * the disk is not found and only one search criteria (ID * or file name) is specified. * @param aHardDisk where to store the found hard disk object (can be NULL) * * @return * S_OK when found or E_INVALIDARG when not found * * @note Locks objects for reading! */ HRESULT VirtualBox:: findHardDisk (const Guid *aId, const BSTR aLocation, bool aSetError, ComObjPtr *aHardDisk /* = NULL */) { ComAssertRet (aId || aLocation, E_INVALIDARG); AutoReaderLock alock (this); /* first lookup the map by UUID if UUID is provided */ if (aId) { HardDiskMap::const_iterator it = mData.mHardDiskMap.find (*aId); if (it != mData.mHardDiskMap.end()) { if (aHardDisk) *aHardDisk = (*it).second; return S_OK; } } /* then iterate and find by location */ bool found = false; if (aLocation) { Utf8Str location = aLocation; for (HardDiskMap::const_iterator it = mData.mHardDiskMap.begin(); !found && it != mData.mHardDiskMap.end(); ++ it) { const ComObjPtr &hd = (*it).second; AutoReaderLock hdLock (hd); if (hd->storageType() == HardDiskStorageType_VirtualDiskImage || hd->storageType() == HardDiskStorageType_VMDKImage) { /* locations of VDI and VMDK hard disks for now are just * file paths */ found = RTPathCompare (location, Utf8Str (hd->toString (false /* aShort */))) == 0; } else { found = aLocation == hd->toString (false /* aShort */); } if (found && aHardDisk) *aHardDisk = hd; } } HRESULT rc = found ? S_OK : E_INVALIDARG; if (aSetError && !found) { if (aId && !aLocation) setError (rc, tr ("Could not find a registered hard disk " "with UUID {%Vuuid}"), aId->raw()); else if (aLocation && !aId) setError (rc, tr ("Could not find a registered hard disk " "with location '%ls'"), aLocation); } return rc; } /** * @deprecated Use #findHardDisk() instead. * * Searches for a HVirtualDiskImage object with the given ID or file path in the * collection of registered hard disks. If both ID and file path are specified, * the first object that matches either of them (not necessarily both) * is returned. * * @param aId ID of the hard disk (NULL when unused) * @param filePathFull full path to the image file (NULL when unused) * @param aSetError if TRUE, the appropriate error info is set in case when * the disk is not found and only one search criteria (ID * or file name) is specified. * @param aHardDisk where to store the found hard disk object (can be NULL) * * @return * S_OK when found or E_INVALIDARG when not found * * @note Locks objects for reading! */ HRESULT VirtualBox:: findVirtualDiskImage (const Guid *aId, const BSTR aFilePathFull, bool aSetError, ComObjPtr *aImage /* = NULL */) { ComAssertRet (aId || aFilePathFull, E_INVALIDARG); AutoReaderLock alock (this); /* first lookup the map by UUID if UUID is provided */ if (aId) { HardDiskMap::const_iterator it = mData.mHardDiskMap.find (*aId); if (it != mData.mHardDiskMap.end()) { AutoReaderLock hdLock ((*it).second); if ((*it).second->storageType() == HardDiskStorageType_VirtualDiskImage) { if (aImage) *aImage = (*it).second->asVDI(); return S_OK; } } } /* then iterate and find by name */ bool found = false; if (aFilePathFull) { for (HardDiskMap::const_iterator it = mData.mHardDiskMap.begin(); !found && it != mData.mHardDiskMap.end(); ++ it) { const ComObjPtr &hd = (*it).second; AutoReaderLock hdLock (hd); if (hd->storageType() != HardDiskStorageType_VirtualDiskImage) continue; found = RTPathCompare (Utf8Str (aFilePathFull), Utf8Str (hd->asVDI()->filePathFull())) == 0; if (found && aImage) *aImage = hd->asVDI(); } } HRESULT rc = found ? S_OK : E_INVALIDARG; if (aSetError && !found) { if (aId && !aFilePathFull) setError (rc, tr ("Could not find a registered VDI hard disk " "with UUID {%Vuuid}"), aId->raw()); else if (aFilePathFull && !aId) setError (rc, tr ("Could not find a registered VDI hard disk " "with the file path '%ls'"), aFilePathFull); } return rc; } /** * Searches for a DVDImage object with the given ID or file path in the * collection of registered DVD images. If both ID and file path are specified, * the first object that matches either of them (not necessarily both) * is returned. * * @param id * ID of the DVD image (unused when NULL) * @param filePathFull * full path to the image file (unused when NULL) * @param aSetError * if TRUE, the appropriate error info is set in case when the image is not * found and only one search criteria (ID or file name) is specified. * @param dvdImage * where to store the found DVD image object (can be NULL) * * @return * S_OK when found or E_INVALIDARG when not found * * @note Locks this object for reading. */ HRESULT VirtualBox::findDVDImage (const Guid *aId, const BSTR aFilePathFull, bool aSetError, ComObjPtr *aImage /* = NULL */) { ComAssertRet (aId || aFilePathFull, E_INVALIDARG); bool found = false; { AutoReaderLock alock (this); for (DVDImageList::const_iterator it = mData.mDVDImages.begin(); !found && it != mData.mDVDImages.end(); ++ it) { /* DVDImage fields are constant, so no need to lock */ found = (aId && (*it)->id() == *aId) || (aFilePathFull && RTPathCompare (Utf8Str (aFilePathFull), Utf8Str ((*it)->filePathFull())) == 0); if (found && aImage) *aImage = *it; } } HRESULT rc = found ? S_OK : E_INVALIDARG; if (aSetError && !found) { if (aId && !aFilePathFull) setError (rc, tr ("Could not find a registered CD/DVD image " "with UUID {%s}"), aId->toString().raw()); else if (aFilePathFull && !aId) setError (rc, tr ("Could not find a registered CD/DVD image " "with the file path '%ls'"), aFilePathFull); } return rc; } /** * Searches for a FloppyImage object with the given ID or file path in the * collection of registered floppy images. If both ID and file path are specified, * the first object that matches either of them (not necessarily both) * is returned. * * @param aId * ID of the floppy image (unused when NULL) * @param aFilePathFull * full path to the image file (unused when NULL) * @param aSetError * if TRUE, the appropriate error info is set in case when the image is not * found and only one search criteria (ID or file name) is specified. * @param aImage * where to store the found floppy image object (can be NULL) * * @return * S_OK when found or E_INVALIDARG when not found * * @note Locks this object for reading. */ HRESULT VirtualBox::findFloppyImage (const Guid *aId, const BSTR aFilePathFull, bool aSetError, ComObjPtr *aImage /* = NULL */) { ComAssertRet (aId || aFilePathFull, E_INVALIDARG); bool found = false; { AutoReaderLock alock (this); for (FloppyImageList::iterator it = mData.mFloppyImages.begin(); !found && it != mData.mFloppyImages.end(); ++ it) { /* FloppyImage fields are constant, so no need to lock */ found = (aId && (*it)->id() == *aId) || (aFilePathFull && RTPathCompare (Utf8Str (aFilePathFull), Utf8Str ((*it)->filePathFull())) == 0); if (found && aImage) *aImage = *it; } } HRESULT rc = found ? S_OK : E_INVALIDARG; if (aSetError && !found) { if (aId && !aFilePathFull) setError (rc, tr ("Could not find a registered floppy image " "with UUID {%s}"), aId->toString().raw()); else if (aFilePathFull && !aId) setError (rc, tr ("Could not find a registered floppy image " "with the file path '%ls'"), aFilePathFull); } return rc; } /** * When \a aHardDisk is not NULL, searches for an object equal to the given * hard disk in the collection of registered hard disks, or, if the given hard * disk is HVirtualDiskImage, for an object with the given file path in the * collection of all registered non-hard disk images (DVDs and floppies). * Other parameters are unused. * * When \a aHardDisk is NULL, searches for an object with the given ID or file * path in the collection of all registered images (VDIs, DVDs and floppies). * If both ID and file path are specified, matching either of them will satisfy * the search. * * If a matching object is found, this method returns E_INVALIDARG and sets the * appropriate error info. Otherwise, S_OK is returned. * * @param aHardDisk hard disk object to check against registered media * (NULL when unused) * @param aId UUID of the media to check (NULL when unused) * @param aFilePathFull full path to the image file (NULL when unused) * * @note Locks objects! */ HRESULT VirtualBox::checkMediaForConflicts (HardDisk *aHardDisk, const Guid *aId, const BSTR aFilePathFull) { AssertReturn (aHardDisk || aId || aFilePathFull, E_FAIL); HRESULT rc = S_OK; AutoReaderLock alock (this); if (aHardDisk) { for (HardDiskMap::const_iterator it = mData.mHardDiskMap.begin(); it != mData.mHardDiskMap.end(); ++ it) { const ComObjPtr &hd = (*it).second; if (hd->sameAs (aHardDisk)) return setError (E_INVALIDARG, tr ("A hard disk with UUID {%Vuuid} or with the same properties " "('%ls') is already registered"), aHardDisk->id().raw(), aHardDisk->toString().raw()); } aId = &aHardDisk->id(); if (aHardDisk->storageType() == HardDiskStorageType_VirtualDiskImage) #if defined(__WIN__) /// @todo (dmik) stupid BSTR declaration lacks the BCSTR counterpart const_cast (aFilePathFull) = aHardDisk->asVDI()->filePathFull(); #else aFilePathFull = aHardDisk->asVDI()->filePathFull(); #endif } bool found = false; if (aId || aFilePathFull) do { if (!aHardDisk) { rc = findHardDisk (aId, aFilePathFull, false /* aSetError */); found = SUCCEEDED (rc); if (found) break; } rc = findDVDImage (aId, aFilePathFull, false /* aSetError */); found = SUCCEEDED (rc); if (found) break; rc = findFloppyImage (aId, aFilePathFull, false /* aSetError */); found = SUCCEEDED (rc); if (found) break; } while (0); if (found) { if (aId && !aFilePathFull) rc = setError (E_INVALIDARG, tr ("A disk image with UUID {%Vuuid} is already registered"), aId->raw()); else if (aFilePathFull && !aId) rc = setError (E_INVALIDARG, tr ("A disk image with file path '%ls' is already registered"), aFilePathFull); else rc = setError (E_INVALIDARG, tr ("A disk image with UUID {%Vuuid} or file path '%ls' " "is already registered"), aId->raw(), aFilePathFull); } else rc = S_OK; return rc; } /** * Reads in the machine definitions from the configuration loader * and creates the relevant objects. * * @note Can be called only from #init(). * @note Doesn't lock anything. */ HRESULT VirtualBox::loadMachines (CFGNODE aGlobal) { AutoCaller autoCaller (this); AssertReturn (autoCaller.state() == InInit, E_FAIL); HRESULT rc = S_OK; CFGNODE machineRegistry = 0; unsigned count = 0; CFGLDRGetChildNode (aGlobal, "MachineRegistry", 0, &machineRegistry); Assert (machineRegistry); CFGLDRCountChildren(machineRegistry, "MachineEntry", &count); for (unsigned i = 0; i < count && SUCCEEDED (rc); i++) { CFGNODE vm = 0; CFGLDRGetChildNode(machineRegistry, "MachineEntry", i, &vm); /* get the UUID */ Guid uuid; CFGLDRQueryUUID(vm, "uuid", uuid.ptr()); /* get the machine configuration file name */ Bstr src; CFGLDRQueryBSTR(vm, "src", src.asOutParam()); /* create a new object */ ComObjPtr machine; rc = machine.createObject(); if (SUCCEEDED (rc)) { /* initialize the machine object and register it */ rc = machine->init (this, src, Machine::Init_Registered, NULL, FALSE, &uuid); if (SUCCEEDED (rc)) rc = registerMachine (machine); } CFGLDRReleaseNode(vm); } CFGLDRReleaseNode(machineRegistry); return rc; } /** * Reads in the disk registration entries from the global settings file * and creates the relevant objects * * @param aGlobal node * * @note Can be called only from #init(). * @note Doesn't lock anything. */ HRESULT VirtualBox::loadDisks (CFGNODE aGlobal) { AutoCaller autoCaller (this); AssertReturn (autoCaller.state() == InInit, E_FAIL); HRESULT rc = S_OK; CFGNODE registryNode = 0; CFGLDRGetChildNode (aGlobal, "DiskRegistry", 0, ®istryNode); ComAssertRet (registryNode, E_FAIL); const char *ImagesNodes[] = { "HardDisks", "DVDImages", "FloppyImages" }; for (size_t node = 0; node < ELEMENTS (ImagesNodes) && SUCCEEDED (rc); node ++) { CFGNODE imagesNode = 0; CFGLDRGetChildNode (registryNode, ImagesNodes [node], 0, &imagesNode); // all three nodes are optional if (!imagesNode) continue; if (node == 0) // HardDisks node rc = loadHardDisks (imagesNode); else { unsigned count = 0; CFGLDRCountChildren (imagesNode, "Image", &count); for (unsigned i = 0; i < count && SUCCEEDED (rc); ++ i) { CFGNODE imageNode = 0; CFGLDRGetChildNode (imagesNode, "Image", i, &imageNode); ComAssertBreak (imageNode, rc = E_FAIL); Guid uuid; // uuid (required) CFGLDRQueryUUID (imageNode, "uuid", uuid.ptr()); Bstr src; // source (required) CFGLDRQueryBSTR (imageNode, "src", src.asOutParam()); switch (node) { case 1: // DVDImages { ComObjPtr dvdImage; dvdImage.createObject(); rc = dvdImage->init (this, src, TRUE /* isRegistered */, uuid); if (SUCCEEDED (rc)) rc = registerDVDImage (dvdImage, TRUE /* aOnStartUp */); break; } case 2: // FloppyImages { ComObjPtr floppyImage; floppyImage.createObject(); rc = floppyImage->init (this, src, TRUE /* isRegistered */, uuid); if (SUCCEEDED (rc)) rc = registerFloppyImage (floppyImage, TRUE /* aOnStartUp */); break; } default: AssertFailed(); } CFGLDRReleaseNode (imageNode); } } CFGLDRReleaseNode (imagesNode); } CFGLDRReleaseNode (registryNode); return rc; } /** * Loads all hard disks from the given node. * Note that all loaded hard disks register themselves within this VirtualBox. * * @param aNode node * * @note Can be called only from #init(). * @note Doesn't lock anything. */ HRESULT VirtualBox::loadHardDisks (CFGNODE aNode) { AutoCaller autoCaller (this); AssertReturn (autoCaller.state() == InInit, E_FAIL); AssertReturn (aNode, E_INVALIDARG); HRESULT rc = S_OK; unsigned count = 0; CFGLDRCountChildren (aNode, "HardDisk", &count); for (unsigned i = 0; i < count && SUCCEEDED (rc); ++ i) { CFGNODE hdNode = 0; CFGLDRGetChildNode (aNode, "HardDisk", i, &hdNode); ComAssertBreak (hdNode, rc = E_FAIL); { CFGNODE storageNode = 0; // detect the type of the hard disk // (either one of HVirtualDiskImage, HISCSIHardDisk or HPhysicalVolume do { CFGLDRGetChildNode (hdNode, "VirtualDiskImage", 0, &storageNode); if (storageNode) { ComObjPtr vdi; vdi.createObject(); rc = vdi->init (this, NULL, hdNode, storageNode); break; } CFGLDRGetChildNode (hdNode, "ISCSIHardDisk", 0, &storageNode); if (storageNode) { ComObjPtr iscsi; iscsi.createObject(); rc = iscsi->init (this, hdNode, storageNode); break; } CFGLDRGetChildNode (hdNode, "VMDKImage", 0, &storageNode); if (storageNode) { ComObjPtr vmdk; vmdk.createObject(); rc = vmdk->init (this, NULL, hdNode, storageNode); break; } /// @todo (dmik) later // CFGLDRGetChildNode (hdNode, "PhysicalVolume", 0, &storageNode); // if (storageNode) // { // break; // } ComAssertMsgFailedBreak (("No valid hard disk storage node!\n"), rc = E_FAIL); } while (0); if (storageNode) CFGLDRReleaseNode (storageNode); } CFGLDRReleaseNode (hdNode); } return rc; } /** * Helper function to write out the configuration to XML. * * @note Locks objects! */ HRESULT VirtualBox::saveConfig() { AutoCaller autoCaller (this); AssertComRCReturn (autoCaller.rc(), autoCaller.rc()); ComAssertRet (!!mData.mCfgFile.mName, E_FAIL); HRESULT rc = S_OK; AutoLock alock (this); CFGHANDLE configLoader; /* load the config file */ int vrc = CFGLDRLoad (&configLoader, Utf8Str (mData.mCfgFile.mName), mData.mCfgFile.mHandle, XmlSchemaNS, true, cfgLdrEntityResolver, NULL); ComAssertRCRet (vrc, E_FAIL); const char * Global = "VirtualBox/Global"; CFGNODE global; vrc = CFGLDRGetNode(configLoader, Global, 0, &global); if (VBOX_FAILURE (vrc)) CFGLDRCreateNode (configLoader, Global, &global); do { ComAssertBreak (global, rc = E_FAIL); /* machines */ do { const char *Registry = "MachineRegistry"; const char *Entry = "MachineEntry"; CFGNODE registryNode = NULL; /* first, delete the entire machine registry */ if (VBOX_SUCCESS (CFGLDRGetChildNode (global, Registry, 0, ®istryNode))) CFGLDRDeleteNode (registryNode); /* then, recreate it */ CFGLDRCreateChildNode (global, Registry, ®istryNode); /* write out the machines */ for (MachineList::iterator it = mData.mMachines.begin(); it != mData.mMachines.end(); ++ it) { /// @todo (dmik) move this part to Machine for better incapsulation ComObjPtr m = *it; AutoReaderLock machineLock (m); AssertMsg (m->data(), ("Machine data must not be NULL")); CFGNODE entryNode; CFGLDRAppendChildNode (registryNode, Entry, &entryNode); /* UUID + curly brackets */ CFGLDRSetUUID (entryNode, "uuid", unconst (m->data()->mUuid).ptr()); /* source */ CFGLDRSetBSTR (entryNode, "src", m->data()->mConfigFile); /* done */ CFGLDRReleaseNode (entryNode); } CFGLDRReleaseNode (registryNode); } while (0); if (FAILED (rc)) break; /* disk images */ do { CFGNODE registryNode = 0; CFGLDRGetChildNode (global, "DiskRegistry", 0, ®istryNode); /* first, delete the entire disk image registr */ if (registryNode) CFGLDRDeleteNode (registryNode); /* then, recreate it */ CFGLDRCreateChildNode (global, "DiskRegistry", ®istryNode); ComAssertBreak (registryNode, rc = E_FAIL); /* write out the hard disks */ { CFGNODE imagesNode = 0; CFGLDRCreateChildNode (registryNode, "HardDisks", &imagesNode); rc = saveHardDisks (imagesNode); CFGLDRReleaseNode (imagesNode); if (FAILED (rc)) break; } /* write out the CD/DVD images */ { CFGNODE imagesNode = 0; CFGLDRCreateChildNode (registryNode, "DVDImages", &imagesNode); for (DVDImageList::iterator it = mData.mDVDImages.begin(); it != mData.mDVDImages.end(); ++ it) { ComObjPtr dvd = *it; /* no need to lock: fields are constant */ CFGNODE imageNode = 0; CFGLDRAppendChildNode (imagesNode, "Image", &imageNode); CFGLDRSetUUID (imageNode, "uuid", dvd->id()); CFGLDRSetBSTR (imageNode, "src", dvd->filePath()); CFGLDRReleaseNode (imageNode); } CFGLDRReleaseNode (imagesNode); } /* write out the floppy images */ { CFGNODE imagesNode = 0; CFGLDRCreateChildNode (registryNode, "FloppyImages", &imagesNode); for (FloppyImageList::iterator it = mData.mFloppyImages.begin(); it != mData.mFloppyImages.end(); ++ it) { ComObjPtr fd = *it; /* no need to lock: fields are constant */ CFGNODE imageNode = 0; CFGLDRAppendChildNode (imagesNode, "Image", &imageNode); CFGLDRSetUUID (imageNode, "uuid", fd->id()); CFGLDRSetBSTR (imageNode, "src", fd->filePath()); CFGLDRReleaseNode (imageNode); } CFGLDRReleaseNode (imagesNode); } CFGLDRReleaseNode (registryNode); } while (0); if (FAILED (rc)) break; do { /* host data (USB filters) */ rc = mData.mHost->saveSettings (global); if (FAILED (rc)) break; rc = mData.mSystemProperties->saveSettings (global); if (FAILED (rc)) break; } while (0); } while (0); if (global) CFGLDRReleaseNode (global); if (SUCCEEDED (rc)) { char *loaderError = NULL; vrc = CFGLDRSave (configLoader, &loaderError); if (VBOX_FAILURE (vrc)) { rc = setError (E_FAIL, tr ("Could not save the settings file '%ls' (%Vrc)%s%s"), mData.mCfgFile.mName.raw(), vrc, loaderError ? ".\n" : "", loaderError ? loaderError : ""); if (loaderError) RTMemTmpFree (loaderError); } } CFGLDRFree(configLoader); return rc; } /** * Saves all hard disks to the given node. * * @param aNode node * * @note Locks this object for reding. */ HRESULT VirtualBox::saveHardDisks (CFGNODE aNode) { AssertReturn (aNode, E_INVALIDARG); HRESULT rc = S_OK; AutoReaderLock alock (this); for (HardDiskList::const_iterator it = mData.mHardDisks.begin(); it != mData.mHardDisks.end() && SUCCEEDED (rc); ++ it) { ComObjPtr hd = *it; AutoReaderLock hdLock (hd); CFGNODE hdNode = 0; CFGLDRAppendChildNode (aNode, "HardDisk", &hdNode); ComAssertBreak (hdNode, rc = E_FAIL); CFGNODE storageNode = 0; switch (hd->storageType()) { case HardDiskStorageType_VirtualDiskImage: { CFGLDRAppendChildNode (hdNode, "VirtualDiskImage", &storageNode); ComAssertBreak (storageNode, rc = E_FAIL); rc = hd->saveSettings (hdNode, storageNode); break; } case HardDiskStorageType_ISCSIHardDisk: { CFGLDRAppendChildNode (hdNode, "ISCSIHardDisk", &storageNode); ComAssertBreak (storageNode, rc = E_FAIL); rc = hd->saveSettings (hdNode, storageNode); break; } case HardDiskStorageType_VMDKImage: { CFGLDRAppendChildNode (hdNode, "VMDKImage", &storageNode); ComAssertBreak (storageNode, rc = E_FAIL); rc = hd->saveSettings (hdNode, storageNode); break; } /// @todo (dmik) later // case HardDiskStorageType_PhysicalVolume: // { // break; // } } if (storageNode) CFGLDRReleaseNode (storageNode); CFGLDRReleaseNode (hdNode); } return rc; } /** * Helper to register the machine. * * When called during VirtualBox startup, adds the given machine to the * collection of registered machines. Otherwise tries to mark the machine * as registered, and, if succeeded, adds it to the collection and * saves global settings. * * @param aMachine machine to register * * @note Locks objects! */ HRESULT VirtualBox::registerMachine (Machine *aMachine) { ComAssertRet (aMachine, E_INVALIDARG); AutoCaller autoCaller (this); CheckComRCReturnRC (autoCaller.rc()); AutoLock alock (this); ComAssertRet (findMachine (aMachine->data()->mUuid, false /* aDoSetError */, NULL) == E_INVALIDARG, E_FAIL); HRESULT rc = S_OK; if (autoCaller.state() != InInit) { /* Machine::trySetRegistered() will commit and save machine settings */ rc = aMachine->trySetRegistered (TRUE); CheckComRCReturnRC (rc); } /* add to the collection of registered machines */ mData.mMachines.push_back (aMachine); if (autoCaller.state() != InInit) rc = saveConfig(); return rc; } /** * Helper to register the hard disk. * * @param aHardDisk object to register * @param aFlags one of RHD_* values * * @note Locks objects! */ HRESULT VirtualBox::registerHardDisk (HardDisk *aHardDisk, RHD_Flags aFlags) { ComAssertRet (aHardDisk, E_INVALIDARG); AutoCaller autoCaller (this); AssertComRCReturn (autoCaller.rc(), autoCaller.rc()); AutoLock alock (this); HRESULT rc = checkMediaForConflicts (aHardDisk, NULL, NULL); CheckComRCReturnRC (rc); /* mark the hard disk as registered only when registration is external */ if (aFlags == RHD_External) { rc = aHardDisk->trySetRegistered (TRUE); CheckComRCReturnRC (rc); } if (!aHardDisk->parent()) { /* add to the collection of top-level images */ mData.mHardDisks.push_back (aHardDisk); } /* insert to the map of hard disks */ mData.mHardDiskMap .insert (HardDiskMap::value_type (aHardDisk->id(), aHardDisk)); /* save global config file if not on startup */ /// @todo (dmik) optimize later to save only the node if (aFlags != RHD_OnStartUp) rc = saveConfig(); return rc; } /** * Helper to unregister the hard disk. * * If the hard disk is a differencing hard disk and if the unregistration * succeeds, the hard disk image is deleted and the object is uninitialized. * * @param aHardDisk hard disk to unregister * * @note Locks objects! */ HRESULT VirtualBox::unregisterHardDisk (HardDisk *aHardDisk) { AssertReturn (aHardDisk, E_INVALIDARG); AutoCaller autoCaller (this); AssertComRCReturn (autoCaller.rc(), autoCaller.rc()); LogFlowThisFunc (("image='%ls'\n", aHardDisk->toString().raw())); AutoLock alock (this); /* Lock the hard disk to ensure nobody registers it again before we delete * the differencing image (sanity check actually -- should never happen). */ AutoLock hdLock (aHardDisk); /* try to unregister */ HRESULT rc = aHardDisk->trySetRegistered (FALSE); CheckComRCReturnRC (rc); /* remove from the map of hard disks */ mData.mHardDiskMap.erase (aHardDisk->id()); if (!aHardDisk->parent()) { /* non-differencing hard disk: * remove from the collection of top-level hard disks */ mData.mHardDisks.remove (aHardDisk); } else { Assert (aHardDisk->isDifferencing()); /* differencing hard disk: delete (only if the last access check * succeeded) and uninitialize */ if (aHardDisk->asVDI()->lastAccessError().isNull()) rc = aHardDisk->asVDI()->DeleteImage(); aHardDisk->uninit(); } /* save the global config file anyway (already unregistered) */ /// @todo (dmik) optimize later to save only the node HRESULT rc2 = saveConfig(); if (SUCCEEDED (rc)) rc = rc2; return rc; } /** * Helper to unregister the differencing hard disk image. * Resets machine ID of the hard disk (to let the unregistration succeed) * and then calls #unregisterHardDisk(). * * @param aHardDisk differencing hard disk image to unregister * * @note Locks objects! */ HRESULT VirtualBox::unregisterDiffHardDisk (HardDisk *aHardDisk) { AssertReturn (aHardDisk, E_INVALIDARG); AutoCaller autoCaller (this); AssertComRCReturn (autoCaller.rc(), autoCaller.rc()); AutoLock alock (this); /* * Note: it's safe to lock aHardDisk here because the same object * will be locked by #unregisterHardDisk(). */ AutoLock hdLock (aHardDisk); AssertReturn (aHardDisk->isDifferencing(), E_INVALIDARG); /* * deassociate the machine from the hard disk * (otherwise trySetRegistered() will definitely fail) */ aHardDisk->setMachineId (Guid()); return unregisterHardDisk (aHardDisk); } /** * Helper to update the global settings file when the name of some machine * changes so that file and directory renaming occurs. This method ensures * that all affected paths in the disk registry are properly updated. * * @param aOldPath old path (full) * @param aNewPath new path (full) * * @note Locks this object + DVD, Floppy and HardDisk children for writing. */ HRESULT VirtualBox::updateSettings (const char *aOldPath, const char *aNewPath) { LogFlowThisFunc (("aOldPath={%s} aNewPath={%s}\n", aOldPath, aNewPath)); AssertReturn (aOldPath, E_INVALIDARG); AssertReturn (aNewPath, E_INVALIDARG); AutoCaller autoCaller (this); AssertComRCReturn (autoCaller.rc(), autoCaller.rc()); AutoLock alock (this); size_t oldPathLen = strlen (aOldPath); /* check DVD paths */ for (DVDImageList::iterator it = mData.mDVDImages.begin(); it != mData.mDVDImages.end(); ++ it) { ComObjPtr image = *it; /* no need to lock: fields are constant */ Utf8Str path = image->filePathFull(); LogFlowThisFunc (("DVD.fullPath={%s}\n", path.raw())); if (RTPathStartsWith (path, aOldPath)) { Utf8Str newPath = Utf8StrFmt ("%s%s", aNewPath, path.raw() + oldPathLen); path = newPath; calculateRelativePath (path, path); image->updatePath (newPath, path); LogFlowThisFunc (("-> updated: full={%s} rel={%s}\n", newPath.raw(), path.raw())); } } /* check Floppy paths */ for (FloppyImageList::iterator it = mData.mFloppyImages.begin(); it != mData.mFloppyImages.end(); ++ it) { ComObjPtr image = *it; /* no need to lock: fields are constant */ Utf8Str path = image->filePathFull(); LogFlowThisFunc (("Floppy.fullPath={%s}\n", path.raw())); if (RTPathStartsWith (path, aOldPath)) { Utf8Str newPath = Utf8StrFmt ("%s%s", aNewPath, path.raw() + oldPathLen); path = newPath; calculateRelativePath (path, path); image->updatePath (newPath, path); LogFlowThisFunc (("-> updated: full={%s} rel={%s}\n", newPath.raw(), path.raw())); } } /* check HardDisk paths */ for (HardDiskList::const_iterator it = mData.mHardDisks.begin(); it != mData.mHardDisks.end(); ++ it) { (*it)->updatePaths (aOldPath, aNewPath); } HRESULT rc = saveConfig(); return rc; } /** * Helper to register the DVD image. * * @param aImage object to register * @param aOnStartUp whether this method called during VirtualBox init or not * * @return COM status code * * @note Locks objects! */ HRESULT VirtualBox::registerDVDImage (DVDImage *aImage, bool aOnStartUp) { AssertReturn (aImage, E_INVALIDARG); AutoCaller autoCaller (this); AssertComRCReturn (autoCaller.rc(), autoCaller.rc()); AutoLock alock (this); HRESULT rc = checkMediaForConflicts (NULL, &aImage->id(), aImage->filePathFull()); CheckComRCReturnRC (rc); /* add to the collection */ mData.mDVDImages.push_back (aImage); /* save global config file if we're supposed to */ if (!aOnStartUp) rc = saveConfig(); return rc; } /** * Helper to register the floppy image. * * @param aImage object to register * @param aOnStartUp whether this method called during VirtualBox init or not * * @return COM status code * * @note Locks objects! */ HRESULT VirtualBox::registerFloppyImage (FloppyImage *aImage, bool aOnStartUp) { AssertReturn (aImage, E_INVALIDARG); AutoCaller autoCaller (this); AssertComRCReturn (autoCaller.rc(), autoCaller.rc()); AutoLock alock (this); HRESULT rc = checkMediaForConflicts (NULL, &aImage->id(), aImage->filePathFull()); CheckComRCReturnRC (rc); /* add to the collection */ mData.mFloppyImages.push_back (aImage); /* save global config file if we're supposed to */ if (!aOnStartUp) rc = saveConfig(); return rc; } /** * Helper function to create the guest OS type objects and our collection * * @returns COM status code */ HRESULT VirtualBox::registerGuestOSTypes() { AutoCaller autoCaller (this); AssertComRCReturn (autoCaller.rc(), E_FAIL); AssertReturn (autoCaller.state() == InInit, E_FAIL); HRESULT rc = S_OK; // this table represents our os type / string mapping static struct { const char *id; // utf-8 const char *description; // utf-8 const OSType osType; const uint32_t recommendedRAM; const uint32_t recommendedVRAM; const uint32_t recommendedHDD; } OSTypes[] = { /// @todo (dmik) get the list of OS types from the XML schema // NOTE: we assume that unknown is always the first entry! { "unknown", "Other/Unknown", OSTypeUnknown, 64, 4, 2000 }, { "dos", "DOS", OSTypeDOS, 32, 4, 500 }, { "win31", "Windows 3.1", OSTypeWin31, 32, 4, 1000 }, { "win95", "Windows 95", OSTypeWin95, 64, 4, 2000 }, { "win98", "Windows 98", OSTypeWin98, 64, 4, 2000 }, { "winme", "Windows Me", OSTypeWinMe, 64, 4, 4000 }, { "winnt4", "Windows NT 4", OSTypeWinNT4, 128, 4, 2000 }, { "win2k", "Windows 2000", OSTypeWin2k, 168, 4, 4000 }, { "winxp", "Windows XP", OSTypeWinXP, 192, 4, 10000 }, { "win2k3", "Windows Server 2003", OSTypeWin2k3, 256, 4, 20000 }, { "winvista", "Windows Vista", OSTypeWinVista, 512, 4, 20000 }, { "os2warp3", "OS/2 Warp 3", OSTypeOS2Warp3, 48, 4, 1000 }, { "os2warp4", "OS/2 Warp 4", OSTypeOS2Warp4, 64, 4, 2000 }, { "os2warp45", "OS/2 Warp 4.5", OSTypeOS2Warp45, 96, 4, 2000 }, { "linux22", "Linux 2.2", OSTypeLinux22, 64, 4, 2000 }, { "linux24", "Linux 2.4", OSTypeLinux24, 128, 4, 4000 }, { "linux26", "Linux 2.6", OSTypeLinux26, 128, 4, 8000 }, { "freebsd", "FreeBSD", OSTypeFreeBSD, 64, 4, 2000 }, { "openbsd", "OpenBSD", OSTypeOpenBSD, 64, 4, 2000 }, { "netbsd", "NetBSD", OSTypeNetBSD, 64, 4, 2000 }, { "netware", "Netware", OSTypeNetware, 128, 4, 4000 }, { "solaris", "Solaris", OSTypeSolaris, 128, 4, 4000 }, { "l4", "L4", OSTypeL4, 64, 4, 2000 } }; for (uint32_t i = 0; i < ELEMENTS (OSTypes) && SUCCEEDED (rc); i++) { ComObjPtr guestOSTypeObj; rc = guestOSTypeObj.createObject(); if (SUCCEEDED (rc)) { rc = guestOSTypeObj->init (OSTypes[i].id, OSTypes[i].description, OSTypes[i].osType, OSTypes[i].recommendedRAM, OSTypes[i].recommendedVRAM, OSTypes[i].recommendedHDD); if (SUCCEEDED (rc)) mData.mGuestOSTypes.push_back (guestOSTypeObj); } } return rc; } /** * Helper to lock the VirtualBox configuration for write access. * * @note This method is not thread safe (must be called only from #init() * or #uninit()). * * @note If the configuration file is not found, the method returns * S_OK, but subsequent #isConfigLocked() will return FALSE. This is used * in some places to determine the (valid) situation when no config file * exists yet, and therefore a new one should be created from scatch. */ HRESULT VirtualBox::lockConfig() { AutoCaller autoCaller (this); AssertComRCReturn (autoCaller.rc(), autoCaller.rc()); AssertReturn (autoCaller.state() == InInit, E_FAIL); HRESULT rc = S_OK; Assert (!isConfigLocked()); if (!isConfigLocked()) { /* open the associated config file */ int vrc = RTFileOpen (&mData.mCfgFile.mHandle, Utf8Str (mData.mCfgFile.mName), RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE | RTFILE_O_WRITE_THROUGH); if (VBOX_FAILURE (vrc)) { mData.mCfgFile.mHandle = NIL_RTFILE; /* * It is ok if the file is not found, it will be created by * init(). Otherwise return an error. */ if (vrc != VERR_FILE_NOT_FOUND) rc = setError (E_FAIL, tr ("Could not lock the settings file '%ls' (%Vrc)"), mData.mCfgFile.mName.raw(), vrc); } LogFlowThisFunc (("mCfgFile.mName='%ls', mCfgFile.mHandle=%d, rc=%08X\n", mData.mCfgFile.mName.raw(), mData.mCfgFile.mHandle, rc)); } return rc; } /** * Helper to unlock the VirtualBox configuration from write access. * * @note This method is not thread safe (must be called only from #init() * or #uninit()). */ HRESULT VirtualBox::unlockConfig() { AutoCaller autoCaller (this); AssertComRCReturn (autoCaller.rc(), E_FAIL); AssertReturn (autoCaller.state() == InUninit, E_FAIL); HRESULT rc = S_OK; if (isConfigLocked()) { RTFileFlush (mData.mCfgFile.mHandle); RTFileClose (mData.mCfgFile.mHandle); /** @todo flush the directory too. */ mData.mCfgFile.mHandle = NIL_RTFILE; LogFlowThisFunc (("\n")); } return rc; } /** * Thread function that watches the termination of all client processes * that have opened sessions using IVirtualBox::OpenSession() */ // static DECLCALLBACK(int) VirtualBox::clientWatcher (RTTHREAD thread, void *pvUser) { LogFlowFuncEnter(); VirtualBox *that = (VirtualBox *) pvUser; Assert (that); SessionMachineVector machines; int cnt = 0; #if defined(__WIN__) HRESULT hrc = CoInitializeEx (NULL, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE | COINIT_SPEED_OVER_MEMORY); AssertComRC (hrc); /// @todo (dmik) processes reaping! HANDLE *handles = new HANDLE [1]; handles [0] = that->mWatcherData.mUpdateReq; do { AutoCaller autoCaller (that); /* VirtualBox has been early uninitialized, terminate */ if (!autoCaller.isOk()) break; do { /* release the caller to let uninit() ever proceed */ autoCaller.release(); DWORD rc = ::WaitForMultipleObjects (cnt + 1, handles, FALSE, INFINITE); /* * Restore the caller before using VirtualBox. If it fails, this * means VirtualBox is being uninitialized and we must terminate. */ autoCaller.add(); if (!autoCaller.isOk()) break; bool update = false; if (rc == WAIT_OBJECT_0) { /* update event is signaled */ update = true; } else if (rc > WAIT_OBJECT_0 && rc <= (WAIT_OBJECT_0 + cnt)) { /* machine mutex is released */ (machines [rc - WAIT_OBJECT_0 - 1])->checkForDeath(); update = true; } else if (rc > WAIT_ABANDONED_0 && rc <= (WAIT_ABANDONED_0 + cnt)) { /* machine mutex is abandoned due to client process termination */ (machines [rc - WAIT_ABANDONED_0 - 1])->checkForDeath(); update = true; } if (update) { /* obtain a new set of opened machines */ that->getOpenedMachines (machines); cnt = machines.size(); LogFlowFunc (("UPDATE: direct session count = %d\n", cnt)); AssertMsg ((cnt + 1) <= MAXIMUM_WAIT_OBJECTS, ("MAXIMUM_WAIT_OBJECTS reached")); /* renew the set of event handles */ delete [] handles; handles = new HANDLE [cnt + 1]; handles [0] = that->mWatcherData.mUpdateReq; for (int i = 0; i < cnt; i++) handles [i + 1] = (machines [i])->ipcSem(); } } while (true); } while (0); /* delete the set of event handles */ delete [] handles; /* delete the set of opened machines if any */ machines.clear(); ::CoUninitialize(); #else bool need_update = false; do { AutoCaller autoCaller (that); if (!autoCaller.isOk()) break; do { /* release the caller to let uninit() ever proceed */ autoCaller.release(); int rc = RTSemEventWait (that->mWatcherData.mUpdateReq, 500); /* * Restore the caller before using VirtualBox. If it fails, this * means VirtualBox is being uninitialized and we must terminate. */ autoCaller.add(); if (!autoCaller.isOk()) break; if (VBOX_SUCCESS (rc) || need_update) { /* VBOX_SUCCESS (rc) means an update event is signaled */ /* obtain a new set of opened machines */ that->getOpenedMachines (machines); cnt = machines.size(); LogFlowFunc (("UPDATE: direct session count = %d\n", cnt)); } need_update = false; for (int i = 0; i < cnt; i++) need_update |= (machines [i])->checkForDeath(); /* reap child processes */ { AutoLock alock (that); if (that->mWatcherData.mProcesses.size()) { LogFlowFunc (("UPDATE: child process count = %d\n", that->mWatcherData.mProcesses.size())); ClientWatcherData::ProcessList::iterator it = that->mWatcherData.mProcesses.begin(); while (it != that->mWatcherData.mProcesses.end()) { RTPROCESS pid = *it; RTPROCSTATUS status; int vrc = ::RTProcWait (pid, RTPROCWAIT_FLAGS_NOBLOCK, &status); if (vrc == VINF_SUCCESS) { LogFlowFunc (("pid %d (%x) was reaped, " "status=%d, reason=%d\n", pid, pid, status.iStatus, status.enmReason)); it = that->mWatcherData.mProcesses.erase (it); } else { LogFlowFunc (("pid %d (%x) was NOT reaped, vrc=%Vrc\n", pid, pid, vrc)); if (vrc != VERR_PROCESS_RUNNING) { /* remove the process if it is not already running */ it = that->mWatcherData.mProcesses.erase (it); } else ++ it; } } } } } while (true); } while (0); /* delete the set of opened machines if any */ machines.clear(); #endif LogFlowFuncLeave(); return 0; } /** * Thread function that handles custom events posted using #postEvent(). */ // static DECLCALLBACK(int) VirtualBox::asyncEventHandler (RTTHREAD thread, void *pvUser) { LogFlowFuncEnter(); AssertReturn (pvUser, VERR_INVALID_POINTER); // create an event queue for the current thread EventQueue *eventQ = new EventQueue(); AssertReturn (eventQ, VERR_NO_MEMORY); // return the queue to the one who created this thread *(static_cast (pvUser)) = eventQ; // signal that we're ready RTThreadUserSignal (thread); BOOL ok = TRUE; Event *event = NULL; while ((ok = eventQ->waitForEvent (&event)) && event) eventQ->handleEvent (event); AssertReturn (ok, VERR_GENERAL_FAILURE); delete eventQ; LogFlowFuncLeave(); return 0; } //////////////////////////////////////////////////////////////////////////////// /** * Takes the current list of registered callbacks of the managed VirtualBox * instance, and calls #handleCallback() for every callback item from the * list, passing the item as an argument. * * @note Locks the managed VirtualBox object for reading but leaves the lock * before iterating over callbacks and calling their methods. */ void *VirtualBox::CallbackEvent::handler() { if (mVirtualBox.isNull()) return NULL; AutoCaller autoCaller (mVirtualBox); if (!autoCaller.isOk()) { LogWarningFunc (("VirtualBox has been uninitialized (state=%d), " "the callback event is discarded!\n", autoCaller.state())); /* We don't need mVirtualBox any more, so release it */ mVirtualBox.setNull(); return NULL; } CallbackVector callbacks; { /* Make a copy to release the lock before iterating */ AutoReaderLock alock (mVirtualBox); callbacks = CallbackVector (mVirtualBox->mData.mCallbacks.begin(), mVirtualBox->mData.mCallbacks.end()); /* We don't need mVirtualBox any more, so release it */ mVirtualBox.setNull(); } for (VirtualBox::CallbackVector::const_iterator it = callbacks.begin(); it != callbacks.end(); ++ it) handleCallback (*it); return NULL; }